# Vault Harvester
Source: https://docs.chain.link/chainlink-automation/tutorials/vault-harvester

> For the complete documentation index, see [llms.txt](/llms.txt).

Harvesting and compounding is key to maximize yield in DeFi yield aggregators. Automate harvesting and compounding using Chainlink Automation's decentralized automation network.

View the template [here](https://github.com/smartcontractkit/chainlink-automation-templates/tree/main/vault-harvester#chainlink-keepers-template-vault-harvester).

Below is the main contract `KeeperCompatibleHarvester.sol`:

```solidity
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "../libraries/UpkeepLibrary.sol";
import "../interfaces/IKeeperRegistry.sol";
import "../interfaces/IHarvester.sol";

abstract contract KeeperCompatibleHarvester is IHarvester, Ownable {
  using SafeERC20 for IERC20;

  // Contracts.
  IKeeperRegistry public keeperRegistry;

  // Configuration state variables.
  uint256 public performUpkeepGasLimit;
  uint256 public performUpkeepGasLimitBuffer;
  uint256 public vaultHarvestFunctionGasOverhead; // Estimated average gas cost of calling harvest()
  uint256 public keeperRegistryGasOverhead; // Gas cost of upstream contract that calls performUpkeep(). This is a private variable on KeeperRegistry.
  uint256 public chainlinkUpkeepTxPremiumFactor; // Tx premium factor/multiplier scaled by 1 gwei (10**9).
  address public callFeeRecipient;

  // State variables that will change across upkeeps.
  uint256 public startIndex;

  constructor(
    address _keeperRegistry,
    uint256 _performUpkeepGasLimit,
    uint256 _performUpkeepGasLimitBuffer,
    uint256 _vaultHarvestFunctionGasOverhead,
    uint256 _keeperRegistryGasOverhead
  ) {
    // Set contract references.
    keeperRegistry = IKeeperRegistry(_keeperRegistry);

    // Initialize state variables from initialize() arguments.
    performUpkeepGasLimit = _performUpkeepGasLimit;
    performUpkeepGasLimitBuffer = _performUpkeepGasLimitBuffer;
    vaultHarvestFunctionGasOverhead = _vaultHarvestFunctionGasOverhead;
    keeperRegistryGasOverhead = _keeperRegistryGasOverhead;

    // Initialize state variables derived from initialize() arguments.
    (, OnchainConfig memory config, , , ) = keeperRegistry.getState();
    chainlinkUpkeepTxPremiumFactor = uint256(config.paymentPremiumPPB);
  }

  /*             */
  /* checkUpkeep */
  /*             */

  function checkUpkeep(
    bytes calldata _checkData // unused
  )
    external
    view
    override
    returns (
      bool upkeepNeeded,
      bytes memory performData // array of vaults +
    )
  {
    _checkData; // dummy reference to get rid of unused parameter warning

    // get vaults to iterate over
    address[] memory vaults = _getVaultAddresses();

    // count vaults to harvest that will fit within gas limit
    (HarvestInfo[] memory harvestInfo, uint256 numberOfVaultsToHarvest, uint256 newStartIndex) = _countVaultsToHarvest(
      vaults
    );
    if (numberOfVaultsToHarvest == 0) return (false, bytes("No vaults to harvest"));

    (address[] memory vaultsToHarvest, uint256 heuristicEstimatedTxCost, uint256 callRewards) = _buildVaultsToHarvest(
      vaults,
      harvestInfo,
      numberOfVaultsToHarvest
    );

    uint256 nonHeuristicEstimatedTxCost = _calculateExpectedTotalUpkeepTxCost(numberOfVaultsToHarvest);

    performData = abi.encode(
      vaultsToHarvest,
      newStartIndex,
      heuristicEstimatedTxCost,
      nonHeuristicEstimatedTxCost,
      callRewards
    );

    return (true, performData);
  }

  function _buildVaultsToHarvest(
    address[] memory _vaults,
    HarvestInfo[] memory _willHarvestVaults,
    uint256 _numberOfVaultsToHarvest
  )
    internal
    view
    returns (address[] memory vaultsToHarvest, uint256 heuristicEstimatedTxCost, uint256 totalCallRewards)
  {
    uint256 vaultPositionInArray;
    vaultsToHarvest = new address[](_numberOfVaultsToHarvest);

    // create array of vaults to harvest. Could reduce code duplication from _countVaultsToHarvest via a another function parameter called _loopPostProcess
    for (uint256 offset; offset < _vaults.length; ++offset) {
      uint256 vaultIndexToCheck = UpkeepLibrary._getCircularIndex(startIndex, offset, _vaults.length);
      address vaultAddress = _vaults[vaultIndexToCheck];

      HarvestInfo memory harvestInfo = _willHarvestVaults[offset];

      if (harvestInfo.willHarvest) {
        vaultsToHarvest[vaultPositionInArray] = vaultAddress;
        heuristicEstimatedTxCost += harvestInfo.estimatedTxCost;
        totalCallRewards += harvestInfo.callRewardsAmount;
        vaultPositionInArray += 1;
      }

      // no need to keep going if we're past last index
      if (vaultPositionInArray == _numberOfVaultsToHarvest) break;
    }

    return (vaultsToHarvest, heuristicEstimatedTxCost, totalCallRewards);
  }

  function _countVaultsToHarvest(
    address[] memory _vaults
  ) internal view returns (HarvestInfo[] memory harvestInfo, uint256 numberOfVaultsToHarvest, uint256 newStartIndex) {
    uint256 gasLeft = _calculateAdjustedGasCap();
    uint256 vaultIndexToCheck; // hoisted up to be able to set newStartIndex
    harvestInfo = new HarvestInfo[](_vaults.length);

    // count the number of vaults to harvest.
    for (uint256 offset; offset < _vaults.length; ++offset) {
      // _startIndex is where to start in the _vaultRegistry array, offset is position from start index (in other words, number of vaults we've checked so far),
      // then modulo to wrap around to the start of the array, until we've checked all vaults, or break early due to hitting gas limit
      // this logic is contained in _getCircularIndex()
      vaultIndexToCheck = UpkeepLibrary._getCircularIndex(startIndex, offset, _vaults.length);
      address vaultAddress = _vaults[vaultIndexToCheck];

      (bool willHarvest, uint256 estimatedTxCost, uint256 callRewardsAmount) = _willHarvestVault(vaultAddress);

      if (willHarvest && gasLeft >= vaultHarvestFunctionGasOverhead) {
        gasLeft -= vaultHarvestFunctionGasOverhead;
        numberOfVaultsToHarvest += 1;
        harvestInfo[offset] = HarvestInfo(true, estimatedTxCost, callRewardsAmount);
      }

      if (gasLeft < vaultHarvestFunctionGasOverhead) {
        break;
      }
    }

    newStartIndex = UpkeepLibrary._getCircularIndex(vaultIndexToCheck, 1, _vaults.length);

    return (harvestInfo, numberOfVaultsToHarvest, newStartIndex);
  }

  function _willHarvestVault(address _vaultAddress) internal view returns (bool willHarvestVault, uint256, uint256) {
    (bool shouldHarvestVault, uint256 estimatedTxCost, uint256 callRewardAmount) = _shouldHarvestVault(_vaultAddress);
    bool canHarvestVault = _canHarvestVault(_vaultAddress);

    willHarvestVault = canHarvestVault && shouldHarvestVault;

    return (willHarvestVault, estimatedTxCost, callRewardAmount);
  }

  function _canHarvestVault(address _vaultAddress) internal view virtual returns (bool canHarvest);

  function _shouldHarvestVault(
    address _vaultAddress
  ) internal view virtual returns (bool shouldHarvestVault, uint256 txCostWithPremium, uint256 callRewardAmount);

  /*               */
  /* performUpkeep */
  /*               */

  function performUpkeep(bytes calldata _performData) external override {
    (
      address[] memory vaultsToHarvest,
      uint256 newStartIndex,
      uint256 heuristicEstimatedTxCost,
      uint256 nonHeuristicEstimatedTxCost,
      uint256 estimatedCallRewards
    ) = abi.decode(_performData, (address[], uint256, uint256, uint256, uint256));

    _runUpkeep(
      vaultsToHarvest,
      newStartIndex,
      heuristicEstimatedTxCost,
      nonHeuristicEstimatedTxCost,
      estimatedCallRewards
    );
  }

  function _runUpkeep(
    address[] memory _vaults,
    uint256 _newStartIndex,
    uint256 _heuristicEstimatedTxCost,
    uint256 _nonHeuristicEstimatedTxCost,
    uint256 _estimatedCallRewards
  ) internal {
    // Make sure estimate looks good.
    if (_estimatedCallRewards < _nonHeuristicEstimatedTxCost) {
      emit HeuristicFailed(
        block.number,
        _heuristicEstimatedTxCost,
        _nonHeuristicEstimatedTxCost,
        _estimatedCallRewards
      );
    }

    uint256 gasBefore = gasleft();
    // multi harvest
    require(_vaults.length > 0, "No vaults to harvest");
    (uint256 numberOfSuccessfulHarvests, uint256 numberOfFailedHarvests, uint256 calculatedCallRewards) = _multiHarvest(
      _vaults
    );

    // ensure _newStartIndex is valid and set startIndex
    uint256 vaultCount = _getVaultAddresses().length;
    require(_newStartIndex >= 0 && _newStartIndex < vaultCount, "_newStartIndex out of range.");
    startIndex = _newStartIndex;

    uint256 gasAfter = gasleft();
    uint256 gasUsedByPerformUpkeep = gasBefore - gasAfter;

    // split these into their own functions to avoid `Stack too deep`
    _reportProfitSummary(
      gasUsedByPerformUpkeep,
      _nonHeuristicEstimatedTxCost,
      _estimatedCallRewards,
      calculatedCallRewards
    );
    _reportHarvestSummary(_newStartIndex, gasUsedByPerformUpkeep, numberOfSuccessfulHarvests, numberOfFailedHarvests);
  }

  function _reportHarvestSummary(
    uint256 _newStartIndex,
    uint256 _gasUsedByPerformUpkeep,
    uint256 _numberOfSuccessfulHarvests,
    uint256 _numberOfFailedHarvests
  ) internal {
    emit HarvestSummary(
      block.number,
      // state variables
      startIndex,
      _newStartIndex,
      // gas metrics
      tx.gasprice,
      _gasUsedByPerformUpkeep,
      // summary metrics
      _numberOfSuccessfulHarvests,
      _numberOfFailedHarvests
    );
  }

  function _reportProfitSummary(
    uint256 _gasUsedByPerformUpkeep,
    uint256 _nonHeuristicEstimatedTxCost,
    uint256 _estimatedCallRewards,
    uint256 _calculatedCallRewards
  ) internal {
    uint256 estimatedTxCost = _nonHeuristicEstimatedTxCost; // use nonHeuristic here as its more accurate
    uint256 estimatedProfit = UpkeepLibrary._calculateProfit(_estimatedCallRewards, estimatedTxCost);

    uint256 calculatedTxCost = _calculateTxCostWithOverheadWithPremium(_gasUsedByPerformUpkeep);
    uint256 calculatedProfit = UpkeepLibrary._calculateProfit(_calculatedCallRewards, calculatedTxCost);

    emit ProfitSummary(
      // predicted values
      estimatedTxCost,
      _estimatedCallRewards,
      estimatedProfit,
      // calculated values
      calculatedTxCost,
      _calculatedCallRewards,
      calculatedProfit
    );
  }

  function _multiHarvest(
    address[] memory _vaults
  )
    internal
    returns (uint256 numberOfSuccessfulHarvests, uint256 numberOfFailedHarvests, uint256 cumulativeCallRewards)
  {
    bool[] memory isSuccessfulHarvest = new bool[](_vaults.length);
    for (uint256 i = 0; i < _vaults.length; ++i) {
      (bool didHarvest, uint256 callRewards) = _harvestVault(_vaults[i]);
      // Add rewards to cumulative tracker.
      if (didHarvest) {
        isSuccessfulHarvest[i] = true;
        cumulativeCallRewards += callRewards;
      }
    }

    (address[] memory successfulHarvests, address[] memory failedHarvests) = _getSuccessfulAndFailedVaults(
      _vaults,
      isSuccessfulHarvest
    );

    emit SuccessfulHarvests(block.number, successfulHarvests);
    emit FailedHarvests(block.number, failedHarvests);

    numberOfSuccessfulHarvests = successfulHarvests.length;
    numberOfFailedHarvests = failedHarvests.length;
    return (numberOfSuccessfulHarvests, numberOfFailedHarvests, cumulativeCallRewards);
  }

  function _harvestVault(address _vault) internal virtual returns (bool didHarvest, uint256 callRewards);

  function _getSuccessfulAndFailedVaults(
    address[] memory _vaults,
    bool[] memory _isSuccessfulHarvest
  ) internal pure returns (address[] memory successfulHarvests, address[] memory failedHarvests) {
    uint256 successfulCount;
    for (uint256 i = 0; i < _vaults.length; i++) {
      if (_isSuccessfulHarvest[i]) {
        successfulCount += 1;
      }
    }

    successfulHarvests = new address[](successfulCount);
    failedHarvests = new address[](_vaults.length - successfulCount);
    uint256 successfulHarvestsIndex;
    uint256 failedHarvestIndex;
    for (uint256 i = 0; i < _vaults.length; i++) {
      if (_isSuccessfulHarvest[i]) {
        successfulHarvests[successfulHarvestsIndex++] = _vaults[i];
      } else {
        failedHarvests[failedHarvestIndex++] = _vaults[i];
      }
    }

    return (successfulHarvests, failedHarvests);
  }

  /*     */
  /* Set */
  /*     */

  function setPerformUpkeepGasLimit(uint256 _performUpkeepGasLimit) external override onlyOwner {
    performUpkeepGasLimit = _performUpkeepGasLimit;
  }

  function setPerformUpkeepGasLimitBuffer(uint256 _performUpkeepGasLimitBuffer) external override onlyOwner {
    performUpkeepGasLimitBuffer = _performUpkeepGasLimitBuffer;
  }

  function setHarvestGasConsumption(uint256 _harvestGasConsumption) external override onlyOwner {
    vaultHarvestFunctionGasOverhead = _harvestGasConsumption;
  }

  /*      */
  /* View */
  /*      */

  function _getVaultAddresses() internal view virtual returns (address[] memory);

  function _getVaultHarvestGasOverhead(address _vault) internal view virtual returns (uint256);

  function _calculateAdjustedGasCap() internal view returns (uint256 adjustedPerformUpkeepGasLimit) {
    return performUpkeepGasLimit - performUpkeepGasLimitBuffer;
  }

  function _calculateTxCostWithPremium(uint256 _gasOverhead) internal view returns (uint256 txCost) {
    return UpkeepLibrary._calculateUpkeepTxCost(tx.gasprice, _gasOverhead, chainlinkUpkeepTxPremiumFactor);
  }

  function _calculateTxCostWithOverheadWithPremium(
    uint256 _totalVaultHarvestOverhead
  ) internal view returns (uint256 txCost) {
    return
      UpkeepLibrary._calculateUpkeepTxCostFromTotalVaultHarvestOverhead(
        tx.gasprice,
        _totalVaultHarvestOverhead,
        keeperRegistryGasOverhead,
        chainlinkUpkeepTxPremiumFactor
      );
  }

  function _calculateExpectedTotalUpkeepTxCost(
    uint256 _numberOfVaultsToHarvest
  ) internal view returns (uint256 txCost) {
    uint256 totalVaultHarvestGasOverhead = vaultHarvestFunctionGasOverhead * _numberOfVaultsToHarvest;
    return
      UpkeepLibrary._calculateUpkeepTxCostFromTotalVaultHarvestOverhead(
        tx.gasprice,
        totalVaultHarvestGasOverhead,
        keeperRegistryGasOverhead,
        chainlinkUpkeepTxPremiumFactor
      );
  }

  function _estimateSingleVaultHarvestGasOverhead(
    uint256 _vaultHarvestFunctionGasOverhead
  ) internal view returns (uint256 totalGasOverhead) {
    totalGasOverhead = _vaultHarvestFunctionGasOverhead + keeperRegistryGasOverhead;
  }

  /*      */
  /* Misc */
  /*      */

  /**
   * @dev Rescues random funds stuck.
   * @param _token address of the token to rescue.
   */
  function inCaseTokensGetStuck(address _token) external onlyOwner {
    IERC20 token = IERC20(_token);

    uint256 amount = token.balanceOf(address(this));
    token.safeTransfer(msg.sender, amount);
  }
}
```

This is an abstract contract that iterates vaults from a provided list and fits vaults within upkeep gas limit. The contract also provides helper functions to calculate gas consumption and estimate profit and contains a trigger mechanism can be time-based, profit-based or custom. Finally, the contract reports profits, successful harvests, and failed harvests.