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
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.
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.
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).
The deployer must have access to the contract deployment process.
No external protocols or oracles are involved in this vulnerability.
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.
The contract becomes unusable if initialized with zero addresses. Users cannot mint, burn, pause, unpause, or upgrade the contract.
// 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);
}
}
No response