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

Open Beginner friendly
#4,601 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
85/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Quiet
Tech stack
go
Domain
blockchain

Research direction

The buggy default liquidity cap calculation is in x/fungible/keeper/evm.go lines 172-175, with the expected scaling rule defined in x/fungible/types/zrc20.go. Apply the big.Int.Exp(10, decimals) pattern from the issue to fix the logic, then add unit tests for DeployZRC20Contract with liquidityCap == nil for 6, 8, and 18 decimal tokens to confirm the cap is computed correctly. Run the x/fungible package test suite to verify no regressions.

Written by the indexing model from the issue text.

Description

bug_bounty zetacore

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

Dominant language
Go
Stars
202
Forks
181
Avg merge
17h 6m
Merged PRs (30d)
2

Getting set up

  • No Dockerfile or Docker Compose file
  • Has a pull request template
  • No contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from zeta-chain/node

All issues in zeta-chain/node

Similar issues

More Go issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.