Skip to content

Commit

Permalink
feat: add response check for Paraswap API (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
gergelylovas authored Nov 26, 2024
1 parent 889e563 commit ac914da
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 4 deletions.
1 change: 1 addition & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ module.exports = {
'^.+\\.(js|jsx)$': 'babel-jest',
},
transformIgnorePatterns: [`/node_modules/(?!micro-eth-signer)`],
modulePathIgnorePatterns: ['<rootDir>/dist/'],
globals: {
EVM_PROVIDER_INFO_NAME: 'EVM_PROVIDER_INFO_NAME',
EVM_PROVIDER_INFO_UUID: 'EVM_PROVIDER_INFO_UUID',
Expand Down
186 changes: 185 additions & 1 deletion src/contexts/SwapProvider/SwapProvider.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ jest.mock('react-i18next', () => ({

jest.mock('ethers');

describe('contexts/SwapProvider', () => {
describe.only('contexts/SwapProvider', () => {
const connectionContext = {
request: jest.fn(),
events: jest.fn(),
Expand Down Expand Up @@ -108,6 +108,7 @@ describe('contexts/SwapProvider', () => {

jest.spyOn(global, 'fetch').mockResolvedValue({
json: async () => ({}),
ok: true,
} as any);

jest.mocked(useConnectionContext).mockReturnValue(connectionContext);
Expand Down Expand Up @@ -279,6 +280,7 @@ describe('contexts/SwapProvider', () => {
json: () => ({
error: 'Invalid tokens',
}),
ok: true,
} as any);
});

Expand All @@ -301,6 +303,7 @@ describe('contexts/SwapProvider', () => {
destAmount: 1000,
},
}),
ok: true,
} as any);

const { getRate } = getSwapProvider();
Expand Down Expand Up @@ -481,10 +484,23 @@ describe('contexts/SwapProvider', () => {
let requestMock;

const mockedTx = {
gas: '0x2710', // 10000n
data: 'approval-tx-data',
};

beforeEach(() => {
jest.spyOn(global, 'fetch').mockResolvedValue({
json: jest.fn().mockResolvedValue({
data: 'data',
to: '0xParaswapContractAddress',
from: '0x123',
value: '0x0',
gas: '1223',
chainId: 123,
}),
ok: true,
} as any);

allowanceMock = jest.fn().mockResolvedValue(0);
requestMock = jest.fn().mockResolvedValue('0xALLOWANCE_HASH');

Expand Down Expand Up @@ -528,7 +544,12 @@ describe('contexts/SwapProvider', () => {
json: jest.fn().mockResolvedValue({
data: 'data',
to: '0xParaswapContractAddress',
from: '0x123',
value: '0x0',
chainId: 123,
someExtraParam: '123',
}),
ok: true,
} as any);

jest.mocked(Contract).mockReturnValueOnce({
Expand Down Expand Up @@ -585,13 +606,176 @@ describe('contexts/SwapProvider', () => {
);
});

it('verifies Paraswap API response for correct parameters', async () => {
const requestMock = jest.fn();

jest.spyOn(global, 'fetch').mockResolvedValue({
json: jest.fn().mockResolvedValue({
data: '',
to: '',
from: '0x123',
value: '0x0',
chainId: 123,
}),
ok: true,
} as any);

jest.mocked(Contract).mockReturnValueOnce({
allowance: jest.fn().mockResolvedValue(Infinity),
} as any);

jest.mocked(useConnectionContext).mockReturnValue({
request: requestMock.mockResolvedValue('0xSwapHash'),
events: jest.fn(),
} as any);

const { swap } = getSwapProvider();

const {
destAmount,
destDecimals,
destToken,
srcAmount,
srcDecimals,
srcToken,
priceRoute,
gasLimit,
slippage,
} = getSwapParams();

await expect(
swap({
srcToken,
srcDecimals,
srcAmount,
destToken,
destDecimals,
destAmount,
gasLimit,
priceRoute,
slippage,
})
).rejects.toThrow('Data Error: Error: Invalid transaction params');
});

it('handles Paraswap API error responses', async () => {
const requestMock = jest.fn();

jest.spyOn(global, 'fetch').mockResolvedValue({
json: jest
.fn()
.mockResolvedValue({ message: 'Some API error happened' }),
ok: true,
} as any);

jest.mocked(Contract).mockReturnValueOnce({
allowance: jest.fn().mockResolvedValue(Infinity),
} as any);

jest.mocked(useConnectionContext).mockReturnValue({
request: requestMock.mockResolvedValue('0xSwapHash'),
events: jest.fn(),
} as any);

const { swap } = getSwapProvider();

const {
destAmount,
destDecimals,
destToken,
srcAmount,
srcDecimals,
srcToken,
priceRoute,
gasLimit,
slippage,
} = getSwapParams();

await expect(
swap({
srcToken,
srcDecimals,
srcAmount,
destToken,
destDecimals,
destAmount,
gasLimit,
priceRoute,
slippage,
})
).rejects.toThrow('Data Error: Error: Some API error happened');
});

it('handles API HTTP errors', async () => {
const requestMock = jest.fn();

jest.spyOn(global, 'fetch').mockResolvedValue({
json: jest.fn().mockResolvedValue({
data: 'data',
to: '0xParaswapContractAddress',
from: '0x123',
value: '0x0',
chainId: 123,
}),
ok: false,
} as any);

jest.mocked(Contract).mockReturnValueOnce({
allowance: jest.fn().mockResolvedValue(Infinity),
} as any);

jest.mocked(useConnectionContext).mockReturnValue({
request: requestMock.mockResolvedValue('0xSwapHash'),
events: jest.fn(),
} as any);

const { swap } = getSwapProvider();

const {
destAmount,
destDecimals,
destToken,
srcAmount,
srcDecimals,
srcToken,
priceRoute,
gasLimit,
slippage,
} = getSwapParams();

await expect(
swap({
srcToken,
srcDecimals,
srcAmount,
destToken,
destDecimals,
destAmount,
gasLimit,
priceRoute,
slippage,
})
).rejects.toThrow('Data Error: Error: Invalid transaction params');
});

describe('when everything goes right', () => {
let allowanceMock;
let requestMock;

beforeEach(() => {
allowanceMock = jest.fn().mockResolvedValue(0);

jest.spyOn(global, 'fetch').mockResolvedValue({
json: async () => ({
data: 'data',
to: '0xParaswapContractAddress',
from: '0x123',
value: '0x0',
chainId: 123,
}),
ok: true,
} as any);

requestMock = jest
.fn()
.mockResolvedValueOnce('0xALLOWANCE_HASH')
Expand Down
32 changes: 29 additions & 3 deletions src/contexts/SwapProvider/SwapProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ import {
hasParaswapError,
DISALLOWED_SWAP_ASSETS,
} from './models';
import Joi from 'joi';
import { isAPIError } from '@src/pages/Swap/utils';

export const SwapContext = createContext<SwapContextAPI>({} as any);

Expand Down Expand Up @@ -198,6 +200,16 @@ export function SwapContextProvider({ children }: { children: any }) {
throw new Error(`Feature (SWAP) is currently unavailable`);
}

const responseSchema = Joi.object({
to: Joi.string().required(),
from: Joi.string().required(),
value: Joi.string().required(),
data: Joi.string().required(),
chainId: Joi.number().required(),
gas: Joi.string().optional(),
gasPrice: Joi.string().optional(),
}).unknown();

const query = new URLSearchParams(options as Record<string, string>);
const txURL = `${
(paraswap as any).apiURL
Expand Down Expand Up @@ -226,7 +238,20 @@ export function SwapContextProvider({ children }: { children: any }) {
},
body: JSON.stringify(txConfig),
});
return await response.json();
const transactionParamsOrError: Transaction | APIError =
await response.json();
const validationResult = responseSchema.validate(
transactionParamsOrError
);

if (!response.ok || validationResult.error) {
if (isAPIError(transactionParamsOrError)) {
throw new Error(transactionParamsOrError.message);
}
throw new Error('Invalid transaction params');
}

return transactionParamsOrError;
},
[featureFlags, paraswap]
);
Expand Down Expand Up @@ -363,7 +388,8 @@ export function SwapContextProvider({ children }: { children: any }) {
params: [
{
chainId: ChainId.AVALANCHE_MAINNET_ID.toString(),
gasLimit: String(approveGasLimit || gasLimit),
gas:
'0x' + Number(approveGasLimit || gasLimit).toString(16),
data,
from: activeAccount.addressC,
to: srcTokenAddress,
Expand Down Expand Up @@ -430,7 +456,7 @@ export function SwapContextProvider({ children }: { children: any }) {
params: [
{
chainId: ChainId.AVALANCHE_MAINNET_ID.toString(),
gasLimit: String(txBuildData.gas),
gas: '0x' + Number(txBuildData.gas).toString(16),
data: txBuildData.data,
to: txBuildData.to,
from: activeAccount.addressC,
Expand Down

0 comments on commit ac914da

Please sign in to comment.