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

feat: veyfi apr #406

Merged
merged 3 commits into from
Oct 25, 2023
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
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
4 changes: 2 additions & 2 deletions apps/veyfi/contexts/useGauge.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, {createContext, memo, useContext, useState} from 'react';
import React, {createContext, memo, useCallback, useContext, useState} from 'react';
import {FixedNumber} from 'ethers';
import {useDeepCompareMemo} from '@react-hookz/web';
import {VEYFI_GAUGE_ABI} from '@veYFI/utils/abi/veYFIGauge.abi';
Expand Down Expand Up @@ -145,7 +145,7 @@ export const GaugeContextApp = memo(function GaugeContextApp({children}: {childr
set_positionsMap(allPositionsAsMap);
}, [address, gauges, isActive]);

const refresh = useAsyncTrigger(async (): Promise<void> => {
const refresh = useCallback(async (): Promise<void> => {
refreshVotingEscrow();
refreshPositions();
}, [refreshPositions, refreshVotingEscrow]);
Expand Down
2 changes: 1 addition & 1 deletion apps/veyfi/contexts/useOption.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export const OptionContextApp = memo(function OptionContextApp({children}: {chil
set_position(toNormalizedBN(dYFIBalance));
}, [userAddress]);

const refresh = useAsyncTrigger(async (): Promise<void> => {
const refresh = useCallback(async (): Promise<void> => {
refreshPrice();
refreshPositions();
}, [refreshPrice, refreshPositions]);
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
Loading