-
Notifications
You must be signed in to change notification settings - Fork 111
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
feat(hook-store): override Hook Gas limit and tokens balances with Tenderly Simulation Data #5039
Changes from all commits
3ac32e9
2260a54
e1b5c4c
4bae430
be46417
8bed508
12e0ee5
450e330
495a56b
7eff54d
beccb79
0d3254c
2726bb1
6ea9624
732ecc9
5cb5b72
22501d4
c80d504
b8bb1f4
f9ade14
5c3f35e
5667d1f
aab2974
050c189
21eb41c
468af55
eff88c7
dd857e3
2a123c5
4e9741f
48da7de
74a8c0c
e78b56b
06e8f32
f2bc296
e30bfc4
1745ea6
4d83f03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import { useMemo } from 'react' | ||
|
||
import { TokenWithLogo } from '@cowprotocol/common-const' | ||
import { CurrencyAmount } from '@uniswap/sdk-core' | ||
|
||
import { useTokensBalancesCombined } from './useTokensBalancesCombined' | ||
|
||
export function useCurrencyAmountBalanceCombined( | ||
token: TokenWithLogo | undefined | null, | ||
): CurrencyAmount<TokenWithLogo> | undefined { | ||
const { values: balances } = useTokensBalancesCombined() | ||
|
||
return useMemo(() => { | ||
if (!token) return undefined | ||
|
||
const balance = balances[token.address.toLowerCase()] | ||
|
||
if (!balance) return undefined | ||
|
||
return CurrencyAmount.fromRawAmount(token, balance.toHexString()) | ||
}, [token, balances]) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import { useMemo } from 'react' | ||
|
||
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' | ||
|
||
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, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './hooks/useCurrencyAmountBalanceCombined' | ||
export * from './hooks/useTokensBalancesCombined' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
import { useTenderlyBundleSimulation } from 'modules/tenderly/hooks/useTenderlyBundleSimulation' | ||
import { useOrderParams } from './useOrderParams' | ||
import { useHooks } from './useHooks' | ||
import { useMemo } from 'react' | ||
import { useWalletInfo } from '@cowprotocol/wallet' | ||
import { BalancesDiff } from 'modules/tenderly/types' | ||
import { BigNumber } from 'ethers' | ||
|
||
const EMPTY_BALANCE_DIFF: BalancesDiff = {} | ||
|
||
export function usePreHookBalanceDiff(): BalancesDiff { | ||
const { data } = useTenderlyBundleSimulation() | ||
|
||
const { preHooks } = useHooks() | ||
|
||
return useMemo(() => { | ||
if (!data || !preHooks.length) return EMPTY_BALANCE_DIFF | ||
|
||
const lastPreHook = preHooks[preHooks.length - 1] | ||
return data[lastPreHook?.uuid]?.cumulativeBalancesDiff || EMPTY_BALANCE_DIFF | ||
}, [data, preHooks]) | ||
} | ||
|
||
// Returns all the ERC20 Balance Diff of the current hook to be passed to the iframe context | ||
export function useHookBalancesDiff(isPreHook: boolean, hookToEditUid?: string): BalancesDiff { | ||
const { account } = useWalletInfo() | ||
const { data } = useTenderlyBundleSimulation() | ||
const orderParams = useOrderParams() | ||
const { preHooks, postHooks } = useHooks() | ||
const preHookBalanceDiff = usePreHookBalanceDiff() | ||
|
||
// balance diff expected from the order without the simulation | ||
// this is used when the order isn't simulated like in the case of only preHooks | ||
const orderExpectedBalanceDiff = useMemo(() => { | ||
if (!account) return EMPTY_BALANCE_DIFF | ||
const balanceDiff: Record<string, string> = {} | ||
|
||
if (orderParams?.buyAmount && orderParams.buyTokenAddress && account) | ||
balanceDiff[orderParams.buyTokenAddress.toLowerCase()] = orderParams.buyAmount | ||
|
||
if (orderParams?.sellAmount && orderParams.sellTokenAddress && account) | ||
balanceDiff[orderParams.sellTokenAddress.toLowerCase()] = `-${orderParams.sellAmount}` | ||
|
||
return { account: balanceDiff } | ||
}, [orderParams, account]) | ||
|
||
const firstPostHookBalanceDiff = useMemo(() => { | ||
return mergeBalanceDiffs(preHookBalanceDiff, orderExpectedBalanceDiff) | ||
}, [preHookBalanceDiff, orderExpectedBalanceDiff]) | ||
|
||
const postHookBalanceDiff = useMemo(() => { | ||
// is adding the first post hook or simulation not available | ||
if (!data || !postHooks) return firstPostHookBalanceDiff | ||
|
||
const lastPostHook = postHooks[postHooks.length - 1] | ||
return data[lastPostHook?.uuid]?.cumulativeBalancesDiff || firstPostHookBalanceDiff | ||
}, [data, postHooks, orderExpectedBalanceDiff, preHookBalanceDiff]) | ||
|
||
const hookToEditBalanceDiff = useMemo(() => { | ||
if (!data || !hookToEditUid) return EMPTY_BALANCE_DIFF | ||
|
||
const otherHooks = isPreHook ? preHooks : postHooks | ||
|
||
const hookToEditIndex = otherHooks.findIndex((hook) => hook.uuid === hookToEditUid) | ||
|
||
// is editing first preHook -> return empty state | ||
if (!hookToEditIndex && isPreHook) return EMPTY_BALANCE_DIFF | ||
|
||
// is editing first postHook -> return | ||
if (!hookToEditIndex && !isPreHook) return firstPostHookBalanceDiff | ||
|
||
// is editing a non first hook, return the latest available hook state | ||
const previousHookIndex = hookToEditIndex - 1 | ||
|
||
return data[otherHooks[previousHookIndex]?.uuid]?.cumulativeBalancesDiff || EMPTY_BALANCE_DIFF | ||
}, [data, hookToEditUid, isPreHook, preHooks, postHooks, firstPostHookBalanceDiff]) | ||
|
||
return useMemo(() => { | ||
if (hookToEditUid) return hookToEditBalanceDiff | ||
if (isPreHook) return preHookBalanceDiff | ||
return postHookBalanceDiff | ||
}, [data, orderParams, preHooks, postHooks]) | ||
} | ||
|
||
function mergeBalanceDiffs(first: BalancesDiff, second: BalancesDiff): BalancesDiff { | ||
const result: BalancesDiff = {} | ||
|
||
// Helper function to add BigNumber strings | ||
|
||
// Process all addresses from first input | ||
for (const address of Object.keys(first)) { | ||
result[address] = { ...first[address] } | ||
} | ||
|
||
// Process all addresses from second input | ||
for (const address of Object.keys(second)) { | ||
yvesfracari marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!result[address]) { | ||
// If address doesn't exist in result, just copy the entire record | ||
result[address] = { ...second[address] } | ||
} else { | ||
// If address exists, we need to merge token balances | ||
for (const token of Object.keys(second[address])) { | ||
if (!result[address][token]) { | ||
// If token doesn't exist for this address, just copy the balance | ||
result[address][token] = second[address][token] | ||
} else { | ||
// If token exists, sum up the balances | ||
try { | ||
result[address][token] = BigNumber.from(result[address][token]).add(second[address][token]).toString() | ||
} catch (error) { | ||
console.error(`Error adding balances for address ${address} and token ${token}:`, error) | ||
throw error | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this logic very similar to the other one that would apply the diffs to the current balances? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that is similar, however is used for only one value instead of all of them and that is why I am not importing the function from there. |
||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
return result | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import { useCallback, useMemo } from 'react' | ||
|
||
import { CowHookDetails } from '@cowprotocol/hook-dapp-lib' | ||
|
||
import { useTenderlyBundleSimulation } from 'modules/tenderly/hooks/useTenderlyBundleSimulation' | ||
|
||
import { useHooks } from './useHooks' | ||
|
||
import { HooksStoreState } from '../state/hookDetailsAtom' | ||
|
||
export function useHooksStateWithSimulatedGas(): HooksStoreState { | ||
const hooksRaw = useHooks() | ||
const { data: tenderlyData } = useTenderlyBundleSimulation() | ||
|
||
const combineHookWithSimulatedGas = useCallback( | ||
(hook: CowHookDetails): CowHookDetails => { | ||
const hookTenderlyData = tenderlyData?.[hook.uuid] | ||
if (!hookTenderlyData?.gasUsed || hookTenderlyData.gasUsed === '0' || !hookTenderlyData.status) return hook | ||
const hookData = { ...hook.hook, gasLimit: hookTenderlyData.gasUsed } | ||
return { ...hook, hook: hookData } | ||
}, | ||
[tenderlyData], | ||
) | ||
|
||
return useMemo(() => { | ||
const preHooksCombined = hooksRaw.preHooks.map(combineHookWithSimulatedGas) | ||
const postHooksCombined = hooksRaw.postHooks.map(combineHookWithSimulatedGas) | ||
return { preHooks: preHooksCombined, postHooks: postHooksCombined } | ||
}, [hooksRaw, combineHookWithSimulatedGas]) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking the type of the
BalancesDiff
I see is a structure to hold token balances. HavingDiff
in its name feels a bit constraining cause you can use it for example to hold the current balances, etc.I would drop the
diff
from the name and would make the merging utilities more generic so they live not in thehookStore
but in a more central place. Or at least can be moved to a utility instead of being in a hook fileThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the main difference between this and a token balances is that it can be negative. Another difference between this and the token balances used on the rest of the app is the first key. Another difference is that the outer key of
BalanceDiff
is theaddress
which the difference is related to while theBalanceState
of the rest of the app the outer key is thechainId
since it is always related to the connected account. For now, I don't see any other place on the app where we would store balances diff from addresses, so I think that there isn't too much gain of moving this to a utility for now.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, makes sense