Skip to content

Commit

Permalink
DAO-842 Updated logic to correctly match allocation balance checking …
Browse files Browse the repository at this point in the history
…process.
  • Loading branch information
Freshenext committed Nov 22, 2024
1 parent db737e2 commit d5f493d
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 45 deletions.
44 changes: 11 additions & 33 deletions src/app/user/Stake/Steps/StepOne.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import { useRef } from 'react'
import { StakeRIF } from '@/app/user/Stake/StakeRIF'
import { useStakingContext } from '@/app/user/Stake/StakingContext'
import { StepProps } from '@/app/user/Stake/types'
import { debounce, formatCurrency, toFixed } from '@/lib/utils'
import { useEffect, useMemo, useState } from 'react'
import { formatCurrency, toFixed } from '@/lib/utils'
import { useMemo } from 'react'
import { useCanAccountUnstakeAmount } from '@/shared/hooks/useCanAccountUnstakeAmount'

export const StepOne = ({ onGoNext = () => {} }: StepProps) => {
const { amount, onAmountChange, tokenToSend, actionName } = useStakingContext()

const { isCanAccountWithdrawLoading, canAccountWithdraw, refetchCanAccountWithdraw } =
useCanAccountUnstakeAmount(BigInt(Math.ceil(Number(amount)))) // Ceiling to avoid crashing when using decimals
// Tracks the most recent amount that completed validation via the withdrawal check API.
const [lastAmountFetched, setLastAmountFetched] = useState('')

const debounceRefetchCanAccountWithdraw = useRef(
debounce((amountToUpdate: string) => {
refetchCanAccountWithdraw().then(() => setLastAmountFetched(amountToUpdate))
}, 1000),
).current
// For now, we can only unstake stRIF - but this might change in the future... so tokenToSend.balance must be handled on each case
const { isCanAccountWithdrawLoading, canAccountWithdraw, backerTotalAllocation } =
useCanAccountUnstakeAmount(Number(amount).toString(), tokenToSend.balance) // Ceil'ing to avoid crashing when using decimals

const balanceToCurrency = useMemo(
() => Number(tokenToSend.price) * Number(tokenToSend.balance),
Expand All @@ -30,7 +21,11 @@ export const StepOne = ({ onGoNext = () => {} }: StepProps) => {
}

const shouldShowCannotWithdraw = useMemo(
() => actionName === 'UNSTAKE' && !isCanAccountWithdrawLoading && canAccountWithdraw === false,
() =>
actionName === 'UNSTAKE' &&
!isCanAccountWithdrawLoading &&
!canAccountWithdraw &&
(backerTotalAllocation || 0n) > 0n,
[actionName, canAccountWithdraw, isCanAccountWithdrawLoading],
)

Expand All @@ -41,9 +36,6 @@ export const StepOne = ({ onGoNext = () => {} }: StepProps) => {
// Extra unstake validation
if (actionName === 'UNSTAKE') {
// Checks if the last amount we checked is the one that is currently introduced in the Input
if (lastAmountFetched !== amount) {
return false
}
if (isCanAccountWithdrawLoading) {
return false
}
Expand All @@ -53,21 +45,7 @@ export const StepOne = ({ onGoNext = () => {} }: StepProps) => {
}

return Number(amount) <= Number(tokenToSend.balance)
}, [
actionName,
amount,
isCanAccountWithdrawLoading,
lastAmountFetched,
shouldShowCannotWithdraw,
tokenToSend.balance,
])

useEffect(() => {
// Run extra validation when UNSTAKE
if (actionName === 'UNSTAKE') {
debounceRefetchCanAccountWithdraw(amount)
}
}, [actionName, amount, debounceRefetchCanAccountWithdraw])
}, [actionName, amount, isCanAccountWithdrawLoading, shouldShowCannotWithdraw, tokenToSend.balance])

return (
<StakeRIF
Expand Down
70 changes: 58 additions & 12 deletions src/shared/hooks/useCanAccountUnstakeAmount.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,74 @@
import { BackersManagerAbi } from '@/lib/abis/v2/BackersManagerAbi'
import { BACKERS_MANAGER_ADDRESS } from '@/lib/constants'
import { useAccount, useReadContract } from 'wagmi'
import { parseEther } from 'viem'

export const useCanAccountUnstakeAmount = (amount: bigint) => {
/**
* Custom hook to determine if an account can unstake a specified amount of RIF tokens
*
* @param amount - The amount to unstake in bigint format
* @param stRifBalance - The current stRIF balance as a string
* @returns An object containing:
* - canAccountWithdraw: boolean indicating if the account can withdraw the specified amount
* - isCanAccountWithdrawLoading: boolean indicating if the data is still loading
*
* @remarks
* The hook calculates if an account can unstake by:
* 1. Fetching the backer's total allocation from the BackersManager contract
* 2. Converting input amount and balance to proper format using parseEther
* 3. Calculating available balance by subtracting total allocation from current balance
* 4. Comparing requested unstake amount against available balance
*
* @example
* ```tsx
* const { canAccountWithdraw, isCanAccountWithdrawLoading } = useCanAccountUnstakeAmount(
* "100", // amount to unstake
* "1000" // current stRIF balance
* );
* ```
*/
export const useCanAccountUnstakeAmount = (amount: string, stRifBalance: string) => {
const { address } = useAccount()
const {
data: canAccountWithdraw,
isLoading: isCanAccountWithdrawLoading,
refetch: refetchCanAccountWithdraw,
} = useReadContract(
const { data: backerTotalAllocation, isLoading: isBackerTotalAllocationLoading } = useReadContract(
address && {
abi: BackersManagerAbi,
address: BACKERS_MANAGER_ADDRESS,
functionName: 'canWithdraw',
args: [address, amount],
functionName: 'backerTotalAllocation',
args: [address],
query: {
enabled: false,
refetchInterval: 10000,
},
},
)

const parsedAmount = parseEther(amount) ?? 0n
const parsedBalance = parseEther(stRifBalance) ?? 0n
const balanceThatCanBeWithdraw = parsedBalance - (backerTotalAllocation || 0n)

return {
canAccountWithdraw,
isCanAccountWithdrawLoading,
refetchCanAccountWithdraw,
canAccountWithdraw: parsedAmount < balanceThatCanBeWithdraw,
isCanAccountWithdrawLoading: isBackerTotalAllocationLoading,
backerTotalAllocation,
}
}

/**
* Example 1
* If you have 300 tokens in your balance and have allocated 250 tokens to backing, your balanceThatCanBeWithdraw would be 50 tokens (300 - 250 = 50).
* This means you can only unstake up to 50 tokens, as the remaining 250 are locked in allocations.
* Example 2
* Let's say you want to unstake 40 tokens:
*
* Your parsedBalance = 300
* Your backerTotalAllocation = 250
* Your balanceThatCanBeWithdraw = 50
* Since 40 < 50, the unstaking will succeed.
*
* Example 3
* However, if you try to unstake 60 tokens:
*
* Your parsedBalance = 300
* Your backerTotalAllocation = 250
* Your balanceThatCanBeWithdraw = 50
* Since 60 > 50, the unstaking will fail because you're trying to unstake more tokens than are available.
*/

0 comments on commit d5f493d

Please sign in to comment.