A Technical Exploration Into The Mechanics of Staking Rewards on EOS
On 13 May 2019, EOS New York published a proposal to introduce staking rewards on EOS. Please go here to read the full post.
Summary of Part 1:
The purposeful expansion of token supply (inflation) is a tool which underpins the EOS incentive model. The EOS incentive model, like all blockchain incentive models, is designed to encourage the necessary work that ensures long-term health and functioning. Currently, it only prioritizes block production, which is obviously vital for maintaining the blockchain. But, the EOS incentive model omits another piece of work critical to EOS, voting.
Voting is required to secure the network and choose block producers, but token-holders are incentivized to do neither. In fact, there is an opportunity cost to vote, where one sacrifices liquidity and time-value. EOS New York is exploring a staking reward in order to incentivize token-holders to vote which further secures the blockchain, further decentralizes the voting base, and completes the EOS incentive model which will in part ensure EOS’ long-term health.
In this article, we will revise some of our previous assumptions while exploring the technical feasibility and specifications to deploy staking rewards on EOS.
Technical Specification & Feasibility
High-Level Design Goals
- Incentivize voter participation.
- Incentivize voting for more the most block producers possible.
- Action(s) associated with claiming must be rate limited and not place a significant additional burden on EOS resources.
- Mitigate the ability to game, abuse, or optimize reward claiming.
An Adjustment from Part 1: Using a Flat Rate Over a Dynamic One
In part 1 (which can be read here) we discussed a dynamic rate which can adjust itself based on the total voter participation on-chain. A far simpler implementation includes a rate which remains flat and agnostic (e.g. X% unchanging). The fewer tokens that are participating, the larger the awarded portion of the total rewards available per token. The fewer participants the fewer times the reward pool is divided, the more participants the more times the pool is divided.
We are not proposing a specific rate at this time as we are just working through the development of how the solution specifically functions. The specific figures are better proposed once the mechanics of the solution are technically understood and the community has had a chance to provide feedback.
Calculating Rewards Based on Vote Weight
One’s first thought might be to simply award tokens based on total tokens staked. While this is a straightforward method it does not incentivize voting for more BPs over fewer. Therefore, we used vote weight over token-stake in our calculations to start.
Vote Weight Explained
The way EOS counts a token-holders vote is not as simple as 1 token:1 vote-weight. A calculation is performed when a vote occurs that encompasses both the number of tokens one is voting with and the time at which the vote is cast relative to all other older votes. EOS Canada does a good job of explaining this.
“total vote weight” = SUM (“vote weight received by BP”)
What this means is that if a voter casts his or her vote for 20 BPs then his or her share of the total vote weight is 20 times higher than if he votes for 1 BP. By using this as the basis of our calculations we can reward voting for more BPs rather than fewer, meeting design goal #2.
Understanding Vote Decay
Vote decay was introduced in Dawn 4.0 of May 2018.
Much of the work we are doing since Dawn 3.0 involves tweaking the system contract. One of those tweaks is the implementation of vote decay. In order to maintain maximum voting influence, each voter will have to re-assert their vote every week. Voting influence decays with a half-life of 1 year for those who do not keep their votes up to date.
“Vote decay” is actually increasing weight of newer votes cast, not the decreasing weight of old votes. So, voters who update their vote will increase their vote weight even if their stakes stays the same (link).
The vote decay “cut-off” is 00:00 UTC each Saturday.
U1 casts their vote of 100 EOS on 11:59 UTC on Friday
U2 casts their vote of 100 EOS 00:01 UTC on Saturday.
U2’s vote weight will be 1.36% than U1 higher despite them both voting with the same amount of tokens.
All of this introduces an incentive to revote each week to receive the maximum and helps us meet design goal #1 & #2.
How Can Vote Weight Change?
- Changing stake (e.g.
– Vote weight can increase each time someone votes.
– Voting for a different number of BPs changes vote weight. (e.g.
voteproducerwhich includes proxies)
- Changing REX balance (e.g.
The total vote weight changes each time someone votes. Therefore, each voter’s share of total vote weight changes as well.
Feasibility of using the Current BP Reward Per-Vote Algorithm
The algorithm that outputs Block Producer reward based on votes, (
eosio.vpay), similarly uses vote-weight in its calculations. Let’s explore the idea of adapting it to fit our purpose to reward voter action.
BPs per-vote reward algorithm is briefly described here.
The idea of the algorithm is to push rewards proportional to both vote weight and the time the vote weight has been held. Let’s call it “vote weight-time”. Vote weight for a BP changes when someone votes for a BP or votes for a new set of BPs where the original BP is no longer present i.e. the BP vote is removed. So, we have periods of constant votes that looks like: from time t1 to time t2 vote weight was W1, from t2 to t3 vote weight was W2, and so on.
So, vote weight-time accumulated over time:
“BPs vote weight-time” = SUM(“BP’s vote weight” * “time the BP has held the vote weight”)
Each time vote weight changes or a BP claims rewards “previous vote weight” * “time since last change” gets added to the BP’s balance (
It also calculates total vote weight-time which increases every time someone votes for BP directly, or proxy votes for BP (in this case BP receives votes from all users of the proxy plus votes of the proxy itself).
When BP claims rewards:
reward = BP’s votepay_share / total_producer_votepay_share * “amount in rewards bucket”
… and BP’s vote weight and total vote weight get decreased by “BP’s
On top of that, the algorithm makes an adjustment to artificially lower vote weights which in turn lowers the total reward for a BP who doesn’t claim often enough to be considered “active”. Meaning, if “you don’t use it, you lose it”.
Adapting eosio.vpay for Voter Rewards
In order to use this same algorithm for voter rewards, we should update a voter’s balance every time the vote weight generated by a voter is changed.
As we’ve seen earlier, vote weight can change not only as a result of voter’s action but also when a proxy votes for different number of BPs.
But there is a problem, there is no way to update voter’s “weight-time” balance when proxy casts its vote, for 2 reasons:
- There are no “pointers” from proxy to its users, which means there is no efficient way to find voters whose data should be updated.
- Even if we could find all users of a proxy, there can be too many of them and updating their data will require a proxy to have extremely high CPU and NET stake. This may also exceed the maximum transaction size.
Here is an example:
Let’s assume there is a voter with 10 EOS who stakes and delegates his or her vote to a proxy that has voted for only 10 BPs. This means that going forward the voter generates the following weight:
10*stake2vote(10 EOS) per hour
One hour later the proxy changes its vote to 5 BPs, 9 hours after that the voter claims his reward. If we apply the algorithm as is then the voter’s “vote weight-time” will look like:
10 *stake2vote(10 EOS) * 10 hours = 100*stake2vote(10 EOS)
But in fact it is:
10*stake2vote(10 EOS) * 1 hour + 5*stake2vote(10 EOS) * 9 hour = 55*stake2vote(10 EOS)
So, we can record the correct vote weight for a voter when a user casts his or her vote. But, if he or she voted for a proxy we are not able to update their vote weight when the proxy changes the number of BPs it has voted for (which changes total vote weight). Furthermore, we would accumulate vote weight-time for the voter at a faster or slower rate than we otherwise should.
Incorrect accumulated vote weight-time will result in an incorrect share of total vote weight-time attributed to the voter. If shares have been miscalculated for one or many voters, then the sum of their shares may be no longer equal to 100% of the reward bucket. This may result in pushing higher rewards to some voters while others get nothing. Or in the inability of voters to claim all rewards generated for them.
In our example, the share of rewards calculated by the original algorithm is almost 2 times higher than the share of “vote weight-time” generated by the voter.
So, applying BPs per-vote reward algorithm as-is to voter rewards is not optimal and it would mean that we could not meet design goal #4.
Adapting Block Producer eosio.vpay Algorithm to Account for Proxy Voting
As we have seen in the previous section, we cannot always calculate the correct SUM(“vote weight time”) for voters who have delegated their votes to a proxy, therefore we cannot calculate their fair share of actual “total vote weight-time”.
To avoid this problem we are going to push rewards based on their share of “observed vote weight-time”. We will record vote weight when voters make their changes. And we will update “observed total vote weight” at the same time. The observed total vote weight may be different from real total vote weight at some periods. But by definition observed total vote weight will always be exactly equal to the sum of observed vote weights of all individual voters.
The substantial difference here is that we are no longer interested in actual total “vote weight-time”. We intentionally and completely ignore the actions of proxies and changes to total vote weight caused by them with this change. But, we still use the number of BPs a proxy has voted for at the moment when a voter chooses the proxy. Next time the voter changes or updates his or her choice of a proxy, we will take the number of BP the proxy voted for again. But we won’t try to adjust to changes made by proxies.
Even the original algorithm for BPs per-vote rewards does not calculate “total vote weight-time” fairly but makes some adjustments for the purpose of stimulating BPs to actively claim their rewards.
A Proposed New Requirement on Registered Proxies
While we have dealt with the issue of not being able to calculate rewards for voting accounts as their proxy’s votes change total vote weight, we’ve unintentionally created another one. By only checking the vote weight of a proxy on behalf of a voter at the time a vote is delegated a coordinated effort could result between the two where the proxy vote is higher when the voter delegates and is subsequently lowered once the vote weight is observed. The voting account will enjoy the maximum amount of rewards but with the lowest vote weight possible. This jeopardizes design goal #2.
The roles and mechanics of proxies in EOS are described well in this post by Myles Snyder, formerly of Aurora EOS.
Voting has a large impact on rewards that go to block producers. These rewards should be distributed widely and as evenly as possible so that they may benefit a diverse set of interests and also so that they are not concentrated into the hands of a few. This understanding was effectively the reason for design goal #2.
For this reason, and to avoid the loophole we’ve described above, we are advocating that an additional requirement be added to proxies; they must vote for a minimum of 21 block producers at any given time.
This requirement is in REX for non-proxy voters but the proxy loophole remains (link). Any EOS account may still choose to vote for fewer BPs on their own, but they will not reap the maximum benefit of the work they’ve done to cast their vote and secure the network.
Now we meet design goal #2 & #4.
As an added benefit, we completely remove the need to have a REX voting requirement at all as it becomes redundant with the staking reward proposal. We propose the community considers removing the requirement due to its redundancy.
Effect of Voter Rewards on Resource Utilization & Limiting Claim Frequency
By implementing this proposal we will incentivize more token holders to vote, to vote more often, and to vote for more BPs. We propose that reward claim periods be once a week where the cutoff is the vote decay time of 00:00 UTC Saturday. This way, we limit incentivizing voters spamming the network with votes unnecessarily and helps meet design goal #3.
The increase in activity will mean an increase in the frequency of the
voteproducer action and the cost of CPU and NET each time the action is called.
NET increase example:
voteproducer takes on average 1,767μs of CPU and 179 bytes of NET (source). Increasing the BPs included in each vote cannot increase the total NET per action beyond 352 bytes, which is the NET cost to vote for 30 BPs (source).
If all 1,257,993 accounts on EOS (total accounts at the time of this writing) directly vote for 30 BPs every week, it will take:
1,257,993*352 = 442,813,536 NET bytes per week
Theoretical total network capacity (2 block per second and 1,048,576 bytes per block):
1,048,576*2*7*24*3600 = 1,268,357,529,600 NET bytes per week
So, voting can take up to:
442,813,536 / 1,268,357,529,600 = 0.00035, i.e. 0.035% of NET
CPU increase example:
The CPU usage will grow with the number of BPs in the
voteproducer action as well because a longer list of BPs takes longer to process. In addition, a new system contract will calculate observed vote weight-time, transfer funds to the voter, and sometimes issue new funds. Unfortunately, it’s hard to come up with reasonable numbers for these factors so we will include an exploration of this additional workload once we begin testing. Even so, we can still get a baseline from working through an example.
Based on the current average of 1,767μs all accounts voting every week will take:
1,257,993×1,767 = 2,222,873,631μs CPU
While theoretical total capacity (2 blocks per second, 200000μs per block)
200000*2*7*24*3600 = 241,920,000,000μs CPU per week
2,222,873,631 / 241,920,000,000 = 0.00919, i.e. 0.9%
Even if this number grew by magnitudes, it would still be a negligible increase.
The New Dynamic
As a result of this proposal being implemented, we expect strong incentives to organically encourage more token-holders to vote once a week at the most, to do so with the maximum of 30 votes, and with little ability to game or optimize the system. As a byproduct, proxy accounts would now be held to the same standard of voting that individual accounts are per REX requirements, and REX requirements would be removed because this better solution now exists. Ultimately, by increasing the total tokens voting on-chain EOS will be more decentralized and secure.
High-Level Design Goals Met
- Incentivize voter participation. ✔️
- Incentivize voting for more block producers than fewer. ✔️
- Action(s) associated with claiming must be rate limited and not place a significant additional burden on EOS resources. ✔️
- Mitigate the ability to game, abuse, or optimize reward claiming. ✔️
EOS New York will continue to develop the open-source code for this solution with the community and we welcome all contributions. Once completed, the code will be released for the community to decide.
EOS New York is a Top 21 Block Producer on the EOS Blockchain