pragma solidity 0.8.26;
import {Test, console} from "forge-std/Test.sol";
import {LevelOne} from "../src/LevelOne.sol";
import {LevelTwo} from "../src/LevelTwo.sol";
import {ERC20Mock} from "@openzeppelin/contracts/mocks/token/ERC20Mock.sol";
contract TeacherPaymentExploitTest is Test {
LevelOne levelOne;
LevelTwo levelTwo;
ERC20Mock usdc;
address principal = makeAddr("principal");
address student = makeAddr("student");
address teacher1 = makeAddr("teacher1");
address teacher2 = makeAddr("teacher2");
address teacher3 = makeAddr("teacher3");
uint256 schoolFees = 100 ether;
uint256 initialBalance = 1000 ether;
function setUp() public {
usdc = new ERC20Mock();
levelOne = new LevelOne();
levelOne.initialize(principal, schoolFees, address(usdc));
levelTwo = new LevelTwo();
usdc.mint(student, initialBalance);
vm.prank(student);
usdc.approve(address(levelOne), schoolFees);
vm.startPrank(principal);
levelOne.addTeacher(teacher1);
levelOne.addTeacher(teacher2);
levelOne.addTeacher(teacher3);
vm.stopPrank();
console.log("Initial setup:");
console.log("Number of teachers:", levelOne.getTotalTeachers());
}
function test_TwoTeachers_Take70Percent() public {
vm.startPrank(principal);
levelOne.removeTeacher(teacher3);
vm.stopPrank();
vm.prank(student);
levelOne.enroll();
console.log("\nTest with 2 teachers:");
console.log("Student enrolled with fees: %s ETH", schoolFees / 1 ether);
console.log("Bursary amount: %s ETH", levelOne.bursary() / 1 ether);
vm.prank(principal);
levelOne.startSession(60);
uint256 currentTime = block.timestamp;
for (uint i = 0; i < 4; i++) {
currentTime += 1 weeks;
vm.warp(currentTime);
vm.prank(teacher1);
levelOne.giveReview(student, true);
}
vm.warp(block.timestamp + 1 days);
uint256 totalBursary = levelOne.bursary();
uint256 teacherWagePercent = levelOne.TEACHER_WAGE();
uint256 precision = levelOne.PRECISION();
uint256 totalTeachers = levelOne.getTotalTeachers();
uint256 teacherPool = (totalBursary * teacherWagePercent) / precision;
uint256 correctPayPerTeacher = teacherPool / totalTeachers;
uint256 incorrectPayPerTeacher = (totalBursary * teacherWagePercent) / precision;
console.log("Payment calculation with 2 teachers:");
console.log("Total bursary: %s ETH", totalBursary / 1 ether);
console.log("Number of teachers: %s", totalTeachers);
console.log("Teacher wage percent: %s%%", teacherWagePercent);
console.log("Correct: Each teacher should get %s ETH", correctPayPerTeacher / 1 ether);
console.log("Incorrect: Each teacher actually gets %s ETH", incorrectPayPerTeacher / 1 ether);
uint256 principalPay = (totalBursary * levelOne.PRINCIPAL_WAGE()) / precision;
uint256 totalTeacherPay = incorrectPayPerTeacher * totalTeachers;
uint256 totalPayout = totalTeacherPay + principalPay;
console.log("Principal payment: %s ETH", principalPay / 1 ether);
console.log("Total teacher payment: %s ETH (%s%% of bursary)", totalTeacherPay / 1 ether, (totalTeacherPay * 100) / totalBursary);
console.log("Total payout: %s ETH (%s%% of bursary)", totalPayout / 1 ether, (totalPayout * 100) / totalBursary);
assertTrue(
totalPayout <= totalBursary,
"With 2 teachers, payout should not exceed total bursary"
);
vm.prank(principal);
levelOne.graduateAndUpgrade(address(levelTwo), "");
}
function test_ThreeTeachers_ExceedFunds() public {
vm.prank(student);
levelOne.enroll();
console.log("\nTest with 3 teachers:");
console.log("Student enrolled with fees: %s ETH", schoolFees / 1 ether);
console.log("Bursary amount: %s ETH", levelOne.bursary() / 1 ether);
vm.prank(principal);
levelOne.startSession(60);
uint256 currentTime = block.timestamp;
for (uint i = 0; i < 4; i++) {
currentTime += 1 weeks;
vm.warp(currentTime);
vm.prank(teacher1);
levelOne.giveReview(student, true);
}
vm.warp(block.timestamp + 1 days);
uint256 totalBursary = levelOne.bursary();
uint256 teacherWagePercent = levelOne.TEACHER_WAGE();
uint256 precision = levelOne.PRECISION();
uint256 totalTeachers = levelOne.getTotalTeachers();
uint256 teacherPool = (totalBursary * teacherWagePercent) / precision;
uint256 correctPayPerTeacher = teacherPool / totalTeachers;
uint256 incorrectPayPerTeacher = (totalBursary * teacherWagePercent) / precision;
console.log("Payment calculation with 3 teachers:");
console.log("Total bursary: %s ETH", totalBursary / 1 ether);
console.log("Number of teachers: %s", totalTeachers);
console.log("Teacher wage percent: %s%%", teacherWagePercent);
console.log("Correct: Each teacher should get %s ETH", correctPayPerTeacher / 1 ether);
console.log("Incorrect: Each teacher actually gets %s ETH", incorrectPayPerTeacher / 1 ether);
uint256 principalPay = (totalBursary * levelOne.PRINCIPAL_WAGE()) / precision;
uint256 totalTeacherPay = incorrectPayPerTeacher * totalTeachers;
uint256 totalPayout = totalTeacherPay + principalPay;
console.log("Principal payment: %s ETH", principalPay / 1 ether);
console.log("Total teacher payment: %s ETH (%s%% of bursary)", totalTeacherPay / 1 ether, (totalTeacherPay * 100) / totalBursary);
console.log("Total payout: %s ETH (%s%% of bursary)", totalPayout / 1 ether, (totalPayout * 100) / totalBursary);
console.log("Exceeds bursary by: %s ETH", (totalPayout > totalBursary) ? (totalPayout - totalBursary) / 1 ether : 0);
assertTrue(
totalPayout > totalBursary,
"With 3 teachers, payout should exceed total bursary"
);
vm.prank(principal);
vm.expectRevert();
levelOne.graduateAndUpgrade(address(levelTwo), "");
}
}