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

POC of tenderly simulation refactoring #4914

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
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
11 changes: 0 additions & 11 deletions apps/cowswap-frontend/src/modules/hooksStore/const.ts

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { useSetupHooksStoreOrderParams } from '../../hooks/useSetupHooksStoreOrd
import { HookRegistryList } from '../HookRegistryList'
import { PostHookButton } from '../PostHookButton'
import { PreHookButton } from '../PreHookButton'
import { BundleTenderlySimulate } from '../TenderlyBundleSimulation'

type HookPosition = 'pre' | 'post'

Expand Down Expand Up @@ -66,7 +67,10 @@ export function HooksStoreWidget() {
)

const BottomContent = shouldNotUseHooks ? null : (
<PostHookButton onOpen={() => setSelectedHookPosition('post')} onEditHook={onPostHookEdit} />
<>
<PostHookButton onOpen={() => setSelectedHookPosition('post')} onEditHook={onPostHookEdit} />
<BundleTenderlySimulate />
</>
)

return <SwapWidget topContent={TopContent} bottomContent={BottomContent} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ export function PostHookButton({ onOpen, onEditHook }: PostHookButtonProps) {
<>
{postHooks.length > 0 && (
<AppliedHookList
account={account}
hooks={postHooks}
account={account}
isPreHook={false} // Indicate that these are post-hooks
removeHook={removeHook}
editHook={onEditHook}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface PreHookButtonProps {

export function PreHookButton({ onOpen, onEditHook }: PreHookButtonProps) {
const { account } = useWalletInfo()

const { preHooks } = useHooks()
const removeHook = useRemoveHook()
const moveHook = useReorderHooks('preHooks')
Expand All @@ -26,9 +27,9 @@ export function PreHookButton({ onOpen, onEditHook }: PreHookButtonProps) {
<>
{preHooks.length > 0 && (
<AppliedHookList
account={account}
hooks={preHooks}
isPreHook={true}
account={account}
removeHook={removeHook}
editHook={onEditHook}
moveHook={moveHook}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { atom, useAtom } from 'jotai'
import { useCallback, useMemo, useState } from 'react'

import { ButtonOutlined, Loader } from '@cowprotocol/ui'

import { ErrorText, ErrorWrapper, LoaderWrapper } from './styled'

import { useHooks } from 'modules/hooksStore/hooks/useHooks'
import { useOrderParams } from 'modules/hooksStore/hooks/useOrderParams'
import { useTokenContract } from 'common/hooks/useContract'
import { useTenderlyBundleSimulate } from 'modules/tenderly/hooks/useTenderlyBundleSimulate'
import { CowHook, HookDappOrderParams } from 'modules/hooksStore/types/hooks'
import { useWalletInfo } from '@cowprotocol/wallet'

const tenderlySimulationSuccessAtom = atom<Record<string, boolean | undefined>>({})

export function BundleTenderlySimulate() {
const { chainId, account } = useWalletInfo()
const hooksData = useHooks()
const orderParams = useOrderParams()
const sellToken = useTokenContract(orderParams?.sellTokenAddress)
const buyToken = useTokenContract(orderParams?.buyTokenAddress)
const preHooks = hooksData?.preHooks.map(({ hook }) => hook)
const postHooks = hooksData?.postHooks.map(({ hook }) => hook)

// update this later
const hooksId = useMemo(
() => getSimulationId({ preHooks, postHooks, orderParams }),
[preHooks, postHooks, orderParams],
)

const [simulationsSuccess, setSimulationsSuccess] = useAtom(tenderlySimulationSuccessAtom)
const simulationSuccess = simulationsSuccess[hooksId]

const [isLoading, setIsLoading] = useState<boolean>(false)
const simulate = useTenderlyBundleSimulate()

const onSimulate = useCallback(async () => {
if (!sellToken || !buyToken || !orderParams || !account) return
setIsLoading(true)

try {
const simulateSuccess = await simulate({
preHooks,
postHooks,
orderParams,
tokenSell: sellToken,
tokenBuy: buyToken,
chainId,
account,
})

setSimulationsSuccess({ [hooksId]: simulateSuccess })
} catch (error: any) {
setSimulationsSuccess({ [hooksId]: false })
} finally {
setIsLoading(false)
}
}, [simulate, preHooks, postHooks, sellToken, buyToken, orderParams, hooksId, chainId, account])

if (isLoading) {
return (
<LoaderWrapper>
<Loader />
</LoaderWrapper>
)
}

if (simulationSuccess) {
return <ButtonOutlined disabled>Success</ButtonOutlined>
}

if (!simulationSuccess && simulationSuccess !== undefined) {
return (
<ErrorWrapper>
<ErrorText>Error</ErrorText>
<ButtonOutlined onClick={onSimulate}>Retry</ButtonOutlined>
</ErrorWrapper>
)
}

return (
<ButtonOutlined onClick={onSimulate} disabled={!sellToken || !buyToken || !orderParams}>
Simulate
</ButtonOutlined>
)
}

function getSimulationId({
preHooks,
postHooks,
orderParams,
}: {
preHooks: CowHook[]
postHooks: CowHook[]
orderParams: HookDappOrderParams | null
}) {
if (!orderParams) return ''
const preHooksPart = preHooks.map(({ target, callData, gasLimit }) => `${target}:${callData}:${gasLimit}`).join(':')
const orderPart = `${orderParams.sellTokenAddress}:${orderParams.buyTokenAddress}:${orderParams.sellAmount}:${orderParams.buyAmount}`
const postHooksPart = postHooks.map(({ target, callData, gasLimit }) => `${target}:${callData}:${gasLimit}`).join(':')
return `${preHooksPart}-${orderPart}-${postHooksPart}`
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { UI } from '@cowprotocol/ui'

import styled from 'styled-components/macro'

export const ExternalLinkContent = styled.span`
display: inline-flex;
align-items: center;
gap: 4px;

&:hover {
text-decoration: underline;
}
`

export const LoaderWrapper = styled.div`
margin: 0 20px;
`

export const ErrorWrapper = styled.div`
text-align: right;
display: flex;
align-items: end;
flex-direction: column;
gap: 5px;
`

export const ErrorText = styled.div`
color: var(${UI.COLOR_ALERT_TEXT});
`
Original file line number Diff line number Diff line change
@@ -1,75 +1,17 @@
import { atom, useAtom } from 'jotai'
import { useCallback, useState } from 'react'
import { ExternalLink, LinkIcon } from '@cowprotocol/ui'

import { errorToString } from '@cowprotocol/common-utils'
import { ButtonOutlined, ExternalLink, LinkIcon, Loader } from '@cowprotocol/ui'
import { useHookSimulationLink } from 'modules/tenderly/state/simulationLink'

import { ErrorText, ErrorWrapper, ExternalLinkContent, LoaderWrapper } from './styled'
import { ExternalLinkContent } from './styled'

import { getSimulationLink } from '../../const'
import { useTenderlySimulate } from '../../hooks/useTenderlySimulate'
import { CowHook } from '../../types/hooks'
import { SimulationError, TenderlySimulation } from '../../types/TenderlySimulation'

interface TenderlySimulateProps {
hook: CowHook
}

function isSimulationSuccessful(res: TenderlySimulation | SimulationError): res is TenderlySimulation {
return !!(res as TenderlySimulation).simulation
}

const tenderlySimulationLinksAtom = atom<Record<string, string | undefined>>({})
const tenderlySimulationErrorsAtom = atom<Record<string, string | undefined>>({})

export function TenderlySimulate({ hook }: TenderlySimulateProps) {
const hookId = [hook.target, hook.callData, hook.gasLimit].join(':')
const [simulationLinks, setSimulationLink] = useAtom(tenderlySimulationLinksAtom)
const simulationLink = simulationLinks[hookId]

const [simulationErrors, setSimulationError] = useAtom(tenderlySimulationErrorsAtom)
const simulationError = simulationErrors[hookId]

const [isLoading, setIsLoading] = useState<boolean>(false)
const simulate = useTenderlySimulate()

const onSimulate = useCallback(async () => {
setIsLoading(true)

try {
const response = await simulate(hook)

if (isSimulationSuccessful(response)) {
const link = getSimulationLink(response.simulation.id)

setSimulationLink({ [hookId]: link })
setSimulationError({ [hookId]: undefined })
} else {
setSimulationError({ [hookId]: response.error.message })
}
} catch (error: any) {
setSimulationError({ [hookId]: errorToString(error) })
} finally {
setIsLoading(false)
}
}, [simulate, hook, hookId])

if (isLoading) {
return (
<LoaderWrapper>
<Loader />
</LoaderWrapper>
)
}

if (simulationError) {
return (
<ErrorWrapper>
<ErrorText>{simulationError}</ErrorText>
<ButtonOutlined onClick={onSimulate}>Retry</ButtonOutlined>
</ErrorWrapper>
)
}
const simulationLink = useHookSimulationLink(hook)

if (simulationLink) {
return (
Expand All @@ -81,5 +23,5 @@ export function TenderlySimulate({ hook }: TenderlySimulateProps) {
)
}

return <ButtonOutlined onClick={onSimulate}>Simulate</ButtonOutlined>
return <></>
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useSetAtom } from 'jotai'
import { useCallback } from 'react'


import { v4 as uuidv4 } from 'uuid'

import { setHooksAtom } from '../state/hookDetailsAtom'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export function useSetupHooksStoreOrderParams() {
validTo: orderParams.validTo,
sellTokenAddress: getCurrencyAddress(orderParams.inputAmount.currency),
buyTokenAddress: getCurrencyAddress(orderParams.outputAmount.currency),
sellAmount: orderParams.inputAmount,
buyAmount: orderParams.outputAmount,
receiver: orderParams.recipient,
})
}, [orderParams])
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { InfoTooltip } from '@cowprotocol/ui'
import { Edit2, Trash2 } from 'react-feather'
import SVG from 'react-inlinesvg'

import { TenderlySimulate } from 'modules/hooksStore/containers/TenderlySimulate'

import * as styledEl from './styled'

import { TenderlySimulate } from '../../containers/TenderlySimulate'
import { CowHookDetailsSerialized } from '../../types/hooks'

interface HookItemProp {
account: string | undefined
account?: string
hookDetails: CowHookDetailsSerialized
isPreHook: boolean
removeHook: (uuid: string, isPreHook: boolean) => void
Expand Down Expand Up @@ -42,7 +43,6 @@ export function AppliedHookItem({ account, hookDetails, isPreHook, editHook, rem
</styledEl.ActionBtn>
</styledEl.HookItemActions>
</styledEl.HookItemHeader>

{account && (
<styledEl.SimulateContainer>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const HookList = styled.ul`
`

interface AppliedHookListProps {
account: string | undefined
account?: string
hooks: CowHookDetailsSerialized[]
isPreHook: boolean
removeHook: (uuid: string, isPreHook: boolean) => void
Expand Down Expand Up @@ -59,8 +59,8 @@ export function AppliedHookList({ account, hooks, isPreHook, removeHook, editHoo
<AppliedHookItem
key={hookDetails.uuid}
index={index}
account={account}
hookDetails={hookDetails}
account={account}
isPreHook={isPreHook}
removeHook={removeHook}
editHook={editHook}
Expand Down
Loading
Loading