Skip to content

Latest commit

 

History

History
245 lines (180 loc) · 8.32 KB

File metadata and controls

245 lines (180 loc) · 8.32 KB

Proper Cider Shark

Medium

Missing Input Validation in initialize Function Will Cause Contract to Become Unusable for Users as Deployer Can Initialize with Zero Addresses

Summary

The lack of input validation in the initialize function of the LeverageToken contract will cause the contract to become unusable for users as the deployer can initialize the contract with zero addresses. This will result in critical roles (minter, governance, and poolFactory) being assigned to address(0), preventing any further interaction with the contract.

Root Cause

In LeverageToken.sol:41 In [LeverageToken.sol:L41] https://github.com/sherlock-audit/2024-12-plaza-finance/blob/main/plaza-evm/src/LeverageToken.sol#L41, the initialize function does not validate whether the input addresses (minter, governance, and poolFactory) are zero addresses. This allows the contract to be initialized in an invalid state, rendering it unusable.

Internal Pre-conditions

Deployer needs to call the initialize function with minter, governance, or poolFactory set to address(0).

The contract must be in an uninitialized state (i.e., the initialize function has not been called before).

External Pre-conditions

The deployer must have access to the contract deployment process.

No external protocols or oracles are involved in this vulnerability.

Attack Path

The deployer deploys the LeverageToken contract and its proxy.

The deployer calls the initialize function with one or more of the following addresses set to address(0):

minter = address(0)

governance = address(0)

poolFactory = address(0)

The contract is initialized in an invalid state:

If minter is address(0), no one can mint or burn tokens.

If governance is address(0), no one can upgrade the contract or manage roles.

If poolFactory is address(0), the onlySecurityCouncil modifier will fail, preventing pausing and unpausing.

The contract becomes unusable, and users cannot interact with it.

Impact

The contract becomes unusable if initialized with zero addresses. Users cannot mint, burn, pause, unpause, or upgrade the contract.

PoC

// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.26;

import "forge-std/Test.sol"; import "../src/LeverageToken.sol"; import {Utils} from "../src/lib/Utils.sol"; import {PoolFactory} from "../src/PoolFactory.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {PausableUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";

contract LeverageTokenTest is Test { LeverageToken private token; ERC1967Proxy private proxy; address private deployer = address(0x1); address private minter = address(0x2); address private governance = address(0x3); address private user = address(0x4); address private user2 = address(0x5); address private securityCouncil = address(0x6);

PoolFactory private poolFactory;

/**
 * @dev Sets up the testing environment.
 * Deploys the LeverageToken contract and a proxy, then initializes them.
 * Grants the minter and governance roles and mints initial tokens.
 */
function setUp() public {
    vm.startPrank(governance);
    poolFactory = PoolFactory(Utils.deploy(address(new PoolFactory()), abi.encodeCall(
        PoolFactory.initialize,
        (governance, address(0), address(0), address(0), address(0), address(0), address(0))
    )));

    poolFactory.grantRole(poolFactory.SECURITY_COUNCIL_ROLE(), securityCouncil);
    vm.stopPrank();

    vm.startPrank(deployer);
    // Deploy and initialize LeverageToken
    LeverageToken implementation = new LeverageToken();

    // Deploy the proxy and initialize the contract through the proxy
    proxy = new ERC1967Proxy(address(implementation), abi.encodeCall(implementation.initialize, ("LeverageToken", "LEVR", minter, governance, address(poolFactory))));

    // Attach the LeverageToken interface to the deployed proxy
    token = LeverageToken(address(proxy));
    vm.stopPrank();

    // Mint some initial tokens to the minter for testing
    vm.startPrank(minter);
    token.mint(minter, 1000);
    vm.stopPrank();
}

/**
 * @dev Tests initialization with zero addresses.
 * Ensures that the contract reverts when initialized with invalid addresses.
 */
function testInitializeWithZeroAddresses() public {
    // Deploy a new implementation of LeverageToken
    LeverageToken implementation = new LeverageToken();

    // Attempt to initialize with zero addresses and expect reverts
    vm.startPrank(deployer);

    // Test zero minter address
    vm.expectRevert("LeverageToken: minter cannot be zero address");
    new ERC1967Proxy(
        address(implementation),
        abi.encodeCall(implementation.initialize, ("LeverageToken", "LEVR", address(0), governance, address(poolFactory)))
    );

    // Test zero governance address
    vm.expectRevert("LeverageToken: governance cannot be zero address");
    new ERC1967Proxy(
        address(implementation),
        abi.encodeCall(implementation.initialize, ("LeverageToken", "LEVR", minter, address(0), address(poolFactory)))
    );

    // Test zero poolFactory address
    vm.expectRevert("LeverageToken: poolFactory cannot be zero address");
    new ERC1967Proxy(
        address(implementation),
        abi.encodeCall(implementation.initialize, ("LeverageToken", "LEVR", minter, governance, address(0)))
    );

    vm.stopPrank();
}

function testPause() public {
    // makes sure it starts false
    assertEq(token.paused(), false);

    // makes sure minting works if not paused
    vm.startPrank(minter);
    token.mint(user, 1000);

    // pause contract
    vm.startPrank(securityCouncil);
    token.pause();

    // check it reverts on minting
    vm.startPrank(minter);
    vm.expectRevert(PausableUpgradeable.EnforcedPause.selector);
    token.mint(user, 1);

    // check it reverts on burning
    vm.expectRevert(PausableUpgradeable.EnforcedPause.selector);
    token.burn(user, 1);

    // check it reverts on transfer
    vm.expectRevert(PausableUpgradeable.EnforcedPause.selector);
    token.transfer(user, 1);

    // @todo: check if contract is still upgradable on pause
    // token._authorizeUpgrade(address(0));

    // unpause contract
    vm.startPrank(securityCouncil);
    token.unpause();

    // make sure you can now do stuff
    vm.startPrank(user);
    token.transfer(user2, 1000);
}

/**
 * @dev Tests minting of tokens by an address with MINTER_ROLE.
 * Asserts that the user's balance is updated correctly.
 */
function testMinting() public {
    uint256 initialBalance = token.balanceOf(minter);
    uint256 mintAmount = 500;

    vm.startPrank(minter);
    token.mint(user, mintAmount);
    vm.stopPrank();

    assertEq(token.balanceOf(user), mintAmount);
    assertEq(token.balanceOf(minter), initialBalance);
}

/**
 * @dev Tests minting of tokens by an address without MINTER_ROLE.
 * Expects the transaction to revert.
 */
function testMintingWithNoPermission() public {
    uint256 initialBalance = token.balanceOf(user);

    vm.expectRevert();
    vm.startPrank(user);
    token.mint(user, 100);
    vm.stopPrank();

    assertEq(token.balanceOf(user), initialBalance);
}

/**
 * @dev Tests burning of tokens by an address with MINTER_ROLE.
 * Asserts that the minter's balance is decreased correctly.
 */
function testBurning() public {
    uint256 initialBalance = token.balanceOf(minter);
    uint256 burnAmount = 100;

    vm.startPrank(minter);
    token.burn(minter, burnAmount);
    vm.stopPrank();

    assertEq(token.balanceOf(minter), initialBalance - burnAmount);
}

/**
 * @dev Tests burning of tokens by an address without MINTER_ROLE.
 * Expects the transaction to revert.
 */
function testBurningWithNoPermission() public {
    uint256 initialBalance = token.balanceOf(user);

    vm.expectRevert();
    vm.startPrank(user);
    token.burn(user, 50);
    vm.stopPrank();

    assertEq(token.balanceOf(user), initialBalance);
}

}

Mitigation

No response