Skip to content

ZRC20 default liquidity cap miscomputed (decimals vs 10^decimals) #4601

@kingpinXD

Description

@kingpinXD

Summary

When MsgDeployFungibleCoinZRC20 omits liquidity_cap, the default is computed as DefaultLiquidityCap (1000) * decimals instead of DefaultLiquidityCap * 10^decimals as documented on the constant. The resulting cap is 18000 wei for an 18-decimal token (e.g. ETH), 6000 wei for a 6-decimal token (e.g. USDC), and 8000 wei for an 8-decimal token (e.g. WBTC). getAndCheckZRC20 rejects any non-dust deposit with ErrForeignCoinCapReached, effectively bricking the token until an operational-policy tx resets the cap.

Root cause

x/fungible/keeper/evm.go:172-175:

if liquidityCap == nil {
    liquidityCap = ptr.Ptr(sdkmath.NewUint(types.DefaultLiquidityCap).MulUint64(uint64(newCoin.Decimals)))
}

newCoin.Decimals is the raw decimal count, not 10^decimals. The comment on DefaultLiquidityCap in x/fungible/types/zrc20.go:5-8 says the on-chain value should be value * 10^decimals.

Reachability

The default-cap branch is only reachable through MsgDeployFungibleCoinZRC20, which is in AdminPolicyMessages (x/authority/types/authorization_list.go:36). MsgWhitelistAsset already wraps the cap via ptr.Ptr(msg.LiquidityCap) before calling the keeper, so it never hits the buggy branch. The bug is admin-error class, recoverable via MsgUpdateZRC20LiquidityCap (operational policy).

Fix

Mirror the existing big.Int.Exp(10, decimals) pattern from gas_coin_and_pool.go:84:

if liquidityCap == nil {
    tenPow := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(newCoin.Decimals)), nil)
    liquidityCap = ptr.Ptr(sdkmath.NewUintFromBigInt(tenPow).MulUint64(types.DefaultLiquidityCap))
}

Add a unit test that exercises DeployZRC20Contract with liquidityCap == nil for decimals 6, 8, and 18 to lock in the correct default and prevent regression.

Optional hardening: tighten MsgDeployFungibleCoinZRC20.ValidateBasic to reject LiquidityCap == nil outright.

History

Introduced in commit 367e4aa4f (Cosmos v0.50 upgrade, PR #3357, 2025-01-24). Has been in main since January 2025; the default-cap branch is untested.

Severity

Low. Admin-gated, recoverable in one transaction by the same policy group, no permanent fund loss under normal operation.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions