Hawk High

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

[M-5] Unbounded‑Loop Gas‑Exhaustion Leads To DoS in Administrative Functions

Summary

The contract implements linear searches over unbounded dynamic arrays in its removeTeacher, expel, and graduateAndUpgrade functions, causing gas consumption to grow proportionally with the number of entries and ultimately exceeding Ethereum’s per‑block gas limit, resulting in irreversible reverts and denial of service. This behavior is catalogued as “Denial of Service with Block Gas Limit” and falls under DoS through unbounded operations. Because these loops can disrupt core administrative and upgrade flows without direct theft of funds, this would be categorized as a Medium finding.

Vulnerability Details

Every invocation of removeTeacher iterates through listOfTeachers to locate and remove a teacher address, executing an O(N) search and swap on storage arrays. The expel function mirrors this pattern on the student list and the graduateAndUpgrade function similarly loops over teachers to distribute USDC payments in a push fashion, compounding gas costs on large cohorts. As the number of teachers or expelled students grows, these unbounded loops incrementally consume more gas until the operation surpasses the block gas limit and reverts.

Impact

Once array sizes cross the threshold where loop gas exceeds the block limit, the principal loses the ability to remove teachers, expel students, or perform payments towards himself and the teachers, effectively stranding USDC in the contract and halting protocol evolution.

Tools Used

  • Foundry

  • Manual Review

  • Aderyn

Proof Of Code

Place the following test into LevelOneAndGraduateTest.t.sol:

function test_removeTeacherGasCostScalesLinearly() public {
// Add a single teacher so the array has length 1
vm.txGasPrice(1);
vm.prank(principal);
levelOneProxy.addTeacher(alice);
// 1) Attempt to remove a teacher in a 1-element array
uint256 gasStart = gasleft();
vm.prank(principal);
levelOneProxy.removeTeacher(alice);
uint256 gasAfterSingle = gasleft();
uint256 gasUsedSingle = (gasStart - gasAfterSingle) * tx.gasprice;
console.log("Gas after single removal:", gasUsedSingle);
// 2) Bulk-add 50 more teachers to balloon the array
uint256 gasStartFifty = gasleft();
for (uint i = 0; i < 50; i++) {
address t = makeAddr(string.concat("teacher", vm.toString(i)));
vm.prank(principal);
levelOneProxy.addTeacher(t);
}
// 3) Attempt the same removal in a 51-element array
vm.prank(principal);
vm.expectRevert(LevelOne.HH__TeacherDoesNotExist.selector);
levelOneProxy.removeTeacher(address(1));
uint256 gasAfterFifty = gasleft();
uint256 gasUsedFifty = (gasStartFifty - gasAfterFifty) * tx.gasprice;
console.log("Gas when removing a teacher in a bigger array:", gasUsedFifty);
assert(gasUsedFifty > gasUsedSingle);
}

Running this test will give us the following output:

We can see that gas consumption is significantly higher when removing a teacher because the function must loop through the entire array of fifty‑plus addresses. The same issue arises in the student context: expelling a non‑existent or end‑of‑array entry from a fifty‑element student list would likewise incur a very costly operation.

Recommendations

Consider using a mapping for both teachers and students instead of an ever‑growing address[] plus boolean flags, so that adding or removing an entry is just a constant‑time write or delete and lookups never scan the array.

Altenatively, you can use OpenZeppelin’s EnumerableSet —its add/remove/contains operations remain O(1) and you only pay the cost of turning its small snapshot into an array when necessary.

Updates

Lead Judging Commences

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

possible DoS when removing teachers

Unbounded loops in teacher lists could result in high gas usage when trying to remove a teacher when teachers are plenty. This could result in a possible DoS

Support

FAQs

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