Hawk High

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

Overpayment of Teachers Due to Missing Division by Number of Teachers

Description: The graduateAndUpgrade() function in the LevelOne contract attempts to distribute 35% of the bursary to teachers and 5% to the principal. However, the calculation of payPerTeacher does not divide the teacher allocation by the number of teachers. As a result, each teacher receives 35% of the bursary, instead of 35% being split across all teachers.

This leads to an overpayment that exceeds the available bursary, potentially draining the contract or causing safeTransfer calls to revert due to insufficient funds.

Impact: If there are n teachers, the total teacher payout becomes n * (35% of bursary), rather than 35%. This results in:

  • Up to n * 35% of the bursary being transferred.

  • Contract reverts due to insufficient balance if n > 2.

  • Principal may not be paid if the contract balance is exhausted prematurely.

Proof of Concept: Paste the following test in LevelOneIAndGraduateTest.t.sol

function test_graduateAndUpgradeOverpaysTeachers() public {
// Create 5 teachers
address charlie = makeAddr("charlie");
address dave = makeAddr("dave");
address eve = makeAddr("eve");
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
levelOneProxy.addTeacher(bob);
levelOneProxy.addTeacher(charlie);
levelOneProxy.addTeacher(dave);
levelOneProxy.addTeacher(eve);
vm.stopPrank();
console2.log("Teachers added");
// Fund contract via a student enrolling
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
console2.log("Student enrolled and paid:", schoolFees);
console2.log("Contract USDC balance:", usdc.balanceOf(address(levelOneProxy)));
// Deploy level two implementation
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
// Calculate expected incorrect payouts
uint256 teacherWage = (schoolFees * levelOneProxy.TEACHER_WAGE()) / levelOneProxy.PRECISION();
uint256 principalWage = (schoolFees * levelOneProxy.PRINCIPAL_WAGE()) / levelOneProxy.PRECISION();
console2.log("Total bursary:", schoolFees);
console2.log("Incorrect payPerTeacher (no division):", teacherWage);
console2.log("Principal payout:", principalWage);
console2.log("Number of teachers:", levelOneProxy.getTotalTeachers());
console2.log("Total teacher payout (5x over):", teacherWage * 5);
// Expect revert due to overpayment
vm.prank(principal);
vm.expectRevert(); // should fail on 3rd or 4th transfer
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
}

Log Output:

[PASS] test_graduateAndUpgradeOverpaysTeachers() (gas: 1072399)
Logs:
Teachers added
Student enrolled and paid: 5000000000000000000000
Contract USDC balance: 5000000000000000000000
Total bursary: 5000000000000000000000
Incorrect payPerTeacher (no division): 1750000000000000000000
Principal payout: 250000000000000000000
Number of teachers: 5
Total teacher payout (5x over): 8750000000000000000000

Recommended Mitigation:
Split the teacher allocation evenly by dividing by the number of teachers:

Patch
uint256 teacherTotal = (bursary * TEACHER_WAGE) / PRECISION;
uint256 payPerTeacher = teacherTotal / listOfTeachers.length;
Updates

Lead Judging Commences

yeahchibyke Lead Judge about 1 month 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.