Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

High: Teacher wage is not divided by teacher count ⇒ each teacher receives 35 % of the bursary; contract is drained and further payouts revert

Description

LevelOne::graduateAndUpgrade() calculates the payPerTeacher once:

uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION; // 35 % of bursary

It then loops over listOfTeachers and transfers that full amount to every teacher:

for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}

Because the formula omits / totalTeachers, the contract actually distributes
totalTeachers × 35 % = totalTeachers × 0.35 of the bursary.
With two teachers it sends 70  %; with three teachers it tries to send 105  % and immediately reverts for lack of balance.

Impact

  • Over‑payment of funds – every teacher gets twice (or more) what they should.

  • Denial of service – if ≥3 teachers exist, LevelOne::graduateAndUpgrade() reverts (ERC20InsufficientBalance), preventing the upgrade path entirely and locking all bursary funds.

Proof of Concept

Add to LevelOneAndGraduateTest.t.sol and run forge test --match-test test_test_teachers_are_overpaid() -vvv => The test will pass, proving that the two teachers receive the full 35% each.

function test_teachers_are_overpaid() public schoolInSession {
uint256 aliceBefore = usdc.balanceOf(alice);
uint256 bobBefore = usdc.balanceOf(bob);
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
uint256 aliceAfter = usdc.balanceOf(alice);
uint256 bobAfter = usdc.balanceOf(bob);
// each got the full 35% instead of 17.5 % => Bursary 30_000e18; 35% = 10_500e18
assertEq(aliceAfter - aliceBefore, 10_500e18);
assertEq(bobAfter - bobBefore, 10_500e18);
// total paid to teachers = 70% of bursary isntead of 35%
assertEq(aliceAfter + bobAfter - aliceBefore - bobBefore, 21_000e18);
}

Adding a third teacher (chris) in LevelOneAndGraduateTest.t.sol makes the call revert:

function _teachersAdded() internal {
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
levelOneProxy.addTeacher(bob);
+ levelOneProxy.addTeacher(chris);
vm.stopPrank();
}
[FAIL: ERC20InsufficientBalance(0x90193C961A926261B756D1E5bb255e67ff9498A1, 9000000000000000000000 [9e21], 10500000000000000000000 [1.05e22])]

Recommended Mitigation

Divide the teacher share by the number of teachers (LevelOne::graduateAndUpgrade):

uint256 totalTeachers = listOfTeachers.length;
-uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
+uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION / totalTeachers;
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

incorrect teacher pay calculation

`payPerTeacher` in `graduateAndUpgrade()` is incorrectly calculated.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.