Skip to content

Commit

Permalink
feat(twap): generate all part orders (#2759)
Browse files Browse the repository at this point in the history
  • Loading branch information
shoom3301 authored Jul 5, 2023
1 parent 19ed7ee commit 7901e1a
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 126 deletions.
6 changes: 0 additions & 6 deletions src/api/gnosisProtocol/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,12 +115,6 @@ function useTwapChildOrders(prodOrders: EnrichedOrder[] | undefined): OrderWithC
}, [twapParticleOrders, prodOrders])
}

export function useHasOrders(account?: string | null): boolean | undefined {
const gpOrders = useGpOrders(account)

return (gpOrders?.length || 0) > 0
}

export type UseSurplusAmountResult = {
surplusAmount: Nullish<CurrencyAmount<Currency>>
isLoading: boolean
Expand Down
2 changes: 1 addition & 1 deletion src/common/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export type OrderWithComposableCowInfo = {

export type SafeTransactionParams = {
submissionDate: string
executionDate: string
executionDate: string | null
isExecuted: boolean
nonce: number
confirmationsRequired: number
Expand Down
2 changes: 2 additions & 0 deletions src/modules/twap/containers/TwapFormWidget/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { partsStateAtom } from '../../state/partsStateAtom'
import { twapTimeIntervalAtom } from '../../state/twapOrderAtom'
import { twapOrdersSettingsAtom, updateTwapOrdersSettingsAtom } from '../../state/twapOrdersSettingsAtom'
import { FallbackHandlerVerificationUpdater } from '../../updaters/FallbackHandlerVerificationUpdater'
import { PartOrdersUpdater } from '../../updaters/PartOrdersUpdater'
import { TwapOrdersUpdater } from '../../updaters/TwapOrdersUpdater'
import { deadlinePartsDisplay } from '../../utils/deadlinePartsDisplay'
import { ActionButtons } from '../ActionButtons'
Expand Down Expand Up @@ -82,6 +83,7 @@ export function TwapFormWidget() {
<>
<QuoteObserverUpdater />
<FallbackHandlerVerificationUpdater />
<PartOrdersUpdater />
{shouldLoadTwapOrders && (
<TwapOrdersUpdater composableCowContract={composableCowContract} safeAddress={account} chainId={chainId} />
)}
Expand Down
84 changes: 0 additions & 84 deletions src/modules/twap/hooks/useFetchTwapPartOrders.ts

This file was deleted.

22 changes: 6 additions & 16 deletions src/modules/twap/state/twapPartOrdersAtom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,19 @@ import { atomWithStorage } from 'jotai/utils'

import { OrderParameters, SupportedChainId } from '@cowprotocol/cow-sdk'

import deepEqual from 'fast-deep-equal'

import { walletInfoAtom } from 'modules/wallet/api/state'

export interface TwapPartOrderItem {
uid: string
index: number
chainId: SupportedChainId
safeAddress: string
twapOrderId: string
order: OrderParameters
signature: string
}
export type TwapPartOrders = { [twapOrderHash: string]: TwapPartOrderItem }

export const twapPartOrdersAtom = atomWithStorage<TwapPartOrders>('twap-part-orders-list:v1', {})
export type TwapPartOrders = { [twapOrderHash: string]: TwapPartOrderItem[] }

export const updateTwapPartOrdersAtom = atom(null, (get, set, nextState: TwapPartOrders) => {
const currentState = get(twapPartOrdersAtom)

if (!deepEqual(currentState, nextState)) {
set(twapPartOrdersAtom, nextState)
}
})
export const twapPartOrdersAtom = atomWithStorage<TwapPartOrders>('twap-part-orders-list:v2', {})

export const twapPartOrdersListAtom = atom<TwapPartOrderItem[]>((get) => {
const { account, chainId } = get(walletInfoAtom)
Expand All @@ -34,7 +24,7 @@ export const twapPartOrdersListAtom = atom<TwapPartOrderItem[]>((get) => {

const accountLowerCase = account.toLowerCase()

return Object.values(get(twapPartOrdersAtom)).filter(
(order) => order.safeAddress === accountLowerCase && order.chainId === chainId
)
const orders = Object.values(get(twapPartOrdersAtom))

return orders.flat().filter((order) => order.safeAddress === accountLowerCase && order.chainId === chainId)
})
120 changes: 120 additions & 0 deletions src/modules/twap/updaters/PartOrdersUpdater.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
import { useEffect } from 'react'

import { Order } from '@cowprotocol/contracts'
import { OrderParameters, SupportedChainId } from '@cowprotocol/cow-sdk'

import { isTruthy } from 'legacy/utils/misc'

import { useWalletInfo } from 'modules/wallet'

import { computeOrderUid } from 'utils/orderUtils/computeOrderUid'

import { twapOrdersListAtom } from '../state/twapOrdersListAtom'
import { TwapPartOrderItem, twapPartOrdersAtom } from '../state/twapPartOrdersAtom'
import { TwapOrderItem } from '../types'

export function PartOrdersUpdater() {
const { chainId, account } = useWalletInfo()
const twapOrdersList = useAtomValue(twapOrdersListAtom)
const updateTwapPartOrders = useUpdateAtom(twapPartOrdersAtom)

useEffect(() => {
if (!chainId || !account) return

const accountLowerCase = account.toLowerCase()
const twapOrders = Object.values(twapOrdersList)

const ordersParts$ = twapOrders.map((twapOrder) => {
return generateTwapOrderParts(twapOrder, accountLowerCase, chainId)
})

Promise.all(ordersParts$).then((ordersParts) => {
const ordersMap = ordersParts.reduce((acc, item) => {
return {
...acc,
...item,
}
}, {})

updateTwapPartOrders(ordersMap)
})
}, [chainId, account, twapOrdersList, updateTwapPartOrders])

return null
}

async function generateTwapOrderParts(
twapOrder: TwapOrderItem,
safeAddress: string,
chainId: SupportedChainId
): Promise<{ [id: string]: TwapPartOrderItem[] }> {
const twapOrderId = twapOrder.id

const parts = [...new Array(twapOrder.order.n)]
.map((_, index) => createPartOrderFromParent(twapOrder, index))
.filter(isTruthy)

const ids = await Promise.all(parts.map((part) => computeOrderUid(chainId, safeAddress, part as Order)))

return {
[twapOrderId]: ids.map<TwapPartOrderItem>((uid, index) => {
return {
uid,
index,
twapOrderId,
chainId,
safeAddress,
order: parts[index],
}
}),
}
}

function createPartOrderFromParent(twapOrder: TwapOrderItem, index: number): OrderParameters | null {
const executionDate = twapOrder.safeTxParams?.executionDate

if (!executionDate) {
return null
}

const blockTimestamp = new Date(executionDate)

return {
sellToken: twapOrder.order.sellToken,
buyToken: twapOrder.order.buyToken,
receiver: twapOrder.order.receiver,
sellAmount: twapOrder.order.partSellAmount,
buyAmount: twapOrder.order.minPartLimit,
validTo: calculateValidTo({
part: index,
startTime: Math.ceil(blockTimestamp.getTime() / 1000),
span: twapOrder.order.span,
frequency: twapOrder.order.t,
}),
appData: twapOrder.order.appData,
feeAmount: '0',
kind: 'sell',
partiallyFillable: false,
sellTokenBalance: 'erc20',
buyTokenBalance: 'erc20',
} as OrderParameters
}

function calculateValidTo({
part,
startTime,
frequency,
span,
}: {
part: number
startTime: number
frequency: number
span: number
}): number {
if (span === 0) {
return startTime + (part + 1) * frequency - 1
}

return startTime + part * frequency + span - 1
}
14 changes: 2 additions & 12 deletions src/modules/twap/updaters/TwapOrdersUpdater.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import { isTruthy } from 'legacy/utils/misc'

import { TWAP_PENDING_STATUSES } from '../const'
import { useFetchTwapOrdersFromSafe } from '../hooks/useFetchTwapOrdersFromSafe'
import { useFetchTwapPartOrders } from '../hooks/useFetchTwapPartOrders'
import { useTwapDiscreteOrders } from '../hooks/useTwapDiscreteOrders'
import { useTwapOrdersAuthMulticall } from '../hooks/useTwapOrdersAuthMulticall'
import { twapOrdersListAtom, updateTwapOrdersListAtom } from '../state/twapOrdersListAtom'
import { updateTwapPartOrdersAtom } from '../state/twapPartOrdersAtom'
import { TwapOrderInfo } from '../types'
import { buildTwapOrdersItems } from '../utils/buildTwapOrdersItems'
import { getConditionalOrderId } from '../utils/getConditionalOrderId'
Expand All @@ -29,7 +27,6 @@ export function TwapOrdersUpdater(props: {
const twapDiscreteOrders = useTwapDiscreteOrders()
const twapOrdersList = useAtomValue(twapOrdersListAtom)
const updateTwapOrders = useUpdateAtom(updateTwapOrdersListAtom)
const updateTwapPartOrders = useUpdateAtom(updateTwapPartOrdersAtom)
const ordersSafeData = useFetchTwapOrdersFromSafe(props)

const allOrdersInfo: TwapOrderInfo[] = useMemo(() => {
Expand All @@ -38,12 +35,13 @@ export function TwapOrdersUpdater(props: {
try {
const id = getConditionalOrderId(data.conditionalOrderParams)
const order = parseTwapOrderStruct(data.conditionalOrderParams.staticInput)
const { executionDate } = data.safeTxParams

return {
id,
orderStruct: order,
safeData: data,
isExpired: isTwapOrderExpired(order),
isExpired: isTwapOrderExpired(order, executionDate ? new Date(executionDate) : null),
}
} catch (e) {
return null
Expand All @@ -65,23 +63,15 @@ export function TwapOrdersUpdater(props: {
})
}, [allOrdersInfo, twapOrdersList])

const partOrders = useFetchTwapPartOrders(safeAddress, chainId, composableCowContract, pendingOrCancelledOrders)
// Here we know which orders are cancelled: if it's auth === false, then it's cancelled
const ordersAuthResult = useTwapOrdersAuthMulticall(safeAddress, composableCowContract, pendingOrCancelledOrders)

useEffect(() => {
if (!ordersAuthResult || !twapDiscreteOrders) return

const items = buildTwapOrdersItems(chainId, safeAddress, allOrdersInfo, ordersAuthResult, twapDiscreteOrders)

updateTwapOrders(items)
}, [chainId, safeAddress, allOrdersInfo, ordersAuthResult, twapDiscreteOrders, updateTwapOrders])

useEffect(() => {
if (!partOrders) return

updateTwapPartOrders(partOrders)
}, [partOrders, updateTwapPartOrders])

return null
}
4 changes: 2 additions & 2 deletions src/modules/twap/utils/buildTwapOrdersItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@ function getTwapOrderItem(
discreteOrder: Order | undefined
): TwapOrderItem {
const { conditionalOrderParams, safeTxParams } = safeData
const { isExecuted, submissionDate } = safeTxParams
const { isExecuted, submissionDate, executionDate: _executionDate } = safeTxParams

const executionDate = new Date(safeTxParams.executionDate)
const executionDate = _executionDate ? new Date(_executionDate) : null
const order = parseTwapOrderStruct(conditionalOrderParams.staticInput)
const status = getTwapOrderStatus(order, isExecuted, executionDate, authorized, discreteOrder)

Expand Down
15 changes: 10 additions & 5 deletions src/modules/twap/utils/getTwapOrderStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ const AUTH_THRESHOLD = ms`1m`
export function getTwapOrderStatus(
order: TWAPOrderStruct,
isExecuted: boolean,
executionDate: Date,
executionDate: Date | null,
auth: boolean | undefined,
discreteOrder: Order | undefined
): TwapOrderStatus {
if (isTwapOrderExpired(order)) return TwapOrderStatus.Expired
if (isTwapOrderExpired(order, executionDate)) return TwapOrderStatus.Expired

if (!isExecuted) return TwapOrderStatus.WaitSigning

Expand All @@ -24,8 +24,11 @@ export function getTwapOrderStatus(
return TwapOrderStatus.Scheduled
}

export function isTwapOrderExpired(order: TWAPOrderStruct): boolean {
const { t0: startTime, n: numOfParts, t: timeInterval } = order
export function isTwapOrderExpired(order: TWAPOrderStruct, startDate: Date | null): boolean {
if (!startDate) return false

const startTime = Math.ceil(startDate.getTime() / 1000)
const { n: numOfParts, t: timeInterval } = order
const endTime = startTime + timeInterval * numOfParts
const nowTimestamp = Math.ceil(Date.now() / 1000)

Expand All @@ -36,7 +39,9 @@ export function isTwapOrderExpired(order: TWAPOrderStruct): boolean {
* ComposableCow.singleOrders returns false by default
* To avoid false-positive values, we should not check authorized flag within first minute after execution time
*/
function shouldCheckAuth(executionDate: Date): boolean {
function shouldCheckAuth(executionDate: Date | null): boolean {
if (!executionDate) return false

const executionTimestamp = executionDate.getTime()
const nowTimestamp = Date.now()

Expand Down

0 comments on commit 7901e1a

Please sign in to comment.