From f96b1ed594e0d708795008cbf7e43c6a2183479c Mon Sep 17 00:00:00 2001 From: Chef Jerry <144641937+ChefJerry@users.noreply.github.com> Date: Thu, 28 Dec 2023 14:49:35 +0800 Subject: [PATCH] fix: update ve amount calc (#8673) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ## PR-Codex overview ### Detailed summary - Updated `LockCakeDataSet` component to display `veCakeAmount` with 2 decimal places. - Added `DebugTooltips` component to conditionally render tooltips based on environment variables. - Updated `getVeCakeAmount` function to use `MAX_VECAKE_LOCK_WEEKS` from config. - Updated `DataHeader` component to handle `BigNumber` values and display them with 2 decimal places. - Added `useVeCakeAmount` hook to calculate estimated `veCake` amount. - Added `useTargetUnlockTime` hook to calculate valid unlock time. - Updated `NotLockingCard` component to use `NewStakingDataSet` and removed `getVeCakeAmount` import. - Updated `TableRow` component to import `DebugTooltips` and use it instead of `Tooltips`. - Updated `LockedVeCakeStatus` component to use `getBalanceAmount` and display `balance` with 2 decimal places. - Updated `DualStakeTooltip` component to display `nativeBalance` and `proxyBalance` with 4 decimal places. - Added `useMaxUnlockTime` and `useMaxUnlockWeeks` hooks to calculate maximum unlock time and weeks. - Updated `LockWeeksForm` component to use `useMaxUnlockWeeks` hook and show maximum unlock weeks based on lock status. > The following files were skipped due to too many changes: `apps/web/src/views/CakeStaking/components/DataSet/NewStakingDataSet.tsx`, `apps/web/src/views/CakeStaking/components/DataSet/LockWeeksDataSet.tsx` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/web/src/utils/getVeCakeAmount.ts | 4 +- .../components/DataSet/DataBox.tsx | 5 ++- .../components/DataSet/LockCakeDataSet.tsx | 2 +- .../components/DataSet/LockWeeksDataSet.tsx | 26 ++++++------ .../components/DataSet/NewStakingDataSet.tsx | 26 ++++++++---- .../components/LockCake/NotLocking.tsx | 7 +--- .../CakeStaking/components/LockWeeksForm.tsx | 29 +++++++++++--- .../components/LockedVeCakeStatus.tsx | 11 ++--- .../views/CakeStaking/components/Tooltips.tsx | 9 +++++ .../CakeStaking/hooks/useMaxUnlockTime.ts | 40 +++++++++++++++++++ .../CakeStaking/hooks/useTargetUnlockTime.ts | 23 +++++++++++ .../CakeStaking/hooks/useVeCakeAmount.ts | 12 ++++++ .../components/Table/VoteTable/TableRow.tsx | 7 ++-- 13 files changed, 156 insertions(+), 45 deletions(-) create mode 100644 apps/web/src/views/CakeStaking/hooks/useMaxUnlockTime.ts create mode 100644 apps/web/src/views/CakeStaking/hooks/useTargetUnlockTime.ts create mode 100644 apps/web/src/views/CakeStaking/hooks/useVeCakeAmount.ts diff --git a/apps/web/src/utils/getVeCakeAmount.ts b/apps/web/src/utils/getVeCakeAmount.ts index ea9341aff36f4..5ed8e7626fece 100644 --- a/apps/web/src/utils/getVeCakeAmount.ts +++ b/apps/web/src/utils/getVeCakeAmount.ts @@ -1,9 +1,9 @@ -import { MAX_VECAKE_LOCK_WEEKS, WEEK } from 'config/constants/veCake' import BN from 'bignumber.js' +import { MAX_VECAKE_LOCK_WEEKS, WEEK } from 'config/constants/veCake' export const getVeCakeAmount = (cakeToLocked: number | bigint | string, seconds: number | string): number => { return new BN(String(cakeToLocked || 0)) .times(seconds || 0) - .div(MAX_VECAKE_LOCK_WEEKS * WEEK) + .div((MAX_VECAKE_LOCK_WEEKS + 1) * WEEK - 1) .toNumber() } diff --git a/apps/web/src/views/CakeStaking/components/DataSet/DataBox.tsx b/apps/web/src/views/CakeStaking/components/DataSet/DataBox.tsx index c06ff923a451b..ea726d40838c4 100644 --- a/apps/web/src/views/CakeStaking/components/DataSet/DataBox.tsx +++ b/apps/web/src/views/CakeStaking/components/DataSet/DataBox.tsx @@ -1,4 +1,5 @@ import { AutoRow, Flex, FlexGap, Text } from '@pancakeswap/uikit' +import BigNumber from 'bignumber.js' import styled from 'styled-components' export const DataBox = styled(AutoRow)` @@ -26,7 +27,7 @@ export const DataRow: React.FC<{ } export const DataHeader: React.FC<{ - value?: string + value?: BigNumber }> = ({ value }) => { return ( - {value} + {value?.lt(0.1) ? value.sd(2).toString() : value?.toFixed(2)} } /> diff --git a/apps/web/src/views/CakeStaking/components/DataSet/LockCakeDataSet.tsx b/apps/web/src/views/CakeStaking/components/DataSet/LockCakeDataSet.tsx index 22a189a9a20d2..f9c95f5bf5499 100644 --- a/apps/web/src/views/CakeStaking/components/DataSet/LockCakeDataSet.tsx +++ b/apps/web/src/views/CakeStaking/components/DataSet/LockCakeDataSet.tsx @@ -34,7 +34,7 @@ export const LockCakeDataSet = () => { return ( - + { const { cakeLockWeeks } = useLockCakeData() const { cakeLockExpired, cakeUnlockTime, nativeCakeLockedAmount } = useCakeLockStatus() const { balance: proxyVeCakeBalance } = useProxyVeCakeBalance() - const currentTimestamp = useCurrentBlockTimestamp() + const unlockTimestamp = useTargetUnlockTime( + Number(cakeLockWeeks) * WEEK, + cakeLockExpired ? undefined : Number(cakeUnlockTime), + ) + const veCakeAmountFromNative = useVeCakeAmount(nativeCakeLockedAmount.toString(), unlockTimestamp) const veCakeAmountFromNativeBN = useMemo(() => { - let duration = cakeUnlockTime - currentTimestamp + Number(cakeLockWeeks || 0) * WEEK - if (duration > MAX_VECAKE_LOCK_WEEKS * WEEK) duration = MAX_VECAKE_LOCK_WEEKS * WEEK - return new BN(getVeCakeAmount(nativeCakeLockedAmount.toString(), duration)) - }, [cakeLockWeeks, cakeUnlockTime, currentTimestamp, nativeCakeLockedAmount]) + return new BN(veCakeAmountFromNative) + }, [veCakeAmountFromNative]) const veCakeAmountBN = useMemo(() => { return proxyVeCakeBalance.plus(veCakeAmountFromNativeBN) @@ -36,14 +37,13 @@ export const LockWeeksDataSet = () => { ? `${veCakeAmountFromNativeBN.div(nativeCakeLockedAmount.toString()).toPrecision(2)}x` : '0.00x' - const newUnlockTimestamp = useRoundedUnlockTimestamp(cakeLockExpired ? undefined : Number(cakeUnlockTime)) const newUnlockTime = useMemo(() => { - return formatDate(dayjs.unix(Number(newUnlockTimestamp))) - }, [newUnlockTimestamp]) + return formatDate(dayjs.unix(Number(unlockTimestamp))) + }, [unlockTimestamp]) return ( - + = ({ veCakeAmount = 0, cakeAmount = 0 }) => { +}> = ({ cakeAmount = 0 }) => { const { t } = useTranslation() - const veCake = veCakeAmount ? getFullDisplayBalance(new BN(veCakeAmount), 0, 3) : '0' - const factor = veCakeAmount && veCakeAmount ? `${new BN(veCakeAmount).div(cakeAmount).toPrecision(2)}x` : '0x' const { cakeLockWeeks } = useLockCakeData() - const unlockTimestamp = useRoundedUnlockTimestamp() + const unlockTimestamp = useTargetUnlockTime(Number(cakeLockWeeks) * WEEK) + const cakeAmountBN = useMemo(() => getBalanceAmount(new BN(cakeAmount)).toString(), [cakeAmount]) + const veCakeAmountFromNative = useVeCakeAmount(cakeAmountBN, unlockTimestamp) + const { balance: proxyVeCakeBalance } = useProxyVeCakeBalance() + const veCakeAmount = useMemo( + () => proxyVeCakeBalance.plus(veCakeAmountFromNative), + [proxyVeCakeBalance, veCakeAmountFromNative], + ) + const veCake = veCakeAmount ? getFullDisplayBalance(new BN(veCakeAmount), 18, 3) : '0' + const factor = + veCakeAmountFromNative && veCakeAmountFromNative + ? `${new BN(veCakeAmountFromNative).div(cakeAmountBN).toPrecision(2)}x` + : '0x' const { isDesktop } = useMatchBreakpoints() const unlockOn = useMemo(() => { return formatDate(dayjs.unix(Number(unlockTimestamp))) diff --git a/apps/web/src/views/CakeStaking/components/LockCake/NotLocking.tsx b/apps/web/src/views/CakeStaking/components/LockCake/NotLocking.tsx index 628436fead5e3..bb18fa6ab75f8 100644 --- a/apps/web/src/views/CakeStaking/components/LockCake/NotLocking.tsx +++ b/apps/web/src/views/CakeStaking/components/LockCake/NotLocking.tsx @@ -1,11 +1,9 @@ import { useTranslation } from '@pancakeswap/localization' import { Box, Button, ColumnCenter, Grid, Heading, useMatchBreakpoints } from '@pancakeswap/uikit' import ConnectWalletButton from 'components/ConnectWalletButton' -import { WEEK } from 'config/constants/veCake' import useActiveWeb3React from 'hooks/useActiveWeb3React' import { useMemo } from 'react' import { useLockCakeData } from 'state/vecake/hooks' -import { getVeCakeAmount } from 'utils/getVeCakeAmount' import { useWriteApproveAndLockCallback } from 'views/CakeStaking/hooks/useContractWrite' import { NewStakingDataSet } from '../DataSet' import { LockCakeForm } from '../LockCakeForm' @@ -49,10 +47,7 @@ export const NotLockingCard = () => { - + {account ? ( + ) : null} )} diff --git a/apps/web/src/views/CakeStaking/components/LockedVeCakeStatus.tsx b/apps/web/src/views/CakeStaking/components/LockedVeCakeStatus.tsx index b66ee994386b7..ce1b8fcccc3fe 100644 --- a/apps/web/src/views/CakeStaking/components/LockedVeCakeStatus.tsx +++ b/apps/web/src/views/CakeStaking/components/LockedVeCakeStatus.tsx @@ -15,7 +15,7 @@ import { RowBetween, Text, } from '@pancakeswap/uikit' -import { formatBigInt, formatNumber, getBalanceNumber } from '@pancakeswap/utils/formatBalance' +import { formatBigInt, formatNumber, getBalanceAmount, getBalanceNumber } from '@pancakeswap/utils/formatBalance' import dayjs from 'dayjs' import relativeTime from 'dayjs/plugin/relativeTime' import { useCakePrice } from 'hooks/useCakePrice' @@ -53,13 +53,14 @@ export const LockedVeCakeStatus: React.FC<{ const { balance: proxyBalance } = useProxyVeCakeBalance() const balanceBN = useMemo(() => getBalanceNumber(balance), [balance]) const proxyCake = useMemo(() => getBalanceNumber(proxyBalance), [proxyBalance]) + const nativeCake = useMemo(() => getBalanceNumber(balance.minus(proxyBalance)), [balance, proxyBalance]) if (status === CakeLockStatus.NotLocked) return null const balanceText = balanceBN > 0 && balanceBN < 0.01 ? ( - {`< 0.01`} + {getBalanceAmount(balance).sd(2).toString()} ) : ( + ) : ( ) @@ -218,10 +219,10 @@ const DualStakeTooltip: React.FC<{
  • - {t('Native:')} {formatNumber(nativeBalance)} veCAKE + {t('Native:')} {formatNumber(nativeBalance, 2, 4)} veCAKE
  • - {t('Migrated:')} {formatNumber(proxyBalance)} veCAKE + {t('Migrated:')} {formatNumber(proxyBalance, 2, 4)} veCAKE

diff --git a/apps/web/src/views/CakeStaking/components/Tooltips.tsx b/apps/web/src/views/CakeStaking/components/Tooltips.tsx index db63712dca1fb..5a5f956fc3b1c 100644 --- a/apps/web/src/views/CakeStaking/components/Tooltips.tsx +++ b/apps/web/src/views/CakeStaking/components/Tooltips.tsx @@ -29,3 +29,12 @@ export const Tooltips: React.FC> = ({ ) } + +export const DebugTooltips: React.FC> = ({ ...props }) => { + return ( + + ) +} diff --git a/apps/web/src/views/CakeStaking/hooks/useMaxUnlockTime.ts b/apps/web/src/views/CakeStaking/hooks/useMaxUnlockTime.ts new file mode 100644 index 0000000000000..9098a4341fe60 --- /dev/null +++ b/apps/web/src/views/CakeStaking/hooks/useMaxUnlockTime.ts @@ -0,0 +1,40 @@ +import { MAX_VECAKE_LOCK_WEEKS, WEEK } from 'config/constants/veCake' +import dayjs from 'dayjs' +import { useMemo } from 'react' +import { useCurrentBlockTimestamp } from './useCurrentBlockTimestamp' + +export const useMaxUnlockTime = (durationSeconds: number, prevUnlockTimestamp: number = 0) => { + const currentBlockTimestamp = useCurrentBlockTimestamp() + const maxUnlockTimestamp = useMemo( + () => dayjs.unix(prevUnlockTimestamp ?? currentBlockTimestamp).add(MAX_VECAKE_LOCK_WEEKS, 'weeks'), + [currentBlockTimestamp, prevUnlockTimestamp], + ) + + return useMemo(() => { + // if not staked before, max unlock time is max weeks + if (prevUnlockTimestamp === 0) return maxUnlockTimestamp.unix() + + const currentWeeks = Math.floor(currentBlockTimestamp / WEEK) + const target = dayjs.unix(prevUnlockTimestamp).add(durationSeconds, 'seconds').unix() + + const prevWeeks = Math.floor(prevUnlockTimestamp / WEEK) + const leftWeeks = MAX_VECAKE_LOCK_WEEKS - (prevWeeks - currentWeeks) + + let targetWeeks = Math.floor(target / WEEK) + + if (targetWeeks - currentWeeks > leftWeeks) targetWeeks = currentWeeks + leftWeeks + return targetWeeks * WEEK + }, [currentBlockTimestamp, durationSeconds, maxUnlockTimestamp, prevUnlockTimestamp]) +} + +export const useMaxUnlockWeeks = (weeksToLock: number, prevUnlockTimestamp: number = 0) => { + const currentBlockTimestamp = useCurrentBlockTimestamp() + const maxUnlockTime = useMaxUnlockTime(weeksToLock * WEEK, prevUnlockTimestamp) + + return useMemo(() => { + if (!prevUnlockTimestamp) return MAX_VECAKE_LOCK_WEEKS + if (maxUnlockTime > currentBlockTimestamp) + return Math.floor(maxUnlockTime / WEEK) - Math.floor(currentBlockTimestamp / WEEK) + return 0 + }, [currentBlockTimestamp, maxUnlockTime, prevUnlockTimestamp]) +} diff --git a/apps/web/src/views/CakeStaking/hooks/useTargetUnlockTime.ts b/apps/web/src/views/CakeStaking/hooks/useTargetUnlockTime.ts new file mode 100644 index 0000000000000..78c1ed0d57de9 --- /dev/null +++ b/apps/web/src/views/CakeStaking/hooks/useTargetUnlockTime.ts @@ -0,0 +1,23 @@ +import { MAX_VECAKE_LOCK_WEEKS, WEEK } from 'config/constants/veCake' +import dayjs from 'dayjs' +import { useMemo } from 'react' +import { useCurrentBlockTimestamp } from './useCurrentBlockTimestamp' + +// return a valid unlock time(should be UTC Thursday 00:00 ), with given duration seconds +export const useTargetUnlockTime = (duration: number, startFromTimestamp?: number) => { + const currentBlockTimestamp = useCurrentBlockTimestamp() + + const maxUnlockTimestamp = useMemo( + () => dayjs.unix(startFromTimestamp ?? currentBlockTimestamp).add(MAX_VECAKE_LOCK_WEEKS, 'weeks'), + [currentBlockTimestamp, startFromTimestamp], + ) + + const target = useMemo(() => { + const end = dayjs.unix(startFromTimestamp ?? currentBlockTimestamp).add(duration, 'seconds') + return end.isBefore(maxUnlockTimestamp) ? end.unix() : maxUnlockTimestamp.unix() + }, [currentBlockTimestamp, duration, maxUnlockTimestamp, startFromTimestamp]) + + return useMemo(() => { + return Math.floor(target / WEEK) * WEEK + }, [target]) +} diff --git a/apps/web/src/views/CakeStaking/hooks/useVeCakeAmount.ts b/apps/web/src/views/CakeStaking/hooks/useVeCakeAmount.ts new file mode 100644 index 0000000000000..27481af1387c6 --- /dev/null +++ b/apps/web/src/views/CakeStaking/hooks/useVeCakeAmount.ts @@ -0,0 +1,12 @@ +import { useMemo } from 'react' +import { getVeCakeAmount } from 'utils/getVeCakeAmount' +import { useCurrentBlockTimestamp } from './useCurrentBlockTimestamp' + +// calculate estimated veCake amount with locked cake amount and duration +export const useVeCakeAmount = (lockedAmount: string | number | bigint, unlockTimestamp: number) => { + const currentBlockTimestamp = useCurrentBlockTimestamp() + return useMemo(() => { + if (currentBlockTimestamp > unlockTimestamp) return 0 + return getVeCakeAmount(lockedAmount, unlockTimestamp - currentBlockTimestamp) + }, [currentBlockTimestamp, lockedAmount, unlockTimestamp]) +} diff --git a/apps/web/src/views/GaugesVoting/components/Table/VoteTable/TableRow.tsx b/apps/web/src/views/GaugesVoting/components/Table/VoteTable/TableRow.tsx index 8074d92b89f43..5d71ef2660d12 100644 --- a/apps/web/src/views/GaugesVoting/components/Table/VoteTable/TableRow.tsx +++ b/apps/web/src/views/GaugesVoting/components/Table/VoteTable/TableRow.tsx @@ -4,7 +4,7 @@ import { Button, ChevronDownIcon, ChevronUpIcon, ErrorIcon, Flex, FlexGap, Grid, import dayjs from 'dayjs' import { useCallback, useMemo, useState } from 'react' import { stringify } from 'viem' -import { Tooltips } from 'views/CakeStaking/components/Tooltips' +import { DebugTooltips, Tooltips } from 'views/CakeStaking/components/Tooltips' import { useCurrentBlockTimestamp } from 'views/CakeStaking/hooks/useCurrentBlockTimestamp' import { useCakeLockStatus } from 'views/CakeStaking/hooks/useVeCakeUserInfo' import { useUserVote } from 'views/GaugesVoting/hooks/useUserVote' @@ -49,8 +49,7 @@ export const TableRow: React.FC = ({ data, vote = { ...DEFAULT_VOTE }, - {stringify( @@ -72,7 +71,7 @@ export const TableRow: React.FC = ({ data, vote = { ...DEFAULT_VOTE }, } > - + {data.pairName}