Hawk High

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

Incorrect Teacher Payout Calculation in `graduateAndUpgrade` Leads to Potential Denial of Service

Summary

This report identifies a critical vulnerability in the graduateAndUpgrade() function of the LevelOne.sol contract. The calculation for payPerTeacher incorrectly allocates a percentage of the entire bursary to each individual teacher. If the number of teachers is three or more, the cumulative percentage allocated to teachers, combined with the principal's share, will exceed 100% of the available bursary. This will cause the usdc.safeTransfer operations to fail due to insufficient funds, leading to a revert of the entire graduateAndUpgrade transaction. Consequently, no payouts can occur, and the contract upgrade authorization will be blocked, effectively causing a denial of service for the end-of-session process.

Vulnerability Details / Issue Description

The graduateAndUpgrade() function in LevelOne.sol calculates teacher and principal payouts as follows:

// From src/LevelOne.sol
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// ...
uint256 totalTeachers = listOfTeachers.length;
// Flawed Calculation:
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION; // TEACHER_WAGE is 35 (35%)
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION; // PRINCIPAL_WAGE is 5 (5%)
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher); // Will revert if total payout > bursary
}
usdc.safeTransfer(principal, principalPay); // May also revert
}

The TEACHER_WAGE constant is 35 (representing 35%) and PRINCIPAL_WAGE is 5 (representing 5%), with PRECISION being 100.

The issue lies in uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;. This line calculates 35% of the total bursary for each teacher. The correct approach would be to calculate a total pool for all teachers (35% of bursary) and then divide that pool among the totalTeachers.

If the sum of percentages (totalTeachers * TEACHER_WAGE + PRINCIPAL_WAGE) exceeds PRECISION (100), the total calculated payout will exceed the bursary amount. For example:

  • With 1 teacher: Total payout = (1 * 35%) + 5% = 40% of bursary. (OK)

  • With 2 teachers: Total payout = (2 * 35%) + 5% = 70% + 5% = 75% of bursary. (OK)

  • With 3 teachers: Total payout = (3 * 35%) + 5% = 105% + 5% = 110% of bursary. (Exceeds bursary)

When the calculated total payout exceeds the actual bursary held by the contract, the usdc.safeTransfer calls will begin to fail due to insufficient balance, causing the entire transaction to revert.

Proof Of Concept / Scenario

  1. Setup:

  • The LevelOne.sol contract is initialized.

  • The principal adds 3 teachers (e.g., Alice, Bob, Charlie). So, totalTeachers = 3.

  • Students enroll, and the bursary accumulates to, for example, 1000 USDC.

  1. Execution:

  • The principal calls graduateAndUpgrade(levelTwoAddress, "").

  • payPerTeacher is calculated as (1000 * 35) / 100 = 350 USDC.

  • principalPay is calculated as (1000 * 5) / 100 = 50 USDC.

  • The total intended payout is (3 * 350) + 50 = 1050 + 50 = 1100 USDC.

  • The contract attempts to transfer 350 USDC to Alice (succeeds if bursary >= 350).

  • The contract attempts to transfer 350 USDC to Bob (succeeds if remaining bursary >= 350).

  • The contract attempts to transfer 350 USDC to Charlie. At this point, the contract may have insufficient funds (e.g., 1000 - 350 - 350 = 300 USDC remaining, but 350 USDC is needed). This usdc.safeTransfer call will revert.

3.Outcome:

  • The entire graduateAndUpgrade transaction reverts.

  • No teachers or the principal receive their payouts.

  • The _authorizeUpgrade(_levelTwo) call does not take effect, so the contract cannot be upgraded.

Impact

The impact of this vulnerability is High:

  • Denial of Service for Core Functionality: The primary mechanism for concluding a session, distributing funds, and upgrading the contract becomes unusable if there are 3 or more teachers.

  • Funds Locked (Effectively): While the USDC tokens are still in the contract, they cannot be distributed as intended by the graduateAndUpgrade function.

  • Blocked Upgrades: The inability to successfully execute graduateAndUpgrade means the UUPS upgrade mechanism cannot be authorized, preventing future evolution of the contract logic.

Tools Used

Manual Code Review

Recommendations

The payout logic for teachers must be corrected. The TEACHER_WAGE should represent the total percentage of the bursary allocated to the collective of teachers. This total teacher pool should then be divided among the totalTeachers.

@@ -79,6 +79,7 @@
error HH__ZeroValue();
error HH__HawkHighFeesNotPaid();
error HH__ReviewPrerequisiteNotMet();
+ error HH__PayoutExceedsBursary();
error HH__NotAllowed();
////////////////////////////////
@@ -300,8 +301,17 @@
uint256 totalTeachers = listOfTeachers.length;
- uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
+ // Calculate the total pool of funds allocated for all teachers
+ uint256 totalTeacherPayoutPool = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
+ uint256 payPerTeacher = 0;
+
+ if (totalTeachers > 0) {
+ // Divide the total teacher pool among the number of teachers
+ payPerTeacher = totalTeacherPayoutPool / totalTeachers;
+ }
+
+ require(totalTeacherPayoutPool + principalPay <= bursary, "HH__PayoutExceedsBursary");
_authorizeUpgrade(_levelTwo);
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.

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.