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.
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:
-
reweighPool
: cc10 - 440k | defi5 - 250k -
reindexPool
: (has not been executed on mainnet, but testing shows ~450k for 5 tokens, ~900k for 10) -
updateCategoryPrices
: cc - 1.1m | defi - 580k -
orderCategoryTokensByMarketCap
: cc - 360k | defi - 250k)
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.