Skip to content

Commit

Permalink
Merge pull request #596 from oasisprotocol/lw/post-damask-estimates
Browse files Browse the repository at this point in the history
Better estimate staking rewards after Damask
  • Loading branch information
lukaw3d authored Nov 9, 2023
2 parents 5f6964e + d92165a commit c9e12d0
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 260 deletions.
62 changes: 41 additions & 21 deletions docs/general/oasis-network/staking_rewards/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import data from './data.json';
const knownOffsets = {
beta: { epoch: 0, date: '2020-10-01T16:00:00Z' },
mainnet: { epoch: 1170, date: '2020-11-18T16:00:00.000Z' },
recent: { epoch: 15835, date: '2022-07-20T16:11:11.000Z' },
epoch15835: { epoch: 15835, date: '2022-07-20T16:11:11.000Z' },
epoch18653: { epoch: 18653, date: '2022-11-13T13:33:25.000Z' },

// epoch from https://oasismonitor.com/block/4000000
// time from https://www.oasisscan.com/blocks/4000000
Expand All @@ -18,44 +19,56 @@ const knownOffsets = {
block6M: { epoch: 9992, date: '2021-11-19T23:18:41.000Z' },
block7M: { epoch: 11656, date: '2022-01-28T05:06:25.000Z' },
block8M: { epoch: 13320, date: '2022-04-07T22:49:44.000Z' },
// Damask Upgrade was at https://api.oasisscan.com/mainnet/chain/block/8048956
block9M: { epoch: 14987, date: '2022-06-15T16:22:57.000Z' },
block10M: { epoch: 16653, date: '2022-08-23T09:29:12.000Z' },
block11M: { epoch: 18320, date: '2022-10-30T22:41:53.000Z' },
block12M: { epoch: 19987, date: '2023-01-07T03:06:08.000Z' },
block13M: { epoch: 21653, date: '2023-03-16T05:47:42.000Z' },
block14M: { epoch: 23320, date: '2023-05-22T22:04:26.000Z' },
block15M: { epoch: 24987, date: '2023-07-29T13:10:58.000Z' },
block16M: { epoch: 26653, date: '2023-10-05T04:13:33.000Z' },

recent: { epoch: 27320, date: '2023-11-01T03:48:29.000Z' },
};
const offsetFrom = knownOffsets.recent;

const estimatedEpochsPerHour = (knownOffsets.recent.epoch - knownOffsets.mainnet.epoch) / ((new Date(knownOffsets.recent.date).getTime() - new Date(knownOffsets.mainnet.date).getTime()) / 1000 / 60 / 60);
const estimatedEpochsPerYear = 365 * 24 * estimatedEpochsPerHour;
// Estimate up to epoch 15835 instead of Damask for consistency with how we made
// the first iteration of estimations. And it mostly covers pre-Damask epochs.
// Switch between estimates at steeper curve at epoch 18653.
const estimatedEpochsPerHourPreDamask = (knownOffsets.epoch15835.epoch - knownOffsets.mainnet.epoch) / ((new Date(knownOffsets.epoch15835.date).getTime() - new Date(knownOffsets.mainnet.date).getTime()) / 1000 / 60 / 60);
const estimatedEpochsPerHourAfterDamask = (knownOffsets.recent.epoch - knownOffsets.block12M.epoch) / ((new Date(knownOffsets.recent.date).getTime() - new Date(knownOffsets.block12M.date).getTime()) / 1000 / 60 / 60);

// From genesis "reward_factor_epoch_signed": "1"
const rewardFactorEpochSigned = 1;
// https://github.com/oasisprotocol/oasis-core/blob/d8e352b/go/staking/api/rewards.go#L22
const rewardAmountDenominator = 100_000_000;

const estimateEpochDates = (epochOffsets: number[]) => {
return epochOffsets
.map(epochOffset => {
const timeOffset = (epochOffset - offsetFrom.epoch) / estimatedEpochsPerHour * 60 * 60 * 1000;
const date = new Date(new Date(offsetFrom.date).getTime() + timeOffset);
return date.toISOString();
});
};
const estimateEpochDatesAndAPY = (data: Array<{ untilEpoch: number, scale: number }>) => {
return data
.map(({ untilEpoch, scale }) => {
const estimatedEpochsPerHour = untilEpoch > knownOffsets.epoch18653.epoch ? estimatedEpochsPerHourAfterDamask : estimatedEpochsPerHourPreDamask
const offsetFrom = untilEpoch > knownOffsets.epoch18653.epoch ? knownOffsets.recent : knownOffsets.epoch15835;

const estimateAPY = (scaleRewards: number[]) => {
return scaleRewards
.map(scale => {
const timeOffset = (untilEpoch - offsetFrom.epoch) / estimatedEpochsPerHour * 60 * 60 * 1000;
const date = new Date(new Date(offsetFrom.date).getTime() + timeOffset).toISOString();

const estimatedEpochsPerYear = 365 * 24 * estimatedEpochsPerHour;
const rewardPerEpoch = scale * rewardFactorEpochSigned / rewardAmountDenominator;
// Better numerical precision than yearlyCompoundedRate = (1 + rewardPerEpoch)**estimatedEpochsPerYear - 1
const yearlyCompoundedRate = Math.expm1(Math.log1p(rewardPerEpoch) * estimatedEpochsPerYear)
return yearlyCompoundedRate;

return { untilEpoch, scale, date, rewardPerEpoch, yearlyCompoundedRate };
});
};


const StakingRewardsChart = () => {
const estimates = estimateEpochDatesAndAPY(data)
const chart: PlotlyDataLayoutConfig = {
data: [
{
x: estimateEpochDates(data.map(d => d.untilEpoch)),
y: estimateAPY(data.map(d => d.scale)),
x: estimates.map(d => d.date),
y: estimates.map(d => d.yearlyCompoundedRate),
mode: 'lines',
name: 'Annualized rewards',
marker: {color: '#4285F4'},
Expand Down Expand Up @@ -118,6 +131,12 @@ const StakingRewardsChart = () => {
// const PlotlyBasic: typeof import('plotly.js-basic-dist') = require('plotly.js-basic-dist');
// PlotlyBasic.downloadImage(chart, { filename: 'fallback', format:'svg', width: 960, height: 600 });

// To generate data.csv:
// const csv = [
// ['Until epoch', 'Estimated date', 'Reward per epoch %', 'Estimated annualized rewards %'].join(','),
// ...estimates.map((d) => [d.untilEpoch, d.date, (d.rewardPerEpoch * 100).toFixed(6), (d.yearlyCompoundedRate * 100).toFixed(4)].join(','))
// ].join('\n')
// console.log(csv)
return <PlotlyChart label="Staking Rewards Schedule (time estimated from epoch interval)" chart={chart} fallbackSvg={FallbackSvg} />;
};

Expand All @@ -132,11 +151,12 @@ function testEstimatedEpochDates() {
maximumFractionDigits: 1,
});

console.info('DEV: estimatedEpochsPerHour', estimatedEpochsPerHour);
console.info('DEV: estimatedEpochsPerHourPreDamask', estimatedEpochsPerHourPreDamask);
console.info('DEV: estimatedEpochsPerHourAfterDamask', estimatedEpochsPerHourAfterDamask);
console.info(
'DEV: diff between estimateEpochDates and known dates',
'DEV: diff between estimates and known dates',
Object.entries(knownOffsets).map(([epochName, {epoch, date}]) => {
const estimatedTime = new Date(estimateEpochDates([epoch])[0]).getTime();
const estimatedTime = new Date(estimateEpochDatesAndAPY([{ untilEpoch: epoch, scale: 1 }])[0].date).getTime();
const indexedTime = new Date(date).getTime();
const diffHours = (indexedTime - estimatedTime) / 1000 / 60 / 60;
return `at ${epochName} (${epoch}): ${diffHoursFormatter.format(diffHours)}`;
Expand Down
Loading

0 comments on commit c9e12d0

Please sign in to comment.