Description:
When the principal calls LevelOne:graduateAndUpgrade, there are no checks to ensure that sessionEnd is reached, allowing upgrade to be called at any time.
Impact:
LevelOne:graduateAndUpgrade can be called even during the school session, violating the invariant which states that "System upgrade cannot take place unless school's sessionEnd has reached"
Proof of Concept:
The following test passes when added to the test suite
function test_upgrade_without_sessionend() public schoolInSession {
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
assertLt(block.timestamp, levelOneProxy.getSessionEnd());
assertTrue(levelOneProxy.getSessionStatus());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
}
Recommended Mitigation:
We can ensure this invariant holds by adding the require statement below
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
++ require(block.timestamp >= sessionEnd, "Session has not ended yet");
uint256 totalTeachers = listOfTeachers.length;
// @audit-info: teachers should share the 35% right? this is doing each teacher is getting paid 35%
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
_authorizeUpgrade(_levelTwo);
// @audit-info: push vs pull? would it be possible for a revert to occur thus giving DOS?
// possible if one of the addresses are blacklisted!
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}