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

Check existence of the order in the orderbook, and add polledOrderInOrderbook #166

Merged
merged 4 commits into from
Sep 14, 2023
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
9 changes: 9 additions & 0 deletions src/composable/ConditionalOrder.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { mockGetOrder } from '../order-book/__mock__/api'
import {
DEFAULT_ORDER_PARAMS,
TestConditionalOrder,
Expand All @@ -10,9 +11,14 @@
import { constants } from 'ethers'
import { OwnerContext, PollParams, PollResultCode, PollResultErrors } from './types'
import { BuyTokenDestination, OrderKind, SellTokenSource } from '../order-book/generated'
import { computeOrderUid } from '../utils'

jest.mock('./contracts')

jest.mock('../utils')

const mockGetComposableCow = getComposableCow as jest.MockedFunction<typeof getComposableCow>
const mockComputeOrderUid = computeOrderUid as jest.MockedFunction<typeof computeOrderUid>

const TWAP_SERIALIZED = (salt?: string, handler?: string): string => {
return (
Expand Down Expand Up @@ -168,7 +174,7 @@
const mockGetTradeableOrderWithSignature = jest.fn()
const param = { owner: OWNER, chainId: 1, provider: {} } as PollParams

const mockPollValidate = jest.fn<Promise<PollResultErrors | undefined>, [params: PollParams], any>()

Check warning on line 177 in src/composable/ConditionalOrder.spec.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type

class MockTestConditionalOrder extends TestConditionalOrder {
protected pollValidate(params: PollParams): Promise<PollResultErrors | undefined> {
Expand All @@ -187,6 +193,9 @@
getTradeableOrderWithSignature: mockGetTradeableOrderWithSignature,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any)

mockComputeOrderUid.mockReturnValue(Promise.resolve(SINGLE_ORDER.id))
mockGetOrder.mockImplementation(() => Promise.reject('Pretend the order does not exist'))
})

test('[SUCCESS] Happy path', async () => {
Expand Down
48 changes: 47 additions & 1 deletion src/composable/ConditionalOrder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BigNumber, constants, ethers, utils } from 'ethers'
import { IConditionalOrder } from './generated/ComposableCoW'
import { GPv2Order, IConditionalOrder } from './generated/ComposableCoW'

import { decodeParams, encodeParams } from './utils'
import {
Expand All @@ -14,6 +14,11 @@
PollResultErrors,
} from './types'
import { getComposableCow, getComposableCowInterface } from './contracts'
import { OrderBookApi, UID } from 'src/order-book'
import { computeOrderUid } from 'src/utils'
import { Order } from '@cowprotocol/contracts'

const orderBookCache: Record<string, OrderBookApi> = {}

/**
* An abstract base class from which all conditional orders should inherit.
Expand Down Expand Up @@ -281,6 +286,33 @@
[]
)

let orderBookApi = orderBookCache[chainId]
if (!orderBookApi) {
orderBookApi = new OrderBookApi({ chainId })
orderBookCache[chainId] = orderBookApi
}

const orderUid = await computeOrderUid(chainId, owner, order as Order)

// Check if the order is already in the order book
const isOrderInOrderbook = await orderBookApi
.getOrder(orderUid)
.then(() => true)
.catch(() => false)

// Let the concrete Conditional Order decide about the poll result (in the case the order is already in the orderbook)
if (isOrderInOrderbook) {
const pollResult = await this.handlePollFailedAlreadyPresent(orderUid, order, params)
if (pollResult) {
return pollResult
}

return {
result: PollResultCode.TRY_NEXT_BLOCK,
reason: 'Order already in orderbook',
}
}

return {
result: PollResultCode.SUCCESS,
order,
Expand Down Expand Up @@ -330,6 +362,20 @@
*/
protected abstract pollValidate(params: PollParams): Promise<PollResultErrors | undefined>

/**
* This method lets the concrete conditional order decide what to do if the order yielded in the polling is already present in the Orderbook API.
*
* The concrete conditional order will have a chance to schedule the next poll.
* For example, a TWAP order that has the current part already in the orderbook, can signal that the next poll should be done at the start time of the next part.
*
* @param params
*/
protected abstract handlePollFailedAlreadyPresent(
orderUid: UID,
order: GPv2Order.DataStructOutput,
params: PollParams
): Promise<PollResultErrors | undefined>

/**
* Convert the struct that the contract expect as an encoded `staticInput` into a friendly data object modelling the smart order.
*
Expand Down Expand Up @@ -364,7 +410,7 @@
s: string,
handler: string,
orderDataTypes: string[],
callback: (d: any, salt: string) => T

Check warning on line 413 in src/composable/ConditionalOrder.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
): T {
try {
// First, decode the `IConditionalOrder.Params` struct
Expand All @@ -378,7 +424,7 @@

// Create a new instance of the class
return callback(d, salt)
} catch (e: any) {

Check warning on line 427 in src/composable/ConditionalOrder.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
if (e.message === 'HandlerMismatch') {
throw e
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/composable/Multiplexer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'src/order-book/__mock__/api'
import { StandardMerkleTree } from '@openzeppelin/merkle-tree'
import { BigNumber, providers, utils } from 'ethers'

Expand Down Expand Up @@ -97,7 +98,7 @@
*/
static fromJSON(s: string): Multiplexer {
// reviver function to deserialize the orders
const reviver = (k: string, v: any) => {

Check warning on line 101 in src/composable/Multiplexer.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
if (k === 'orders' && typeof v === 'object' && v !== null) {
const orders: Orders = {}

Expand Down Expand Up @@ -434,7 +435,7 @@
*/
public static registerOrderType(
orderType: string,
conditionalOrderClass: new (...args: unknown[]) => ConditionalOrder<unknown, unknown>
conditionalOrderClass: new (...args: any[]) => ConditionalOrder<unknown, unknown>

Check warning on line 438 in src/composable/Multiplexer.ts

View workflow job for this annotation

GitHub Actions / eslint

Unexpected any. Specify a different type
) {
Multiplexer.orderTypeRegistry[orderType] = conditionalOrderClass
}
Expand Down
1 change: 1 addition & 0 deletions src/composable/orderTypes/Twap.spec.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import '../../order-book/__mock__/api'
import { DurationType, StartTimeValue, Twap, TWAP_ADDRESS, TwapData } from './Twap'
import { BigNumber, utils, constants } from 'ethers'

Expand Down
9 changes: 9 additions & 0 deletions src/composable/orderTypes/Twap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
PollResultErrors,
} from '../types'
import { encodeParams, formatEpoch, getBlockInfo, isValidAbi } from '../utils'
import { GPv2Order } from '../generated/ComposableCoW'

// The type of Conditional Order
const TWAP_ORDER_TYPE = 'twap'
Expand Down Expand Up @@ -350,6 +351,14 @@
return undefined
}

protected async handlePollFailedAlreadyPresent(
_orderUid: string,

Check warning on line 355 in src/composable/orderTypes/Twap.ts

View workflow job for this annotation

GitHub Actions / eslint

'_orderUid' is defined but never used
_order: GPv2Order.DataStructOutput,

Check warning on line 356 in src/composable/orderTypes/Twap.ts

View workflow job for this annotation

GitHub Actions / eslint

'_order' is defined but never used
_params: PollParams
): Promise<PollResultErrors | undefined> {
return undefined
anxolin marked this conversation as resolved.
Show resolved Hide resolved
}

/**
* Serialize the TWAP order into it's ABI-encoded form.
* @returns {string} The ABI-encoded TWAP order.
Expand Down
12 changes: 10 additions & 2 deletions src/composable/orderTypes/test/TestConditionalOrder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GPv2Order } from '../../generated/ComposableCoW'
import { ConditionalOrder } from '../../ConditionalOrder'
import { IsValidResult, PollResultErrors } from '../../types'
import { IsValidResult, PollParams, PollResultErrors } from '../../types'
import { encodeParams } from '../../utils'

export const DEFAULT_ORDER_PARAMS: TestConditionalOrderParams = {
Expand Down Expand Up @@ -48,7 +49,14 @@ export class TestConditionalOrder extends ConditionalOrder<string, string> {
return params
}

protected async pollValidate(): Promise<PollResultErrors | undefined> {
protected async pollValidate(_params: PollParams): Promise<PollResultErrors | undefined> {
return undefined
}
protected async handlePollFailedAlreadyPresent(
_orderUid: string,
_order: GPv2Order.DataStructOutput,
_params: PollParams
): Promise<PollResultErrors | undefined> {
return undefined
}

Expand Down
1 change: 1 addition & 0 deletions src/composable/utils.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'src/order-book/__mock__/api'
import { utils, providers } from 'ethers'
import {
COMPOSABLE_COW_CONTRACT_ADDRESS,
Expand Down
9 changes: 9 additions & 0 deletions src/order-book/__mock__/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
jest.mock('../api', () => {
return {
OrderBookApi: class MockedOrderBookApi {
getOrder = mockGetOrder
},
}
})

export const mockGetOrder = jest.fn()
10 changes: 10 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import type { Order } from '@cowprotocol/contracts'
import type { SupportedChainId } from './common'
import { OrderSigningUtils } from './order-signing'

export async function computeOrderUid(chainId: SupportedChainId, owner: string, order: Order): Promise<string> {
const { computeOrderUid: _computeOrderUid } = await import('@cowprotocol/contracts')
const domain = await OrderSigningUtils.getDomain(chainId)

return _computeOrderUid(domain, order, owner)
}
Loading