Description: At the end of a session, the Principal calls LevelOne::graduateAndUpgrade
to upgrade the existing system and pay all wages. According to the intended logic:
Teacher wage: 35% of the bursary (to be shared among all teachers).
Principal wage: 5% of the bursary
However, the current implementation in the protocol incorrectly remits 35% of the bursary to each individual teacher, rather than splitting the 35% among all teachers as intended.
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: Teachers receive significantly higher wages than intended. In cases where there are multiple teachers, the entire bursary may be exhausted before all teachers are paid potentially leaving the Principal unpaid.
Proof of Concept: 60% of the bursary should be left after upgrading, but clearly less is left. Paste this code block in the test suite:
function testTeacherSalaryIsMoreThanExpected() public schoolInSession {
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
uint256 bursaryAmountBeforeGraduation = levelOneProxy.bursary();
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
uint256 sixtyPercentOfBursary = (bursaryAmountBeforeGraduation * 60) / 100;
uint256 aliceBalance = usdc.balanceOf(alice);
uint256 bobBalance = usdc.balanceOf(bob);
uint256 principalBalance = usdc.balanceOf(principal);
uint256 actualBursaryBalanceAfterGraduation = bursaryAmountBeforeGraduation - (principalBalance + bobBalance + aliceBalance);
console2.log(actualBursaryBalanceAfterGraduation);
console2.log(bursaryAmountBeforeGraduation);
console2.log(sixtyPercentOfBursary);
assert(sixtyPercentOfBursary != actualBursaryBalanceAfterGraduation);
assert(sixtyPercentOfBursary > actualBursaryBalanceAfterGraduation);
}
Recommended Mitigation: Calculate the wage each teacher is supposed to earn from the 35% before disbursing the funds.
+ uint256 totalTeacherPay = (bursary * TEACHER_WAGE) / PRECISION;
+ uint256 payPerTeacher = totalTeacherPay / totalTeachers;
- 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);
}