Description:
When wages are paid out during the call to LevelOne:graduateAndUpgrade
, the structure is supposed to follow the invariant:
* `principal` gets 5% of `bursary`
* `teachers` share of 35% of bursary
* remaining 60% should reflect in the bursary after upgrade
However this is not the case as teachers are each receiving 35% of the bursary, rather than collectively sharing 35%.
Impact:
This violates the invariant given and would also cause the contract to run out of funds if there are 3 teachers or more.
Proof of Concept:
The following test shows that the remaining funds within the contract is less than expected, which should be 60% of the initial bursary before wages are paid.
function test_payment_structure_wrong() public schoolInSession {
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
uint256 initialBursary = schoolFees * 6;
assertEq(usdc.balanceOf(proxyAddress), initialBursary);
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
uint256 bursaryRemaining = 60 * initialBursary / 100;
assertLt(usdc.balanceOf(proxyAddress), bursaryRemaining);
}
Recommended Mitigation:
The payPerTeacher
variable should be further divided by the number of teachers in the school as shown below:
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 payPerTeacher = (bursary * TEACHER_WAGE) / (PRECISION * listOfTeachers.length);
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}