Summary
A vulnerability in the LevelOne smart contract allows students to graduate and upgrade to LevelTwo even when their final score falls below the established cutoff score. This bypasses critical academic requirements and compromises the integrity of the educational system.
The LevelOne contract's score management system allows teachers to reduce student scores below the minimum cutoff threshold set by the principal, violating the intended business logic and potentially leading to unfair student evaluations.
Vulnerability Details for Unrestricted Graduation Despite Failing Cutoff Score
The contract fails to properly enforce the cutoff score requirement during the graduation process. While the contract tracks student scores and allows setting a cutoff score, the graduateAndUpgrade function does not validate that students meet the minimum score requirement before allowing graduation.
POC for Unrestricted Graduation Despite Failing Cutoff Score
pragma solidity 0.8.26;
import {Test, console2} from "forge-std/Test.sol";
import {DeployLevelOne} from "../script/DeployLevelOne.s.sol";
import {LevelOne} from "../src/LevelOne.sol";
import {LevelTwo} from "../src/LevelTwo.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract CutoffScoreVulnerabilityTest is Test {
LevelOne levelOneProxy;
IERC20 usdc;
address proxyAddress;
address principal;
address alice;
address bob;
address clara;
address david;
uint256 constant STARTING_BALANCE = 10000e18;
uint256 schoolFees;
function setUp() public {
DeployLevelOne deployer = new DeployLevelOne();
proxyAddress = deployer.deployLevelOne();
levelOneProxy = LevelOne(proxyAddress);
usdc = deployer.getUSDC();
principal = deployer.principal();
schoolFees = deployer.getSchoolFees();
alice = makeAddr("alice");
bob = makeAddr("bob");
clara = makeAddr("clara");
david = makeAddr("david");
deal(address(usdc), clara, STARTING_BALANCE);
deal(address(usdc), david, STARTING_BALANCE);
}
function test_CutoffScoreVulnerabilities() public {
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
vm.stopPrank();
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(principal);
levelOneProxy.startSession(1000);
assertEq(levelOneProxy.cutOffScore(), 1000);
vm.stopPrank();
vm.startPrank(alice);
for (uint i = 0; i < 5; i++) {
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
}
vm.stopPrank();
uint256 finalScore = levelOneProxy.studentScore(clara);
assertEq(finalScore, 50);
vm.warp(block.timestamp + 4 weeks);
LevelTwo levelTwo = new LevelTwo();
address levelTwoImplementation = address(levelTwo);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementation, data);
}
}
The POC demonstrates the vulnerability through the following steps:
A teacher (Alice) is added to the system by the principal
A student (Clara) enrolls by paying school fees
The principal sets an unreasonably high cutoff score of 1000
The teacher gives multiple negative reviews, dropping the student's score to 50
Despite having a score (50) well below the cutoff (1000), the student can still graduate and upgrade to LevelTwo
Key vulnerable code paths:
levelOneProxy.graduateAndUpgrade(levelTwoImplementation, data);
Test Output for Unrestricted Graduation Despite Failing Cutoff Score
Ran 1 test for test/CutoffScoreVulnerabilityTest.t.sol:CutoffScoreVulnerabilityTest
[PASS] test_CutoffScoreVulnerabilities() (gas: 914394)
Traces:
[934294] CutoffScoreVulnerabilityTest::test_CutoffScoreVulnerabilities()
├─ [0] VM::startPrank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [78232] ERC1967Proxy::fallback(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ ├─ [73258] LevelOne::addTeacher(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [delegatecall]
│ │ ├─ emit TeacherAdded(: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674])
│ └─ ← [Return]
├─ [25298] MockUSDC::approve(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ ├─ emit Approval(owner: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], spender: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ └─ ← [Return] true
├─ [152722] ERC1967Proxy::fallback()
│ ├─ [152251] LevelOne::enroll() [delegatecall]
│ │ ├─ [31619] MockUSDC::transferFrom(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ │ │ ├─ emit Transfer(from: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], to: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ emit Enrolled(: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [50532] ERC1967Proxy::fallback(1000)
│ ├─ [50058] LevelOne::startSession(1000) [delegatecall]
│ │ ├─ emit SchoolInSession(startTime: 1, endTime: 2419201 [2.419e6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [968] ERC1967Proxy::fallback() [staticcall]
│ ├─ [494] LevelOne::cutOffScore() [delegatecall]
│ │ └─ ← [Return] 1000
│ └─ ← [Return] 1000
├─ [0] VM::assertEq(1000, 1000) [staticcall]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ └─ ← [Return]
├─ [0] VM::warp(604801 [6.048e5])
│ └─ ← [Return]
├─ [29010] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [28533] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 90)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(1209601 [1.209e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 80)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(1814401 [1.814e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 70)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(2419201 [2.419e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 60)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(3024001 [3.024e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 50)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 50
│ └─ ← [Return] 50
├─ [0] VM::assertEq(50, 50) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(5443201 [5.443e6])
│ └─ ← [Return]
├─ [445078] → new LevelTwo@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 2223 bytes of code
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [57124] ERC1967Proxy::fallback(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0xd3618cca)
│ ├─ [56635] LevelOne::graduateAndUpgrade(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0xd3618cca) [delegatecall]
│ │ ├─ [25750] MockUSDC::transfer(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], 1750000000000000000000 [1.75e21])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], value: 1750000000000000000000 [1.75e21])
│ │ │ └─ ← [Return] true
│ │ ├─ [25750] MockUSDC::transfer(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca], 250000000000000000000 [2.5e20])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca], value: 250000000000000000000 [2.5e20])
│ │ │ └─ ← [Return] true
│ │ └─ ← [Stop]
│ └─ ← [Return]
└─ ← [Stop]
Vulnerability Details for Score Manipulation Below Minimum Cutoff Threshold
The contract's score management system contains a critical flaw where negative reviews can continue to decrease a student's score even after it reaches the minimum cutoff score set by the principal. This vulnerability is demonstrated in the following sequence:
-
The principal sets a minimum cutoff score of 50
-
A student starts with an initial score of 100
-
Through consecutive negative weekly reviews, the score can be reduced to:
The contract lacks proper validation to prevent scores from falling below the established minimum cutoff value.
POC for Score Manipulation Below Minimum Cutoff Threshold
pragma solidity 0.8.26;
import {Test, console2} from "forge-std/Test.sol";
import {DeployLevelOne} from "../script/DeployLevelOne.s.sol";
import {LevelOne} from "../src/LevelOne.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
contract ScoreManipulationTest is Test {
LevelOne levelOneProxy;
IERC20 usdc;
address proxyAddress;
address principal;
address alice;
address clara;
uint256 constant STARTING_BALANCE = 10000e18;
uint256 schoolFees;
function setUp() public {
DeployLevelOne deployer = new DeployLevelOne();
proxyAddress = deployer.deployLevelOne();
levelOneProxy = LevelOne(proxyAddress);
usdc = deployer.getUSDC();
principal = deployer.principal();
schoolFees = deployer.getSchoolFees();
alice = makeAddr("alice");
clara = makeAddr("clara");
deal(address(usdc), clara, STARTING_BALANCE);
}
function test_ScoreManipulationVulnerability() public {
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
vm.stopPrank();
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.prank(principal);
levelOneProxy.startSession(50);
vm.startPrank(alice);
assertEq(levelOneProxy.studentScore(clara), 100);
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 90);
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 80);
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 70);
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 60);
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 50);
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, false);
assertEq(levelOneProxy.studentScore(clara), 40);
vm.stopPrank();
}
}
Test Output for Score Manipulation Below Minimum Cutoff Threshold
Ran 1 test for test/ScoreManipulationTest.t.sol:ScoreManipulationTest
[PASS] test_ScoreManipulationVulnerability() (gas: 398533)
Traces:
[418433] ScoreManipulationTest::test_ScoreManipulationVulnerability()
├─ [0] VM::startPrank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [78232] ERC1967Proxy::fallback(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ ├─ [73258] LevelOne::addTeacher(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [delegatecall]
│ │ ├─ emit TeacherAdded(: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674])
│ └─ ← [Return]
├─ [25298] MockUSDC::approve(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ ├─ emit Approval(owner: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], spender: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ └─ ← [Return] true
├─ [152722] ERC1967Proxy::fallback()
│ ├─ [152251] LevelOne::enroll() [delegatecall]
│ │ ├─ [31619] MockUSDC::transferFrom(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ │ │ ├─ emit Transfer(from: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], to: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ emit Enrolled(: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [50532] ERC1967Proxy::fallback(50)
│ ├─ [50058] LevelOne::startSession(50) [delegatecall]
│ │ ├─ emit SchoolInSession(startTime: 1, endTime: 2419201 [2.419e6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::startPrank(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 100
│ └─ ← [Return] 100
├─ [0] VM::assertEq(100, 100) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(604801 [6.048e5])
│ └─ ← [Return]
├─ [29010] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [28533] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 90)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 90
│ └─ ← [Return] 90
├─ [0] VM::assertEq(90, 90) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(1209601 [1.209e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 80)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 80
│ └─ ← [Return] 80
├─ [0] VM::assertEq(80, 80) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(1814401 [1.814e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 70)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 70
│ └─ ← [Return] 70
├─ [0] VM::assertEq(70, 70) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(2419201 [2.419e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 60)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 60
│ └─ ← [Return] 60
├─ [0] VM::assertEq(60, 60) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(3024001 [3.024e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 50)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 50
│ └─ ← [Return] 50
├─ [0] VM::assertEq(50, 50) [staticcall]
│ └─ ← [Return]
├─ [0] VM::warp(3628801 [3.628e6])
│ └─ ← [Return]
├─ [5110] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false)
│ ├─ [4633] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], false) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: false, studentScore: 40)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [1325] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [848] LevelOne::studentScore(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] 40
│ └─ ← [Return] 40
├─ [0] VM::assertEq(40, 40) [staticcall]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
└─ ← [Stop]
Impact
Students can graduate without meeting minimum academic requirements
Compromises educational standards and certification integrity
Potential reputational damage to the institution
Undermines the purpose of having a cutoff score system
Students can be unfairly evaluated with scores below the minimum threshold
Violation of the school's grading policy
Potential financial implications if scholarship decisions or continued enrollment are based on scores
Loss of system integrity and trust
Tools Used
Foundry Framework
Forge testing suite
Manual review
Recommendations
Add score validation in the graduateAndUpgrade function:
function graduateAndUpgrade(address implementation, bytes memory data) external {
require(studentScore[msg.sender] >= cutOffScore, "Score below cutoff");
}
Implement a score floor check in the giveReview function:
function giveReview(address student, bool positive) public {
uint256 currentScore = studentScore[student];
uint256 newScore = positive ? currentScore + 10 : currentScore - 10;
if (!positive && newScore < minimumCutoffScore) {
newScore = minimumCutoffScore;
}
studentScore[student] = newScore;
}