Hawk High

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

graduateAndUpgrade() does not perform upgrade despite its name and intent

Summary

The graduateAndUpgrade() function implies that it both processes the end-of-session payments and performs a contract upgrade to a new implementation (_levelTwo). However, the function fails to actually perform the upgrade. It only calls _authorizeUpgrade(...), which is a permission check and does not change the implementation.

Vulnerability Details

The function:

_authorizeUpgrade(_levelTwo);

...only triggers the access control logic. It does not invoke any upgrade mechanism such as:

  • upgradeToAndCall(...)

  • or _upgradeToAndCallUUPS(...)

As a result, the contract remains at LevelOne and the version() function continues to return "LevelOne" even after a successful call to graduateAndUpgrade().

This was verified on-chain via a proxy setup using OpenZeppelin’s ERC1967Proxy.

Impact

Function does not fulfill its name or intended behavior

  • Creates developer confusion or false sense of upgrade safety

  • Future upgrades may be mistakenly assumed completed

  • Could lead to logic inconsistencies if LevelTwo contains critical updates that are never applied

Tools Used

  • Manual review of UUPSUpgradeable.sol

  • OpenZeppelin documentation

  • Remix test deployment using ERC1967Proxy

Recommendations

Replace the permission-only call with a real upgrade using OpenZeppelin’s internal upgrade function:

_upgradeToAndCallUUPS(_levelTwo, data);

Ensure that this line comes after all state and payment logic to maintain upgrade safety.

Here's how you should modifiy your code to actually perform to upgrade :

function graduateAndUpgrade(
address _levelTwo,
bytes memory data
) public onlyPrincipal {
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
// Perform financial operations first
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
// Pay teachers and principal
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
// Now perform the actual upgrade
_upgradeToAndCallUUPS(_levelTwo, data);
}

Updates

Lead Judging Commences

yeahchibyke Lead Judge about 1 month 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.