Skip to content

Commit

Permalink
fix: update ve amount calc (#8673)
Browse files Browse the repository at this point in the history
<!--
Before opening a pull request, please read the [contributing
guidelines](https://github.com/pancakeswap/pancake-frontend/blob/develop/CONTRIBUTING.md)
first
-->

<!-- start pr-codex -->

---

## 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}`

<!-- end pr-codex -->
  • Loading branch information
ChefJerry authored Dec 28, 2023
1 parent 5cf6af8 commit f96b1ed
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 45 deletions.
4 changes: 2 additions & 2 deletions apps/web/src/utils/getVeCakeAmount.ts
Original file line number Diff line number Diff line change
@@ -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()
}
5 changes: 3 additions & 2 deletions apps/web/src/views/CakeStaking/components/DataSet/DataBox.tsx
Original file line number Diff line number Diff line change
@@ -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)`
Expand Down Expand Up @@ -26,7 +27,7 @@ export const DataRow: React.FC<{
}

export const DataHeader: React.FC<{
value?: string
value?: BigNumber
}> = ({ value }) => {
return (
<DataRow
Expand All @@ -40,7 +41,7 @@ export const DataHeader: React.FC<{
}
value={
<Text fontSize="16px" bold>
{value}
{value?.lt(0.1) ? value.sd(2).toString() : value?.toFixed(2)}
</Text>
}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const LockCakeDataSet = () => {

return (
<DataBox gap="8px">
<DataHeader value={String(veCakeAmount.toFixed(2))} />
<DataHeader value={veCakeAmount} />
<DataRow label={t('CAKE to be locked')} value={amount.toFixed(2)} />
<DataRow
label={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import { useTranslation } from '@pancakeswap/localization'
import { FlexGap, Text, TooltipText } from '@pancakeswap/uikit'
import { getBalanceAmount } from '@pancakeswap/utils/formatBalance'
import BN from 'bignumber.js'
import { MAX_VECAKE_LOCK_WEEKS, WEEK } from 'config/constants/veCake'
import { WEEK } from 'config/constants/veCake'
import dayjs from 'dayjs'
import { useMemo } from 'react'
import { useLockCakeData } from 'state/vecake/hooks'
import { getVeCakeAmount } from 'utils/getVeCakeAmount'
import { useCurrentBlockTimestamp } from 'views/CakeStaking/hooks/useCurrentBlockTimestamp'
import { useProxyVeCakeBalance } from 'views/CakeStaking/hooks/useProxyVeCakeBalance'
import { useRoundedUnlockTimestamp } from 'views/CakeStaking/hooks/useRoundedUnlockTimestamp'
import { useTargetUnlockTime } from 'views/CakeStaking/hooks/useTargetUnlockTime'
import { useVeCakeAmount } from 'views/CakeStaking/hooks/useVeCakeAmount'
import { useCakeLockStatus } from 'views/CakeStaking/hooks/useVeCakeUserInfo'
import { Tooltips } from '../Tooltips'
import { DataBox, DataHeader, DataRow } from './DataBox'
Expand All @@ -20,12 +19,14 @@ export const LockWeeksDataSet = () => {
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)
Expand All @@ -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 (
<DataBox gap="8px">
<DataHeader value={String(getBalanceAmount(veCakeAmountBN).toFixed(2))} />
<DataHeader value={getBalanceAmount(veCakeAmountBN)} />
<DataRow
label={
<Tooltips
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useTranslation } from '@pancakeswap/localization'
import { AutoRow, Box, Text, TooltipText, useMatchBreakpoints } from '@pancakeswap/uikit'
import { getFullDisplayBalance } from '@pancakeswap/utils/formatBalance'
import { getBalanceAmount, getFullDisplayBalance } from '@pancakeswap/utils/formatBalance'
import BN from 'bignumber.js'
import { WEEK } from 'config/constants/veCake'
import dayjs from 'dayjs'
import React, { useMemo } from 'react'
import { useLockCakeData } from 'state/vecake/hooks'
import styled from 'styled-components'
import { useRoundedUnlockTimestamp } from 'views/CakeStaking/hooks/useRoundedUnlockTimestamp'
import { useProxyVeCakeBalance } from 'views/CakeStaking/hooks/useProxyVeCakeBalance'
import { useTargetUnlockTime } from 'views/CakeStaking/hooks/useTargetUnlockTime'
import { useVeCakeAmount } from 'views/CakeStaking/hooks/useVeCakeAmount'
import { MyVeCakeCard } from '../MyVeCakeCard'
import { Tooltips } from '../Tooltips'
import { DataRow } from './DataBox'
Expand All @@ -18,14 +21,23 @@ const ValueText = styled(Text)`
`

export const NewStakingDataSet: React.FC<{
veCakeAmount?: number
cakeAmount?: number
}> = ({ 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)))
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -49,10 +47,7 @@ export const NotLockingCard = () => {
<LockCakeForm fieldOnly />
<LockWeeksForm fieldOnly />
</Grid>
<NewStakingDataSet
veCakeAmount={getVeCakeAmount(cakeLockAmount, Number(cakeLockWeeks || 0) * WEEK)}
cakeAmount={Number(cakeLockAmount)}
/>
<NewStakingDataSet cakeAmount={Number(cakeLockAmount)} />
<ColumnCenter>
{account ? (
<Button disabled={disabled} width={['100%', '100%', '50%']} onClick={handleModalOpen}>
Expand Down
29 changes: 24 additions & 5 deletions apps/web/src/views/CakeStaking/components/LockWeeksForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { cakeLockWeeksAtom } from 'state/vecake/atoms'
import styled from 'styled-components'
import { useWriteIncreaseLockWeeksCallback } from '../hooks/useContractWrite'
import { useWriteWithdrawCallback } from '../hooks/useContractWrite/useWriteWithdrawCallback'
import { useMaxUnlockWeeks } from '../hooks/useMaxUnlockTime'
import { useCakeLockStatus } from '../hooks/useVeCakeUserInfo'
import { LockWeeksDataSet } from './DataSet'

Expand Down Expand Up @@ -43,15 +44,21 @@ const WeekInput: React.FC<{
disabled?: boolean
}> = ({ value, onUserInput, disabled }) => {
const { t } = useTranslation()
const { cakeLockExpired, cakeUnlockTime } = useCakeLockStatus()
const showMax = useMemo(() => (cakeLockExpired ? false : cakeUnlockTime > 0), [cakeLockExpired, cakeUnlockTime])
const weekOptions = useMemo(() => {
return showMax ? weeks.slice(0, weeks.length - 1) : weeks
}, [showMax])
const maxUnlockWeeks = useMaxUnlockWeeks(MAX_VECAKE_LOCK_WEEKS, cakeLockExpired ? 0 : cakeUnlockTime)
const onInput = useCallback(
(v: string) => {
if (Number(v) > MAX_VECAKE_LOCK_WEEKS) {
onUserInput(String(MAX_VECAKE_LOCK_WEEKS))
if (Number(v) > maxUnlockWeeks) {
onUserInput(String(maxUnlockWeeks))
} else {
onUserInput(v)
}
},
[onUserInput],
[maxUnlockWeeks, onUserInput],
)
const handleWeekSelect = useCallback(
(e: React.MouseEvent<HTMLButtonElement>) => {
Expand All @@ -78,18 +85,30 @@ const WeekInput: React.FC<{
/>
{disabled ? null : (
<FlexGap justifyContent="space-between" flexWrap="wrap" gap="4px" width="100%">
{weeks.map(({ value: v, label }) => (
{weekOptions.map(({ value: v, label }) => (
<Button
key={v}
data-week={v}
disabled={disabled}
disabled={disabled || maxUnlockWeeks < v}
onClick={handleWeekSelect}
scale="sm"
variant={Number(value) === v ? 'subtle' : 'light'}
>
{label}
</Button>
))}

{showMax ? (
<Button
data-week={maxUnlockWeeks}
disabled={disabled || maxUnlockWeeks <= 0}
onClick={handleWeekSelect}
scale="sm"
variant={Number(value) === maxUnlockWeeks ? 'subtle' : 'light'}
>
{t('Max')}
</Button>
) : null}
</FlexGap>
)}
</>
Expand Down
11 changes: 6 additions & 5 deletions apps/web/src/views/CakeStaking/components/LockedVeCakeStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 ? (
<UnderlineText fontSize="20px" bold color={balance.eq(0) ? 'failure' : 'secondary'}>
{`< 0.01`}
{getBalanceAmount(balance).sd(2).toString()}
</UnderlineText>
) : (
<UnderlinedBalance
Expand All @@ -81,7 +82,7 @@ export const LockedVeCakeStatus: React.FC<{
<Tooltips
content={
proxyBalance.gt(0) ? (
<DualStakeTooltip nativeBalance={balanceBN} proxyBalance={proxyCake} />
<DualStakeTooltip nativeBalance={nativeCake} proxyBalance={proxyCake} />
) : (
<SingleStakeTooltip />
)
Expand Down Expand Up @@ -218,10 +219,10 @@ const DualStakeTooltip: React.FC<{
<br />
<ul>
<li>
{t('Native:')} {formatNumber(nativeBalance)} veCAKE
{t('Native:')} {formatNumber(nativeBalance, 2, 4)} veCAKE
</li>
<li>
{t('Migrated:')} {formatNumber(proxyBalance)} veCAKE
{t('Migrated:')} {formatNumber(proxyBalance, 2, 4)} veCAKE
</li>
</ul>
<br />
Expand Down
9 changes: 9 additions & 0 deletions apps/web/src/views/CakeStaking/components/Tooltips.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,12 @@ export const Tooltips: React.FC<React.PropsWithChildren<TooltipsProps>> = ({
</>
)
}

export const DebugTooltips: React.FC<React.PropsWithChildren<TooltipsProps>> = ({ ...props }) => {
return (
<Tooltips
{...props}
disabled={!(process.env.NODE_ENV === 'development' || process.env.NEXT_PUBLIC_VERCEL_ENV === 'preview')}
/>
)
}
40 changes: 40 additions & 0 deletions apps/web/src/views/CakeStaking/hooks/useMaxUnlockTime.ts
Original file line number Diff line number Diff line change
@@ -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])
}
23 changes: 23 additions & 0 deletions apps/web/src/views/CakeStaking/hooks/useTargetUnlockTime.ts
Original file line number Diff line number Diff line change
@@ -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])
}
12 changes: 12 additions & 0 deletions apps/web/src/views/CakeStaking/hooks/useVeCakeAmount.ts
Original file line number Diff line number Diff line change
@@ -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])
}
Loading

1 comment on commit f96b1ed

@vercel
Copy link

@vercel vercel bot commented on f96b1ed Dec 28, 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.