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

fix(combinedBalances): Optimize balance diff calculations #5082

Merged
merged 5 commits into from
Nov 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -1,16 +1,19 @@
import { useState } from 'react'

import { HookToDappMatch } from '@cowprotocol/hook-dapp-lib'
import { CowHookDetails, HookToDappMatch } from '@cowprotocol/hook-dapp-lib'

import { ChevronDown, ChevronUp } from 'react-feather'

import { clickOnHooks } from 'modules/analytics'
import { useSimulationData } from 'modules/tenderly/hooks/useSimulationData'

import * as styledEl from './styled'

export function HookItem({ item, index }: { item: HookToDappMatch; index: number }) {
export function HookItem({ details, item, index }: { details?: CowHookDetails; item: HookToDappMatch; index: number }) {
const [isOpen, setIsOpen] = useState(false)

const simulationData = useSimulationData(details?.uuid)

const handleLinkClick = () => {
clickOnHooks(item.dapp?.name || 'Unknown hook dapp')
}
Expand All @@ -37,6 +40,16 @@ export function HookItem({ item, index }: { item: HookToDappMatch; index: number
<styledEl.HookItemContent>
{item.dapp && (
<>
{simulationData && (
<p>
<b>Simulation:</b>
<styledEl.SimulationLink status={simulationData.status}>
<a href={simulationData.link} target="_blank" rel="noopener noreferrer">
{simulationData.status ? 'Simulation successful' : 'Simulation failed'}
</a>
</styledEl.SimulationLink>
</p>
)}
<p>
<b>Description:</b> {item.dapp.descriptionShort}
</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,12 @@ export const ToggleIcon = styled.div<{ isOpen: boolean }>`
}
}
`

export const SimulationLink = styled.span<{ status: boolean }>`
color: var(${({ status }) => (status ? UI.COLOR_SUCCESS : UI.COLOR_DANGER)});
border-radius: 8px;

&:hover {
color: var(${({ status }) => (status ? UI.COLOR_SUCCESS_TEXT : UI.COLOR_DANGER_TEXT)});
}
`
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { ReactElement, useEffect, useMemo, useState } from 'react'

import { latest } from '@cowprotocol/app-data'
import { HookToDappMatch, matchHooksToDappsRegistry } from '@cowprotocol/hook-dapp-lib'
import { CowHookDetails, HookToDappMatch, matchHooksToDappsRegistry } from '@cowprotocol/hook-dapp-lib'
import { InfoTooltip } from '@cowprotocol/ui'

import { ChevronDown, ChevronUp } from 'react-feather'

import { AppDataInfo, decodeAppData } from 'modules/appData'
import { useCustomHookDapps } from 'modules/hooksStore'
import { useCustomHookDapps, useHooks, useHooksStateWithSimulatedGas } from 'modules/hooksStore'
import { useTenderlyBundleSimulation } from 'modules/tenderly/hooks/useTenderlyBundleSimulation'

import { HookItem } from './HookItem'
Expand All @@ -28,6 +28,8 @@ export function OrderHooksDetails({ appData, children, margin }: OrderHooksDetai
const preCustomHookDapps = useCustomHookDapps(true)
const postCustomHookDapps = useCustomHookDapps(false)

const hooks = useHooksStateWithSimulatedGas()

const { mutate, isValidating, data } = useTenderlyBundleSimulation()

useEffect(() => {
Expand Down Expand Up @@ -74,8 +76,8 @@ export function OrderHooksDetails({ appData, children, margin }: OrderHooksDetai
</styledEl.Summary>
{isOpen && (
<styledEl.Details>
<HooksInfo data={preHooksToDapp} title="Pre Hooks" />
<HooksInfo data={postHooksToDapp} title="Post Hooks" />
<HooksInfo data={preHooksToDapp} hooks={hooks.preHooks} title="Pre Hooks" />
<HooksInfo data={postHooksToDapp} hooks={hooks.postHooks} title="Post Hooks" />
</styledEl.Details>
)}
</styledEl.Wrapper>,
Expand All @@ -85,9 +87,10 @@ export function OrderHooksDetails({ appData, children, margin }: OrderHooksDetai
interface HooksInfoProps {
data: HookToDappMatch[]
title: string
hooks: CowHookDetails[]
}

function HooksInfo({ data, title }: HooksInfoProps) {
function HooksInfo({ data, title, hooks }: HooksInfoProps) {
return (
<>
{data.length ? (
Expand All @@ -96,9 +99,11 @@ function HooksInfo({ data, title }: HooksInfoProps) {
{title} <CircleCount>{data.length}</CircleCount>
</h3>
<styledEl.HooksList>
{data.map((item, index) => (
<HookItem key={item.hook.callData + item.hook.target + item.hook.gasLimit} item={item} index={index} />
))}
{data.map((item, index) => {
const key = item.hook.callData + item.hook.target + item.hook.gasLimit
const details = hooks.find(({ hook }) => key === hook.callData + hook.target + hook.gasLimit)
return <HookItem key={key} item={item} index={index} details={details} />
})}
</styledEl.HooksList>
</styledEl.InfoWrapper>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { GasPriceStrategyUpdater } from 'legacy/state/gas/gas-price-strategy-upd

import { addListAnalytics, removeListAnalytics } from 'modules/analytics'
import { UploadToIpfsUpdater } from 'modules/appData/updater/UploadToIpfsUpdater'
import { BalancesCombinedUpdater } from 'modules/combinedBalances/updater/BalancesCombinedUpdater'
import { CowEventsUpdater, InjectedWidgetUpdater, useInjectedWidgetParams } from 'modules/injectedWidget'
import { FinalizeTxUpdater } from 'modules/onchainTransactions'
import { OrdersNotificationsUpdater } from 'modules/orders'
Expand Down Expand Up @@ -90,6 +91,7 @@ export function Updaters() {
<PoolsInfoUpdater />
<LpTokensWithBalancesUpdater />
<VampireAttackUpdater />
<BalancesCombinedUpdater />
</>
)
}
Original file line number Diff line number Diff line change
@@ -1,45 +1,7 @@
import { useMemo } from 'react'
import { useAtomValue } from 'jotai'

import { BalancesState, useTokensBalances } from '@cowprotocol/balances-and-allowances'
import { useWalletInfo } from '@cowprotocol/wallet'

import { BigNumber } from 'ethers'

import { usePreHookBalanceDiff } from 'modules/hooksStore/hooks/useBalancesDiff'
import { useIsHooksTradeType } from 'modules/trade'
import { balancesCombinedAtom } from '../state/balanceCombinedAtom'

export function useTokensBalancesCombined() {
const { account } = useWalletInfo()
const preHooksBalancesDiff = usePreHookBalanceDiff()
const tokenBalances = useTokensBalances()
const isHooksTradeType = useIsHooksTradeType()

return useMemo(() => {
if (!account || !isHooksTradeType) return tokenBalances
const accountBalancesDiff = preHooksBalancesDiff[account.toLowerCase()] || {}
return applyBalanceDiffs(tokenBalances, accountBalancesDiff)
}, [account, preHooksBalancesDiff, tokenBalances, isHooksTradeType])
}

function applyBalanceDiffs(currentBalances: BalancesState, balanceDiff: Record<string, string>): BalancesState {
// Get all unique addresses from both objects
const allAddresses = [...new Set([...Object.keys(currentBalances.values), ...Object.keys(balanceDiff)])]

const normalizedValues = allAddresses.reduce(
(acc, address) => {
const currentBalance = currentBalances.values[address] || BigNumber.from(0)
const diff = balanceDiff[address] ? BigNumber.from(balanceDiff[address]) : BigNumber.from(0)

return {
...acc,
[address]: currentBalance.add(diff),
}
},
{} as Record<string, BigNumber>,
)

return {
isLoading: currentBalances.isLoading,
values: normalizedValues,
}
return useAtomValue(balancesCombinedAtom)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { atomWithReset } from 'jotai/utils'

import { BalancesState } from '@cowprotocol/balances-and-allowances'

export const balancesCombinedAtom = atomWithReset<BalancesState>({ isLoading: false, values: {} })
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { useEffect } from 'react'

import { BalancesState, useTokensBalances } from '@cowprotocol/balances-and-allowances'
import { useWalletInfo } from '@cowprotocol/wallet'

import { BigNumber } from 'ethers'

import { useHooks } from 'modules/hooksStore'
import { usePreHookBalanceDiff } from 'modules/hooksStore/hooks/useBalancesDiff'
import { useIsHooksTradeType } from 'modules/trade'
import { useSetAtom } from 'jotai'
import { balancesCombinedAtom } from '../state/balanceCombinedAtom'

export function BalancesCombinedUpdater() {
const { account } = useWalletInfo()
const setBalancesCombined = useSetAtom(balancesCombinedAtom)
const preHooksBalancesDiff = usePreHookBalanceDiff()
const { preHooks } = useHooks()
const tokenBalances = useTokensBalances()
const isHooksTradeType = useIsHooksTradeType()

useEffect(() => {
if (!account || !isHooksTradeType || !preHooks.length) {
setBalancesCombined(tokenBalances)
return
}
const accountBalancesDiff = preHooksBalancesDiff[account.toLowerCase()] || {}
setBalancesCombined(applyBalanceDiffs(tokenBalances, accountBalancesDiff))
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you know why this is being re-computed still every few seconds?

image

Maybe this can help to figure it out https://github.com/simbathesailor/use-what-changed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

IIUC is because the tokenBalances state is updated every few seconds.

Copy link
Contributor

Choose a reason for hiding this comment

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

Then our fault, we will need to look at this tomorrow, cause my balance were not changing, so probably is not correctly memorized and its likely there's a lot more parts of the app that gets redrawn because of this

}, [account, preHooksBalancesDiff, isHooksTradeType, tokenBalances])

return null
}

function applyBalanceDiffs(currentBalances: BalancesState, balanceDiff: Record<string, string>): BalancesState {
const normalizedValues = { ...currentBalances.values }

// Only process addresses that have balance differences
// This optimizes since the balances diff object is usually smaller than the balances object
Object.entries(balanceDiff).forEach(([address, diff]) => {
const currentBalance = normalizedValues[address]
if (currentBalance === undefined) return
const balanceWithDiff = currentBalance.add(BigNumber.from(diff))

// If the balance with diff is negative, set the balance to 0
// This avoid the UI crashing in case of some error
normalizedValues[address] = balanceWithDiff.isNegative()
? BigNumber.from(0)
: currentBalance.add(BigNumber.from(diff))
})

return {
isLoading: currentBalances.isLoading,
values: normalizedValues,
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { useMemo } from 'react'

import ICON_CHECK_ICON from '@cowprotocol/assets/cow-swap/check-singular.svg'
import ICON_GRID from '@cowprotocol/assets/cow-swap/grid.svg'
import TenderlyLogo from '@cowprotocol/assets/cow-swap/tenderly-logo.svg'
Expand All @@ -10,6 +8,7 @@ import { InfoTooltip } from '@cowprotocol/ui'
import { Edit2, Trash2, ExternalLink as ExternalLinkIcon, RefreshCw } from 'react-feather'
import SVG from 'react-inlinesvg'

import { useSimulationData } from 'modules/tenderly/hooks/useSimulationData'
import { useTenderlyBundleSimulation } from 'modules/tenderly/hooks/useTenderlyBundleSimulation'

import * as styledEl from './styled'
Expand All @@ -31,12 +30,9 @@ interface HookItemProp {
const isBundleSimulationReady = true

export function AppliedHookItem({ account, hookDetails, dapp, isPreHook, editHook, removeHook, index }: HookItemProp) {
const { isValidating, data, mutate } = useTenderlyBundleSimulation()
const { isValidating, mutate } = useTenderlyBundleSimulation()

const simulationData = useMemo(() => {
if (!data) return
return data[hookDetails.uuid]
}, [data, hookDetails.uuid])
const simulationData = useSimulationData(hookDetails.uuid)

const simulationStatus = simulationData?.status ? 'Simulation successful' : 'Simulation failed'
const simulationTooltip = simulationData?.status
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useMemo } from 'react'

import { useTenderlyBundleSimulation } from './useTenderlyBundleSimulation'

export function useSimulationData(hookUuid?: string) {
const { data } = useTenderlyBundleSimulation()

return useMemo(() => {
if (!data || !hookUuid) return
return data[hookUuid]
}, [data, hookUuid])
}
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ export function useTenderlyBundleSimulation() {
revalidateOnFocus: false,
revalidateOnReconnect: false,
refreshWhenOffline: false,
revalidateOnMount: false,
},
)
}
Loading