Skip to content

Conversation

@expertdicer
Copy link
Contributor

@expertdicer expertdicer commented Sep 15, 2025

Purpose / Abstract

This PR enable the usage of WNIBI for gas. Modifying the current auth DeductFeeDecorator to fallback to WNIBI when users doesn't have enough unibi in their account.

Related issues:

Key changes

  • Gas payments with WNIBI: Extended the antehandler and account verification logic so transactions can consume gas using WNIBI balances. Both in EVM transactions and Cosmos transactions.
  • Genesis updates: Added wnibi.json and updated localnet/genesis configuration so WNIBI is initialized by default when spinning new localnet.
  • Testing: Expanded unit tests and E2E tests to cover WNIBI gas scenarios, ensuring compatibility and correctness.

…ling

- Added FeegrantKeeper interface in expected_keeper.go for fee management.
- Refactored handler_opts.go to remove unused imports and clean up code.
- Enhanced gas consumption logic in evmante_gas_consume.go to support WNIBI for transaction fees.
- Updated tests in evmante_gas_consume_test.go to validate WNIBI interactions.
- Modified evmante_handler.go to accommodate changes in gas consumption handling.
- Improved account verification in evmante_verify_eth_acc.go to check WNIBI balance.
- Added new tests in evmante_verify_eth_acc_test.go for WNIBI balance checks.
- Updated localnet.sh to initialize WNIBI in the genesis state.
- Created wnibi.json to define WNIBI parameters and initial balances.
- Enhanced setup scripts in evm-e2e/test/setup.ts for multi-account testing.
- Added utility functions in evm-e2e/test/utils.ts for deploying WNIBI contracts.
- Implemented WNIBI gas tests in evm-e2e/test/wnibi_gas.test.ts to validate gas payment scenarios.
- Updated keeper.go to include mock GETH message for EVM operations.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 15, 2025

📝 Walkthrough

Walkthrough

Adds WNIBI/ERC20-backed fee deduction and fallback paths: new DeductFeeDecorator and helpers, Feegrant interface, EVM ante wiring to use AccountKeeper for multi-token gas payment, updated EVM ante verification and tests, localnet WNIBI genesis/script, E2E WNIBI gas test, and a changelog entry.

Changes

Cohort / File(s) Summary
Changelog
CHANGELOG.md
Adds Unreleased entry for "Multi VM Gas Token Flexibility".
Ante integration (non‑EVM)
app/ante.go
Replaces authante.NewDeductFeeDecorator with ante.NewDeductFeeDecorator(..., EvmKeeper, ...) to use app ante implementation.
Ante: fee deduction & interfaces
app/ante/deductfee.go, app/ante/expected_keeper.go, app/ante/handler_opts.go
Adds DeductFeeDecorator, NewDeductFeeDecorator, DeductFees/DeductFeesWithWNIBI helpers, introduces FeegrantKeeper.UseGrantedFees, and removes local TxFeeChecker type.
EVM ante: gas consume wiring
app/evmante/evmante_gas_consume.go, app/evmante/evmante_handler.go
Adds accountKeeper field and parameter to AnteDecEthGasConsume and NewAnteDecEthGasConsume; tries WNIBI deduction (DeductFeesWithWNIBI) when native funds insufficient.
EVM ante tests: gas consume
app/evmante/evmante_gas_consume_test.go
Extends tests for native and WNIBI-funded gas, passes AccountKeeper to constructor, and adds CalcMappingSlot helper to set WNIBI mapping storage.
EVM ante: verify eth account
app/evmante/evmante_verify_eth_acc.go, app/evmante/evmante_verify_eth_acc_test.go
Adds fallback verification using a temporary StateDB + ERC20 balanceOf against CanonicalWnibi when native balance insufficient; adds matching test path.
Localnet WNIBI genesis & script
contrib/scripts/localnet.sh, contrib/wnibi.json
Adds WNIBI genesis artifact and script steps to merge WNIBI auth/evm/bank state, update supply, and set account numbering for localnet.
E2E test harness & utils
evm-e2e/test/setup.ts, evm-e2e/test/utils.ts, evm-e2e/test/wnibi_gas.test.ts
Adds second HD-derived account (account2), getWNIBI helper to reuse deployed contract, and E2E test validating gas payment with WNIBI when native balance is zero.
EVM testing helpers
x/evm/evmtest/tx.go, x/evm/mock_msg.go
Adds ExecuteNibiTransferTo test helper and exported MOCK_GETH_MESSAGE for EVM tests.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant Mempool as Mempool/CheckTx
  participant Ante as AnteChain
  participant DFD as DeductFeeDecorator
  participant AK as AccountKeeper
  participant EK as EvmKeeper
  participant BK as BankKeeper
  participant FK as FeegrantKeeper
  participant FC as FeeCollector

  User->>Mempool: Submit Tx (FeeTx)
  Mempool->>Ante: Run ante-handlers
  Ante->>DFD: Deduct fees
  DFD->>DFD: Validate FeeTx & compute fee/priority
  alt Fee granter present
    DFD->>FK: UseGrantedFees(granter, grantee, fee, msgs)
  end
  alt Native funds sufficient
    DFD->>BK: SendCoins payer -> FC
    DFD-->>Ante: success
  else Fallback to WNIBI
    DFD->>AK: Get fee payer account
    DFD->>EK: Query WNIBI balance via StateDB/ERC20
    DFD->>EK: Transfer WNIBI payer -> FC
    DFD-->>Ante: success or ErrInsufficientFunds
  end
  Ante-->>Mempool: proceed or reject
Loading
sequenceDiagram
  actor User
  participant Verify as VerifyEthAcc (CheckTx)
  participant BK as Bank
  participant EK as EvmKeeper
  participant WN as WNIBI Contract

  User->>Verify: Submit Ethereum Tx
  Verify->>BK: Get native balance(sender)
  alt Native sufficient
    Verify-->>User: ok
  else
    Verify->>EK: Init temp StateDB/EVM (mock message)
    Verify->>WN: balanceOf(sender)
    alt WNIBI balance >= cost
      Verify-->>User: ok
    else
      Verify-->>User: ErrInsufficientFunds
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

x: evm

Suggested reviewers

  • Unique-Divine
  • onikonychev
  • k-yang

Poem

I hop through mempools, swift and spry,
When native coin runs low, I try.
I fetch WNIBI, nibble gas with glee,
Transfer tokens so transactions flee.
Rabbit ledger dancing — fees paid free 🐇

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The description provides a Purpose/Abstract section and detailed breakdown of changes but omits the mandatory “- Closes #” line under the Purpose/Abstract heading required by the repository’s template and instead uses a separate “Related issues” list. Add the bullet “- Closes #2321” (and “- Closes #2322” if appropriate) immediately under the “# Purpose / Abstract” heading to conform with the required template before listing related issues and key changes.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title succinctly captures the primary enhancement—adding flexibility for using multiple gas tokens across VMs—and directly aligns with the changeset’s focus on enabling fallback to WNIBI for gas payments.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch expertdicer/multi-gas-token

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link

codecov bot commented Sep 15, 2025

Codecov Report

❌ Patch coverage is 12.89062% with 223 lines in your changes missing coverage. Please review.
✅ Project coverage is 56.54%. Comparing base (9219a94) to head (d618806).

Files with missing lines Patch % Lines
app/ante/deductfee.go 0.00% 194 Missing ⚠️
x/evm/evmtest/tx.go 0.00% 18 Missing ⚠️
app/evmante/evmante_gas_consume.go 63.15% 5 Missing and 2 partials ⚠️
app/evmante/evmante_verify_eth_acc.go 82.60% 2 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2384      +/-   ##
==========================================
- Coverage   56.95%   56.54%   -0.42%     
==========================================
  Files         335      336       +1     
  Lines       27445    27691     +246     
==========================================
+ Hits        15632    15657      +25     
- Misses      10740    10958     +218     
- Partials     1073     1076       +3     
Files with missing lines Coverage Δ
app/ante.go 76.27% <100.00%> (ø)
app/ante/handler_opts.go 0.00% <ø> (ø)
app/evmante/evmante_handler.go 100.00% <100.00%> (ø)
app/evmante/evmante_verify_eth_acc.go 85.96% <82.60%> (-5.71%) ⬇️
app/evmante/evmante_gas_consume.go 72.03% <63.15%> (-0.35%) ⬇️
x/evm/evmtest/tx.go 50.81% <0.00%> (-4.05%) ⬇️
app/ante/deductfee.go 0.00% <0.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@expertdicer expertdicer marked this pull request as ready for review September 15, 2025 09:06
@expertdicer expertdicer requested a review from a team as a code owner September 15, 2025 09:06
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (15)
CHANGELOG.md (1)

62-62: Changelog entry: add scope and hyphenation for consistency.

Prefer subsystem-scoped, hyphenated entries.

-- [#2384](https://github.com/NibiruChain/nibiru/pull/2384) - feat: Multi VM Gas Token Flexibility
+- [#2384](https://github.com/NibiruChain/nibiru/pull/2384) - feat(ante,evm): Multi-VM gas token flexibility (WNIBI fallback)
x/evm/keeper/keeper.go (1)

9-11: Import alias: prefer gethtypes for clarity; drop unused core if possible.

If core is only used for the interface type in MOCK_GETH_MESSAGE, keep it; otherwise remove.

-	"github.com/ethereum/go-ethereum/core"
-	gethcore "github.com/ethereum/go-ethereum/core/types"
+	"github.com/ethereum/go-ethereum/core"
+	gethcore "github.com/ethereum/go-ethereum/core/types" // aka gethtypes
evm-e2e/test/setup.ts (2)

8-10: Fail fast on missing env; avoid non-null assertion on MNEMONIC.

Improves DX and avoids cryptic runtime errors.

-const provider = new ethers.JsonRpcProvider(process.env.JSON_RPC_ENDPOINT)
-const mnemonic = Mnemonic.fromPhrase(process.env.MNEMONIC!)
-const account = HDNodeWallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/0").connect(provider)
+const rpc = process.env.JSON_RPC_ENDPOINT
+if (!rpc) throw new Error("JSON_RPC_ENDPOINT is required")
+const provider = new ethers.JsonRpcProvider(rpc)
+const phrase = process.env.MNEMONIC
+if (!phrase) throw new Error("MNEMONIC is required")
+const mnemonic = Mnemonic.fromPhrase(phrase)
+const account = HDNodeWallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/0").connect(provider)

10-10: Keep derivations together; minor readability nit.

Co-locate account2 derivation next to account creation after the env checks.

-const account2 = HDNodeWallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/1").connect(provider)
+const account2 = HDNodeWallet.fromMnemonic(mnemonic, "m/44'/60'/0'/0/1").connect(provider)
contrib/wnibi.json (1)

7-13: Static account_number in EthAccount; prefer 0 or omit to avoid collisions.

Hardcoding "12" can conflict with other genesis accounts in merges.

-                    "account_number": "12",
+                    "account_number": "0",
evm-e2e/test/wnibi_gas.test.ts (1)

1-6: Remove unused imports to satisfy strict TS builds.

toBigInt, account, and TX_WAIT_TIMEOUT are unused.

-import { parseUnits, toBigInt, Wallet } from "ethers"
+import { parseUnits, Wallet } from "ethers"
 
-import { account, account2, provider, TEST_TIMEOUT, TX_WAIT_TIMEOUT } from "./setup"
+import { account2, provider, TEST_TIMEOUT } from "./setup"
app/evmante/evmante_verify_eth_acc.go (1)

99-102: Improve error message to reflect both native and WNIBI balances.

Aids debugging when fallback was attempted.

-        return ctx, sdkioerrors.Wrapf(
-            sdkerrors.ErrInsufficientFunds,
-            "sender balance < tx cost (%s < %s)", acct.BalanceNative.ToBig(), cost,
-        )
+        return ctx, sdkioerrors.Wrapf(
+            sdkerrors.ErrInsufficientFunds,
+            "sender funds < tx cost (native: %s, wnibi: %s, cost: %s)",
+            acct.BalanceNative.ToBig(), wnibiBal, cost,
+        )
evm-e2e/test/utils.ts (2)

3-10: Add getAddress import for address normalization/validation.

You call provider.getCode with user‑supplied addresses. Normalize early to avoid checksum/format issues and reduce noisy warnings.

-import {
-  ethers,
-  ContractFactory,
-  ContractTransactionResponse,
-  parseEther,
-  toBigInt,
-  Wallet,
-  type TransactionRequest,
-} from "ethers"
+import {
+  ethers,
+  getAddress,
+  ContractFactory,
+  ContractTransactionResponse,
+  parseEther,
+  toBigInt,
+  Wallet,
+  type TransactionRequest,
+} from "ethers"

110-138: Harden reuse path: normalize address and prefer factory.attach over new ethers.Contract.

  • Normalize maybeAddress and short‑circuit on valid code.
  • factory.attach keeps signer/bytecode context consistent.
 export const deployContractWNIBIIfNeeded = async (
   maybeAddress?: string
 ): Promise<{
   contract: WNIBI & DeploymentTx
 }> => {
   const { abi, bytecode } = WNIBI_JSON
   const factory = new ContractFactory(abi, bytecode, account)

-  let contract: ethers.Contract
+  let contract: ethers.Contract

   if (maybeAddress) {
     try {
-      const code = await account.provider.getCode(maybeAddress)
+      const addr = getAddress(maybeAddress)
+      const code = await account.provider.getCode(addr)
       if (code !== "0x") {
         // Contract already deployed
-        console.log(`Contract already deployed at ${maybeAddress}`)
-        contract = new ethers.Contract(maybeAddress, abi, account)
+        console.log(`Contract already deployed at ${addr}`)
+        contract = factory.attach(addr) as unknown as ethers.Contract
         return { contract: contract as unknown as WNIBI & DeploymentTx }
       }
     } catch (error) {
       console.warn(`Failed to check code at ${maybeAddress}:`, error)
       // Fall through to deploy new contract
     }
   }

   contract = (await factory.deploy()) as ethers.Contract
   await contract.waitForDeployment()
   return { contract: contract as unknown as WNIBI & DeploymentTx }
 }
app/evmante/evmante_gas_consume.go (1)

4-17: Remove unused fmt import after switching to SDK error wrapping.

-import (
-  "fmt"
-  "math"
+import (
+  "math"
app/evmante/evmante_verify_eth_acc_test.go (1)

33-45: Avoid magic storage slot (“3”) for WNIBI balances; document or centralize.

Hard‑coding the mapping slot is brittle if WNIBI storage layout changes. Define a named const near usage (or in a shared testutil) and reference it.

-        slot := CalcMappingSlot(deps.Sender.EthAddr, 3)
+        const balancesSlot uint64 = 3 // WNIBI: mapping(address => uint) balanceOf storage slot
+        slot := CalcMappingSlot(deps.Sender.EthAddr, balancesSlot)

Also, please confirm slot index 3 matches the compiled WNIBI.sol layout being used in tests.

app/evmante/evmante_gas_consume_test.go (2)

40-55: Mirror the storage‑slot constant to reduce brittleness.

Use a named const for the WNIBI balances slot rather than literal 3 to make intent clear and future changes localized.

-        slot := CalcMappingSlot(deps.Sender.EthAddr, 3)
+        const balancesSlot uint64 = 3
+        slot := CalcMappingSlot(deps.Sender.EthAddr, balancesSlot)

107-111: Deduplicate CalcMappingSlot across tests.

It’s duplicated in multiple test files. Extract to a shared test helper (e.g., x/evm/evmtest/testutil/storage.go) and import from there.

app/ante/deductfee.go (2)

58-69: Remove duplicate zero-height guard in AnteHandle.

The block-height==0 early return appears twice; keep only the first to avoid confusion.

-  if ctx.BlockHeight() == 0 {
-    return next(ctx, tx, simulate)
-  }

141-177: Consider restricting WNIBI fallback to insufficient-funds only.

DeductFees falls back to WNIBI for any bank error. For clearer semantics and less surprising behavior, gate the fallback on ErrInsufficientFunds (similar to the EVM ante suggestion).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 68b9b71 and 89df6b5.

📒 Files selected for processing (16)
  • CHANGELOG.md (1 hunks)
  • app/ante.go (1 hunks)
  • app/ante/deductfee.go (1 hunks)
  • app/ante/expected_keeper.go (1 hunks)
  • app/ante/handler_opts.go (0 hunks)
  • app/evmante/evmante_gas_consume.go (3 hunks)
  • app/evmante/evmante_gas_consume_test.go (5 hunks)
  • app/evmante/evmante_handler.go (1 hunks)
  • app/evmante/evmante_verify_eth_acc.go (1 hunks)
  • app/evmante/evmante_verify_eth_acc_test.go (4 hunks)
  • contrib/scripts/localnet.sh (1 hunks)
  • contrib/wnibi.json (1 hunks)
  • evm-e2e/test/setup.ts (1 hunks)
  • evm-e2e/test/utils.ts (2 hunks)
  • evm-e2e/test/wnibi_gas.test.ts (1 hunks)
  • x/evm/keeper/keeper.go (2 hunks)
💤 Files with no reviewable changes (1)
  • app/ante/handler_opts.go
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2024-10-31T18:28:26.466Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2095
File: x/evm/keeper/bank_extension.go:0-0
Timestamp: 2024-10-31T18:28:26.466Z
Learning: When implementing the `bankkeeper.Keeper` interface in `x/evm/keeper/bank_extension.go`, methods must use non-pointer receivers to satisfy the interface requirements.

Applied to files:

  • app/ante/expected_keeper.go
📚 Learning: 2024-07-27T01:11:54.422Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#1680
File: x/perp/v2/keeper/msg_server.go:210-210
Timestamp: 2024-07-27T01:11:54.422Z
Learning: The user indicated that error handling for `sdk.AccAddressFromBech32(msg.Sender)` is covered by prior `ValidateBasic` checks in the code flow.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2025-05-21T21:47:04.451Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2315
File: app/upgrades/v2_5_0/v2_5_0.go:0-0
Timestamp: 2025-05-21T21:47:04.451Z
Learning: In the v2.5.0 upgrade handler for Nibiru Chain, excess stNIBI ERC20 token balances (those not belonging to explicitly tracked holders) are sent to a Nibiru team-controlled Gnosis Safe for redistribution to ensure no tokens are lost during contract upgrades.

Applied to files:

  • CHANGELOG.md
📚 Learning: 2025-09-07T16:02:13.748Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2345
File: x/evm/keeper/msg_convert_evm_to_coin.go:311-315
Timestamp: 2025-09-07T16:02:13.748Z
Learning: WNIBI (Wrapped NIBI) does not use module account escrow like regular FunTokens. WNIBI works through direct contract interactions where users deposit/withdraw native NIBI directly to/from the WNIBI contract. The fund transfers use SendCoins between user accounts, not SendCoinsFromModuleToAccount like regular FunToken conversions.

Applied to files:

  • contrib/wnibi.json
📚 Learning: 2024-12-06T16:10:34.174Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2119
File: x/evm/keeper/bank_extension.go:0-0
Timestamp: 2024-12-06T16:10:34.174Z
Learning: In `x/evm/keeper/bank_extension.go`, assigning a new `GasMeter` to the context within the `NibiruBankKeeper.ForceGasInvariant` function is intentional and acceptable. This ensures that subsequent operations do not affect the parent `ctx`'s gas consumption.

Applied to files:

  • app/evmante/evmante_gas_consume.go
  • app/evmante/evmante_gas_consume_test.go
📚 Learning: 2024-12-06T11:54:38.275Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2119
File: x/evm/keeper/bank_extension_test.go:67-71
Timestamp: 2024-12-06T11:54:38.275Z
Learning: When writing tests in `x/evm/keeper/bank_extension_test.go`, prefer comparing gas consumption values as formatted strings using `fmt.Sprintf("%d", value)` instead of numeric values directly in `s.Equalf`. This avoids hexadecimal representations that are hard to read in shell outputs.

Applied to files:

  • app/evmante/evmante_gas_consume_test.go
🧬 Code graph analysis (12)
app/ante/expected_keeper.go (1)
x/common/testutil/sample.go (1)
  • AccAddress (24-27)
app/evmante/evmante_verify_eth_acc.go (3)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
x/evm/statedb/config.go (1)
  • TxConfig (15-20)
x/evm/keeper/keeper.go (1)
  • MOCK_GETH_MESSAGE (187-200)
app/evmante/evmante_verify_eth_acc_test.go (3)
x/evm/evmtest/test_deps.go (1)
  • TestDeps (27-33)
eth/eth_account.go (1)
  • EthAddrToNibiruAddr (15-17)
app/evmante/evmante_gas_consume_test.go (1)
  • CalcMappingSlot (107-111)
x/evm/keeper/keeper.go (2)
x/evm/const.go (2)
  • EVM_MODULE_ADDRESS (125-125)
  • Big0 (236-236)
x/evm/tx_data_access_list.go (1)
  • AccessList (18-18)
evm-e2e/test/wnibi_gas.test.ts (1)
evm-e2e/test/utils.ts (1)
  • deployContractWNIBIIfNeeded (110-138)
evm-e2e/test/utils.ts (3)
evm-core-ts/src/ethers/typechain/index.ts (1)
  • WNIBI (14-14)
evm-core-ts/src/ethers/typechain/WNIBI.ts (1)
  • WNIBI (158-367)
evm-e2e/test/setup.ts (1)
  • account (16-16)
app/evmante/evmante_handler.go (1)
app/evmante/evmante_gas_consume.go (1)
  • NewAnteDecEthGasConsume (28-38)
app/evmante/evmante_gas_consume.go (1)
app/ante/deductfee.go (1)
  • DeductFeesWithWNIBI (180-227)
app/ante.go (3)
app/ante/deductfee.go (1)
  • NewDeductFeeDecorator (34-46)
x/evm/deps.go (1)
  • AccountKeeper (11-32)
app/ante/expected_keeper.go (1)
  • FeegrantKeeper (8-10)
app/ante/deductfee.go (7)
x/evm/keeper/keeper.go (2)
  • Keeper (30-57)
  • MOCK_GETH_MESSAGE (187-200)
app/ante/expected_keeper.go (1)
  • FeegrantKeeper (8-10)
app/appconst/appconst.go (1)
  • BondDenom (22-22)
eth/gas_limit.go (1)
  • NewInfiniteGasMeter (54-56)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
eth/eth_account.go (1)
  • NibiruAddrToEthAddr (19-21)
x/evm/const.go (1)
  • NativeToWei (142-145)
contrib/scripts/localnet.sh (1)
contrib/scripts/chaosnet.sh (1)
  • add_genesis_param (39-45)
app/evmante/evmante_gas_consume_test.go (6)
x/evm/evmtest/test_deps.go (1)
  • TestDeps (27-33)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
eth/eth_account.go (1)
  • EthAddrToNibiruAddr (15-17)
x/evm/const.go (1)
  • NativeToWei (142-145)
x/evm/evmtest/evmante.go (1)
  • HappyCreateContractTx (69-81)
app/evmante/evmante_gas_consume.go (1)
  • NewAnteDecEthGasConsume (28-38)
🔇 Additional comments (9)
contrib/wnibi.json (2)

47-51: Confirm canonical_wnibi correctness across environments.

Ensure this address matches the deployed WNIBI for localnet; mismatch breaks WNIBI fallback.


19-43: Code hash verified — no action required.
Keccak256(code) equals auth.accounts[0].code_hash in contrib/wnibi.json (0xffb88e0eb48147949565e65de3ec8a54b746214da7b9dd5b9a8a3ae7df46193b).

contrib/scripts/localnet.sh (1)

231-233: Make merges idempotent — dedupe accounts/balances

Ensure repeated runs don’t create duplicate accounts or balances; use jq unique_by on accounts and balances.

File: contrib/scripts/localnet.sh Lines: 231-233

-jq --slurpfile evm contrib/wnibi.json '.app_state.evm = $evm[0].evm | .app_state.auth.accounts += $evm[0].auth.accounts |.app_state.bank.balances += $evm[0].bank.balances' $CHAIN_DIR/config/genesis.json > $CHAIN_DIR/config/genesis_tmp.json
+jq --slurpfile evm contrib/wnibi.json '
+  .app_state.evm = $evm[0].evm
+  | .app_state.auth.accounts += $evm[0].auth.accounts
+  | .app_state.auth.accounts |= unique_by(.base_account.address // .address)
+  | .app_state.bank.balances += $evm[0].bank.balances
+  | .app_state.bank.balances |= unique_by(.address)
+' $CHAIN_DIR/config/genesis.json > $CHAIN_DIR/config/genesis_tmp.json
 mv $CHAIN_DIR/config/genesis_tmp.json $CHAIN_DIR/config/genesis.json

Verification couldn’t be performed here — $HOME/.nibid/config/genesis.json was not present in the verification environment. After applying the patch, run the supplied supply-check locally to confirm balances sum equals supply.

x/evm/keeper/keeper.go (1)

187-200: MOCK_GETH_MESSAGE construction is invalid; use types.NewMessage to build a core.Message.

core.Message is an interface in recent geth; composite literal won’t compile. Build via types.NewMessage and assign to core.Message.

-var MOCK_GETH_MESSAGE = core.Message{
-	To:               nil,
-	From:             evm.EVM_MODULE_ADDRESS,
-	Nonce:            0,
-	Value:            evm.Big0, // amount
-	GasLimit:         0,
-	GasPrice:         evm.Big0,
-	GasFeeCap:        evm.Big0,
-	GasTipCap:        evm.Big0,
-	Data:             []byte{},
-	AccessList:       gethcore.AccessList{},
-	SkipNonceChecks:  false,
-	SkipFromEOACheck: false,
-}
+var MOCK_GETH_MESSAGE core.Message = gethcore.NewMessage(
+	evm.EVM_MODULE_ADDRESS, // from
+	nil,                    // to
+	0,                      // nonce
+	evm.Big0,               // value
+	0,                      // gasLimit
+	evm.Big0,               // gasPrice
+	evm.Big0,               // gasFeeCap
+	evm.Big0,               // gasTipCap
+	[]byte{},               // data
+	gethcore.AccessList{},  // access list
+	false,                  // checkNonce
+)

Likely an incorrect or invalid review comment.

app/evmante/evmante_verify_eth_acc.go (1)

73-77: Short‑circuit on sufficient native balance is correct.

This preserves the fast path and only triggers WNIBI fallback when needed.

app/evmante/evmante_handler.go (1)

22-23: Approve — AccountKeeper wiring and ante ordering OK; call sites updated. Confirmed NewAnteDecEthGasConsume is invoked with the three-arg signature in app/evmante/evmante_handler.go and app/evmante/evmante_gas_consume_test.go; ante-chain ordering matches Ethermint.

app/ante/expected_keeper.go (1)

7-10: Interface shape LGTM — verify concrete keeper implements UseGrantedFees

  • Wiring: app/keepers/all_keepers.go:44 sets FeeGrantKeeper = feegrantkeeper.Keeper and it's passed into NewDeductFeeDecorator in app/ante.go:82; call site using the method: app/ante/deductfee.go:107.
  • No local implementation found in this repo; confirm github.com/cosmos/cosmos-sdk/x/feegrant/keeper.Keeper exposes UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error or run go build to validate.
app/ante.go (1)

82-84: Approve: DeductFeeDecorator placement — verify EvmKeeper non‑nil

Good integration of EvmKeeper for WNIBI fallback. Ran the grep you suggested — EvmKeeper is declared as a *evmkeeper.Keeper (app/ante/handler_opts.go) and used in NewAnteHandlerNonEVM (app/ante.go:82); app/app.go passes app.EvmKeeper into ante options. Confirm NewNibiruApp always instantiates app.EvmKeeper (search for evmkeeper.NewKeeper or an assignment to app.EvmKeeper); if it can be nil, add a nil-guard before calling NewDeductFeeDecorator or make the decorator handle a nil EvmKeeper.

evm-e2e/test/utils.ts (1)

120-129: Confirm expectations when canonical address has no code.

If the canonical WNIBI isn’t predeployed, this helper deploys to a different address, which may break tests relying on the canonical address being used. Confirm tests only depend on the returned contract and not on maybeAddress being honored for new deployments.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
app/evmante/evmante_verify_eth_acc.go (2)

81-87: Critical: don’t clobber global Bank.StateDB; restore original instead.

The defer unconditionally sets Bank.StateDB to nil even if it was non‑nil, breaking subsequent EVM usage. Capture and restore the original value.

-		stateDB := anteDec.evmKeeper.Bank.StateDB
-		if stateDB == nil {
-			stateDB = anteDec.evmKeeper.NewStateDB(ctx, anteDec.evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
-		}
-		defer func() {
-			anteDec.evmKeeper.Bank.StateDB = nil
-		}()
+		origStateDB := anteDec.evmKeeper.Bank.StateDB
+		stateDB := origStateDB
+		if stateDB == nil {
+			stateDB = anteDec.evmKeeper.NewStateDB(ctx, anteDec.evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
+		}
+		defer func() {
+			anteDec.evmKeeper.Bank.StateDB = origStateDB
+		}()

79-90: Guard against unset CanonicalWnibi and handle BalanceOf errors deterministically.

If CanonicalWnibi isn’t configured or BalanceOf errors, don’t proceed with fallback; return the original insufficient funds error.

-		wnibi := anteDec.evmKeeper.GetParams(ctx).CanonicalWnibi
+		wnibi := anteDec.evmKeeper.GetParams(ctx).CanonicalWnibi
+		if wnibi.Address == (gethcommon.Address{}) {
+			// WNIBI not configured: keep original InsufficientFunds semantics
+			return ctx, sdkerrors.ErrInsufficientFunds
+		}
@@
-		wnibiBal, err := anteDec.evmKeeper.ERC20().BalanceOf(wnibi.Address, fromAddr, ctx, evmObj)
+		wnibiBal, balErr := anteDec.evmKeeper.ERC20().BalanceOf(wnibi.Address, fromAddr, ctx, evmObj)
+		if balErr != nil {
+			// Don’t mask errors from BalanceOf as sufficient funds
+			return ctx, sdkerrors.ErrInsufficientFunds
+		}
🧹 Nitpick comments (2)
app/evmante/evmante_verify_eth_acc.go (1)

97-100: Preserve typed insufficient-funds error; consider enhancing context.

Typed ErrInsufficientFunds is good. Optionally include both native and WNIBI balances in the message to aid diagnostics.

app/evmante/evmante_gas_consume.go (1)

181-191: WNIBI deduction flow: mostly good; preserve detailed error when available.

If DeductFeesWithWNIBI returns a typed ErrInsufficientFunds, rewrapping loses context. Prefer returning err as-is when it’s already classified.

-	err = ante.DeductFeesWithWNIBI(ctx, anteDec.accountKeeper, anteDec.evmKeeper, acc, fees)
-	if err == nil {
-		return nil
-	}
-
-	return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient balance across supported gas tokens to cover %s", fees[0].Amount)
+	err = ante.DeductFeesWithWNIBI(ctx, anteDec.accountKeeper, anteDec.evmKeeper, acc, fees)
+	if err == nil {
+		return nil
+	}
+	// Preserve original typed error if it’s already ErrInsufficientFunds
+	if sdkioerrors.IsOf(err, sdkerrors.ErrInsufficientFunds) {
+		return err
+	}
+	return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFunds,
+		"insufficient balance across supported gas tokens to cover %s: %v",
+		fees[0].Amount, err)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89df6b5 and 3332fb9.

📒 Files selected for processing (3)
  • app/evmante/evmante_gas_consume.go (3 hunks)
  • app/evmante/evmante_verify_eth_acc.go (1 hunks)
  • evm-e2e/test/wnibi_gas.test.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • evm-e2e/test/wnibi_gas.test.ts
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2024-10-31T18:28:26.466Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2095
File: x/evm/keeper/bank_extension.go:0-0
Timestamp: 2024-10-31T18:28:26.466Z
Learning: When implementing the `bankkeeper.Keeper` interface in `x/evm/keeper/bank_extension.go`, methods must use non-pointer receivers to satisfy the interface requirements.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2024-12-06T16:09:01.767Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2119
File: x/evm/keeper/bank_extension_test.go:161-165
Timestamp: 2024-12-06T16:09:01.767Z
Learning: In Go test assertions within `x/evm/keeper/bank_extension_test.go`, when comparing `uint64` values using `s.Equalf`, prefer formatting the values as decimal strings with `fmt.Sprintf("%d", value)` to avoid them being printed in hexadecimal.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2024-12-06T16:10:34.174Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2119
File: x/evm/keeper/bank_extension.go:0-0
Timestamp: 2024-12-06T16:10:34.174Z
Learning: In `x/evm/keeper/bank_extension.go`, assigning a new `GasMeter` to the context within the `NibiruBankKeeper.ForceGasInvariant` function is intentional and acceptable. This ensures that subsequent operations do not affect the parent `ctx`'s gas consumption.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
  • app/evmante/evmante_gas_consume.go
📚 Learning: 2024-12-06T11:54:38.275Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2119
File: x/evm/keeper/bank_extension_test.go:67-71
Timestamp: 2024-12-06T11:54:38.275Z
Learning: When writing tests in `x/evm/keeper/bank_extension_test.go`, prefer comparing gas consumption values as formatted strings using `fmt.Sprintf("%d", value)` instead of numeric values directly in `s.Equalf`. This avoids hexadecimal representations that are hard to read in shell outputs.

Applied to files:

  • app/evmante/evmante_gas_consume.go
📚 Learning: 2024-11-04T20:38:45.909Z
Learnt from: k-yang
PR: NibiruChain/nibiru#2093
File: x/evm/precompile/funtoken.go:85-87
Timestamp: 2024-11-04T20:38:45.909Z
Learning: In the `Run` methods of precompiled contracts in `x/evm/precompile/`, after handling errors from `OnRunStart`, it's unnecessary to check if `err` is nil before consuming gas with `contract.UseGas()`, as `err` will always be nil at that point.

Applied to files:

  • app/evmante/evmante_gas_consume.go
🧬 Code graph analysis (2)
app/evmante/evmante_verify_eth_acc.go (3)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
x/evm/statedb/config.go (1)
  • TxConfig (15-20)
x/evm/keeper/keeper.go (1)
  • MOCK_GETH_MESSAGE (187-200)
app/evmante/evmante_gas_consume.go (2)
app/evmante/interfaces.go (1)
  • EVMKeeper (10-10)
app/ante/deductfee.go (1)
  • DeductFeesWithWNIBI (180-227)
🔇 Additional comments (5)
app/evmante/evmante_gas_consume.go (4)

12-12: New ante import: LGTM.

Importing app/ante to use DeductFeesWithWNIBI is appropriate.


21-24: Struct field additions: LGTM.

Wiring AccountKeeper into the decorator aligns with the new deduction path.


171-179: Correctly gate WNIBI fallback only on insufficient funds.

This prevents masking real bank errors. Good fix.


27-36: Constructor signature change: LGTM — call sites updated.

  • Updated call sites: app/evmante/evmante_handler.go:22; app/evmante/evmante_gas_consume_test.go:84-86.
app/evmante/evmante_verify_eth_acc.go (1)

92-95: Compare WNIBI against gas-only fees, not tx Cost().

txData.Cost() = value + gas; WNIBI only pays fees. Replace the Cost() comparison with a fee-only verification (convert returned fee to wei) and separately ensure the sender’s native/EVM balance covers tx value.

File: app/evmante/evmante_verify_eth_acc.go:92-95

-		cost := txData.Cost()
-		if wnibi.Address != (gethcommon.Address{}) && err == nil && wnibiBal.Cmp(cost) >= 0 {
-			continue
-		}
+		fees, feeErr := keeper.VerifyFee(txData, anteDec.evmKeeper.BaseFeeMicronibiPerGas(ctx), ctx)
+		if feeErr != nil {
+			return ctx, sdkioerrors.Wrap(feeErr, "failed to verify fees for WNIBI fallback")
+		}
+		gasWei := evm.NativeToWei(fees[0].Amount.BigInt())
+		if wnibiBal.Cmp(gasWei) >= 0 {
+			continue
+		}

Follow-up: enforce native balance covers tx value when WNIBI only covers gas; add tests for value>0 with gas in WNIBI and value>0 without native balance.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (7)
evm-e2e/test/setup.ts (1)

8-12: Consider safer environment variable handling.

The non-null assertion on process.env.MNEMONIC! will throw a runtime error if the environment variable is undefined. Consider adding validation or a fallback to provide a clearer error message in development environments.

-const mnemonic = Mnemonic.fromPhrase(process.env.MNEMONIC!)
+if (!process.env.MNEMONIC) {
+  throw new Error("MNEMONIC environment variable is required")
+}
+const mnemonic = Mnemonic.fromPhrase(process.env.MNEMONIC)
app/evmante/evmante_verify_eth_acc_test.go (1)

43-43: Consider optimizing WNIBI address retrieval.

Based on the past review comment, you could potentially remove the explicit wnibi parameter and retrieve the WNIBI address from the context. However, the current approach is clear and functional for testing purposes.

Based on learnings

x/evm/mock_msg.go (1)

8-21: Use Go‑idiomatic exported name and add a brief doc comment

Public identifiers should be MixedCaps. Also document intended usage to avoid misuse outside tests.

-var MOCK_GETH_MESSAGE = core.Message{
+// MockGethMessage is a zero-gas, module-signed message used for read-only EVM calls in tests/utilities.
+var MockGethMessage = core.Message{

Follow-up: update call sites (e.g., evmante) to evm.MockGethMessage.

contrib/scripts/localnet.sh (1)

247-248: Merge from the temp file and clean up; recompute supply after merge

-jq --slurpfile evm contrib/wnibi.json '.app_state.evm = $evm[0].evm | .app_state.auth.accounts += $evm[0].auth.accounts |.app_state.bank.balances += $evm[0].bank.balances' $CHAIN_DIR/config/genesis.json > $CHAIN_DIR/config/genesis_tmp.json
+jq --slurpfile evm "$WNIBI_TMP" '
+  .app_state.evm = $evm[0].evm
+  | .app_state.auth.accounts += $evm[0].auth.accounts
+  | .app_state.bank.balances += $evm[0].bank.balances
+' "$CHAIN_DIR/config/genesis.json" > "$CHAIN_DIR/config/genesis_tmp.json"
 mv $CHAIN_DIR/config/genesis_tmp.json $CHAIN_DIR/config/genesis.json
+rm -f "$WNIBI_TMP"
+
+# Recompute bank supply now that balances changed
+jq '
+  .app_state.bank.supply =
+  (
+    [ .app_state.bank.balances[].coins[] ]
+    | sort_by(.denom)
+    | group_by(.denom)
+    | map({denom: .[0].denom, amount: (map(.amount|tonumber) | add | tostring)})
+  )
+' $CHAIN_DIR/config/genesis.json > $CHAIN_DIR/config/genesis_tmp.json && \
+mv $CHAIN_DIR/config/genesis_tmp.json $CHAIN_DIR/config/genesis.json
contrib/wnibi.json (1)

7-13: Avoid hardcoding account_number in committed JSON

Since the script sets account_number dynamically, commit this as "0" or omit the field to reduce accidental conflicts. If you keep it, document that localnet.sh overwrites it.

-          "account_number": "3",
+          "account_number": "0",
app/evmante/evmante_verify_eth_acc.go (2)

89-95: Use the new evm.MockGethMessage (if renamed) and guard EVM init failures

If NewEVM can error via config/state issues, surface that instead of proceeding with a nil evmObj.


64-68: Verify native-balance path when EVM account is nil

Creating a Cosmos account then using NewEmptyAccount() sets BalanceNative=0, which forces WNIBI fallback even if the user holds unibi. Confirm this is intended or populate acct.BalanceNative from bank for first-time EOAs.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e87875 and e105554.

📒 Files selected for processing (10)
  • app/ante/deductfee.go (1 hunks)
  • app/evmante/evmante_verify_eth_acc.go (1 hunks)
  • app/evmante/evmante_verify_eth_acc_test.go (3 hunks)
  • contrib/scripts/localnet.sh (1 hunks)
  • contrib/wnibi.json (1 hunks)
  • evm-e2e/test/setup.ts (1 hunks)
  • evm-e2e/test/utils.ts (2 hunks)
  • evm-e2e/test/wnibi_gas.test.ts (1 hunks)
  • x/evm/evmtest/tx.go (1 hunks)
  • x/evm/mock_msg.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • evm-e2e/test/utils.ts
🧰 Additional context used
🧠 Learnings (13)
📚 Learning: 2025-09-07T16:02:13.748Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2345
File: x/evm/keeper/msg_convert_evm_to_coin.go:311-315
Timestamp: 2025-09-07T16:02:13.748Z
Learning: WNIBI (Wrapped NIBI) does not use module account escrow like regular FunTokens. WNIBI works through direct contract interactions where users deposit/withdraw native NIBI directly to/from the WNIBI contract. The fund transfers use SendCoins between user accounts, not SendCoinsFromModuleToAccount like regular FunToken conversions.

Applied to files:

  • contrib/wnibi.json
  • evm-e2e/test/wnibi_gas.test.ts
📚 Learning: 2024-10-31T18:28:26.466Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2095
File: x/evm/keeper/bank_extension.go:0-0
Timestamp: 2024-10-31T18:28:26.466Z
Learning: When implementing the `bankkeeper.Keeper` interface in `x/evm/keeper/bank_extension.go`, methods must use non-pointer receivers to satisfy the interface requirements.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2024-12-06T16:09:01.767Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2119
File: x/evm/keeper/bank_extension_test.go:161-165
Timestamp: 2024-12-06T16:09:01.767Z
Learning: In Go test assertions within `x/evm/keeper/bank_extension_test.go`, when comparing `uint64` values using `s.Equalf`, prefer formatting the values as decimal strings with `fmt.Sprintf("%d", value)` to avoid them being printed in hexadecimal.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2024-12-06T16:10:34.174Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2119
File: x/evm/keeper/bank_extension.go:0-0
Timestamp: 2024-12-06T16:10:34.174Z
Learning: In `x/evm/keeper/bank_extension.go`, assigning a new `GasMeter` to the context within the `NibiruBankKeeper.ForceGasInvariant` function is intentional and acceptable. This ensures that subsequent operations do not affect the parent `ctx`'s gas consumption.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2024-10-08T15:45:59.488Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#1680
File: x/perp/v2/keeper/msg_server.go:210-210
Timestamp: 2024-10-08T15:45:59.488Z
Learning: The user indicated that error handling for `sdk.AccAddressFromBech32(msg.Sender)` is covered by prior `ValidateBasic` checks in the code flow.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2024-10-13T05:48:51.771Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2076
File: x/evm/keeper/msg_server.go:115-116
Timestamp: 2024-10-13T05:48:51.771Z
Learning: In the `ApplyEvmTx` function within `x/evm/keeper/msg_server.go`, a zero value for `weiPerGas` is acceptable as it signifies that gas is free. There is no need to check for zero before performing the gas refund in this context.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2024-10-08T15:45:59.488Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#1680
File: x/perp/v2/keeper/msg_server.go:198-198
Timestamp: 2024-10-08T15:45:59.488Z
Learning: The user Unique-Divine has indicated that the error check for `sdk.AccAddressFromBech32(msg.Sender)` is not necessary in the context of their code because it is covered by the `ValidateBasic` method.

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2025-09-16T00:14:44.279Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2385
File: x/evm/keeper/msg_server.go:92-97
Timestamp: 2025-09-16T00:14:44.279Z
Learning: In the EthereumTx function in x/evm/keeper/msg_server.go, the existing pattern of checking `if evmResp != nil` before calling SafeConsumeGas is correct because the control flow already handles the nil case through an early return when there's a consensus error (applyErr != nil && evmResp == nil).

Applied to files:

  • app/evmante/evmante_verify_eth_acc.go
📚 Learning: 2024-12-06T11:54:38.275Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2119
File: x/evm/keeper/bank_extension_test.go:67-71
Timestamp: 2024-12-06T11:54:38.275Z
Learning: When writing tests in `x/evm/keeper/bank_extension_test.go`, prefer comparing gas consumption values as formatted strings using `fmt.Sprintf("%d", value)` instead of numeric values directly in `s.Equalf`. This avoids hexadecimal representations that are hard to read in shell outputs.

Applied to files:

  • app/evmante/evmante_verify_eth_acc_test.go
📚 Learning: 2025-09-25T05:01:20.715Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2384
File: contrib/scripts/localnet.sh:225-225
Timestamp: 2025-09-25T05:01:20.715Z
Learning: In Cosmos SDK genesis files, bank.supply should be computed from balances rather than hardcoded, as hardcoded values can become inconsistent when merging additional balance data from external sources like wnibi.json.

Applied to files:

  • contrib/scripts/localnet.sh
📚 Learning: 2025-09-10T07:13:02.800Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2379
File: x/tokenfactory/types/genesis.go:43-45
Timestamp: 2025-09-10T07:13:02.800Z
Learning: In x/tokenfactory/types/genesis.go, the error handling in GenesisState.Validate() intentionally uses fmt.Errorf with %w to wrap the underlying sdk.AccAddressFromBech32 error rather than wrapping with ErrInvalidGenesis. This allows callers to unwrap and inspect the specific address parsing error while keeping genesis-level errors as contextual strings.

Applied to files:

  • contrib/scripts/localnet.sh
📚 Learning: 2024-10-01T16:58:59.204Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2054
File: x/evm/embeds/artifacts/contracts/Wasm.sol/IWasm.json:175-198
Timestamp: 2024-10-01T16:58:59.204Z
Learning: In the IWasm interface, the `contractAddr` parameter is a bech32 nibi-prefixed address and should be of type `string` in Solidity, not `address`.

Applied to files:

  • evm-e2e/test/wnibi_gas.test.ts
📚 Learning: 2024-10-01T16:59:08.812Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2054
File: x/evm/embeds/artifacts/contracts/Wasm.sol/IWasm.json:6-46
Timestamp: 2024-10-01T16:59:08.812Z
Learning: In the `IWasm` interface, the `contractAddr` parameter should be of type `string` because it's a bech32 nibi-prefixed address, not an Ethereum address.

Applied to files:

  • evm-e2e/test/wnibi_gas.test.ts
🧬 Code graph analysis (7)
app/evmante/evmante_verify_eth_acc.go (3)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
x/evm/statedb/config.go (1)
  • TxConfig (15-20)
x/evm/evmtest/tx.go (1)
  • MOCK_GETH_MESSAGE (383-396)
app/evmante/evmante_verify_eth_acc_test.go (6)
x/evm/evmtest/test_deps.go (1)
  • TestDeps (27-33)
x/evm/const.go (1)
  • NativeToWei (142-145)
x/evm/evmtest/evmante.go (1)
  • HappyCreateContractTx (69-81)
x/common/testutil/testapp/testapp.go (1)
  • FundAccount (156-165)
x/evm/evmtest/tx.go (1)
  • ExecuteNibiTransferTo (50-68)
x/evm/query.pb.go (3)
  • QueryBalanceRequest (277-280)
  • QueryBalanceRequest (284-284)
  • QueryBalanceRequest (285-287)
contrib/scripts/localnet.sh (1)
contrib/scripts/chaosnet.sh (1)
  • add_genesis_param (39-45)
x/evm/mock_msg.go (2)
x/evm/const.go (2)
  • EVM_MODULE_ADDRESS (125-125)
  • Big0 (236-236)
x/evm/tx_data_access_list.go (1)
  • AccessList (18-18)
x/evm/evmtest/tx.go (3)
x/evm/evmtest/test_deps.go (1)
  • TestDeps (27-33)
x/evm/tx.pb.go (6)
  • MsgEthereumTx (40-51)
  • MsgEthereumTx (55-55)
  • MsgEthereumTx (56-58)
  • MsgEthereumTxResponse (333-348)
  • MsgEthereumTxResponse (352-352)
  • MsgEthereumTxResponse (353-355)
x/evm/json_tx_args.go (1)
  • JsonTxArgs (24-46)
evm-e2e/test/wnibi_gas.test.ts (1)
evm-e2e/test/utils.ts (1)
  • getWNIBI (110-130)
app/ante/deductfee.go (8)
x/evm/keeper/keeper.go (1)
  • Keeper (27-54)
app/ante/expected_keeper.go (1)
  • FeegrantKeeper (8-10)
app/appconst/appconst.go (1)
  • BondDenom (22-22)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
x/evm/statedb/config.go (1)
  • TxConfig (15-20)
x/evm/evmtest/tx.go (1)
  • MOCK_GETH_MESSAGE (383-396)
eth/eth_account.go (1)
  • NibiruAddrToEthAddr (19-21)
x/evm/const.go (1)
  • NativeToWei (142-145)
🪛 Gitleaks (8.28.0)
contrib/wnibi.json

[high] 35-35: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 39-39: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: proto-gen-rs
  • GitHub Check: build
  • GitHub Check: unit-tests
  • GitHub Check: integration-tests
  • GitHub Check: e2e-evm
  • GitHub Check: lint
🔇 Additional comments (14)
x/evm/evmtest/tx.go (1)

50-68: LGTM! Well-designed test helper function.

The new ExecuteNibiTransferTo function follows the established pattern from ExecuteNibiTransfer and provides useful flexibility for testing specific recipient addresses and transfer values. The implementation correctly handles transaction construction, signing, and execution with proper error checking.

evm-e2e/test/setup.ts (1)

9-12: Excellent documentation for account derivations.

The comments clearly explain the purpose of each account and their respective derivation paths. This addresses the previous review feedback and makes the setup much more understandable for developers.

app/ante/deductfee.go (3)

29-30: Add compile-time interface assertion.

Based on previous review feedback, add a compile-time interface assertion to ensure DeductFeeDecorator implements sdk.AnteDecorator correctly.

 var (
-	_ sdk.AnteDecorator = DeductFeeDecorator{}
+	_ sdk.AnteDecorator = (*DeductFeeDecorator)(nil)
 )

155-160: Gas meter manipulation looks correct.

The defer function properly restores the gas meter state by refunding consumed gas and then re-consuming the baseline amount. This pattern ensures that EVM operations don't interfere with the ante handler's gas accounting.


183-231: WNIBI fallback implementation is well-structured.

The DeductFeesWithWNIBI function properly:

  • Retrieves WNIBI contract parameters
  • Sets up StateDB for EVM operations
  • Checks WNIBI balance using ERC20 interface
  • Transfers WNIBI to fee collector via ConvertEvmToCoinForWNIBI
  • Updates account sequence correctly

The implementation aligns with the WNIBI fee payment requirements.

app/evmante/evmante_verify_eth_acc_test.go (1)

33-57: Well-designed WNIBI gas payment test case.

The new test case properly validates the WNIBI fallback mechanism by:

  • Funding the account with WNIBI tokens via ExecuteNibiTransferTo
  • Explicitly verifying zero native balance before the transaction
  • Testing that gas can be paid using WNIBI when native funds are insufficient

The test setup correctly uses deps.DeployWNIBI and follows established testing patterns.

evm-e2e/test/wnibi_gas.test.ts (3)

19-23: Previous review feedback has been addressed.

The code now correctly uses account2.address as the recipient for the ERC-20 transfer, addressing the type safety concern from previous reviews.


26-39: Gas fee handling properly implemented.

The test now includes explicit fee overrides (maxFeePerGas and maxPriorityFeePerGas) to ensure deterministic gas consumption, and uses alice.address as the recipient. This addresses the previous review feedback about potential test flakiness and type safety.


7-42: Comprehensive WNIBI gas payment validation.

This end-to-end test effectively validates the multi-token gas payment feature by:

  • Verifying zero native balance initially
  • Funding account with WNIBI tokens
  • Executing a transfer with explicit gas fees
  • Confirming gas was paid from WNIBI balance (balance drops below expected amount)

The test provides good coverage of the WNIBI gas payment flow introduced in this PR.

x/evm/mock_msg.go (1)

13-17: Confirm gas semantics for zero GasLimit/Price in read-only calls

If BalanceOf uses the provided Message gas, GasLimit:0 could cause OOG on some execution paths. If your call path enforces eth_call semantics (no meter), this is fine; otherwise set a small non-zero gas limit.

contrib/wnibi.json (1)

21-44: Sanity check: deployed bytecode and storage are public test fixtures, not secrets

Gitleaks “Generic API Key” is a false positive on contract storage keys. Ensure this file contains only testnet/localnet data and no production private keys.

contrib/scripts/localnet.sh (1)

225-225: Do not hardcode bank.supply; recompute from balances after merges

- add_genesis_param '.app_state.bank.supply[0].amount = "30000420000000"'
+# Recompute bank.supply from balances (all denoms) after any merges
+jq '
+  .app_state.bank.supply =
+  (
+    [ .app_state.bank.balances[].coins[] ]
+    | sort_by(.denom)
+    | group_by(.denom)
+    | map({denom: .[0].denom, amount: (map(.amount|tonumber) | add | tostring)})
+  )
+' $CHAIN_DIR/config/genesis.json > $CHAIN_DIR/config/genesis_tmp.json && \
+mv $CHAIN_DIR/config/genesis_tmp.json $CHAIN_DIR/config/genesis.json

Optional: run again after collect-gentxs to include staking/bonding edits.

#!/bin/bash
# Verify supply equals sum(balances) per denom
jq -r '
  def sum_balances: ([.app_state.bank.balances[].coins[]] | group_by(.denom)
    | map({denom: .[0].denom, amount: (map(.amount|tonumber)|add)}));
  {supply: (.app_state.bank.supply|map({denom,amount:(.amount|tonumber)})),
   sums: sum_balances}
' "$HOME/.nibid/config/genesis.json"
app/evmante/evmante_verify_eth_acc.go (2)

73-77: Fallback only on insufficient funds; return other errors

-        if err := keeper.CheckSenderBalance(
-            evm.NativeToWei(acct.BalanceNative.ToBig()), txData,
-        ); err == nil {
-            continue
-        }
+        err := keeper.CheckSenderBalance(
+            evm.NativeToWei(acct.BalanceNative.ToBig()), txData,
+        )
+        if err == nil {
+            continue
+        }
+        if !sdkioerrors.IsOf(err, sdkerrors.ErrInsufficientFunds) {
+            return ctx, err
+        }

81-88: Don’t clobber shared Bank.StateDB; restore original

Currently the deferred nil assignment can wipe a preexisting StateDB set elsewhere.

-        stateDB := anteDec.evmKeeper.Bank.StateDB
+        origStateDB := anteDec.evmKeeper.Bank.StateDB
+        stateDB := origStateDB
         if stateDB == nil {
             stateDB = anteDec.evmKeeper.NewStateDB(ctx, anteDec.evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
         }
-        defer func() {
-            anteDec.evmKeeper.Bank.StateDB = nil
-        }()
+        // No mutation to Bank.StateDB required here; if you ever set it, restore it:
+        // defer func() { anteDec.evmKeeper.Bank.StateDB = origStateDB }()

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (3)
contrib/scripts/localnet.sh (1)

241-243: Merge safety: dedupe accounts by address; then recompute supply and validate

  • The merge can duplicate an existing account. Deduplicate by .base_account.address.
  • Immediately recompute bank.supply from balances (big‑int safe) and run genesis validate.

Apply:

-jq --slurpfile evm contrib/wnibi.json '.app_state.evm = $evm[0].evm | .app_state.auth.accounts += $evm[0].auth.accounts |.app_state.bank.balances += $evm[0].bank.balances' $CHAIN_DIR/config/genesis.json > $CHAIN_DIR/config/genesis_tmp.json
+jq --slurpfile evm "$WNIBI_TMP" '
+  .app_state.evm = $evm[0].evm
+  | .app_state.auth.accounts =
+      (.app_state.auth.accounts + $evm[0].auth.accounts
+       | unique_by(.base_account.address))
+  | .app_state.bank.balances += $evm[0].bank.balances
+' "$CHAIN_DIR/config/genesis.json" > "$CHAIN_DIR/config/genesis_tmp.json"
 mv $CHAIN_DIR/config/genesis_tmp.json $CHAIN_DIR/config/genesis.json
+
+# Recompute bank.supply (big-int safe) and validate
+python - <<'PY' "$CHAIN_DIR/config/genesis.json"
+import json,sys
+p=sys.argv[1]; g=json.load(open(p)); s={}
+for b in g["app_state"]["bank"]["balances"]:
+  for c in b.get("coins",[]):
+    s[c["denom"]]=s.get(c["denom"],0)+int(c["amount"])
+g["app_state"]["bank"]["supply"]=[{"denom":d,"amount":str(a)} for d,a in sorted(s.items())]
+json.dump(g,open(p,"w"),separators=(", ",": "))
+PY
+"$BINARY" genesis validate

Optionally, clean up the temp wnibi file after use:

rm -f "$WNIBI_TMP"
app/ante/deductfee.go (2)

171-176: Error message clarity for unsupported fee denom

If multiple denoms are supplied, users will see “fee denom must be unibi” even if unibi is present but not at index 0. The earlier single‑denom check mitigates this; ensure message guidance is consistent.

-		return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee denom must be %s, got %s", appconst.BondDenom, fees[0].Denom)
+		return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "only %s fees are accepted", appconst.BondDenom)

52-60: Minor: gas > 0 check is good; consider IsReCheckTx short‑circuit

Optional: emulate SDK behavior to skip certain checks on ReCheckTx to reduce mempool churn.

-	if !simulate && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 {
+	if !simulate && !ctx.IsReCheckTx() && ctx.BlockHeight() > 0 && feeTx.GetGas() == 0 {
 		return ctx, sdkioerrors.Wrap(sdkerrors.ErrInvalidGasLimit, "must provide positive gas")
 	}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e105554 and a703178.

📒 Files selected for processing (2)
  • app/ante/deductfee.go (1 hunks)
  • contrib/scripts/localnet.sh (1 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-25T05:01:20.715Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2384
File: contrib/scripts/localnet.sh:225-225
Timestamp: 2025-09-25T05:01:20.715Z
Learning: In Cosmos SDK genesis files, bank.supply should be computed from balances rather than hardcoded, as hardcoded values can become inconsistent when merging additional balance data from external sources like wnibi.json.

Applied to files:

  • contrib/scripts/localnet.sh
📚 Learning: 2025-09-10T07:13:02.800Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2379
File: x/tokenfactory/types/genesis.go:43-45
Timestamp: 2025-09-10T07:13:02.800Z
Learning: In x/tokenfactory/types/genesis.go, the error handling in GenesisState.Validate() intentionally uses fmt.Errorf with %w to wrap the underlying sdk.AccAddressFromBech32 error rather than wrapping with ErrInvalidGenesis. This allows callers to unwrap and inspect the specific address parsing error while keeping genesis-level errors as contextual strings.

Applied to files:

  • contrib/scripts/localnet.sh
📚 Learning: 2024-10-31T09:55:25.630Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2097
File: x/oracle/keeper/keeper.go:195-195
Timestamp: 2024-10-31T09:55:25.630Z
Learning: In `x/oracle/keeper/keeper.go`, within the `GetDatedExchangeRate` method, it's not possible for `exchangeRate.CreatedBlock` to be in the future relative to `ctx.BlockHeight()`, so additional validation before time calculation is unnecessary.

Applied to files:

  • app/ante/deductfee.go
🧬 Code graph analysis (2)
contrib/scripts/localnet.sh (1)
contrib/scripts/chaosnet.sh (1)
  • add_genesis_param (39-45)
app/ante/deductfee.go (8)
x/evm/keeper/keeper.go (1)
  • Keeper (27-54)
app/ante/expected_keeper.go (1)
  • FeegrantKeeper (8-10)
app/appconst/appconst.go (1)
  • BondDenom (22-22)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
x/evm/statedb/config.go (1)
  • TxConfig (15-20)
x/evm/mock_msg.go (1)
  • MOCK_GETH_MESSAGE (8-21)
eth/eth_account.go (1)
  • NibiruAddrToEthAddr (19-21)
x/evm/const.go (1)
  • NativeToWei (142-145)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: integration-tests
  • GitHub Check: e2e-evm
  • GitHub Check: lint
  • GitHub Check: proto-gen-rs
  • GitHub Check: unit-tests
  • GitHub Check: build
🔇 Additional comments (4)
contrib/scripts/localnet.sh (3)

236-240: Do not mutate contrib/wnibi.json; write to a temp file and keep repo clean

Editing files under contrib/ in place dirties the working tree and is fragile across runs. Generate a temp copy with the computed account_number and use that for merging.

Apply:

-echo_info "Setting WNIBI auth account_number to next open index"
-NEXT_ACC_NUM=$(get_auth_accounts_len)
-jq ".auth.accounts[0].base_account.account_number=\"${NEXT_ACC_NUM}\"" contrib/wnibi.json > contrib/wnibi.tmp.json
-mv contrib/wnibi.tmp.json contrib/wnibi.json
+echo_info "Preparing WNIBI json with computed account_number"
+NEXT_ACC_NUM=$(get_next_open_account_number)
+WNIBI_TMP="$(mktemp "${CHAIN_DIR}/wnibi.XXXXXX.json")"
+jq --argjson n "$NEXT_ACC_NUM" \
+  '.auth.accounts[0].base_account.account_number = ($n|tostring)' \
+  contrib/wnibi.json > "$WNIBI_TMP"

227-230: Length ≠ next account_number; derive max(account_number)+1

Using .auth.accounts | length can collide if numbers are sparse. Compute the next open number from the maximum existing account_number instead.

Apply:

 get_auth_accounts_len() {
-  cat $CHAIN_DIR/config/genesis.json | jq '.app_state.auth.accounts | length'
+  jq '.app_state.auth.accounts | length' "$CHAIN_DIR/config/genesis.json"
 }
+
+# Next open auth account_number = max(existing)+1 (or 0 if none)
+get_next_open_account_number() {
+  jq -r '
+    ( [ .app_state.auth.accounts[]
+        | ( .base_account.account_number // .account_number // "0" ) | tonumber
+      ] ) as $nums
+    | ( ($nums|length) == 0 ) ? 0 : (($nums|max) + 1)
+  ' "$CHAIN_DIR/config/genesis.json"
+}

And update usage at line 237 to call get_next_open_account_number (see next comment).

#!/bin/bash
jq -r '[.app_state.auth.accounts[]|.base_account.account_number//"0"|tonumber]|max' "$CHAIN_DIR/config/genesis.json"

225-225: Request manual verification of genesis validation
Ensure the BINARY environment variable points to your node binary (e.g. nibid) and then re-run:

export BINARY="/path/to/your/binary"
"$BINARY" genesis validate --home "$CHAIN_DIR"

Confirm that the command exits cleanly and reports no validation errors.

app/ante/deductfee.go (1)

22-24: Good: compile‑time interface assertion present

This guarantees DeductFeeDecorator implements sdk.AnteDecorator at build time.

Comment on lines +3 to +20
import (
"fmt"
"math"

sdkioerrors "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
"github.com/cosmos/cosmos-sdk/x/auth/types"
gethcommon "github.com/ethereum/go-ethereum/common"

"github.com/NibiruChain/nibiru/v2/app/appconst"
"github.com/NibiruChain/nibiru/v2/eth"
"github.com/NibiruChain/nibiru/v2/x/evm"
evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper"
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix imports: use authtypes and banktypes (current types.BankKeeper won’t compile)

You're importing x/auth/types as "types" and later using types.BankKeeper, which doesn't exist in that package. Use explicit aliases and import bank types.

Apply:

@@
-import (
+import (
 	"fmt"
 	"math"
 
 	sdkioerrors "cosmossdk.io/errors"
 	sdkmath "cosmossdk.io/math"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 	authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
 	authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
-	"github.com/cosmos/cosmos-sdk/x/auth/types"
+	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+	banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
 	gethcommon "github.com/ethereum/go-ethereum/common"
 
 	"github.com/NibiruChain/nibiru/v2/app/appconst"
 	"github.com/NibiruChain/nibiru/v2/eth"
 	"github.com/NibiruChain/nibiru/v2/x/evm"
 	evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper"
 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import (
"fmt"
"math"
sdkioerrors "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
"github.com/cosmos/cosmos-sdk/x/auth/types"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/NibiruChain/nibiru/v2/app/appconst"
"github.com/NibiruChain/nibiru/v2/eth"
"github.com/NibiruChain/nibiru/v2/x/evm"
evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper"
)
import (
"fmt"
"math"
sdkioerrors "cosmossdk.io/errors"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
gethcommon "github.com/ethereum/go-ethereum/common"
"github.com/NibiruChain/nibiru/v2/app/appconst"
"github.com/NibiruChain/nibiru/v2/eth"
"github.com/NibiruChain/nibiru/v2/x/evm"
evmkeeper "github.com/NibiruChain/nibiru/v2/x/evm/keeper"
)
🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 3 to 20, the import alias "types" for
x/auth is incorrect because types.BankKeeper does not exist there; replace the
imports to explicitly alias x/auth/types as authtypes and import x/bank/types as
banktypes (or similar names used elsewhere), then update any usages from
types.BankKeeper to banktypes.BankKeeper and any auth-specific types to
authtypes to match the correct packages so the file compiles.

Comment on lines +31 to +36
accountKeeper authkeeper.AccountKeeper
evmkeeper *evmkeeper.Keeper
bankKeeper types.BankKeeper
feegrantKeeper FeegrantKeeper
txFeeChecker authante.TxFeeChecker
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Use the correct BankKeeper interface type

Update the field to the bank module’s interface; current type reference is invalid.

 type DeductFeeDecorator struct {
 	accountKeeper  authkeeper.AccountKeeper
 	evmkeeper      *evmkeeper.Keeper
-	bankKeeper     types.BankKeeper
+	bankKeeper     banktypes.BankKeeper
 	feegrantKeeper FeegrantKeeper
 	txFeeChecker   authante.TxFeeChecker
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
accountKeeper authkeeper.AccountKeeper
evmkeeper *evmkeeper.Keeper
bankKeeper types.BankKeeper
feegrantKeeper FeegrantKeeper
txFeeChecker authante.TxFeeChecker
}
type DeductFeeDecorator struct {
accountKeeper authkeeper.AccountKeeper
evmkeeper *evmkeeper.Keeper
bankKeeper banktypes.BankKeeper
feegrantKeeper FeegrantKeeper
txFeeChecker authante.TxFeeChecker
}
🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 31 to 36, the bankKeeper field is
currently typed as types.BankKeeper which is not the bank module's keeper
interface; change the field type to the bank module keeper (e.g.
bankkeeper.Keeper or the bank keeper interface exported by x/bank) and update
imports accordingly so the field uses the correct bank keeper interface from the
bank module package.

Comment on lines +93 to +95
if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil {
return fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Use authtypes.FeeCollectorName after alias change

Replace references to types.FeeCollectorName accordingly.

-	if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil {
+	if addr := dfd.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName); addr == nil {
 		return fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName)
 	}
@@
-		err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
+		err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), authtypes.FeeCollectorName, fees)
@@
-	feeCollector := eth.NibiruAddrToEthAddr(accountKeeper.GetModuleAddress(types.FeeCollectorName))
+	feeCollector := eth.NibiruAddrToEthAddr(accountKeeper.GetModuleAddress(authtypes.FeeCollectorName))
@@
-			ctx, stateDB, wnibi, sender, accountKeeper.GetModuleAddress(types.FeeCollectorName),
+			ctx, stateDB, wnibi, sender, accountKeeper.GetModuleAddress(authtypes.FeeCollectorName),

Also applies to: 158-160, 203-205, 213-216

🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 93-95 (and similarly at 158-160, 203-205,
213-216), replace usages of types.FeeCollectorName with
authtypes.FeeCollectorName and update imports to include the auth types package
aliased as authtypes (e.g. import authtypes
"github.com/cosmos/cosmos-sdk/x/auth/types"); ensure any existing types import
isn’t clashing and adjust references accordingly so all FeeCollectorName usages
point to authtypes.FeeCollectorName.

Comment on lines +141 to +146
// DeductFees deducts fees from the given account.
func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper types.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error {
if !fees.IsValid() {
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Correct signatures and validate single‑denom fees

Make types explicit (banktypes.BankKeeper, authtypes.AccountI) and enforce exactly one fee denom (BondDenom) to avoid indexing fees[0] on multi‑denom inputs.

-// DeductFees deducts fees from the given account.
-func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper types.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error {
+// DeductFees deducts fees from the given account.
+func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper banktypes.BankKeeper, ctx sdk.Context, acc authtypes.AccountI, fees sdk.Coins) error {
 	if !fees.IsValid() {
 		return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
 	}
+	if fees.Len() != 1 {
+		return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "exactly 1 fee denom (%s) supported; got %s", appconst.BondDenom, fees.String())
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// DeductFees deducts fees from the given account.
func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper types.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error {
if !fees.IsValid() {
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
// DeductFees deducts fees from the given account.
func DeductFees(
accountKeeper authante.AccountKeeper,
evmKeeper *evmkeeper.Keeper,
bankKeeper banktypes.BankKeeper,
ctx sdk.Context,
acc authtypes.AccountI,
fees sdk.Coins,
) error {
if !fees.IsValid() {
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
if fees.Len() != 1 {
return sdkioerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"exactly 1 fee denom (%s) supported; got %s",
appconst.BondDenom,
fees.String(),
)
}
// ... rest of existing logic ...
}
🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 141-146, update the DeductFees signature
to use concrete types banktypes.BankKeeper and authtypes.AccountI (replace
types.BankKeeper and types.AccountI), and add validation to ensure fees contains
exactly one coin and that coin.Denom equals the chain BondDenom before indexing
fees[0]; if not, return an appropriate wrapped sdkerror (e.g.
ErrInvalidCoins/ErrInsufficientFee). After validating single-denom and denom
equality, proceed to extract the coin and continue existing fee deduction logic.
Ensure necessary imports (banktypes, authtypes, and the BondDenom constant
source) are added.

Comment on lines +147 to +156
gasMeterBefore := ctx.GasMeter()
gasConsumedBefore := gasMeterBefore.GasConsumed()
baseOpGasConsumed := uint64(0)

defer func() {
// NOTE: we have to refund the entire gasMeterBefore because it's modified by AfterOp
// stateDB.getStateObject() reads from state using the local root ctx which affects the gas meter
gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "")
gasMeterBefore.ConsumeGas(gasConsumedBefore+baseOpGasConsumed, "DeductFeeDecorator invariant")
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Gas refund math double‑counts; overcharges gas

You snapshot gas twice (gasConsumedBefore + baseOpGasConsumed where baseOp already includes the former). Preserve the pre‑fallback consumption only and ignore EVM fallback gas as intended.

-	gasMeterBefore := ctx.GasMeter()
-	gasConsumedBefore := gasMeterBefore.GasConsumed()
-	baseOpGasConsumed := uint64(0)
+	gasMeterBefore := ctx.GasMeter()
+	preConsumed := gasMeterBefore.GasConsumed()
 
 	defer func() {
-		// NOTE: we have to refund the entire gasMeterBefore because it's modified by AfterOp
-		// stateDB.getStateObject() reads from state using the local root ctx which affects the gas meter
-		gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "")
-		gasMeterBefore.ConsumeGas(gasConsumedBefore+baseOpGasConsumed, "DeductFeeDecorator invariant")
+		// Reset to the pre‑fallback consumption; EVM reads should not affect gas here.
+		gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "")
+		gasMeterBefore.ConsumeGas(preConsumed, "DeductFeeDecorator invariant")
 	}()
@@
-		baseOpGasConsumed = ctx.GasMeter().GasConsumed()
-		ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
+		ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())

Also applies to: 164-166

🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 147-156, the defer currently refunds then
reconsumes gas using gasConsumedBefore+baseOpGasConsumed which double-counts gas
(baseOpGasConsumed already includes the pre-fallback consumption); change the
logic to refund the entire current meter and then only consume the original
pre-fallback consumption (gasConsumedBefore) so EVM fallback gas is ignored as
intended; make the same correction at the other occurrence around lines 164-166.

Comment on lines +179 to +186
// DeductFeesWithWNIBI tries to deduct fees from WNIBI balance if native deduction fails.
func DeductFeesWithWNIBI(
ctx sdk.Context,
accountKeeper authante.AccountKeeper,
evmKeeper *evmkeeper.Keeper,
acc types.AccountI,
fees sdk.Coins,
) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Correct DeductFeesWithWNIBI signature (AccountI type)

Use authtypes.AccountI to match the auth module interface after alias changes.

-func DeductFeesWithWNIBI(
+func DeductFeesWithWNIBI(
 	ctx sdk.Context,
-	accountKeeper authante.AccountKeeper,
-	evmKeeper *evmkeeper.Keeper,
-	acc types.AccountI,
+	accountKeeper authante.AccountKeeper,
+	evmKeeper *evmkeeper.Keeper,
+	acc authtypes.AccountI,
 	fees sdk.Coins,
 ) error {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// DeductFeesWithWNIBI tries to deduct fees from WNIBI balance if native deduction fails.
func DeductFeesWithWNIBI(
ctx sdk.Context,
accountKeeper authante.AccountKeeper,
evmKeeper *evmkeeper.Keeper,
acc types.AccountI,
fees sdk.Coins,
) error {
// DeductFeesWithWNIBI tries to deduct fees from WNIBI balance if native deduction fails.
func DeductFeesWithWNIBI(
ctx sdk.Context,
accountKeeper authante.AccountKeeper,
evmKeeper *evmkeeper.Keeper,
acc authtypes.AccountI,
fees sdk.Coins,
) error {
// ...existing implementation...
}
🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 179 to 186, the DeductFeesWithWNIBI
function parameter uses types.AccountI but the auth module now exposes
authtypes.AccountI; update the function signature to accept authtypes.AccountI,
change any local references accordingly, and add/adjust the import for authtypes
(or update alias) so the file compiles with the new auth alias. Ensure any
callers are updated if necessary to pass authtypes.AccountI (or keep existing
variables but cast to authtypes.AccountI where appropriate).

Comment on lines +210 to +224
if wnibiBal.Cmp(evm.NativeToWei(feesAmount.BigInt())) >= 0 {
nonce := evmKeeper.GetAccNonce(ctx, sender.Eth)
_, err = evmKeeper.ConvertEvmToCoinForWNIBI(
ctx, stateDB, wnibi, sender, accountKeeper.GetModuleAddress(types.FeeCollectorName),
sdkmath.NewIntFromBigInt(evm.NativeToWei(feesAmount.BigInt())),
nil,
)
if err != nil {
return sdkioerrors.Wrapf(err, "failed to transfer WNIBI from %s to %s", sender.Eth.Hex(), feeCollector.Hex())
}
if err := acc.SetSequence(nonce); err != nil {
return sdkioerrors.Wrapf(err, "failed to set sequence to %d", nonce)
}
accountKeeper.SetAccount(ctx, acc)
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Do not set Cosmos account sequence from EVM nonce

Setting acc.SetSequence(nonce) will corrupt the Cosmos sequence for the fee payer (affects signature verification and replay protection). Remove the nonce fetch and sequence mutation.

-	if wnibiBal.Cmp(evm.NativeToWei(feesAmount.BigInt())) >= 0 {
-		nonce := evmKeeper.GetAccNonce(ctx, sender.Eth)
+	if wnibiBal.Cmp(evm.NativeToWei(feesAmount.BigInt())) >= 0 {
 		_, err = evmKeeper.ConvertEvmToCoinForWNIBI(
 			ctx, stateDB, wnibi, sender, accountKeeper.GetModuleAddress(types.FeeCollectorName),
 			sdkmath.NewIntFromBigInt(evm.NativeToWei(feesAmount.BigInt())),
 			nil,
 		)
 		if err != nil {
 			return sdkioerrors.Wrapf(err, "failed to transfer WNIBI from %s to %s", sender.Eth.Hex(), feeCollector.Hex())
 		}
-		if err := acc.SetSequence(nonce); err != nil {
-			return sdkioerrors.Wrapf(err, "failed to set sequence to %d", nonce)
-		}
-		accountKeeper.SetAccount(ctx, acc)
 		return nil
 	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if wnibiBal.Cmp(evm.NativeToWei(feesAmount.BigInt())) >= 0 {
nonce := evmKeeper.GetAccNonce(ctx, sender.Eth)
_, err = evmKeeper.ConvertEvmToCoinForWNIBI(
ctx, stateDB, wnibi, sender, accountKeeper.GetModuleAddress(types.FeeCollectorName),
sdkmath.NewIntFromBigInt(evm.NativeToWei(feesAmount.BigInt())),
nil,
)
if err != nil {
return sdkioerrors.Wrapf(err, "failed to transfer WNIBI from %s to %s", sender.Eth.Hex(), feeCollector.Hex())
}
if err := acc.SetSequence(nonce); err != nil {
return sdkioerrors.Wrapf(err, "failed to set sequence to %d", nonce)
}
accountKeeper.SetAccount(ctx, acc)
return nil
if wnibiBal.Cmp(evm.NativeToWei(feesAmount.BigInt())) >= 0 {
_, err = evmKeeper.ConvertEvmToCoinForWNIBI(
ctx, stateDB, wnibi, sender, accountKeeper.GetModuleAddress(types.FeeCollectorName),
sdkmath.NewIntFromBigInt(evm.NativeToWei(feesAmount.BigInt())),
nil,
)
if err != nil {
return sdkioerrors.Wrapf(err, "failed to transfer WNIBI from %s to %s", sender.Eth.Hex(), feeCollector.Hex())
}
return nil
}
🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 210 to 224, remove the logic that fetches
the EVM nonce and mutates the Cosmos account sequence: delete the nonce :=
evmKeeper.GetAccNonce(...) line and the block that calls acc.SetSequence(nonce)
and its error handling. Do not modify the Cosmos account's sequence from EVM
data; just perform the WNIBI transfer, keep accountKeeper.SetAccount(ctx, acc)
if needed, and return nil on success.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
app/evmante/evmante_verify_eth_acc.go (2)

76-80: Only fallback when the native balance check truly lacks funds

Right now every error from CheckSenderBalance triggers the WNIBI fallback, masking real failures. Bubble up non-ErrInsufficientFunds errors so we only fallback on genuine insufficiency.

-		if err := keeper.CheckSenderBalance(
-			evm.NativeToWei(acct.BalanceNative.ToBig()), txData,
-		); err == nil {
-			continue
-		}
+		err := keeper.CheckSenderBalance(
+			evm.NativeToWei(acct.BalanceNative.ToBig()), txData,
+		)
+		if err == nil {
+			continue
+		}
+		if !sdkioerrors.IsOf(err, sdkerrors.ErrInsufficientFunds) {
+			return ctx, err
+		}

84-90: Restore the original Bank.StateDB instead of always nil-ing it

The deferred cleanup currently sets Bank.StateDB to nil even when a caller already provided a populated instance, breaking subsequent calls. Capture the original pointer and restore it in the defer.

-		stateDB := anteDec.evmKeeper.Bank.StateDB
-		if stateDB == nil {
-			stateDB = anteDec.evmKeeper.NewStateDB(ctx, anteDec.evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
-		}
-		defer func() {
-			anteDec.evmKeeper.Bank.StateDB = nil
-		}()
+		origStateDB := anteDec.evmKeeper.Bank.StateDB
+		stateDB := origStateDB
+		if stateDB == nil {
+			stateDB = anteDec.evmKeeper.NewStateDB(ctx, anteDec.evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
+		}
+		defer func() {
+			anteDec.evmKeeper.Bank.StateDB = origStateDB
+		}()
app/ante/deductfee.go (3)

153-172: Stop double-counting gas during fallback

gasConsumedBefore + baseOpGasConsumed reconsumes the same amount twice, overcharging users. Preserve only the pre-fallback consumption and drop the redundant bookkeeping.

-	gasMeterBefore := ctx.GasMeter()
-	gasConsumedBefore := gasMeterBefore.GasConsumed()
-	baseOpGasConsumed := uint64(0)
+	gasMeterBefore := ctx.GasMeter()
+	preConsumed := gasMeterBefore.GasConsumed()
@@
-		gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "")
-		gasMeterBefore.ConsumeGas(gasConsumedBefore+baseOpGasConsumed, "DeductFeeDecorator invariant")
+		gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "")
+		gasMeterBefore.ConsumeGas(preConsumed, "DeductFeeDecorator invariant")
@@
-		baseOpGasConsumed = ctx.GasMeter().GasConsumed()
-		ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
+		ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())

217-229: Do not overwrite the Cosmos sequence with the EVM nonce

Setting acc.SetSequence(nonce) ties the Cosmos seq to the EVM nonce, breaking signature verification and replay protection for future Cosmos msgs. Just perform the WNIBI transfer; leave the sequence alone.

-		if err := acc.SetSequence(nonce); err != nil {
-			return sdkioerrors.Wrapf(err, "failed to set sequence to %d", nonce)
-		}
-		accountKeeper.SetAccount(ctx, acc)
 		return nil

(Remove the nonce := … line above as well.)


13-36: Fix keeper imports and type usage so the file compiles

types.BankKeeper doesn’t exist in the auth package, so this file won’t build. Alias auth types, import the bank keeper, and update every usage accordingly (including FeeCollectorName and account interfaces).

-import (
+import (
 	"fmt"
 	"math"
 
 	sdkioerrors "cosmossdk.io/errors"
 	sdkmath "cosmossdk.io/math"
 	sdk "github.com/cosmos/cosmos-sdk/types"
 	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
 	authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
 	authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
-	"github.com/cosmos/cosmos-sdk/x/auth/types"
+	authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
+	bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
 	gethcommon "github.com/ethereum/go-ethereum/common"
@@
 type DeductFeeDecorator struct {
 	accountKeeper  authkeeper.AccountKeeper
 	evmkeeper      *evmkeeper.Keeper
-	bankKeeper     types.BankKeeper
+	bankKeeper     *bankkeeper.Keeper
 	feegrantKeeper FeegrantKeeper
 	txFeeChecker   authante.TxFeeChecker
 }
@@
-func NewDeductFeeDecorator(ak authkeeper.AccountKeeper, ek *evmkeeper.Keeper, bk types.BankKeeper, fk FeegrantKeeper, tfc authante.TxFeeChecker) DeductFeeDecorator {
+func NewDeductFeeDecorator(ak authkeeper.AccountKeeper, ek *evmkeeper.Keeper, bk *bankkeeper.Keeper, fk FeegrantKeeper, tfc authante.TxFeeChecker) DeductFeeDecorator {
@@
-	if addr := dfd.accountKeeper.GetModuleAddress(types.FeeCollectorName); addr == nil {
-		return fmt.Errorf("fee collector module account (%s) has not been set", types.FeeCollectorName)
+	if addr := dfd.accountKeeper.GetModuleAddress(authtypes.FeeCollectorName); addr == nil {
+		return fmt.Errorf("fee collector module account (%s) has not been set", authtypes.FeeCollectorName)
@@
-func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper types.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error {
+func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper *bankkeeper.Keeper, ctx sdk.Context, acc authtypes.AccountI, fees sdk.Coins) error {

(Apply the same authtypes.FeeCollectorName and authtypes.AccountI replacement in the rest of the file.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a703178 and 7b9a5b5.

📒 Files selected for processing (2)
  • app/ante/deductfee.go (1 hunks)
  • app/evmante/evmante_verify_eth_acc.go (2 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-31T18:28:26.466Z
Learnt from: Unique-Divine
PR: NibiruChain/nibiru#2095
File: x/evm/keeper/bank_extension.go:0-0
Timestamp: 2024-10-31T18:28:26.466Z
Learning: When implementing the `bankkeeper.Keeper` interface in `x/evm/keeper/bank_extension.go`, methods must use non-pointer receivers to satisfy the interface requirements.

Applied to files:

  • app/ante/deductfee.go
🧬 Code graph analysis (2)
app/evmante/evmante_verify_eth_acc.go (3)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
x/evm/statedb/config.go (1)
  • TxConfig (15-20)
x/evm/mock_msg.go (1)
  • MOCK_GETH_MESSAGE (8-21)
app/ante/deductfee.go (7)
x/evm/keeper/keeper.go (1)
  • Keeper (27-54)
app/ante/expected_keeper.go (1)
  • FeegrantKeeper (8-10)
app/appconst/appconst.go (1)
  • BondDenom (22-22)
x/evm/statedb/statedb.go (1)
  • StateDB (45-85)
x/evm/mock_msg.go (1)
  • MOCK_GETH_MESSAGE (8-21)
eth/eth_account.go (1)
  • NibiruAddrToEthAddr (19-21)
x/evm/const.go (1)
  • NativeToWei (142-145)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: unit-tests
  • GitHub Check: build
  • GitHub Check: proto-gen-rs
  • GitHub Check: e2e-evm
  • GitHub Check: integration-tests
  • GitHub Check: lint

Comment on lines +147 to +185
// DeductFees deducts fees from the given account.
func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper types.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error {
if !fees.IsValid() {
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}

gasMeterBefore := ctx.GasMeter()
gasConsumedBefore := gasMeterBefore.GasConsumed()
baseOpGasConsumed := uint64(0)

defer func() {
// NOTE: we have to refund the entire gasMeterBefore because it's modified by AfterOp
// stateDB.getStateObject() reads from state using the local root ctx which affects the gas meter
gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "")
gasMeterBefore.ConsumeGas(gasConsumedBefore+baseOpGasConsumed, "DeductFeeDecorator invariant")
}()

if fees[0].Denom == appconst.BondDenom {
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
if err == nil {
return nil
}

baseOpGasConsumed = ctx.GasMeter().GasConsumed()
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())

// fallback to WNIBI
err = DeductFeesWithWNIBI(ctx, accountKeeper, evmKeeper, acc, fees)
if err == nil {
return nil
}

return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient balance across supported gas tokens to cover %s", fees[0].Amount)
} else {
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee denom must be %s, got %s", appconst.BondDenom, fees[0].Denom)
}
}

// DeductFeesWithWNIBI tries to deduct fees from WNIBI balance if native deduction fails.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Validate single-denom fees before indexing

Both deduction paths assume fees[0] exists, so multi-denom or empty inputs panic or misclassify valid fees. Enforce exactly one coin before touching fees[0].

 	if !fees.IsValid() {
 		return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
 	}
+	if fees.Len() != 1 {
+		return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "exactly one fee denom (%s) is supported; got %s", appconst.BondDenom, fees)
+	}
@@
 func DeductFeesWithWNIBI(
 	ctx sdk.Context,
 	accountKeeper authante.AccountKeeper,
 	evmKeeper *evmkeeper.Keeper,
-	acc types.AccountI,
+	acc authtypes.AccountI,
 	fees sdk.Coins,
 ) error {
+	if fees.Len() != 1 {
+		return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "exactly one fee denom (%s) is supported; got %s", appconst.BondDenom, fees)
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// DeductFees deducts fees from the given account.
func DeductFees(accountKeeper authante.AccountKeeper, evmKeeper *evmkeeper.Keeper, bankKeeper types.BankKeeper, ctx sdk.Context, acc types.AccountI, fees sdk.Coins) error {
if !fees.IsValid() {
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
gasMeterBefore := ctx.GasMeter()
gasConsumedBefore := gasMeterBefore.GasConsumed()
baseOpGasConsumed := uint64(0)
defer func() {
// NOTE: we have to refund the entire gasMeterBefore because it's modified by AfterOp
// stateDB.getStateObject() reads from state using the local root ctx which affects the gas meter
gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "")
gasMeterBefore.ConsumeGas(gasConsumedBefore+baseOpGasConsumed, "DeductFeeDecorator invariant")
}()
if fees[0].Denom == appconst.BondDenom {
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
if err == nil {
return nil
}
baseOpGasConsumed = ctx.GasMeter().GasConsumed()
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
// fallback to WNIBI
err = DeductFeesWithWNIBI(ctx, accountKeeper, evmKeeper, acc, fees)
if err == nil {
return nil
}
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFunds, "insufficient balance across supported gas tokens to cover %s", fees[0].Amount)
} else {
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "fee denom must be %s, got %s", appconst.BondDenom, fees[0].Denom)
}
}
// DeductFeesWithWNIBI tries to deduct fees from WNIBI balance if native deduction fails.
// DeductFees deducts fees from the given account.
func DeductFees(
accountKeeper authante.AccountKeeper,
evmKeeper *evmkeeper.Keeper,
bankKeeper types.BankKeeper,
ctx sdk.Context,
acc types.AccountI,
fees sdk.Coins,
) error {
if !fees.IsValid() {
return sdkioerrors.Wrapf(sdkerrors.ErrInsufficientFee, "invalid fee amount: %s", fees)
}
if fees.Len() != 1 {
return sdkioerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"exactly one fee denom (%s) is supported; got %s",
appconst.BondDenom,
fees,
)
}
gasMeterBefore := ctx.GasMeter()
gasConsumedBefore := gasMeterBefore.GasConsumed()
baseOpGasConsumed := uint64(0)
defer func() {
// NOTE: we have to refund the entire gasMeterBefore because it's modified by AfterOp
// stateDB.getStateObject() reads from state using the local root ctx which affects the gas meter
gasMeterBefore.RefundGas(gasMeterBefore.GasConsumed(), "")
gasMeterBefore.ConsumeGas(gasConsumedBefore+baseOpGasConsumed, "DeductFeeDecorator invariant")
}()
if fees[0].Denom == appconst.BondDenom {
err := bankKeeper.SendCoinsFromAccountToModule(ctx, acc.GetAddress(), types.FeeCollectorName, fees)
if err == nil {
return nil
}
baseOpGasConsumed = ctx.GasMeter().GasConsumed()
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
// fallback to WNIBI
err = DeductFeesWithWNIBI(ctx, accountKeeper, evmKeeper, acc, fees)
if err == nil {
return nil
}
return sdkioerrors.Wrapf(
sdkerrors.ErrInsufficientFunds,
"insufficient balance across supported gas tokens to cover %s",
fees[0].Amount,
)
}
return sdkioerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"fee denom must be %s, got %s",
appconst.BondDenom,
fees[0].Denom,
)
}
// DeductFeesWithWNIBI tries to deduct fees from WNIBI balance if native deduction fails.
func DeductFeesWithWNIBI(
ctx sdk.Context,
accountKeeper authante.AccountKeeper,
evmKeeper *evmkeeper.Keeper,
acc authtypes.AccountI,
fees sdk.Coins,
) error {
if fees.Len() != 1 {
return sdkioerrors.Wrapf(
sdkerrors.ErrInsufficientFee,
"exactly one fee denom (%s) is supported; got %s",
appconst.BondDenom,
fees,
)
}
// ... rest of implementation unchanged
}
🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 147 to 185, the code indexes fees[0]
without ensuring the coins slice has exactly one entry, which can panic or
misclassify valid fees; add a guard after validating fees to require len(fees)
== 1 (and non-empty) and return a clear sdkerrors.ErrInsufficientFee-wrapped
error if not, so both branches safely use fees[0] only when a single-denom fee
is provided. Ensure the new check runs before any access to fees[0] and keep
existing error types/messages consistent with surrounding logic.

Comment on lines +195 to +201
stateDB := evmKeeper.Bank.StateDB
if stateDB == nil {
stateDB = evmKeeper.NewStateDB(ctx, evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
}
defer func() {
evmKeeper.Bank.StateDB = nil
}()
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Don’t clobber EVM bank state after the fallback

This defer always sets Bank.StateDB to nil, wiping any existing shared state. Track the original pointer and restore it just like the verify decorator.

-	stateDB := evmKeeper.Bank.StateDB
-	if stateDB == nil {
-		stateDB = evmKeeper.NewStateDB(ctx, evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
-	}
-	defer func() {
-		evmKeeper.Bank.StateDB = nil
-	}()
+	origStateDB := evmKeeper.Bank.StateDB
+	stateDB := origStateDB
+	if stateDB == nil {
+		stateDB = evmKeeper.NewStateDB(ctx, evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
+	}
+	defer func() {
+		evmKeeper.Bank.StateDB = origStateDB
+	}()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
stateDB := evmKeeper.Bank.StateDB
if stateDB == nil {
stateDB = evmKeeper.NewStateDB(ctx, evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
}
defer func() {
evmKeeper.Bank.StateDB = nil
}()
origStateDB := evmKeeper.Bank.StateDB
stateDB := origStateDB
if stateDB == nil {
stateDB = evmKeeper.NewStateDB(ctx, evmKeeper.TxConfig(ctx, gethcommon.Hash{}))
}
defer func() {
evmKeeper.Bank.StateDB = origStateDB
}()
🤖 Prompt for AI Agents
In app/ante/deductfee.go around lines 195 to 201, the defer unconditionally sets
evmKeeper.Bank.StateDB = nil which clobbers any preexisting shared StateDB;
instead capture the original StateDB pointer before the fallback (origStateDB :=
evmKeeper.Bank.StateDB), only set the fallback when nil, and in the defer
restore the original pointer (evmKeeper.Bank.StateDB = origStateDB) so you don't
wipe an existing shared state — follow the same save/restore pattern used in the
verify decorator.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c5fd722 and d618806.

📒 Files selected for processing (1)
  • CHANGELOG.md (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: lint
  • GitHub Check: proto-gen-rs
  • GitHub Check: build
  • GitHub Check: unit-tests
  • GitHub Check: integration-tests
  • GitHub Check: e2e-evm

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.

3 participants