Summary
The removeTeacher
function in LevelOne
uses a linear search on an array, which leads to increased gas costs as the number of teachers grows. This inefficiency can affect usability and scalability in production deployments.
Vulnerability Details
removeTeacher
uses a loop over listOfTeachers
, causing higher gas costs as the list grows.
Impact
Gas cost increases significantly with the number of teachers.
Tools Used
I found this issue through a manual audit.
Proof of Concept:
Here is a test that compares gas usage for removing a teacher from a list of 10 vs 100 teachers.
function test_remove_teacher_gas_cost() public {
for (uint256 i = 0; i < 10; i++) {
address teacher = makeAddr(string(abi.encodePacked("t10_", vm.toString(i))));
vm.prank(principal);
levelOneProxy.addTeacher(teacher);
}
address t10 = makeAddr("t10_0");
uint256 gasBefore10 = gasleft();
vm.prank(principal);
levelOneProxy.removeTeacher(t10);
uint256 gasUsed10 = gasBefore10 - gasleft();
for (uint256 i = 0; i < 100; i++) {
address teacher = makeAddr(string(abi.encodePacked("t100_", vm.toString(i))));
vm.prank(principal);
levelOneProxy.addTeacher(teacher);
}
address t100 = makeAddr("t100_0");
uint256 gasBefore100 = gasleft();
vm.prank(principal);
levelOneProxy.removeTeacher(t100);
uint256 gasUsed100 = gasBefore100 - gasleft();
console2.log("Gas used for removing teacher from 10-person list:", gasUsed10);
console2.log("Gas used for removing teacher from 100-person list:", gasUsed100);
}
Gas used for removing teacher from 10-person list: 6306
Gas used for removing teacher from 100-person list: 9942
Recommendations
Use a mapping(address => uint256) to store the index of each teacher. This allows O(1) lookup and removal by swapping with the last element and popping the array.
Add this state variable:
+ mapping(address => uint256) private teacherIndex;
Update LevelOne::addTeacher
to store the index:
+ teacherIndex[_teacher] = listOfTeachers.length - 1;
Modify LevelOne::removeTeacher
for efficient removal:
- uint256 teacherLength = listOfTeachers.length;
- for (uint256 n = 0; n < teacherLength; n++) {
- if (listOfTeachers[n] == _teacher) {
- listOfTeachers[n] = listOfTeachers[teacherLength - 1];
- listOfTeachers.pop();
- break;
- }
- }
-
- isTeacher[_teacher] = false;
+ uint256 index = teacherIndex[_teacher];
+ uint256 lastIndex = listOfTeachers.length - 1;
+
+ if (index != lastIndex) {
+ address lastTeacher = listOfTeachers[lastIndex];
+ listOfTeachers[index] = lastTeacher;
+ teacherIndex[lastTeacher] = index;
+ }
+
+ listOfTeachers.pop();
+ isTeacher[_teacher] = false;
+ delete teacherIndex[_teacher];