Skip to content
This repository has been archived by the owner on Jun 24, 2022. It is now read-only.

Commit

Permalink
Merge pull request #2612 from gnosis/hotfix/1.12.4
Browse files Browse the repository at this point in the history
hotfix - 1.12.4 - rc.1
  • Loading branch information
W3stside authored Mar 30, 2022
2 parents c9d7f16 + 420873e commit e6c55bb
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 42 deletions.
116 changes: 76 additions & 40 deletions src/custom/api/gnosisProtocol/api.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { SupportedChainId as ChainId, SupportedChainId } from 'constants/chains'
import { OrderKind, QuoteQuery } from '@gnosis.pm/gp-v2-contracts'
import { stringify } from 'qs'
import { getSigningSchemeApiValue, OrderCancellation, OrderCreation, SigningSchemeValue } from 'utils/signatures'
import {
getSigningSchemeApiValue,
OrderCancellation,
OrderCreation,
SigningSchemeValue,
UnsignedOrder,
} from 'utils/signatures'
import { APP_DATA_HASH, GAS_FEE_ENDPOINTS } from 'constants/index'
import { registerOnWindow } from 'utils/misc'
import { isBarn, isDev, isLocal, isPr } from '../../utils/environments'
Expand All @@ -21,10 +27,11 @@ import { FeeQuoteParams, PriceInformation, PriceQuoteParams, SimpleGetQuoteRespo

import { DEFAULT_NETWORK_FOR_LISTS } from 'constants/lists'
import * as Sentry from '@sentry/browser'
import { constructSentryError } from 'utils/logging'
import { checkAndThrowIfJsonSerialisableError, constructSentryError } from 'utils/logging'
import { ZERO_ADDRESS } from 'constants/misc'
import { getAppDataHash } from 'constants/appDataHash'
import { GpPriceStrategy } from 'hooks/useGetGpPriceStrategy'
import { Context } from '@sentry/types'

function getGnosisProtocolUrl(): Partial<Record<ChainId, string>> {
if (isLocal || isDev || isPr || isBarn) {
Expand Down Expand Up @@ -220,23 +227,15 @@ export async function sendOrder(params: { chainId: ChainId; order: OrderCreation
const { chainId, order, owner } = params
console.log(`[api:${API_NAME}] Post signed order for network`, chainId, order)

// Call API
const response = await _post(chainId, `/orders`, {
const orderParams = {
...order,
signingScheme: getSigningSchemeApiValue(order.signingScheme),
from: owner,
})

// Handle response
if (!response.ok) {
// Raise an exception
const errorMessage = await OperatorError.getErrorFromStatusCode(response, 'create')
throw new Error(errorMessage)
}
// Call API
const response = await _post(chainId, `/orders`, orderParams)

const uid = (await response.json()) as string
console.log(`[api:${API_NAME}] Success posting the signed order`, uid)
return uid
return _handleOrderResponse<string, typeof orderParams>(response, orderParams)
}

type OrderCancellationParams = {
Expand Down Expand Up @@ -275,52 +274,89 @@ const UNHANDLED_ORDER_ERROR: ApiErrorObject = {
description: ApiErrorCodeDetails.UNHANDLED_CREATE_ERROR,
}

function _handleError<P extends Context>(error: any, response: Response, params: P, operation: 'ORDER' | 'QUOTE') {
// Create a new sentry error OR
// use the previously created and rethrown error from the try block
const sentryError =
error?.sentryError ||
constructSentryError(error, response, {
message: error?.message || error,
name: `[${operation}-ERROR] - Unmapped ${operation} Error`,
})
// Create the error tags or use the previously constructed ones from the try block
const tags = error?.tags || { errorType: operation, backendErrorCode: response.status }

// report to sentry
Sentry.captureException(sentryError, {
tags,
// TODO: change/remove this in context update pr
contexts: { params: { ...params } },
})

return error?.baseError || error
}

async function _handleOrderResponse<T = any, P extends UnsignedOrder = UnsignedOrder>(
response: Response,
params: P
): Promise<T> {
try {
// Handle response
if (!response.ok) {
// Raise an exception
const [errorObject, description] = await Promise.all<[Promise<ApiErrorObject>, Promise<string>]>([
response.json(),
OperatorError.getErrorFromStatusCode(response, 'create'),
])
// create the OperatorError from the constructed error message and the original error
const error = new OperatorError(Object.assign({}, errorObject, { description }))

// we need to create a sentry error and keep the original mapped quote error
throw constructSentryError(error, response, {
message: `${error.description}`,
name: `[${error.name}] - ${error.type}`,
optionalTags: {
orderErrorType: error.type,
},
})
} else {
const uid = await response.json()
console.log(`[api:${API_NAME}] Success posting the signed order`, JSON.stringify(uid))
return uid
}
} catch (error) {
throw _handleError(error, response, params, 'ORDER')
}
}

async function _handleQuoteResponse<T = any, P extends FeeQuoteParams = FeeQuoteParams>(
response: Response,
params: P
): Promise<T> {
try {
if (!response.ok) {
// don't attempt json parse if not json response...
if (response.headers.get('Content-Type') !== 'application/json') {
throw new Error(`${response.status} error occurred. ${response.statusText}`)
}
checkAndThrowIfJsonSerialisableError(response)

const errorObj: ApiErrorObject = await response.json()

// we need to map the backend error codes to match our own for quotes
const mappedError = mapOperatorErrorToQuoteError(errorObj)
const quoteError = new QuoteError(mappedError)
const error = new QuoteError(mappedError)

// we need to create a sentry error and keep the original mapped quote error
throw constructSentryError(quoteError, response, {
message: `${quoteError.description} [sellToken: ${params.sellToken}]//[buyToken: ${params.buyToken}]`,
name: `[${quoteError.name}] - ${quoteError.type}`,
throw constructSentryError(error, response, {
message: `${error.description}`,
name: `[${error.name}] - ${error.type}`,
optionalTags: {
quoteErrorType: quoteError.type,
quoteErrorType: error.type,
},
})
} else {
return response.json()
}
} catch (error) {
// Create a new sentry error OR
// use the previously created and rethrown error from the try block
const sentryError =
error?.sentryError ||
constructSentryError(error, response, {
message: `Potential backend error detected - status code: ${response.status}`,
name: '[HandleQuoteResponse] - Unmapped Quote Error',
})
// Create the error tags or use the previously constructed ones from the try block
const tags = error?.tags || { errorType: 'handleQuoteResponse', backendErrorCode: response.status }

// report to sentry
Sentry.captureException(sentryError, {
tags,
contexts: { params: { ...params } },
})

throw error?.baseError || error
throw _handleError(error, response, params, 'QUOTE')
}
}

Expand Down
10 changes: 8 additions & 2 deletions src/custom/api/gnosisProtocol/errors/OperatorError.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { checkAndThrowIfJsonSerialisableError } from 'utils/logging'

type ApiActionType = 'get' | 'create' | 'delete'

export interface ApiErrorObject {
Expand Down Expand Up @@ -97,7 +99,11 @@ export default class OperatorError extends Error {

public static async getErrorMessage(response: Response, action: ApiActionType) {
try {
const orderPostError: ApiErrorObject = await response.json()
// don't attempt json parse if not json response...
checkAndThrowIfJsonSerialisableError(response)
// clone response body
const clonedResponse = response.clone()
const orderPostError: ApiErrorObject = await clonedResponse.json()

if (orderPostError.errorType) {
const errorMessage = OperatorError.apiErrorDetails[orderPostError.errorType]
Expand All @@ -112,7 +118,7 @@ export default class OperatorError extends Error {
return _mapActionToErrorDetail(action)
}
}
static async getErrorFromStatusCode(response: Response, action: 'create' | 'delete') {
static async getErrorFromStatusCode(response: Response, action: 'create' | 'delete'): Promise<string> {
switch (response.status) {
case 400:
case 404:
Expand Down
2 changes: 2 additions & 0 deletions src/custom/api/gnosisProtocol/errors/QuoteError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export enum GpQuoteErrorCodes {
UNHANDLED_ERROR = 'UNHANDLED_ERROR',
}

export const SENTRY_IGNORED_GP_QUOTE_ERRORS = [GpQuoteErrorCodes.FeeExceedsFrom]

export enum GpQuoteErrorDetails {
UnsupportedToken = 'One of the tokens you are trading is unsupported. Please read the FAQ for more info.',
InsufficientLiquidity = 'Token pair selected has insufficient liquidity.',
Expand Down
2 changes: 2 additions & 0 deletions src/custom/pages/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { environmentName } from 'utils/environments'
import { useFilterEmptyQueryParams } from 'hooks/useFilterEmptyQueryParams'
import RedirectAnySwapAffectedUsers from 'pages/error/AnySwapAffectedUsers/RedirectAnySwapAffectedUsers'
import { IS_CLAIMING_ENABLED } from 'pages/Claim/const'
import { SENTRY_IGNORED_GP_QUOTE_ERRORS } from 'api/gnosisProtocol/errors/QuoteError'

const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN
const SENTRY_TRACES_SAMPLE_RATE = process.env.REACT_APP_SENTRY_TRACES_SAMPLE_RATE
Expand All @@ -34,6 +35,7 @@ if (SENTRY_DSN) {
integrations: [new Integrations.BrowserTracing()],
release: 'CowSwap@v' + version,
environment: environmentName,
ignoreErrors: [...SENTRY_IGNORED_GP_QUOTE_ERRORS],

// Set tracesSampleRate to 1.0 to capture 100%
// of transactions for performance monitoring.
Expand Down
8 changes: 8 additions & 0 deletions src/custom/utils/logging/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,11 @@ export function constructSentryError(

return { baseError, sentryError: constructedError, tags }
}

// checks response for non json/application return type and throw appropriate error
export function checkAndThrowIfJsonSerialisableError(response: Response) {
// don't attempt json parse if not json response...
if (response.headers.get('Content-Type') !== 'application/json') {
throw new Error(`Error code ${response.status} occurred`)
}
}

0 comments on commit e6c55bb

Please sign in to comment.