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

[4] feat(balances): update balances for priority tokens #3417

Merged
merged 30 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1e2db20
feat: multicall library
shoom3301 Nov 18, 2023
04dcf87
feat: balances and allowances lib
shoom3301 Nov 18, 2023
85962d4
feat: replace uniswap milticall by new balances and allowances lib
shoom3301 Nov 18, 2023
4cc05c1
refactor: remove uniswap multicall and legacy hooks
shoom3301 Nov 18, 2023
68f79d0
feat: replace uniswap milticall by new balances and allowances lib
shoom3301 Nov 18, 2023
36f3eb8
feat: multicall library
shoom3301 Nov 18, 2023
2791105
Merge branch 'feat/multicall' of https://github.com/cowprotocol/cowsw…
shoom3301 Nov 18, 2023
3d27d18
Merge branch 'feat/balances-and-allowances' of https://github.com/cow…
shoom3301 Nov 18, 2023
725ede0
Merge branch 'feat/refactor-balances' of https://github.com/cowprotoc…
shoom3301 Nov 18, 2023
27a540b
chore: update yarnlock
shoom3301 Nov 18, 2023
ce2460b
chore: fix useClassifiedUserClaims
shoom3301 Nov 19, 2023
ea3af64
chore: remove unused file
shoom3301 Nov 19, 2023
e34ce1e
feat(balances): update balances for priority tokens
shoom3301 Nov 19, 2023
9654afd
feat: multicall library
shoom3301 Nov 18, 2023
6ee8391
Merge branch 'feat/multicall' of https://github.com/cowprotocol/cowsw…
shoom3301 Nov 22, 2023
24fbe15
chore: merge multicall
shoom3301 Nov 22, 2023
96796a4
chore: fix typos
shoom3301 Nov 22, 2023
2d215a5
chore: move ERC_20_INTERFACE to abis lib
shoom3301 Nov 22, 2023
3ffd46f
Merge branch 'feat/balances-and-allowances' of https://github.com/cow…
shoom3301 Nov 22, 2023
8b09660
Merge branch 'refactor/remove-uniswap-multicall' of https://github.co…
shoom3301 Nov 22, 2023
4d6ee4d
chore: merge changes
shoom3301 Nov 22, 2023
2edde6d
fix: do not display balance when wallet not connected
shoom3301 Nov 22, 2023
23b6253
chore: fix balances and allowances in orders table
shoom3301 Dec 4, 2023
2eed9dd
chore: reset balances and allowances when wallet is not connected
shoom3301 Dec 4, 2023
4d7c349
fix(swap): update need approve state faster
shoom3301 Dec 4, 2023
2d56de0
chore: fix getOrderParams tests
shoom3301 Dec 4, 2023
57a986e
chore: fix enough allowance/balance check
shoom3301 Dec 5, 2023
185c30f
fix(orders-table): display balances warning for tokens with unknown p…
shoom3301 Dec 5, 2023
167185a
Merge branch 'develop' of https://github.com/cowprotocol/cowswap into…
shoom3301 Dec 6, 2023
1b28db3
chore: test commit
shoom3301 Dec 6, 2023
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
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { CowModal } from 'common/pure/Modal'
import { TransactionErrorContent } from 'common/pure/TransactionErrorContent'

import { useTradeApproveCallback } from './useTradeApproveCallback'
import { useTradeApproveState } from './useTradeApproveState'

import { useApproveState } from '../../hooks/useApproveState'

export interface TradeApproveButtonProps {
amountToApprove: CurrencyAmount<Currency>
Expand All @@ -22,7 +23,7 @@ export function TradeApproveButton(props: TradeApproveButtonProps) {

const currency = amountToApprove.currency

const approvalState = useTradeApproveState(amountToApprove)
const approvalState = useApproveState(amountToApprove)
const tradeApproveCallback = useTradeApproveCallback(amountToApprove)
const shouldZeroApprove = useShouldZeroApprove(amountToApprove)
const zeroApprove = useZeroApprove(amountToApprove.currency)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export * from './TradeApproveButton'
export * from './TradeApproveModal'
export * from './useTradeApproveCallback'
export * from './useTradeApproveState'

This file was deleted.

36 changes: 20 additions & 16 deletions apps/cowswap-frontend/src/common/hooks/useApproveState.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,56 @@
import { useMemo } from 'react'

import { useTokensAllowances } from '@cowprotocol/balances-and-allowances'
import { usePrevious } from '@cowprotocol/common-hooks'
import { FractionUtils, getWrappedToken } from '@cowprotocol/common-utils'
import { useWalletInfo } from '@cowprotocol/wallet'
import { getWrappedToken } from '@cowprotocol/common-utils'
import { BigNumber } from '@ethersproject/bignumber'
import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core'

import { Nullish } from 'types'

import { ApprovalState } from 'legacy/hooks/useApproveCallback/useApproveCallbackMod'
import { useTokenAllowance } from 'legacy/hooks/useTokenAllowance'
import { useHasPendingApproval } from 'legacy/state/enhancedTransactions/hooks'

import { useSafeMemo } from 'common/hooks/useSafeMemo'

import { useTradeSpenderAddress } from './useTradeSpenderAddress'

function getCurrencyToApprove(amountToApprove: Nullish<CurrencyAmount<Currency>>): Token | undefined {
if (!amountToApprove) return undefined

return getWrappedToken(amountToApprove.currency)
}

export function useApproveState(amountToApprove: Nullish<CurrencyAmount<Currency>>, spender?: string): ApprovalState {
const { account } = useWalletInfo()
export function useApproveState(amountToApprove: Nullish<CurrencyAmount<Currency>>): ApprovalState {
const spender = useTradeSpenderAddress()
const token = getCurrencyToApprove(amountToApprove)
const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
const pendingApproval = useHasPendingApproval(token?.address, spender)
const tokenAddress = token?.address?.toLowerCase()
const allowances = useTokensAllowances()
const pendingApproval = useHasPendingApproval(tokenAddress, spender)

const currentAllowance = tokenAddress ? allowances.values[tokenAddress] : undefined

const approvalStateBase = useSafeMemo(() => {
if (!amountToApprove || !spender || !currentAllowance) {
if (!amountToApprove || !currentAllowance) {
return ApprovalState.UNKNOWN
}

if (FractionUtils.gte(currentAllowance, amountToApprove)) {
const amountToApproveString = amountToApprove.quotient.toString()

if (currentAllowance.gte(amountToApproveString)) {
return ApprovalState.APPROVED
}

if (pendingApproval) {
return ApprovalState.PENDING
}

if (currentAllowance.lessThan(amountToApprove)) {
if (currentAllowance.lt(amountToApproveString)) {
return ApprovalState.NOT_APPROVED
}

return ApprovalState.APPROVED
}, [amountToApprove, currentAllowance, pendingApproval, spender])
}, [amountToApprove, currentAllowance, pendingApproval])

return useAuxApprovalState(approvalStateBase, currentAllowance)
}
Expand All @@ -55,12 +62,9 @@ export function useApproveState(amountToApprove: Nullish<CurrencyAmount<Currency
*
* Solution: we check the prev approval state and also check if the allowance has been updated
*/
function useAuxApprovalState(
approvalStateBase: ApprovalState,
currentAllowance?: CurrencyAmount<Currency>
): ApprovalState {
function useAuxApprovalState(approvalStateBase: ApprovalState, currentAllowance?: BigNumber): ApprovalState {
const previousApprovalState = usePrevious(approvalStateBase)
const currentAllowanceString = currentAllowance?.quotient.toString()
const currentAllowanceString = currentAllowance?.toHexString()
const previousAllowanceString = usePrevious(currentAllowanceString)
// Has allowance actually updated?
const allowanceHasNotChanged = previousAllowanceString === currentAllowanceString
Expand Down
18 changes: 14 additions & 4 deletions apps/cowswap-frontend/src/legacy/hooks/useTokenAllowance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import { useMemo } from 'react'
import { useTokenContract } from '@cowprotocol/common-hooks'
import { CurrencyAmount, Token } from '@uniswap/sdk-core'

import ms from 'ms.macro'
import useSWR from 'swr'
import { Nullish } from 'types'

const ALLOWANCES_SWR_CONFIG = { refreshInterval: ms`10s` }

/**
* @deprecated use useTokensAllowances() hook instead
*/
export function useTokenAllowance(
token: Nullish<Token>,
owner?: string,
Expand All @@ -14,11 +20,15 @@ export function useTokenAllowance(
const tokenAddress = token?.address
const contract = useTokenContract(tokenAddress, false)

const { data: allowance } = useSWR(['useTokenAllowance', tokenAddress, owner, spender], async () => {
if (!owner || !spender) return undefined
const { data: allowance } = useSWR(
['useTokenAllowance', tokenAddress, owner, spender],
async () => {
if (!owner || !spender) return undefined

return contract?.callStatic.allowance(owner, spender)
})
return contract?.callStatic.allowance(owner, spender)
},
ALLOWANCES_SWR_CONFIG
)

return useMemo(
() => (token && allowance ? CurrencyAmount.fromRawAmount(token, allowance.toString()) : undefined),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export function isTransactionRecent(tx: EnhancedTransactionDetails): boolean {
// returns whether a token has a pending approval transaction
export function useHasPendingApproval(tokenAddress: string | undefined, spender: string | undefined): boolean {
const allTransactions = useAllTransactions()

return useMemo(
() =>
typeof tokenAddress === 'string' &&
Expand All @@ -37,7 +38,12 @@ export function useHasPendingApproval(tokenAddress: string | undefined, spender:
} else {
const approval = tx.approval
if (!approval) return false
return approval.spender === spender && approval.tokenAddress === tokenAddress && isTransactionRecent(tx)

return (
approval.spender.toLowerCase() === spender.toLowerCase() &&
approval.tokenAddress.toLowerCase() === tokenAddress &&
isTransactionRecent(tx)
)
}
}),
[allTransactions, spender, tokenAddress]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,14 @@ export function OrderRow({

const showCancellationModal = orderActions.getShowCancellationModal(order)

/**
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@alfetopito please, take a look on the fix, I'm not sure if it's correct

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The idea here - like with hasEnoughAllowance - is to only set the warning if we know there's no valid permit.
hasValidPendingPermit can be undefined, when we don't know/can't tell.

Was that causing any issues?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see now that this has been reverted on #3487

* TODO: I'm not sure about !hasValidPendingPermit
* Before the fix it was: hasValidPendingPermit === false
* In useCheckHasValidPendingPermit() we intentionally return undefined in cases when we don't know the permit status
*/
const withAllowanceWarning = hasEnoughAllowance === false && !hasValidPendingPermit
const withWarning =
(hasEnoughBalance === false || (hasEnoughAllowance === false && hasValidPendingPermit === false)) &&
(hasEnoughBalance === false || withAllowanceWarning) &&
// show the warning only for pending and scheduled orders
(status === OrderStatus.PENDING || status === OrderStatus.SCHEDULED)
const theme = useContext(ThemeContext)
Expand All @@ -185,6 +191,7 @@ export function OrderRow({
const isScheduledCreating = isOrderScheduled && Date.now() > creationTime.getTime()
const expirationTimeAgo = useTimeAgo(expirationTime, TIME_AGO_UPDATE_INTERVAL)
const creationTimeAgo = useTimeAgo(creationTime, TIME_AGO_UPDATE_INTERVAL)

// TODO: set the real value when API returns it
// const executedTimeAgo = useTimeAgo(expirationTime, TIME_AGO_UPDATE_INTERVAL)
const activityUrl = chainId ? getActivityUrl(chainId, order) : undefined
Expand Down Expand Up @@ -359,7 +366,7 @@ export function OrderRow({
{hasEnoughBalance === false && (
<BalanceWarning symbol={inputTokenSymbol} isScheduled={isOrderScheduled} />
)}
{hasEnoughAllowance === false && hasValidPendingPermit === false && (
{withAllowanceWarning && (
<AllowanceWarning
approve={() => orderActions.approveOrderToken(order.inputToken)}
symbol={inputTokenSymbol}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { BigNumber } from '@ethersproject/bignumber'

import { BalancesAndAllowances } from 'modules/tokens'

import { getOrderParams } from './getOrderParams'

import { BalancesAndAllowances } from '../../../../tokens'
import { ordersMock } from '../orders.mock'

describe('getOrderParams', () => {
const BASE_ORDER = ordersMock[0]
const BASE_BALANCES_AND_ALLOWANCES: BalancesAndAllowances = {
balances: {
[BASE_ORDER.inputToken.address]: BigNumber.from(BASE_ORDER.sellAmount),
[BASE_ORDER.inputToken.address.toLowerCase()]: BigNumber.from(BASE_ORDER.sellAmount),
},
allowances: {
[BASE_ORDER.inputToken.address]: BigNumber.from(BASE_ORDER.sellAmount),
[BASE_ORDER.inputToken.address.toLowerCase()]: BigNumber.from(BASE_ORDER.sellAmount),
},
isLoading: false,
}
Expand Down Expand Up @@ -66,7 +67,7 @@ describe('getOrderParams', () => {
const balancesAndAllowances: BalancesAndAllowances = {
...BASE_BALANCES_AND_ALLOWANCES,
balances: {
[order.inputToken.address]: BigNumber.from(String(+order.sellAmount * 0.00051)),
[order.inputToken.address.toLowerCase()]: BigNumber.from(String(+order.sellAmount * 0.00051)),
},
}
const result = getOrderParams(1, balancesAndAllowances, order)
Expand All @@ -77,7 +78,7 @@ describe('getOrderParams', () => {
const balancesAndAllowances: BalancesAndAllowances = {
...BASE_BALANCES_AND_ALLOWANCES,
balances: {
[order.inputToken.address]: BigNumber.from(String(+order.sellAmount * 0.00049)),
[order.inputToken.address.toLowerCase()]: BigNumber.from(String(+order.sellAmount * 0.00049)),
},
}
const result = getOrderParams(1, balancesAndAllowances, order)
Expand All @@ -89,7 +90,7 @@ describe('getOrderParams', () => {
const balancesAndAllowances: BalancesAndAllowances = {
...BASE_BALANCES_AND_ALLOWANCES,
allowances: {
[order.inputToken.address]: BigNumber.from(String(+order.sellAmount * 0.00051)),
[order.inputToken.address.toLowerCase()]: BigNumber.from(String(+order.sellAmount * 0.00051)),
},
}
const result = getOrderParams(1, balancesAndAllowances, order)
Expand All @@ -100,7 +101,7 @@ describe('getOrderParams', () => {
const balancesAndAllowances: BalancesAndAllowances = {
...BASE_BALANCES_AND_ALLOWANCES,
allowances: {
[order.inputToken.address]: BigNumber.from(String(+order.sellAmount * 0.00049)),
[order.inputToken.address.toLowerCase()]: BigNumber.from(String(+order.sellAmount * 0.00049)),
},
}
const result = getOrderParams(1, balancesAndAllowances, order)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ export function getOrderParams(
}

const { balances, allowances } = balancesAndAllowances
const balance = balances[order.inputToken.address]
const allowance = allowances[order.inputToken.address]
const balance = balances[order.inputToken.address.toLowerCase()]
const allowance = allowances[order.inputToken.address.toLowerCase()]

const { hasEnoughBalance, hasEnoughAllowance } = _hasEnoughBalanceAndAllowance({
partiallyFillable: order.partiallyFillable,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ import { HandleSwapCallback } from 'modules/swap/pure/SwapButtons'
import { ethFlowContextAtom } from 'modules/swap/state/EthFlow/ethFlowContextAtom'
import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken'

import { useTradeApproveCallback, useTradeApproveState } from 'common/containers/TradeApprove'
import { useTradeApproveCallback } from 'common/containers/TradeApprove'
import { useApproveState } from 'common/hooks/useApproveState'
import { CowModal } from 'common/pure/Modal'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'

Expand All @@ -44,7 +45,7 @@ function EthFlow({
const isExpertMode = useIsExpertMode()
const native = useNativeCurrency()
const wrapped = useWrappedToken()
const approvalState = useTradeApproveState(nativeInput || null)
const approvalState = useApproveState(nativeInput || null)

const ethFlowContext = useAtomValue(ethFlowContextAtom)
const approveCallback = useTradeApproveCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import { useIsNativeIn } from 'modules/trade/hooks/useIsNativeInOrOut'
import { useIsWrappedOut } from 'modules/trade/hooks/useIsWrappedInOrOut'
import { useWrappedToken } from 'modules/trade/hooks/useWrappedToken'

import { useTradeApproveState } from 'common/containers/TradeApprove/useTradeApproveState'
import { useApproveState } from 'common/hooks/useApproveState'

import { useSafeBundleEthFlowContext } from './useSafeBundleEthFlowContext'
import { useDerivedSwapInfo, useSwapActionHandlers } from './useSwapState'
Expand Down Expand Up @@ -81,7 +81,7 @@ export function useSwapButtonContext(input: SwapButtonInput): SwapButtonsContext
const wrapUnwrapAmount = isNativeInSwap ? currencyAmountToTokenAmount(inputAmount) || undefined : inputAmount
const hasEnoughWrappedBalanceForSwap = useHasEnoughWrappedBalanceForSwap(wrapUnwrapAmount)
const wrapCallback = useWrapNativeFlow()
const approvalState = useTradeApproveState(slippageAdjustedSellAmount || null)
const approvalState = useApproveState(slippageAdjustedSellAmount || null)

const handleSwap = useHandleSwap(priceImpactParams)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function hasEnoughBalanceAndAllowance(params: EnoughBalanceParams): UseEn

const isNativeCurrency = !!amount?.currency && getIsNativeToken(amount?.currency)
const token = amount?.currency && getWrappedToken(amount.currency)
const tokenAddress = getAddress(token)
const tokenAddress = getAddress(token)?.toLowerCase()

const enoughBalance = _enoughBalance(tokenAddress, amount, balances)
const enoughAllowance = _enoughAllowance(tokenAddress, amount, allowances, isNativeCurrency)
Expand All @@ -84,7 +84,7 @@ export function hasEnoughBalanceAndAllowance(params: EnoughBalanceParams): UseEn
}

function _enoughBalance(
tokenAddress: string | null,
tokenAddress: string | undefined,
amount: CurrencyAmount<Currency>,
balances: BalancesState['values']
): boolean | undefined {
Expand All @@ -94,7 +94,7 @@ function _enoughBalance(
}

function _enoughAllowance(
tokenAddress: string | null,
tokenAddress: string | undefined,
amount: CurrencyAmount<Currency>,
allowances: AllowancesState['values'] | undefined,
isNativeCurrency: boolean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function TokenListItem(props: TokenListItemProps) {

const isTokenSelected = token.address.toLowerCase() === selectedToken?.toLowerCase()

const balanceAmount = CurrencyAmount.fromRawAmount(token, balance?.toHexString() ?? 0)
const balanceAmount = balance ? CurrencyAmount.fromRawAmount(token, balance.toHexString()) : undefined

return (
<styledEl.TokenItem
Expand All @@ -41,9 +41,7 @@ export function TokenListItem(props: TokenListItemProps) {
>
<TokenInfo token={token} />
<TokenTags isUnsupported={isUnsupported} isPermitCompatible={isPermitCompatible} />
<span>
<TokenAmount amount={balanceAmount} />
</span>
<span>{balanceAmount && <TokenAmount amount={balanceAmount} />}</span>
</styledEl.TokenItem>
)
}
Loading
Loading