Skip to content

Commit

Permalink
feat: veyfi apr (#406)
Browse files Browse the repository at this point in the history
* feat: veyfi apr

* fix: adjust from

* fix: APR

---------

Co-authored-by: Majorfi <[email protected]>
  • Loading branch information
Majorfi and Majorfi authored Oct 25, 2023
1 parent 6a3844e commit c579657
Show file tree
Hide file tree
Showing 5 changed files with 194 additions and 26 deletions.
28 changes: 21 additions & 7 deletions apps/veyfi/components/RedeemTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,9 @@ export function RedeemTab(): ReactElement {
'Got dYFI, want YFI? You’ve come to the right place. Redeem dYFI for YFI by paying the redemption cost in ETH. Enjoy your cheap YFI anon.'
}
</p>
<b className={'mt-4 block'}>
<b
suppressHydrationWarning
className={'mt-4 block'}>
{`Current discount: ${formatAmount(Number(discount.normalized) * 100, 2, 2)}%`}
</b>
</div>
Expand All @@ -125,10 +127,14 @@ export function RedeemTab(): ReactElement {
error={redeemAmountError}
legend={
<div className={'flex flex-row justify-between'}>
<p className={'text-neutral-400'}>
<p
suppressHydrationWarning
className={'text-neutral-400'}>
{formatCounterValue(redeemAmount.normalized, dYFIPrice)}
</p>
<p className={'text-neutral-400'}>{`You have: ${formatAmount(
<p
suppressHydrationWarning
className={'text-neutral-400'}>{`You have: ${formatAmount(
dYFIBalance.normalized,
2,
6
Expand All @@ -142,13 +148,17 @@ export function RedeemTab(): ReactElement {
amount={ethRequired}
legend={
<div className={'flex flex-row justify-between'}>
<p className={'text-neutral-400'}>
<p
suppressHydrationWarning
className={'text-neutral-400'}>
{formatCounterValue(
ethRequired.normalized,
Number(ethBalance.price.normalized) ?? 0
)}
</p>
<p className={'text-neutral-400'}>{`You have: ${formatAmount(
<p
suppressHydrationWarning
className={'text-neutral-400'}>{`You have: ${formatAmount(
ethBalance.balance.normalized,
2,
6
Expand All @@ -163,10 +173,14 @@ export function RedeemTab(): ReactElement {
amount={redeemAmount}
legend={
<div className={'flex flex-row justify-between'}>
<p className={'text-neutral-400'}>
<p
suppressHydrationWarning
className={'text-neutral-400'}>
{formatCounterValue(redeemAmount.normalized, yfiPrice)}
</p>
<p className={'text-neutral-400'}>{`You have: ${formatAmount(
<p
suppressHydrationWarning
className={'text-neutral-400'}>{`You have: ${formatAmount(
yfiBalance.normalized,
2,
6
Expand Down
137 changes: 137 additions & 0 deletions apps/veyfi/hooks/useVeYFIAPR.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import {useMemo, useState} from 'react';
import {useContractRead} from 'wagmi';
import {VEYFI_ABI} from '@veYFI/utils/abi/veYFI.abi';
import {VEYFI_GAUGE_ABI} from '@veYFI/utils/abi/veYFIGauge.abi';
import {SECONDS_PER_YEAR, VE_YFI_GAUGES, VEYFI_CHAIN_ID} from '@veYFI/utils/constants';
import {readContracts} from '@wagmi/core';
import {toAddress} from '@yearn-finance/web-lib/utils/address';
import {VEYFI_ADDRESS, YFI_ADDRESS} from '@yearn-finance/web-lib/utils/constants';
import {decodeAsBigInt} from '@yearn-finance/web-lib/utils/decoder';
import {toBigInt, toNormalizedBN} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {getClient} from '@yearn-finance/web-lib/utils/wagmi/utils';
import {useAsyncTrigger} from '@common/hooks/useAsyncEffect';
import {useTokenPrice} from '@common/hooks/useTokenPrice';

import type {TAddress} from '@yearn-finance/web-lib/types';
import type {TNormalizedBN} from '@common/types/types';

type TUseVeYFIAPR = {
dYFIPrice: number;
};
function useVeYFIAPR({dYFIPrice}: TUseVeYFIAPR): number {
const [rate, set_rate] = useState<bigint>(0n);
const yfiPrice = useTokenPrice(YFI_ADDRESS);
const {data: veYFISupply} = useContractRead({
address: VEYFI_ADDRESS,
abi: VEYFI_ABI,
functionName: 'totalSupply',
chainId: VEYFI_CHAIN_ID
});

useAsyncTrigger(async (): Promise<void> => {
const publicClient = getClient(VEYFI_CHAIN_ID);
const rangeLimit = toBigInt(process.env.RANGE_LIMIT);
const currentBlockNumber = await publicClient.getBlockNumber();
const from = 18373500n;

const depositors: [{address: TAddress; gauge: TAddress; balance: TNormalizedBN}] = [] as any;
/* 🔵 - Yearn Finance **********************************************************************
** First we need to retrieve all the depositors in a gauge
******************************************************************************************/
for (let i = from; i < currentBlockNumber; i += rangeLimit) {
const logs = await publicClient.getLogs({
address: VE_YFI_GAUGES,
events: [
{
anonymous: false,
inputs: [
{indexed: true, internalType: 'address', name: 'caller', type: 'address'},
{indexed: true, internalType: 'address', name: 'owner', type: 'address'},
{indexed: false, internalType: 'uint256', name: 'assets', type: 'uint256'},
{indexed: false, internalType: 'uint256', name: 'shares', type: 'uint256'}
],
name: 'Deposit',
type: 'event'
}
],
fromBlock: i,
toBlock: i + rangeLimit
});
for (const log of logs) {
depositors.push({address: toAddress(log.args.owner), gauge: log.address, balance: toNormalizedBN(0)});
}
}

/* 🔵 - Yearn Finance **********************************************************************
** Then, for each one of theses depositors, we need to check the current boostedBalance.
******************************************************************************************/
const allDepositorsBalances = await readContracts({
contracts: depositors.map(({gauge, address}): any => ({
address: gauge,
abi: VEYFI_GAUGE_ABI,
chainId: VEYFI_CHAIN_ID,
functionName: 'boostedBalanceOf',
args: [address]
}))
});
for (let i = 0; i < depositors.length; i++) {
depositors[i].balance = toNormalizedBN(decodeAsBigInt(allDepositorsBalances[i]), 18);
}

// Remove duplicates (on address and gauge)
const seen = new Set();
const depositorsWithoutDuplicates = depositors.filter((depositor): boolean => {
const isDuplicate = seen.has(depositor.address + depositor.gauge);
seen.add(depositor.address + depositor.gauge);
return !isDuplicate;
});

// remove depositors with 0 balance
const depositorsWithBalance = depositorsWithoutDuplicates.filter(
(depositor): boolean => depositor.balance.raw > 0n
);

/* 🔵 - Yearn Finance **********************************************************************
** Then, for each gauge we need to know the totalSupply and the rewardRate
******************************************************************************************/
const calls = [];
for (const gauge of VE_YFI_GAUGES) {
calls.push({address: gauge, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'totalSupply'});
calls.push({address: gauge, abi: VEYFI_GAUGE_ABI, chainId: VEYFI_CHAIN_ID, functionName: 'rewardRate'});
}
const totalSupplyAndRewardRate = await readContracts({
contracts: calls
});

/* 🔵 - Yearn Finance **********************************************************************
** Then we can calculate the rate for each gauge
******************************************************************************************/
let rate = 0n;
let index = 0;
for (const gauge of VE_YFI_GAUGES) {
const supply = decodeAsBigInt(totalSupplyAndRewardRate[index++]);
const rewardRate = decodeAsBigInt(totalSupplyAndRewardRate[index++]);
let boosted = 0n;
for (const depositor of depositorsWithBalance) {
if (toAddress(depositor.gauge) === toAddress(gauge)) {
boosted += depositor.balance.raw;
}
}
rate += (rewardRate * (supply - boosted)) / supply;
}
set_rate(rate);
}, []);

const APR = useMemo((): number => {
return (
(Number(toNormalizedBN(rate).normalized) * SECONDS_PER_YEAR * dYFIPrice) /
Number(toNormalizedBN(toBigInt(veYFISupply)).normalized) /
yfiPrice
);
}, [rate, dYFIPrice, yfiPrice, veYFISupply]);
console.warn(APR);

return APR;
}

export {useVeYFIAPR};
2 changes: 2 additions & 0 deletions apps/veyfi/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const VEYFI_SUPPORTED_NETWORK = 1;
export const VEYFI_REGISTRY_ADDRESS: TAddress = toAddress(''); // TODO: update once deployed
export const VEYFI_OPTIONS_ADDRESS = toAddress('0x2fBa208E1B2106d40DaA472Cb7AE0c6C7EFc0224');
export const VEYFI_DYFI_ADDRESS = toAddress('0x41252E8691e964f7DE35156B68493bAb6797a275');
export const VEYFI_ADDRESS = toAddress('0x90c1f9220d90d3966FbeE24045EDd73E1d588aD5');
export const VEYFI_POSITION_HELPER_ADDRESS = toAddress('0x5A70cD937bA3Daec8188E937E243fFa43d6ECbe8');

export const SNAPSHOT_DELEGATE_REGISTRY_ADDRESS = toAddress('0x469788fE6E9E9681C6ebF3bF78e7Fd26Fc015446');
export const YEARN_SNAPSHOT_SPACE = 'veyfi.eth';
Expand Down
1 change: 1 addition & 0 deletions next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const config = {
PARTNER_ID_ADDRESS: '0xFEB4acf3df3cDEA7399794D0869ef76A6EfAff52',
SHOULD_USE_PARTNER_CONTRACT: true,
YDAEMON_BASE_URI: process.env.YDAEMON_BASE_URI,
RANGE_LIMIT: 1_000_000,

// YDAEMON_BASE_URI: 'https://ydaemon.ycorpo.com',
// YDAEMON_BASE_URI: 'http://localhost:8080',
Expand Down
52 changes: 33 additions & 19 deletions pages/veyfi/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import {RedeemTab} from '@veYFI/components/RedeemTab';
import {RewardsTab} from '@veYFI/components/RewardsTab';
import {TabManageGauges} from '@veYFI/components/TabManageGauges';
import {TabManageVeYFI} from '@veYFI/components/TabManageVeYFI';
import {useOption} from '@veYFI/contexts/useOption';
import {useVotingEscrow} from '@veYFI/contexts/useVotingEscrow';
import {useVeYFIAPR} from '@veYFI/hooks/useVeYFIAPR';
import {Wrapper} from '@veYFI/Wrapper';
import {formatToNormalizedValue, toBigInt} from '@yearn-finance/web-lib/utils/format.bigNumber';
import {formatAmount} from '@yearn-finance/web-lib/utils/format.number';
import {formatAmount, formatPercent} from '@yearn-finance/web-lib/utils/format.number';
import {PageProgressBar} from '@common/components/PageProgressBar';
import {SummaryData} from '@common/components/SummaryData';
import {Tabs} from '@common/components/Tabs';
Expand All @@ -14,11 +16,38 @@ import {formatDateShort} from '@common/utils';
import type {NextRouter} from 'next/router';
import type {ReactElement} from 'react';

function Index(): ReactElement {
const {votingEscrow, positions, isLoading} = useVotingEscrow();
function HeadingData(): ReactElement {
const {votingEscrow, positions} = useVotingEscrow();
const {dYFIPrice} = useOption();
const APR = useVeYFIAPR({dYFIPrice});

const totalLockedYFI = formatToNormalizedValue(toBigInt(votingEscrow?.supply), 18);
const yourLockedYFI = formatToNormalizedValue(toBigInt(positions?.deposit?.underlyingBalance), 18);
return (
<SummaryData
items={[
{
label: 'Total Locked YFI',
content: formatAmount(totalLockedYFI, 4) ?? '-'
},
{
label: 'Your Locked YFI',
content: formatAmount(yourLockedYFI, 4) ?? '-'
},
{
label: 'Expiration for the lock',
content: positions?.unlockTime ? formatDateShort(positions.unlockTime) : '-'
},
{
label: 'veYFI APR',
content: APR ? formatPercent(APR * 100) : '-'
}
]}
/>
);
}
function Index(): ReactElement {
const {isLoading} = useVotingEscrow();

const tabs = [
{id: 'gauges', label: 'Manage Gauges', content: <TabManageGauges />},
Expand All @@ -34,22 +63,7 @@ function Index(): ReactElement {
<h1 className={'w-full text-center text-8xl font-bold'}>{'veYFI'}</h1>

<div className={'my-14 w-full'}>
<SummaryData
items={[
{
label: 'Total Locked YFI',
content: formatAmount(totalLockedYFI, 4) ?? '-'
},
{
label: 'Your Locked YFI',
content: formatAmount(yourLockedYFI, 4) ?? '-'
},
{
label: 'Expiration for the lock',
content: positions?.unlockTime ? formatDateShort(positions.unlockTime) : '-'
}
]}
/>
<HeadingData />
</div>

<Tabs items={tabs} />
Expand Down

1 comment on commit c579657

@vercel
Copy link

@vercel vercel bot commented on c579657 Oct 25, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.