Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Improper enforcement of session duration in startSession() __LevelOne.sol

Summary

The startSession function currently allows starting a school session at any time, regardless of how much time is left in the academic year. While it always sets the session duration to 4 weeks, it does not enforce that the session must actually be able to run for that full period. This breaks the system invariant:

"A school session lasts 4 weeks."

Vulnerability Details

The session system is designed such that each session should last exactly 4 weeks. However, the function does not prevent sessions from being started late in the school year when there isn't enough time left for a full 4-week period. This allows a session to spill over beyond the intended boundary, violating the invariant.

function startSession(uint256 _cutOffScore) public onlyPrincipal notYetInSession {
sessionEnd = block.timestamp + 4 weeks;
inSession = true;
cutOffScore = _cutOffScore;
emit SchoolInSession(block.timestamp, sessionEnd);
}

Example Scenario

Suppose:

  • block.timestamp = June 1

  • schoolYearEnd = June 15

If startSession(50) is called, the contract sets:

sessionEnd = June 1 + 4 weeks = June 29

This exceeds the schoolYearEnd, even though the system assumes every session must be completed within the academic year.


Proof of Concept (PoC)

function test_startSession() public {
vm.deal(STUDENT1, 2e18);
vm.prank(STUDENT1);
levelOne.enroll();
vm.warp(block.timestamp + 30 days);
vm.prank(PRINCIPAL);
levelOne.startSession(50);
}

Without additional checks, this test passes confirming the session is improperly allowed to start late.

Traces:
[227082] LevelOneTest::test_startSession()
├─ [0] VM::deal(student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], 2000000000000000000 [2e18])
│ └─ ← [Return]
├─ [0] VM::prank(student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8])
│ └─ ← [Return]
├─ [161573] LevelOne::enroll()
│ ├─ [36419] MockUSDC::transferFrom(student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], LevelOne: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 1000000000000000000 [1e18])
│ │ ├─ emit Transfer(from: student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], to: LevelOne: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], value: 1000000000000000000 [1e18])
│ │ └─ ← [Return] true
│ ├─ emit Enrolled(: student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8])
│ └─ ← [Stop]
├─ [0] VM::warp(25920001 [2.592e7])
│ └─ ← [Return]
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [50080] LevelOne::startSession(50)
│ ├─ emit SchoolInSession(startTime: 25920001 [2.592e7], endTime: 28339201 [2.833e7])
│ └─ ← [Stop]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.37ms (240.11µs CPU time)
Ran 1 test suite in 2.34s (2.37ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
claudia@LAPTOP-MKPOQDLO:~/code/marketplace$

Impact

Breaks declared invariant: sessions last 4 weeks

Can result in overlapping or logically inconsistent session behavior

Tools Used

Manual Review

Foundry (forge test)

Recommendations

Recommended Fix:

> @ uint256 public schoolYearEnd;
function startSession(uint256 _cutOffScore) public onlyPrincipal notYetInSession {
> @ require(block.timestamp + 4 weeks <= schoolYearEnd, "School year has ended");
sessionEnd = block.timestamp + 4 weeks;
inSession = true;
cutOffScore = _cutOffScore;
emit SchoolInSession(block.timestamp, sessionEnd);
}
function test_startSession() public {
vm.deal(STUDENT1, 2e18);
vm.prank(STUDENT1);
levelOne.enroll();
vm.warp(block.timestamp + 300 days);
vm.prank(PRINCIPAL);
vm.expectRevert("School year has ended");
levelOne.startSession(50);
}

With these changes the test reverts when attempting to start a session too late.

Ran 1 test for test/LevelOne.t.sol:LevelOneTest
[PASS] test_startSession() (gas: 181093)
Traces:
[181093] LevelOneTest::test_startSession()
├─ [0] VM::deal(student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], 2000000000000000000 [2e18])
│ └─ ← [Return]
├─ [0] VM::prank(student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8])
│ └─ ← [Return]
├─ [161573] LevelOne::enroll()
│ ├─ [36419] MockUSDC::transferFrom(student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], LevelOne: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 1000000000000000000 [1e18])
│ │ ├─ emit Transfer(from: student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], to: LevelOne: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], value: 1000000000000000000 [1e18])
│ │ └─ ← [Return] true
│ ├─ emit Enrolled(: student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8])
│ └─ ← [Stop]
├─ [0] VM::warp(25920001 [2.592e7])
│ └─ ← [Return]
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error 0xf28dceb3: School year has ended)
│ └─ ← [Return]
├─ [3512] LevelOne::startSession(50)
│ └─ ← [Revert] revert: School year has ended
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.31ms (572.72µs CPU time)
Ran 1 test suite in 31.36ms (8.31ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
Updates

Lead Judging Commences

yeahchibyke Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Appeal created

hawks Submitter
4 months ago
yeahchibyke Lead Judge
4 months ago
yeahchibyke Lead Judge 4 months ago
Submission Judgement Published
Invalidated
Reason: Non-acceptable severity

Support

FAQs

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