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);
}