Summary
The function graduateAndUpgrade upgrades the contract without checking whether individual students meet the cutOffScore, violating the stated invariant.
Vulnerability Details
The current implementation lacks any logic that filters or validates students based on their scores. If upgrading students is part of the upgrade process (e.g., migrating state, promoting users), this results in all students being upgraded, including those who have not met the required threshold.
This contradicts the expected invariant:
“Any student who doesn't meet the cutOffScore should not be upgraded.”
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}
Impact
Recommendations
function graduateAndUpgrade(address _levelTwo, bytes memory) public onlyPrincipal {
if (_levelTwo == address(0)) {
revert HH__ZeroAddress();
}
address[] memory eligibleStudents = new address[](listOfStudents.length);
uint256 eligibleCount = 0;
for (uint256 i = 0; i < listOfStudents.length; i++) {
address student = listOfStudents[i];
if (studentScore[student] >= cutOffScore) {
eligibleStudents[eligibleCount] = student;
eligibleCount++;
}
}
listOfStudents = new address[](eligibleCount);
for (uint256 i = 0; i < eligibleCount; i++) {
listOfStudents[i] = eligibleStudents[i];
}
uint256 totalTeachers = listOfTeachers.length;
uint256 payPerTeacher = (bursary * TEACHER_WAGE) / PRECISION;
uint256 principalPay = (bursary * PRINCIPAL_WAGE) / PRECISION;
_authorizeUpgrade(_levelTwo);
for (uint256 n = 0; n < totalTeachers; n++) {
usdc.safeTransfer(listOfTeachers[n], payPerTeacher);
}
usdc.safeTransfer(principal, principalPay);
}