Hawk High

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

LevelOne contract school session lacks a session end mechanism

Summary

The LevelOne contract lacks a mechanism to end a school session once it's started. The contract tracks a sessionEnd timestamp but never uses it to actually end the session. This results in a school that remains permanently "in session" once the first session begins, preventing any new student enrollments indefinitely.

Vulnerability Details

When the startSession() function is called, it sets inSession = true and calculates a sessionEnd timestamp, but there is no corresponding function or mechanism to set inSession back to false once this end time is reached.

The test below demonstrates this vulnerability by showing that the school remains in session even after the sessionEnd time has passed:

function testNoSessionEndMechanism() public {
// 1. verifying that the school is not yet in session
assertFalse(levelOneProxy.getSessionStatus(), "School should not be in session initially");
// 2. Start a session
vm.prank(principal);
levelOneProxy.startSession(70);
assertTrue(levelOneProxy.getSessionStatus(), "School should be in session after starting");
// 3. Note the session end time
uint256 sessionEndTime = levelOneProxy.getSessionEnd();
console2.log("Session end time:", sessionEndTime);
// 4. Warp to after the session end time
vm.warp(sessionEndTime + 1 days);
console2.log("Current time:", block.timestamp);
// 5. Verify that the school is STILL in session despite being past the end time
assertTrue(levelOneProxy.getSessionStatus(), "School is still in session despite being past end time");
// 6. Try to enroll a new student
address newStudent = makeAddr("late_student");
usdc.mint(newStudent, schoolFees);
vm.startPrank(newStudent);
usdc.approve(address(levelOneProxy), schoolFees);
// should fail with HH__AlreadyInSession because the session never ends
vm.expectRevert(LevelOne.HH__AlreadyInSession.selector);
levelOneProxy.enroll();
vm.stopPrank();
}

The crucial issue is that Ethereum smart contracts don't have automatic time-based triggers. The contract sets sessionEnd = block.timestamp + 4 weeks, but this is simply a stored value that doesn't automatically update any state when that time is reached. The notYetInSession modifier only checks the boolean flag inSession, not whether the current time exceeds sessionEnd.

Impact

  1. Permanent Lock State: After the first session starts, the school is permanently "in session".

  2. Enrollment Restriction: No new students can ever enroll after the first session begins.

  3. Single Session Limitation: The school can never have multiple academic sessions/years.

  4. Operational Failure: This makes the contract unusable for ongoing educational operations, as it can only support a single cohort of students.

Tools Used

  • Foundry testing framework

  • Manual code review

  • Custom test function demonstrating the issue (testNoSessionEndMechanism())

Recommendations

Adding an explicit session end function that can only be called by the principal:

function endSession() public onlyPrincipal {
require(block.timestamp >= sessionEnd, "Current session not yet ended");
inSession = false;
emit SessionEnded(block.timestamp);
}
Updates

Lead Judging Commences

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

session state not updated

`inSession` not updated after during upgrade

Support

FAQs

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