Job overview and code

This job is designed to be used for triggering Gerabox gauge updates (and CRV, CVX gauge updates on Mainnet).

The purpose of updating gauges is in replenishing the Gearbox rewards in CRV, CVX and powering the Gearbox governance on Mainnet, Optimism, and Arbitrum. The update must occur weekly on Monday at about 0530 AM UTC.

Code below

Mainnet:

pragma solidity ^0.8.20;

import "Interfaces.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Updater is Ownable(msg.sender) {
    IAddressProvider public provider;
    ICvx public cvx;
    ICrv public crv;
    uint256 public pid;
    uint256 public delay;
    uint256 public interval;
    uint256 public canExecAt;
    event updatedGauge(string indexed name);
    
    constructor(address _addressProvider, address _cvx, address _crv, uint256 _pid, uint256 _delay, uint256 _interval){
        provider = IAddressProvider(_addressProvider);
        cvx = ICvx(_cvx);
        crv = ICrv(_crv);
        pid = _pid;
        delay = _delay;
        interval = _interval;
        canExecAt = getBlockTimestamp()+delay;
    }

    function setParams (address _addressProvider, address _cvx, address _crv, uint256 _pid, uint256 _delay, uint256 _interval) public onlyOwner {
        provider = IAddressProvider(_addressProvider);
        cvx = ICvx(_cvx);
        crv = ICrv(_crv);
        pid = _pid;
        delay = _delay;
        uint256 dummy = canExecAt - interval;
        interval = _interval;
        canExecAt = dummy + interval;
    }

    function canExec() public view returns (bool, bytes memory) {
        if (getBlockTimestamp() >= canExecAt){
            bytes memory cdata = abi.encodePacked(this.performFullUpdate.selector);
            return (true, cdata);
        }
    }

    function getBlockTimestamp() public view returns (uint256){
        return block.timestamp;
    }

    function updateCvxCrv() public returns (bool){
        crv.refreshGaugeRewards();
        bool ok = cvx.earmarkRewards(pid);
        require(ok, "Something went wrong in the earmark rewards function");
        return ok;
    }

    function updateGauges() public {
        IContractsRegister register = IContractsRegister(provider.getContractsRegister());
        address[] memory pools = register.getPools();
        for (uint256 i = 0; i<pools.length; i++){
            IPool pool = IPool(pools[i]);
            (bool ok, bytes memory output) = address(pool).call(abi.encodePacked(pool.poolQuotaKeeper.selector));
            if (ok){
                (address keeperAddress) = abi.decode(output, (address));
                IPoolQuotaKeeper keeper = IPoolQuotaKeeper(keeperAddress);
                IGauge gauge = IGauge(keeper.gauge());
                gauge.updateEpoch();
                string memory name = pool.name();
                emit updatedGauge(name);
            }
            //otherwise the pool don't have a quota keeper - that can happen
        }
    }

    function performFullUpdate() public returns (bool){
        bool ok = updateCvxCrv();
        updateGauges();
        require (ok, "Something went wrong in the earmark rewards function");
        canExecAt = getBlockTimestamp() + interval;
        return ok;
    }
}

Other networks:

pragma solidity ^0.8.20;

import "Interfaces.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract Updater is Ownable(msg.sender) {
    IAddressProvider public provider;
    uint256 public delay;
    uint256 public interval;
    uint256 public canExecAt;
    event updatedGauge(string indexed name);
    
    constructor(address _addressProvider, uint256 _delay, uint256 _interval){
        provider = IAddressProvider(_addressProvider);
        delay = _delay;
        interval = _interval;
        canExecAt = getBlockTimestamp()+delay;
    }

    function setParams (address _addressProvider, uint256 _delay, uint256 _interval) public onlyOwner {
        provider = IAddressProvider(_addressProvider);
        delay = _delay;
        uint256 dummy = canExecAt - interval;
        interval = _interval;
        canExecAt = dummy + interval;
    }

    function canExec() public view returns (bool, bytes memory) {
        if (getBlockTimestamp() >= canExecAt){
            bytes memory cdata = abi.encodePacked(this.performFullUpdate.selector);
            return (true, cdata);
        }
    }

    function getBlockTimestamp() public view returns (uint256){
        return block.timestamp;
    }

    function updateGauges() public {
        IContractsRegister register = IContractsRegister(provider.getContractsRegister());
        address[] memory pools = register.getPools();
        for (uint256 i = 0; i<pools.length; i++){
            IPool pool = IPool(pools[i]);
            (bool ok, bytes memory output) = address(pool).call(abi.encodePacked(pool.poolQuotaKeeper.selector));
            if (ok){
                (address keeperAddress) = abi.decode(output, (address));
                IPoolQuotaKeeper keeper = IPoolQuotaKeeper(keeperAddress);
                IGauge gauge = IGauge(keeper.gauge());
                gauge.updateEpoch();
                string memory name = pool.name();
                emit updatedGauge(name);
            }
            //otherwise the pool don't have a quota keeper - that can happen
        }
    }

    function performFullUpdate() public returns (bool){
        updateGauges();
        canExecAt = getBlockTimestamp() + interval;
        return true;
    }
}

Interfaces:

pragma solidity ^0.8.20;

interface IAddressProvider{
    function getContractsRegister() external view returns (address);
    function getDataCompressor() external view returns (address);
}

interface IDataCompressor{
    struct PoolData {
        address addr;
        bool isWETH;
        address underlying;
        address dieselToken;
        uint256 linearCumulativeIndex;
        uint256 availableLiquidity;
        uint256 expectedLiquidity;
        uint256 expectedLiquidityLimit;
        uint256 totalBorrowed;
        uint256 depositAPY_RAY;
        uint256 borrowAPY_RAY;
        uint256 dieselRate_RAY;
        uint256 withdrawFee;
        uint256 cumulativeIndex_RAY;
        uint256 timestampLU;
        uint8 version;
    }
    function getPoolData(address _pool) external view returns (PoolData memory);
}

interface IContractsRegister{
    function getPoolsCount() external view returns (uint256);
    function getPools() external view returns (address[] memory);
}

interface IPoolQuotaKeeper{
    function gauge() external view returns (address);
}

interface IPool{
    function poolQuotaKeeper() external view returns (address);
    function name() external view returns (string memory);
}

interface IGauge{
    function updateEpoch() external;
}

interface ICvx{
    function earmarkRewards(uint256 _pid) external returns(bool);
}

interface ICrv{
    function refreshGaugeRewards() external;
}

Principle of operation

  1. On all networks, the contract is deployed with the Gearbox addressProvider address given as a parameter. At each invocation, this provider is called upon to provide the address of the contracts register, which is then called for a list of pools. Thereafter, the list is iterated over, and each pool’s gauge keeper is obtained if present. Pools that lack a gauge keeper are skipped. The rest have their gauge obtained from the keeper and then updated.
  2. On mainnet, cvx and crv gauges are also updated
  3. This is done as a cronjob with a parametric delay meant for tweaking the time of execution start

Invoked functions

The keeper invokes the canExec function as a resolver. When it returns true, performFullUpdate is called. It can also be called manually, and its composite parts updateCvxCrv and updateGauges can be separately called by anyone at any time as well.