Hawk High

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

Missing `sessionEnd` check allows premature upgrade (Invariant Broken)

Description: The graduateAndUpgrade() function does not enforce that the current time (block.timestamp) is greater than or equal to sessionEnd. This means the principal can perform a system upgrade at any time, including during an active academic session. Although the sessionEnd timestamp is set in startSession(), it is never used to gate the upgrade path.

This breaks the logical flow of the contract, where a full session (spanning 4 weeks) is expected to complete before a graduation or system transition takes place.

Impact: Premature upgrades can:

  • Cut the session short before all reviews are complete,

  • Undermine scoring and timing assumptions across the contract,

  • Allow bypass of time-locked evaluation and scoring mechanisms.

Proof of Concept: Paste the following test in the test suite

function test_upgradeSucceedsBeforeSessionEnd() public {
// Add one teacher
vm.prank(principal);
levelOneProxy.addTeacher(alice);
// Enroll one student
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
// Start the session (sets sessionEnd = now + 4 weeks)
vm.prank(principal);
levelOneProxy.startSession(75);
// Confirm sessionEnd is in the future
uint256 nowTs = block.timestamp;
uint256 endTs = levelOneProxy.getSessionEnd();
console2.log("Current timestamp:", nowTs);
console2.log("Session end timestamp:", endTs);
assertGt(endTs, nowTs);
// Deploy LevelTwo and upgrade immediately (should not be allowed logically)
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
// Check that the upgrade still went through
LevelTwo levelTwoProxy = LevelTwo(address(levelOneProxy));
console2.log("Upgrade succeeded. Total students in LevelTwo:", levelTwoProxy.getTotalStudents());
assertEq(levelTwoProxy.getTotalStudents(), 1);
}

Logs:

[PASS] test_upgradeSucceedsBeforeSessionEnd() (gas: 899767)
Logs:
Current timestamp: 1
Session end timestamp: 2419201
Upgrade succeeded. Total students in LevelTwo: 1

Recommended Mitigation: Enforce a time check at the beginning of graduateAndUpgrade():

Patch
require(block.timestamp >= sessionEnd, "Session has not ended yet");
Updates

Lead Judging Commences

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

can graduate without session end

`graduateAndUpgrade()` can be called successfully even when the school session has not ended

Support

FAQs

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