• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

cowprotocol / cow-sdk / 13793952932

11 Mar 2025 05:05PM UTC coverage: 6.624% (-72.2%) from 78.821%
13793952932

Pull #246

github

anxolin
fix: dont use deprecated eth flow contract
Pull Request #246: feat: draft bridging SDK

21 of 405 branches covered (5.19%)

Branch coverage included in aggregate %.

0 of 49 new or added lines in 5 files covered. (0.0%)

596 existing lines in 43 files now uncovered.

62 of 848 relevant lines covered (7.31%)

0.52 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/composable/ConditionalOrder.ts
1
import { BigNumber, constants, ethers, utils } from 'ethers'
2
import { GPv2Order, IConditionalOrder } from '../common/generated/ComposableCoW'
3

4
import { decodeParams, encodeParams, fromStructToOrder, getIsValidResult } from './utils'
5
import {
6
  ConditionalOrderArguments,
7
  ConditionalOrderParams,
8
  ContextFactory,
9
  IsValidResult,
10
  OwnerContext,
11
  PollParams,
12
  PollResult,
13
  PollResultCode,
14
  PollResultErrors,
15
} from './types'
16
import { getComposableCow, getComposableCowInterface } from './contracts'
17
import { UID } from '../order-book'
18
import { computeOrderUid } from '../utils'
19

20
/**
21
 * An abstract base class from which all conditional orders should inherit.
22
 *
23
 * This class provides some basic functionality to help with handling conditional orders,
24
 * such as:
25
 * - Validating the conditional order
26
 * - Creating a human-readable string representation of the conditional order
27
 * - Serializing the conditional order for use with the `IConditionalOrder` struct
28
 * - Getting any dependencies for the conditional order
29
 * - Getting the off-chain input for the conditional order
30
 *
31
 * **NOTE**: Instances of conditional orders have an `id` property that is a `keccak256` hash of
32
 *           the serialized conditional order.
33
 */
34
export abstract class ConditionalOrder<D, S> {
35
  public readonly handler: string
36
  public readonly salt: string
37
  public readonly data: D
38
  public readonly staticInput: S
39
  public readonly hasOffChainInput: boolean
40

41
  /**
42
   * A constructor that provides some basic validation for the conditional order.
43
   *
44
   * This constructor **MUST** be called by any class that inherits from `ConditionalOrder`.
45
   *
46
   * **NOTE**: The salt is optional and will be randomly generated if not provided.
47
   * @param handler The address of the handler for the conditional order.
48
   * @param salt A 32-byte string used to salt the conditional order.
49
   * @param data The data of the order
50
   * @param hasOffChainInput Whether the conditional order has off-chain input.
51
   * @throws If the handler is not a valid ethereum address.
52
   * @throws If the salt is not a valid 32-byte string.
53
   */
54
  constructor(params: ConditionalOrderArguments<D>) {
UNCOV
55
    const { handler, salt = utils.keccak256(utils.randomBytes(32)), data, hasOffChainInput = false } = params
×
56
    // Verify input to the constructor
57
    // 1. Verify that the handler is a valid ethereum address
UNCOV
58
    if (!ethers.utils.isAddress(handler)) {
×
UNCOV
59
      throw new Error(`Invalid handler: ${handler}`)
×
60
    }
61

62
    // 2. Verify that the salt is a valid 32-byte string usable with ethers
UNCOV
63
    if (!ethers.utils.isHexString(salt) || ethers.utils.hexDataLength(salt) !== 32) {
×
UNCOV
64
      throw new Error(`Invalid salt: ${salt}`)
×
65
    }
66

UNCOV
67
    this.handler = handler
×
UNCOV
68
    this.salt = salt
×
UNCOV
69
    this.data = data
×
UNCOV
70
    this.staticInput = this.transformDataToStruct(data)
×
71

UNCOV
72
    this.hasOffChainInput = hasOffChainInput
×
73
  }
74

75
  // TODO: https://github.com/cowprotocol/cow-sdk/issues/155
76
  abstract get isSingleOrder(): boolean
77

78
  /**
79
   * Get a descriptive name for the type of the conditional order (i.e twap, dca, etc).
80
   *
81
   * @returns {string} The concrete type of the conditional order.
82
   */
83
  abstract get orderType(): string
84

85
  /**
86
   * Get the context dependency for the conditional order.
87
   *
88
   * This is used when calling `createWithContext` or `setRootWithContext` on a ComposableCoW-enabled Safe.
89
   * @returns The context dependency.
90
   */
91
  get context(): ContextFactory | undefined {
UNCOV
92
    return undefined
×
93
  }
94

95
  assertIsValid(): void {
UNCOV
96
    const isValidResult = this.isValid()
×
UNCOV
97
    if (!getIsValidResult(isValidResult)) {
×
UNCOV
98
      throw new Error(`Invalid order: ${isValidResult.reason}`)
×
99
    }
100
  }
101

102
  abstract isValid(): IsValidResult
103

104
  /**
105
   * Get the calldata for creating the conditional order.
106
   *
107
   * This will automatically determine whether or not to use `create` or `createWithContext` based on the
108
   * order type's context dependency.
109
   *
110
   * **NOTE**: By default, this will cause the create to emit the `ConditionalOrderCreated` event.
111
   * @returns The calldata for creating the conditional order.
112
   */
113
  get createCalldata(): string {
114
    this.assertIsValid()
×
115

116
    const context = this.context
×
117
    const composableCow = getComposableCowInterface()
×
118
    const paramsStruct: IConditionalOrder.ConditionalOrderParamsStruct = {
×
119
      handler: this.handler,
120
      salt: this.salt,
121
      staticInput: this.encodeStaticInput(),
122
    }
123

124
    if (context) {
×
125
      // Create (with context)
126
      const contextArgsAbi = context.factoryArgs
×
127
        ? utils.defaultAbiCoder.encode(context.factoryArgs.argsType, context.factoryArgs.args)
128
        : '0x'
129
      return composableCow.encodeFunctionData('createWithContext', [
×
130
        paramsStruct,
131
        context.address,
132
        contextArgsAbi,
133
        true,
134
      ])
135
    } else {
136
      // Create
137
      return composableCow.encodeFunctionData('create', [paramsStruct, true])
×
138
    }
139
  }
140

141
  /**
142
   * Get the calldata for removing a conditional order that was created as a single order.
143
   * @returns The calldata for removing the conditional order.
144
   */
145
  get removeCalldata(): string {
146
    this.assertIsValid()
×
147

148
    return getComposableCowInterface().encodeFunctionData('remove', [this.id])
×
149
  }
150

151
  /**
152
   * Calculate the id of the conditional order (which also happens to be the key used for `ctx` in the ComposableCoW contract).
153
   *
154
   * This is a `keccak256` hash of the serialized conditional order.
155
   * @returns The id of the conditional order.
156
   */
157
  get id(): string {
UNCOV
158
    return utils.keccak256(this.serialize())
×
159
  }
160

161
  /**
162
   * The context key of the order (bytes32(0) if a merkle tree is used, otherwise H(params)) with which to lookup the cabinet
163
   *
164
   * The context, relates to the 'ctx' in the contract: https://github.com/cowprotocol/composable-cow/blob/c7fb85ab10c05e28a1632ba97a1749fb261fcdfb/src/interfaces/IConditionalOrder.sol#L38
165
   */
166
  protected get ctx(): string {
UNCOV
167
    return this.isSingleOrder ? this.id : constants.HashZero
×
168
  }
169

170
  /**
171
   * Get the `leaf` of the conditional order. This is the data that is used to create the merkle tree.
172
   *
173
   * For the purposes of this library, the `leaf` is the `ConditionalOrderParams` struct.
174
   * @returns The `leaf` of the conditional order.
175
   * @see ConditionalOrderParams
176
   */
177
  get leaf(): ConditionalOrderParams {
UNCOV
178
    return {
×
179
      handler: this.handler,
180
      salt: this.salt,
181
      staticInput: this.encodeStaticInput(),
182
    }
183
  }
184

185
  /**
186
   * Calculate the id of the conditional order.
187
   * @param leaf The `leaf` representing the conditional order.
188
   * @returns The id of the conditional order.
189
   * @see ConditionalOrderParams
190
   */
191
  static leafToId(leaf: ConditionalOrderParams): string {
UNCOV
192
    return utils.keccak256(encodeParams(leaf))
×
193
  }
194

195
  /**
196
   * If the conditional order has off-chain input, return it!
197
   *
198
   * **NOTE**: This should be overridden by any conditional order that has off-chain input.
199
   * @returns The off-chain input.
200
   */
201
  get offChainInput(): string {
UNCOV
202
    return '0x'
×
203
  }
204

205
  /**
206
   * Create a human-readable string representation of the conditional order.
207
   *
208
   * @param tokenFormatter An optional function that takes an address and an amount and returns a human-readable string.
209
   */
210
  abstract toString(tokenFormatter?: (address: string, amount: BigNumber) => string): string
211

212
  /**
213
   * Serializes the conditional order into it's ABI-encoded form.
214
   *
215
   * @returns The equivalent of `IConditionalOrder.Params` for the conditional order.
216
   */
217
  abstract serialize(): string
218

219
  /**
220
   * Encode the `staticInput` for the conditional order.
221
   *
222
   * @returns The ABI-encoded `staticInput` for the conditional order.
223
   * @see ConditionalOrderParams
224
   */
225
  abstract encodeStaticInput(): string
226

227
  /**
228
   * A helper function for generically serializing a conditional order's static input.
229
   *
230
   * @param orderDataTypes ABI types for the order's data struct.
231
   * @param data The order's data struct.
232
   * @returns An ABI-encoded representation of the order's data struct.
233
   */
234
  protected encodeStaticInputHelper(orderDataTypes: string[], staticInput: S): string {
UNCOV
235
    return utils.defaultAbiCoder.encode(orderDataTypes, [staticInput])
×
236
  }
237

238
  /**
239
   * Poll a conditional order to see if it is tradeable.
240
   *
241
   * @param owner The owner of the conditional order.
242
   * @param p The proof and parameters.
243
   * @param chain Which chain to use for the ComposableCoW contract.
244
   * @param provider An RPC provider for the chain.
245
   * @param offChainInputFn A function, if provided, that will return the off-chain input for the conditional order.
246
   * @throws If the conditional order is not tradeable.
247
   * @returns The tradeable `GPv2Order.Data` struct and the `signature` for the conditional order.
248
   */
249
  async poll(params: PollParams): Promise<PollResult> {
UNCOV
250
    const { chainId, owner, provider, orderBookApi } = params
×
UNCOV
251
    const composableCow = getComposableCow(chainId, provider)
×
252

UNCOV
253
    try {
×
UNCOV
254
      const isValid = this.isValid()
×
255
      // Do a validation first
UNCOV
256
      if (!getIsValidResult(isValid)) {
×
UNCOV
257
        return {
×
258
          result: PollResultCode.DONT_TRY_AGAIN,
259
          reason: `InvalidConditionalOrder. Reason: ${isValid.reason}`,
260
        }
261
      }
262

263
      // Let the concrete Conditional Order decide about the poll result
UNCOV
264
      const pollResult = await this.pollValidate(params)
×
UNCOV
265
      if (pollResult) {
×
UNCOV
266
        return pollResult
×
267
      }
268

269
      // Check if the owner authorized the order
UNCOV
270
      const isAuthorized = await this.isAuthorized(params)
×
UNCOV
271
      if (!isAuthorized) {
×
UNCOV
272
        return {
×
273
          result: PollResultCode.DONT_TRY_AGAIN,
274
          reason: `NotAuthorized: Order ${this.id} is not authorized for ${owner} on chain ${chainId}`,
275
        }
276
      }
277

278
      // Lastly, try to get the tradeable order and signature
UNCOV
279
      const [order, signature] = await composableCow.getTradeableOrderWithSignature(
×
280
        owner,
281
        this.leaf,
282
        this.offChainInput,
283
        []
284
      )
285

UNCOV
286
      const orderUid = await computeOrderUid(chainId, owner, fromStructToOrder(order))
×
287

288
      // Check if the order is already in the order book
UNCOV
289
      const isOrderInOrderbook = await orderBookApi
×
290
        .getOrder(orderUid)
UNCOV
291
        .then(() => true)
×
UNCOV
292
        .catch(() => false)
×
293

294
      // Let the concrete Conditional Order decide about the poll result (in the case the order is already in the orderbook)
UNCOV
295
      if (isOrderInOrderbook) {
×
UNCOV
296
        const pollResult = await this.handlePollFailedAlreadyPresent(orderUid, order, params)
×
UNCOV
297
        if (pollResult) {
×
298
          return pollResult
×
299
        }
300

UNCOV
301
        return {
×
302
          result: PollResultCode.TRY_NEXT_BLOCK,
303
          reason: 'Order already in orderbook',
304
        }
305
      }
306

UNCOV
307
      return {
×
308
        result: PollResultCode.SUCCESS,
309
        order,
310
        signature,
311
      }
312
    } catch (error) {
UNCOV
313
      return {
×
314
        result: PollResultCode.UNEXPECTED_ERROR,
315
        error: error,
316
      }
317
    }
318
  }
319

320
  /**
321
   * Checks if the owner authorized the conditional order.
322
   *
323
   * @param params owner context, to be able to check if the order is authorized
324
   * @returns true if the owner authorized the order, false otherwise.
325
   */
326
  public isAuthorized(params: OwnerContext): Promise<boolean> {
UNCOV
327
    const { chainId, owner, provider } = params
×
UNCOV
328
    const composableCow = getComposableCow(chainId, provider)
×
UNCOV
329
    return composableCow.callStatic.singleOrders(owner, this.id)
×
330
  }
331

332
  /**
333
   * Checks the value in the cabinet for a given owner and chain
334
   *
335
   * @param params owner context, to be able to check the cabinet
336
   */
337
  public cabinet(params: OwnerContext): Promise<string> {
UNCOV
338
    const { chainId, owner, provider } = params
×
339

UNCOV
340
    const composableCow = getComposableCow(chainId, provider)
×
UNCOV
341
    return composableCow.callStatic.cabinet(owner, this.ctx)
×
342
  }
343

344
  /**
345
   * Allow concrete conditional orders to perform additional validation for the poll method.
346
   *
347
   * This will allow the concrete orders to decide when an order shouldn't be polled again. For example, if the orders is expired.
348
   * It also allows to signal when should the next check be done. For example, an order could signal that the validations will fail until a certain time or block.
349
   *
350
   * @param params The poll parameters
351
   *
352
   * @returns undefined if the concrete order can't make a decision. Otherwise, it returns a PollResultErrors object.
353
   */
354
  protected abstract pollValidate(params: PollParams): Promise<PollResultErrors | undefined>
355

356
  /**
357
   * 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.
358
   *
359
   * The concrete conditional order will have a chance to schedule the next poll.
360
   * 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.
361
   *
362
   * @param params
363
   */
364
  protected abstract handlePollFailedAlreadyPresent(
365
    orderUid: UID,
366
    order: GPv2Order.DataStruct,
367
    params: PollParams
368
  ): Promise<PollResultErrors | undefined>
369

370
  /**
371
   * Convert the struct that the contract expect as an encoded `staticInput` into a friendly data object modelling the smart order.
372
   *
373
   * **NOTE**: This should be overridden by any conditional order that requires transformations.
374
   * This implementation is a no-op if you use the same type for both.
375
   *
376
   * @param params {S} Parameters that are passed in to the constructor.
377
   * @returns {D} The static input for the conditional order.
378
   */
379
  abstract transformStructToData(params: S): D
380

381
  /**
382
   * Converts a friendly data object modelling the smart order into the struct that the contract expect as an encoded `staticInput`.
383
   *
384
   * **NOTE**: This should be overridden by any conditional order that requires transformations.
385
   * This implementation is a no-op if you use the same type for both.
386
   *
387
   * @param params {S} Parameters that are passed in to the constructor.
388
   * @returns {D} The static input for the conditional order.
389
   */
390
  abstract transformDataToStruct(params: D): S
391

392
  /**
393
   * A helper function for generically deserializing a conditional order.
394
   * @param s The ABI-encoded `IConditionalOrder.Params` struct to deserialize.
395
   * @param handler Address of the handler for the conditional order.
396
   * @param orderDataTypes ABI types for the order's data struct.
397
   * @param callback A callback function that takes the deserialized data struct and the salt and returns an instance of the class.
398
   * @returns An instance of the conditional order class.
399
   */
400
  protected static deserializeHelper<T>(
401
    s: string,
402
    handler: string,
403
    orderDataTypes: string[],
404
    callback: (d: any, salt: string) => T
405
  ): T {
UNCOV
406
    try {
×
407
      // First, decode the `IConditionalOrder.Params` struct
UNCOV
408
      const { handler: recoveredHandler, salt, staticInput } = decodeParams(s)
×
409

410
      // Second, verify that the recovered handler is the correct handler
UNCOV
411
      if (!(recoveredHandler == handler)) throw new Error('HandlerMismatch')
×
412

413
      // Third, decode the data struct
UNCOV
414
      const [d] = utils.defaultAbiCoder.decode(orderDataTypes, staticInput)
×
415

416
      // Create a new instance of the class
UNCOV
417
      return callback(d, salt)
×
418
    } catch (e: any) {
UNCOV
419
      if (e.message === 'HandlerMismatch') {
×
UNCOV
420
        throw e
×
421
      } else {
UNCOV
422
        throw new Error('InvalidSerializedConditionalOrder')
×
423
      }
424
    }
425
  }
426
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc