Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: low
Likelihood: medium
Invalid

Improper Placement of `__UUPSUpgradeable_init()` in Initializer

Summary

The LevelOne smart contract's initialize(...) function places a __UUPSUpgradeable_init() call at the end of the function, after critical state variables have already been set. This ordering introduces a subtle but significant risk in the context of upgradable contract security, particularly around proxy initialization and upgrade paths.

While the __UUPSUpgradeable_init() function itself is not currently overridden or implemented, best practices and defensive programming dictate that it should be called before any sensitive logic, to ensure the proxy's state is properly initialized before any other operations.

Vulnerability Details

In UUPS (Universal Upgradeable Proxy Standard) upgradeable contracts, the __UUPSUpgradeable_init() function initializes the upgradeability mechanism via OpenZeppelin’s UUPSUpgradeable base. This method typically sets internal storage variables and includes access control logic related to upgrades.

function initialize(address _principal, uint256 _schoolFees, address _usdcAddress) public initializer {
...
principal = _principal;
schoolFees = _schoolFees;
usdc = IERC20(_usdcAddress);
__UUPSUpgradeable_init(); // Poor practice :\
}

The issue is that __UUPSUpgradeable_init() is called after the contract has already set critical parameters, which could introduce inconsistencies if the initializer modifier were removed or bypassed due to inheritance conflicts or bugs. If an attacker finds a way to re-initialize the contract (through a misconfigured proxy, for instance), calling sensitive logic before properly initializing upgradeability controls can potentially lead to a privilege escalation or denial of service.

Impact

  • Incorrect Initialization Sequence: If __UUPSUpgradeable_init() contains security-critical logic (such as setting upgrade permissions), invoking it after modifying storage could expose the contract to improper states.

  • Potential Exploitation in Edge Cases: If the initializer modifier is not correctly applied (e.g., due to complex inheritance), a malicious actor could take advantage of the incorrect order.

  • Reduced Auditability: Deviating from common upgradeable contract patterns makes future audits and verifications more error-prone.

While this specific instance may not be immediately exploitable, it's a ticking time bomb in complex deployments — especially as upgradeability logic gets layered in.

Tools Used

  • Foundry

  • Manual code review

Recommendations

Move the __UUPSUpgradeable_init() call to the beginning of the initialize() function to ensure the contract's upgradeable mechanism is set up before executing any state-changing logic:

function initialize(address _principal, uint256 _schoolFees, address _usdcAddress) public initializer {
__UUPSUpgradeable_init(); // Move to the top for proper upgradeable pattern
if (_principal == address(0)) {
revert HH__ZeroAddress();
}
if (_schoolFees == 0) {
revert HH__ZeroValue();
}
if (_usdcAddress == address(0)) {
revert HH__ZeroAddress();
}
principal = _principal;
schoolFees = _schoolFees;
usdc = IERC20(_usdcAddress);
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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