The contract fails to enforce a core requirement that students must receive 4 weekly reviews before graduation. It can be found here:
pragma solidity 0.8.26;
import {Test, console2} from "forge-std/Test.sol";
import {DeployLevelOne} from "../script/DeployLevelOne.s.sol";
import {GraduateToLevelTwo} from "../script/GraduateToLevelTwo.s.sol";
import {LevelOne} from "../src/LevelOne.sol";
import {LevelTwo} from "../src/LevelTwo.sol";
import {MockUSDC} from "./mocks/MockUSDC.sol";
contract MissingReviewValidationTest is Test {
DeployLevelOne deployBot;
GraduateToLevelTwo graduateBot;
LevelOne levelOneProxy;
LevelTwo levelTwoImplementation;
MockUSDC usdc;
address proxyAddress;
address levelOneImplementationAddress;
address principal;
uint256 schoolFees;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
address clara = makeAddr("clara");
address dan = makeAddr("dan");
function setUp() public {
deployBot = new DeployLevelOne();
proxyAddress = deployBot.deployLevelOne();
levelOneProxy = LevelOne(proxyAddress);
usdc = deployBot.getUSDC();
principal = deployBot.principal();
schoolFees = deployBot.getSchoolFees();
levelOneImplementationAddress = deployBot.getImplementationAddress();
clara = makeAddr("clara");
dan = makeAddr("dan");
alice = makeAddr("alice");
bob = makeAddr("bob");
vm.startPrank(address(usdc));
usdc.mint(clara, schoolFees);
usdc.mint(dan, schoolFees);
vm.stopPrank();
}
function test_POC_GraduateWithoutRequiredReviews() public {
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
levelOneProxy.addTeacher(bob);
vm.stopPrank();
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(dan);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.prank(principal);
levelOneProxy.startSession(70);
vm.startPrank(alice);
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, true);
levelOneProxy.giveReview(dan, true);
vm.warp(block.timestamp + 1 weeks);
levelOneProxy.giveReview(clara, true);
levelOneProxy.giveReview(dan, true);
vm.stopPrank();
vm.warp(block.timestamp + 2 weeks);
levelTwoImplementation = new LevelTwo();
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
uint256 aliceBalanceBefore = usdc.balanceOf(alice);
uint256 bobBalanceBefore = usdc.balanceOf(bob);
uint256 principalBalanceBefore = usdc.balanceOf(principal);
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(address(levelTwoImplementation), data);
uint256 aliceBalanceAfter = usdc.balanceOf(alice);
uint256 bobBalanceAfter = usdc.balanceOf(bob);
uint256 principalBalanceAfter = usdc.balanceOf(principal);
console2.log(
"Alice balance change:",
aliceBalanceAfter - aliceBalanceBefore
);
console2.log("Bob balance change:", bobBalanceAfter - bobBalanceBefore);
console2.log(
"Principal balance change:",
principalBalanceAfter - principalBalanceBefore
);
LevelTwo levelTwo = LevelTwo(proxyAddress);
console2.log("Clara is student:", levelTwo.isStudent(clara));
console2.log("Dan is student:", levelTwo.isStudent(dan));
assertTrue(
levelTwo.isStudent(clara),
"Clara graduated with incomplete reviews"
);
assertTrue(
levelTwo.isStudent(dan),
"Dan graduated with incomplete reviews"
);
}
function test_POC_GraduateWithNoReviews() public {
vm.prank(principal);
levelOneProxy.addTeacher(alice);
vm.startPrank(clara);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.prank(principal);
levelOneProxy.startSession(70);
vm.warp(block.timestamp + 4 weeks);
levelTwoImplementation = new LevelTwo();
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(address(levelTwoImplementation), data);
LevelTwo levelTwo = LevelTwo(address(levelTwoImplementation));
assertTrue(
levelTwo.isStudent(clara),
"Clara graduated with no reviews"
);
}
}
Ran 1 test for test/MissingReviewValidationTest.t.sol:MissingReviewValidationTest
[PASS] test_POC_GraduateWithoutRequiredReviews() (gas: 1138683)
Logs:
Alice balance change: 3500000000000000000000
Bob balance change: 3500000000000000000000
Principal balance change: 500000000000000000000
Clara is student: true
Dan is student: true
Traces:
[1188083] MissingReviewValidationTest::test_POC_GraduateWithoutRequiredReviews()
├─ [0] VM::startPrank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [78232] ERC1967Proxy::fallback(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ ├─ [73258] LevelOne::addTeacher(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [delegatecall]
│ │ ├─ emit TeacherAdded(: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e])
│ ├─ [49358] LevelOne::addTeacher(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e]) [delegatecall]
│ │ ├─ emit TeacherAdded(: bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e])
│ │ └─ ← [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(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd])
│ └─ ← [Return]
├─ [25298] MockUSDC::approve(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ ├─ emit Approval(owner: dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], spender: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ └─ ← [Return] true
├─ [83022] ERC1967Proxy::fallback()
│ ├─ [82551] LevelOne::enroll() [delegatecall]
│ │ ├─ [9719] MockUSDC::transferFrom(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ │ │ ├─ emit Transfer(from: dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], to: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ emit Enrolled(: dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [50532] ERC1967Proxy::fallback(70)
│ ├─ [50058] LevelOne::startSession(70) [delegatecall]
│ │ ├─ emit SchoolInSession(startTime: 1, endTime: 2419201 [2.419e6])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::startPrank(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6])
│ └─ ← [Return]
├─ [0] VM::warp(604801 [6.048e5])
│ └─ ← [Return]
├─ [28511] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], true)
│ ├─ [28034] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], true) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: true, studentScore: 100)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [28511] ERC1967Proxy::fallback(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], true)
│ ├─ [28034] LevelOne::giveReview(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], true) [delegatecall]
│ │ ├─ emit ReviewGiven(student: dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], review: true, studentScore: 100)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::warp(1209601 [1.209e6])
│ └─ ← [Return]
├─ [4611] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], true)
│ ├─ [4134] LevelOne::giveReview(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], true) [delegatecall]
│ │ ├─ emit ReviewGiven(student: clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674], review: true, studentScore: 100)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [4611] ERC1967Proxy::fallback(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], true)
│ ├─ [4134] LevelOne::giveReview(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], true) [delegatecall]
│ │ ├─ emit ReviewGiven(student: dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd], review: true, studentScore: 100)
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::warp(2419201 [2.419e6])
│ └─ ← [Return]
├─ [445078] → new LevelTwo@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 2223 bytes of code
├─ [2851] MockUSDC::balanceOf(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [staticcall]
│ └─ ← [Return] 0
├─ [2851] MockUSDC::balanceOf(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e]) [staticcall]
│ └─ ← [Return] 0
├─ [2851] MockUSDC::balanceOf(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca]) [staticcall]
│ └─ ← [Return] 0
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [78129] ERC1967Proxy::fallback(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0xd3618cca)
│ ├─ [77640] LevelOne::graduateAndUpgrade(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0xd3618cca) [delegatecall]
│ │ ├─ [23750] MockUSDC::transfer(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], 3500000000000000000000 [3.5e21])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6], value: 3500000000000000000000 [3.5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ [23750] MockUSDC::transfer(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e], 3500000000000000000000 [3.5e21])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e], value: 3500000000000000000000 [3.5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ [23750] MockUSDC::transfer(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca], 500000000000000000000 [5e20])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca], value: 500000000000000000000 [5e20])
│ │ │ └─ ← [Return] true
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [851] MockUSDC::balanceOf(alice: [0x328809Bc894f92807417D2dAD6b7C998c1aFdac6]) [staticcall]
│ └─ ← [Return] 3500000000000000000000 [3.5e21]
├─ [851] MockUSDC::balanceOf(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e]) [staticcall]
│ └─ ← [Return] 3500000000000000000000 [3.5e21]
├─ [851] MockUSDC::balanceOf(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca]) [staticcall]
│ └─ ← [Return] 500000000000000000000 [5e20]
├─ [0] console::log("Alice balance change:", 3500000000000000000000 [3.5e21]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Bob balance change:", 3500000000000000000000 [3.5e21]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Principal balance change:", 500000000000000000000 [5e20]) [staticcall]
│ └─ ← [Stop]
├─ [1378] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [901] LevelOne::isStudent(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] true
│ └─ ← [Return] true
├─ [0] console::log("Clara is student:", true) [staticcall]
│ └─ ← [Stop]
├─ [1378] ERC1967Proxy::fallback(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd]) [staticcall]
│ ├─ [901] LevelOne::isStudent(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd]) [delegatecall]
│ │ └─ ← [Return] true
│ └─ ← [Return] true
├─ [0] console::log("Dan is student:", true) [staticcall]
│ └─ ← [Stop]
├─ [1378] ERC1967Proxy::fallback(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [staticcall]
│ ├─ [901] LevelOne::isStudent(clara: [0xE55d6ba4bE0A6E0D87c4cA26B7C80779573Dc674]) [delegatecall]
│ │ └─ ← [Return] true
│ └─ ← [Return] true
├─ [0] VM::assertTrue(true, "Clara graduated with incomplete reviews") [staticcall]
│ └─ ← [Return]
├─ [1378] ERC1967Proxy::fallback(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd]) [staticcall]
│ ├─ [901] LevelOne::isStudent(dan: [0xb72116984E306d834a0ae638688Ef9AF1f7FE2cd]) [delegatecall]
│ │ └─ ← [Return] true
│ └─ ← [Return] true
├─ [0] VM::assertTrue(true, "Dan graduated with incomplete reviews") [staticcall]
│ └─ ← [Return]
└─ ← [Stop]