Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User burn events in inflation charts #1357

Merged
merged 2 commits into from
Jul 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/components/dashboard/Inflation.vue
Original file line number Diff line number Diff line change
Expand Up @@ -88,12 +88,12 @@ export default defineComponent({
const numberFromPercentage = (value?: number): number | string =>
value !== undefined ? value * 100 : '--';

const adjustableStakersPercentage = computed<number>(() =>
Number(
(
realizedAdjustableStakersPart.value / inflationParameters.value.adjustableStakersPart
).toFixed(3)
)
const adjustableStakersPercentage = computed<number>(
() =>
Math.round(
(realizedAdjustableStakersPart.value / inflationParameters.value.adjustableStakersPart) *
100
) / 100
);

watch(
Expand Down
17 changes: 13 additions & 4 deletions src/components/dashboard/InflationRateChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
</template>

<script lang="ts">
import { defineComponent, computed, ref, watch } from 'vue';
import { defineComponent, computed, ref, watch, onMounted } from 'vue';
import { Chart } from 'highcharts-vue';
import { useStore } from 'src/store';
import { titleFormatter, seriesFormatter } from 'src/modules/token-api';
Expand All @@ -43,11 +43,16 @@ export default defineComponent({
const getTextColor = (): string => (isDarkTheme.value ? '#5F656F' : '#B1B7C1');
const hasData = ref<boolean>(false);
const { t } = useI18n();
const { maximumInflationData, realizedInflationData, estimatedInflation, inflationParameters } =
useInflation();
const {
maximumInflationData,
realizedInflationData,
estimatedInflation,
inflationParameters,
estimateRealizedInflation,
} = useInflation();

const maximumInflationRate = computed<string>(() =>
(inflationParameters.value.maxInflationRate * 100).toFixed(1)
((inflationParameters.value?.maxInflationRate ?? 0) * 100).toFixed(1)
);

Highcharts.setOptions({
Expand Down Expand Up @@ -154,6 +159,10 @@ export default defineComponent({
}
});

onMounted(() => {
estimateRealizedInflation();
});

return {
estimatedInflation,
chartOptions,
Expand Down
118 changes: 81 additions & 37 deletions src/hooks/useInflation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { computed, watch, ref, Ref, ComputedRef } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStore } from 'src/store';
import { container } from 'src/v2/common';
import { IBalancesRepository, IInflationRepository } from 'src/v2/repositories';
import {
BurnEvent,
IBalancesRepository,
IInflationRepository,
ITokenApiRepository,
} from 'src/v2/repositories';
import { Symbols } from 'src/v2/symbols';
import { InflationConfiguration } from 'src/v2/models';
import { InflationParam, useDappStaking } from 'src/staking-v3';
Expand All @@ -20,6 +25,7 @@ type UseInflation = {
fetchActiveConfigurationToStore: () => Promise<void>;
fetchInflationParamsToStore: () => Promise<void>;
getInflationParameters: () => Promise<InflationParam>;
estimateRealizedInflation: () => Promise<void>;
};

export function useInflation(): UseInflation {
Expand Down Expand Up @@ -57,12 +63,24 @@ 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<number | undefined> => {
const estimateRealizedInflation = async (): Promise<void> => {
let inflation: number | undefined;

try {
Expand All @@ -76,13 +94,28 @@ export function useInflation(): UseInflation {
}

const balancesRepository = container.get<IBalancesRepository>(Symbols.BalancesRepository);
const initialTotalIssuance =
(await balancesRepository.getTotalIssuance(period1StartBlock - 1)) -
(networkNameSubstrate.value.toLowerCase() === 'astar'
? BigInt('350000000000000000000000000')
: BigInt(0)); // Quick fox for token burning event. TODO make a proper solution
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 @@ -95,21 +128,22 @@ export function useInflation(): UseInflation {
(standardErasPerBuildAndEarnPeriod + standardErasPerVotingPeriod);
const blockDifference = BigInt(currentBlock.value - period1StartBlock);
const slope =
BigInt((realizedTotalIssuance - initialTotalIssuance).toString()) / blockDifference;
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) + initialTotalIssuance
slope * BigInt(endOfCycleBlock - period1StartBlock) + initialTotalIssuanceWithoutBurn
)
);

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

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

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

calculateAdjustableStakerRewards(
Expand All @@ -140,7 +176,7 @@ export function useInflation(): UseInflation {
console.error('Error calculating realized inflation', error);
}

return inflation;
estimatedInflation.value = inflation;
};

const calculateMaximumInflationData = (
Expand All @@ -149,22 +185,30 @@ export function useInflation(): UseInflation {
firstBlockIssuance: bigint,
cycleLengthInBlocks: number,
maxInflation: number,
eraLength: number
eraLength: number,
burnEvents: BurnEvent[]
): 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 i = firstBlock; i <= lastBlock; i += eraLength) {
const inflation =
(cycleProgression * BigInt(i - firstBlock)) / cycleLength + firstBlockIssuance;
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;

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

// console.log((result[result.length - 1][1] - result[0][1]) / result[result.length - 1][1]);
maximumInflationData.value = result;
};

Expand All @@ -173,16 +217,25 @@ export function useInflation(): UseInflation {
lastBlock: number,
slope: bigint,
eraLength: number,
firstBlockIssuance: bigint
firstBlockIssuance: bigint,
burnEvents: BurnEvent[]
): void => {
const result: [number, number][] = [];

for (let i = firstBlock; i <= lastBlock; i += eraLength) {
const currentBlockIssuance = Number(
ethers.utils.formatEther(slope * BigInt(i - firstBlock) + firstBlockIssuance)
);
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]);
result.push([i, currentBlockIssuance]);
}
}

realizedInflationData.value = result;
Expand All @@ -203,18 +256,8 @@ export function useInflation(): UseInflation {
realizedAdjustableStakersPart.value = Number(result.toFixed(3));
};

watch(
[activeInflationConfiguration, inflationParameters],
async () => {
if (activeInflationConfiguration.value && inflationParameters.value) {
estimatedInflation.value = await estimateRealizedInflation();
}
},
{ immediate: true }
);

return {
activeInflationConfiguration,
activeInflationConfiguration: activeInflationConfiguration,
estimatedInflation,
inflationParameters,
maximumInflationData,
Expand All @@ -223,5 +266,6 @@ export function useInflation(): UseInflation {
fetchActiveConfigurationToStore,
fetchInflationParamsToStore,
getInflationParameters,
estimateRealizedInflation,
};
}
13 changes: 13 additions & 0 deletions src/v2/repositories/ITokenApiRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ export type PeriodData = {
stakeAmount: bigint;
};

export type BurnEvent = {
blockNumber: number;
timestamp: number;
amount: bigint;
user: string;
};

/**
* Definition of repository for access token price.
*/
Expand All @@ -20,4 +27,10 @@ export interface ITokenApiRepository {
* @param period Period number.
*/
getStakingPeriodStatistics(network: string, period: number): Promise<PeriodData[]>;

/**
* Gets burn events for the given network.
* @param network Network name.
*/
getBurnEvents(network: string): Promise<BurnEvent[]>;
}
2 changes: 1 addition & 1 deletion src/v2/repositories/implementations/SubscanRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export class SubscanRepository implements ISubscanRepository {

const payload = {
module,
event: eventName,
event_id: eventName,
page,
row: pageSize,
};
Expand Down
19 changes: 18 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 { ITokenApiRepository, PeriodData } from '../ITokenApiRepository';
import { BurnEvent, ITokenApiRepository, PeriodData } from '../ITokenApiRepository';

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

public async getBurnEvents(network: string): Promise<BurnEvent[]> {
try {
const url = `${TokenApiRepository.BaseUrl}/v1/${network}/burn/events`;
const response = await axios.get<BurnEvent[]>(url);
return response.data.map((data) => {
return {
blockNumber: data.blockNumber,
timestamp: data.timestamp,
amount: BigInt(data.amount),
user: data.user,
};
});
} catch (error) {
return [];
}
}
}
Loading