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 PaymentValidationTest is Test {
DeployLevelOne deployBot;
LevelOne levelOneProxy;
LevelTwo levelTwoImplementation;
MockUSDC usdc;
address proxyAddress;
address principal;
uint256 schoolFees;
address[] teachers;
address student;
function setUp() public {
deployBot = new DeployLevelOne();
proxyAddress = deployBot.deployLevelOne();
levelOneProxy = LevelOne(proxyAddress);
usdc = deployBot.getUSDC();
principal = deployBot.getPrincipal();
schoolFees = deployBot.getSchoolFees();
teachers = new address[](10);
for (uint256 i = 0; i < 10; i++) {
teachers[i] = makeAddr(
string(abi.encodePacked("teacher_", vm.toString(i)))
);
}
student = makeAddr("student");
}
function testPaymentValidationVulnerability() public {
vm.startPrank(principal);
levelOneProxy.addTeacher(teachers[0]);
levelOneProxy.addTeacher(teachers[1]);
vm.stopPrank();
vm.startPrank(student);
deal(address(usdc), student, schoolFees);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(principal);
levelOneProxy.startSession(70);
vm.stopPrank();
uint256 weekDuration = 1 weeks;
uint256 currentTime = block.timestamp;
for (uint256 week = 0; week < 4; week++) {
currentTime += weekDuration;
vm.warp(currentTime);
vm.startPrank(teachers[0]);
levelOneProxy.giveReview(student, true);
vm.stopPrank();
}
uint256 bursaryAmount = schoolFees;
uint256 teacherWage = (bursaryAmount * 35) / 100;
uint256 principalWage = (bursaryAmount * 5) / 100;
uint256 totalPayout = (teacherWage * 2) + principalWage;
levelTwoImplementation = new LevelTwo();
vm.startPrank(principal);
uint256 initialUSDCBalance = usdc.balanceOf(address(levelOneProxy));
levelOneProxy.graduateAndUpgrade(address(levelTwoImplementation), "");
uint256 finalUSDCBalance = usdc.balanceOf(address(levelOneProxy));
uint256 actualDistributed = initialUSDCBalance - finalUSDCBalance;
assertGt(
totalPayout,
(bursaryAmount * 40) / 100,
"Payment should exceed 40% due to rounding"
);
assertTrue(
actualDistributed != (bursaryAmount * 40) / 100,
"Distribution should not equal exactly 40%"
);
vm.stopPrank();
}
}
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
uint256 totalDistribution = (payPerTeacher * totalTeachers) + principalPay;
require(totalDistribution <= bursary, "Distribution exceeds bursary");
require(totalDistribution == (bursary * 40) / PRECISION,
"Distribution must equal exactly 40% of bursary");
require(usdc.balanceOf(address(this)) >= totalDistribution,
"Insufficient USDC balance");
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}