From c35ba08072fb69a38d02ddbaa1b4f508ee2d4284 Mon Sep 17 00:00:00 2001 From: Maxence Raballand Date: Mon, 25 Nov 2024 17:31:51 +0100 Subject: [PATCH] feat: add getTokens and getMarkets (#146) * feat: add getTokens and getMarkets * chore: update tests from biem anvil to prool * chore: format --------- Co-authored-by: maxencerb --- .changeset/spotty-islands-admire.md | 5 ++ bun.lockb | Bin 116720 -> 123968 bytes package.json | 5 +- src/actions/index.ts | 16 ++++ src/actions/reader.test.ts | 66 +++++++++++++++ src/actions/reader.ts | 80 +++++++++++++++++++ src/actions/tokens.test.ts | 86 ++++++++++++++++++++ src/actions/tokens.ts | 111 ++++++++++++++++++++++++++ src/builder/index.ts | 4 + src/builder/reader.ts | 16 ++++ src/bundle/public/general-actions.ts | 34 +++++++- src/bundle/public/mangrove-actions.ts | 47 ++++++++++- src/package.json | 2 +- test/globalSetup.ts | 23 +++--- 14 files changed, 478 insertions(+), 17 deletions(-) create mode 100644 .changeset/spotty-islands-admire.md create mode 100644 src/actions/reader.test.ts create mode 100644 src/actions/reader.ts create mode 100644 src/actions/tokens.test.ts create mode 100644 src/actions/tokens.ts create mode 100644 src/builder/reader.ts diff --git a/.changeset/spotty-islands-admire.md b/.changeset/spotty-islands-admire.md new file mode 100644 index 0000000..81e8cd9 --- /dev/null +++ b/.changeset/spotty-islands-admire.md @@ -0,0 +1,5 @@ +--- +"@mangrovedao/mgv": patch +--- + +Added getTokens and getOpenMarkets diff --git a/bun.lockb b/bun.lockb index 37431af1aa1b16d5dcf89a69c672367346ee028a..d8c5c63e133b35e5544ef82bf0d4821ec14d1c46 100755 GIT binary patch delta 22669 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 delta 18775 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 x7sM>l{vVQ!9Vh?* diff --git a/package.json b/package.json index d05bf7d..61e00bf 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,12 @@ "devDependencies": { "@biomejs/biome": "1.8.3", "@types/bun": "^1.1.8", - "@viem/anvil": "^0.0.10", "@vitest/coverage-v8": "^2.0.5", "globby": "^14.0.2", "rimraf": "^6.0.1", - "simple-git-hooks": "^2.11.1", "viem": "^2.21.2", - "vitest": "^2.0.5" + "vitest": "^2.0.5", + "prool": "^0.0.16" }, "peerDependencies": { "typescript": "^5.5.4" diff --git a/src/actions/index.ts b/src/actions/index.ts index 0f23c7d..5ec73c4 100644 --- a/src/actions/index.ts +++ b/src/actions/index.ts @@ -119,3 +119,19 @@ export type { GetKandelStateResult, KandelStatus, } from './kandel/view.js' + +export { getTokens, GetTokenInfoError } from './tokens.js' + +export type { + GetTokensParams, + GetTokensResult, +} from './tokens.js' + +export { getRawOpenMarkets, getOpenMarkets } from './reader.js' + +export type { + GetOpenMarketArgs, + GetOpenMarketRawArgs, + GetOpenMarketRawResult, + GetOpenMarketResult, +} from './reader.js' diff --git a/src/actions/reader.test.ts b/src/actions/reader.test.ts new file mode 100644 index 0000000..f89c53b --- /dev/null +++ b/src/actions/reader.test.ts @@ -0,0 +1,66 @@ +import { describe, expect, inject, it } from 'vitest' +import { getClient } from '~test/src/client.js' +import { getOpenMarkets } from './reader.js' + +const client = getClient() +const params = inject('mangrove') +const { WETH, USDC, DAI } = inject('tokens') +const { wethDAI, wethUSDC } = inject('markets') + +describe('getOpenMarkets', () => { + it('should return the open markets', async () => { + const markets = await getOpenMarkets(client, params, { + cashnesses: { + WETH: 1, + USDC: 1000, + DAI: 1000, + }, + }) + + expect(markets[0]?.base.address.toLowerCase()).toEqual( + wethUSDC.base.address.toLowerCase(), + ) + expect(markets[0]?.quote.address.toLowerCase()).toEqual( + wethUSDC.quote.address.toLowerCase(), + ) + expect(markets[1]?.base.address.toLowerCase()).toEqual( + wethDAI.base.address.toLowerCase(), + ) + expect(markets[1]?.quote.address.toLowerCase()).toEqual( + wethDAI.quote.address.toLowerCase(), + ) + + expect(markets[0]?.base.symbol).toEqual(WETH.symbol) + expect(markets[0]?.quote.symbol).toEqual(USDC.symbol) + expect(markets[1]?.base.symbol).toEqual(WETH.symbol) + expect(markets[1]?.quote.symbol).toEqual(DAI.symbol) + + expect(markets[0]?.base.decimals).toEqual(WETH.decimals) + expect(markets[0]?.quote.decimals).toEqual(USDC.decimals) + expect(markets[1]?.base.decimals).toEqual(WETH.decimals) + expect(markets[1]?.quote.decimals).toEqual(DAI.decimals) + }) + + it('should return the open markets with inverted cashnesses', async () => { + const markets = await getOpenMarkets(client, params, { + cashnesses: { + WETH: 100000, + USDC: 1000, + DAI: 1000, + }, + }) + + expect(markets[0]?.base.address.toLowerCase()).toEqual( + wethUSDC.quote.address.toLowerCase(), + ) + expect(markets[0]?.quote.address.toLowerCase()).toEqual( + wethUSDC.base.address.toLowerCase(), + ) + expect(markets[1]?.base.address.toLowerCase()).toEqual( + wethDAI.quote.address.toLowerCase(), + ) + expect(markets[1]?.quote.address.toLowerCase()).toEqual( + wethDAI.base.address.toLowerCase(), + ) + }) +}) diff --git a/src/actions/reader.ts b/src/actions/reader.ts new file mode 100644 index 0000000..4461801 --- /dev/null +++ b/src/actions/reader.ts @@ -0,0 +1,80 @@ +import type { Address, Client, ContractFunctionParameters } from 'viem' +import { readContract } from 'viem/actions' +import { getOpenMarketsParams, type mgvReaderABI } from '../builder/reader.js' +import type { + BuiltArgs, + MangroveActionsDefaultParams, + MarketParams, +} from '../types/index.js' +import { getAction } from '../utils/getAction.js' +import { type GetTokensParams, getTokens } from './tokens.js' + +export type GetOpenMarketRawArgs = Omit< + ContractFunctionParameters, + BuiltArgs +> + +export type GetOpenMarketRawResult = { + tkn0: Address + tkn1: Address + tickSpacing: bigint +}[] + +export async function getRawOpenMarkets( + client: Client, + params: MangroveActionsDefaultParams, + args?: GetOpenMarketRawArgs, +): Promise { + const result = await getAction( + client, + readContract, + 'readContract', + )({ + ...args, + address: params.mgvReader, + ...getOpenMarketsParams, + }) + + return result[0] as GetOpenMarketRawResult +} + +export type GetOpenMarketArgs = Omit & + GetOpenMarketRawArgs & { + // symbol -> cashness + cashnesses: Record + } + +export type GetOpenMarketResult = MarketParams[] + +export async function getOpenMarkets( + client: Client, + params: MangroveActionsDefaultParams, + args: GetOpenMarketArgs, +): Promise { + const raw = await getRawOpenMarkets(client, params, args) + const tokens = await getTokens(client, { + ...args, + tokens: raw.flatMap((market) => [market.tkn0, market.tkn1]), + }) + + return raw.map((market): MarketParams => { + // we don't use isAddressEqual because both are supposedly checksummed from viem + const tkn0 = tokens.find((token) => token.address === market.tkn0) + const tkn1 = tokens.find((token) => token.address === market.tkn1) + + if (!tkn0 || !tkn1) { + throw new Error( + 'Token not found, this is a bug, please report at https://github.com/mangrovedao/mgv/issues', + ) + } + + const tkn0Cashness = args.cashnesses[tkn0.symbol] ?? 0 + const tkn1Cashness = args.cashnesses[tkn1.symbol] ?? 0 + + return { + base: tkn0Cashness > tkn1Cashness ? tkn1 : tkn0, + quote: tkn0Cashness > tkn1Cashness ? tkn0 : tkn1, + tickSpacing: market.tickSpacing, + } + }) +} diff --git a/src/actions/tokens.test.ts b/src/actions/tokens.test.ts new file mode 100644 index 0000000..e3bdf29 --- /dev/null +++ b/src/actions/tokens.test.ts @@ -0,0 +1,86 @@ +import { ContractFunctionExecutionError, zeroAddress } from 'viem' +import { describe, expect, inject, it } from 'vitest' +import { getClient } from '~test/src/client.js' +import { GetTokenInfoError, getTokens } from './tokens.js' + +const { WETH, USDC, DAI } = inject('tokens') +const client = getClient() + +describe('tokens', () => { + it('should get tokens', async () => { + const tokens = await getTokens(client, { tokens: [WETH.address] as const }) + expect(tokens).toEqual([WETH]) + + const foundWETH = tokens[0] + + expect(foundWETH.mgvTestToken).toBe(false) + expect(foundWETH.address).toBe(WETH.address) + expect(foundWETH.symbol).toBe(WETH.symbol) + expect(foundWETH.decimals).toBe(WETH.decimals) + }) + + it('should get multiple tokens', async () => { + const tokens = await getTokens(client, { + tokens: [WETH.address, USDC.address, DAI.address] as const, + }) + expect(tokens).toEqual([WETH, USDC, DAI]) + + const foundWETH = tokens[0] + expect(foundWETH.mgvTestToken).toBe(false) + expect(foundWETH.address).toBe(WETH.address) + expect(foundWETH.symbol).toBe(WETH.symbol) + expect(foundWETH.decimals).toBe(WETH.decimals) + + const foundUSDC = tokens[1] + expect(foundUSDC.mgvTestToken).toBe(false) + expect(foundUSDC.address).toBe(USDC.address) + expect(foundUSDC.symbol).toBe(USDC.symbol) + expect(foundUSDC.decimals).toBe(USDC.decimals) + + const foundDAI = tokens[2] + expect(foundDAI.mgvTestToken).toBe(false) + expect(foundDAI.address).toBe(DAI.address) + expect(foundDAI.symbol).toBe(DAI.symbol) + expect(foundDAI.decimals).toBe(DAI.decimals) + }) + + it('should get test tokens', async () => { + const tokens = await getTokens(client, { + tokens: [WETH.address] as const, + testTokens: [WETH.address], + }) + + const foundWETH = tokens[0] + expect(foundWETH.mgvTestToken).toBe(true) + expect(foundWETH.address).toBe(WETH.address) + expect(foundWETH.symbol).toBe(WETH.symbol) + expect(foundWETH.decimals).toBe(WETH.decimals) + }) + + it('should have display decimals', async () => { + const tokens = await getTokens(client, { + tokens: [WETH.address, USDC.address, DAI.address] as const, + displayDecimals: { [WETH.symbol]: 1000 }, + priceDisplayDecimals: { [WETH.symbol]: 1000 }, + }) + + const foundWETH = tokens[0] + expect(foundWETH.displayDecimals).toBe(1000) + expect(foundWETH.priceDisplayDecimals).toBe(1000) + }) + + it('should fail on unknown token', async () => { + try { + await getTokens(client, { tokens: [zeroAddress] as const }) + } catch (error) { + expect(error).toBeInstanceOf(GetTokenInfoError) + const typedError = error as GetTokenInfoError + expect(typedError.shortMessage).toBe( + `No decimals found for token ${zeroAddress}`, + ) + expect(typedError.cause).toBeInstanceOf(ContractFunctionExecutionError) + const typedCause = typedError.cause as ContractFunctionExecutionError + expect(typedCause.contractAddress).toBe(zeroAddress) + } + }) +}) diff --git a/src/actions/tokens.ts b/src/actions/tokens.ts new file mode 100644 index 0000000..3ed2b3b --- /dev/null +++ b/src/actions/tokens.ts @@ -0,0 +1,111 @@ +import { + type Address, + BaseError, + type Client, + type MulticallParameters, + parseAbi, +} from 'viem' +import { multicall } from 'viem/actions' +import { type Token, buildToken } from '../addresses/index.js' +import { getAction } from '../utils/getAction.js' + +export type GetTokensParams = + { + tokens: T + displayDecimals?: Record + priceDisplayDecimals?: Record + testTokens?: T[number][] + } & Omit + +export type GetTokensResult = + { + [K in keyof T]: Token + } + +const tokenABI = parseAbi([ + 'function decimals() external view returns (uint8)', + 'function symbol() external view returns (string)', +]) + +export class GetTokenInfoError extends BaseError { + constructor( + tokenAddress: Address, + param: 'decimals' | 'symbol', + cause: Error, + ) { + super(`No ${param} found for token ${tokenAddress}`, { cause }) + } +} + +export async function getTokens< + T extends readonly Address[] = readonly Address[], +>( + client: Client, + { + tokens, + displayDecimals = {}, + priceDisplayDecimals = {}, + testTokens = [], + }: GetTokensParams, +): Promise> { + const tokenInfos = await getAction( + client, + multicall, + 'multicall', + )({ + contracts: tokens.flatMap( + (token) => + [ + { + address: token, + abi: tokenABI, + functionName: 'decimals', + }, + { + address: token, + abi: tokenABI, + functionName: 'symbol', + }, + ] as const, + ), + }) + + return tokens.map((token: T[number], i) => { + const decimalsResult = tokenInfos[i * 2] + const symbolResult = tokenInfos[i * 2 + 1] + + if (!decimalsResult || !symbolResult) + throw new Error( + 'Error while getting token infos, This is a bug, please report at https://github.com/mangrovedao/mgv/issues', + ) + + if (decimalsResult.status === 'failure') + throw new GetTokenInfoError(token, 'decimals', decimalsResult.error) + if (symbolResult.status === 'failure') + throw new GetTokenInfoError(token, 'symbol', symbolResult.error) + + const decimals = decimalsResult.result + const symbol = symbolResult.result + + if (typeof symbol !== 'string') + throw new Error( + 'Error while getting token infos, This is a bug, please report at https://github.com/mangrovedao/mgv/issues', + ) + if (typeof decimals !== 'number') + throw new Error( + 'Error while getting token infos, This is a bug, please report at https://github.com/mangrovedao/mgv/issues', + ) + + const display = displayDecimals[symbol] + const priceDisplay = priceDisplayDecimals[symbol] + + return buildToken({ + address: token, + symbol, + decimals, + displayDecimals: display, + priceDisplayDecimals: priceDisplay, + mgvTestToken: testTokens.includes(token), + }) + }) as GetTokensResult +} diff --git a/src/builder/index.ts b/src/builder/index.ts index 0186b31..13345cf 100644 --- a/src/builder/index.ts +++ b/src/builder/index.ts @@ -112,3 +112,7 @@ export { deployRouterParams, bindParams, } from './smart-router.js' + +// reader + +export { getOpenMarketsParams, mgvReaderABI } from './reader.js' diff --git a/src/builder/reader.ts b/src/builder/reader.ts new file mode 100644 index 0000000..6793069 --- /dev/null +++ b/src/builder/reader.ts @@ -0,0 +1,16 @@ +import { type ContractFunctionParameters, parseAbi } from 'viem' + +export const mgvReaderABI = parseAbi([ + 'struct Market { address tkn0; address tkn1; uint tickSpacing;}', + 'struct MarketConfig {LocalUnpacked config01;LocalUnpacked config10;}', + 'struct LocalUnpacked { bool active; uint fee; uint density; uint binPosInLeaf; uint level3; uint level2; uint level1; uint root; uint kilo_offer_gasbase; bool lock; uint last;}', + 'function openMarkets() external view returns (Market[] memory, MarketConfig[] memory)', +]) + +export const getOpenMarketsParams = { + abi: mgvReaderABI, + functionName: 'openMarkets', +} satisfies Omit< + ContractFunctionParameters, + 'address' +> diff --git a/src/bundle/public/general-actions.ts b/src/bundle/public/general-actions.ts index 418acf7..b3d1f23 100644 --- a/src/bundle/public/general-actions.ts +++ b/src/bundle/public/general-actions.ts @@ -1,9 +1,14 @@ -import type { Client } from 'viem' +import type { Address, Client } from 'viem' import { type GetBalanceResult, type GetBalancesArgs, getBalances, } from '../../actions/balances.js' +import { + type GetTokensParams, + type GetTokensResult, + getTokens, +} from '../../actions/tokens.js' import type { Logic } from '../../addresses/logics/utils.js' export type GeneralActions = { @@ -17,8 +22,35 @@ export type GeneralActions = { getBalances: ( args: GetBalancesArgs, ) => Promise> + + /** + * + * @param args.tokens the tokens to get info for + * @param args.displayDecimals the decimals to display for each token + * @param args.priceDisplayDecimals the decimals to display for each token's price + * @param args.testTokens the tokens that are mangrove test tokens + * @returns all tokens and their info + * @example + * ```ts + * const tokens = await client.getTokens({ + * tokens: [WETHaddress, USDCaddress], + * displayDecimals: { WETH: 4, USDC: 2 }, // optional + * priceDisplayDecimals: { WETH: 2, USDC: 4 }, // optional + * testTokens: [WETH.address] // optional + * }) + * // Returns: + * // { + * // WETH: { address: "0x...", symbol: "WETH", decimals: 18, displayDecimals: 4, priceDisplayDecimals: 2, isTestToken: true }, + * // USDC: { address: "0x...", symbol: "USDC", decimals: 6, displayDecimals: 2, priceDisplayDecimals: 4, isTestToken: false } + * // } + * ``` + */ + getTokens: ( + args: GetTokensParams, + ) => Promise> } export const generalActions = (client: Client): GeneralActions => ({ getBalances: (args) => getBalances(client, args), + getTokens: (args) => getTokens(client, args), }) diff --git a/src/bundle/public/mangrove-actions.ts b/src/bundle/public/mangrove-actions.ts index d6bb63a..b3b44c2 100644 --- a/src/bundle/public/mangrove-actions.ts +++ b/src/bundle/public/mangrove-actions.ts @@ -1,6 +1,10 @@ import type { Address, Client } from 'viem' -import type { GetUserRouterArgs } from '../../actions/index.js' -import { getUserRouter } from '../../actions/index.js' +import type { + GetOpenMarketArgs, + GetOpenMarketResult, + GetUserRouterArgs, +} from '../../actions/index.js' +import { getOpenMarkets, getUserRouter } from '../../actions/index.js' import { type GetOrdersArgs, type GetSingleOrderArgs, @@ -34,6 +38,44 @@ export type MangroveActions = { /** Gets multiple orders details given their markets, sides, and ids */ getOrders: (args: GetOrdersArgs) => Promise + + /** + * Gets all open markets on Mangrove + * @param args.cashnesses The cashness values for each token symbol (e.g. { "WETH": 10, "USDC": 100 }). + * Tokens with higher cashness will be quote tokens, lower cashness will be base tokens. + * For example, in the WETH/USDC market, WETH has higher cashness so it's the quote token. + * @param args.displayDecimals The number of decimals to display for each token symbol + * @param args.priceDisplayDecimals The number of decimals to display for prices in each token symbol + * @param args.testTokens Array of token addresses that are test tokens + * @returns Array of market parameters containing token pairs and tick spacing + * @example + * ```ts + * const markets = await client.getOpenMarkets({ + * cashnesses: { + * "WETH": 10, // Lower cashness -> WETH will be base token + * "USDC": 100 // Higher cashness -> USDC will be quote token + * }, + * displayDecimals: { + * "WETH": 4, + * "USDC": 2 + * }, + * priceDisplayDecimals: { + * "WETH": 2, + * "USDC": 4 + * } + * }); + * // Returns: + * // [ + * // { + * // base: { address: "0x...", symbol: "WETH", decimals: 18, ... }, + * // quote: { address: "0x...", symbol: "USDC", decimals: 6, ... }, + * // tickSpacing: 1n + * // }, + * // ... + * // ] + * ``` + */ + getOpenMarkets: (args: GetOpenMarketArgs) => Promise } export function mangroveActions(actionsParams: MangroveActionsDefaultParams) { @@ -43,5 +85,6 @@ export function mangroveActions(actionsParams: MangroveActionsDefaultParams) { simulateDeployRouter(client, actionsParams, args), getOrder: (args) => getOrder(client, actionsParams, args), getOrders: (args) => getOrders(client, actionsParams, args), + getOpenMarkets: (args) => getOpenMarkets(client, actionsParams, args), }) } diff --git a/src/package.json b/src/package.json index 55303e0..caa9c9a 100644 --- a/src/package.json +++ b/src/package.json @@ -55,7 +55,7 @@ }, "peerDependencies": { "typescript": ">=5.0.4", - "viem": "^2.9.1" + "viem": ">=2.9.1" }, "peerDependenciesMeta": { "typescript": { diff --git a/test/globalSetup.ts b/test/globalSetup.ts index e3babf1..b0364e6 100644 --- a/test/globalSetup.ts +++ b/test/globalSetup.ts @@ -1,4 +1,6 @@ -import { createAnvil, startProxy } from '@viem/anvil' +import { createServer, defineInstance } from 'prool' +import { anvil } from 'prool/instances' +// import { createAnvil, startProxy } from '@viem/anvil' import { type Address, parseAbi, parseEther, parseUnits } from 'viem' import { foundry } from 'viem/chains' import type { GlobalSetupContext } from 'vitest/node' @@ -26,12 +28,11 @@ export const multicall: Address = '0xcA11bde05977b3631167028862bE2a173976CA11' export default async function ({ provide }: GlobalSetupContext) { // create an anvil instance - const anvil = createAnvil({ + const globalInstance = anvil({ port: Number(process.env.MAIN_PORT || 8546), - chainId: foundry.id, ipc: '/tmp/anvil.ipc', }) - await anvil.start() + await globalInstance.start() // setting initial balances of accounts for (const account of accounts) { @@ -130,16 +131,18 @@ export default async function ({ provide }: GlobalSetupContext) { }) // starts a proxy pool from there - const shutdown = await startProxy({ - port: Number(process.env.PROXY_PORT || 8545), - options: { + const server = createServer({ + instance: anvil({ forkUrl: '/tmp/anvil.ipc', - }, + }), + port: Number(process.env.PROXY_PORT || 8545), }) + await server.start() + return async () => { - await shutdown() - await anvil.stop() + await server.stop() + await globalInstance.stop() } }