[Draft] Keeper Rewards Contracts

Summary

Terms - A keeper is a user or bot which executes some job on a smart contract. A job is a task which must be executed on a regular basis.

I’ve finished a draft version of the keeper contracts and I would like to get some feedback on the design.

Github Repository

Motivation

Several components of the Indexed protocol require regular updates:

The pool controller must reweigh or reindex each pool once a week to set new targets, which requires that the Uniswap oracle have a price record for each token between 1 and 10.5 days old.

The UnboundTokenSeller contract, which is used by index pools to sell tokens when they reach a weight of 1% on their way out of a pool, also uses the Uniswap oracle to price swaps. It requires that the oracle have a price record between 20 minutes and 1 day old.

In order to keep current and future contracts running without manual effort or individuals incurring costs, 5% of the initial NDX supply has been reserved for keepers. This is looking like overkill at current prices, so we can probably use a large fraction of that for additional liquidity incentives instead.

Design

The core contract is KeeperVault. This is a simple contract which holds the NDX, keeps track of approved jobs contracts, and pays keepers as requested by job contracts.

The vault can be configured with a rewardsPerGas value which sets the amount of NDX (in wei) that are paid per unit of gas spent in a job contract.

The job contracts use a function modifier that determines the gas used during job execution, adds 50k (for the gas used by the vault) and then submits the collection request to the vault to reward the caller.

The keeper has a maxDailyRewards value which can be configured to prevent the contract from being drained before we can respond in the event of someone finding a way to do a gas-burn attack on one of the jobs contracts.

This design will make it very simple to add or replace keeper jobs, we just need to approve new ones on the vault and disapprove any we want to remove.

Pool Controller Jobs
The current jobs contract can be used to reward keepers for updating the data needed by MarketCapSqrtController. It has 4 jobs that can be executed:

  • reweighPool
  • reindexPool
  • updateCategoryPrices
  • orderCategoryTokensByMarketCap

These functions all call the function on the pool controller of the same name.

updateCategoryPrices requires that the job not be executed more than once per day – this is more often than is needed, but is a useful time-frame to ensure pools which are out of sync with each other’s weighting times can always access fresh prices. We could modify the duration on this to be one week to avoid unnecessary expenditure of NDX, but we would need to replace the contract to shorten the duration once we have more than one index per category.

orderCategoryTokensByMarketCap requires that the job not be executed within 3.5 days of the last time the category was sorted. This job is somewhat difficult to determine a time restriction for, as the categories only need to be sorted in two situations: when an index pool for the category is ready to re-index, and when an index pool is about to be deployed.

The former only happens once every month, but when it does happen the controller requires that the category be sorted in the last 24 hours. I realize now that this was a poor design choice when keepers are considered, as there is no way for the keeper job to check which category a pool uses.

It may be best to simply remove this job and do it manually until we can get the community to accept an update of the pool controller which exposes query functions to enable this job to be run in the reindexPool job.

Rewards
The average gas price over the last few weeks has been around 70 gwei. If we use the current Uniswap spot price for NDX, we get a ratio of 1 ETH = 300 NDX. At 70 gwei, that results in a price for 1 gas of 21,000 gwei in NDX.

Here are some gas costs for the jobs we need to give rewards for:

Using the above info we can assume the following average prices:

  • reindexPool: 14 NDX
  • reweighPool: 7 NDX
  • updateCategoryPrices: 17 NDX
  • orderCategoryTokensByMarketCap: 6 NDX

Assuming 1 index pool per category, we can estimate the following monthly rewards for each index:

  • ~510 NDX per month updating token prices
  • ~20 NDX per month re-indexing the pool (combined with the category sorting)
  • ~21 NDX per month re-weighing the pool

This gives an annual cost of 6,612 NDX per pool at current rates, so about 13k for the two index pools we have. We would need to adjust this rate over time as the value of NDX changes.

Alternatives

There are a couple of alternatives to the current design that I can think of.

Price updates

The price updates using the entire category are sort of redundant, as the current 2 categories use many of the same tokens (all the DEFI tokens are included in the CC category). It may be better to simply whitelist the tokens we want to track prices for, but we’d need to update this any time we add or remove a token from a category.

Rewards

We could use Keep3r for rewards instead of this custom KeeperVault. This would simplify things quite a bit and would remove the need to have people running custom bots, because if they allowed us to we could just include the scripts in the existing keeper bot they have. The main reason I didn’t go with this initially is the fact that NDX governance would need to acquire NDX/KPR liquidity if the dao approved us, or ETH/KPR liquidity if not.

1 Like

Some initial thoughts:

  • I think using a custom KeeperVault and paying people in NDX to maintain the protocol is better than using keep3r for rewards.
    • It adds additional utility to NDX beyond governance as NDX is now being used for rewarding people to maintain the protocol and governance
    • If keep3r is used in order for people to be compensated financial for their work as keepers ultimately depends on whether or not KPR has any financial value.
    • Andre routinely tests in prod which I think is a very bad idea and you would be risking the security of keepers and the money they get paid on code that is routinely tested in prod
    • Whether or not keep3r is continually developed ultimately depends on Andre, if Andre stops deciding to maintain keep3r than Indexed now has to maintain Indexed and Keep3r
    • Using custom keepers creates healthy competition and essentially lets “the best bot maker win”
  • Keepers can use create2 to calculate addresses so it might not be problematic that keepers cant check which category a pool uses. (copy & pasting this from our discord conversation)

It may be better to simply whitelist the tokens we want to track prices for, but we’d need to update this any time we add or remove a token from a category.

This is probably an okay alternative for now given that there’s only two index pools but this would be a pain in the ass to maintain at scale and increases likelihood of errors that could have adverse effects on the protocol.

After looking into the idea for a token whitelist, I’ve realized it’d most likely waste a similar amount of gas as just allowing redundant calls using the category lists, as we would need to store each token’s last update timestamp. Also as mentioned it would require much more maintenance.

Thanks @bonedaddy for the reminder regarding the c2 address derivation. I’ve updated the contract to reflect that by removing the orderCategoryTokensByMarketCap job and adding the logic it held to the reindexPool job.

1 Like