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 {MockUSDC} from "./mocks/MockUSDC.sol";
contract PaymentDistributionOverflowTest is Test {
DeployLevelOne deployBot;
LevelOne levelOneProxy;
LevelTwo levelTwoImplementation;
MockUSDC usdc;
address principal;
uint256 schoolFees;
address alice;
address bob;
address charlie;
address student1;
function setUp() public {
deployBot = new DeployLevelOne();
levelOneProxy = LevelOne(deployBot.deployLevelOne());
levelTwoImplementation = new LevelTwo();
usdc = deployBot.getUSDC();
principal = deployBot.getPrincipal();
schoolFees = deployBot.getSchoolFees();
alice = makeAddr("alice");
bob = makeAddr("bob");
charlie = makeAddr("charlie");
student1 = makeAddr("student1");
usdc.mint(student1, schoolFees);
}
function testPaymentDistributionOverflow() public {
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
levelOneProxy.addTeacher(bob);
levelOneProxy.addTeacher(charlie);
vm.stopPrank();
vm.startPrank(student1);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.prank(principal);
levelOneProxy.startSession(70);
vm.warp(block.timestamp + 4 weeks);
console2.log("School Fees:", schoolFees);
console2.log(
"Contract Balance:",
usdc.balanceOf(address(levelOneProxy))
);
console2.log("Teacher Wage Percentage:", levelOneProxy.TEACHER_WAGE());
uint256 expectedTeacherAllocation = (schoolFees *
levelOneProxy.TEACHER_WAGE()) / levelOneProxy.PRECISION();
console2.log(
"Expected Total Teacher Allocation:",
expectedTeacherAllocation
);
console2.log(
"Expected Per Teacher (should be):",
expectedTeacherAllocation / 3
);
vm.expectRevert();
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(address(levelTwoImplementation), "");
}
}
Ran 1 test for test/PaymentDistributionOverflowTest.t.sol:PaymentDistributionOverflowTest
[PASS] testPaymentDistributionOverflow() (gas: 491965)
Logs:
School Fees: 5000000000000000000000
Contract Balance: 5000000000000000000000
Teacher Wage Percentage: 35
Expected Total Teacher Allocation: 1750000000000000000000
Expected Per Teacher (should be): 583333333333333333333
Traces:
[516665] PaymentDistributionOverflowTest::testPaymentDistributionOverflow()
├─ [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]
├─ [49832] ERC1967Proxy::fallback(charlie: [0xea475d60c118d7058beF4bDd9c32bA51139a74e0])
│ ├─ [49358] LevelOne::addTeacher(charlie: [0xea475d60c118d7058beF4bDd9c32bA51139a74e0]) [delegatecall]
│ │ ├─ emit TeacherAdded(: charlie: [0xea475d60c118d7058beF4bDd9c32bA51139a74e0])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [0] VM::startPrank(student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8])
│ └─ ← [Return]
├─ [25298] MockUSDC::approve(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ ├─ emit Approval(owner: student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], spender: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ └─ ← [Return] true
├─ [152722] ERC1967Proxy::fallback()
│ ├─ [152251] LevelOne::enroll() [delegatecall]
│ │ ├─ [31619] MockUSDC::transferFrom(student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ │ │ ├─ emit Transfer(from: student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8], to: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ emit Enrolled(: student1: [0x3A17b82638fdF8A1cAf9c60d2B13CecB85ABb5A8])
│ │ └─ ← [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::warp(2419201 [2.419e6])
│ └─ ← [Return]
├─ [0] console::log("School Fees:", 5000000000000000000000 [5e21]) [staticcall]
│ └─ ← [Stop]
├─ [851] MockUSDC::balanceOf(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1]) [staticcall]
│ └─ ← [Return] 5000000000000000000000 [5e21]
├─ [0] console::log("Contract Balance:", 5000000000000000000000 [5e21]) [staticcall]
│ └─ ← [Stop]
├─ [910] ERC1967Proxy::fallback() [staticcall]
│ ├─ [436] LevelOne::TEACHER_WAGE() [delegatecall]
│ │ └─ ← [Return] 35
│ └─ ← [Return] 35
├─ [0] console::log("Teacher Wage Percentage:", 35) [staticcall]
│ └─ ← [Stop]
├─ [888] ERC1967Proxy::fallback() [staticcall]
│ ├─ [414] LevelOne::PRECISION() [delegatecall]
│ │ └─ ← [Return] 100
│ └─ ← [Return] 100
├─ [910] ERC1967Proxy::fallback() [staticcall]
│ ├─ [436] LevelOne::TEACHER_WAGE() [delegatecall]
│ │ └─ ← [Return] 35
│ └─ ← [Return] 35
├─ [0] console::log("Expected Total Teacher Allocation:", 1750000000000000000000 [1.75e21]) [staticcall]
│ └─ ← [Stop]
├─ [0] console::log("Expected Per Teacher (should be):", 583333333333333333333 [5.833e20]) [staticcall]
│ └─ ← [Stop]
├─ [0] VM::expectRevert(custom error 0xf4844814)
│ └─ ← [Return]
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [60008] ERC1967Proxy::fallback(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0x)
│ ├─ [59512] LevelOne::graduateAndUpgrade(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0x) [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(bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e], 1750000000000000000000 [1.75e21])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: bob: [0x1D96F2f6BeF1202E4Ce1Ff6Dad0c2CB002861d3e], value: 1750000000000000000000 [1.75e21])
│ │ │ └─ ← [Return] true
│ │ ├─ [1541] MockUSDC::transfer(charlie: [0xea475d60c118d7058beF4bDd9c32bA51139a74e0], 1750000000000000000000 [1.75e21])
│ │ │ └─ ← [Revert] ERC20InsufficientBalance(0x90193C961A926261B756D1E5bb255e67ff9498A1, 1500000000000000000000 [1.5e21], 1750000000000000000000 [1.75e21])
│ │ └─ ← [Revert] ERC20InsufficientBalance(0x90193C961A926261B756D1E5bb255e67ff9498A1, 1500000000000000000000 [1.5e21], 1750000000000000000000 [1.75e21])
│ └─ ← [Revert] ERC20InsufficientBalance(0x90193C961A926261B756D1E5bb255e67ff9498A1, 1500000000000000000000 [1.5e21], 1750000000000000000000 [1.75e21])
└─ ← [Stop]
The payment distribution overflow vulnerability in LevelOne.sol can definitely be classified as a Denial of Service (DoS) vulnerability. Here's why:
Therefore, this vulnerability can be classified as a DoS because it prevents the execution of critical contract functions and blocks the normal operation of the school system when multiple teachers are present.