Hawk High

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

Missing enforcement of full student review cycle allows premature upgrade (Invariant Broken)

Description: The graduateAndUpgrade() function does not enforce that all students have received their full cycle of reviews before allowing a system upgrade to LevelTwo. According to the intended lifecycle, each student should be reviewed once per week, for a total of 4 reviews across a 4-week session. However, the current implementation contains no conditional check to verify whether any student has received fewer than 4 reviews.

As a result, the principal can invoke graduateAndUpgrade() even if the session is incomplete or some students have been reviewed less than 4 times.

Impact: Allowing an upgrade before students have completed their full 4-week review cycle undermines the integrity of the system’s evaluation phase. Specifically:

  • Students may be prematurely transitioned to the next level without a full assessment.

  • Review-based scoring logic becomes inconsistent.

  • Any logic in LevelTwo relying on studentScore or reviewCount may be invalid or misaligned.

Proof of Concept: Paste the following test in LevelOneIAndGraduateTest.t.sol

function test_upgradeSucceedsWithoutAllReviews() public {
// Setup: Add 2 teachers
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
levelOneProxy.addTeacher(bob);
vm.stopPrank();
// Enroll one student
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
// Start the session
vm.prank(principal);
levelOneProxy.startSession(70); // cutoff score
// Give only 2 reviews, with a 1 week interval
vm.warp(block.timestamp + 1 weeks);
vm.prank(alice);
levelOneProxy.giveReview(clara, false);
vm.warp(block.timestamp + 1 weeks);
vm.prank(bob);
levelOneProxy.giveReview(clara, true);
// Deploy LevelTwo implementation
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
// Attempt upgrade — this should succeed, proving invariant is broken
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
LevelTwo levelTwoProxy = LevelTwo(address(levelOneProxy));
assertEq(levelTwoProxy.getTotalStudents(), 1);
console2.log("Upgrade succeeded, LevelTwo reports students:", levelTwoProxy.getTotalStudents());
}

Logs:

[PASS] test_upgradeSucceedsWithoutAllReviews() (gas: 1013688)
Logs:
Upgrade succeeded, LevelTwo reports students: 1

Recommended Mitigation: Enforce the invariant by validating that all students have received at least 4 reviews before allowing an upgrade:

Patch
for (uint256 i = 0; i < listOfStudents.length; i++) {
require(reviewCount[listOfStudents[i]] >= 4, "Student has not completed 4 reviews");
}
Updates

Lead Judging Commences

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

cut-off criteria not applied

All students are graduated when the graduation function is called as the cut-off criteria is not applied.

Support

FAQs

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