Skip to content

Commit

Permalink
Add Morphex (GMX fork)
Browse files Browse the repository at this point in the history
  • Loading branch information
pbnather committed Aug 21, 2023
1 parent 7581781 commit 85b2567
Show file tree
Hide file tree
Showing 8 changed files with 362 additions and 1 deletion.
2 changes: 1 addition & 1 deletion scripts/dex-integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ function testIntegration(argv: IOptions) {
process.env.NODE_ENV = 'test';
}

require('../node_modules/jest-cli/build/cli').run(
require('../node_modules/jest-cli/build/run').run(
`src\/dex\/${dexNameParam}\/.+\.test\.ts`,
);
}
Expand Down
2 changes: 2 additions & 0 deletions src/dex/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { SpiritSwapV3 } from './quickswap/spiritswap-v3';
import { TraderJoeV21 } from './trader-joe-v2.1';
import { PancakeswapV3 } from './pancakeswap-v3/pancakeswap-v3';
import { Algebra } from './algebra/algebra';
import { Morphex } from './morphex/morphex';

const LegacyDexes = [
CurveV2,
Expand Down Expand Up @@ -137,6 +138,7 @@ const Dexes = [
MaverickV1,
Camelot,
SwaapV2,
Morphex,
];

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

export const MorphexConfig: DexConfigMap<DexParams> = {
Morphex: {
[Network.FANTOM]: {
vault: '0x245cD6d33578de9aF75a3C0c636c726b1A8cbdAa',
reader: '0xcA47b9b612a152ece991F31d8D3547D73BaF2Ecc',
priceFeed: '0x7a451DE877CbB6551AACa671d0458B6f9dF1e29A',
fastPriceFeed: '0x7f54C35A38D89fcf5Fe516206E6628745ed38CC7',
fastPriceEvents: '0xDc7C389be5da32e326A261dC0126feCa7AE04d79',
usdg: '0xe135c7BFfda932b5B862Da442cF4CbC4d43DC3Ad',
},
},
};

export const Adapters: {
[chainId: number]: {
[side: string]: { name: string; index: number }[] | null;
};
} = {
[Network.FANTOM]: {
[SwapSide.SELL]: [
{
name: 'FantomAdapter01',
index: 6, // TODO: it's for aavev3, but there is no Morphex adapter
},
],
},
};
91 changes: 91 additions & 0 deletions src/dex/morphex/morphex-e2e.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import dotenv from 'dotenv';
dotenv.config();

import { testE2E } from '../../../tests/utils-e2e';
import {
Tokens,
Holders,
NativeTokenSymbols,
} from '../../../tests/constants-e2e';
import { Network, ContractMethod, SwapSide } from '../../constants';
import { StaticJsonRpcProvider } from '@ethersproject/providers';
import { generateConfig } from '../../config';

describe('Morphex E2E', () => {
const dexKey = 'Morphex';

describe('Morphex Fantom', () => {
const network = Network.FANTOM;
const tokens = Tokens[network];
const holders = Holders[network];
const provider = new StaticJsonRpcProvider(
generateConfig(network).privateHttpProvider,
network,
);

const tokenASymbol: string = 'axlUSDC';
const tokenBSymbol: string = 'lzUSDC';
const nativeTokenSymbol = NativeTokenSymbols[network];

const tokenAAmount: string = '500000000'; // 500 Axelar USDC
const tokenBAmount: string = '500000000'; // 500 Layer Zero USDC
const nativeTokenAmount = '100000000000000000000'; // 100 FTM

const sideToContractMethods = new Map([
[
SwapSide.SELL,
[
ContractMethod.simpleSwap,
// ContractMethod.multiSwap,
// ContractMethod.megaSwap,
],
],
]);

sideToContractMethods.forEach((contractMethods, side) =>
contractMethods.forEach((contractMethod: ContractMethod) => {
describe(`${contractMethod}`, () => {
it(nativeTokenSymbol + ' -> TOKEN', async () => {
await testE2E(
tokens[nativeTokenSymbol],
tokens[tokenASymbol],
holders[nativeTokenSymbol],
side === SwapSide.SELL ? nativeTokenAmount : tokenAAmount,
side,
dexKey,
contractMethod,
network,
provider,
);
});
it('TOKEN -> ' + nativeTokenSymbol, async () => {
await testE2E(
tokens[tokenASymbol],
tokens[nativeTokenSymbol],
holders[tokenASymbol],
side === SwapSide.SELL ? tokenAAmount : nativeTokenAmount,
side,
dexKey,
contractMethod,
network,
provider,
);
});
it('TOKEN -> TOKEN', async () => {
await testE2E(
tokens[tokenASymbol],
tokens[tokenBSymbol],
holders[tokenASymbol],
side === SwapSide.SELL ? tokenAAmount : tokenBAmount,
side,
dexKey,
contractMethod,
network,
provider,
);
});
});
}),
);
});
});
92 changes: 92 additions & 0 deletions src/dex/morphex/morphex-events.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import dotenv from 'dotenv';
dotenv.config();

import { GMXEventPool } from '../gmx/pool';
import { MorphexConfig } from './config';
import { Network } from '../../constants';
import { DummyDexHelper } from '../../dex-helper/index';
import { testEventSubscriber } from '../../../tests/utils-events';
import { PoolState } from '../gmx/types';

jest.setTimeout(50 * 1000);
const dexKey = 'Morphex';
const network = Network.FANTOM;
const params = MorphexConfig[dexKey][network];

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

// timestamp can't be compared exactly as the event released
// doesn't have the timestamp. It is safe to consider the
// timestamp as the blockTime as the max deviation is bounded
// on the contract
const stateWithoutTimestamp = (state: PoolState) => ({
...state,
secondaryPrices: {
prices: state.secondaryPrices.prices,
// timestamp (this is removed)
},
});

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

describe('Morphex Event', function () {
const blockNumbers: { [eventName: string]: number[] } = {
IncreaseUsdgAmount: [
67247602, 67247565, 67247561, 67247508, 67247393, 67247305, 67247303,
67247302, 67247230, 67247220, 67247218, 67247216, 67247215, 67247145,
67247059, 67247026, 67246788, 67246731,
],
DecreaseUsdgAmount: [
67247778, 67247602, 67247565, 67247561, 67247508, 67247393, 67247305,
67247303, 67247302, 67247230, 67247220, 67247218, 67247216, 67247215,
67247145, 67247059, 67247026, 67246788,
],
Transfer: [
67087282, 67087063, 67087039, 67068002, 67052880, 67052806, 67052801,
],
PriceUpdate: [67248035, 67247977, 67247907, 67247897, 67247893],
};

describe('MorphexEventPool', function () {
Object.keys(blockNumbers).forEach((event: string) => {
blockNumbers[event].forEach((blockNumber: number) => {
it(`Should return the correct state after the ${blockNumber}:${event}`, async function () {
const dexHelper = new DummyDexHelper(network);
const logger = dexHelper.getLogger(dexKey);

const config = await GMXEventPool.getConfig(
params,
blockNumber,
dexHelper.multiContract,
);
const gmxPool = new GMXEventPool(
dexKey,
network,
dexHelper,
logger,
config,
);

await testEventSubscriber(
gmxPool,
gmxPool.addressesSubscribed,
(_blockNumber: number) => fetchPoolState(gmxPool, _blockNumber),
blockNumber,
`${dexKey}_${params.vault}`,
dexHelper.provider,
compareState,
);
});
});
});
});
});
113 changes: 113 additions & 0 deletions src/dex/morphex/morphex-integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import dotenv from 'dotenv';
dotenv.config();

import { Interface } from '@ethersproject/abi';
import { DummyDexHelper } from '../../dex-helper/index';
import { Network, SwapSide } from '../../constants';
import { Morphex } from './morphex';
import { MorphexConfig } from './config';
import {
checkPoolPrices,
checkPoolsLiquidity,
checkConstantPoolPrices,
} from '../../../tests/utils';
import { Tokens } from '../../../tests/constants-e2e';
import ReaderABI from '../../abi/gmx/reader.json';

const network = Network.FANTOM;
const TokenASymbol = 'axlUSDC';
const TokenA = Tokens[network][TokenASymbol];

const TokenBSymbol = 'WFTM';
const TokenB = Tokens[network][TokenBSymbol];

const amounts = [
0n,
1000000000n,
2000000000n,
3000000000n,
4000000000n,
5000000000n,
];

const dexKey = 'Morphex';
const params = MorphexConfig[dexKey][network];
const readerInterface = new Interface(ReaderABI);
const readerAddress = params.reader;

describe('Morphex', function () {
it('getPoolIdentifiers and getPricesVolume SELL', async function () {
const dexHelper = new DummyDexHelper(network);
const blocknumber = await dexHelper.web3Provider.eth.getBlockNumber();
const gmx = new Morphex(network, dexKey, dexHelper);

await gmx.initializePricing(blocknumber);

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

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

const poolPrices = await gmx.getPricesVolume(
TokenA,
TokenB,
amounts,
SwapSide.SELL,
blocknumber,
pools,
);
console.log(`${TokenASymbol} <> ${TokenBSymbol} Pool Prices: `, poolPrices);

expect(poolPrices).not.toBeNull();
if (gmx.hasConstantPriceLargeAmounts) {
checkConstantPoolPrices(poolPrices!, amounts, dexKey);
} else {
checkPoolPrices(poolPrices!, amounts, SwapSide.SELL, dexKey);
}

// Do on chain pricing based on reader to compare
const readerCallData = amounts.map(a => ({
target: readerAddress,
callData: readerInterface.encodeFunctionData('getAmountOut', [
params.vault,
TokenA.address,
TokenB.address,
a.toString(),
]),
}));

const readerResult = (
await dexHelper.multiContract.methods
.aggregate(readerCallData)
.call({}, blocknumber)
).returnData;
const expectedPrices = readerResult.map((p: any) =>
BigInt(
readerInterface.decodeFunctionResult('getAmountOut', p)[0].toString(),
),
);

expect(poolPrices![0].prices).toEqual(expectedPrices);
});

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

await gmx.updatePoolState();
const poolLiquidity = await gmx.getTopPoolsForToken(TokenA.address, 10);
console.log(
`${TokenASymbol} Top Pools:`,
JSON.stringify(poolLiquidity, null, 2),
);

if (!gmx.hasConstantPriceLargeAmounts) {
checkPoolsLiquidity(poolLiquidity, TokenA.address, dexKey);
}
});
});
22 changes: 22 additions & 0 deletions src/dex/morphex/morphex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Network } from '../../constants';
import { IDexHelper } from '../../dex-helper';
import { DexParams } from '../gmx/types';
import { Adapters, MorphexConfig } from './config';
import { GMX } from '../gmx/gmx';
import { getDexKeysWithNetwork } from '../../utils';

export class Morphex extends GMX {
public static dexKeysWithNetwork: { key: string; networks: Network[] }[] =
getDexKeysWithNetwork(MorphexConfig);

constructor(
protected network: Network,
dexKey: string,
protected dexHelper: IDexHelper,
protected adapters = Adapters[network],
protected params: DexParams = MorphexConfig[dexKey][network],
) {
super(network, dexKey, dexHelper, adapters, params);
this.logger = dexHelper.getLogger(dexKey);
}
}
Loading

0 comments on commit 85b2567

Please sign in to comment.