Skip to content

Commit

Permalink
Merge branch 'master' into feat/configuration
Browse files Browse the repository at this point in the history
Fix woo-fi, dystopia, platypus, LO etc. and conflicts.
  • Loading branch information
ColonelJ committed Jun 27, 2022
2 parents 7af3948 + d1f66b6 commit c12be3c
Show file tree
Hide file tree
Showing 98 changed files with 14,074 additions and 1,030 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ node_modules
build/
yarn-error.log
.env
.vscode/
tests/logs.json
tests/states.json
tests/configs.json
.idea/
local-scripts
2 changes: 1 addition & 1 deletion dex-template/__DexName__-e2e.test.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,6 @@ describe('__DexName__ E2E', () => {
}),
);

// TODO: Add any aditional test cases required to test __DexName__
// TODO: Add any additional test cases required to test __DexName__
});
});
4 changes: 2 additions & 2 deletions dex-template/__DexName__-events.test.ts.template
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ import { PoolState } from './types';

This test script adds unit tests for __DexName__ event based
system. This is done by fetching the state on-chain before the
event block, manually pushing the block logs to the event-subsriber,
event block, manually pushing the block logs to the event-subscriber,
comparing the local state with on-chain state.

Most of the logic for testing is abstracted by `testEventSubscriber`.
You need to do two things to make the tests work:

1. Fetch the block numbers where certain events were released. You
can modify the `./scripts/fetch-event-blocknumber.ts` to get the
blocknumbers for different events. Make sure to get sufficient
block numbers for different events. Make sure to get sufficient
number of blockNumbers to cover all possible cases for the event
mutations.

Expand Down
112 changes: 94 additions & 18 deletions dex-template/__DexName__-integration.test.ts.template
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import dotenv from 'dotenv';
dotenv.config();

import { Interface, Result } from '@ethersproject/abi';
import { DummyDexHelper } from '../../dex-helper/index';
import { Network, SwapSide } from '../../constants';
import { BI_POWS } from '../../bigint-constants';
Expand Down Expand Up @@ -36,21 +37,89 @@ const TokenB = Tokens[network][TokenBSymbol];

const amounts = [0n, BI_POWS[18], 2000000000000000000n];

const dexHelper = new DummyDexHelper(network);
const dexKey = '__DexName__';

function getReaderCalldata(
exchangeAddress: string,
readerIface: Interface,
amounts: bigint[],
funcName: string,
// TODO: Put here additional arguments you need
) {
return amounts.map(amount => ({
target: exchangeAddress,
callData: readerIface.encodeFunctionData(funcName, [
// TODO: Put here additional arguments to encode them
amount,
]),
}));
}

function decodeReaderResult(
results: Result,
readerIface: Interface,
funcName: string,
) {
// TODO: Adapt this function for your needs
return results.map(result => {
const parsed = readerIface.decodeFunctionResult(funcName, result);
return BigInt(parsed[0]._hex);
});
}

async function checkOnChainPricing(
__DexNameCamel__: __DexName__,
funcName: string,
blockNumber: number,
prices: bigint[],
) {
const exchangeAddress = ''; // TODO: Put here the real exchange address

// TODO: Replace dummy interface with the real one
// Normally you can get it from __DexNameCamel__.Iface or from eventPool.
// It depends on your implementation
const readerIface = new Interface('');

const readerCallData = getReaderCalldata(
exchangeAddress,
readerIface,
amounts.slice(1),
funcName,
);
const readerResult = (
await dexHelper.multiContract.methods
.aggregate(readerCallData)
.call({}, blockNumber)
).returnData;
const expectedPrices = [0n].concat(
decodeReaderResult(
readerResult,
readerIface,
funcName,
),
);

expect(prices).toEqual(expectedPrices);
}

describe('__DexName__', function () {
it('getPoolIdentifiers and getPricesVolume SELL', async function () {
const dexHelper = new DummyDexHelper(network);
const blocknumber = await dexHelper.provider.getBlockNumber();
const __DexNameCamel__ = new __DexName__(network, dexKey, dexHelper);
let blockNumber: number;
let __DexNameCamel__: __DexName__;

await __DexNameCamel__.initializePricing(blocknumber);
beforeAll(async () => {
blockNumber = await dexHelper.provider.getBlockNumber();

__DexNameCamel__ = new __DexName__(network, dexKey, dexHelper);
await __DexNameCamel__.initializePricing(blockNumber);
});

it('getPoolIdentifiers and getPricesVolume SELL', async function () {
const pools = await __DexNameCamel__.getPoolIdentifiers(
TokenA,
TokenB,
SwapSide.SELL,
blocknumber,
blockNumber,
);
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools);

Expand All @@ -61,7 +130,7 @@ describe('__DexName__', function () {
TokenB,
amounts,
SwapSide.SELL,
blocknumber,
blockNumber,
pools,
);
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices);
Expand All @@ -72,20 +141,22 @@ describe('__DexName__', function () {
} else {
checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey);
}

// Check if onchain pricing equals to calculated ones
await checkOnChainPricing(
__DexNameCamel__,
'', // TODO: Put here the functionName to call
blockNumber,
poolPrices![0].prices,
);
});

it('getPoolIdentifiers and getPricesVolume BUY', async function () {
const dexHelper = new DummyDexHelper(network);
const blocknumber = await dexHelper.provider.getBlockNumber();
const __DexNameCamel__ = new __DexName__(network, dexKey, dexHelper);

await __DexNameCamel__.initializePricing(blocknumber);

const pools = await __DexNameCamel__.getPoolIdentifiers(
TokenA,
TokenB,
SwapSide.BUY,
blocknumber,
blockNumber,
);
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Identifiers: `, pools);

Expand All @@ -96,7 +167,7 @@ describe('__DexName__', function () {
TokenB,
amounts,
SwapSide.BUY,
blocknumber,
blockNumber,
pools,
);
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices);
Expand All @@ -107,12 +178,17 @@ describe('__DexName__', function () {
} else {
checkPoolPrices(poolPrices!, amounts, SwapSide.BUY, dexKey);
}

// Check if onchain pricing equals to calculated ones
await checkOnChainPricing(
__DexNameCamel__,
'', // TODO: Put here the functionName to call
blockNumber,
poolPrices![0].prices,
);
});

it('getTopPoolsForToken', async function () {
const dexHelper = new DummyDexHelper(network);
const __DexNameCamel__ = new __DexName__(network, dexKey, dexHelper);

const poolLiquidity = await __DexNameCamel__.getTopPoolsForToken(
TokenA.address,
10,
Expand Down
85 changes: 85 additions & 0 deletions dex-template/__DexName__-pool.ts.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { Interface } from '@ethersproject/abi';
import { DeepReadonly } from 'ts-essentials';
import { Log, Logger } from '../../types';
import { StatefulEventSubscriber } from '../../stateful-event-subscriber';
import { IDexHelper } from '../../dex-helper/idex-helper';
import { __DexName__Data, PoolState } from './types';
import { __DexName__Config } from './config';

export class __DexName__EventPool extends StatefulEventSubscriber<PoolState> {
handlers: {
[event: string]: (event: any, pool: PoolState, log: Log) => PoolState;
} = {};

logDecoder: (log: Log) => any;

addressesSubscribed: string[];

constructor(
protected parentName: string,
protected network: number,
protected dexHelper: IDexHelper,
logger: Logger,
protected __DexNameCamel__Iface = new Interface(
'' /* TODO: Import and put here __DexName__ ABI */,
), // TODO: add any additional params required for event subscriber
) {
super(parentName, logger);

// TODO: make logDecoder decode logs that
this.logDecoder = (log: Log) => this.__DexNameCamel__Iface.parseLog(log);
this.addressesSubscribed = [
/* subscribed addresses */
];

// Add handlers
this.handlers['myEvent'] = this.handleMyEvent.bind(this);
}

/**
* The function is called every time any of the subscribed
* addresses release log. The function accepts the current
* state, updates the state according to the log, and returns
* the updated state.
* @param state - Current state of event subscriber
* @param log - Log released by one of the subscribed addresses
* @returns Updates state of the event subscriber after the log
*/
protected processLog(
state: DeepReadonly<PoolState>,
log: Readonly<Log>,
): DeepReadonly<PoolState> | null {
try {
const event = this.logDecoder(log);
if (event.name in this.handlers) {
return this.handlers[event.name](event, state, log);
}
return state;
} catch (e) {
this.logger.error(
`Error_${this.parentName}_processLog could not parse the log with topic ${log.topics}:`,
e,
);
return null;
}
}

/**
* The function generates state using on-chain calls. This
* function is called to regenerate state if the event based
* system fails to fetch events and the local state is no
* more correct.
* @param blockNumber - Blocknumber for which the state should
* should be generated
* @returns state of the event subscriber at blocknumber
*/
async generateState(blockNumber: number): Promise<Readonly<PoolState>> {
// TODO: complete me!
return {};
}

// Its just a dummy example
handleMyEvent(event: any, pool: PoolState, log: Log) {
return pool;
}
}
Loading

0 comments on commit c12be3c

Please sign in to comment.