Skip to content

Commit

Permalink
Inflation chart fix (#1417)
Browse files Browse the repository at this point in the history
* Inflation chart fix

* Current inflation calculation and cleanup

* Minor bug fixes

* Division by zero fix
  • Loading branch information
bobo-k2 authored Nov 28, 2024
1 parent c654da4 commit bbbde12
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 129 deletions.
28 changes: 14 additions & 14 deletions src/components/dashboard/InflationRateChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
</div>
<div class="row chart--value">
<div>
<span class="text--value text-color--neon">{{ estimatedInflation?.toFixed(1) }}%</span>
<span class="text--value text-color--neon">{{ estimatedInflation?.toFixed(2) }}%</span>
</div>
</div>
<div class="chart">
Expand All @@ -22,7 +22,7 @@
</template>

<script lang="ts">
import { defineComponent, computed, ref, watch, onMounted } from 'vue';
import { defineComponent, computed, ref, watch } from 'vue';
import { Chart } from 'highcharts-vue';
import { useStore } from 'src/store';
import { titleFormatter, seriesFormatter } from 'src/modules/token-api';
Expand Down Expand Up @@ -150,18 +150,18 @@ export default defineComponent({
chartOptions.value.xAxis.labels.style.color = getTextColor();
});
watch([maximumInflationData], () => {
if (maximumInflationData.value && maximumInflationData.value.length > 0) {
hasData.value = true;
chartOptions.value.series[0].data = maximumInflationData.value;
} else {
hasData.value = false;
}
});
onMounted(() => {
estimateRealizedInflation();
});
watch(
[maximumInflationData],
() => {
if (maximumInflationData.value && maximumInflationData.value.length > 0) {
hasData.value = true;
chartOptions.value.series[0].data = maximumInflationData.value;
} else {
hasData.value = false;
}
},
{ immediate: true }
);
return {
estimatedInflation,
Expand Down
168 changes: 60 additions & 108 deletions src/hooks/useInflation.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { computed, watch, ref, Ref, ComputedRef } from 'vue';
import { computed, ref, Ref, ComputedRef, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStore } from 'src/store';
import { container } from 'src/v2/common';
import {
BurnEvent,
IBalancesRepository,
IInflationRepository,
ITokenApiRepository,
Expand All @@ -28,15 +27,20 @@ type UseInflation = {
estimateRealizedInflation: () => Promise<void>;
};

const estimatedInflation = ref<number | undefined>(undefined);
const maximumInflationData = ref<[number, number][]>([]);
const realizedInflationData = ref<[number, number][]>([]);
const realizedAdjustableStakersPart = ref<number>(0);

export function useInflation(): UseInflation {
const store = useStore();
const { t } = useI18n();
const { eraLengths, currentEraInfo } = useDappStaking();
const { networkNameSubstrate } = useNetworkInfo();
const estimatedInflation = ref<number | undefined>(undefined);
const maximumInflationData = ref<[number, number][]>([]);
const realizedInflationData = ref<[number, number][]>([]);
const realizedAdjustableStakersPart = ref<number>(0);
// const estimatedInflation = ref<number | undefined>(undefined);
// const maximumInflationData = ref<[number, number][]>([]);
// const realizedInflationData = ref<[number, number][]>([]);
// const realizedAdjustableStakersPart = ref<number>(0);

const activeInflationConfiguration = computed<InflationConfiguration>(
() => store.getters['general/getActiveInflationConfiguration']
Expand All @@ -63,23 +67,6 @@ export function useInflation(): UseInflation {
return await inflationRepository.getInflationParams();
};

const getBurnEvents = async (): Promise<BurnEvent[]> => {
// Ignore burn events with less than 1M ASTAR. They are not impacting charts a lot small burn amounts
// could be a spam.
const minBurn = BigInt('1000000000000000000000000');
const tokenApiRepository = container.get<ITokenApiRepository>(Symbols.TokenApiRepository);
const burnEvents = await tokenApiRepository.getBurnEvents(
networkNameSubstrate.value.toLowerCase()
);

return burnEvents.filter((item) => item.amount >= minBurn);
};

/**
* Estimates the realized inflation rate percentage based on the actual total issuance at the beginning
* and estimated total issuance at the end of the current cycle.
* According to the https://github.com/AstarNetwork/astar-apps/issues/1259
*/
const estimateRealizedInflation = async (): Promise<void> => {
let inflation: number | undefined;

Expand All @@ -97,25 +84,6 @@ export function useInflation(): UseInflation {
const initialTotalIssuance = await balancesRepository.getTotalIssuance(period1StartBlock - 1); // -
const realizedTotalIssuance = await balancesRepository.getTotalIssuance();

const burnEvents = await getBurnEvents();
// Add first and last block so charts can be easily drawn.
burnEvents.splice(0, 0, {
blockNumber: period1StartBlock,
amount: BigInt(0),
user: '',
timestamp: 0,
});
burnEvents.push({
blockNumber: currentBlock.value,
amount: BigInt(0),
user: '',
timestamp: 0,
});

const totalBurn = burnEvents.reduce((acc, item) => acc + item.amount, BigInt(0));
// Used to calculate inflation rate.
const initialTotalIssuanceWithoutBurn = initialTotalIssuance - totalBurn;

const {
periodsPerCycle,
standardEraLength,
Expand All @@ -126,25 +94,13 @@ export function useInflation(): UseInflation {
standardEraLength *
periodsPerCycle *
(standardErasPerBuildAndEarnPeriod + standardErasPerVotingPeriod);
const blockDifference = BigInt(currentBlock.value - period1StartBlock);
const slope =
BigInt((realizedTotalIssuance - initialTotalIssuanceWithoutBurn).toString()) /
blockDifference;

// Estimate total issuance at the end of the current cycle.
const endOfCycleBlock = period1StartBlock + cycleLengthInBlocks;
const endOfCycleTotalIssuance = Number(
ethers.utils.formatEther(
slope * BigInt(endOfCycleBlock - period1StartBlock) + initialTotalIssuanceWithoutBurn
)
);
const endOfCycleBlock = Math.max(period1StartBlock + cycleLengthInBlocks, currentBlock.value);

// Estimated inflation at the end of the current cycle.
// Calculate realized inflation.
inflation =
(100 *
(endOfCycleTotalIssuance -
Number(ethers.utils.formatEther(initialTotalIssuanceWithoutBurn.toString())))) /
endOfCycleTotalIssuance;
100 * (Number(realizedTotalIssuance - initialTotalIssuance) / Number(initialTotalIssuance));

// Calculate maximum and realized inflation for each era in the cycle.
calculateMaximumInflationData(
Expand All @@ -154,16 +110,8 @@ export function useInflation(): UseInflation {
cycleLengthInBlocks,
inflationParameters.value?.maxInflationRate ?? 0,
eraLengths.value.standardEraLength,
burnEvents
);

calculateRealizedInflationData(
period1StartBlock,
currentBlock.value,
slope,
eraLengths.value.standardEraLength,
initialTotalIssuance,
burnEvents
eraLengths.value.standardErasPerVotingPeriod,
eraLengths.value.standardErasPerBuildAndEarnPeriod
);

calculateAdjustableStakerRewards(
Expand All @@ -172,6 +120,8 @@ export function useInflation(): UseInflation {
inflationParameters.value.adjustableStakersPart,
inflationParameters.value.idealStakingRate
);

fetchRealizedInflationData(currentBlock.value, realizedTotalIssuance);
} catch (error) {
console.error('Error calculating realized inflation', error);
}
Expand All @@ -185,60 +135,53 @@ export function useInflation(): UseInflation {
firstBlockIssuance: bigint,
cycleLengthInBlocks: number,
maxInflation: number,
eraLength: number,
burnEvents: BurnEvent[]
standardEraLength: number,
standardErasPerVotingPeriod: number,
standardErasPerBuildAndEarnPeriod: number
): void => {
const result: [number, number][] = [];
const inflation = BigInt(Math.floor(maxInflation * 100)) * BigInt('10000000000000000');
const cycleProgression = (firstBlockIssuance * inflation) / BigInt('1000000000000000000');
const cycleLength = BigInt(cycleLengthInBlocks);

// One sample per era.
for (let j = 0; j < burnEvents.length - 1; j++) {
for (
let i = burnEvents[j].blockNumber;
i <= burnEvents[j + 1].blockNumber + eraLength;
i += eraLength
) {
const inflation =
(cycleProgression * BigInt(i - firstBlock)) / cycleLength +
firstBlockIssuance -
burnEvents[j].amount;
// One sample per era (take into consideration that voting era takes multiple standard era lengths).
let era = 0;
const getEraLength = (era: number, block: number): number =>
era === 1 || era % (standardErasPerBuildAndEarnPeriod + 2) === 0
? standardEraLength * standardErasPerVotingPeriod
: standardEraLength;

result.push([i, Number(ethers.utils.formatEther(inflation.toString()))]);
}
for (let i = firstBlock - 1; i <= lastBlock; i += getEraLength(era, i)) {
const inflation =
(cycleProgression * BigInt(i - firstBlock)) / cycleLength + firstBlockIssuance;

result.push([i, Number(ethers.utils.formatEther(inflation.toString()))]);
era++;
}

maximumInflationData.value = result;
};

const calculateRealizedInflationData = (
firstBlock: number,
lastBlock: number,
slope: bigint,
eraLength: number,
firstBlockIssuance: bigint,
burnEvents: BurnEvent[]
): void => {
const result: [number, number][] = [];

for (let j = 0; j < burnEvents.length - 1; j++) {
for (
let i = burnEvents[j].blockNumber;
i <= burnEvents[j + 1].blockNumber + eraLength;
i += eraLength
) {
const currentBlockIssuance = Number(
ethers.utils.formatEther(
slope * BigInt(i - firstBlock) + firstBlockIssuance - burnEvents[j].amount
)
);

result.push([i, currentBlockIssuance]);
}
}
const fetchRealizedInflationData = async (
currentBlock: number,
totalIssuance: bigint
): Promise<void> => {
const tokenApiRepository = container.get<ITokenApiRepository>(Symbols.TokenApiRepository);
const issuanceHistory = await tokenApiRepository.getTokeIssuanceHistory(
networkNameSubstrate.value.toLowerCase()
);

realizedInflationData.value = result;
// Current issuance is not included in the history yet.
issuanceHistory.push({
block: currentBlock,
timestamp: Date.now(),
balance: totalIssuance,
});

realizedInflationData.value = issuanceHistory.map((item) => [
item.block,
Number(ethers.utils.formatEther(item.balance.toString())),
]);
};

const calculateAdjustableStakerRewards = (
Expand All @@ -256,6 +199,15 @@ export function useInflation(): UseInflation {
realizedAdjustableStakersPart.value = Number(result.toFixed(3));
};

watch(eraLengths, async () => {
if (
(eraLengths.value && maximumInflationData.value.length === 0) ||
realizedInflationData.value.length === 0
) {
estimateRealizedInflation();
}
});

return {
activeInflationConfiguration: activeInflationConfiguration,
estimatedInflation,
Expand Down
6 changes: 1 addition & 5 deletions src/staking-v3/components/data/DataList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@
</template>

<script lang="ts">
import { defineComponent, computed, onMounted } from 'vue';
import { defineComponent, computed } from 'vue';
import { useDataCalculations } from 'src/staking-v3/hooks';
import DataCard from './DataCard.vue';
import { useDappStaking, useDapps, usePeriod } from 'src/staking-v3/hooks';
Expand Down Expand Up @@ -183,10 +183,6 @@ export default defineComponent({
estimatedInflation.value ? `${estimatedInflation.value.toFixed(2)} %` : '--'
);
onMounted(() => {
estimateRealizedInflation();
});
return {
protocolState,
periodName,
Expand Down
12 changes: 12 additions & 0 deletions src/v2/repositories/ITokenApiRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ export type BurnEvent = {
user: string;
};

export type TokenIssuance = {
block: number;
timestamp: number;
balance: bigint;
};

/**
* Definition of repository for access token price.
*/
Expand All @@ -33,4 +39,10 @@ export interface ITokenApiRepository {
* @param network Network name.
*/
getBurnEvents(network: string): Promise<BurnEvent[]>;

/**
* Gets token issuance history (one sample per dApp staking era) for the given network.
* @param network Network name.
*/
getTokeIssuanceHistory(network: string): Promise<TokenIssuance[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { ExtrinsicPayload, IApi } from 'src/v2/integration';
import { IdentityData } from 'src/v2/models';
import { IAccountUnificationRepository, IIdentityRepository } from 'src/v2/repositories';
import { Symbols } from 'src/v2/symbols';
import e from 'express';

@injectable()
export class AccountUnificationRepository implements IAccountUnificationRepository {
Expand Down
12 changes: 11 additions & 1 deletion src/v2/repositories/implementations/TokenApiRepository.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import axios from 'axios';
import { injectable } from 'inversify';
import { BurnEvent, ITokenApiRepository, PeriodData } from '../ITokenApiRepository';
import { BurnEvent, ITokenApiRepository, PeriodData, TokenIssuance } from '../ITokenApiRepository';

@injectable()
export class TokenApiRepository implements ITokenApiRepository {
Expand Down Expand Up @@ -48,4 +48,14 @@ export class TokenApiRepository implements ITokenApiRepository {
return [];
}
}

public async getTokeIssuanceHistory(network: string): Promise<TokenIssuance[]> {
try {
const url = `${TokenApiRepository.BaseUrl}/v1/${network}/token/supply-history`;
const response = await axios.get<TokenIssuance[]>(url);
return response.data;
} catch (error) {
return [];
}
}
}

0 comments on commit bbbde12

Please sign in to comment.