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

fix: parse jupiter errors to human readable translations #8433

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from

Conversation

NeOMakinG
Copy link
Collaborator

Description

Added some error arrays to the swappers so every swappers can parse their errors into human readable errors

Issue (if applicable)

closes #8303

Risk

High Risk PRs Require 2 approvals

What protocols, transaction types, wallets or contract interactions might be affected by this PR?

Testing

  • Try to trade a very small amount of SOL to USDC on Solana with 0.01 as slippage
  • Sometime you can see the Trade consumed more fees than expected error too, we already recently upgraded the buffer for this, I suspect small amount trades could be the root cause but if not, we might need to investigate a bit this particular case

Engineering

n/a

Operations

n/a

  • 🏁 My feature is behind a flag and doesn't require operations testing (yet)

Screenshots (if applicable)

image image

Comment on lines 2 to 4
SLIPPAGE_EXCEEDED = 'trade.errors.slippageExceeded',
UNDER_MINIMUM_AMOUNT = 'trade.errors.underMinimumAmount',
CONSUMED_MORE_FEES = 'trade.errors.consumedMoreFees',
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It can be weird to have translation keys in the swapper package, but for the sake of simplicity, I wanted to keep it here

The very clean way would be to return error codes or strings, and map these errors codes/strings to our translation keys on react app side, but the swapper is not consumed outside of this scope

Depending on reviews args, we might want to do it, but my choice has been done already 😄

Copy link
Contributor

@gomesalexandre gomesalexandre Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the sake of simplicity, would actually prefer to keep just indexes. This is redeclaring keys from JUPITER_ERROR_VALUES, reading this is a big indirection for what should be a simple mapping (three count between JUPITER_ERROR_VALUES, redundant JUPITER_ERRORS, and this, can pattern match in /useTradeExecution instead

@NeOMakinG NeOMakinG marked this pull request as ready for review December 20, 2024 16:09
@NeOMakinG NeOMakinG requested a review from a team as a code owner December 20, 2024 16:09
@gomesalexandre gomesalexandre self-requested a review December 20, 2024 16:37
import { SWAPPER_ERRORS } from '../baseErrors'

export const JUPITER_ERROR_VALUES = {
SLIPPAGE_EXCEEDED: 'SlippageToleranceExceeded',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SLIPPAGE_EXCEEDED: 'SlippageToleranceExceeded',
SLIPPAGE_TOLERANCE_EXCEEDED: 'SlippageToleranceExceeded',

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 2de18a2

@@ -6,3 +6,5 @@ export * from './thorchain-utils'
export * from './cowswap-utils'
export * from './safe-utils'
export * from './swappers/CowSwapper'
export * from './swappers/JupiterSwapper'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused

Suggested change
export * from './swappers/JupiterSwapper'

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's used again in 2de18a2

import type { SwapperErrorMapping } from '../baseErrors'
import { SWAPPER_ERRORS } from '../baseErrors'

export const JUPITER_ERROR_VALUES = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about the naming here, this is rather a subtring check , maybe something like pattern vernacular would be a better fit?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also looks better suited to an enum.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 17f168a

CONSUMED_MORE_FEES: 'exceeded CUs meter',
} as const

export const JUPITER_ERRORS: SwapperErrorMapping[] = [
Copy link
Contributor

@gomesalexandre gomesalexandre Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preferably-blocking: This is redundant.

  1. this is a K,V mapping which 1/1 maps the keys and values of JUPITER_ERROR_VALUES
  2. We're effectively reinventing the wheel and creating a poor man's flavour of Object

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 17f168a

Comment on lines 7 to 10
export type SwapperErrorMapping = {
key: SWAPPER_ERRORS
value: string
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See https://github.com/shapeshift/web/pull/8433/files#r1894277094, we're effectively reinventing objects.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah this is just a Record<SWAPPER_ERRORS, string>.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 17f168a

Comment on lines 138 to 158
const swapperError = swapperErrors.find(error => e.message.includes(error.value))

if (swapperError) {
return {
message: translate(swapperError.key),
translationKey: swapperError.key,
error: undefined,
}
}

return {
message: translate(e.metadata.translation, e.metadata.options),
translationKey: e.metadata.translation,
error: e,
}
}

return {
message: (e as Error).message ?? undefined,
translationKey: undefined,
error: e,
Copy link
Contributor

@gomesalexandre gomesalexandre Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion:
switch(true) {} then case: <pattern>.test(e.message) and default to l.155`

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not relevant anymore, changed in 17f168a

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: filename is confusing, wouldn't be sure what errors means and the amount of nesting doesn't help here , maybe pattern terminology here too?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file doesn't exists anymore, but changed to errorPatterns on jup side in 17f168a

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also if we use indexes this goes away, feels weird to import and export this guy in the same file (errors.ts)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in 17f168a

import { SWAPPER_ERRORS } from './baseErrors'
import { JUPITER_ERRORS } from './JupiterSwapper/errors'

export { SWAPPER_ERRORS }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see https://github.com/shapeshift/web/pull/8433/files#r1894285675 re: import/export in the same file, feels like something we may not want to do?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not relevant anymore, changed in 17f168a

import { SWAPPER_ERRORS } from '../baseErrors'

export const JUPITER_ERROR_VALUES = {
SLIPPAGE_EXCEEDED: 'SlippageToleranceExceeded',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

q: do we even feel like errors at execution time is something that generalizes to the point we shouls abstract this away? Feels like all we're doing here could be done wherever the error bubbles up (useTradeExecution())

From what I'm seeing, this is really solana/jupiter specific, and none of our other supported chains would return errors at Tx broadcast time, are you aware of other Solana swappers doing the same as jupiter @NeOMakinG ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way we've done it is now throwing specific error types from the swapper easing the handling and seems like the right home, it's also a good pattern we could take in the future, wdyt?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming we want to go from broadcast error to generalized error monad, 100%!

@@ -132,18 +133,37 @@ export const useTradeExecution = (
dispatch(tradeQuoteSlice.actions.setSwapTxPending({ hopIndex, id: confirmedTradeId }))

const onFail = (e: unknown) => {
const message = (() => {
const { message, translationKey, error } = (() => {
if (e instanceof ChainAdapterError) {
Copy link
Contributor

@gomesalexandre gomesalexandre Dec 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another suggestion which would help achieving what we want to here (keep error-handling at broadcast time colocated with its swapper): solana's executeSolanaTransaction is currently just consuming the base implementation of executeSolanaTransaction from swapper/src/utils.

If you wrap executeSolanaTx into an handler, then you can actually do proper error-handling there and return something better

diff --git a/packages/swapper/src/swappers/JupiterSwapper/JupiterSwapper.ts b/packages/swapper/src/swappers/JupiterSwapper/JupiterSwapper.ts
index eb1966aead..deeba7c500 100644
--- a/packages/swapper/src/swappers/JupiterSwapper/JupiterSwapper.ts
+++ b/packages/swapper/src/swappers/JupiterSwapper/JupiterSwapper.ts
@@ -1,0 +2 @@ import type { AssetId } from '@shapeshiftoss/caip'
+import { ChainAdapterError } from '@shapeshiftoss/chain-adapters'
@@ -7,0 +9,7 @@ import { jupiterSupportedChainIds } from './utils/constants'
+export class SolanaLogsError extends Error {
+  constructor(message: string) {
+    super(message)
+    this.name = 'SolanaLogsError'
+  }
+}
+
@@ -9 +17,16 @@ export const jupiterSwapper: Swapper = {
-  executeSolanaTransaction,
+  executeSolanaTransaction: async (...args) => {
+    try {
+      const txid = await executeSolanaTransaction(...args)
+      return txid
+    } catch (e: unknown) {
+      if (e instanceof ChainAdapterError) {
+        // replace me with actual pattern matching
+        const isLogsError = true
+        // where errorMember is the actual error from enum
+        if (isLogsError) throw new SolanaLogsError(SLIPPAGE_EXCEEDED)
+      }
+
+      // rethrow is not instanceof ChainAdapterError, shouldn't be the case but jus to satisfy TS
+      throw e
+    }
+  },
diff --git a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/hooks/useTradeExecution.tsx b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/hooks/useTradeExecution.tsx
index c87f2e4589..ea4894c0e1 100644
--- a/src/components/MultiHopTrade/components/MultiHopTradeConfirm/hooks/useTradeExecution.tsx
+++ b/src/components/MultiHopTrade/components/MultiHopTradeConfirm/hooks/useTradeExecution.tsx
@@ -24,0 +25 @@ import { LIFI_TRADE_POLL_INTERVAL_MILLISECONDS } from '@shapeshiftoss/swapper/di
+import { SolanaLogsError } from '@shapeshiftoss/swapper/src/swappers/JupiterSwapper/JupiterSwapper'
@@ -136,0 +138,3 @@ export const useTradeExecution = (
+          if (e instanceof SolanaLogsError) {
+            // switch error enum member to translation here
+          }

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in 17f168a

Copy link
Contributor

@gomesalexandre gomesalexandre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wasn't able to get to any error state as Jupiter swaps are turbos stable now but conceptually looks like it should do what it says on the box.

However nothing a bit of confusing indirection and data structures looking off here.

Suggestion:

  • Keep things atomic and use indexes only (as possible)
  • Don't colocate translations keys in swapper, which would help with the former (just return an error from enum)
  • Pattern match in web using switch (true) { case (<pattern>.test(str)): } (or, if returning

Alternatively, proposed to re-throw a SolanaLogsError, which would avoid this altogether

@NeOMakinG
Copy link
Collaborator Author

NeOMakinG commented Dec 23, 2024

8ed7c3b

Found a better home for translation keys in 8ed7c3b

Should be ready for review again!

const txid = await executeSolanaTransaction(...args)
return txid
} catch (e) {
if (e instanceof Error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove me, all errors are instanceof Error

@@ -974,7 +974,10 @@
"unsafeQuote": "This amount is below the recommended minimum for this pair (%{recommendedMin} %{symbol}). This could cause your trade to fail or loss of funds.",
"rateLimitExceeded": "Rate limit exceeded, try again later",
"maxSlippageExceededWithExpectedSlippage": "The maximum allowed slippage tolerance for this trade has been exceeded during simulation. Expected slippage: %{expectedSlippage}%. Try again with a higher slipppage tolerance.",
"executionRevertedWithExpectedSlippage": "Execution reverted during execution. This may be due to insufficient slippage tolerance. Expected slippage: %{expectedSlippage}%. Try again with a higher slipppage tolerance."
"executionRevertedWithExpectedSlippage": "Execution reverted during execution. This may be due to insufficient slippage tolerance. Expected slippage: %{expectedSlippage}%. Try again with a higher slipppage tolerance.",
"slippageExceeded": "Slippage tolerance exceeded.",
Copy link
Contributor

@gomesalexandre gomesalexandre Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"slippageExceeded": "Slippage tolerance exceeded.",
"slippageToleranceExceeded": "Slippage tolerance exceeded.",

Comment on lines +8 to +18
export enum JUPITER_ERROR_PATTERNS {
SLIPPAGE_TOLERANCE_EXCEEDED = 'SlippageToleranceExceeded',
UNDER_MINIMUM_AMOUNT = 'RequireGteViolated',
CONSUMED_MORE_FEES = 'exceeded CUs meter',
}

export enum JUPITER_ERROR_NAMES {
SLIPPAGE_TOLERANCE_EXCEEDED = 'SlippageToleranceExceeded',
UNDER_MINIMUM_AMOUNT = 'UnderMinimumAmount',
CONSUMED_MORE_FEES = 'ConsumedMoreFees',
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: don't pluralize enums

Copy link
Contributor

@gomesalexandre gomesalexandre Dec 23, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick: <error> in values of JUPITER_ERROR_NAMES are the same as trade.errors.<error> (except for SlippageToleranceExceeded, see https://github.com/shapeshift/web/pull/8433/files#r1895898762) with a different casing only, we could use lodash/camelCase to avoid another mapping

Copy link
Contributor

@gomesalexandre gomesalexandre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much cleaner now! Added some additional cleanup comments to tackle at your own convenience, but happy with this one conceptually as well as runtime-wise:

https://jam.dev/c/8203fb6a-d518-453c-8a0c-bda5d6d40668

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Jupiter - decode "SlippageToleranceExceeded" errors
3 participants