DeFi Security AllianceRequest an audit
Menu

Security research

How to spot hidden mint functions

A token can create unauthorized balances without exposing a function named mint. Follow every path that writes balances or changes the implementation, then test the accounting rules those paths must preserve.

Start with the interface and the balance writes

The ERC-20 standard specifies methods such as totalSupply, balanceOf and transfer. It does not require a public function named mint. A familiar ABI therefore cannot establish that supply is fixed. Search the complete implementation, inherited contracts and imported libraries for writes to the balance mapping and supply counter.

Compare static analysis tools for authority checks before selecting a source-analysis workflow.

In OpenZeppelin's supply example, _mint is an internal function. A contract can expose an external function that calls it. The relevant question is who can reach that call and under what conditions. An internal visibility label alone says nothing about whether an external caller can trigger the operation.

For this review, distinguish an intended issuance mechanism from an unexpected balance increase. A documented, restricted mint may be part of the token design. A balance write that violates that design is the finding that needs a reproducible test.

A one-character error in the earlier example

The earlier version of this article displayed uint120(0) in a modified subtraction function but explained the result of ~uint120(0). These expressions produce different values. Casting zero gives zero. Applying bitwise complement at a width of 120 bits gives 2^120 − 1.

We reproduced both branches locally, alongside ordinary checked subtraction. Each branch runs when amount == 1, before the balance check. The following results use an initial balance of 10 and an amount of 1:

Local arithmetic results, Solidity 0.8.36
OperationReturned balance
Checked subtraction9
return uint120(0)0
return ~uint120(0)1329227995784915872903807060280344575

Assigning the zero result would erase the sender's remaining balance in this example. Assigning the complement result would create a very large balance. Neither conclusion requires a function called mint. These are deliberately broken arithmetic cases, not evidence of a deployed token's behavior.

The deterministic matrix covers 16 pairs: every combination of balances and amounts in [0, 1, 2, 10]. The zero branch differs from checked subtraction in 3 pairs; the complement branch differs in 4. Differences include an unexpected success where ordinary subtraction rejects an insufficient balance. The zero branch happens to match the correct result for balance 1 and amount 1.

Check accounting independently of the supply counter

A separate test keeps a recorded supply of 10, assigns the complement result to the sender and assigns 1 to the recipient. The sum of those two modeled balances exceeds recorded supply even though the supply variable stays at 10.

Reading totalSupply() alone would miss that inconsistency.

For a conventional token transfer without fees or rebasing, a useful test checks that the sender's decrease equals the recipient's increase and that their combined balance is conserved. Write the property against the documented design: fee recipients, burns and rebasing require different accounting. Track every affected account in the test harness so that a missing recipient does not create a false finding.

Also inspect emitted events, but use state changes to validate the result. The presence of a familiar Transfer event does not prove that the implementation obeyed its intended accounting rules.

Follow permissions and implementation changes

  1. Identify the exact code. Record the chain, contract address, implementation address if proxied, source revision and compiler settings. A similarly named repository is insufficient.
  2. Trace balance writes. Follow entry points through internal calls, inherited overrides and custom arithmetic. Compare the actual imported library code with the claimed dependency version.
  3. Resolve privileged callers. Inspect who can mint, grant minting roles or change their administrators. OpenZeppelin's access-control reference distinguishes role membership from the power to grant and revoke roles.
  4. Inspect upgrade authority. An implementation review covers the code examined. A permitted upgrade can replace it; record the controller and conditions governing that change. See the OpenZeppelin upgrade guide.
  5. Reproduce the suspected violation. Preserve the initial state, caller, transaction sequence and expected accounting property. Check whether the behavior is reachable by the actor described in the finding.

Use Slither to help inspect Solidity code, Echidna to exercise accounting properties and Foundry for a regression case. Tool output still needs a reviewer to establish reachability and impact. The DSA review methodology explains the evidence required for a technical assessment.

Reproduce the correction

Download the instructions, Foundry configuration and Solidity test, retaining its test/ directory. The recorded run used Forge 1.5.1, solc 0.8.36 and the Paris EVM target. All three test functions passed; the result summary records the input set and counts.

forge test --root . --use 0.8.36 -vv

This finite test does not implement a complete ERC-20 token, establish vulnerability prevalence or demonstrate that an inflated balance can be sold. It isolates the arithmetic correction and a balance-accounting failure for readers reviewing similar code.

Sources

Checked . Local examples include their inputs and limits.

  1. ERC-20 token standard
  2. OpenZeppelin: Creating ERC-20 Supply
  3. OpenZeppelin: Access Control API
  4. OpenZeppelin: Upgrading Smart Contracts

About this revision

Corrected the zero-versus-bitwise-complement example, clarified ERC-20 mint visibility and added local regression results. The original contribution was attributed to SolidProof; this revision was prepared by DeFiSec editorial and has not been represented as a new SolidProof review.

Suggest a correction with evidence

Comments

3
  1. Leah C.

    Searching only for a mint function name would miss alternate paths into balance updates. The privilege map is a better starting point for the review. I would follow each reachable balance update back to its callers. Then I would identify which caller can change the effective supply. A concrete call trace would make the concern easier to assess than the function name alone.

  2. Chloe N.

    The supply check also needs the upgrade path. A token can retain the same address while its implementation gains new issuance behavior.

  3. Ben O.

    The local example makes the distinction between a suspicious name and reachable behavior much clearer. I would keep the reproducing inputs with the finding. The finding could also identify the exact role needed to reach that balance change.

Leave a comment

Share a question or observation about this article.

10 to 3,000 characters.