Hawk High

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

Incorrect Bursary Payment Distribution Logic

Summary

The contract implements an incorrect payment distribution algorithm in the graduateAndUpgrade function. Instead of splitting the designated teacher wage percentage (35%) among all teachers, the contract gives each teacher 35% of the bursary. This causes the total distribution to exceed the available bursary when there are more than 2 teachers, which will lead to transaction failures.

Vulnerability Details

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);
}

The issue occurs in the calculation of payPerTeacher. Instead of dividing the total teacher allocation by the number of teachers, it assigns each teacher the full 35% allocation. This means:

  • With 2 teachers: 70% + 5% = 75% of bursary paid out

  • With 3 teachers: 105% + 5% = 110% of bursary paid out (exceeds available funds)

  • With 4 teachers: 140% + 5% = 145% of bursary paid out (exceeds available funds)

Impact

When there are 3 or more teachers, the function will attempt to distribute more funds than are available in the bursary.

  • This will cause the transaction to revert due to insufficient funds, making it impossible to upgrade the contract.

  • Even with only 2 teachers, the contract distributes 75% of the bursary instead of the intended 40%, depleting funds faster than intended.

Tools Used

Foundry, Manual Code Review

Recommendations

Modify the payment calculation to properly distribute the teacher allocation among all teachers:

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
uint256 totalTeachers = listOfTeachers.length;
// Calculate total teacher allocation first
uint256 totalTeacherAllocation = (bursary * TEACHER_WAGE) / PRECISION;
// Then divide by number of teachers to get per-teacher amount
uint256 payPerTeacher = totalTeachers > 0 ? totalTeacherAllocation / totalTeachers : 0;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}

This ensures that all teachers collectively receive 35% of the bursary, regardless of how many teachers there are

Poc

function test_Bursary_Payment_Precision_Error() public {
// Setup: Add teachers and enroll students
_teachersAdded();
_studentsEnrolled();
// Calculate expected values
uint256 initialBursary = levelOneProxy.bursary();
uint256 totalTeachers = levelOneProxy.getTotalTeachers();
uint256 expectedPayPerTeacher = (initialBursary *
levelOneProxy.TEACHER_WAGE()) /
(levelOneProxy.PRECISION() * totalTeachers);
uint256 currentPayPerTeacher = (initialBursary *
levelOneProxy.TEACHER_WAGE()) / levelOneProxy.PRECISION();
uint256 principalPay = (initialBursary *
levelOneProxy.PRINCIPAL_WAGE()) / levelOneProxy.PRECISION();
// Total amount that would be paid with current implementation
uint256 totalPaidWithBug = (currentPayPerTeacher * totalTeachers) +
principalPay;
// Log values for clarity
console.log("Initial Bursary:", initialBursary);
console.log("Number of Teachers:", totalTeachers);
console.log(
"Expected Pay Per Teacher (corrected calculation):",
expectedPayPerTeacher
);
console.log(
"Current Pay Per Teacher (buggy calculation):",
currentPayPerTeacher
);
console.log("Principal Pay:", principalPay);
console.log("Total Paid With Bug:", totalPaidWithBug);
// Calculate the intended and actual percentage distributions
uint256 intendedPercentage = levelOneProxy.TEACHER_WAGE() +
levelOneProxy.PRINCIPAL_WAGE();
uint256 actualPercentage = (((currentPayPerTeacher * totalTeachers) +
principalPay) * 100) / initialBursary;
console.log(
"Intended Distribution Percentage:",
intendedPercentage,
"%"
);
console.log("Actual Distribution Percentage:", actualPercentage, "%");
// The issue exists when more than one teacher is present
if (totalTeachers > 1) {
// Assert that the actual percentage is higher than intended when multiple teachers exist
assertTrue(
actualPercentage > intendedPercentage,
"Bug not detected: Actual percentage should exceed intended percentage"
);
// Show how the payout scales with more teachers
console.log(
"Each teacher gets 35% of the bursary instead of (35% / totalTeachers)"
);
// Calculate what would happen with more teachers to demonstrate the severity
uint256 teachersToExceedBursary = (100 -
levelOneProxy.PRINCIPAL_WAGE()) /
levelOneProxy.TEACHER_WAGE() +
1;
console.log(
"With",
teachersToExceedBursary,
"teachers, the total payout would exceed 100% of the bursary"
);
}
// Test with a simulated larger number of teachers to show failure
uint256 simulatedTeachers = 4; // Choose a number that would exceed 100%
uint256 simulatedPayout = (currentPayPerTeacher * simulatedTeachers) +
principalPay;
uint256 simulatedPercentage = (simulatedPayout * 100) / initialBursary;
console.log("Simulated with", simulatedTeachers, "teachers:");
console.log("Simulated Total Payout:", simulatedPayout);
console.log(
"Simulated Percentage of Bursary:",
simulatedPercentage,
"%"
);
// This assertion should pass if we've chosen enough simulated teachers
assertTrue(
simulatedPercentage > 100,
"With enough teachers, payout should exceed 100% of bursary"
);
}

Output

[PASS] test_Bursary_Payment_Precision_Error() (gas: 791244)
Logs:
Initial Bursary: 30000000000000000000000
Number of Teachers: 2
Expected Pay Per Teacher (corrected calculation): 5250000000000000000000
Current Pay Per Teacher (buggy calculation): 10500000000000000000000
Principal Pay: 1500000000000000000000
Total Paid With Bug: 22500000000000000000000
Intended Distribution Percentage: 40 %
Actual Distribution Percentage: 75 %
Each teacher gets 35% of the bursary instead of (35% / totalTeachers)
With 3 teachers, the total payout would exceed 100% of the bursary
Simulated with 4 teachers:
Simulated Total Payout: 43500000000000000000000
Simulated Percentage of Bursary: 145 %

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.

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.