Skip to content

Commit

Permalink
feat: implement UsualPP
Browse files Browse the repository at this point in the history
  • Loading branch information
KanievskyiDanylo committed Jan 10, 2025
1 parent 3974486 commit 492654c
Show file tree
Hide file tree
Showing 10 changed files with 1,383 additions and 0 deletions.
750 changes: 750 additions & 0 deletions src/abi/usual-pp/abi.json

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/dex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ import { Stader } from './stader/stader';
import { UsualBond } from './usual/usual-bond';
import { UsualMWrappedM } from './usual/usual-m-wrapped-m';
import { UsualMUsd0 } from './usual/usual-m-usd0';
import { UsualPP } from './usual-pp/usual-pp';

const LegacyDexes = [
CurveV2,
Expand Down Expand Up @@ -194,6 +195,7 @@ const Dexes = [
FluidDex,
UsualMWrappedM,
UsualMUsd0,
UsualPP,
];

export type LegacyDexConstructor = new (dexHelper: IDexHelper) => IDexTxBuilder<
Expand Down
18 changes: 18 additions & 0 deletions src/dex/usual-pp/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Network } from '../../constants';
import { DexConfigMap } from '../../types';
import { DexParams } from './types';

export const Config: DexConfigMap<DexParams> = {
UsualPP: {
[Network.MAINNET]: {
USD0: {
address: '0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5',
decimals: 18,
},
USD0PP: {
address: '0x35d8949372d46b7a3d5a56006ae77b215fc69bc0',
decimals: 18,
},
},
},
};
77 changes: 77 additions & 0 deletions src/dex/usual-pp/pool.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import _ from 'lodash';
import { Interface } from '@ethersproject/abi';
import { IDexHelper } from '../../dex-helper';
import { StatefulEventSubscriber } from '../../stateful-event-subscriber';
import { Address, Log, Logger } from '../../types';
import { AsyncOrSync, DeepReadonly } from 'ts-essentials';
import { PoolState } from './types';
import { getOnChainState } from './utils';

export class UsualPool extends StatefulEventSubscriber<PoolState> {
decoder = (log: Log) => this.poolInterface.parseLog(log);

constructor(
parentName: string,
protected dexHelper: IDexHelper,
private poolAddress: Address,
private poolInterface: Interface,
logger: Logger,
) {
super(parentName, 'UsualPool', dexHelper, logger);
this.addressesSubscribed = [poolAddress];
}

protected processLog(
state: DeepReadonly<PoolState>,
log: Readonly<Log>,
): AsyncOrSync<DeepReadonly<PoolState> | null> {
const event = this.decoder(log);
const _state: PoolState = _.cloneDeep(state);
if (event.name === 'FloorPriceUpdated') {
_state.price = BigInt(event.args.newFloorPrice);
return _state;
}
return null;
}

async generateState(
blockNumber: number | 'latest' = 'latest',
): Promise<DeepReadonly<PoolState>> {
const state = await getOnChainState(
this.dexHelper.multiContract,
this.poolAddress,
this.poolInterface,
blockNumber,
);
return state;
}

async getPrice(blockNumber: number): Promise<bigint> {
const state = await this.getOrGenerateState(blockNumber);
if (!state) throw new Error('Cannot compute state');
return state.price;
}

async getOrGenerateState(
blockNumber: number,
): Promise<DeepReadonly<PoolState> | null> {
const state = this.getState(blockNumber);
if (state) {
return state;
}

this.logger.debug(
`No state found for ${this.addressesSubscribed[0]}, generating new one`,
);
const newState = await this.generateState(blockNumber);

if (!newState) {
this.logger.debug(
`Could not regenerate state for ${this.addressesSubscribed[0]}`,
);
return null;
}
this.setState(newState, blockNumber);
return newState;
}
}
12 changes: 12 additions & 0 deletions src/dex/usual-pp/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { Address } from '../../types';

export type PoolState = {
price: bigint;
};

export type UsualPPData = {};

export type DexParams = {
USD0: { address: Address; decimals: number };
USD0PP: { address: Address; decimals: number };
};
70 changes: 70 additions & 0 deletions src/dex/usual-pp/usual-events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import dotenv from 'dotenv';
dotenv.config();

import { Network } from '../../constants';
import { DummyDexHelper } from '../../dex-helper/index';
import { testEventSubscriber } from '../../../tests/utils-events';
import { PoolState } from './types';
import { Config } from './config';
import { UsualPool } from './pool';
import { PoolConfig } from '../curve-v1/types';
import UsualPoolAbi from '../../abi/usual-pp/abi.json';
import { Interface } from '@ethersproject/abi';

jest.setTimeout(50 * 1000);
const dexKey = 'UsualPP';
const network = Network.MAINNET;
const config = Config[dexKey][network];
const dexHelper = new DummyDexHelper(network);
const logger = dexHelper.getLogger(dexKey);

async function fetchPoolState(
usualPool: UsualPool,
blockNumber: number,
): Promise<PoolState> {
return usualPool.generateState(blockNumber);
}

function compareState(state: PoolState, expectedState: PoolState) {
expect(state).toEqual(expectedState);
}

describe('UsualPP Event', function () {
const blockNumbers: { [eventName: string]: number[] } = {
FloorPriceUpdated: [21589702],
};

describe('UsualPool', function () {
let usualPool: UsualPool;
let blockNumber: number;

const usualPoolIface = new Interface(UsualPoolAbi);

beforeAll(async function () {
blockNumber = await dexHelper.web3Provider.eth.getBlockNumber();
});

Object.keys(blockNumbers).forEach((event: string) => {
blockNumbers[event].forEach((blockNumber: number) => {
it(`Should return the correct state after the ${blockNumber}:${event}`, async function () {
usualPool = new UsualPool(
dexKey,
dexHelper,
config.USD0PP.address,
usualPoolIface,
logger,
);
await testEventSubscriber(
usualPool,
usualPool.addressesSubscribed,
(_blockNumber: number) => fetchPoolState(usualPool, _blockNumber),
blockNumber,
`${dexKey}_${config.USD0PP.address}`,
dexHelper.provider,
compareState,
);
});
});
});
});
});
143 changes: 143 additions & 0 deletions src/dex/usual-pp/usual-integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* eslint-disable no-console */
import dotenv from 'dotenv';
dotenv.config();

import { Interface } from '@ethersproject/abi';
import { DummyDexHelper } from '../../dex-helper/index';
import { Network, SwapSide } from '../../constants';
import { BI_POWS } from '../../bigint-constants';
import {
checkPoolPrices,
checkConstantPoolPrices,
checkPoolsLiquidity,
} from '../../../tests/utils';
import { Tokens } from '../../../tests/constants-e2e';
import { UsualPP } from './usual-pp';

async function testPricingOnNetwork(
usual: UsualPP,
network: Network,
dexKey: string,
blockNumber: number,
srcTokenSymbol: string,
destTokenSymbol: string,
side: SwapSide,
amounts: bigint[],
funcNameToCheck: string,
) {
const networkTokens = Tokens[network];

console.log(amounts);

const pools = await usual.getPoolIdentifiers(
networkTokens[srcTokenSymbol],
networkTokens[destTokenSymbol],
side,
blockNumber,
);
console.log(
`${srcTokenSymbol} <> ${destTokenSymbol} Pool Identifiers: `,
pools,
);

expect(pools.length).toBeGreaterThan(0);

const poolPrices = await usual.getPricesVolume(
networkTokens[srcTokenSymbol],
networkTokens[destTokenSymbol],
amounts,
side,
blockNumber,
pools,
);
console.log(
`${srcTokenSymbol} <> ${destTokenSymbol} Pool Prices: `,
poolPrices,
);

expect(poolPrices).not.toBeNull();
// Check if onchain pricing equals to calculated ones
}

describe('UsualPP', function () {
const dexKey = 'UsualPP';
let blockNumber: number;
let usualPP: UsualPP;

describe('Mainnet', () => {
const network = Network.MAINNET;
const dexHelper = new DummyDexHelper(network);

// Don't forget to update relevant tokens in constant-e2e.ts

const amountsForSell = [
0n,
1n * BI_POWS[18],
2n * BI_POWS[18],
3n * BI_POWS[18],
4n * BI_POWS[18],
5n * BI_POWS[18],
6n * BI_POWS[18],
7n * BI_POWS[18],
8n * BI_POWS[18],
9n * BI_POWS[18],
10n * BI_POWS[18],
];

beforeAll(async () => {
blockNumber = await dexHelper.web3Provider.eth.getBlockNumber();
usualPP = new UsualPP(network, dexKey, dexHelper);
if (usualPP.initializePricing) {
await usualPP.initializePricing(blockNumber);
}
});

it('getPoolIdentifiers and getPricesVolume SELL', async function () {
await testPricingOnNetwork(
usualPP,
network,
dexKey,
blockNumber,
'USD0++',
'USD0',
SwapSide.SELL,
amountsForSell,
'',
);
});

it('getTopPoolsForToken: USD0', async function () {
const tokenA = Tokens[network]['USD0'];
const dexHelper = new DummyDexHelper(network);
const usualPP = new UsualPP(network, dexKey, dexHelper);

const poolLiquidity = await usualPP.getTopPoolsForToken(
tokenA.address,
10,
);
console.log(
`${tokenA.symbol} Top Pools:`,
JSON.stringify(poolLiquidity, null, 2),
);

checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey);
});

it('getTopPoolsForToken: USD0++', async function () {
const tokenA = Tokens[network]['USD0++'];
const dexHelper = new DummyDexHelper(network);
const usualPP = new UsualPP(network, dexKey, dexHelper);

const poolLiquidity = await usualPP.getTopPoolsForToken(
tokenA.address,
10,
);
console.log(
`${tokenA.symbol} Top Pools:`,
JSON.stringify(poolLiquidity, null, 2),
);

checkPoolsLiquidity(poolLiquidity, tokenA.address, dexKey);
});
});
});
Loading

0 comments on commit 492654c

Please sign in to comment.