Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

Silent Failure to Upgrade in LevelOne::graduateAndUpgrade

Summary

The LevelOne::graduateAndUpgrade function intends to perform contract upgrade logic alongside graduation and reward distribution. However, it incorrectly calls _authorizeUpgrade(_levelTwo) directly, which only performs an access control check but does not execute the actual upgrade. As a result, the proxy contract's implementation is never changed, and the upgrade silently fails.


Vulnerability Details

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
...
_authorizeUpgrade(_levelTwo); // @audit-issue only performs access check
// @> Proxy implementation is never updated; upgradeTo is never called
}

Issue Explanation

  1. Misunderstanding of _authorizeUpgrade Role
    _authorizeUpgrade() is a hook meant to be called by the upgrade mechanism to verify permissions. Calling it manually does not trigger any upgrade.

  2. No Use of upgradeTo or upgradeToAndCall
    The correct upgrade mechanism provided by OpenZeppelin’s UUPSUpgradeable is never invoked, meaning the proxy continues pointing to the old implementation.

  3. Silent Failure
    Since _authorizeUpgrade() will pass if msg.sender is authorized, the function appears to succeed, misleading developers and users into thinking the upgrade was applied.


Impact

  • Functionality Breakage: Proxy contract remains on the old implementation despite expecting a transition.

  • Upgrade Failure: New logic in LevelTwo (e.g., updated graduate() or state variables) never becomes active.

  • Misleading Execution: Graduation proceeds with the belief of successful upgrade, leading to inconsistencies.


Tools Used

  • Manual Code Review


Recommendations

Replace the manual _authorizeUpgrade call with an actual upgrade operation:

Option A – Use upgradeTo

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
upgradeTo(_levelTwo); // @fix triggers actual proxy upgrade via UUPS
...
}

Option B – Use upgradeToAndCall (if initialization logic is required)

function graduateAndUpgrade(address _levelTwo, bytes memory data) public onlyPrincipal {
upgradeToAndCall(_levelTwo, data); // @fix upgrade + initialize new implementation
...
}

Ensure that the contract extends UUPSUpgradeable and that _authorizeUpgrade() is properly implemented for access control:

function _authorizeUpgrade(address newImplementation) internal override onlyPrincipal {}

Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Validated
Assigned finding tags:

failed upgrade

The system doesn't implement UUPS properly.

Support

FAQs

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