Skip to content

Commit

Permalink
feat: voting sort (#8536)
Browse files Browse the repository at this point in the history
  • Loading branch information
ChefJerry authored Dec 7, 2023
1 parent 10bf138 commit fe26339
Show file tree
Hide file tree
Showing 6 changed files with 132 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,19 @@ export const TableRow: React.FC<RowProps> = ({ data, vote = { ...DEFAULT_VOTE },
const { cakeLockedAmount } = useCakeLockStatus()
const cakeLocked = useMemo(() => cakeLockedAmount > 0n, [cakeLockedAmount])
const userVote = useUserVote(data)
const { currentVoteWeight, currentVotePercent, previewVoteWeight, voteValue, voteLocked, willUnlock } =
useRowVoteState({
data,
vote,
onChange,
})
const {
currentVoteWeight,
currentVotePercent,
previewVoteWeight,
voteValue,
voteLocked,
willUnlock,
proxyVeCakeBalance,
} = useRowVoteState({
data,
vote,
onChange,
})
const onMax = () => {
onChange(vote, true)
}
Expand All @@ -48,12 +55,13 @@ export const TableRow: React.FC<RowProps> = ({ data, vote = { ...DEFAULT_VOTE },
{
...userVote,
currentTimestamp: debugFormat(currentTimestamp),
nativeLasVoteTime: debugFormat(userVote?.lastVoteTime),
nativeLasVoteTime: debugFormat(userVote?.nativeLastVoteTime),
proxyLastVoteTime: debugFormat(userVote?.proxyLastVoteTime),
lastVoteTime: debugFormat(userVote?.lastVoteTime),
end: debugFormat(userVote?.end),
proxyEnd: debugFormat(userVote?.proxyEnd),
nativeEnd: debugFormat(userVote?.nativeEnd),
proxyVeCakeBalance: proxyVeCakeBalance?.toString(),
},
undefined,
2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,25 @@ export const useGaugeRows = () => {
const previousAccount = usePreviousValue(account)
const { data: prevVotedGauges, refetch } = useUserVoteGauges()
const [selectRowsHash, setSelectRowsHash] = useState<Gauge['hash'][]>([])
const [initialed, setInitialed] = useState(false)
const rows = useMemo(() => {
return gauges?.filter((gauge) => selectRowsHash.includes(gauge.hash))
}, [gauges, selectRowsHash])

useEffect(() => {
if (account !== previousAccount) {
setInitialed(false)
setSelectRowsHash([])
}
}, [account, previousAccount, selectRowsHash])

// add all gauges to selectRows when user has voted gauges
useEffect(() => {
if (prevVotedGauges?.length && !selectRowsHash.length) {
if (prevVotedGauges?.length && !selectRowsHash.length && !initialed) {
setSelectRowsHash(prevVotedGauges.map((gauge) => gauge.hash))
setInitialed(true)
}
}, [prevVotedGauges, selectRowsHash.length])
}, [initialed, prevVotedGauges, selectRowsHash.length])

const onRowSelect = useCallback(
(hash: Gauge['hash']) => {
Expand All @@ -40,6 +43,7 @@ export const useGaugeRows = () => {
)

return {
gauges,
rows,
isLoading,
onRowSelect,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,16 @@ import { useVeCakeBalance } from 'hooks/useTokenBalance'
import { useEffect, useMemo } from 'react'
import { Hex } from 'viem'
import { useCurrentBlockTimestamp } from 'views/CakeStaking/hooks/useCurrentBlockTimestamp'
import { useProxyVeCakeBalance } from 'views/CakeStaking/hooks/useProxyVeCakeBalance'
import { useEpochVotePower } from 'views/GaugesVoting/hooks/useEpochVotePower'
import { useUserVote } from 'views/GaugesVoting/hooks/useUserVote'
import { RowProps } from '../types'
import { DEFAULT_VOTE, RowProps } from '../types'

export const useRowVoteState = ({ data, vote, onChange }: RowProps) => {
const userVote = useUserVote(data)
const voteLocked = userVote?.voteLocked
const { balance: veCakeBalance } = useVeCakeBalance()
const { balance: proxyVeCakeBalance } = useProxyVeCakeBalance()
const currentTimestamp = useCurrentBlockTimestamp()
const epochVotePower = useEpochVotePower()
const willUnlock = useMemo(
Expand Down Expand Up @@ -52,18 +54,27 @@ export const useRowVoteState = ({ data, vote, onChange }: RowProps) => {

const voteValue = useMemo(() => {
if (voteLocked) return currentVotePercent ?? ''
return willUnlock ? '0' : vote?.power ?? ''
if (willUnlock) return '0'
if (vote?.power === DEFAULT_VOTE.power) return currentVotePercent ?? ''
return vote?.power ?? ''
}, [voteLocked, currentVotePercent, willUnlock, vote?.power])

const previewVoteWeight = useMemo(() => {
const p = Number(voteValue || 0) * 100
// const powerBN = new BN(epochVotePower.toString())
const amount = getBalanceNumber(veCakeBalance.times(p).div(10000))
let balance = veCakeBalance
if (userVote?.ignoredSide === 'proxy') {
balance = veCakeBalance.minus(proxyVeCakeBalance)
}
if (userVote?.ignoredSide === 'native') {
balance = proxyVeCakeBalance
}
const amount = getBalanceNumber(balance.times(p).div(10000))

if (amount === 0) return 0
if (amount < 1) return amount.toPrecision(2)
return amount < 1000 ? amount.toFixed(2) : formatLocalisedCompactNumber(amount, true)
}, [veCakeBalance, voteValue])
}, [voteValue, veCakeBalance, userVote?.ignoredSide, proxyVeCakeBalance])

// init vote value if still default
useEffect(() => {
Expand All @@ -83,5 +94,6 @@ export const useRowVoteState = ({ data, vote, onChange }: RowProps) => {
voteValue,
voteLocked,
willUnlock,
proxyVeCakeBalance,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useGaugesVotingCount } from 'views/CakeStaking/hooks/useGaugesVotingCou
import { useCakeLockStatus } from 'views/CakeStaking/hooks/useVeCakeUserInfo'
import { useEpochOnTally } from 'views/GaugesVoting/hooks/useEpochTime'
import { useEpochVotePower } from 'views/GaugesVoting/hooks/useEpochVotePower'
import { useUserVoteSlopes } from 'views/GaugesVoting/hooks/useUserVoteGauges'
import { useWriteGaugesVoteCallback } from 'views/GaugesVoting/hooks/useWriteGaugesVoteCallback'
import { RemainingVotePower } from '../../RemainingVotePower'
import { AddGaugeModal } from '../AddGauge/AddGaugeModal'
Expand All @@ -31,6 +32,10 @@ import { ExpandRow, TableRow } from './TableRow'
import { useGaugeRows } from './hooks/useGaugeRows'
import { UserVote } from './types'

type GaugeWithDelta = Gauge & {
delta: bigint
}

const Scrollable = styled.div.withConfig({ shouldForwardProp: (prop) => !['expanded'].includes(prop) })<{
expanded: boolean
}>`
Expand All @@ -57,7 +62,8 @@ export const VoteTable = () => {
return Object.values(votes).reduce((acc, cur) => acc + (cur?.locked ? Number(cur?.power) : 0), 0)
}, [votes])

const { rows, onRowSelect, refetch, isLoading } = useGaugeRows()
const { gauges, rows, onRowSelect, refetch, isLoading } = useGaugeRows()
const { data: slopes } = useUserVoteSlopes()
const { isDesktop } = useMatchBreakpoints()
const rowsWithLock = useMemo(() => {
return rows?.map((row) => {
Expand Down Expand Up @@ -101,8 +107,10 @@ export const VoteTable = () => {
const { writeVote, isPending } = useWriteGaugesVoteCallback()

const disabled = useMemo(() => {
const lockedSum = Object.values(votes).reduce((acc, cur) => acc + (cur?.locked ? Number(cur?.power) : 0), 0)
const newAddSum = Object.values(votes).reduce((acc, cur) => acc + (!cur?.locked ? Number(cur?.power) : 0), 0)
let lockedSum = Object.values(votes).reduce((acc, cur) => acc + (cur?.locked ? Number(cur?.power) : 0), 0)
let newAddSum = Object.values(votes).reduce((acc, cur) => acc + (!cur?.locked ? Number(cur?.power) : 0), 0)
lockedSum = Number(Number(lockedSum).toFixed(2))
newAddSum = Number(Number(newAddSum).toFixed(2))

// voting ended
if (onTally) return true
Expand All @@ -120,26 +128,45 @@ export const VoteTable = () => {
return Number(gaugesCount) - (rows?.length || 0)
}, [gaugesCount, rows])

const submitVote = useCallback(async () => {
const voteGauges = Object.values(votes)
.map((vote) => {
if (!vote.locked && Number(vote.power)) {
const row = rows?.find((r) => r.hash === vote.hash)
const sortedSubmitVotes = useMemo(() => {
const voteGauges = slopes
.map((slope) => {
const vote = votes[slope.hash]
// update vote power
if (vote && !vote?.locked) {
const row = gauges?.find((r) => r.hash === slope.hash)
if (!row) return undefined
const currentPower = BigInt((Number(vote.power) * 100).toFixed(0))
const { nativePower = 0, proxyPower = 0 } = slope || {}
return {
...row,
weight: BigInt((Number(vote.power) * 100).toFixed(0)),
delta: currentPower - (BigInt(nativePower) + BigInt(proxyPower)),
weight: currentPower,
}
}
// vote deleted
if (!vote && (slope.proxyPower > 0 || slope.nativePower > 0)) {
const row = gauges?.find((r) => r.hash === slope.hash)
if (!row) return undefined
return {
...row,
delta: 0n - (BigInt(slope.nativePower) + BigInt(slope.proxyPower)),
weight: 0n,
}
}
return undefined
})
.filter((gauge: Gauge | undefined): gauge is Gauge => Boolean(gauge))
.filter((gauge: GaugeWithDelta | undefined): gauge is GaugeWithDelta => Boolean(gauge))
.sort((a, b) => (b.delta < a.delta ? 1 : -1))
return voteGauges
}, [slopes, votes, gauges])

await writeVote(voteGauges)
const submitVote = useCallback(async () => {
await writeVote(sortedSubmitVotes)
await refetch()
}, [refetch, rows, votes, writeVote])
}, [refetch, sortedSubmitVotes, writeVote])

const gauges = isDesktop ? (
const gaugesTable = isDesktop ? (
<>
<TableHeader count={rows?.length} />

Expand Down Expand Up @@ -220,7 +247,7 @@ export const VoteTable = () => {
onDismiss={() => setIsOpen(false)}
/>
<Card innerCardProps={{ padding: isDesktop ? '2em' : '0', paddingTop: isDesktop ? '1em' : '0' }} mt="2em">
{gauges}
{gaugesTable}

{rowsWithLock?.length && epochPower <= 0n && cakeLockedAmount > 0n ? (
<Box width={['100%', '100%', '100%', '50%']} px={['16px', 'auto']} mx="auto">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type RowProps = {
} & SpaceProps

export const DEFAULT_VOTE: UserVote = {
power: '0',
power: 'DEFAULT',
locked: false,
hash: '0x',
}
56 changes: 53 additions & 3 deletions apps/web/src/views/GaugesVoting/hooks/useUserVote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Address, Hex, isAddressEqual, zeroAddress } from 'viem'
import { useCurrentBlockTimestamp } from 'views/CakeStaking/hooks/useCurrentBlockTimestamp'
import { useVeCakeUserInfo } from 'views/CakeStaking/hooks/useVeCakeUserInfo'
import { usePublicClient } from 'wagmi'
import { useNextEpochStart } from './useEpochTime'

export type VotedSlope = {
hash: string
Expand All @@ -28,6 +29,9 @@ export type VotedSlope = {
end: bigint
lastVoteTime: bigint
voteLocked: boolean
ignoredSide?: 'native' | 'proxy'
ignoredSlope?: bigint
ignoredPower?: bigint
}

const max = (a: bigint, b: bigint) => (a > b ? a : b)
Expand All @@ -39,7 +43,7 @@ export const useUserVote = (gauge?: Gauge, useProxyPool: boolean = true) => {
const contract = useGaugesVotingContract()
const { data: userInfo } = useVeCakeUserInfo()
const currentTimestamp = useCurrentBlockTimestamp()
// const nextEpochStart = useNextEpochStart()
const nextEpochStart = useNextEpochStart()

const { data } = useQuery(
['/vecake/userVoteSlopes', contract.address, gauge?.hash, account],
Expand Down Expand Up @@ -76,9 +80,9 @@ export const useUserVote = (gauge?: Gauge, useProxyPool: boolean = true) => {
allowFailure: false,
})
const [
[proxySlope, proxyPower, proxyEnd],
[_proxySlope, _proxyPower, proxyEnd],
proxyLastVoteTime,
[nativeSlope, nativePower, nativeEnd],
[_nativeSlope, _nativePower, nativeEnd],
nativeLastVoteTime,
] = response
const proxyVoteLocked = dayjs
Expand All @@ -89,6 +93,49 @@ export const useUserVote = (gauge?: Gauge, useProxyPool: boolean = true) => {
.unix(Number(nativeLastVoteTime))
.add(10, 'day')
.isAfter(dayjs.unix(currentTimestamp))
let [nativeSlope, nativePower, proxySlope, proxyPower] = [_nativeSlope, _nativePower, _proxySlope, _proxyPower]
let ignoredSlope = 0n
let ignoredPower = 0n
let ignoredSide: 'native' | 'proxy' | undefined
// when native slope will expire before current epochEnd
// use proxy slope only
if (nativeEnd < nextEpochStart && proxyEnd > nextEpochStart) {
ignoredSlope = nativeSlope
ignoredPower = nativePower
ignoredSide = 'native'
nativeSlope = 0n
nativePower = 0n
}
// when proxy slope will expire before current epochEnd
// use native slope only
if (proxyEnd < nextEpochStart && nativeEnd > nextEpochStart) {
ignoredSlope = proxySlope
ignoredPower = proxyPower
ignoredSide = 'proxy'
proxySlope = 0n
proxyPower = 0n
}

// when both slopes will expire before current epochEnd
// use max of both slopes
if (nativeEnd < nextEpochStart && proxyEnd < nextEpochStart) {
const nativeWeight = _nativeSlope * (nativeEnd - BigInt(currentTimestamp))
const proxyWeight = _proxySlope * (proxyEnd - BigInt(currentTimestamp))
if (nativeWeight > proxyWeight) {
ignoredPower = proxyPower
ignoredSlope = proxySlope
ignoredSide = 'proxy'
proxySlope = 0n
proxyPower = 0n
} else {
ignoredPower = nativePower
ignoredSlope = nativeSlope
ignoredSide = 'native'
nativeSlope = 0n
nativePower = 0n
}
}

return {
hash: gauge?.hash as Hex,
proxyPower,
Expand All @@ -107,6 +154,9 @@ export const useUserVote = (gauge?: Gauge, useProxyPool: boolean = true) => {
end: max(nativeEnd, nativeEnd),
voteLocked: proxyVoteLocked || nativeVoteLocked,
lastVoteTime: proxyLastVoteTime < nativeLastVoteTime ? nativeLastVoteTime : proxyLastVoteTime,
ignoredPower,
ignoredSlope,
ignoredSide,
}
}
const response = await publicClient.multicall({
Expand Down

1 comment on commit fe26339

@vercel
Copy link

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