Skip to content

Frozen tokens may exceed balance and break active-balance assumptions #375

Description

@rya-sge

Severity: medium
Version concerned: CMTAT v3.2.0

Summary

setFrozenTokens(account, amount) can intentionally set frozen > balance (per ERC-7943 semantics), but some internal arithmetic paths historically assumed frozen <= balance.

This mismatch can lead to unexpected reverts/underflows in active-balance dependent flows if not explicitly handled.

Context

ERC-7943 allows absolute frozen amounts to exceed current balance (future balance withholding model).

So this behavior is valid from a spec standpoint, but implementation logic must be robust to it everywhere.

Impact

Without hardening, the following can break when frozen > balance:

  • active balance reads
  • transfer checks based on active balance
  • forced transfer/unfreeze paths using arithmetic derived from active balance

Result: unexpected reverts and inconsistent behavior in enforcement flows.

Root cause

Internal code paths performed direct subtraction like:

  • active = balance - frozen

This is unsafe when frozen >= balance.

Reproduction (pre-fix behavior)

  1. Mint tokens to account.
  2. Call setFrozenTokens(account, balance + 1).
  3. Trigger active-balance dependent path (transfer, getActiveBalanceOf, forced transfer internals).
  4. Observe underflow/revert or inconsistent behavior.

Remediation

Adopt permissive ERC-7943 model and harden arithmetic logic:

  1. Keep setFrozenTokens permissive (frozen may exceed balance).
  2. In active-balance calculations, treat frozen >= balance as active balance 0 (saturating logic).
  3. Ensure forced-transfer unfreeze path computes activeBalance safely under the same condition.
  4. Add regression tests for frozen > balance scenarios.

Implemented changes

Contract hardening

Updated:

  • contracts/modules/internal/ERC20EnforcementModuleInternal.sol

Changes:

  • _checkActiveBalance(...):
    • if frozen >= balance, returns (value == 0 ? true : false, 0).
  • _getActiveBalanceOf(...):
    • returns 0 when frozen >= balance.
  • _unfreezeTokens(...):
    • safe active-balance computation when frozen > balance.

Added inline rationale comments:

  • Frozen amounts can be > balance through setFrozenTokens.

Test coverage

Updated:

  • test/common/ERC20EnforcementModuleCommon.js

Added tests:

  • testCanSetFrozenTokensGreaterThanBalance
  • testCanForcedTransferWhenFrozenTokensGreaterThanBalance

These verify:

  • permissive setFrozenTokens(balance + 1) is accepted,
  • active balance is safely treated as 0,
  • normal transfer fails with ERC7943InsufficientUnfrozenBalance(..., unfrozen=0),
  • forced transfer remains operational.

Notes

  • This is a consistency hardening issue, not an ERC-7943 non-compliance issue.
  • Optional alternative policy (not chosen here): enforce setFrozenTokens <= balance and document as stricter-than-ERC-7943 profile.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Next releaseThe issue has been merged into dev and will be part of the next release

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions