Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Denial-of-Service via Teacher Wage Transfer Reverts

Summary

The graduateAndUpgrade function uses a loop to distribute teacher wages via USDC transfers. If any teacher’s address rejects USDC (e.g., a contract without IERC20 compatibility), the entire transaction reverts, permanently blocking protocol upgrades and wage distributions.

Vulnerability Details

Affected Component:

graduateAndUpgrade Function:

  • Code Flaw: The function iterates over teachers and uses safeTransfer to send wages. A single failed transfer (e.g., to a contract lacking USDC support) reverts the entire transaction.

  • Protocol Impact: Upgrades, principal payments, and remaining teacher wages are blocked until the failing teacher is removed or fixed.

Example:

  1. Teachers: [TeacherA (valid), TeacherB (malicious/defective)].

  2. Action: Principal calls graduateAndUpgrade().

  3. Result: Transfer to TeacherB reverts → entire transaction fails.

Impact

Critical Protocol Halting:

  • Upgrades Blocked: The system cannot progress to the next level.

  • Wage Theft: Teachers and the principal are unpaid until the issue is resolved.

  • Reputational Damage: Stakeholders lose trust in the protocol’s reliability.

Tools Used

Recommendations

Fix 1: Use a Pull Payment Pattern

Allow teachers to withdraw their wages instead of pushing funds:

mapping(address => uint256) public teacherWithdrawable;
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// ... existing logic ...
for (uint256 n = 0; n < totalTeachers; n++) {
address teacher = listOfTeachers[n];
teacherWithdrawable[teacher] += payPerTeacher; // Track owed wages
}
bursary -= (totalTeacherPay + principalPay);
// Transfer principal’s share immediately (no dependency on teachers)
usdc.safeTransfer(principal, principalPay);
// ... upgrade logic ...
}
// Let teachers withdraw their wages independently
function withdrawTeacherWages() external {
uint256 amount = teacherWithdrawable[msg.sender];
require(amount > 0, "No wages available");
teacherWithdrawable[msg.sender] = 0;
usdc.safeTransfer(msg.sender, amount);
}

Fix 2: Isolate Principal Payment from Teacher Transfers

Separate principal payment from teacher transfers to ensure principal is paid even if teacher transfers fail:

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// ... existing logic ...
// Pay principal first
usdc.safeTransfer(principal, principalPay);
// Pay teachers individually (continue on failure)
for (uint256 n = 0; n < totalTeachers; n++) {
address teacher = listOfTeachers[n];
try usdc.safeTransfer(teacher, payPerTeacher) {
bursary -= payPerTeacher;
} catch {
// Log failure but continue execution
emit TransferFailed(teacher);
}
}
// ... upgrade logic ...
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice
yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Appeal created

nhippolyt Submitter
3 months ago
yeahchibyke Lead Judge
3 months ago
yeahchibyke Lead Judge 3 months ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

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