# Attacker can call `KeeperFactory#settle` with empty arrays as input parameters to steal all keeper fees

KeeperFactory#settle accepts empty arrays, skipping the settle loop but still paying the keeper fee, letting an attacker drain all keeper fees.

- Payout: Contest reward
- Severity: high
- Program: Perennial
- Credited: Emmanuel
- Disclosed: 2023-10-26
- Write-up quality: 6/10
- Original: https://github.com/sherlock-audit/2023-10-perennial-judging/issues/50
- Library page: https://bugbountylibrary.com/r/sherlock%3A2023-10-perennial%3AH-4
- Tags: domain:web3 impact:funds-loss kind:contest platform:sherlock sev:high tech:evm tech:solidity technique:source-review vuln:accounting-error vuln:business-logic

**Target:** KeeperFactory#settle keeper reward contract
**Root cause:** settle() validates array length equality but not that ids.length > 0, so the for-loop is skipped while the keep modifier still pays the caller.
**Steps:**
1. Call `KeeperFactory.settle([],[],[],[])` with all empty arrays
2. Length-equality and calldata-stuffing checks pass since all lengths are 0
3. for-loop body never executes, but `keep` modifier pays caller the keeper fee
4. Wrap in a loop in an attack contract to drain the keeper token balance
**How found:** Code review of settle()'s input validation: noticed length checks compare arrays to each other but never enforce non-zero length, so the loop is bypassable while the keep modifier still rewards the caller.
**Exploitation:** Attacker contract repeatedly calls settle with empty arrays; each call succeeds and pays a keeper fee to the attacker until the keeper token balance is exhausted, then transfers proceeds to attacker.
**Escalation:** Looping the call drains the entire keeper fee pool, removing incentives to commit oracle versions and potentially bricking markets.
**Insight:** When a function pays a reward via a modifier before/around a loop, check that the loop can't be made a no-op (empty arrays, zero iterations) while still triggering the payout.
