Skip to content

Commit

Permalink
Merge pull request #47 from terminal-fi/tests
Browse files Browse the repository at this point in the history
[core] add a way to test all pairs
  • Loading branch information
zviadm authored Jul 15, 2023
2 parents 3f57c67 + be4e4de commit 63bfc2a
Show file tree
Hide file tree
Showing 18 changed files with 198 additions and 98 deletions.
66 changes: 7 additions & 59 deletions src/cli/cli.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
#!/usr/bin/env node
import commander from 'commander';
import axios from 'axios';
import { ContractKit, newKit } from '@celo/contractkit';
import { newKit } from '@celo/contractkit';
import BigNumber from 'bignumber.js';
import { toTransactionObject } from '@celo/connect';

import * as ubeswapTokens from '@ubeswap/default-token-list/ubeswap-experimental.token-list.json'
import { Ierc20, ABI as Ierc20ABI } from '../../types/web3-v1-contracts/IERC20';
import { address as swappaRouterV1Address} from '../../tools/deployed/mainnet.SwappaRouterV1.addr.json';

import { SwappaManager } from '../swappa-manager';
import {
mainnetRegistryMobius, mainnetRegistryMoola, mainnetRegistryMoolaV2,
mainnetRegistrySavingsCELO, mainnetRegistrySushiswap, mainnetRegistryUbeswap,
mainnetRegistryCeloDex, mainnetRegistrySymmetric, mainnetRegistryMisc, mainnetRegistryCurve, mainnetRegistryUniswapV3, mainnetRegistriesWhitelist, mainnetRegistryStCelo,
} from '../registry-cfg';
import { RegistryMento } from '../registries/mento';
import { Registry } from '../registry';
import { mainnetRegistriesWhitelist } from '../registry-cfg';
import { initAllTokens, tokenByAddrOrSymbol } from './tokens';
import { registriesByName } from './registries';

const program = commander.program
.option("--network <network>", "Celo client URL to connect to.", "http://localhost:8545")
Expand All @@ -37,61 +31,15 @@ process.on('unhandledRejection', (reason: any, _promise: any) => {
process.exit(1)
})

const registriesByName: {[name: string]: (kit: ContractKit) => Registry} = {
"mento": (kit: ContractKit) => new RegistryMento(kit),
"ubeswap": mainnetRegistryUbeswap,
"sushiswap": mainnetRegistrySushiswap,
"mobius": mainnetRegistryMobius,
"moola": mainnetRegistryMoola,
"moola-v2": mainnetRegistryMoolaV2,
"savingscelo": mainnetRegistrySavingsCELO,
"celodex": mainnetRegistryCeloDex,
"symmetric": mainnetRegistrySymmetric,
"misc": mainnetRegistryMisc,
"curve": mainnetRegistryCurve,
"uniswap-v3": mainnetRegistryUniswapV3,
"stcelo": mainnetRegistryStCelo,
}

interface Token {
chainId: number,
address: string,
symbol: string,
decimals: number,
}
let chainId: number
let ALL_TOKENS: Token[]

function tokenByAddrOrSymbol(addressOrSymbol: string) {
const t = ALL_TOKENS.find((t) => t.chainId === chainId && (t.address === addressOrSymbol || t.symbol === addressOrSymbol))
if (!t) {
throw new Error(`Unrecognized token: ${addressOrSymbol}!`)
}
return t
}

async function main() {
const opts = program.opts()
const kit = await newKit(opts.network)
chainId = await kit.web3.eth.getChainId()

const celoTokenListURI = "https://celo-org.github.io/celo-token-list/celo.tokenlist.json"
const celoTokenList = (await axios.get<{tokens: Token[]}>(celoTokenListURI)).data
ALL_TOKENS = [
...ubeswapTokens.tokens,
...celoTokenList.tokens,
{
chainId: 42220,
address: "0x617f3112bf5397D0467D315cC709EF968D9ba546",
symbol: "USDTxWormhole",
decimals: 6,
}
]

const chainId = await kit.web3.eth.getChainId()
const allTokens = await initAllTokens(chainId)
const inputToken = tokenByAddrOrSymbol(opts.input)
const outputToken = tokenByAddrOrSymbol(opts.output)
const inputAmount = new BigNumber(opts.amount).shiftedBy(inputToken.decimals)
const tokenWhitelist = ALL_TOKENS.filter((v) => v.chainId === chainId).map((v) => v.address)
const tokenWhitelist = allTokens.filter((v) => v.chainId === chainId).map((v) => v.address)

let registries
if (opts.registries === "default"){
Expand Down
36 changes: 36 additions & 0 deletions src/cli/registries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { ContractKit } from '@celo/contractkit';
import {
mainnetRegistryCeloDex,
mainnetRegistryCurve,
mainnetRegistryMisc,
mainnetRegistryMobius,
mainnetRegistryMoola,
mainnetRegistryMoolaV2,
mainnetRegistrySavingsCELO,
mainnetRegistryStCelo,
mainnetRegistrySushiswap,
mainnetRegistrySymmetric,
mainnetRegistryUbeswap,
mainnetRegistryUniswapV3,
} from '../registry-cfg';
import { RegistryMento } from '../registries/mento';
import { Registry } from '../registry';

export const registriesByName: {[name: string]: (kit: ContractKit) => Registry} = {
// Sorted by importance based on TVL.
"mento": (kit: ContractKit) => new RegistryMento(kit),
"curve": mainnetRegistryCurve,
"uniswap-v3": mainnetRegistryUniswapV3,
"moola-v2": mainnetRegistryMoolaV2,
"stcelo": mainnetRegistryStCelo,
"ubeswap": mainnetRegistryUbeswap,
"sushiswap": mainnetRegistrySushiswap,
"mobius": mainnetRegistryMobius,
"misc": mainnetRegistryMisc,

// DEPRECATED stuff:
"savingscelo": mainnetRegistrySavingsCELO,
"moola": mainnetRegistryMoola,
"celodex": mainnetRegistryCeloDex,
"symmetric": mainnetRegistrySymmetric,
}
87 changes: 87 additions & 0 deletions src/cli/test-pairs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/usr/bin/env node
import commander from 'commander';
import { ContractKit, newKit, setImplementationOnProxy } from '@celo/contractkit';
import BigNumber from 'bignumber.js';

import { address as swappaRouterV1Address} from '../../tools/deployed/mainnet.SwappaRouterV1.addr.json';

import { SwappaManager } from '../swappa-manager';
import { initAllTokens, tokenByAddrOrSymbol } from './tokens';
import { registriesByName } from './registries';

const program = commander.program
.option("--network <network>", "Celo client URL to connect to.", "http://localhost:8545")
.option("--registry <registry>", "Registry to use for testing.", "")
.option("--amount <amount>", "Input amount.", "0.001")
.parse(process.argv)

process.on('unhandledRejection', (reason: any, _promise: any) => {
// @ts-ignore
console.error('Unhandled Rejection for promise:', _promise, 'at:', reason.stack || reason)
process.exit(1)
})

async function main() {
const opts = program.opts()
const kit = await newKit(opts.network)
const chainId = await kit.web3.eth.getChainId()
const allTokens = await initAllTokens(chainId)

const tokenWhitelist = allTokens.filter((v) => v.chainId === chainId).map((v) => v.address)

const registries = [registriesByName[opts.registry](kit)]
const manager = new SwappaManager(kit, swappaRouterV1Address, registries)
console.info(`Finding & initializing pairs...`)
const pairs = await manager.reinitializePairs(tokenWhitelist)
console.info(`Pairs (${pairs.length}):`)

const initBlockN = await kit.web3.eth.getBlockNumber()
console.info("Waiting for new block before running tests...")
while (true) {
const blockN = await kit.web3.eth.getBlockNumber()
if (blockN > initBlockN) {
break
}
await new Promise(resolve => setTimeout(resolve, 100))
}

console.info("Running tests...")
let passedN = 0
let failedN = 0
let highN = 0
for (const pair of pairs) {
const inputAmountA = new BigNumber(opts.amount).shiftedBy(tokenByAddrOrSymbol(pair.tokenA).decimals)
const inputAmountB = new BigNumber(opts.amount).shiftedBy(tokenByAddrOrSymbol(pair.tokenB).decimals)
const [
expectedOutputB,
expectedOutputA,
_
] = await Promise.all([
pair.outputAmountAsync(pair.tokenA, inputAmountA).catch(() => { return 0 }),
pair.outputAmountAsync(pair.tokenB, inputAmountB).catch(() => { return 0 }),
pair.refresh()
])
const outputB = pair.outputAmount(pair.tokenA, inputAmountA)
const outputA = pair.outputAmount(pair.tokenB, inputAmountB)
const passed = outputB.eq(expectedOutputB) && outputA.eq(expectedOutputA)
const highOutput = outputB.gt(expectedOutputB) || outputA.gt(expectedOutputA)
if (!passed) {
console.warn(
`Mismatch (HIGH?: ${highOutput}): ${tokenByAddrOrSymbol(pair.tokenA).symbol}/${tokenByAddrOrSymbol(pair.tokenB).symbol}: ` +
`${outputB.toFixed(0)} vs ${expectedOutputB} (${outputB.eq(expectedOutputB)}), ` +
`${outputA.toFixed(0)} vs ${expectedOutputA} (${outputA.eq(expectedOutputA)})`)
failedN += 1
if (highOutput) {
highN += 1
}
} else {
passedN += 1
}
}

console.info(`--------------------------------------------------------------------------------`)
console.info(`PASSED: ${passedN}, FAILED: ${failedN}, HIGH?: ${highN}`)
kit.stop()
}

main()
36 changes: 36 additions & 0 deletions src/cli/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import axios from 'axios';
import * as ubeswapTokens from '@ubeswap/default-token-list/ubeswap-experimental.token-list.json'

export interface Token {
chainId: number,
address: string,
symbol: string,
decimals: number,
}
let _CHAIN_ID: number
let _ALL_TOKENS: Token[]

export async function initAllTokens(chainId: number) {
_CHAIN_ID = chainId
const celoTokenListURI = "https://celo-org.github.io/celo-token-list/celo.tokenlist.json"
const celoTokenList = (await axios.get<{tokens: Token[]}>(celoTokenListURI)).data
_ALL_TOKENS = [
...ubeswapTokens.tokens,
...celoTokenList.tokens,
{
chainId: 42220,
address: "0x617f3112bf5397D0467D315cC709EF968D9ba546",
symbol: "USDTxWormhole",
decimals: 6,
}
]
return _ALL_TOKENS
}

export function tokenByAddrOrSymbol(addressOrSymbol: string) {
const t = _ALL_TOKENS.find((t) => t.chainId === _CHAIN_ID && (t.address === addressOrSymbol || t.symbol === addressOrSymbol))
if (!t) {
throw new Error(`Unrecognized token: ${addressOrSymbol}!`)
}
return t
}
25 changes: 20 additions & 5 deletions src/pair.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import BigNumber from "bignumber.js"
import Web3 from "web3"
import { ISwappaPairV1, ABI as ISwappaPairV1ABI } from "../types/web3-v1-contracts/ISwappaPairV1"

export type Address = string

Expand All @@ -14,13 +16,15 @@ export abstract class Pair {
// pairKey is used to identify conflicting pairs. In a single route, every non-null pairKey must
// be unique. On the otherhand, Pair-s with null pairKey can be used unlimited amount of times in
// a single route.
public pairKey: string | null = null;
public tokenA: Address = "";
public tokenB: Address = "";
private swappaPairAddress: Address = "";
public pairKey: string | null = null
public tokenA: Address = ""
public tokenB: Address = ""
private swappaPairAddress: Address
private swappaPair: ISwappaPairV1

constructor(swappaPairAddress: Address) {
constructor(web3: Web3, swappaPairAddress: Address) {
this.swappaPairAddress = swappaPairAddress
this.swappaPair = new web3.eth.Contract(ISwappaPairV1ABI, this.swappaPairAddress) as unknown as ISwappaPairV1
}

public async init(): Promise<void> {
Expand All @@ -45,6 +49,17 @@ export abstract class Pair {
protected abstract swapExtraData(inputToken: Address): string;
public abstract outputAmount(inputToken: Address, inputAmount: BigNumber): BigNumber;

public outputAmountAsync = async (inputToken: Address, inputAmount: BigNumber): Promise<BigNumber> => {
const outputToken = inputToken === this.tokenA ? this.tokenB : this.tokenA
const out = await this.swappaPair.methods.getOutputAmount(
inputToken,
outputToken,
inputAmount.toFixed(0),
this.swapExtraData(inputToken),
).call()
return new BigNumber(out)
}

public abstract snapshot(): Snapshot;
public abstract restore(snapshot: Snapshot): void;
}
Expand Down
2 changes: 1 addition & 1 deletion src/pairs/atoken-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class PairATokenV2 extends Pair {
private poolAddr: Address,
private reserve: Address,
) {
super(selectAddress(chainId, {mainnet: pairATokenV2Address}))
super(web3, selectAddress(chainId, {mainnet: pairATokenV2Address}))
this.pool = new this.web3.eth.Contract(ILendingPoolV2ABI, this.poolAddr) as unknown as ILendingPoolV2
}

Expand Down
2 changes: 1 addition & 1 deletion src/pairs/atoken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class PairAToken extends Pair {
private providerAddr: Address,
private reserve: Address,
) {
super(selectAddress(chainId, {mainnet: pairATokenAddress}))
super(kit.web3 as unknown as Web3, selectAddress(chainId, {mainnet: pairATokenAddress}))
this.provider = new kit.web3.eth.Contract(
LendingPoolAddressProviderABI, providerAddr) as unknown as ILendingPoolAddressesProvider
}
Expand Down
2 changes: 1 addition & 1 deletion src/pairs/bpool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export class PairBPool extends Pair {
public tokenA: Address,
public tokenB: Address,
) {
super(selectAddress(chainId, {mainnet: pairBPoolAddress}))
super(web3, selectAddress(chainId, {mainnet: pairBPoolAddress}))
this.bPool = new web3.eth.Contract(BPoolABI, poolAddr) as unknown as IbPool
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,12 +85,6 @@ export abstract class PairContentratedLiquidity extends Pair {
// Tick index is calculated based on the positon of the current tick within the tick's wordPosition
protected tickIndex?: number;

public abstract outputAmountAsync(
inputToken: Address,
inputAmount: bigint,
outputToken: string
): Promise<bigint>;

public static transformGetSpotTicksPayload({
sqrtPriceX96,
tick,
Expand Down
2 changes: 1 addition & 1 deletion src/pairs/curve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class PairCurve extends Pair {
private poolAddr: Address,
opts?: {nCoins: number, token0Idx: number, token1Idx: number},
) {
super(selectAddress(chainId, {mainnet: pairCurveAddress}))
super(web3, selectAddress(chainId, {mainnet: pairCurveAddress}))
this.curvePool = new web3.eth.Contract(CurveABI, poolAddr) as unknown as ICurve
this.nCoins = opts ? opts.nCoins : 2
this.token0Idx = opts ? opts.token0Idx : 0
Expand Down
2 changes: 1 addition & 1 deletion src/pairs/mento.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class PairMento extends PairXYeqK {
private kit: ContractKit,
private stableToken: StableToken,
) {
super(selectAddress(chainId, {mainnet: pairMentoAddress}))
super(kit.web3 as unknown as Web3, selectAddress(chainId, {mainnet: pairMentoAddress}))
}

protected async _init() {
Expand Down
2 changes: 1 addition & 1 deletion src/pairs/opensumswap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export class PairOpenSumSwap extends Pair {
web3: Web3,
private swapPoolAddr: Address,
) {
super(selectAddress(chainId, {mainnet: pairOpenSumSwapAddress}))
super(web3, selectAddress(chainId, {mainnet: pairOpenSumSwapAddress}))
this.swapPool = new web3.eth.Contract(SwapABI, swapPoolAddr) as unknown as IOpenSumSwap
}

Expand Down
2 changes: 1 addition & 1 deletion src/pairs/savingscelo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class PairSavingsCELO extends Pair {
private kit: ContractKit,
savingsCELOAddr: Address,
) {
super(selectAddress(chainId, {mainnet: pairSavingsCELOAddress}))
super(kit.web3 as unknown as Web3, selectAddress(chainId, {mainnet: pairSavingsCELOAddress}))
this.savingsKit = new SavingsKit(kit, savingsCELOAddr)
}

Expand Down
2 changes: 1 addition & 1 deletion src/pairs/stCelo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ abstract class PairStakedCelo extends Pair {
private stakedCeloAddress: Address,
private accountAddress: Address
) {
super(swappaPairAddress);
super(web3, swappaPairAddress);
this.stCeloContract = new this.web3.eth.Contract(
StakedCeloABI,
this.stakedCeloAddress
Expand Down
2 changes: 1 addition & 1 deletion src/pairs/stableswap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class PairStableSwap extends Pair {
private web3: Web3,
private swapPoolAddr: Address,
) {
super(selectAddress(chainId, {mainnet: pairStableSwapAddress}))
super(web3, selectAddress(chainId, {mainnet: pairStableSwapAddress}))
this.swapPool = new web3.eth.Contract(SwapABI, swapPoolAddr) as unknown as ISwap
}

Expand Down
Loading

0 comments on commit 63bfc2a

Please sign in to comment.