diff --git a/packages/rest-api/src/routes/bridgeLimitsRoute.ts b/packages/rest-api/src/routes/bridgeLimitsRoute.ts index 14ab637671..392937ac28 100644 --- a/packages/rest-api/src/routes/bridgeLimitsRoute.ts +++ b/packages/rest-api/src/routes/bridgeLimitsRoute.ts @@ -8,6 +8,7 @@ import { isTokenSupportedOnChain } from './../utils/isTokenSupportedOnChain' import { isTokenAddress } from '../utils/isTokenAddress' import { normalizeNativeTokenAddress } from '../middleware/normalizeNativeTokenAddress' import { checksumAddresses } from '../middleware/checksumAddresses' +import { validateRouteExists } from '../validations/validateRouteExists' const router = express.Router() @@ -130,6 +131,13 @@ router.get( isTokenSupportedOnChain(value, req.query.toChain as string) ) .withMessage('Token not supported on specified chain'), + check() + .custom((_value, { req }) => { + const { fromChain, toChain, fromToken, toToken } = req.query + + return validateRouteExists(fromChain, fromToken, toChain, toToken) + }) + .withMessage('No valid route exists for the chain/token combination'), ], showFirstValidationError, bridgeLimitsController diff --git a/packages/rest-api/src/routes/bridgeRoute.ts b/packages/rest-api/src/routes/bridgeRoute.ts index 01fccb708b..0e4a88b947 100644 --- a/packages/rest-api/src/routes/bridgeRoute.ts +++ b/packages/rest-api/src/routes/bridgeRoute.ts @@ -8,6 +8,7 @@ import { bridgeController } from '../controllers/bridgeController' import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain' import { checksumAddresses } from '../middleware/checksumAddresses' import { normalizeNativeTokenAddress } from '../middleware/normalizeNativeTokenAddress' +import { validateRouteExists } from '../validations/validateRouteExists' const router = express.Router() @@ -222,6 +223,13 @@ router.get( ) .withMessage('Token not supported on specified chain'), check('amount').isNumeric().exists().withMessage('amount is required'), + check() + .custom((_value, { req }) => { + const { fromChain, toChain, fromToken, toToken } = req.query + + return validateRouteExists(fromChain, fromToken, toChain, toToken) + }) + .withMessage('No valid route exists for the chain/token combination'), ], showFirstValidationError, bridgeController diff --git a/packages/rest-api/src/routes/bridgeTxInfoRoute.ts b/packages/rest-api/src/routes/bridgeTxInfoRoute.ts index 09f249b99b..5a0ce30326 100644 --- a/packages/rest-api/src/routes/bridgeTxInfoRoute.ts +++ b/packages/rest-api/src/routes/bridgeTxInfoRoute.ts @@ -9,6 +9,7 @@ import { isTokenAddress } from '../utils/isTokenAddress' import { isTokenSupportedOnChain } from '../utils/isTokenSupportedOnChain' import { checksumAddresses } from '../middleware/checksumAddresses' import { normalizeNativeTokenAddress } from '../middleware/normalizeNativeTokenAddress' +import { validateRouteExists } from '../validations/validateRouteExists' const router = express.Router() @@ -163,6 +164,13 @@ router.get( .withMessage('destAddress is required') .custom((value) => isAddress(value)) .withMessage('Invalid destination address'), + check() + .custom((_value, { req }) => { + const { fromChain, toChain, fromToken, toToken } = req.query + + return validateRouteExists(fromChain, fromToken, toChain, toToken) + }) + .withMessage('No valid route exists for the chain/token combination'), ], showFirstValidationError, bridgeTxInfoController diff --git a/packages/rest-api/src/tests/bridgeLimitsRoute.test.ts b/packages/rest-api/src/tests/bridgeLimitsRoute.test.ts index 921393773b..9e68a5d20e 100644 --- a/packages/rest-api/src/tests/bridgeLimitsRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeLimitsRoute.test.ts @@ -3,6 +3,7 @@ import express from 'express' import bridgeLimitsRoute from '../routes/bridgeLimitsRoute' import { USDC, ETH } from '../constants/bridgeable' +import { NativeGasAddress } from '../constants' const app = express() app.use('/bridgeLimits', bridgeLimitsRoute) @@ -34,6 +35,20 @@ describe('Get Bridge Limits Route', () => { expect(response.body).toHaveProperty('minOriginAmount') }, 10_000) + it('should return 400 for unsupported route', async () => { + const response = await request(app).get('/bridgeLimits').query({ + fromChain: '1', + toChain: '10', + fromToken: NativeGasAddress, + toToken: USDC.addresses[10], + }) + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty( + 'message', + 'No valid route exists for the chain/token combination' + ) + }) + it('should return 400 for unsupported fromChain', async () => { const response = await request(app).get('/bridgeLimits').query({ fromChain: '999', @@ -46,7 +61,7 @@ describe('Get Bridge Limits Route', () => { 'message', 'Unsupported fromChain' ) - }, 10_000) + }) it('should return 400 for unsupported toChain', async () => { const response = await request(app).get('/bridgeLimits').query({ @@ -57,7 +72,7 @@ describe('Get Bridge Limits Route', () => { }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('message', 'Unsupported toChain') - }, 10_000) + }) it('should return 400 for missing fromToken', async () => { const response = await request(app).get('/bridgeLimits').query({ @@ -67,7 +82,7 @@ describe('Get Bridge Limits Route', () => { }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'fromToken') - }, 10_000) + }) it('should return 400 for missing toToken', async () => { const response = await request(app).get('/bridgeLimits').query({ @@ -77,5 +92,5 @@ describe('Get Bridge Limits Route', () => { }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'toToken') - }, 10_000) + }) }) diff --git a/packages/rest-api/src/tests/bridgeRoute.test.ts b/packages/rest-api/src/tests/bridgeRoute.test.ts index 756089f50b..3617d5d71b 100644 --- a/packages/rest-api/src/tests/bridgeRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeRoute.test.ts @@ -17,6 +17,7 @@ describe('Bridge Route with Real Synapse Service', () => { toToken: USDC.addresses[10], amount: '1000', }) + expect(response.status).toBe(200) expect(Array.isArray(response.body)).toBe(true) expect(response.body.length).toBeGreaterThan(0) @@ -55,6 +56,22 @@ describe('Bridge Route with Real Synapse Service', () => { expect(response.body[0]).toHaveProperty('bridgeFeeFormatted') }, 15000) + it('should return 400 for unsupported route', async () => { + const response = await request(app).get('/bridge').query({ + fromChain: '1', + toChain: '10', + fromToken: NativeGasAddress, + toToken: USDC.addresses[10], + amount: '10', + }) + + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty( + 'message', + 'No valid route exists for the chain/token combination' + ) + }) + it('should return 400 for unsupported fromChain, with error message', async () => { const response = await request(app).get('/bridge').query({ fromChain: '999', @@ -68,7 +85,7 @@ describe('Bridge Route with Real Synapse Service', () => { 'message', 'Unsupported fromChain' ) - }, 10000) + }) it('should return 400 for unsupported toChain, with error message', async () => { const response = await request(app).get('/bridge').query({ @@ -80,7 +97,7 @@ describe('Bridge Route with Real Synapse Service', () => { }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('message', 'Unsupported toChain') - }, 10000) + }) it('should return 400 for invalid fromToken address, with error message', async () => { const response = await request(app).get('/bridge').query({ @@ -95,7 +112,7 @@ describe('Bridge Route with Real Synapse Service', () => { 'message', 'Invalid fromToken address' ) - }, 10000) + }) it('should return 400 for token not supported on specified chain, with error message', async () => { const response = await request(app).get('/bridge').query({ @@ -110,7 +127,7 @@ describe('Bridge Route with Real Synapse Service', () => { 'message', 'Invalid fromToken address' ) - }, 10000) + }) it('should return 400 for missing amount, with error message', async () => { const response = await request(app).get('/bridge').query({ @@ -121,5 +138,5 @@ describe('Bridge Route with Real Synapse Service', () => { }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'amount') - }, 10000) + }) }) diff --git a/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts b/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts index 822491f887..9f5d183bbe 100644 --- a/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts +++ b/packages/rest-api/src/tests/bridgeTxInfoRoute.test.ts @@ -3,6 +3,7 @@ import express from 'express' import bridgeTxInfoRoute from '../routes/bridgeTxInfoRoute' import { USDC } from '../constants/bridgeable' +import { NativeGasAddress } from '../constants' const app = express() app.use('/bridgeTxInfo', bridgeTxInfoRoute) @@ -28,6 +29,22 @@ describe('Bridge TX Info Route', () => { ) }, 10_000) + it('should return 400 for unsupported route', async () => { + const response = await request(app).get('/bridgeTxInfo').query({ + fromChain: '1', + toChain: '10', + fromToken: NativeGasAddress, + toToken: USDC.addresses[10], + amount: '10', + destAddress: '0x742d35Cc6634C0532925a3b844Bc454e4438f44e', + }) + expect(response.status).toBe(400) + expect(response.body.error).toHaveProperty( + 'message', + 'No valid route exists for the chain/token combination' + ) + }) + it('should return 400 for unsupported fromChain', async () => { const response = await request(app).get('/bridgeTxInfo').query({ fromChain: '999', @@ -42,7 +59,7 @@ describe('Bridge TX Info Route', () => { 'message', 'Unsupported fromChain' ) - }, 10_000) + }) it('should return 400 for invalid fromToken address', async () => { const response = await request(app).get('/bridgeTxInfo').query({ @@ -58,7 +75,7 @@ describe('Bridge TX Info Route', () => { 'message', 'Invalid fromToken address' ) - }, 10_000) + }) it('should return 400 for token not supported on specified chain', async () => { const response = await request(app).get('/bridgeTxInfo').query({ @@ -74,7 +91,7 @@ describe('Bridge TX Info Route', () => { 'message', 'Invalid fromToken address' ) - }, 10_000) + }) it('should return 400 for missing amount', async () => { const response = await request(app).get('/bridgeTxInfo').query({ @@ -86,7 +103,7 @@ describe('Bridge TX Info Route', () => { }) expect(response.status).toBe(400) expect(response.body.error).toHaveProperty('field', 'amount') - }, 10_000) + }) it('should return 400 for invalid destAddress', async () => { const response = await request(app).get('/bridgeTxInfo').query({ @@ -102,5 +119,5 @@ describe('Bridge TX Info Route', () => { 'message', 'Invalid destination address' ) - }, 10_000) + }) }) diff --git a/packages/rest-api/src/utils/bridgeRouteMapping.ts b/packages/rest-api/src/utils/bridgeRouteMapping.ts index 996652e543..992ccffe0f 100644 --- a/packages/rest-api/src/utils/bridgeRouteMapping.ts +++ b/packages/rest-api/src/utils/bridgeRouteMapping.ts @@ -13,7 +13,7 @@ type TransformedBridgeRoutes = Record const constructJSON = ( swappableMap, exclusionList -): TransformedBridgeRoutes => { +): StringifiedBridgeRoutes => { const result = {} // Iterate through the chains @@ -56,8 +56,7 @@ const constructJSON = ( } } } - - return transformBridgeRouteValues(result) + return result } const transformPair = (string: string): any => { @@ -97,4 +96,7 @@ const transformBridgeRouteValues = ( ) } -export const BRIDGE_ROUTE_MAPPING = constructJSON(BRIDGE_MAP, []) +export const BRIDGE_ROUTE_MAPPING_SYMBOLS = constructJSON(BRIDGE_MAP, []) +export const BRIDGE_ROUTE_MAPPING = transformBridgeRouteValues( + BRIDGE_ROUTE_MAPPING_SYMBOLS +) diff --git a/packages/rest-api/src/validations/validateRouteExists.ts b/packages/rest-api/src/validations/validateRouteExists.ts new file mode 100644 index 0000000000..4339d9d8c1 --- /dev/null +++ b/packages/rest-api/src/validations/validateRouteExists.ts @@ -0,0 +1,20 @@ +import { tokenAddressToToken } from '../utils/tokenAddressToToken' +import { BRIDGE_ROUTE_MAPPING_SYMBOLS } from '../utils/bridgeRouteMapping' + +export const validateRouteExists = (fromChain, fromToken, toChain, toToken) => { + const fromTokenInfo = tokenAddressToToken(fromChain.toString(), fromToken) + const toTokenInfo = tokenAddressToToken(toChain.toString(), toToken) + + if (!fromTokenInfo || !toTokenInfo) { + return false + } + + const key = `${fromTokenInfo.symbol}-${fromChain}` + const routes = BRIDGE_ROUTE_MAPPING_SYMBOLS[key] + + if (!routes) { + return false + } + + return routes.includes(`${toTokenInfo.symbol}-${toChain}`) +} diff --git a/packages/rest-api/src/validations/validateTokens.ts b/packages/rest-api/src/validations/validateTokens.ts deleted file mode 100644 index 6e89a9c70c..0000000000 --- a/packages/rest-api/src/validations/validateTokens.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { check } from 'express-validator' - -import { tokenSymbolToToken } from '../utils/tokenSymbolToToken' - -export const validateTokens = (chainParam, tokenParam, paramName) => { - return check(tokenParam) - .isString() - .exists() - .withMessage(`${paramName} is required`) - .custom((value, { req }) => { - const chain = req.query[chainParam] - const tokenInfo = tokenSymbolToToken(chain, value) - if (!tokenInfo) { - throw new Error(`Invalid ${paramName} symbol`) - } - if (!req.res.locals.tokenInfo) { - req.res.locals.tokenInfo = {} - } - req.res.locals.tokenInfo[paramName] = tokenInfo - return true - }) -}