Hawk High

First Flight #39
Beginner FriendlySolidity
100 EXP
View results
Submission Details
Impact: high
Likelihood: high
Invalid

Unbounded payment loops block graduation

he graduateAndUpgrade() function pays each teacher with:

for (uint256 i; i < listOfTeachers.length; ++i) {
usdc.transfer(listOfTeachers[i], teacherShare);
}

With no cap on listOfTeachers.length, gas usage grows linearly with the number of teachers. Once there are hundreds of entries, the call can exceed the block gas limit, revert every time, and trap the funds.

Impact

  • Financial Denial of Service: the contract cannot graduate students or withdraw funds.

  • Upgrade Lock-out: the vulnerable logic cannot be replaced or patched.

  • Ease of Abuse: an attacker need only add fake teachers to force the DoS.

Proof of concept

function test_manyTeachersMayExceedGas() public {
// ---------- 1. Añadimos 1 024 docentes ----------
vm.startPrank(principal); // ← TODAS las addTeacher provienen del principal
for (uint256 i; i < 1024; ++i) {
levelOneProxy.addTeacher(
makeAddr(string(abi.encodePacked("t", vm.toString(i))))
);
}
vm.stopPrank();
// ---------- 2. Escenario normal de graduación ----------
_studentsEnrolled();
vm.prank(principal);
levelOneProxy.startSession(70);
vm.warp(block.timestamp + 4 weeks);
LevelTwo implV2 = new LevelTwo();
// ---------- 3. Medimos gas o esperamos revert ----------
vm.prank(principal);
vm.expectRevert(); // transacción debería agotar gas o revertir
levelOneProxy.graduateAndUpgrade(address(implV2), bytes(""));
}

**Output**

No files changed, compilation skipped
Ran 1 test for test/LeveOnelAndGraduateTest.t.sol:LevelOneAndGraduateTest
[PASS] test_manyTeachersMayExceedGas() (gas: 56977432)
Traces:
[57125632] LevelOneAndGraduateTest::test_manyTeachersMayExceedGas()
├─ [0] VM::startPrank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [0] VM::toString(0) [staticcall]
│ └─ ← [Return] "0"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t0: [0xe3B1eAb0030360C7fcA9b3Fab5B710C2Ed132ab0]
├─ [0] VM::label(t0: [0xe3B1eAb0030360C7fcA9b3Fab5B710C2Ed132ab0], "t0")
│ └─ ← [Return]
├─ [78232] ERC1967Proxy::fallback(t0: [0xe3B1eAb0030360C7fcA9b3Fab5B710C2Ed132ab0])
│ ├─ [73258] LevelOne::addTeacher(t0: [0xe3B1eAb0030360C7fcA9b3Fab5B710C2Ed132ab0]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t0: [0xe3B1eAb0030360C7fcA9b3Fab5B710C2Ed132ab0])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(1) [staticcall]
│ └─ ← [Return] "1"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t1: [0x5B12BE6D349275cca1c24672D7cF0FAE19e23718]
├─ [0] VM::label(t1: [0x5B12BE6D349275cca1c24672D7cF0FAE19e23718], "t1")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t1: [0x5B12BE6D349275cca1c24672D7cF0FAE19e23718])
│ ├─ [49358] LevelOne::addTeacher(t1: [0x5B12BE6D349275cca1c24672D7cF0FAE19e23718]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t1: [0x5B12BE6D349275cca1c24672D7cF0FAE19e23718])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(2) [staticcall]
│ └─ ← [Return] "2"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t2: [0x6A8548f54C723e46F27892911e8C1bADF15a21A5]
├─ [0] VM::label(t2: [0x6A8548f54C723e46F27892911e8C1bADF15a21A5], "t2")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t2: [0x6A8548f54C723e46F27892911e8C1bADF15a21A5])
│ ├─ [49358] LevelOne::addTeacher(t2: [0x6A8548f54C723e46F27892911e8C1bADF15a21A5]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t2: [0x6A8548f54C723e46F27892911e8C1bADF15a21A5])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(3) [staticcall]
│ └─ ← [Return] "3"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t3: [0x130e227F4ee7064a3C9AA0c06698e7F42e5D4BCd]
├─ [0] VM::label(t3: [0x130e227F4ee7064a3C9AA0c06698e7F42e5D4BCd], "t3")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t3: [0x130e227F4ee7064a3C9AA0c06698e7F42e5D4BCd])
│ ├─ [49358] LevelOne::addTeacher(t3: [0x130e227F4ee7064a3C9AA0c06698e7F42e5D4BCd]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t3: [0x130e227F4ee7064a3C9AA0c06698e7F42e5D4BCd])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(4) [staticcall]
│ └─ ← [Return] "4"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t4: [0x74d8470CD29D840ddA0a4F9D15869090Ad3565fe]
├─ [0] VM::label(t4: [0x74d8470CD29D840ddA0a4F9D15869090Ad3565fe], "t4")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t4: [0x74d8470CD29D840ddA0a4F9D15869090Ad3565fe])
│ ├─ [49358] LevelOne::addTeacher(t4: [0x74d8470CD29D840ddA0a4F9D15869090Ad3565fe]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t4: [0x74d8470CD29D840ddA0a4F9D15869090Ad3565fe])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(5) [staticcall]
│ └─ ← [Return] "5"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t5: [0x4A42da15BbD6c2336781f4e49795C19083Dc1BFf]
├─ [0] VM::label(t5: [0x4A42da15BbD6c2336781f4e49795C19083Dc1BFf], "t5")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t5: [0x4A42da15BbD6c2336781f4e49795C19083Dc1BFf])
│ ├─ [49358] LevelOne::addTeacher(t5: [0x4A42da15BbD6c2336781f4e49795C19083Dc1BFf]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t5: [0x4A42da15BbD6c2336781f4e49795C19083Dc1BFf])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(6) [staticcall]
│ └─ ← [Return] "6"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t6: [0xaDc9892085c8C124aBDbFeFf72edB33C9e7a3410]
├─ [0] VM::label(t6: [0xaDc9892085c8C124aBDbFeFf72edB33C9e7a3410], "t6")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t6: [0xaDc9892085c8C124aBDbFeFf72edB33C9e7a3410])
│ ├─ [49358] LevelOne::addTeacher(t6: [0xaDc9892085c8C124aBDbFeFf72edB33C9e7a3410]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t6: [0xaDc9892085c8C124aBDbFeFf72edB33C9e7a3410])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(7) [staticcall]
│ └─ ← [Return] "7"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t7: [0x5F0609dd31B4F9542406a2B3d3f01Ea013fC9070]
├─ [0] VM::label(t7: [0x5F0609dd31B4F9542406a2B3d3f01Ea013fC9070], "t7")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t7: [0x5F0609dd31B4F9542406a2B3d3f01Ea013fC9070])
│ ├─ [49358] LevelOne::addTeacher(t7: [0x5F0609dd31B4F9542406a2B3d3f01Ea013fC9070]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t7: [0x5F0609dd31B4F9542406a2B3d3f01Ea013fC9070])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(8) [staticcall]
│ └─ ← [Return] "8"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t8: [0xeD36A411A8263e69AF1A7fc5E045c08f53131bC3]
├─ [0] VM::label(t8: [0xeD36A411A8263e69AF1A7fc5E045c08f53131bC3], "t8")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t8: [0xeD36A411A8263e69AF1A7fc5E045c08f53131bC3])
│ ├─ [49358] LevelOne::addTeacher(t8: [0xeD36A411A8263e69AF1A7fc5E045c08f53131bC3]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t8: [0xeD36A411A8263e69AF1A7fc5E045c08f53131bC3])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(9) [staticcall]
│ └─ ← [Return] "9"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t9: [0x9B630750ae1F64B4757978063758a8684dcE647b]
├─ [0] VM::label(t9: [0x9B630750ae1F64B4757978063758a8684dcE647b], "t9")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t9: [0x9B630750ae1F64B4757978063758a8684dcE647b])
│ ├─ [49358] LevelOne::addTeacher(t9: [0x9B630750ae1F64B4757978063758a8684dcE647b]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t9: [0x9B630750ae1F64B4757978063758a8684dcE647b])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(10) [staticcall]
│ └─ ← [Return] "10"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t10: [0x55503E9Fe52FB2E180562d13E5450363A38e5352]
├─ [0] VM::label(t10: [0x55503E9Fe52FB2E180562d13E5450363A38e5352], "t10")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t10: [0x55503E9Fe52FB2E180562d13E5450363A38e5352])
│ ├─ [49358] LevelOne::addTeacher(t10: [0x55503E9Fe52FB2E180562d13E5450363A38e5352]) [delegatecall]
│ │ ├─ emit TeacherAdded(: t10: [0x55503E9Fe52FB2E180562d13E5450363A38e5352])
│ │ └─ ← [Stop]
│ └─ ← [Return]
├─ [0] VM::toString(11) [staticcall]
│ └─ ← [Return] "11"
├─ [0] VM::addr(<pk>) [staticcall]
│ └─ ← [Return] t11: [0xcd88ACC2FCc62152E9E4d0028c79e50A0D5A2Cfc]
├─ [0] VM::label(t11: [0xcd88ACC2FCc62152E9E4d0028c79e50A0D5A2Cfc], "t11")
│ └─ ← [Return]
├─ [49832] ERC1967Proxy::fallback(t11: [0xcd88ACC2FCc62152E9E4d0028c79e50A0D5A2Cfc])
│ ├─ [49358] LevelOne::addTeacher(t11: [0xcd88ACC2FCc62152E9E4d0028c79
// long out
├─ [0] VM::startPrank(six_student: [0x983Cec4DF373E6f3809b4483fEBf6C9469B0769b])
│ └─ ← [Return]
├─ [25298] MockUSDC::approve(ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ ├─ emit Approval(owner: six_student: [0x983Cec4DF373E6f3809b4483fEBf6C9469B0769b], spender: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ └─ ← [Return] true
├─ [83022] ERC1967Proxy::fallback()
│ ├─ [82551] LevelOne::enroll() [delegatecall]
│ │ ├─ [9719] MockUSDC::transferFrom(six_student: [0x983Cec4DF373E6f3809b4483fEBf6C9469B0769b], ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], 5000000000000000000000 [5e21])
│ │ │ ├─ emit Transfer(from: six_student: [0x983Cec4DF373E6f3809b4483fEBf6C9469B0769b], to: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], value: 5000000000000000000000 [5e21])
│ │ │ └─ ← [Return] true
│ │ ├─ emit Enrolled(: six_student: [0x983Cec4DF373E6f3809b4483fEBf6C9469B0769b])
│ │ └─ ← [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]
├─ [445078] → new LevelTwo@0x2e234DAe75C793f67A35089C9d99245E1C58470b
│ └─ ← [Return] 2223 bytes of code
├─ [0] VM::prank(principal: [0x6b9470599cb23a06988C6332ABE964d6608A50ca])
│ └─ ← [Return]
├─ [0] VM::expectRevert(custom error 0xf4844814)
│ └─ ← [Return]
├─ [60008] ERC1967Proxy::fallback(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0x)
│ ├─ [59512] LevelOne::graduateAndUpgrade(LevelTwo: [0x2e234DAe75C793f67A35089C9d99245E1C58470b], 0x) [delegatecall]
│ │ ├─ [25750] MockUSDC::transfer(t0: [0xe3B1eAb0030360C7fcA9b3Fab5B710C2Ed132ab0], 10500000000000000000000 [1.05e22])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: t0: [0xe3B1eAb0030360C7fcA9b3Fab5B710C2Ed132ab0], value: 10500000000000000000000 [1.05e22])
│ │ │ └─ ← [Return] true
│ │ ├─ [25750] MockUSDC::transfer(t1: [0x5B12BE6D349275cca1c24672D7cF0FAE19e23718], 10500000000000000000000 [1.05e22])
│ │ │ ├─ emit Transfer(from: ERC1967Proxy: [0x90193C961A926261B756D1E5bb255e67ff9498A1], to: t1: [0x5B12BE6D349275cca1c24672D7cF0FAE19e23718], value: 10500000000000000000000 [1.05e22])
│ │ │ └─ ← [Return] true
│ │ ├─ [1541] MockUSDC::transfer(t2: [0x6A8548f54C723e46F27892911e8C1bADF15a21A5], 10500000000000000000000 [1.05e22])
│ │ │ └─ ← [Revert] ERC20InsufficientBalance(0x90193C961A926261B756D1E5bb255e67ff9498A1, 9000000000000000000000 [9e21], 10500000000000000000000 [1.05e22])
│ │ └─ ← [Revert] ERC20InsufficientBalance(0x90193C961A926261B756D1E5bb255e67ff9498A1, 9000000000000000000000 [9e21], 10500000000000000000000 [1.05e22])
│ └─ ← [Revert] ERC20InsufficientBalance(0x90193C961A926261B756D1E5bb255e67ff9498A1, 9000000000000000000000 [9e21], 10500000000000000000000 [1.05e22])
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 146.66ms (143.50ms CPU time)
Ran 1 test suite in 857.17ms (146.66ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)

Recommended Mitigation

-Switch to a pull model: record each teacher’s salary and allow them to withdraw it individually (withdrawSalary).

-Implement batch payments (e.g. 50 teachers per transaction) with index tracking.

-Cap the size of listOfTeachers or enforce an internal gas check (require(gasleft() > X)).

-Perform the upgrade first, then execute the payments—this lets you introduce the optimized logic without risking the initial DoS.

Updates

Lead Judging Commences

yeahchibyke Lead Judge about 2 months ago
Submission Judgement Published
Invalidated
Reason: Incorrect statement

Support

FAQs

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