BriVault

First Flight #52
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Owner can mint unlimited tokens via public mint()

Root + Impact

Description

  • Normal behavior: Token contracts should restrict minting (if allowed) with caps, role-based access, timelocks, or single-use initial mints so token supply and inflation are predictable.


  • Specific issue: mint() is public and onlyOwner, and it always mints 10_000_000 * 1e18 to the owner on every call. The owner can call mint() repeatedly to inflate supply arbitrarily. This is a centralization/trust vulnerability: the owner can wreck token economics, drain value, or rug investors at any time.

// Root cause in the codebase with @> marks to highlight the relevant section
function mint() public onlyOwner {
@> _mint(owner(), 10_000_000 * 1e18);
}

Risk

Likelihood:

  • The issue occurs whenever the contract owner executes the mint() function after deployment, since the function can be called repeatedly without restriction.

  • It also occurs whenever the ownership is transferred to another address that subsequently calls mint(), as the new owner inherits the same unlimited minting privilege.

Impact:

  • The total token supply can become unbounded, causing severe inflation and devaluation of user-held tokens.

  • Unlimited minting enables the owner (or a compromised owner wallet) to manipulate the vault’s token-based economy, breaking trust and potentially draining all value from token holders.

Proof of Concept


Run this script after compiling; it demonstrates calling mint() twice increases owner balance twice.
Expected result: owner balance increases by 10_000_000 * 1e18 each call.

// scripts/poc-mint.js (Hardhat)
async function main() {
const [deployer] = await ethers.getSigners();
const Token = await ethers.getContractFactory("BriTechToken");
const token = await Token.deploy();
await token.deployed();
console.log("Deployer:", deployer.address);
let bal = await token.balanceOf(deployer.address);
console.log("Initial balance:", bal.toString());
await token.mint(); // first mint
bal = await token.balanceOf(deployer.address);
console.log("After 1st mint:", bal.toString());
await token.mint(); // second mint
bal = await token.balanceOf(deployer.address);
console.log("After 2nd mint:", bal.toString());
}
main().catch(console.error);

Recommended Mitigation

Brief explanation: Replace the unconditional public mint with a guarded mint design: (a) add a CAP on total supply, or (b) change mint() to accept an amount and enforce totalSupply + amount <= CAP, and/or (c) make minting one-time (initial mint only) or controlled via a multisig/timelock. Also consider removing mint entirely if not required.

- remove this code
+ add this code
contract BriTechToken is ERC20, Ownable {
- constructor() ERC20("BriTechLabs", "BTT") Ownable(msg.sender) {}
-
- function mint() public onlyOwner {
- _mint(owner(), 10_000_000 * 1e18);
- }
+ uint256 public constant CAP = 100_000_000 * 1e18; // example cap
+
+ constructor() ERC20("BriTechLabs", "BTT") {
+ // owner is set by Ownable() automatically (msg.sender)
+ // optionally perform a one-time initial mint if required
+ // _mint(msg.sender, 10_000_000 * 1e18);
+ }
+
+ /// @notice Mint new tokens up to the CAP. Only owner can call.
+ function mint(uint256 amount) external onlyOwner {
+ require(amount > 0, "mint: zero");
+ require(totalSupply() + amount <= CAP, "mint: cap exceeded");
+ _mint(owner(), amount);
+ }
+
+ /// @notice Emergency: renounce ability to mint by transferring ownership to timelock/multisig or by renouncing.
+ /// Owner should consider transferring ownership to multisig or renouncing after initial distribution.
}
Updates

Appeal created

bube Lead Judge 20 days ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!