Skip to content

feat: Multi VM Gas Token Flexibility - #2384

Open
expertdicer wants to merge 1689 commits into
mainfrom
expertdicer/multi-gas-token
Open

feat: Multi VM Gas Token Flexibility #2384
expertdicer wants to merge 1689 commits into
mainfrom
expertdicer/multi-gas-token

Conversation

@expertdicer

@expertdicer expertdicer commented Sep 15, 2025

Copy link
Copy Markdown
Contributor

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.

dependabot Bot and others added 30 commits September 19, 2024 02:01
* chore(deps-dev): bump axios from 1.7.3 to 1.7.4 in /x/evm/embeds

Bumps [axios](https://github.com/axios/axios) from 1.7.3 to 1.7.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](axios/axios@v1.7.3...v1.7.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updated changelog - dependabot

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique-Divine <Unique-Divine@users.noreply.github.com>
* test(evm): backend tests with test network and real txs

* chore: changelog update
* feat(evm): evm tx indexer service implemented

* feat(evm): evm-indexer cli command to fill the indexing gaps

* chore: lint

* chore: changelog update

* fix: graceful stopping of the evm indexer

* fix: race conditions on evm indexer service

* fix: race condition within evm indexer
* feat(evm): ante handler to prohibit authz grant evm messages

* feat(authz): rejecting authz exec messages with MsgEthereumTx inside

* chore: lint

* chore: typo fix

---------

Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Kevin Yang <5478483+k-yang@users.noreply.github.com>
)

* chore(deps): bump bufbuild/buf-setup-action from 1.36.0 to 1.42.0

Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.36.0 to 1.42.0.
- [Release notes](https://github.com/bufbuild/buf-setup-action/releases)
- [Commits](bufbuild/buf-setup-action@v1.36.0...v1.42.0)

---
updated-dependencies:
- dependency-name: bufbuild/buf-setup-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updated changelog - dependabot

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique-Divine <Unique-Divine@users.noreply.github.com>
…rectory (#2050)

* gitignore and issue comment

* refactor(oracle): remove unused code and collapse empty client/cli directory

* chore: changelog

* fix last few import paths
…ute Wasm contracts. (#2054)

* Implement wasm precompile in evm/embeds

* feat(evm-precompile): Precompile for one-way EVM calls to invoke/execute Wasm contracts.

* impl remaining Wasm.sol methods and document thoroughly

* more tests and docs
)

Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.42.0 to 1.43.0.
- [Release notes](https://github.com/bufbuild/buf-setup-action/releases)
- [Commits](bufbuild/buf-setup-action@v1.42.0...v1.43.0)

---
updated-dependencies:
- dependency-name: bufbuild/buf-setup-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
* refactor(evm): made all eth events typed

* refactor(evm): tests for evm events upgraded

* chore: cleanup

* chore: cleanup

* chore: cleanup

* chore: cosmetic fixes

---------

Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
* feat(oracle): add solidity interface

* feat(oracle): fix solidity IOracle.sol interface

* feat(evm): add oracle precompile

* chore: update changelog

* Update x/evm/embeds/contracts/IOracle.sol

Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>

* Update x/evm/embeds/contracts/IOracle.sol

Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>

* Update x/evm/precompile/oracle.go

Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>

* Update x/evm/precompile/oracle.go

Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>

* Update IOracle.json

* chore: linter

---

Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Oleg Nikonychev <oleg.nikonychev@gmail.com>
* chore(deps): bump github.com/CosmWasm/wasmvm from 1.5.0 to 1.5.5

Bumps [github.com/CosmWasm/wasmvm](https://github.com/CosmWasm/wasmvm) from 1.5.0 to 1.5.5.
- [Release notes](https://github.com/CosmWasm/wasmvm/releases)
- [Changelog](https://github.com/CosmWasm/wasmvm/blob/main/CHANGELOG.md)
- [Commits](CosmWasm/wasmvm@v1.5.0...v1.5.5)

---
updated-dependencies:
- dependency-name: github.com/CosmWasm/wasmvm
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updated changelog - dependabot

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique-Divine <Unique-Divine@users.noreply.github.com>
* refactor(evm)!: Refactor out dead code from the evm.Params

* fix(evm-genesis_test)

* fix tests
)

Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.43.0 to 1.44.0.
- [Release notes](https://github.com/bufbuild/buf-setup-action/releases)
- [Commits](bufbuild/buf-setup-action@v1.43.0...v1.44.0)

---
updated-dependencies:
- dependency-name: bufbuild/buf-setup-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
* chore(deps): bump docker/build-push-action from 5 to 6

Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 5 to 6.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](docker/build-push-action@v5...v6)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updated changelog - dependabot

---------

Signed-off-by: Unique-Divine <Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique-Divine <Unique-Divine@users.noreply.github.com>
…on (#2074)

* fix(evm-keeper): better utilize ERC20 metadata during FunToken creation

* chore: changelog
* fix(gas-fees): use effective gas price in RefundGas

* Add even more unit clarity for base fee and delete code

* fix e2e test and address coderabbitai PR comments

* red, green, refactor

* red, green, refactor

* fix e2e tests
#2088)

* refactor(evm): remove outdated comment and improper error message text

* chore: changelog
…nt (#2084)

* refactor: Collapse e2e directory -> evm-e2e

* remove unused ADR directory. Save content elsewhere for later use

* feat(evm-forge): Add foundry (forge) template with working commands

* chore: changelog
…execution (#2089)

* fix(erc20): better handling of gas consumption within erc20 contract execution

* chore: changelog update

* fix: refactored CallContractWithInput

* small PR feedback changes

oops

oops 2

---------

Co-authored-by: Unique-Divine <realuniquedivine@gmail.com>
…emming from uncommitted, dirty journal entries in the EVM StateDB (#2086)
… 4.0.3 to 4.0.4 in /x/evm/embeds (#2087)

* chore(deps): bump secp256k1 from 4.0.3 to 4.0.4 in /x/evm/embeds

Bumps [secp256k1](https://github.com/cryptocoinjs/secp256k1-node) from 4.0.3 to 4.0.4.
- [Release notes](https://github.com/cryptocoinjs/secp256k1-node/releases)
- [Commits](cryptocoinjs/secp256k1-node@v4.0.3...v4.0.4)

---
updated-dependencies:
- dependency-name: secp256k1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* chore: changelog

---------

Signed-off-by: Unique-Divine <realuniquedivine@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique-Divine <realuniquedivine@gmail.com>
* feat: add simple validation for fun token fee in set params

* chore: changelog

* linting changelog

* lint: fix lint issues

---------

Co-authored-by: Unique-Divine <realuniquedivine@gmail.com>
)

* chore(deps): bump bufbuild/buf-setup-action from 1.44.0 to 1.45.0

Bumps [bufbuild/buf-setup-action](https://github.com/bufbuild/buf-setup-action) from 1.44.0 to 1.45.0.
- [Release notes](https://github.com/bufbuild/buf-setup-action/releases)
- [Commits](bufbuild/buf-setup-action@v1.44.0...v1.45.0)

---
updated-dependencies:
- dependency-name: bufbuild/buf-setup-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updated changelog - dependabot

---------

Signed-off-by: Unique-Divine <Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
* chore(deps): bump github.com/btcsuite/btcd from 0.24.0 to 0.24.2

Bumps [github.com/btcsuite/btcd](https://github.com/btcsuite/btcd) from 0.24.0 to 0.24.2.
- [Release notes](https://github.com/btcsuite/btcd/releases)
- [Changelog](https://github.com/btcsuite/btcd/blob/master/CHANGES)
- [Commits](btcsuite/btcd@v0.24.0...v0.24.2)

---
updated-dependencies:
- dependency-name: github.com/btcsuite/btcd
  dependency-type: direct:production
...

* Updated changelog - dependabot

---------

Signed-off-by: Unique-Divine <Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique Divine <51418232+Unique-Divine@users.noreply.github.com>
Co-authored-by: Unique-Divine <Unique-Divine@users.noreply.github.com>
…le (#2098)

* test(evm): statedb tests for race conditions within funtoken precompile

* chore: changelog update
…er Nibiru bank keeper that respects the EVM (#2094)

* statedb: add cacheing for multistore before precompile runs

* messy, working first version that allows for precompile reversion

* wip!: Save checkpoint.
1. Created NibiruBankKeeper with safety around NIBI transfers inside of
   EthereumTx.
2. The "PrecompileCalled" JournalChange now has a propery implementation
   and a strong test case to show that reverting the precompile calls
   works as intended.
3. Remove unneeded functions created for testing with low-level struct
   fields.

* chore: changelog

* finalize bank keeper changes

* revert to previous commit 7f904a0

* fix strange ignored file issue

* remove new bank keeper

* chore: comments from self-review
* feat: fix transfer for native to erc20

* chore: changelog

* feat: check transfer occurred by ensuring new balance is higher than previous one

* feat: fix the 0 check

* fix: fix error message

* feat: move transfer logic to erc20.go

* chore: changelog

* fix: make the FunToken precompile return the sent amount

---------

Co-authored-by: Unique-Divine <realuniquedivine@gmail.com>
* feat: add validation for multi message execution wasm

* chore: changelog

* merge conflicts + simplify

---------

Co-authored-by: Unique-Divine <realuniquedivine@gmail.com>
@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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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.

Comment thread app/evmante/evmante_gas_consume.go Outdated
Comment thread app/evmante/evmante_gas_consume.go Outdated
Comment thread app/evmante/evmante_verify_eth_acc.go
Comment thread app/evmante/evmante_verify_eth_acc.go Outdated
Comment thread contrib/scripts/localnet.sh
Comment thread evm-e2e/test/wnibi_gas.test.ts Outdated
Comment thread evm-e2e/test/wnibi_gas.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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.

Comment thread app/evmante/evmante_verify_eth_acc.go
Unique-Divine and others added 8 commits September 15, 2025 21:00
…ng. Add composite Chainlink-like oracle (#2385)

* impl: solidity changes to compiler deps,
(2) docs added for TestInfiniteRecursionERC20.sol
(3) implementation for NibiruOracleChainLinkMulti.json

* evm: 63/64 gas clamp for ERC20 calls; improved VM error surfacing; add composite Chainlink-like oracle

* fix: pr comments and panic safety

* refactor: minor cleanup

---------

Co-authored-by: Oleg Nikonychev <oleg.nikonychev@gmail.com>
* chore: erc20 token registry new tokens: cbBTC, uBTC

* chore: changelog
* chore(deps): bump axios from 1.9.0 to 1.12.1 in /x/evm/embeds

Bumps [axios](https://github.com/axios/axios) from 1.9.0 to 1.12.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](axios/axios@v1.9.0...v1.12.1)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.12.1
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* Updated changelog - dependabot

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Unique-Divine <51418232+Unique-Divine@users.noreply.github.com>
Comment thread app/ante/deductfee.go
Comment thread evm-e2e/test/utils.ts Outdated
Comment thread evm-e2e/test/setup.ts
Comment thread x/evm/keeper/keeper.go Outdated
Comment thread contrib/scripts/localnet.sh
Comment thread contrib/scripts/localnet.sh
Comment thread app/evmante/evmante_verify_eth_acc_test.go Outdated
Comment thread app/evmante/evmante_gas_consume_test.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 }()

Comment thread app/ante/deductfee.go Outdated
Comment thread contrib/scripts/localnet.sh Outdated
Comment thread contrib/scripts/localnet.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 thread app/ante/deductfee.go
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
Copy Markdown
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 thread app/ante/deductfee.go
Comment on lines +31 to +36
accountKeeper authkeeper.AccountKeeper
evmkeeper *evmkeeper.Keeper
bankKeeper types.BankKeeper
feegrantKeeper FeegrantKeeper
txFeeChecker authante.TxFeeChecker
}

Copy link
Copy Markdown
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 thread app/ante/deductfee.go
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
Copy Markdown
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 thread app/ante/deductfee.go
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
Copy Markdown
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 thread app/ante/deductfee.go
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
Copy Markdown
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 thread app/ante/deductfee.go
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
Copy Markdown
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 thread app/ante/deductfee.go
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
Copy Markdown
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.

Unique-Divine and others added 2 commits October 2, 2025 19:03
* feat(proto): impl script for gRPC Gateway REST doc generation

* fix: add PR feedback

* chore: prettier

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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 thread app/ante/deductfee.go
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
Copy Markdown
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 thread app/ante/deductfee.go
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
Copy Markdown
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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

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

Comment thread CHANGELOG.md
@Unique-Divine
Unique-Divine force-pushed the main branch 4 times, most recently from 22fb261 to ac05154 Compare July 2, 2026 11:43
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.