I had expected to make more progress on my offchain content tool, but what I thought would be a quick little task got me bogged down for a while: calculating how much an individual's vote contributes to a post's rewards. I want to make sure to limit use of the tool to people who have a big enough vote to actually get rewards for the post even if nobody else votes for it, so I need to know how much an individual's vote is worth. And rather than relying on the folk wisdom of how to calculate that, I figured it would be beneficial for me to figure out the real process myself by digging through the blockchain's C++ code.
I found the relevant C++ code pretty hard to read, partly for understandable reasons (the code needs to be explicit about using some things in database data structures), partly due to bloat from a single codebase supporting all the hardforks (there are lots of "if hardfork X then do A, otherwise do B" blocks of code), partly because C++ tends to invite abstractions and spreading things across multiple files, and partly due to the way some of the particular code was written. But I eventually figured things out and now have a pretty good understanding of how vests, mana, rshares, the reward pool, and the "convergent linear" reward curve influence the value of a vote.
I decided to write up a little proof-of-concept script in Node.js to sanity check everything, which shows the value of various "sea creature" levels and how much their vote would be worth if it was the only vote on a post (the convergent linear curve works on the sum of all votes, not each individual vote, but if only one person votes then they're the same thing).
const dsteem = require('dsteem');
const client = new dsteem.Client('https://api.steemit.com');
var creatures = [{name: 'Redfish', vests:0, power:1},
{name: 'Minnow', vests:1000000, power:1},
{name: 'Dolphin', vests:10000000, power:1},
{name: 'Orca', vests:100000000, power:1},
{name: 'Whale', vests:1000000000, power:1}];
const accountsToQuery = ['danmaruschak', 'steemcurator01'];
// do api calls to get info
var dgp = client.database.getDynamicGlobalProperties();
var conf = client.database.getConfig();
var medianPrice = client.database.getCurrentMedianHistoryPrice();
var rewardFund = client.database.call('get_reward_fund', ['post']);
var account = client.database.getAccounts(accountsToQuery);
// wait until we have responses for all the API calls.
Promise.all([dgp, conf, medianPrice, rewardFund, account])
.then(([dgpResp, cfgResp, medianPriceResp, rewardFundResp, accountResp]) => {
// biggest vote size = (# of seconds in 1 day) /
// (steady state votes per day)*(time to regen 100% of mana)
// should be 2% with current values.
var fractionForMaxVote = (60 * 60 * 24) /
(dgpResp.vote_power_reserve_rate *
cfgResp.STEEM_VOTING_MANA_REGENERATION_SECONDS);
// constant for "convergent linear" rewards curve
var curveConstant = parseInt(rewardFundResp.content_constant);
// calculate how much 1 rshare of the reward pool is worth.
var steemInRewardPool = parseFloat(rewardFundResp.reward_balance.split(" ")[0]);
var rsharesInRewardPool = parseInt(rewardFundResp.recent_claims);
var steemPerRshare = steemInRewardPool / rsharesInRewardPool;
// calculate "current" price of Steem
var sbdInPrice = parseFloat(medianPriceResp.base.amount);
var steemInPrice = parseFloat(medianPriceResp.quote.amount);
// Price is num of SBDs for 1 steem. N SBDs = M STEEMs, divide both sides by M
var currentMedianPrice = sbdInPrice / steemInPrice;
// get information about specific accounts
accountResp.forEach((acct) => {
var effVests = parseFloat(acct.vesting_shares.split(" ")[0])
- parseFloat(acct.delegated_vesting_shares.split(" ")[0])
+ parseFloat(acct.received_vesting_shares.split(" ")[0]);
var maxMana = effVests * 1000000; // mana is on the scale of microvests
var curTime = Math.floor(Date.now()/1000); // seconds since the epoch
var secondsSinceManaUpdate = curTime - acct.voting_manabar.last_update_time;
var manaGrowthRate = maxMana / cfgResp.STEEM_VOTING_MANA_REGENERATION_SECONDS;
// can't gain more mana than your max.
var curMana = Math.min(maxMana, parseInt(acct.voting_manabar.current_mana)
+ secondsSinceManaUpdate*manaGrowthRate);
// put this info onto the list we'll present as a table.
creatures.push({name: acct.name, vests:effVests, power:curMana/maxMana});
});
// output out a markdown table header
console.log("| Name | Vests | SP | Mana % | Linear vote | Curved vote |");
console.log("|:-----|------:|---:|-------:|------------:|------------:|");
creatures.forEach((item, i) => {
// convert vests into SteemPower
var spPerVest = parseFloat(dgpResp.total_vesting_fund_steem.split(" ")[0]) /
parseFloat(dgpResp.total_vesting_shares.split(" ")[0]);
var sp = Math.floor(item.vests*spPerVest);
// rshares is based on microVests, 1,000,000 microVests per vest
var rShares = item.vests * 1000000 * fractionForMaxVote;
// apply "convergent linear" reward curve.
var convergentRshares = ((rShares + curveConstant)**2 - (curveConstant**2)) /
(rShares + 4*curveConstant);
var linearVote = rShares * steemPerRshare * currentMedianPrice;
var curvedVote = convergentRshares * steemPerRshare * currentMedianPrice;
// output a markdown table row
console.log("|", item.name, "|", Math.floor(item.vests), "|", sp,
"|", (item.power*100).toFixed(2)+"%",
"|", "$"+linearVote.toFixed(3), "|", "$"+curvedVote.toFixed(3), "|");
});
});
And here's the output of the script:
Name | Vests | SP | Mana % | Linear vote | Curved vote |
---|---|---|---|---|---|
Redfish | 0 | 0 | 100.00% | $0.000 | $0.000 |
Minnow | 1000000 | 555 | 100.00% | $0.008 | $0.004 |
Dolphin | 10000000 | 5553 | 100.00% | $0.078 | $0.040 |
Orca | 100000000 | 55533 | 100.00% | $0.777 | $0.466 |
Whale | 1000000000 | 555332 | 100.00% | $7.766 | $6.656 |
danmaruschak | 13133576 | 7293 | 99.73% | $0.102 | $0.053 |
steemcurator01 | 20012089237 | 11113357 | 97.98% | $155.404 | $153.882 |