Description
The LevelOne::graduateAndUpgrade
function lacks a critical check to verify whether the school session has officially ended (sessionEnd
). Without this check, the function can be invoked prematurely by the principal
, which could lead to unexpected and potentially dangerous outcomes.
This includes:
Premature termination of the current session
Unexpected state transitions
Potential inconsistencies in logic or data
Misuse or loss of bursary funds due to early payouts
Vulnerable Function
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}
Impact
Security Risk: The contract allows an upgrade before the intended session duration.
Data Corruption: Logic tied to session length or order of operations may break.
Financial Risk: Funds may be transferred or locked inappropriately.
Proof of Concept
function test_confirm_OwnerCanCompleteSessionBeforeTime() public schoolInSession {
vm.warp(block.timestamp + 1 weeks);
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
LevelTwo levelTwoProxy = LevelTwo(proxyAddress);
console2.log(levelTwoProxy.bursary());
console2.log(levelTwoProxy.getTotalStudents());
}
Recommended Mitigation
Add a require
statement at the beginning of graduateAndUpgrade
:
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
require(block.timestamp >= sessionEnd, "HH: Session has not ended");
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}