Hawk High

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

Missing Bursary Reset After Fund Distribution

Summary

The graduateAndUpgrade function in the LevelOne contract distributes 40% of funds to teachers and principal but fails to reset the bursary value afterward. This creates an accounting inconsistency where the full bursary amount persists in state after partial distribution, potentially leading to overpayment in future distributions if additional upgrades are implemented.

Vulnerability Details

In the graduateAndUpgrade function, tokens are distributed to teachers and principal based on percentages of the bursary value:

// LevelOne.sol:389-408
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; // 35%
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION; // 5%
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
// Missing: bursary should be reset or updated here
}

The critical issue is that after distributing 40% of the funds (35% to teachers, 5% to principal), the bursary state variable is never updated to reflect this distribution. The value remains unchanged, creating a discrepancy between:

  1. The actual USDC balance in the contract (60% of original bursary)

  2. The bursary state variable value (still 100% of original bursary)

This discrepancy carries over to the LevelTwo contract since the storage layout preserves the bursary value:

// LevelTwo.sol:10-13
address principal;
bool inSession;
uint256 public sessionEnd;
uint256 public bursary; // This maintains the incorrect value from LevelOne

Impact

This vulnerability has high severity because:

  1. It creates a permanent accounting inconsistency between actual token balance and recorded bursary value

  2. It could lead to attempt to distribute funds that don't exist if any future implementation uses the bursary value

  3. If LevelTwo or a subsequent upgrade attempts to distribute funds based on bursary, it would try to distribute more than is available, causing transactions to fail

  4. The accounting error could hide other balance issues or complicate auditing

The likelihood is high because:

  1. The issue occurs 100% of the time during upgrade

  2. The missing reset is a fundamental part of fund distribution logic

  3. There's no mechanism to correct this after the upgrade

Quantifiable impact:

  • If bursary shows 100,000 USDC but actual balance is 60,000 USDC, any function using bursary would overestimate available funds by 40,000 USDC

Tools Used

Manual code review

Recommendations

  1. Reset Bursary After Distribution:

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// ... existing code ...
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
uint256 totalDistributed = (payPerTeacher * totalTeachers) + principalPay;
uint256 remainingFunds = bursary - totalDistributed;
// Reset bursary to remaining amount before upgrade
bursary = remainingFunds;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}

2.Add Bursary Reconciliation in LevelTwo:

// Add to LevelTwo.sol
function reconcileBursary() public onlyPrincipal {
uint256 actualBalance = usdc.balanceOf(address(this));
bursary = actualBalance;
emit BursaryReconciled(bursary);
}
event BursaryReconciled(uint256 newBursaryValue);

3.Add Post-Upgrade Check in LevelTwo's graduate():

function graduate() public reinitializer(2) {
// Verify and potentially correct bursary value
uint256 actualBalance = usdc.balanceOf(address(this));
if (bursary != actualBalance) {
bursary = actualBalance;
emit BursaryReconciled(bursary);
}
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

bursary not updated

The bursary is not updated after wages have been paid in `graduateAndUpgrade()` function

Support

FAQs

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