Skip to content

[Bug] x/wasmbinding SetTokenMetadata omits top-level Metadata.Decimals, breaking ERC20 decimals() for adapter-wrapped denoms #37

Description

@danvaneijck

[Bug] x/wasmbinding SetTokenMetadata doesn't set top-level Metadata.Decimals, breaking auto-deployed ERC20 decimals() for adapter-wrapped denoms

Summary

The CosmWasm SetTokenMetadata wasm-binding message translates to a tokenfactory.MsgSetDenomMetadata whose Metadata struct omits the top-level Decimals field. The bank precompile (used by every auto-deployed MintBurnBankERC20 / FixedSupplyBankERC20) reads metadata.Decimals directly when serving decimals(). Result: any ERC20 pair auto-deployed for a tokenfactory denom whose metadata was set via the wasm binding reports decimals() == 0 regardless of the actual decimal count.

This is a one-line oversight in injective-chain/wasmbinding/message_plugin.go that diverged from the precompile when Injective extended the bank Metadata proto with a top-level Decimals field.

Code locations

Buginjective-chain/wasmbinding/message_plugin.go (lines 140–160 of release/v1.19.x):

case contractMsg.SetTokenMetadata != nil:
    mt := contractMsg.SetTokenMetadata
    sdkMsg = &tokenfactorytypes.MsgSetDenomMetadata{
        Sender: contractAddr.String(),
        Metadata: banktypes.Metadata{
            Base:    mt.Denom,
            Display: mt.Symbol,
            Name:    mt.Name,
            Symbol:  mt.Symbol,
            // ← Decimals field is never set
            DenomUnits: []*banktypes.DenomUnit{
                { Denom: mt.Denom, Exponent: 0 },
                { Denom: mt.Symbol, Exponent: mt.Decimals },
            },
        },
    }

Conflicting readinjective-chain/modules/evm/precompiles/bank/bank.go (line 320 of release/v1.19.x):

metadata, _ := bc.bankKeeper.GetDenomMetaData(stateDB.CacheContext(), denom)
return method.Outputs.Pack(metadata.Name, metadata.Symbol, uint8(metadata.Decimals))

The precompile reads metadata.Decimals (top-level), not DenomUnits[i].Exponent.

Evidence (mainnet, 2026-05-27)

Two mainnet denoms have populated metadata set via the immutable CW20 Adapter inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk by calling UpdateMetadata. Both show the same broken pattern:

denom display display-unit exponent top-level Decimals
factory/inj14ej…/inj1300xcg9…87xm8 (SHROOM / shroomin CW20) SHROOM 18 0
factory/inj14ej…/inj1x997dy…zcnq (TRUINJ) TRUINJ 18 0

For comparison, a denom whose metadata was set via direct MsgSetDenomMetadata (proposal 594, $DRUGS):

denom display display-unit exponent top-level Decimals deployed ERC20 decimals()
factory/inj178zy…/DRUGS DRUGS 6 6 6 (verified via EVM RPC at 0x7F073F52051eEF8512519ee7A627F8db1cD63303)

So the precompile path works correctly when metadata.Decimals is populated; the bug is strictly in the wasm-binding translation that fails to populate it.

Impact

Every CW20-adapter-wrapped tokenfactory denom (197 denoms registered today via the standard inj14ej… adapter) is affected as soon as the project tries to MTS-register the bank denom. The wrapper ERC20 would report decimals() == 0, breaking DEX pricing, balance display, and any other EVM-side use.

Existing workarounds are all blocked:

  • Direct MsgSetDenomMetadata with the new proto requires admin signature; admin is the non-signing CW20 Adapter contract.
  • Gov-bypass MsgSetDenomMetadata only activates when admin == ""; the adapter's denoms have admin set.
  • Adapter migration is impossible: the adapter contract has admin: "" (wasmd-level), so MsgMigrateContract cannot succeed.
  • Adapter ChangeAdmin isn't exposed in the adapter's ExecuteMsg enum, so denom admin can't be transferred away.

Without a chain-level fix, every project using the standard CW20 Adapter is locked out of functional MTS registration.

Proposed fix

One-line addition in message_plugin.go SetTokenMetadata branch:

 Metadata: banktypes.Metadata{
     Base:    mt.Denom,
     Display: mt.Symbol,
     Name:    mt.Name,
     Symbol:  mt.Symbol,
+    Decimals: mt.Decimals,
     DenomUnits: []*banktypes.DenomUnit{
         { Denom: mt.Denom, Exponent: 0 },
         { Denom: mt.Symbol, Exponent: mt.Decimals },
     },
 },

The value is already correctly propagated as the second DenomUnit.Exponent. This fix surfaces the same value as the top-level field the precompile reads.

Compatibility / safety

  • Existing denoms unaffected by the patch. They keep their current Decimals=0 until UpdateMetadata is re-run on them after the fix lands.
  • Validation pass. tokenfactory.MsgServer.SetDenomMetadata enforces existingMetadata.Decimals != 0 && existingMetadata.Decimals != msg.Metadata.Decimals → first-time set or matching-set both pass.
  • Bank Metadata.Validate() accepts the field — the extended Decimals isn't restricted.

Will follow up with a PR.

Reproduction script

For anyone investigating, this injectived snippet reproduces the divergence (mainnet):

# Query SHROOM bank metadata — note `decimals: 0` despite display-unit exponent 18
injectived q bank denom-metadata factory/inj14ejqjyq8um4p3xfqj74yld5waqljf88f9eneuk/inj1300xcg9naqy00fujsr9r8alwk7dh65uqu87xm8 \
  --node https://sentry.tm.injective.network:443

# Compare with $DRUGS (set via direct MsgSetDenomMetadata, decimals=6 populated)
injectived q bank denom-metadata factory/inj178zy7myyxewek7ka7v9hru8ycpvfnen6xeps89/DRUGS \
  --node https://sentry.tm.injective.network:443

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions