Skip to content

Commit

Permalink
Add framework for validating order using custom handler logics (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
anxolin authored Aug 21, 2023
1 parent 028955d commit c09b7a9
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 34 deletions.
80 changes: 46 additions & 34 deletions actions/checkForAndPlaceOrder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,15 @@ import {
init,
writeRegistry,
} from "./utils";
import { ChainContext, ConditionalOrder, OrderStatus } from "./model";
import {
ChainContext,
ConditionalOrder,
OrderStatus,
SmartOrderValidationResult,
ValidationResult,
} from "./model";
import { GPv2Order } from "./types/ComposableCoW";
import { validateOrder } from "./handlers";

const GPV2SETTLEMENT = "0x9008D19f58AAbD9eD0D60971565AA8510560ab41";

Expand Down Expand Up @@ -148,6 +155,24 @@ async function _processConditionalOrder(
}
}

// Do custom Conditional Order checks
const [handler, salt, staticInput] = await (() => {
const { handler, salt, staticInput } = conditionalOrder.params;
return Promise.all([handler, salt, staticInput]);
})();
const validateResult = await validateOrder({
handler,
salt,
staticInput,
});
if (validateResult && validateResult.result !== ValidationResult.Success) {
const { result, deleteConditionalOrder } = validateResult;
return {
error: result !== ValidationResult.FailedButIsExpected, // If we expected the call to fail, then we don't consider it an error
deleteConditionalOrder,
};
}

// Get GPv2 Order
const tradeableOrderResult = await _getTradeableOrderWithSignature(
owner,
Expand All @@ -157,10 +182,10 @@ async function _processConditionalOrder(
);

// Return early if the simulation fails
if (tradeableOrderResult.result != CallResult.Success) {
if (tradeableOrderResult.result != ValidationResult.Success) {
const { deleteConditionalOrder, result } = tradeableOrderResult;
return {
error: result !== CallResult.FailedButIsExpected, // If we expected the call to fail, then we don't consider it an error
error: result !== ValidationResult.FailedButIsExpected, // If we expected the call to fail, then we don't consider it an error
deleteConditionalOrder,
};
}
Expand Down Expand Up @@ -328,36 +353,17 @@ function _handleOrderBookError(
return { shouldThrow: true };
}

type GetTradeableOrderWithSignatureResult =
| GetTradeableOrderWithSignatureSuccess
| GetTradeableOrderWithSignatureError;

enum CallResult {
Success,
Failed,
FailedButIsExpected,
}

type GetTradeableOrderWithSignatureSuccess = {
result: CallResult.Success;
deleteConditionalOrder: boolean;
data: {
order: GPv2Order.DataStructOutput;
signature: string;
};
};
type GetTradeableOrderWithSignatureError = {
result: CallResult.Failed | CallResult.FailedButIsExpected;
deleteConditionalOrder: boolean;
errorObj: any;
};
export type TradableOrderWithSignatureResult = SmartOrderValidationResult<{
order: GPv2Order.DataStructOutput;
signature: string;
}>;

async function _getTradeableOrderWithSignature(
owner: string,
network: string,
conditionalOrder: ConditionalOrder,
contract: ComposableCoW
): Promise<GetTradeableOrderWithSignatureResult> {
): Promise<TradableOrderWithSignatureResult> {
const proof = conditionalOrder.proof ? conditionalOrder.proof.path : [];
const offchainInput = "0x";
const { to, data } =
Expand All @@ -380,7 +386,10 @@ async function _getTradeableOrderWithSignature(
proof
);

return { result: CallResult.Success, deleteConditionalOrder: false, data };
return {
result: ValidationResult.Success,
data,
};
} catch (error: any) {
// Print and handle the error
// We need to decide if the error is final or not (if a re-attempt might help). If it doesn't, we delete the order
Expand All @@ -400,7 +409,7 @@ function _handleGetTradableOrderCall(
error: any,
owner: string
): {
result: CallResult.Failed | CallResult.FailedButIsExpected;
result: ValidationResult.Failed | ValidationResult.FailedButIsExpected;
deleteConditionalOrder: boolean;
} {
if (error.code === Logger.errors.CALL_EXCEPTION) {
Expand All @@ -411,7 +420,7 @@ function _handleGetTradableOrderCall(
// The conditional order has not expired, or been cancelled, but the order is not valid
// For example, with TWAPs, this may be after `span` seconds have passed in the epoch.
return {
result: CallResult.FailedButIsExpected,
result: ValidationResult.FailedButIsExpected,
deleteConditionalOrder: false,
};
case "SingleOrderNotAuthed":
Expand All @@ -423,7 +432,7 @@ function _handleGetTradableOrderCall(
`${errorMessagePrefix}: Single order on safe ${owner} not authed. Deleting order...`
);
return {
result: CallResult.FailedButIsExpected,
result: ValidationResult.FailedButIsExpected,
deleteConditionalOrder: true,
};
case "ProofNotAuthed":
Expand All @@ -435,7 +444,7 @@ function _handleGetTradableOrderCall(
`${errorMessagePrefix}: Proof on safe ${owner} not authed. Deleting order...`
);
return {
result: CallResult.FailedButIsExpected,
result: ValidationResult.FailedButIsExpected,
deleteConditionalOrder: true,
};
default:
Expand All @@ -448,13 +457,16 @@ function _handleGetTradableOrderCall(
error
);
// If we don't know the reason, is better to not delete the order
return { result: CallResult.Failed, deleteConditionalOrder: false };
return {
result: ValidationResult.Failed,
deleteConditionalOrder: false,
};
}
}

console.error("[getTradeableOrderWithSignature] Unexpected error", error);
// If we don't know the reason, is better to not delete the order
return { result: CallResult.Failed, deleteConditionalOrder: false };
return { result: ValidationResult.Failed, deleteConditionalOrder: false };
}

/**
Expand Down
27 changes: 27 additions & 0 deletions actions/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {
SmartOrderValidationResult,
SmartOrderValidator,
ValidateOrderParams,
} from "../model";

import {
handlerAddress as handlerTwap,
validateOrder as validateOrderTwap,
} from "./twap";

const validatorsForHandler: Record<string, SmartOrderValidator> = {
[handlerTwap.toLowerCase()]: validateOrderTwap,
};

/**
* Validate a smart order using the custom handler logic (if known)
*
* @returns The result of the validation if the handler is known, undefined otherwise
*/
export async function validateOrder(
params: ValidateOrderParams
): Promise<SmartOrderValidationResult<void> | undefined> {
const { handler } = params;
const validateFn = validatorsForHandler[handler.toLowerCase()];
return validateFn ? validateFn(params) : undefined;
}
14 changes: 14 additions & 0 deletions actions/handlers/twap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { TWAP_ADDRESS } from "@cowprotocol/cow-sdk";

import { SmartOrderValidator, ValidationResult } from "../model";

export const handlerAddress = TWAP_ADDRESS;

export const validateOrder: SmartOrderValidator = async (_params) => {
// TODO: Implement validation logic, ideally delegate to the SDK

return {
result: ValidationResult.Success,
data: undefined,
};
};
29 changes: 29 additions & 0 deletions actions/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,3 +182,32 @@ export function replacer(_key: any, value: any) {
return value;
}
}

export type SmartOrderValidationResult<T> =
| SmartOrderValidationSuccess<T>
| SmartOrderValidationError;

export enum ValidationResult {
Success,
Failed,
FailedButIsExpected,
}

export type SmartOrderValidationSuccess<T> = {
result: ValidationResult.Success;
data: T;
};
export type SmartOrderValidationError = {
result: ValidationResult.Failed | ValidationResult.FailedButIsExpected;
deleteConditionalOrder: boolean;
errorObj: any;
};

export type SmartOrderValidator = (
params: ValidateOrderParams
) => Promise<SmartOrderValidationResult<void>>;
export interface ValidateOrderParams {
handler: string;
salt: BytesLike;
staticInput: BytesLike;
}
1 change: 1 addition & 0 deletions actions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
},
"dependencies": {
"@cowprotocol/contracts": "^1.4.0",
"@cowprotocol/cow-sdk": "^2.4.0-rc.0",
"@sentry/integrations": "^7.64.0",
"@sentry/node": "^7.64.0",
"@tenderly/actions": "^0.0.13",
Expand Down

0 comments on commit c09b7a9

Please sign in to comment.