Reward Distributors 101
The cool trick powering liquidity farms, NFT staking, and, soon, DIG Network rewards
If you’ve been in crypto for some time, you’re familiar with the concept of liquidity farms - contracts where users lock liquidity tokens to continuously receive rewards for every second the assets are stored, all while being able to withdraw the underlying assets at any time. In this post, I’d like to shine some light on how they work and how the main trick is used in a new app that will soon be live on Chia.
How to Avoid a Lot of Transactions
Believe it or not, there was a time when the concept of staking tokens did not exist at all. The naive approach to distributing tokens to stakers - calculating and sending rewards every second or block - required a lot of transactions to be made very frequently. Implementing a liquidity farm with this approach was simply too expensive for any protocol, even with relatively few users. Scaling would also be impossible, as the transactions from only one farm would probably exceed block size limits after reaching thousands of users. That number is orders of magnitude less than the number of liquidity providers a top AMM may reasonably expect.
The naive approach had no chance of working, which makes the existence of liquidity farms so much more impressive. At some point, someone ran with the idea and came up with an optimization that opened up the ‘staking era’ of DeFi. The main insight needed for reward distribution is to realize that payout transactions are, for most blocks, the same - which allows a contract to group them together.
Let’s take an example. Alice and Bob are the only two users to stake their tokens in a farm (one LP token each). Thus, they each earn the same share of rewards. If the reward rate is 9 REW (reward tokens) per second, Alice and Bob will each get 4.5 REW per second. To avoid making a transaction every block, a contract can just keep track of each participant’s earning rate and calculate how much REW they’re owed when they request a payout or unstake their tokens. More specifically, if Alice requests her rewards 100 seconds after she staked her LP token, and she is supposed to receive 4.5 REW per second, the contract would just send her 100 * 4.5= 450 REW in a single transaction. Simple, yet powerful math!
What happens if Bob deposits another LP token? The farm would now have, in total, 3 LP tokens staked, one from Alice and two from Bob. Given the same total distribution rate of 9 REW per second, Alice should earn 3 REW per second, and Bob would get 6 REW per second. Additionally, when the change happens, the contract could pay Alice and Bob for the last period (where they each got 4.5 tokens per second).
There is still a major way to improve the contract further. Note that, in the method above, when the reward weights are changed, we need to recompute the reward rate for every user. If you’re aiming for many entries, that recalculation is the main scalability bottleneck. Triggering payouts for everyone every time someone stakes or unstakes some tokens is also pretty inefficient.
The solution is to keep track of a reward accumulator. This is the REW one token of LP would earn if it was staked at the very beginning of the reward distributor. Let’s take another example. Say Bob staked an LP token when the accumulator was 200 (thanks to things that happened beforehand and are irrelevant) and the distribution rate was 1 REW per second. Bob is a pretty indecisive guy, so he unstakes his LP just 50 seconds later. Given the rate, the accumulator’s value is now 200 + 50 * 1 = 250 REW. Bob receives the difference between the current value of the accumulator and the value when he first staked his token: 250 - 200 = 50 REW tokens paid. If he staked 2 LP tokens, the amount would double.
This might seem like a step backward at first - we’ve gone up in complexity only to get the same result. However, this method is far better because we now only care about the accumulator, not direct reward rates to individual users. There can be thousands or millions of rate adjustments between a stake and unstake operation - user-specific data (namely, the number of LP tokens Bob staked and the value of the REW accumulator when Bob staked them) is only accessed when rewards need to be paid out. This decoupling also means the liquidity farm has a compact state that is modified frequently - cumulative reward per share (the accumulator) and total deposited shares (LP tokens), along with epoch information (e.g., how many rewards to distribute and until when). Bob’s actions will not lead to Alice’s or any other user’s particular data being updated, but rather only change the small state in a way that’s easy to compute.
If you’d prefer a more academic format, you can check out the paper that nicely presents the idea here.
What DIG is Doing
Last year, Michael Taylor reached out to me looking to build an on-chain solution for DIG mirror rewards. The system at the time involved the validators (reward managers) sending payouts at fixed intervals of time. Its main issue was throughput - the fewer payments per week, the more trust mirrors put into validators, as users are not certain they’ll receive rewards until they see them in their wallet. However, more frequent payments would bring more network activity, which means more fees paid for distribution. If you’ve made it to this part of the post, you probably have a good idea of what a good solution to the problem looks like.
With the design presented above, the ideal (trust-minimizing) number of payments goes from “once every second” to “one when a user requests it or the mirror goes offline” - no matter what happens, mirrors get paid for the time when they have active shares in the rewards distributor.
Minimizing the number of on-chain payments is a goal in itself, but putting all active mirrors into a singleton lets us remove even more trust. For example, DIG rewards don’t necessarily need to come from the validator - anyone can add them to the current reward period or commit them to a future epoch. What’s more, if the validator misbehaves, sponsors (people who contributed rewards to future epochs) can claw back a percentage of the rewards, disincentivizing bad behavior. This opens the gates to a decentralized content network, where people interested in making some data accessible can contribute to the reward pot that gets distributed to mirrors that store and serve the data.
More recently, Michael asked me how hard it would be to turn the reward distributor into an NFT farm. Essentially, the code that allowed a trusted validator to add/remove entries was replaced by code that accepted any NFT minted by a particular DID for one share of the rewards. This makes NFT distributors an exciting way to reward NFT collection holders. The NFT reward distributor is now included in the official CHIP, which was just published for public review.
In a way, DIG - much like liquidity farms that came before - needed a very specific scaling solution. By optimizing based on transaction patterns, reward recipients can get the same assurance as they would get from payout transactions every second, all while not clogging the Chia blockchain. Reward distributors scale extremely well, making DIG prepared for its upcoming testing deployments, mainnet release, and eventual world domination. My hope is that, with this post, you’ve seen some of the magic that makes them possible.