The LevelOne::graduateAndUpgrade
function distributes the bursary
to teachers and the principal. However, the function does not update the bursary
state variable after these payouts are made. This violates the project's documented invariant:
Invariant: “Remaining 60% should reflect in the bursary after upgrade.”
As a result, the bursary
variable retains the full pre-distribution value, even though 40% has already been transferred out of the contract. This may seem like a misaccounting issue at first, but it introduces a critical denial-of-service condition.
If graduateAndUpgrade()
is called again (e.g. as part of a planned re-upgrade (after another 4-week term)), the function will recalculate payouts based on the stale bursary
value. Since the contract no longer holds the full amount, ERC20 transfers will fail, and the entire transaction will revert. This locks up the upgrade path and prevents the principal from initiating further transitions, violating protocol liveness and payout guarantees.
Add to LevelOneAndGraduateTest.t.sol
and run forge test --match-test test_bursary_updated_after_graduation -vvv
=> The test will fail, proving that the bursary and the balance don't match after the upgrade/payouts.
Update the bursary
variable after payouts to reflect only the remaining 60%:
This ensures bursary
always represents the actual undistributed funds, as required by the protocol's invariant and prevents denial of service on repeated or retry calls.
The bursary is not updated after wages have been paid in `graduateAndUpgrade()` function
The bursary is not updated after wages have been paid in `graduateAndUpgrade()` function
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.