From 6cd7e05d8d0b588abd888ba214a910dfa8166429 Mon Sep 17 00:00:00 2001 From: Leandro Date: Thu, 5 Sep 2024 14:21:58 +0100 Subject: [PATCH] feat(quote): add smartSlippage property to quote (#64) * feat: add quote v1.1.0 with the `smartQuote` property * feat: new parent schema v1.2.0 with updated quote schema * chore: add generated types from new schema * feat: add unittests for new schemas * chore: update version to current one * refactor: rename smartQuote to smartSlippage --- src/generatedTypes/index.ts | 11 ++- src/generatedTypes/v1.2.0.ts | 165 +++++++++++++++++++++++++++++++ src/schemas/quote/v1.1.0.json | 23 +++++ src/schemas/v1.2.0.json | 78 +++++++++++++++ test/schema.spec.ts | 177 ++++++++++++++++++++++++---------- 5 files changed, 398 insertions(+), 56 deletions(-) create mode 100644 src/generatedTypes/v1.2.0.ts create mode 100644 src/schemas/quote/v1.1.0.json create mode 100644 src/schemas/v1.2.0.json diff --git a/src/generatedTypes/index.ts b/src/generatedTypes/index.ts index d095d5a..70d45b0 100644 --- a/src/generatedTypes/index.ts +++ b/src/generatedTypes/index.ts @@ -13,11 +13,12 @@ import * as v0_8_0 from './v0.8.0' import * as v0_9_0 from './v0.9.0' import * as v1_0_0 from './v1.0.0' import * as v1_1_0 from './v1.1.0' +import * as v1_2_0 from './v1.2.0' -export * as latest from './v1.1.0' +export * as latest from './v1.2.0' -export const LATEST_APP_DATA_VERSION = '1.1.0' -export const LATEST_QUOTE_METADATA_VERSION = '1.0.0' +export const LATEST_APP_DATA_VERSION = '1.2.0' +export const LATEST_QUOTE_METADATA_VERSION = '1.1.0' export const LATEST_REFERRER_METADATA_VERSION = '0.2.0' export const LATEST_ORDER_CLASS_METADATA_VERSION = '0.3.0' export const LATEST_UTM_METADATA_VERSION = '0.2.0' @@ -27,8 +28,9 @@ export const LATEST_WIDGET_METADATA_VERSION = '0.1.0' export const LATEST_PARTNER_FEE_METADATA_VERSION = '0.1.0' export const LATEST_REPLACED_ORDER_METADATA_VERSION = '0.1.0' -export type LatestAppDataDocVersion = v1_1_0.AppDataRootSchema +export type LatestAppDataDocVersion = v1_2_0.AppDataRootSchema export type AnyAppDataDocVersion = + | v1_2_0.AppDataRootSchema | v1_1_0.AppDataRootSchema | v1_0_0.AppDataRootSchema | v0_11_0.AppDataRootSchema @@ -44,6 +46,7 @@ export type AnyAppDataDocVersion = | v0_1_0.AppDataRootSchema export { + v1_2_0, v1_1_0, v1_0_0, v0_11_0, diff --git a/src/generatedTypes/v1.2.0.ts b/src/generatedTypes/v1.2.0.ts new file mode 100644 index 0000000..e19b407 --- /dev/null +++ b/src/generatedTypes/v1.2.0.ts @@ -0,0 +1,165 @@ +/* tslint:disable */ +/** + * This file was automatically generated by json-schema-to-typescript. + * DO NOT MODIFY IT BY HAND. Instead, modify the source JSONSchema file, + * and run json-schema-to-typescript to regenerate this file. + */ + +/** + * Semantic versioning of document. + */ +export type Version = string; +/** + * The code identifying the CLI, UI, service generating the order. + */ +export type AppCode = string; +/** + * Environment from which the order came from. + */ +export type Environment = string; +/** + * The address of the trader who signs the CoW Swap order. This field should normally be omitted; it is recommended to use it if the signer is a smart-contract wallet using EIP-1271 signatures. + */ +export type Signer = string; +export type ReferrerAddress = string; +/** + * Tracks in which medium the traffic originated from (twitter, facebook, etc.) + */ +export type UTMSource = string; +/** + * Tracks in which medium the traffic originated from (mail, CPC, social, etc.) + */ +export type UTMMedium = string; +/** + * Track the performance of a specific campaign + */ +export type UTMCampaign = string; +/** + * Track which link was clicked + */ +export type UTMContent = string; +/** + * Track which keyword term a website visitor came from + */ +export type UTMKeywordTerm = string; +/** + * Slippage tolerance that was applied to the order to get the limit price. Expressed in Basis Points (BPS). One basis point is equivalent to 0.01% (1/100th of a percent) + */ +export type SlippageBips = number; +/** + * Whether the given slippageBips used is originated from a Smart slippage suggestion + */ +export type SmartSlippage = boolean; +/** + * Indicator of the order class. + */ +export type OrderClass1 = "market" | "limit" | "liquidity" | "twap"; +/** + * Semantic versioning of document. + */ +export type Version1 = string; +/** + * The contract to call for the hook + */ +export type HookTarget = string; +/** + * The calldata to use when calling the hook + */ +export type HookCallData = string; +/** + * The gas limit (in gas units) for the hook + */ +export type HookGasLimit = string; +/** + * CoW Hooks to call before an order executes + */ +export type PreHooks = CoWHook[]; +/** + * CoW Hooks to call after an order executes + */ +export type PostHooks = CoWHook[]; +/** + * The code identifying the UI powering the widget + */ +export type AppCode1 = string; +/** + * Environment from which the order came from. + */ +export type Environment1 = string; +/** + * The fee in basis points (BPS) to be paid to the partner. One basis point is equivalent to 0.01% (1/100th of a percent) + */ +export type BasisPointBPS = number; +/** + * The Ethereum address of the partner to receive the fee. + */ +export type PartnerAccount = string; +/** + * The replaced order UID. + */ +export type ReplacedOrderUID = string; + +/** + * Metadata JSON document for adding information to orders. + */ +export interface AppDataRootSchema { + version: Version; + appCode?: AppCode; + environment?: Environment; + metadata: Metadata; +} +/** + * Each metadata will specify one aspect of the order. + */ +export interface Metadata { + signer?: Signer; + referrer?: Referrer; + utm?: UTMCodes; + quote?: Quote; + orderClass?: OrderClass; + hooks?: OrderInteractionHooks; + widget?: Widget; + partnerFee?: PartnerFee; + replacedOrder?: ReplacedOrder; +} +export interface Referrer { + address: ReferrerAddress; +} +export interface UTMCodes { + utmSource?: UTMSource; + utmMedium?: UTMMedium; + utmCampaign?: UTMCampaign; + utmContent?: UTMContent; + utmTerm?: UTMKeywordTerm; +} +export interface Quote { + slippageBips: SlippageBips; + smartSlippage?: SmartSlippage; +} +export interface OrderClass { + orderClass: OrderClass1; +} +/** + * Optional Pre and Post order interaction hooks attached to a single order + */ +export interface OrderInteractionHooks { + version?: Version1; + pre?: PreHooks; + post?: PostHooks; +} +export interface CoWHook { + target: HookTarget; + callData: HookCallData; + gasLimit: HookGasLimit; +} +export interface Widget { + appCode: AppCode1; + environment?: Environment1; +} +export interface PartnerFee { + bps: BasisPointBPS; + recipient: PartnerAccount; +} +export interface ReplacedOrder { + uid: ReplacedOrderUID; +} diff --git a/src/schemas/quote/v1.1.0.json b/src/schemas/quote/v1.1.0.json new file mode 100644 index 0000000..160808b --- /dev/null +++ b/src/schemas/quote/v1.1.0.json @@ -0,0 +1,23 @@ +{ + "$id": "#quote/v0.2.0.json", + "$schema": "http://json-schema.org/draft-07/schema", + "required": [ + "slippageBips" + ], + "title": "Quote", + "type": "object", + "additionalProperties": false, + "properties": { + "slippageBips": { + "title": "Slippage Bips", + "description": "Slippage tolerance that was applied to the order to get the limit price. Expressed in Basis Points (BPS). One basis point is equivalent to 0.01% (1/100th of a percent)", + "$ref": "../definitions.json#/definitions/bps" + }, + "smartSlippage": { + "title": "Smart Slippage", + "description": "Whether the given slippageBips used is originated from a Smart slippage suggestion", + "type": "boolean", + "default": false + } + } +} \ No newline at end of file diff --git a/src/schemas/v1.2.0.json b/src/schemas/v1.2.0.json new file mode 100644 index 0000000..ad463b7 --- /dev/null +++ b/src/schemas/v1.2.0.json @@ -0,0 +1,78 @@ +{ + "$id": "https://cowswap.exchange/schemas/app-data/v1.2.0.json", + "$schema": "http://json-schema.org/draft-07/schema", + "description": "Metadata JSON document for adding information to orders.", + "required": [ + "version", + "metadata" + ], + "title": "AppData Root Schema", + "type": "object", + "additionalProperties": false, + "properties": { + "version": { + "$ref": "definitions.json#/definitions/version", + "readOnly": true, + "default": "1.2.0" + }, + "appCode": { + "$id": "#/properties/appCode", + "description": "The code identifying the CLI, UI, service generating the order.", + "examples": [ + "CoW Swap" + ], + "title": "App Code", + "type": "string" + }, + "environment": { + "$id": "#/properties/environment", + "description": "Environment from which the order came from.", + "title": "Environment", + "type": "string", + "examples": [ + "production", + "development", + "staging", + "ens" + ] + }, + "metadata": { + "$id": "#/properties/metadata", + "default": {}, + "description": "Each metadata will specify one aspect of the order.", + "required": [], + "title": "Metadata", + "type": "object", + "additionalProperties": false, + "properties": { + "signer": { + "$ref": "signer/v0.1.0.json#" + }, + "referrer": { + "$ref": "referrer/v0.2.0.json#" + }, + "utm": { + "$ref": "utm/v0.2.0.json#" + }, + "quote": { + "$ref": "quote/v1.1.0.json#" + }, + "orderClass": { + "$ref": "orderClass/v0.3.0.json#" + }, + "hooks": { + "$ref": "hooks/v0.1.0.json#" + }, + "widget": { + "$ref": "widget/v0.1.0.json#" + }, + "partnerFee": { + "$ref": "partnerFee/v0.1.0.json#" + }, + "replacedOrder": { + "$ref": "replacedOrder/v0.1.0.json#" + } + } + } + } +} \ No newline at end of file diff --git a/test/schema.spec.ts b/test/schema.spec.ts index 9e3c1f6..fd8b959 100644 --- a/test/schema.spec.ts +++ b/test/schema.spec.ts @@ -1,16 +1,17 @@ import Ajv, { ValidateFunction } from 'ajv' import schemaV0_1_0 from '../schemas/v0.1.0.json' +import schemaV0_10_0 from '../schemas/v0.10.0.json' +import schemaV0_11_0 from '../schemas/v0.11.0.json' import schemaV0_2_0 from '../schemas/v0.2.0.json' import schemaV0_3_0 from '../schemas/v0.3.0.json' import schemaV0_4_0 from '../schemas/v0.4.0.json' import schemaV0_5_0 from '../schemas/v0.5.0.json' import schemaV0_6_0 from '../schemas/v0.6.0.json' import schemaV0_9_0 from '../schemas/v0.9.0.json' -import schemaV0_10_0 from '../schemas/v0.10.0.json' -import schemaV0_11_0 from '../schemas/v0.11.0.json' import schemaV1_0_0 from '../schemas/v1.0.0.json' import schemaV1_1_0 from '../schemas/v1.1.0.json' +import schemaV1_2_0 from '../schemas/v1.2.0.json' const ADDRESS = '0xb6BAd41ae76A11D10f7b0E664C5007b908bC77C9' const REFERRER_V0_1_0 = { address: ADDRESS, version: '0.1.0' } @@ -888,62 +889,134 @@ describe('Schema v1.0.0: Update quote definition', () => { }) describe('Schema v1.1.0: Add replaced order', () => { - const ajv = new Ajv() - const validator = ajv.compile(schemaV1_1_0) - - const BASE_DOCUMENT = { - version: '1.1.0', - metadata: {}, - } + const ajv = new Ajv() + const validator = ajv.compile(schemaV1_1_0) - test( - 'Valid order id', - _buildAssertValidFn(validator, { - ...BASE_DOCUMENT, - metadata: { replacedOrder: { uid: "0xff2e2e54d178997f173266817c1e9ed6fee1a1aae4b43971c53b543cffcc2969845c6f5599fbb25dbdd1b9b013daf85c03f3c63763e4bc4a" } }, - }) - ) + const BASE_DOCUMENT = { + version: '1.1.0', + metadata: {}, + } + test( + 'Valid order id', + _buildAssertValidFn(validator, { + ...BASE_DOCUMENT, + metadata: { + replacedOrder: { + uid: '0xff2e2e54d178997f173266817c1e9ed6fee1a1aae4b43971c53b543cffcc2969845c6f5599fbb25dbdd1b9b013daf85c03f3c63763e4bc4a', + }, + }, + }) + ) - test( - 'Invalid order id length', - _buildAssertInvalidFn( - validator, - { - ...BASE_DOCUMENT, - metadata: { replacedOrder: { uid: "0xgogogog" } }, - }, - [ - { - instancePath: '/metadata/replacedOrder/uid', - keyword: "pattern", - message: "must match pattern \"^0x[a-fA-F0-9]{112}$\"", - params: { pattern: "^0x[a-fA-F0-9]{112}$" }, - schemaPath: '#/properties/metadata/properties/replacedOrder/properties/uid/pattern', - }, - ] - ) + test( + 'Invalid order id length', + _buildAssertInvalidFn( + validator, + { + ...BASE_DOCUMENT, + metadata: { replacedOrder: { uid: '0xgogogog' } }, + }, + [ + { + instancePath: '/metadata/replacedOrder/uid', + keyword: 'pattern', + message: 'must match pattern "^0x[a-fA-F0-9]{112}$"', + params: { pattern: '^0x[a-fA-F0-9]{112}$' }, + schemaPath: '#/properties/metadata/properties/replacedOrder/properties/uid/pattern', + }, + ] ) + ) - test( - 'Invalid order id length', - _buildAssertInvalidFn( - validator, - { - ...BASE_DOCUMENT, - metadata: { replacedOrder: { } }, - }, - [ - { - instancePath: '/metadata/replacedOrder', - keyword: "required", - message: "must have required property 'uid'", - params: { missingProperty: "uid" }, - schemaPath: '#/properties/metadata/properties/replacedOrder/required', - }, - ] - ) + test( + 'Invalid order id length', + _buildAssertInvalidFn( + validator, + { + ...BASE_DOCUMENT, + metadata: { replacedOrder: {} }, + }, + [ + { + instancePath: '/metadata/replacedOrder', + keyword: 'required', + message: "must have required property 'uid'", + params: { missingProperty: 'uid' }, + schemaPath: '#/properties/metadata/properties/replacedOrder/required', + }, + ] ) + ) +}) + +describe('Schema v1.2.0', () => { + const ajv = new Ajv() + const validator = ajv.compile(schemaV1_2_0) + + const BASE_DOCUMENT = { + version: '1.2.0', + metadata: {}, + } + + test('Minimal valid schema', _buildAssertValidFn(validator, BASE_DOCUMENT)) + + test('With quote metadata v1.1.0', () => { + const validQuote = { + slippageBips: 5, + smartSlippage: true, + } + + const validDocument = { + ...BASE_DOCUMENT, + metadata: { + quote: validQuote, + }, + } + + expect(validator(validDocument)).toBe(true) + }) + + test('With valid metadata (without smartSlippage)', () => { + const quote = { + slippageBips: 5, + } + + const validDocument = { + ...BASE_DOCUMENT, + metadata: { + quote, + }, + } + + expect(validator(validDocument)).toBe(true) + }) + + test('With invalid quote metadata (invalid slippageBips)', () => { + const invalidQuote = { + slippageBips: 'invalid', + smartSlippage: true, + } + + const invalidDocument = { + ...BASE_DOCUMENT, + metadata: { + quote: invalidQuote, + }, + } + + const errors = [ + { + instancePath: '/metadata/quote/slippageBips', + keyword: 'pattern', + message: 'must match pattern "^\\d+$"', + params: { pattern: '^\\d+$' }, + schemaPath: '#/properties/metadata/properties/quote/properties/slippageBips/pattern', + }, + ] + + expect(_buildAssertInvalidFn(validator, invalidDocument, errors)).toBeTruthy() + }) }) function _buildAssertValidFn(validator: ValidateFunction, doc: any) {