Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Severity: high
Valid

[m] incorrect calculation of teachers’ payment

Vulnerability Details

According to the invariant that teachers share of 35% of bursary, the implementation shows that every teacher can get 35% of bursary, not share of 35% of bursary.

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// ...
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
for (uint256 n = 0; n < totalTeachers; n++) {
// every teacher gets 35% of bursary
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
// ...
}

Impact

Due to an error in calculating the actor teacher's fee, when there are two or more teachers in the system, paying their salaries will deplete the bursary in system.

// SPDX-License-Identifier: SEE LICENSE IN LICENSE
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 LevelOneAndGraduateTest is Test {
DeployLevelOne deployBot;
GraduateToLevelTwo graduateBot;
LevelOne levelOneProxy;
LevelTwo levelTwoImplementation;
address proxyAddress;
address levelOneImplementationAddress;
address levelTwoImplementationAddress;
MockUSDC usdc;
address principal;
uint256 schoolFees;
// teachers
address alice;
address bob;
// students
address clara;
address dan;
address eli;
address fin;
address grey;
address harriet;
function setUp() public {
deployBot = new DeployLevelOne();
proxyAddress = deployBot.deployLevelOne();
levelOneProxy = LevelOne(proxyAddress);
// graduateBot = new GraduateToLevelTwo();
usdc = deployBot.getUSDC();
principal = deployBot.principal();
schoolFees = deployBot.getSchoolFees();
levelOneImplementationAddress = deployBot.getImplementationAddress();
alice = makeAddr("first_teacher");
bob = makeAddr("second_teacher");
clara = makeAddr("first_student");
dan = makeAddr("second_student");
eli = makeAddr("third_student");
fin = makeAddr("fourth_student");
grey = makeAddr("fifth_student");
harriet = makeAddr("six_student");
usdc.mint(clara, schoolFees);
usdc.mint(dan, schoolFees);
usdc.mint(eli, schoolFees);
usdc.mint(fin, schoolFees);
usdc.mint(grey, schoolFees);
usdc.mint(harriet, schoolFees);
}
function test_poc_teacher_payment() external {
// setup
_teachersAdded();
_studentsEnrolled();
// totalBursary = 30000e18
uint256 totalBursary = levelOneProxy.bursary();
// call graduateAndUpgrade()
levelTwoImplementation = new LevelTwo();
levelTwoImplementationAddress = address(levelTwoImplementation);
bytes memory data = abi.encodeCall(LevelTwo.graduate, ());
vm.prank(principal);
levelOneProxy.graduateAndUpgrade(levelTwoImplementationAddress, data);
// assertions
assertEq(usdc.balanceOf(principal), (totalBursary * 5) / 100);
// system
assertEq(usdc.balanceOf(address(levelOneProxy)), (totalBursary * 60) / 100);
// there are two teacher in the system
// assertEq(usdc.balanceOf(alice), (totalBursary * 35) / (2 * 100));
// assertEq(usdc.balanceOf(bob), (totalBursary * 35) / (2 * 100));
}
function _teachersAdded() internal {
vm.startPrank(principal);
levelOneProxy.addTeacher(alice);
levelOneProxy.addTeacher(bob);
vm.stopPrank();
}
function _studentsEnrolled() internal {
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.startPrank(eli);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(fin);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(grey);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
vm.startPrank(harriet);
usdc.approve(address(levelOneProxy), schoolFees);
levelOneProxy.enroll();
vm.stopPrank();
}
}

The log shows that the system's bursary are below 60% after paying wages:

├─ [0] VM::stopPrank()
│ └─ ← [Return]
├─ [988] ERC1967Proxy::fallback() [staticcall]
│ ├─ [514] LevelOne::bursary() [delegatecall]
│ │ └─ ← [Return] 30000000000000000000000 [3e22]
│ └─ ← [Return] 30000000000000000000000 [3e22]
├─ [445078] → new LevelTwo@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 2223 bytes of code
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [84129] ERC1967Proxy::fallback(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0xd3618cca)
│ ├─ [83640] LevelOne::graduateAndUpgrade(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0xd3618cca) [delegatecall]
│ │ ├─ [25750] MockUSDC::transfer(first_teacher: [0xeeEeC5A3afd714e3C63A0b1ef6d80722Bcc514b3], 10500000000000000000000 [1.05e22])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: first_teacher: [0xeeEeC5A3afd714e3C63A0b1ef6d80722Bcc514b3], value: 10500000000000000000000 [1.05e22])
│ │ │ └─ ← [Return] true
│ │ ├─ [25750] MockUSDC::transfer(second_teacher: [0xb4c265c1f1d07474E3715F65724E8fa9d662BF0e], 10500000000000000000000 [1.05e22])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: second_teacher: [0xb4c265c1f1d07474E3715F65724E8fa9d662BF0e], value: 10500000000000000000000 [1.05e22])
│ │ │ └─ ← [Return] true
│ │ ├─ [25750] MockUSDC::transfer(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca], 1500000000000000000000 [1.5e21])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca], value: 1500000000000000000000 [1.5e21])
│ │ │ └─ ← [Return] true
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [851] MockUSDC::balanceOf(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca]) [staticcall]
│ └─ ← [Return] 1500000000000000000000 [1.5e21]
├─ [0] VM::assertEq(1500000000000000000000 [1.5e21], 1500000000000000000000 [1.5e21]) [staticcall]
│ └─ ← [Return]
├─ [851] MockUSDC::balanceOf(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1]) [staticcall]
│ └─ ← [Return] 7500000000000000000000 [7.5e21]
├─ [0] VM::assertEq(7500000000000000000000 [7.5e21], 18000000000000000000000 [1.8e22]) [staticcall]
│ └─ ← [Revert] assertion failed: 7500000000000000000000 != 18000000000000000000000
└─ ← [Revert] assertion failed: 7500000000000000000000 != 18000000000000000000000

Tools Used

Manual.

Recommendations

function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
// ...
uint256 totalTeachers = listOfTeachers.length;
> uint256 payPerTeacher = (bursary * TEACHER_WAGE) / (PRECISION * totalTeachers);
for (uint256 n = 0; n < totalTeachers; n++) {
// every teacher gets 35% of bursary
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
// ...
}
Updates

Lead Judging Commences

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

incorrect teacher pay calculation

`payPerTeacher` in `graduateAndUpgrade()` is incorrectly calculated.

yeahchibyke Lead Judge 6 months ago
Submission Judgement Published
Validated
Assigned finding tags:

incorrect teacher pay calculation

`payPerTeacher` in `graduateAndUpgrade()` is incorrectly calculated.

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.