Hawk High

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

Incorrect Wage Calculation in `LevelOne::graduateAndUpgrade` Leads to Overpayment to Teachers

Summary

In the LevelOne::graduateAndUpgrade function, the teacher wage calculation incorrectly assigns 35% of the bursary to each teacher, rather than dividing the 35% share among all teachers. This results in overpayment, potentially exceeding the available bursary, violating the invariant that teachers collectively receive 35%, the principal receives 5%, and 60% remains in the bursary after the upgrade. The issue disrupts financial integrity and could cause the upgrade to fail due to insufficient funds.

Vulnerability Details

The graduateAndUpgrade function calculates teacher wages as follows:

uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;

Where TEACHER_WAGE = 35 and PRECISION = 100, this computes 35% of the bursary and assigns it to payPerTeacher. The function then transfers payPerTeacher to each teacher in a loop:

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

For n teachers, this results in a total payout of 35% * n of the bursary, instead of dividing the 35% share equally among teachers (i.e., 35% / n per teacher). This violates Invariant 4, which specifies the payment structure:

  • Principal: 5% of bursary

  • Teachers: 35% of bursary (shared)

  • Remaining: 60% in bursary after upgrade

For example, with a bursary of 1000e18 USDC and 3 teachers:

  • Intended: Teachers share 350e18 (35%), so each gets ~116.67e18.

  • Actual: Each teacher receives 350e18, totaling 1050e18 (105%), leaving insufficient funds for the principal’s 5% (50e18) and the 60% remaining (600e18).


Proof Of Code

add the following function in LevelOneIAndGraduateTest.t.sol

function test_incorrectTeacherWageOverpayment() public schoolInSession {
uint256 initialBursary = levelOneProxy.bursary(); // Assume 6000e18 from 6 students
uint256 expectedTeacherShare = (initialBursary * 35) / 100; // 35% = 2100e18
uint256 expectedPayPerTeacher = expectedTeacherShare / 2; // 1050e18 per teacher (2 teachers)
uint256 expectedPrincipalPay = (initialBursary * 5) / 100; // 300e18
uint256 expectedRemainingBursary = initialBursary - (expectedTeacherShare + expectedPrincipalPay); // 3600e18
levelTwoImplementation = new LevelTwo();
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(address(levelTwoImplementation), "");
assert(usdc.balanceOf(address(levelOneProxy))!= expectedRemainingBursary);
assert(usdc.balanceOf(alice) != expectedPayPerTeacher);
assert(usdc.balanceOf(bob) != expectedPayPerTeacher);
assert(usdc.balanceOf(alice) == expectedTeacherShare);
assert(usdc.balanceOf(bob) == expectedTeacherShare);
assert(usdc.balanceOf(principal) == expectedPrincipalPay);
}
Output::
forge test --mt test_incorrectTeacherWageOverpayment -vvv
[⠊] Compiling...
[⠊] Compiling 1 files with Solc 0.8.26
[⠒] Solc 0.8.26 finished in 869.19ms
Compiler run successful!
Ran 1 test for test/LeveOnelAndGraduateTest.t.sol:LevelOneAndGraduateTest
[PASS] test_incorrectTeacherWageOverpayment() (gas: 1407754)
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 15.10ms (476.12µs CPU time)
Ran 1 test suite in 177.76ms (15.10ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Impact

Overpayment to Teachers: If there are n teachers, the contract pays 35% * n of the bursary to teachers, potentially exceeding the available funds.

Bursary Depletion: The remaining bursary (intended to be 60%) is reduced or becomes negative, breaking the payment structure invariant.

Financial Loss: The contract may fail to transfer funds to LevelTwo or revert due to insufficient balance, disrupting the upgrade process.

Protocol Integrity: Incorrect payouts undermine trust in the system’s financial management.

Tools Used

Foundry

Recommendations

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 teacherShare = (bursary * TEACHER_WAGE) / PRECISION;
+ uint256 payPerTeacher = totalTeachers > 0 ? teacherShare / totalTeachers : 0;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
+ uint256 totalPayout = (payPerTeacher * totalTeachers) + principalPay;
+ uint256 remainingBursary = bursary - totalPayout;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
- usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
+ usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
+ inSession = false;
+ bursary = remainingBursary;
+ emit Graduated(_levelTwo);
}
Updates

Lead Judging Commences

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

Give us feedback!