## State & Module Vulnerability Patterns

> Updated for Cosmos SDK v0.53.x. State management, module interaction, and data handling patterns (§11-23) covering bookkeeping, bank keeper, pagination, events, transaction replay, governance, arithmetic, encoding, and deprecated modules. Referenced by [SKILL.md](../SKILL.md) checklist items via `§` anchors.

### 11 BROKEN BOOKKEEPING

**Description**: Custom internal accounting alongside `x/bank` module becomes inconsistent when direct token transfers bypass internal bookkeeping.

**Detection Patterns**:
```go
// VULNERABLE: Internal bookkeeping separate from x/bank
type Keeper struct {
    // Internal tracking of balances
    userBalances map[string]sdk.Coins  // NOT synchronized with x/bank!
}

func (k Keeper) Deposit(ctx context.Context, user string, amount sdk.Coins) {
    // Updates internal bookkeeping
    k.userBalances[user] = k.userBalances[user].Add(amount...)

    // Also updates x/bank
    k.bankKeeper.SendCoins(ctx, sender, moduleAccount, amount)
}

// PROBLEM: Direct IBC transfer bypasses internal bookkeeping!
// User receives tokens via IBC -> x/bank updated but userBalances not updated
// Invariant violated: sum(userBalances) != bankKeeper.GetSupply()
```

**What to Check**:
- [ ] No custom balance tracking alongside x/bank
- [ ] OR custom tracking uses blocklist to prevent unexpected transfers
- [ ] Invariant checks compare internal accounting to x/bank
- [ ] Module accounts use SendEnabled parameter

**Mitigation**:
```go
// OPTION 1: Use blocklist to prevent unexpected transfers
func (k Keeper) BeforeTokenTransfer(ctx context.Context, from, to string) error {
    if !k.IsAuthorizedTransfer(ctx, from, to) {
        return errors.New("direct transfers blocked")
    }
    return nil
}

// OPTION 2: Use SendEnabled parameter
// In x/bank params, set SendEnabled = false for your token

// OPTION 3: Don't maintain separate bookkeeping
// Use x/bank as source of truth, query when needed
func (k Keeper) GetUserBalance(ctx context.Context, user string) sdk.Coins {
    addr := sdk.AccAddress(user)
    return k.bankKeeper.GetAllBalances(ctx, addr)
}
```

**Invariant Testing**:
```go
func (k Keeper) InvariantCheck(ctx sdk.Context) error {
    internalTotal := sdk.NewCoins()
    k.IterateBalances(ctx, func(addr string, balance sdk.Coins) bool {
        internalTotal = internalTotal.Add(balance...)
        return false
    })

    moduleBalance := k.bankKeeper.GetAllBalances(ctx, k.moduleAccount)
    if !internalTotal.Equal(moduleBalance) {
        return fmt.Errorf("bookkeeping mismatch: internal=%v bank=%v",
            internalTotal, moduleBalance)
    }
    return nil
}
```

**References**: building-secure-contracts/not-so-smart-contracts/cosmos/broken_bookkeeping

---

### 12 BANK KEEPER & TOKEN SECURITY

**Description**: Any module with access to `x/bank`'s keeper can mint, burn, and transfer tokens at will. The bank module provides blocklist, `SendEnabled`, and `SendRestriction` mechanisms to control this, but they must be configured correctly.

**What to Check**:
- [ ] Modules with bank keeper access: [all methods are fully exposed](https://github.com/cosmos/cosmos-sdk/issues/7093) — review every usage for unauthorized mint/burn/transfer
- [ ] [Blocklist](https://docs.cosmos.network/master/modules/bank/02_keepers.html#blocklisting-addresses) prevents unexpected transfers to module accounts
  - Addresses blocked during startup via bank keeper's argument. Default blocked addresses come from `maccPerms` via `BlockedAddresses()`
  - `x/gov` module address explicitly **removed** from blocklist (receives proposal deposits) — verify custom chains make the same intentional exclusions
  - Blocklist explicitly checked [whenever new transfer functionality is implemented](https://github.com/cosmos/cosmos-sdk/issues/8463#issuecomment-801046285)
- [ ] [`SendEnabled`](https://docs.cosmos.network/v0.45/modules/bank/05_params.html#parameters) prevents unexpected transfers of specific denominations. `DefaultSendEnabled` is true by default
- [ ] `SendRestriction` functions (`AppendSendRestriction`, `PrependSendRestriction`) reviewed if used. Order matters (Prepend vs Append). `ClearSendRestriction` removes ALL restrictions. Since v0.53.0, `SendCoins` checks restrictions BEFORE deducting coins (#24106, #24053)
- [ ] `SpendableCoins` used instead of `GetBalance` for accounts with [locked/vesting funds](https://github.com/umee-network/umee/issues/896)
- [ ] [Deflationary tokens](https://github.com/code-423n4/2021-08-gravitybridge-findings/issues/62) considered in transfer accounting
- [ ] If `x/mint` uses custom `MintFn` (via `keeper.WithMintFn`), no unbounded minting or inflation bypass. Old `InflationCalculationFn` must be `nil`

**References**: [Desmos blocklist vulnerability](https://github.com/desmos-labs/desmos/compare/v0.15.3...v0.15.4)

---

### 13 UNBOUNDED PAGINATION / QUERY DoS

**Description**: gRPC query endpoints using `types/query.Paginate` accept a `Limit` field from clients with no enforced maximum. A malicious client can set `Limit: math.MaxUint64`, forcing the node to iterate over the entire store in a single request, causing OOM or extended blocking. The SDK's `DefaultLimit` is 100, but it is only applied when `Limit == 0` — any non-zero value is accepted as-is. Recent fix `d9d77304fd` addressed a related issue in `x/auth/tx` and `x/bank` genesis, but the core `types/query/pagination.go` still lacks a hard cap.

**Detection Patterns**:
```go
// VULNERABLE: No max limit enforcement
func (k Keeper) AllRecords(ctx context.Context, req *types.QueryAllRecordsRequest) (*types.QueryAllRecordsResponse, error) {
    store := k.storeService.OpenKVStore(ctx)
    // Client controls req.Pagination.Limit with no upper bound!
    results, pageRes, err := query.CollectionPaginate(ctx, k.Records, req.Pagination,
        func(key string, value types.Record) (types.Record, error) {
            return value, nil
        },
    )
    // ...
}
```

**What to Check**:
- [ ] All gRPC query handlers enforce a maximum pagination limit
- [ ] Custom query endpoints that iterate stores have bounded iteration
- [ ] `CountTotal = true` requests are bounded (counting all records is expensive)
- [ ] Genesis export with large state is tested

**Mitigation**:
```go
// SECURE: Enforce maximum pagination limit
const MaxPaginationLimit = 1000

func sanitizePagination(req *query.PageRequest) *query.PageRequest {
    if req == nil {
        req = &query.PageRequest{}
    }
    if req.Limit == 0 || req.Limit > MaxPaginationLimit {
        req.Limit = MaxPaginationLimit
    }
    return req
}

func (k Keeper) AllRecords(ctx context.Context, req *types.QueryAllRecordsRequest) (*types.QueryAllRecordsResponse, error) {
    req.Pagination = sanitizePagination(req.Pagination)
    // ...
}
```

---

### 14 EVENT OVERRIDE / SUPPRESSION

**Description**: Cosmos SDK v0.53 introduced `EventManager.OverrideEvents()` which replaces all previously emitted events with a new set. If called in module code (especially in message handlers or ABCI hooks), it can silently suppress events emitted by earlier modules in the same block or by the SDK framework itself. This can break indexers, IBC relayers, and monitoring tools that depend on events.

**Detection Patterns**:
```go
// VULNERABLE: Overriding events suppresses earlier module events
func (ms msgServer) Execute(ctx context.Context, msg *types.MsgExecute) (*types.MsgExecuteResponse, error) {
    sdkCtx := sdk.UnwrapSDKContext(ctx)
    // This wipes ALL events emitted so far in this transaction/block!
    sdkCtx.EventManager().OverrideEvents(sdk.Events{myEvent})
    // Events from auth, bank, etc. are now gone
}
```

**What to Check**:
- [ ] No `OverrideEvents()` calls in module message handlers or ABCI methods
- [ ] If `OverrideEvents()` is used, verify it only operates on a child context's EventManager, not the parent
- [ ] Events from prior modules are not silently dropped
- [ ] IBC relayer and indexer compatibility verified after event changes

**Mitigation**:
```go
// SECURE: Use EmitTypedEvent to add events, never OverrideEvents
func (ms msgServer) Execute(ctx context.Context, msg *types.MsgExecute) (*types.MsgExecuteResponse, error) {
    sdkCtx := sdk.UnwrapSDKContext(ctx)
    sdkCtx.EventManager().EmitTypedEvent(&types.EventExecute{...})
    // Preserves all existing events
}
```

---

### 15 UNORDERED TRANSACTION REPLAY

**Description**: Cosmos SDK v0.53 supports unordered transactions (`TxBody.Unordered = true`) with timeout-timestamp-based replay protection instead of sequence numbers. These transactions use nonce tracking in `x/auth` with a `PreBlocker` that cleans up expired nonces. Vulnerabilities arise from: (1) insufficient timeout duration allowing replay after nonce cleanup, (2) chains that don't enable the `UnorderedTxDecorator` or set `maxTxTimeoutDuration` too high, (3) nonce storage DoS by flooding with unique unordered transactions.

**Detection Patterns**:
```go
// VULNERABLE: Unordered tx support enabled without proper timeout limits
func NewAnteHandler(options HandlerOptions) sdk.AnteHandler {
    decorators := []sdk.AnteDecorator{
        ante.NewSigVerificationDecorator(
            options.AccountKeeper,
            options.SignModeHandler,
            // Missing: WithMaxUnorderedTxTimeoutDuration
            // Missing: WithUnorderedTxGasCost
        ),
    }
}

// VULNERABLE: Application logic assumes sequence-based ordering
func (k Keeper) ProcessUserAction(ctx context.Context, msg *types.MsgAction) error {
    // With unordered txs, multiple actions from the same sender
    // can execute in any order within the same block!
    lastAction := k.GetLastAction(ctx, msg.Sender)
    // This ordering assumption breaks with unordered txs
}
```

**What to Check**:
- [ ] `WithMaxUnorderedTxTimeoutDuration` is set to a reasonable value (default: 10 minutes)
- [ ] `WithUnorderedTxGasCost` is configured (default: 2240 gas) to prevent nonce flooding
- [ ] Application logic doesn't assume transaction ordering within a block
- [ ] PreBlocker includes `RemoveExpiredUnorderedNonces` cleanup
- [ ] State-dependent operations handle concurrent unordered transactions safely

**Mitigation**:
```go
// SECURE: Configure unordered tx parameters
ante.NewSigVerificationDecorator(
    options.AccountKeeper,
    options.SignModeHandler,
    ante.WithMaxUnorderedTxTimeoutDuration(10 * time.Minute),
    ante.WithUnorderedTxGasCost(2240),
)

// SECURE: Don't rely on transaction ordering
func (k Keeper) ProcessUserAction(ctx context.Context, msg *types.MsgAction) error {
    // Use idempotent operations or explicit ordering fields
    // instead of relying on sequence-based ordering
}
```

---

### 16 MESSAGE PRIORITY / PROPOSAL ORDERING

**Description**: Missing prioritization of critical messages (oracle updates, emergency pause, governance) allows front-running and censorship during network congestion. In Cosmos SDK v0.50+ with ABCI 2.0, the block proposer controls transaction ordering via `PrepareProposal` and `ProcessProposal`, making this even more important.

**Detection Patterns**:
```go
// VULNERABLE: No priority for critical oracle update in CheckTx
func (app *App) CheckTx(req *abci.CheckTxRequest) (*abci.CheckTxResponse, error) {
    // All messages treated equally, oracle updates can be delayed
    return app.BaseApp.CheckTx(req)
}

// VULNERABLE: PrepareProposal does not prioritize critical messages
func (app *App) PrepareProposal(req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
    // Default ordering - no special treatment for emergency messages
    return app.BaseApp.PrepareProposal(req)
}
```

**What to Check**:
- [ ] Oracle/price feed updates have high priority
- [ ] Emergency pause/circuit breaker messages prioritized
- [ ] Critical governance proposals prioritized
- [ ] `PrepareProposal` considers message priority when ordering transactions
- [ ] `ProcessProposal` validates that critical messages are not censored
- [ ] High fees required for priority transactions (prevent spam)

**Mitigation**:
```go
// SECURE: Custom PrepareProposal that prioritizes critical messages
func (app *App) PrepareProposal(req *abci.PrepareProposalRequest) (*abci.PrepareProposalResponse, error) {
    // Sort transactions by priority, putting critical messages first
    prioritized := prioritizeTxs(req.Txs, req.MaxTxBytes)
    return &abci.PrepareProposalResponse{Txs: prioritized}, nil
}

func prioritizeTxs(txs [][]byte, maxBytes int64) [][]byte {
    // Decode and classify transactions
    // Place oracle updates and emergency messages first
    // Then normal transactions by fee
    // ...
}
```

**References**: building-secure-contracts/not-so-smart-contracts/cosmos/messages_priority

---

### 17 GOVERNANCE SECURITY

**Description**: Governance mechanisms can be abused for spam, manipulation, or fund locking.

**What to Check**:
- [ ] If `MsgSubmitProposal` is registered, submission cost is non-zero. **`DefaultMinInitialDepositRatio` is zero** (`x/gov/types/v1/params.go:27`) — proposals can be submitted with zero deposit by default, creating spam risk. Check both `MinDeposit` AND `MinInitialDepositRatio`
- [ ] If `x/gov` uses custom `CalculateVoteResultsAndVotingPowerFn` (via `WithCustomCalculateVoteResultsAndVotingPowerFn`), verify the tally function correctly weights votes and handles edge cases (zero voting power, abstentions, validator vs delegator votes). A buggy tally function could manipulate governance outcomes
- [ ] If `x/circuit` module is enabled, the circuit breaker authority is reviewed — a compromised authority (or captured governance) can disable critical messages (e.g., unbonding, slashing), effectively locking user funds or preventing security-critical operations

---

### 18 MODULE-SPECIFIC CHECKS

**Description**: Known vulnerabilities, configuration pitfalls, and behavioral changes in specific SDK modules.

**What to Check**:

**x/distribution**:
- [ ] Historical rewards bounded — overflow can halt chain ([GHSA-p22h-3m2v-cmgh](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-p22h-3m2v-cmgh), fixed v0.53.3). Verify `incrementReferenceCount` and reward accumulation on older versions/forks

**x/staking**:
- [ ] Delegator slashing bypass fixed ([GHSA-86h5-xcpx-cfqc](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-86h5-xcpx-cfqc), fixed v0.50.5) — verify slashing applies to all delegations including those created between infraction and slash execution
- [ ] Panics replaced with error returns in v0.53.0 ([#24391](https://github.com/cosmos/cosmos-sdk/pull/24391)) — custom code catching panics via `recover()` must handle returned errors instead

**baseapp**:
- [ ] If `ValidateVoteExtensions` helper is used, verify chain runs >= v0.50.5. Original had bug ([GHSA-95rx-m9m5-m94v](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-95rx-m9m5-m94v)) where `currentHeight`/`chainID` args were misused. In fixed version they're ignored (read from context). In v0.51+ they're removed
- [ ] If `DisableBlockGasMeter` is enabled (`baseapp.DisableBlockGasMeter()`), per-block gas limits removed — only CometBFT's `max_bytes` applies. Removes DoS protection layer; only use if gas metered externally
- [ ] If Optimistic Execution enabled (`baseapp/oe/`), `FinalizeBlock` logic must not have side effects outside SDK state (external API calls, file writes) — speculative execution of rejected blocks cannot be rolled back
- [ ] `SetCheckTxHandler` can bypass standard ante handler validation if misused
- [ ] Custom `PrepareProposal` handlers must be deterministic in gas consumption and ordering. `ProcessProposal` rejections can cause consensus failures. Custom `SignerExtractionAdapter` must extract signers faithfully

**x/epochs**:
- [ ] Epoch hooks (`BeforeEpochStart`, `AfterEpochEnd`) must not panic and must handle errors gracefully. `InvokeSetHooks` had a bug fixed in v0.53.2 ([#24770](https://github.com/cosmos/cosmos-sdk/pull/24770)). `CurrentEpochStartHeight` semantics fixed in v0.53.0 ([#24610](https://github.com/cosmos/cosmos-sdk/pull/24610))

**x/protocolpool**:
- [ ] If used alongside `x/distribution`, verify `CommunityPoolSpend`/`FundCommunityPool`/`CommunityPool` query handlers are NOT called (they return errors). Funds can get stuck if migration from distribution's community pool is incomplete

**ABCIListener / StreamingManager**:
- [ ] If `SetStreamingManager` is used, listeners must not panic (halts chain), must not be excessively slow (synchronous during block processing), and must tolerate rejected blocks — especially with Optimistic Execution

**Other**:
- [ ] No custom storage/cache implemented — [may fail to revert after failed transactions](https://github.com/iczc/billboard/tree/main/writeup#the-cache)
- [ ] [`errorsmod.Register`](https://pkg.go.dev/cosmossdk.io/errors) (from `cosmossdk.io/errors`) used for errors, not hand-rolled mechanisms
- [ ] [All defined messages actually registered in a handler](https://github.com/code-423n4/2021-08-gravitybridge-findings/issues/64)
- [ ] [CometBFT config/genesis.json not overwritten without validation](https://github.com/nomic-io/nomic/issues/104); [genesis.json save is atomic](https://github.com/crypto-org-chain/chain-main/issues/398)

---

### 19 MODULE INITIALIZATION ORDERING

**Description**: Cosmos SDK modules must be initialized in a specific order. Incorrect ordering can cause panics during genesis, missing state, or security bypasses.

**What to Check**:
- [ ] `SetOrderPreBlockers`: `upgrade` module must be first. `auth` must also be in PreBlockers (required for unordered tx cleanup via `RemoveExpiredUnorderedNonces`)
- [ ] `SetOrderBeginBlockers` / `SetOrderEndBlockers`: `x/epochs` is in BeginBlockers and fires epoch hooks — modules depending on epoch events must come after `epochs`. Note: `capability` module has been removed from cosmos-sdk (moved to ibc-go)
- [ ] `SetOrderInitGenesis`: [`genutils` after `staking`](https://github.com/cosmos/cosmos-sdk/blob/cb6fea997cb416b6c9d3d1b47986bc464bd4bd49/simapp/app.go#L387-L388) (pool initialization); [`genutils` after `auth`](https://github.com/cosmos/cosmos-sdk/blob/cb6fea997cb416b6c9d3d1b47986bc464bd4bd49/simapp/app.go#L389) (param access)
- [ ] `SetOrderExportGenesis`: `protocolpool` must be exported before `bank`
- [ ] [Keepers passed to other keepers are initialized](https://github.com/osmosis-labs/osmosis/issues/665#issuecomment-995925080). Suspicious pattern: keeper initialized → uninitialized copy passed to another → first one assigned to `app.TheKeeper`

---

### 20 ARITHMETIC ERRORS

**Description**: Arithmetic bugs in financially-critical code — rounding errors, wrapping overflows, wrong operand selection, and missing sign/bound validation. Any deviation directly translates to fund loss. `math.LegacyDec` (formerly `sdk.Dec`) has precision issues and lacks associativity.

**Detection Patterns**:

#### Pattern 1: Rounding Errors (Precision Loss)
```go
// VULNERABLE: Division before multiplication (loses precision)
sharePrice := totalValue.Quo(totalShares)  // Division first
userValue := sharePrice.Mul(userShares)    // Multiplication second
// User gets less value due to rounding down in division

// VULNERABLE: math.LegacyDec associativity issues
result1 := a.Mul(b).Quo(c)  // (1 * 10) / 100 = 0.1
result2 := a.Quo(c).Mul(b)  // (1 / 100) * 10 = 0.1 (but different precision!)
// result1 != result2 due to precision handling

// VULNERABLE: Repeated rounding favors users
for _, user := range users {
    reward := totalReward.Quo(math.LegacyNewDec(int64(len(users))))
    k.MintReward(ctx, user, reward)
}
// Total minted > totalReward due to rounding up
```

#### Pattern 2: Wrong Operand in Calculation
```go
// VULNERABLE: Osmosis LP share miscalculation (Finding 9, $5M loss)
// MaximalExactRatioJoin multiplied shareRatio * userTokens
// instead of shareRatio * totalPoolLiquidity
shareRatio := userTokens.Quo(totalPoolLiquidity)
newShares := shareRatio.Mul(userTokens)  // WRONG: should be shareRatio.Mul(totalShares)
// Users received 50% more LP shares than deserved
```

#### Pattern 3: Missing Sign/Bound Validation
```go
// VULNERABLE: Vesting Account Poisoning (Barberry, Finding 19)
func (msg MsgCreatePeriodicVestingAccount) ValidateBasic() error {
    for _, period := range msg.VestingPeriods {
        if period.Length <= 0 {
            return errors.New("period length must be positive")
        }
        // Missing: validation that period.Amount coins are all positive!
        // Negative amounts cause LockedCoins() to panic on withdrawal
    }
}
```

**What to Check**:
- [ ] Multiplication before division pattern used
- [ ] Rounding direction favors protocol, not users
- [ ] No repeated rounding in loops
- [ ] Consistent calculation order across all operations
- [ ] Consider using integer arithmetic with scaling factor
- [ ] Operands in share/ratio calculations are verified against the formula (not swapped)
- [ ] CosmWasm wrapping overflow: see [COSMWASM_VULNERABILITY_PATTERNS.md §1](COSMWASM_VULNERABILITY_PATTERNS.md#1-wrapping-overflow-in-uint256512-arithmetic)
- [ ] All amount/coin fields validated for positive values (not just positive length/duration) — negative amounts in vesting periods cause permanent fund freeze
- [ ] Front-running of account creation reviewed — malicious vesting accounts can be created at victim addresses before the victim's first transaction

**Mitigation**:
```go
// SECURE: Multiply before divide (preserves precision)
userValue := totalValue.Mul(userShares).Quo(totalShares)

// SECURE: Round in favor of system
reward := totalReward.Mul(userShares).QuoTruncate(totalShares)

// SECURE: Round up when calculating fees (users pay slightly more)
fee := amount.Mul(feeRate).QuoRoundUp(math.LegacyNewDec(10000))

// SECURE: Distribute with remainder handling
totalDistributed := math.LegacyZeroDec()
for i, user := range users {
    var reward math.LegacyDec
    if i == len(users)-1 {
        reward = totalReward.Sub(totalDistributed)
    } else {
        reward = totalReward.Quo(math.LegacyNewDec(int64(len(users))))
        totalDistributed = totalDistributed.Add(reward)
    }
    k.MintReward(ctx, user, reward)
}

// SECURE: Validate all coin amounts are positive
for _, period := range msg.VestingPeriods {
    if !period.Amount.IsAllPositive() {
        return errors.New("all period amounts must be positive")
    }
}
```

**References**:
- building-secure-contracts/not-so-smart-contracts/cosmos/rounding_errors
- [Osmosis $5M exploit](https://www.certik.com/resources/blog/1o2gR0mG6wP83aHp5jzoor-osmosis-incident-analysis) (Finding 9)
- [Barberry / Vest in Peace](https://blog.fordefi.com/vest-in-peace-freezing-cosmos-account-funds-through-invalid-vesting-periods) (Finding 19)

---

### 21 DATA VALIDATION & ENCODING

**Description**: Encoding, validation, and type handling issues that can cause panics, state corruption, or chain halts.

**What to Check**:
- [ ] [`Coins`/`DecCoins` always sorted and non-negative](https://github.com/cosmos/cosmos-sdk/blob/5f840be76aea654821cf4bb1a3de94757e09c0cc/types/dec_coin.go#L228-L235) — SDK relies on this for `Add`, `IsAnyGTE`, and genesis state loading
- [ ] [Unicode in `Coins` denominations handled correctly](https://github.com/cosmos/cosmos-sdk/issues/7046). [Differences between `Coin` vs `Coins` types understood](https://github.com/cosmos/cosmos-sdk/issues/7046)
- [ ] Since v0.53, `Coins.AmountOf()` no longer validates the denom and returns zero for missing denoms (no panic). `AmountOfNoDenomValidation` is deprecated (just calls `AmountOf`). Code that relied on `AmountOf` panicking on invalid denom must be updated
- [ ] `Add`, `Sub`, `Quo` on `LegacyDec` still panic — use error-returning variants or validate inputs first. Using pointer to `LegacyDec` (`*math.LegacyDec`) is error-prone: Dec consists of a single `big.Int` pointer, so pointer-to-pointer adds no performance benefit and risks nil-pointer dereference
- [ ] `cosmossdk.io/collections` framework: deterministic key encoding (custom key codecs produce consistent bytes), `IndexedMap` secondary indexes in sync, no `Remove`/`Set` during `Walk` iteration ([fix: #24460](https://github.com/cosmos/cosmos-sdk/pull/24460)), prefix/range queries used correctly
- [ ] [Encoding enforced as early as possible](https://github.com/axelarnetwork/axelar-core/issues/1148) — in msg server handler or protobuf definition (`string` in protobuf enforces UTF-8). Careful with [invalid UTF-8 string length](https://henvic.dev/posts/go-utf8/)
- [ ] [Invalid encoding in input does not halt chain](https://github.com/code-423n4/2021-08-gravitybridge-findings/issues/4)
- [ ] MustUnmarshal usage is safe
- [ ] `MsgUpdateParams` handlers validate parameter bounds — invalid params (rate > 100%, negative values, zero denominators) can be silently set via governance and cause issues in `BeginBlock`/`EndBlock`
- [ ] Default values are safe:
  - [Configurable parameters are set explicitly or default values are reviewed](https://github.com/cosmos/cosmos-sdk/blob/5f840be76aea654821cf4bb1a3de94757e09c0cc/x/genutil/client/cli/migrate.go#L77-L83)
  - Initial (genesis) system parameters are validated. Safety checks exist for parameters that get updated from "null" only after the first block ([Umee issue](https://github.com/umee-network/umee/commit/5372343a4f187864cc37f0c0d798dc7df3de16cc))
  - [`getter` methods do not return valid values on errors — they should panic or return nil](https://github.com/umee-network/umee/issues/861)
- [ ] Amino (`LegacyAmino`) only for backward compatibility (legacy JSON signing, CLI proposals). New modules use protobuf exclusively
- [ ] If Ethereum addresses used, NOT using `HexToAddress` ([does not verify format, ignores errors](https://pkg.go.dev/github.com/ethereum/go-ethereum/common#HexToAddress))
- [ ] [`AnteHandler`s enforce non-zero fee](https://docs.cosmos.network/master/basics/gas-fees.html#introduction-to-gas-and-fees) to prevent spam. See [Cosmos discussion](https://github.com/cosmos/cosmos-sdk/discussions/8224)
- [ ] Parsing of empty `Coin(s)` considers any amount of extra whitespace — [relevant source](https://github.com/cosmos/cosmos-sdk/blob/v0.33.2/types/coin.go#L527-L534). For example, checking `msg.Funds == "" || msg.Funds == "0"` is incomplete; use the SDK's parsing functions instead of manual string comparison

---

### 22 EVENTS & LOGGING (INCLUDING CacheContext EVENT LEAK)

**Description**: Events are critical for indexers, relayers, and monitoring tools. Incorrect event handling breaks the ecosystem. A particularly dangerous pattern is the **CacheContext event leak**: when `CacheContext` is used to tentatively execute operations, state changes are correctly rolled back on failure, but events are still emitted to the parent context. Off-chain systems (bridges, indexers) trusting events without verifying on-chain state can be exploited.

**Detection Patterns**:
```go
// VULNERABLE: CacheContext event leak (Huckleberry, Finding 4)
func (k Keeper) OnRecvPacket(ctx sdk.Context, packet channeltypes.Packet) exported.Acknowledgement {
    cacheCtx, writeFn := ctx.CacheContext()

    // Execute in cached context — emits events to cacheCtx
    err := k.processPacket(cacheCtx, packet)
    if err != nil {
        // State is rolled back (writeFn not called)
        // BUT events from cacheCtx are still visible to parent context!
        return channeltypes.NewErrorAcknowledgement(err)
    }
    writeFn()
    return channeltypes.NewResultAcknowledgement([]byte{1})
}
// Bridge monitors see valid deposit events for transfers that never happened
// Attacker gets credited on the bridge without tokens actually moving
```

**What to Check**:
- [ ] Enough event logs to debug on-chain transactions
- [ ] Events NOT generated for failed transactions — [Huckleberry: IBC Event Hallucinations](https://jumpcrypto.com/writing/huckleberry-ibc-event-hallucinations/)
- [ ] `CacheContext` usage reviewed: events emitted in cached context are visible even if `writeFn` is never called — state rolls back but events persist
- [ ] Off-chain systems (bridges, indexers, relayers) do NOT rely solely on events without verifying on-chain state
- [ ] Custom IBC `OnRecvPacket` that returns error ack does not emit misleading deposit/transfer events
- [ ] No log injection possible

**References**:
- [Jump Crypto — Huckleberry: IBC Event Hallucinations](https://jumpcrypto.com/writing/huckleberry-ibc-event-hallucinations/) (Finding 4)

---

### 23 DEPRECATED MODULES

> As of v0.53.5+. Flag usage and advise migration.

- **`x/crisis`** (Invariant type) — deprecated, will be removed. Use simulations (`simsx` framework in v0.53+) and property-based tests instead
- **`x/nft`** — deprecated ([#24575](https://github.com/cosmos/cosmos-sdk/pull/24575)), moving to [cosmos-legacy](https://github.com/cosmos/cosmos-legacy)
- **`x/group`** — deprecated ([#24571](https://github.com/cosmos/cosmos-sdk/pull/24571)), moving to cosmos-legacy. EndBlocker had chain-halt bugs ([GHSA-47ww-ff84-4jrg](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-47ww-ff84-4jrg), [GHSA-x5vx-95h7-rv4p](https://github.com/cosmos/cosmos-sdk/security/advisories/GHSA-x5vx-95h7-rv4p), fixed v0.50.12/v0.53.0)
- **`x/params`** — deprecated, modules use direct keeper param storage via `collections`
- **`ValidateBasic()`** — deprecated and facultative (via `HasValidateBasic` interface). Validation belongs in msg server handlers
- **ed25519 tx signing** — added in v0.53.0 ([#24030](https://github.com/cosmos/cosmos-sdk/pull/24030)). Chains should explicitly decide which key types to permit. Unlike secp256k1, ed25519 is not compatible with Ledger hardware wallets without custom apps

---
