Skip to content

Commit

Permalink
Test slippage calculation with real data (#85)
Browse files Browse the repository at this point in the history
* Remove test-data

* Add test using realistic data to help the slippage match our expectations

* Make it easier to test in local
  • Loading branch information
anxolin authored Aug 22, 2024
1 parent be1b58d commit 4eaa935
Show file tree
Hide file tree
Showing 44 changed files with 35,241 additions and 35,105 deletions.
115 changes: 63 additions & 52 deletions apps/api/scripts/test-slippage.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,72 +13,72 @@ const PAIRS_TEST = {
Mainnet: [
{
pair: 'USDC-DAI',
quoteToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
baseToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
baseToken: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
quoteToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
},
{
pair: 'DAI-ETH',
quoteToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
baseToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
baseToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
quoteToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
},
{
pair: 'DAI-WETH',
quoteToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
baseToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
baseToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
quoteToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
},
{
pair: 'PEPE-DAI',
quoteToken: '0x6982508145454Ce325dDbE47a25d4ec3d2311933',
baseToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
baseToken: '0x6982508145454Ce325dDbE47a25d4ec3d2311933',
quoteToken: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
},
{
pair: 'PEPE-WETH',
quoteToken: '0x6982508145454Ce325dDbE47a25d4ec3d2311933',
baseToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
baseToken: '0x6982508145454Ce325dDbE47a25d4ec3d2311933',
quoteToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
},
{
pair: 'COW-WETH',
quoteToken: '0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB',
baseToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
baseToken: '0xDEf1CA1fb7FBcDC777520aa7f396b4E015F497aB',
quoteToken: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
},
],
'Gnosis Chain': [
{
pair: 'USDC-xDAI',
quoteToken: '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83',
baseToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
baseToken: '0xDDAfbb505ad214D7b80b1f830fcCc89B60fb7A83',
quoteToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
},
{
pair: 'sDAI-xDAI',
quoteToken: '0xaf204776c7245bF4147c2612BF6e5972Ee483701',
baseToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
baseToken: '0xaf204776c7245bF4147c2612BF6e5972Ee483701',
quoteToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
},
{
pair: 'sDAI-wxDAI',
quoteToken: '0xaf204776c7245bF4147c2612BF6e5972Ee483701',
baseToken: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d',
baseToken: '0xaf204776c7245bF4147c2612BF6e5972Ee483701',
quoteToken: '0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d',
},
{
pair: 'WETH-xDAI',
quoteToken: '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1',
baseToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
baseToken: '0x6A023CCd1ff6F2045C3309768eAd9E68F978f6e1',
quoteToken: '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE',
},
],
Arbitrum: [
{
pair: 'USDC-DAI',
quoteToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
baseToken: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
baseToken: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831',
quoteToken: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
},
{
pair: 'DAI-WETH',
quoteToken: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
baseToken: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
baseToken: '0xDA10009cBd5D07dd0CeCc66161FC93D7c9000da1',
quoteToken: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
},
{
pair: 'COW-WETH',
quoteToken: '0xcb8b5CD20BdCaea9a010aC1F8d835824F5C87A04',
baseToken: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
baseToken: '0xcb8b5CD20BdCaea9a010aC1F8d835824F5C87A04',
quoteToken: '0x82aF49447D8a07e3bd95BD0d56f35241523fBab1',
},
],
};
Expand All @@ -98,42 +98,53 @@ const fetchForMarket = async (chainId, quoteToken, baseToken, endpoint) => {
};

const fetchVolatilityDetails = async (pair, chainId, quoteToken, baseToken) => {
const result = await fetchForMarket(
chainId,
quoteToken,
baseToken,
'volatilityDetails'
);

fs.writeFileSync(
`${chainId}-${pair}__volatilityDetails.json`,
JSON.stringify(result, null, 2)
);
return fetchForMarket(chainId, quoteToken, baseToken, 'volatilityDetails');
};

const fetchSlippageTolerance = async (pair, chainId, quoteToken, baseToken) => {
const result = await fetchForMarket(
chainId,
quoteToken,
baseToken,
'slippageTolerance'
);
console.log(
`${pair}: ${result ? result.slippageBps : 'Error fetching data'}`
);
fs.writeFileSync(
`${chainId}-${pair}__slippageTolerance.json`,
JSON.stringify(result, null, 2)
);
return fetchForMarket(chainId, quoteToken, baseToken, 'slippageTolerance');
};

(async function () {
for (const chain in PAIRS_TEST) {
console.log(`\nFetching slippage tolerances for ${chain}:`);
const chainId = NetworkToChainId[chain];
for (const { pair, quoteToken, baseToken } of PAIRS_TEST[chain]) {
await fetchSlippageTolerance(pair, chainId, quoteToken, baseToken);
await fetchVolatilityDetails(pair, chainId, quoteToken, baseToken);
const slippage = await fetchSlippageTolerance(
pair,
chainId,
quoteToken,
baseToken
);
const volatilityDetails = await fetchVolatilityDetails(
pair,
chainId,
quoteToken,
baseToken
);

if (!slippage) {
console.error(`Error fetching slippage tolerance for ${pair}`);
process.exit(1);
}

console.log(`${pair}: ${slippage.slippageBps}`);

fs.writeFileSync(
`${chainId}-${pair}.json`,
JSON.stringify(
{
pair,
chainId,
baseToken,
quoteToken,
slippageBps: slippage ? slippage.slippageBps : null,
volatilityDetails,
},
null,
2
)
);
}
}
})();
21 changes: 18 additions & 3 deletions apps/api/src/app/plugins/cors.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
import cors from "@fastify/cors";
import fp from "fastify-plugin";
import cors, { FastifyCorsOptions } from '@fastify/cors';
import fp from 'fastify-plugin';

export default fp(async (fastify, opts) => {
const options = {
const options: FastifyCorsOptions = {
...opts,
origin: false,
delegator: (req, callback) => {
const corsOptions = {
origin: false,
};

// do not include CORS headers for requests from localhost
const origin = req.headers.origin;
console.log('delegator', origin);
if (origin && /^http:\/\/localhost/.test(origin)) {
corsOptions.origin = true;
}

// callback expects two parameters: error and options
callback(null, corsOptions);
},
};
fastify.register(cors, options);
});
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ const POINTS_WITH_LOW_VOLATILITY = getPoints([
100.0001, 100.0002, 100.0003, 100.0004,
]); // 0.0001% each 5min

describe('SlippageServiceMain', () => {
/**
* Test specification for the SlippageService main implementation
*/
describe('SlippageServiceMain Specification', () => {
let slippageService: SlippageServiceMain;
let usdRepositoryMock: UsdRepository;

Expand Down
105 changes: 105 additions & 0 deletions libs/services/src/SlippageService/SlippageServiceMain.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { SlippageServiceMain } from './SlippageServiceMain';
import fs from 'fs';
import path from 'path';
import {
ChainNames,
SupportedChainId,
toSupportedChainId,
} from '../../../shared/src';

const getUsdPrice = jest.fn();
const getUsdPrices = jest.fn();

/**
* Test the SlippageService main implementation using realistic test data.
* One difference between these tests and the ones in SlippageServiceMain.spec.ts is that in these tests we will use
* real data for prices and USD we received from Coingecko and we save into test data files.
*
* These will allow to refine the slippage calculation algorithm to make it work well with real data.
*/
describe('SlippageServiceMain: Real test data', () => {
let slippageService: SlippageServiceMain;

// Read all files in test-data folder
const testDataDir = path.join(__dirname, 'test-data');
const testDataFiles = fs.readdirSync(testDataDir);

for (const fileName of testDataFiles) {
const {
pair,
baseToken: baseTokenAddress,
quoteToken: quoteTokenAddress,
chainId: chainIdValue,
volatilityDetails,
slippageBps,
} = readTestFile(path.join(testDataDir, fileName));

const chainId = toSupportedChainId(chainIdValue);
const chainName = ChainNames[chainId];

// Uncomment to tests a single pair
// if (pair !== 'WETH-xDAI' || chainId !== SupportedChainId.GNOSIS_CHAIN)
// continue;

test(`Expect ${chainName} ${pair} slippage to be ${slippageBps} BPS`, async () => {
const {
quoteToken: quoteTokenVolatilityDetails,
baseToken: baseTokenVolatilityDetails,
} = volatilityDetails;

// GIVEN: USD price for the base and quote tokens
getUsdPrice.mockImplementation(async (_chainId, tokenAddress) => {
if (tokenAddress === baseTokenAddress) {
return baseTokenVolatilityDetails.usdPrice;
} else {
return quoteTokenVolatilityDetails.usdPrice;
}
});

// GIVEN: USD prices for the base and quote tokens
getUsdPrices.mockImplementation(async (_chainId, tokenAddress) => {
if (tokenAddress === baseTokenAddress) {
return baseTokenVolatilityDetails.prices;
} else {
return quoteTokenVolatilityDetails.prices;
}
});

// WHEN: Get the slippage
const slippage = await slippageService.getSlippageBps({
chainId,
baseTokenAddress,
quoteTokenAddress,
});

// THEN: The slippage should be as expected
expect(slippage).toBe(slippageBps);
});
}

beforeEach(() => {
slippageService = new SlippageServiceMain({
getUsdPrice,
getUsdPrices,
});
});
});

function readTestFile(filePath: string) {
const testContent = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
const volatilityDetails = testContent.volatilityDetails;

volatilityDetails.quoteToken.prices =
volatilityDetails.quoteToken.prices.map(fixDateForPrices);
volatilityDetails.baseToken.prices =
volatilityDetails.baseToken.prices.map(fixDateForPrices);

return testContent;
}

function fixDateForPrices(price: any) {
return {
...price,
date: new Date(price.date),
};
}
Loading

0 comments on commit 4eaa935

Please sign in to comment.