It's possible to drain all the funds from ExternalBribe
The function `earned()` is used to calculate the amount rewards owed to a tokenId, to do so it performs a series operations over a loop and then it always executes:\\n```\\n    Checkpoint memory cp = checkpoints[tokenId][_endIndex];\\n      uint _lastEpochStart = _bribeStart(cp.timestamp);\\n        uint _lastEpochEnd = _lastEpochStart + DURATION;\\n    if (block.timestamp > _lastEpochEnd) {\\n            reward += (cp.balanceOf * \\n         tokenRewardsPerEpoch[token][_lastEpochStart]) /\\n           supplyCheckpoints[getPriorSupplyIndex(_lastEpochEnd)].supply;\\n```\\n\\nwhich adds to reward the amount of rewards earned by the tokenId during the last epoch in which it was used to vote, but only if that happened at least a week prior (block.timestamp > _lastEpochEnd). Because of this, it's possible to call `earned()` multiple times in a row with a tokenId that voted more than a week before to drain the contract funds.
The function `earned()` is taken from the Velodrome protocol and is known to have issues. Because it uses the convoluted logic of looping over votes to calculate the rewards per epoch instead of looping over epochs, we recommend using the Velodrome fixed implementation, which we reviewed:\\n```\\n      function earned(address token, uint tokenId) public view returns (uint) {\\n      if (numCheckpoints[tokenId] == 0) {\\n          return 0;\\n      }\\n        uint reward = 0;\\n          uint _ts = 0;\\n           uint _bal = 0;\\n             uint _supply = 1;\\n               uint _index = 0;\\n          uint _currTs = _bribeStart(lastEarn[token][tokenId]); // take epoch last claimed in as starting point\\n      _index = getPriorBalanceIndex(tokenId, _currTs);\\n          _ts = checkpoints[tokenId][_index].timestamp;\\n              _bal = checkpoints[tokenId][_index].balanceOf;\\n      // accounts for case where lastEarn is before first checkpoint\\n        _currTs = Math.max(_currTs, _bribeStart(_ts));\\n      // get epochs between current epoch and first checkpoint in same epoch as last claim\\n      uint numEpochs = (_bribeStart(block.timestamp) - _currTs) / DURATION;\\n      if (numEpochs > 0) {\\n      for (uint256 i = 0; i < numEpochs; i++) {\\n      // get index of last checkpoint in this epoch\\n      _index = getPriorBalanceIndex(tokenId, _currTs + DURATION);\\n      // get checkpoint in this epoch\\n      _ts = checkpoints[tokenId][_index].timestamp;\\n          _bal = checkpoints[tokenId][_index].balanceOf;\\n      // get supply of last checkpoint in this epoch\\n            _supply = supplyCheckpoints[getPriorSupplyIndex(_currTs + DURATION)].supply;\\n      reward += _bal * tokenRewardsPerEpoch[token][_currTs] / _supply;\\n            _currTs += DURATION;\\n             }\\n           }\\n      return reward;\\n        }\\n```\\n
```\\n    Checkpoint memory cp = checkpoints[tokenId][_endIndex];\\n      uint _lastEpochStart = _bribeStart(cp.timestamp);\\n        uint _lastEpochEnd = _lastEpochStart + DURATION;\\n    if (block.timestamp > _lastEpochEnd) {\\n            reward += (cp.balanceOf * \\n         tokenRewardsPerEpoch[token][_lastEpochStart]) /\\n           supplyCheckpoints[getPriorSupplyIndex(_lastEpochEnd)].supply;\\n```\\n