From 3962cacfe067b7d47bd9717778f323ce661b8741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 08:53:42 +0100 Subject: [PATCH 01/22] feat!(token configuration): Remove fetchDecimalsFromAddress --- CHANGELOG.md | 1 + package.json | 1 - src/configuration.ts | 11 ----------- yarn.lock | 25 ------------------------- 4 files changed, 1 insertion(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 421ea0763..8895f3c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ # 2.0.0-5 - feat: Add usage of Geometric Kandel's call-data-reducing function +- feat!: Removed `configuration.tokens.fetchDecimalsFromAddress`. Instead, use `MgvToken.createTokenFromAddress` and read the decimals from that token. # 2.0.0-4 diff --git a/package.json b/package.json index d2630d50a..a9ddb1a20 100644 --- a/package.json +++ b/package.json @@ -76,7 +76,6 @@ "just-clone": "^6.2.0", "logform": "^2.5.1", "loglevel": "^1.8.1", - "moize": "^6.1.6", "node-cleanup": "^2.1.2", "object-inspect": "^1.12.3", "semver": "^7.5.4", diff --git a/src/configuration.ts b/src/configuration.ts index bb186c435..94a0cddf6 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -19,7 +19,6 @@ import * as contextAddresses from "@mangrovedao/context-addresses"; import * as eth from "./eth"; import clone from "just-clone"; import deepmerge from "deepmerge"; -import moize from "moize"; import semver from "semver"; // Make keys optional at all levels of T @@ -315,16 +314,6 @@ export const tokensConfiguration = { return decimals; }, - /** - * Read chain for decimals of `address` on current network - */ - fetchDecimalsFromAddress: moize( - async (address: string, provider: Provider): Promise => { - const token = typechain.IERC20__factory.connect(address, provider); - return token.decimals(); - }, - ), - /** * Read displayed decimals for `tokenName`. */ diff --git a/yarn.lock b/yarn.lock index 2dd53ec13..6fe9858bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1325,7 +1325,6 @@ __metadata: mkdirp: ^3.0.1 mocha: ^10.2.0 mocha-multi-reporters: ^1.5.1 - moize: ^6.1.6 node-cleanup: ^2.1.2 npm-run-all: ^4.1.5 nyc: ^15.1.0 @@ -3254,13 +3253,6 @@ __metadata: languageName: node linkType: hard -"fast-equals@npm:^3.0.1": - version: 3.0.3 - resolution: "fast-equals@npm:3.0.3" - checksum: e7ac0ae5a10289c773f75654ced22563837336bde7ebb595b7d238a20b77008a821c1ca3526a50e96fe0662ced7454cf99b7488bb64506463a4f4729c523ac4c - languageName: node - linkType: hard - "fast-glob@npm:^3.2.9": version: 3.2.12 resolution: "fast-glob@npm:3.2.12" @@ -4812,13 +4804,6 @@ __metadata: languageName: node linkType: hard -"micro-memoize@npm:^4.1.2": - version: 4.1.2 - resolution: "micro-memoize@npm:4.1.2" - checksum: 4b02750622d44b5ab31573c629b5d91927dd0c2727743ff75e790c223ab6cd02c48cc3bddea69da0dffb688091a0a71a17944947dd165f8ba9e03728bc30a76d - languageName: node - linkType: hard - "micromatch@npm:4.0.5, micromatch@npm:^4.0.4": version: 4.0.5 resolution: "micromatch@npm:4.0.5" @@ -5069,16 +5054,6 @@ __metadata: languageName: node linkType: hard -"moize@npm:^6.1.6": - version: 6.1.6 - resolution: "moize@npm:6.1.6" - dependencies: - fast-equals: ^3.0.1 - micro-memoize: ^4.1.2 - checksum: a81c56e8d3d30ad0c324369ab636e48b9799abb7f5e0955250f99744dbea456848107d293436face8e12acf7bc820e759048118083cc37644a4c95a37c66e566 - languageName: node - linkType: hard - "ms@npm:2.1.2": version: 2.1.2 resolution: "ms@npm:2.1.2" From 535e0fa3fa32f66fa471bfb1c0972cfcb42b1cb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 09:09:35 +0100 Subject: [PATCH 02/22] feat!(unit calculations): Only accept decimals, not token name/symbol --- CHANGELOG.md | 1 + src/mangrove.ts | 25 ++++++---------------- src/util/unitCalculations.ts | 28 ++----------------------- test/unit/unitCalculations.unit.test.ts | 14 +++---------- 4 files changed, 12 insertions(+), 56 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8895f3c07..a4f18b879 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - feat: Add usage of Geometric Kandel's call-data-reducing function - feat!: Removed `configuration.tokens.fetchDecimalsFromAddress`. Instead, use `MgvToken.createTokenFromAddress` and read the decimals from that token. +- feat!: `Mangrove.toUnits|fromUnits` no longer accepts a token name/symbol as this was ambiguous. Instead, use `MgvToken.createToken` and call `toUnits|fromUnits` on that. # 2.0.0-4 diff --git a/src/mangrove.ts b/src/mangrove.ts index 0178d6446..05e34ce60 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -573,9 +573,6 @@ class Mangrove { } /** Convert public token amount to internal token representation. - * - * if `nameOrDecimals` is a string, it is interpreted as a token name. Otherwise - * it is the number of decimals. * * For convenience, has a static and an instance version. * @@ -585,32 +582,22 @@ class Mangrove { * mgv.toUnits(10,6) // 10e6 as ethers.BigNumber * ``` */ - static toUnits( - amount: Bigish, - nameOrDecimals: string | number, - ): ethers.BigNumber { - return UnitCalculations.toUnits(amount, nameOrDecimals); + static toUnits(amount: Bigish, decimals: number): ethers.BigNumber { + return UnitCalculations.toUnits(amount, decimals); } - toUnits(amount: Bigish, nameOrDecimals: string | number): ethers.BigNumber { - return Mangrove.toUnits(amount, nameOrDecimals); + toUnits(amount: Bigish, decimals: number): ethers.BigNumber { + return Mangrove.toUnits(amount, decimals); } /** Convert internal token amount to public token representation. - * - * if `nameOrDecimals` is a string, it is interpreted as a token name. Otherwise - * it is the number of decimals. * * @example * ``` - * mgv.fromUnits("1e19","DAI") // 10 * mgv.fromUnits("1e19",18) // 10 * ``` */ - fromUnits( - amount: number | string | ethers.BigNumber, - nameOrDecimals: string | number, - ): Big { - return UnitCalculations.fromUnits(amount, nameOrDecimals); + fromUnits(amount: number | string | ethers.BigNumber, decimals: number): Big { + return UnitCalculations.fromUnits(amount, decimals); } /** Provision available at mangrove for address given in argument, in ethers */ diff --git a/src/util/unitCalculations.ts b/src/util/unitCalculations.ts index 2d50d9dab..1cc5bf67c 100644 --- a/src/util/unitCalculations.ts +++ b/src/util/unitCalculations.ts @@ -1,54 +1,30 @@ import Big from "big.js"; import * as ethers from "ethers"; -import configuration from "../configuration"; import { Bigish } from "../types"; class UnitCalculations { /** Convert public token amount to internal token representation. - * - * if `nameOrDecimals` is a string, it is interpreted as a token name. Otherwise - * it is the number of decimals. * * @example * ``` - * mgv.toUnits(10,"USDC") // 10e6 as ethers.BigNumber * mgv.toUnits(10,6) // 10e6 as ethers.BigNumber * ``` */ - static toUnits( - amount: Bigish, - nameOrDecimals: string | number, - ): ethers.BigNumber { - let decimals; - if (typeof nameOrDecimals === "number") { - decimals = nameOrDecimals; - } else { - decimals = configuration.tokens.getDecimalsOrFail(nameOrDecimals); - } + static toUnits(amount: Bigish, decimals: number): ethers.BigNumber { return ethers.BigNumber.from(Big(10).pow(decimals).mul(amount).toFixed(0)); } /** Convert internal token amount to public token representation. - * - * if `nameOrDecimals` is a string, it is interpreted as a token name. Otherwise - * it is the number of decimals. * * @example * ``` - * mgv.fromUnits("1e19","DAI") // 10 * mgv.fromUnits("1e19",18) // 10 * ``` */ static fromUnits( amount: number | string | ethers.BigNumber, - nameOrDecimals: string | number, + decimals: number, ): Big { - let decimals; - if (typeof nameOrDecimals === "number") { - decimals = nameOrDecimals; - } else { - decimals = configuration.tokens.getDecimalsOrFail(nameOrDecimals); - } if (amount instanceof ethers.BigNumber) { amount = amount.toString(); } diff --git a/test/unit/unitCalculations.unit.test.ts b/test/unit/unitCalculations.unit.test.ts index 0153ed8c0..d04e86e65 100644 --- a/test/unit/unitCalculations.unit.test.ts +++ b/test/unit/unitCalculations.unit.test.ts @@ -7,7 +7,7 @@ import UnitCalculations from "../../src/util/unitCalculations"; describe("UnitCalculations unit tests suite", () => { describe("fromUnits", () => { - it("returns Big number, amount is number and nameOrDecimal is number", async function () { + it("returns Big number, amount is number", async function () { //Act const result = UnitCalculations.fromUnits(123, 11); @@ -15,7 +15,7 @@ describe("UnitCalculations unit tests suite", () => { equal(result.eq(Big(123).div(Big(10).pow(11))), true); }); - it("returns Big number, amount is string and nameOrDecimal is number", async function () { + it("returns Big number, amount is string", async function () { //Act const result = UnitCalculations.fromUnits("123", 11); @@ -23,20 +23,12 @@ describe("UnitCalculations unit tests suite", () => { equal(result.eq(Big(123).div(Big(10).pow(11))), true); }); - it("returns Big number, amount is BigNumber and nameOrDecimal is number", async function () { + it("returns Big number, amount is BigNumber", async function () { //Act const result = UnitCalculations.fromUnits(BigNumber.from(123), 11); //Assert equal(result.eq(Big(123).div(Big(10).pow(11))), true); }); - - it("returns Big number, amount is number and nameOrDecimal is string", async function () { - //Act - const result = UnitCalculations.fromUnits(123, "DAI"); - - //Assert - equal(result.eq(Big(123).div(Big(10).pow(18))), true); - }); }); }); From ec90e52e60237dcc14d95546a526d5549106df43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 10:29:34 +0100 Subject: [PATCH 03/22] feat!: Remove token configuration getters&setters from Mangrove class --- CHANGELOG.md | 1 + src/mangrove.ts | 79 ------------------- test/integration/mangrove.integration.test.ts | 3 +- 3 files changed, 3 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f18b879..ee2870cfb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - feat: Add usage of Geometric Kandel's call-data-reducing function - feat!: Removed `configuration.tokens.fetchDecimalsFromAddress`. Instead, use `MgvToken.createTokenFromAddress` and read the decimals from that token. - feat!: `Mangrove.toUnits|fromUnits` no longer accepts a token name/symbol as this was ambiguous. Instead, use `MgvToken.createToken` and call `toUnits|fromUnits` on that. +- feat!: Static token configuration getters and setters have been removed from `Mangrove`. Instead, use the methods on `MgvToken`. # 2.0.0-4 diff --git a/src/mangrove.ts b/src/mangrove.ts index 05e34ce60..7b2529eac 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -840,78 +840,6 @@ class Mangrove { return configuration.addresses.getNameFromAddress(address, network); } - /** - * Read decimals for `tokenName` on given network. - * To read decimals directly onchain, use `fetchDecimals`. - */ - static getDecimals(tokenName: string): number | undefined { - return configuration.tokens.getDecimals(tokenName); - } - - /** - * Read decimals for `tokenName`. Fails if the decimals are not in the configuration. - * To read decimals directly onchain, use `fetchDecimals`. - */ - static getDecimalsOrFail(tokenName: string): number { - return configuration.tokens.getDecimalsOrFail(tokenName); - } - - /** - * Read decimals for `tokenName` on given network. - * If not found in the local configuration, fetch them from the current network and save them - */ - static getOrFetchDecimals( - tokenName: string, - provider: Provider, - ): Promise { - return configuration.tokens.getOrFetchDecimals(tokenName, provider); - } - - /** - * Read chain for decimals of `tokenName` on current network and save them - */ - static async fetchDecimals( - tokenName: string, - provider: Provider, - ): Promise { - return configuration.tokens.fetchDecimals(tokenName, provider); - } - - /** - * Read displayed decimals for `tokenName`. - */ - static getDisplayedDecimals(tokenName: string): number { - return configuration.tokens.getDisplayedPriceDecimals(tokenName); - } - - /** - * Read displayed decimals for `tokenName` when displayed as a price. - */ - static getDisplayedPriceDecimals(tokenName: string): number { - return configuration.tokens.getDisplayedPriceDecimals(tokenName); - } - - /** - * Set decimals for `tokenName` on current network. - */ - static setDecimals(tokenName: string, dec: number): void { - configuration.tokens.setDecimals(tokenName, dec); - } - - /** - * Set displayed decimals for `tokenName`. - */ - static setDisplayedDecimals(tokenName: string, dec: number): void { - configuration.tokens.setDisplayedDecimals(tokenName, dec); - } - - /** - * Set displayed decimals for `tokenName` when displayed as a price. - */ - static setDisplayedPriceDecimals(tokenName: string, dec: number): void { - configuration.tokens.setDisplayedPriceDecimals(tokenName, dec); - } - /** * Setup dev node necessary contracts if needed, register dev Multicall2 * address, listen to future additions (a script external to mangrove.js may @@ -1116,13 +1044,6 @@ class Mangrove { ); } - /** Set the relative cashness of a token. This determines which token is base & which is quote in a {@link Market}. - * Lower cashness is base, higher cashness is quote, tiebreaker is lexicographic ordering of name string (name is most likely the same as the symbol). - */ - setCashness(name: string, cashness: number) { - configuration.tokens.setCashness(name, cashness); - } - // cashness is "how similar to cash is a token". The cashier token is the quote. // toBaseQuoteByCashness orders tokens according to relative cashness. // Assume cashness of both to be 0 if cashness is undefined for at least one argument. diff --git a/test/integration/mangrove.integration.test.ts b/test/integration/mangrove.integration.test.ts index 4279e048b..e91052fc5 100644 --- a/test/integration/mangrove.integration.test.ts +++ b/test/integration/mangrove.integration.test.ts @@ -6,6 +6,7 @@ import { toWei } from "../util/helpers"; import { serverType } from "../../src/util/node"; import { Mangrove } from "../../src"; +import { configuration } from "../../src/configuration"; import { Big } from "big.js"; @@ -119,7 +120,7 @@ describe("Mangrove integration tests suite", function () { assert.deepEqual(marketData[0].base, tokenAData); assert.deepEqual(marketData[0].quote, tokenBData); - mgv.setCashness("TokenA", 1000000); + configuration.tokens.setCashness("TokenA", 1000000); marketData = await mgv.openMarketsData(); assert.deepEqual(marketData[0].base, tokenBData); From 02adda880250561eeb9d587b557c8c23b1c07610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 10:31:04 +0100 Subject: [PATCH 04/22] feat!: Remove Mangrove.getTokenAndAddress --- CHANGELOG.md | 1 + src/kandel/kandelFarm.ts | 24 ++++++++++++------------ src/mangrove.ts | 12 ------------ 3 files changed, 13 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ee2870cfb..454047425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - feat!: Removed `configuration.tokens.fetchDecimalsFromAddress`. Instead, use `MgvToken.createTokenFromAddress` and read the decimals from that token. - feat!: `Mangrove.toUnits|fromUnits` no longer accepts a token name/symbol as this was ambiguous. Instead, use `MgvToken.createToken` and call `toUnits|fromUnits` on that. - feat!: Static token configuration getters and setters have been removed from `Mangrove`. Instead, use the methods on `MgvToken`. +- feat!: `Mangrove.getTokenAndAddress` has been removed. Instead, use `Mangrove.tokenFromAddress` and read the address from there. # 2.0.0-4 diff --git a/src/kandel/kandelFarm.ts b/src/kandel/kandelFarm.ts index 6de6d569f..9381df2a5 100644 --- a/src/kandel/kandelFarm.ts +++ b/src/kandel/kandelFarm.ts @@ -85,20 +85,20 @@ class KandelFarm { const olKeyStruct = this.mgv.getOlKeyStruct( x.args.baseQuoteOlKeyHash, ); - const baseToken = await this.mgv.getTokenAndAddress( - olKeyStruct!.outbound_tkn, + const baseToken = await this.mgv.tokenFromAddress( + await olKeyStruct!.outbound_tkn, ); - const quoteToken = await this.mgv.getTokenAndAddress( - olKeyStruct!.inbound_tkn, + const quoteToken = await this.mgv.tokenFromAddress( + await olKeyStruct!.inbound_tkn, ); return { kandelAddress: x.args.kandel, ownerAddress: x.args.owner, onAave: false, baseAddress: baseToken.address, - base: baseToken.token, + base: baseToken, quoteAddress: quoteToken.address, - quote: quoteToken.token, + quote: quoteToken, }; }) : []; @@ -115,20 +115,20 @@ class KandelFarm { const olKeyStruct = this.mgv.getOlKeyStruct( x.args.baseQuoteOlKeyHash, ); - const baseToken = await this.mgv.getTokenAndAddress( - olKeyStruct!.outbound_tkn, + const baseToken = await this.mgv.tokenFromAddress( + await olKeyStruct!.outbound_tkn, ); - const quoteToken = await this.mgv.getTokenAndAddress( - olKeyStruct!.inbound_tkn, + const quoteToken = await this.mgv.tokenFromAddress( + await olKeyStruct!.inbound_tkn, ); return { kandelAddress: x.args.aaveKandel, ownerAddress: x.args.owner, onAave: true, baseAddress: baseToken.address, - base: baseToken.token, + base: baseToken, quoteAddress: quoteToken.address, - quote: quoteToken.token, + quote: quoteToken, }; }) : []; diff --git a/src/mangrove.ts b/src/mangrove.ts index 7b2529eac..82c056b76 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -560,18 +560,6 @@ class Mangrove { ); } - /** Gets the token corresponding to the address if it is known; otherwise, undefined. - */ - async getTokenAndAddress( - address: string, - ): Promise<{ address: string; token?: MgvToken }> { - const name = this.getNameFromAddress(address); - return { - address, - token: name === undefined ? undefined : await this.token(name), - }; - } - /** Convert public token amount to internal token representation. * * For convenience, has a static and an instance version. From 06aa6303648d0c0a80e63486682cd8c42b533935 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 11:05:18 +0100 Subject: [PATCH 05/22] feat!: Remove configuration getters&setters from MgvToken class --- CHANGELOG.md | 2 +- src/mgvtoken.ts | 38 ++------------------------------------ 2 files changed, 3 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 454047425..9c4be8ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ - feat: Add usage of Geometric Kandel's call-data-reducing function - feat!: Removed `configuration.tokens.fetchDecimalsFromAddress`. Instead, use `MgvToken.createTokenFromAddress` and read the decimals from that token. - feat!: `Mangrove.toUnits|fromUnits` no longer accepts a token name/symbol as this was ambiguous. Instead, use `MgvToken.createToken` and call `toUnits|fromUnits` on that. -- feat!: Static token configuration getters and setters have been removed from `Mangrove`. Instead, use the methods on `MgvToken`. +- feat!: Static token configuration getters and setters have been removed from `Mangrove` and `MgvToken`. Instead, use the methods on `configuration.token`. - feat!: `Mangrove.getTokenAndAddress` has been removed. Instead, use `Mangrove.tokenFromAddress` and read the address from there. # 2.0.0-4 diff --git a/src/mgvtoken.ts b/src/mgvtoken.ts index 15f453a11..e26ecfd5b 100644 --- a/src/mgvtoken.ts +++ b/src/mgvtoken.ts @@ -1,7 +1,7 @@ import Big from "big.js"; import * as ethers from "ethers"; import Mangrove from "./mangrove"; -import { Bigish, Provider } from "./types"; +import { Bigish } from "./types"; import { typechain } from "./types"; import UnitCalculations from "./util/unitCalculations"; import configuration from "./configuration"; @@ -88,7 +88,7 @@ class MgvToken { MgvToken.#applyOptions(name, mgv, options); // Ensure decimals are known before token construction as it will otherwise fail. - await MgvToken.getOrFetchDecimals(name, mgv.provider); + await configuration.tokens.getOrFetchDecimals(name, mgv.provider); return new MgvToken(name, mgv, options); } @@ -223,40 +223,6 @@ class MgvToken { return await this.contract.allowance(params.owner, params.spender); } - /** - * Read decimals for `tokenName` on given network. - * To read decimals directly onchain, use `fetchDecimals`. - */ - static getDecimals(tokenName: string): number | undefined { - return configuration.tokens.getDecimals(tokenName); - } - - /** - * Read decimals for `tokenName`. Fails if the decimals are not in the configuration. - * To read decimals directly onchain, use `fetchDecimals`. - */ - static getDecimalsOrFail(tokenName: string): number { - return configuration.tokens.getDecimalsOrFail(tokenName); - } - - /** - * Read decimals for `tokenName` on given network. - * If not found in the local configuration, fetch them from the current network and save them - */ - static getOrFetchDecimals( - tokenName: string, - provider: Provider, - ): Promise { - return configuration.tokens.getOrFetchDecimals(tokenName, provider); - } - - /** - * Set decimals for `tokenName` on current network. - */ - static setDecimals(tokenName: string, dec: number): void { - configuration.tokens.setDecimals(tokenName, dec); - } - /** * Set approval for Mangrove to `amount`. */ From 1dfba25ef55c6d1bef88f86a9f50183ba98e025e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 13:16:43 +0100 Subject: [PATCH 06/22] feat!/fix: Replace token names with id and symbol Token `name` was misused: Sometimes it was assumed to be a symbol and sometimes an ID. It has therefore been replaced by `id` and `symbol` in all relevant places. Configuration files have been converted to use the token instance ID's from the context-addresses package to avoid ambiguity among (1) different tokens with the same symbol and (2) multiple token instances such as `USDC` (Circle issued) and `USDC.e` (bridged from Ethereum). --- CHANGELOG.md | 2 + examples/aaveV3Module.ts | 22 +-- src/configuration.ts | 133 +++++++++++------ src/constants/kandelConfiguration.json | 35 ++--- src/constants/tokens.json | 139 +++++------------- src/kandel/coreKandelInstance.ts | 4 +- src/kandel/kandelConfiguration.ts | 28 ++-- src/mangrove.ts | 63 ++++---- src/mangroveEventSubscriber.ts | 6 +- src/mgvtoken.ts | 55 ++++--- src/offerLogic.ts | 4 +- src/semibook.ts | 2 +- src/util/test/mgvIntegrationTestUtil.ts | 2 +- src/util/trade.ts | 8 +- src/util/tradeEventManagement.ts | 4 +- .../kandel/farm.integration.test.ts | 6 +- .../kandel/seeder.integration.test.ts | 4 +- test/integration/mangrove.integration.test.ts | 4 +- test/integration/market.integration.test.ts | 8 +- test/integration/mgvtoken.integration.test.ts | 8 +- .../offermaker.integration.test.ts | 2 +- .../kandel/kandelConfiguration.unit.test.ts | 14 +- 22 files changed, 272 insertions(+), 281 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c4be8ec6..2ac9fc439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ - feat!: `Mangrove.toUnits|fromUnits` no longer accepts a token name/symbol as this was ambiguous. Instead, use `MgvToken.createToken` and call `toUnits|fromUnits` on that. - feat!: Static token configuration getters and setters have been removed from `Mangrove` and `MgvToken`. Instead, use the methods on `configuration.token`. - feat!: `Mangrove.getTokenAndAddress` has been removed. Instead, use `Mangrove.tokenFromAddress` and read the address from there. +- feat!: Remove `Mangrove.tokenFromConfig`. Use `Mangrove.token` instead. +- feat!/fix: Token `name` was misused: Sometimes it was assumed to be a symbol and sometimes an ID. It has therefore been replaced by `id` and `symbol` in all relevant places. Configuration files have been converted to use the token instance ID's from the context-addresses package to avoid ambiguity among (1) different tokens with the same symbol and (2) multiple token instances such as `USDC` (Circle issued) and `USDC.e` (bridged from Ethereum). # 2.0.0-4 diff --git a/examples/aaveV3Module.ts b/examples/aaveV3Module.ts index 70a90f708..f37ed8e10 100644 --- a/examples/aaveV3Module.ts +++ b/examples/aaveV3Module.ts @@ -27,10 +27,10 @@ class AaveV3Module { } async #debtToken( - tokenName: string, + tokenId: string, signer?: SignerOrProvider, ): Promise { - const asset_address = this.mgv.getAddress(tokenName); + const asset_address = this.mgv.getAddress(tokenId); const debt_address = await this.contract.debtToken(asset_address); return typechain.ICreditDelegationToken__factory.connect( debt_address, @@ -39,11 +39,11 @@ class AaveV3Module { } async approveDelegation( - tokenName: string, + tokenId: string, borrower: string, overrides: ethers.Overrides = {}, ): Promise { - const dTtkn = await this.#debtToken(tokenName); + const dTtkn = await this.#debtToken(tokenId); return dTtkn.approveDelegation( borrower, ethers.constants.MaxUint256, @@ -52,11 +52,11 @@ class AaveV3Module { } async status( - tokenName: string, + tokenId: string, account: string, ): Promise<{ available: Big; borrowable: Big; borrowing: Big }> { - const asset = await this.mgv.token(tokenName); - const dToken = await this.#debtToken(tokenName); + const asset = await this.mgv.token(tokenId); + const dToken = await this.#debtToken(tokenId); const { maxRedeemableUnderlying, maxBorrowAfterRedeemInUnderlying } = await this.contract.maxGettableUnderlying(asset.address, true, account); return { @@ -66,11 +66,11 @@ class AaveV3Module { }; } - async logStatus(tokenNames: string[], account?: string): Promise { + async logStatus(tokenIds: string[], account?: string): Promise { account = account ? account : await this.mgv.signer.getAddress(); - for (const tokenName of tokenNames) { - const stat = await this.status(tokenName, account); - console.log(`----------${tokenName}----------`); + for (const tokenId of tokenIds) { + const stat = await this.status(tokenId, account); + console.log(`----------${tokenId}----------`); console.log("debit:", `\u001b[32m${stat.available}\u001b[0m`); console.log("credit:", `\u001b[33m${stat.borrowable}\u001b[0m`); console.log("debt:", `\u001b[31m${stat.borrowing}\u001b[0m`); diff --git a/src/configuration.ts b/src/configuration.ts index 94a0cddf6..8b751b7d7 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -32,12 +32,15 @@ export type RecursivePartial = { export type network = string; export type address = string; +export type tokenId = string; export type tokenSymbol = string; export type NamedAddresses = Record; export type AddressesConfig = Record; export type TokenConfig = { + symbol?: tokenSymbol; + description?: string; decimals?: number; displayedDecimals?: number; displayedAsPriceDecimals?: number; @@ -113,7 +116,7 @@ export type PartialKandelAllConfigurationFields = Partial; export type PartialMarketConfig = PartialKandelAllConfigurationFields; export type PartialNetworkConfig = PartialKandelAllConfigurationFields & { - markets?: Record>; // base symbol -> quote symbol -> market config + markets?: Record>; // base ID -> quote ID -> market config }; export type PartialKandelConfiguration = PartialKandelAllConfigurationFields & { @@ -137,7 +140,7 @@ export type PartialMangroveOrderConfiguration = export type Configuration = { addressesByNetwork: AddressesConfig; tokenDefaults: TokenDefaults; - tokens: Record; + tokens: Record; mangroveOrder: PartialMangroveOrderConfiguration; reliableEventSubscriber: ReliableEventSubscriberConfig; kandel: PartialKandelConfiguration; @@ -251,121 +254,169 @@ export const addressesConfiguration = { /// TOKENS -function getOrCreateTokenConfig(tokenName: string) { - let tokenConfig = config.tokens[tokenName]; +function getOrCreateTokenConfig(tokenId: tokenId) { + let tokenConfig = config.tokens[tokenId]; if (tokenConfig === undefined) { - config.tokens[tokenName] = tokenConfig = {}; + config.tokens[tokenId] = tokenConfig = {}; } return tokenConfig; } export const tokensConfiguration = { /** - * Read decimals for `tokenName`. + * Read decimals for `tokenId`. * To read decimals directly onchain, use `fetchDecimals`. */ - getDecimals: (tokenName: string): number | undefined => { - return config.tokens[tokenName]?.decimals; + getDecimals: (tokenId: tokenId): number | undefined => { + return getOrCreateTokenConfig(tokenId).decimals; }, /** - * Read decimals for `tokenName`. Fails if the decimals are not in the configuration. + * Read decimals for `tokenId`. Fails if the decimals are not in the configuration. * To read decimals directly onchain, use `fetchDecimals`. */ - getDecimalsOrFail: (tokenName: string): number => { - const decimals = tokensConfiguration.getDecimals(tokenName); + getDecimalsOrFail: (tokenId: tokenId): number => { + const decimals = tokensConfiguration.getDecimals(tokenId); if (decimals === undefined) { - throw Error(`No decimals on record for token ${tokenName}`); + throw Error(`No decimals on record for token ${tokenId}`); } return decimals; }, /** - * Read decimals for `tokenName` on given network. + * Read decimals for `tokenId` on given network. * If not found in the local configuration, fetch them from the current network and save them */ getOrFetchDecimals: async ( - tokenName: string, + tokenId: tokenId, provider: Provider, ): Promise => { - const decimals = tokensConfiguration.getDecimals(tokenName); + const decimals = tokensConfiguration.getDecimals(tokenId); if (decimals !== undefined) { return decimals; } - return tokensConfiguration.fetchDecimals(tokenName, provider); + return tokensConfiguration.fetchDecimals(tokenId, provider); }, /** - * Read chain for decimals of `tokenName` on current network and save them + * Read chain for decimals of `tokenId` on current network and save them */ fetchDecimals: async ( - tokenName: string, + tokenId: tokenId, provider: Provider, ): Promise => { const network = await eth.getProviderNetwork(provider); const token = typechain.IERC20__factory.connect( - addressesConfiguration.getAddress(tokenName, network.name), + addressesConfiguration.getAddress(tokenId, network.name), provider, ); const decimals = await token.decimals(); - tokensConfiguration.setDecimals(tokenName, decimals); + tokensConfiguration.setDecimals(tokenId, decimals); return decimals; }, /** - * Read displayed decimals for `tokenName`. + * Read symbol for `tokenId`. + * To read symbol directly onchain, use `fetchSymbol`. */ - getDisplayedDecimals: (tokenName: string): number => { + getSymbol: (tokenId: tokenId): tokenSymbol | undefined => { + return getOrCreateTokenConfig(tokenId).symbol; + }, + + /** + * Read symbol for `tokenId` on given network. + * If not found in the local configuration, fetch them from the current network and save them + */ + getOrFetchSymbol: async ( + tokenId: tokenId, + provider: Provider, + ): Promise => { + const symbol = tokensConfiguration.getSymbol(tokenId); + if (symbol !== undefined) { + return symbol; + } + + return tokensConfiguration.fetchSymbol(tokenId, provider); + }, + + /** + * Read chain for symbol of `tokenId` on current network and save them + */ + fetchSymbol: async ( + tokenId: tokenId, + provider: Provider, + ): Promise => { + const network = await eth.getProviderNetwork(provider); + const token = typechain.IERC20__factory.connect( + addressesConfiguration.getAddress(tokenId, network.name), + provider, + ); + const symbol = await token.symbol(); + tokensConfiguration.setSymbol(tokenId, symbol); + return symbol; + }, + + /** + * Read displayed decimals for `tokenId`. + */ + getDisplayedDecimals: (tokenId: tokenId): number => { return ( - config.tokens[tokenName]?.displayedDecimals || + getOrCreateTokenConfig(tokenId).displayedDecimals || config.tokenDefaults.defaultDisplayedDecimals ); }, /** - * Read displayed decimals for `tokenName` when displayed as a price. + * Read displayed decimals for `tokenId` when displayed as a price. */ - getDisplayedPriceDecimals: (tokenName: string): number => { + getDisplayedPriceDecimals: (tokenId: tokenId): number => { return ( - config.tokens[tokenName]?.displayedAsPriceDecimals || + getOrCreateTokenConfig(tokenId).displayedAsPriceDecimals || config.tokenDefaults.defaultDisplayedPriceDecimals ); }, /** Get the cashness of a token. See {@link setCashness} for details. */ - getCashness: (tokenName: string): number | undefined => { - return config.tokens[tokenName]?.cashness; + getCashness: (tokenId: tokenId): number | undefined => { + return getOrCreateTokenConfig(tokenId).cashness; + }, + + /** + * Set decimals for `tokenId` on the given network. + */ + setDecimals: (tokenId: tokenId, dec: number): void => { + getOrCreateTokenConfig(tokenId).decimals = dec; }, /** - * Set decimals for `tokenName`. + * Set symbol for `tokenId` on the given network. */ - setDecimals: (tokenName: string, dec: number): void => { - getOrCreateTokenConfig(tokenName).decimals = dec; + setSymbol: (tokenId: tokenId, symbol: tokenSymbol): void => { + getOrCreateTokenConfig(tokenId).symbol = symbol; }, /** - * Set displayed decimals for `tokenName`. + * Set displayed decimals for `tokenId` on the given network. */ - setDisplayedDecimals: (tokenName: string, dec: number): void => { - getOrCreateTokenConfig(tokenName).displayedDecimals = dec; + setDisplayedDecimals: (tokenId: tokenId, dec: number): void => { + getOrCreateTokenConfig(tokenId).displayedDecimals = dec; }, /** - * Set displayed decimals for `tokenName` when displayed as a price. + * Set displayed decimals for `tokenId` on the given network when displayed as a price. */ - setDisplayedPriceDecimals: (tokenName: string, dec: number): void => { - getOrCreateTokenConfig(tokenName).displayedAsPriceDecimals = dec; + setDisplayedPriceDecimals: (tokenId: tokenId, dec: number): void => { + getOrCreateTokenConfig(tokenId).displayedAsPriceDecimals = dec; }, - /** Set the relative cashness of a token. This determines which token is base & which is quote in a {@link Market}. + /** Set the relative cashness of a token on the given network. This determines which token is base & which is quote in a {@link Market}. * Lower cashness is base, higher cashness is quote, tiebreaker is lexicographic ordering of name string (name is most likely the same as the symbol). */ - setCashness: (tokenName: string, cashness: number) => { - getOrCreateTokenConfig(tokenName).cashness = cashness; + setCashness: (tokenId: tokenId, cashness: number) => { + getOrCreateTokenConfig(tokenId).cashness = cashness; }, }; @@ -448,7 +499,7 @@ export function resetConfiguration(): void { defaultDisplayedDecimals: 2, defaultDisplayedPriceDecimals: 6, }, - tokens: clone(loadedTokens as Record), + tokens: clone(loadedTokens as Record), reliableEventSubscriber: { defaultBlockManagerOptions: { maxBlockCached: 50, diff --git a/src/constants/kandelConfiguration.json b/src/constants/kandelConfiguration.json index 1f4fbc19b..af0ac4811 100644 --- a/src/constants/kandelConfiguration.json +++ b/src/constants/kandelConfiguration.json @@ -13,35 +13,35 @@ "minimumBasePerOfferFactor": 10, "minimumQuotePerOfferFactor": 10, "markets": { - "WETH": { - "DAI": { + "WETH.T/MGV": { + "DAI.T/AAVEv3": { "aaveEnabled": false }, - "USDC": { + "USDC.T/MGV": { "minimumBasePerOfferFactor": 6, "minimumQuotePerOfferFactor": 6, "aaveEnabled": false } }, - "DAI": { - "USDC": { + "DAI.T/AAVEv3": { + "USDC.T/MGV": { "aaveEnabled": true } }, - "WMATIC": { - "USDT": { + "WMATIC.T/MGV": { + "USDT.T/MGV": { "minimumBasePerOfferFactor": 6, "minimumQuotePerOfferFactor": 6 } }, - "WBTC": { - "USDT": {}, - "DAI": { + "WBTC.T/AAVEv3": { + "USDT.T/MGV": {}, + "DAI.T/AAVEv3": { "aaveEnabled": true } }, - "CRV": { - "WBTC": { + "CRV.T/AAVEv3": { + "WBTC.T/AAVEv3": { "aaveEnabled": true } } @@ -54,17 +54,14 @@ "minimumQuotePerOfferFactor": 10, "aaveEnabled": true, "markets": { - "WETH": { - "USDC": { + "WETH.e": { + "USDC.e": { "minimumBasePerOfferFactor": 6, "minimumQuotePerOfferFactor": 6 } }, - "USDC": { - "USDT": {} - }, - "WMATIC": { - "WETH": {} + "USDC.e": { + "USDT.e": {} } } }, diff --git a/src/constants/tokens.json b/src/constants/tokens.json index 6f5b683f9..5f07b4c13 100644 --- a/src/constants/tokens.json +++ b/src/constants/tokens.json @@ -1,114 +1,59 @@ { - "cBAT": { - "decimals": 8 - }, - "cCOMP": { - "decimals": 8 - }, - "cDAI": { - "decimals": 8 - }, - "cETH": { - "decimals": 8 - }, - "cLINK": { - "decimals": 8 - }, - "cREP": { - "decimals": 8 - }, - "cSAI": { - "decimals": 8 - }, - "cTUSD": { - "decimals": 8 - }, - "cUNI": { - "decimals": 8 - }, - "cUSDC": { - "decimals": 8 - }, - "cUSDT": { - "decimals": 8 - }, - "cWBTC": { - "decimals": 8 - }, - "cZRX": { - "decimals": 8 - }, - "BAT": { - "decimals": 18 - }, - "BTC": { - "decimals": 8 - }, - "COMP": { - "decimals": 18 - }, - "DAI": { - "decimals": 18, + "DAI.": { "displayedDecimals": 2, "displayedAsPriceDecimals": 6 }, - "ETH": { - "decimals": 18 - }, - "GRT": { - "decimals": 18 - }, - "KNC": { - "decimals": 18 - }, - "LINK": { - "decimals": 18 + "DAI.e": { + "displayedDecimals": 2, + "displayedAsPriceDecimals": 6 }, - "MGV": { - "decimals": 18, + "DAI.T/AAVEv3": { "displayedDecimals": 2, "displayedAsPriceDecimals": 6 }, - "REP": { - "decimals": 18 + "DAI.T/MGV": { + "displayedDecimals": 2, + "displayedAsPriceDecimals": 6 }, - "SAI": { - "decimals": 18 + "USDC.": { + "displayedDecimals": 2, + "displayedAsPriceDecimals": 6 }, - "SNX": { - "decimals": 18 + "USDC.e": { + "displayedDecimals": 2, + "displayedAsPriceDecimals": 6 }, - "TUSD": { - "decimals": 18 + "USDC.T/MGV": { + "displayedDecimals": 2, + "displayedAsPriceDecimals": 6 }, - "UNI": { - "decimals": 18 + "USDT.": { + "displayedDecimals": 2, + "displayedAsPriceDecimals": 6 }, - "USDC": { - "decimals": 6, + "USDT.e": { "displayedDecimals": 2, "displayedAsPriceDecimals": 6 }, - "USDT": { - "decimals": 6, + "USDT.T/MGV": { "displayedDecimals": 2, "displayedAsPriceDecimals": 6 }, - "WBTC": { - "decimals": 8, + "WBTC.e": { "displayedDecimals": 6, "displayedAsPriceDecimals": 6 }, - "WMATIC": { - "decimals": 18, - "displayedDecimals": 2, + "WBTC.T/AAVEv3": { + "displayedDecimals": 6, "displayedAsPriceDecimals": 6 }, - "XTZ": { - "decimals": 6 + "WBTC.T/MGV": { + "displayedDecimals": 6, + "displayedAsPriceDecimals": 6 }, - "ZRX": { - "decimals": 18 + "WMATIC.T/MGV": { + "displayedDecimals": 2, + "displayedAsPriceDecimals": 6 }, "TokenA": { "decimals": 18, @@ -118,24 +63,16 @@ "decimals": 18, "cashness": 1000 }, - "WETH": { - "decimals": 18, + "WETH.": { "displayedDecimals": 4, "displayedAsPriceDecimals": 10 }, - "aWETH": { - "decimals": 18 - }, - "aDAI": { - "decimals": 18 - }, - "aUSDC": { - "decimals": 6 - }, - "PxUSDC": { - "decimals": 6 + "WETH.e": { + "displayedDecimals": 4, + "displayedAsPriceDecimals": 10 }, - "PxMATIC": { - "decimals": 17 + "WETH.T/MGV": { + "displayedDecimals": 4, + "displayedAsPriceDecimals": 10 } } diff --git a/src/kandel/coreKandelInstance.ts b/src/kandel/coreKandelInstance.ts index 9c372ca2c..d5dc0e56a 100644 --- a/src/kandel/coreKandelInstance.ts +++ b/src/kandel/coreKandelInstance.ts @@ -499,8 +499,8 @@ class CoreKandelInstance { getMostSpecificConfig() { return this.configuration.getMostSpecificConfig( this.market.mgv.network.name, - this.getBase().name, - this.getQuote().name, + this.getBase().id, + this.getQuote().id, ); } diff --git a/src/kandel/kandelConfiguration.ts b/src/kandel/kandelConfiguration.ts index 090cf76ae..796ee026e 100644 --- a/src/kandel/kandelConfiguration.ts +++ b/src/kandel/kandelConfiguration.ts @@ -28,18 +28,18 @@ class KandelConfiguration { /** Gets the most specific available config for the network and the base/quote pair. * @param networkName The name of the network. - * @param baseName The name of the base token. - * @param quoteName The name of the quote token. + * @param baseId The ID of the base token. + * @param quoteId The ID of the quote token. * @returns The most specific configuration available for the network and the base/quote pair. */ public getMostSpecificConfig( networkName: string, - baseName: string, - quoteName: string, + baseId: string, + quoteId: string, ): KandelNetworkConfiguration & Partial { const networkSpecificConfig = this.rawConfiguration.networks?.[networkName]; - const baseSpecificConfig = networkSpecificConfig?.markets?.[baseName]; - const marketSpecificConfig = baseSpecificConfig?.[quoteName]; + const baseSpecificConfig = networkSpecificConfig?.markets?.[baseId]; + const marketSpecificConfig = baseSpecificConfig?.[quoteId]; const config = { ...this.rawConfiguration, @@ -70,21 +70,21 @@ class KandelConfiguration { /** Gets the config for the network and the base/quote pair. * @param networkName The name of the network. - * @param baseName The name of the base token. - * @param quoteName The name of the quote token. + * @param baseId The ID of the base token. + * @param quoteId The ID of the quote token. * @returns The configuration for the network and the base/quote pair. * @throws If the full config is not available for the network and the base/quote pair. */ public getConfigForBaseQuote( networkName: string, - baseName: string, - quoteName: string, + baseId: string, + quoteId: string, ): KandelNetworkConfiguration & KandelMarketConfiguration { - const config = this.getMostSpecificConfig(networkName, baseName, quoteName); + const config = this.getMostSpecificConfig(networkName, baseId, quoteId); function thrower(msg: string): never { throw new Error( - `${msg} for pair ${baseName}/${quoteName} on network ${networkName}.`, + `${msg} for pair ${baseId}/${quoteId} on network ${networkName}.`, ); } @@ -119,8 +119,8 @@ class KandelConfiguration { ): KandelNetworkConfiguration & KandelMarketConfiguration { return this.getConfigForBaseQuote( market.mgv.network.name, - market.base.name, - market.quote.name, + market.base.id, + market.quote.id, ); } diff --git a/src/mangrove.ts b/src/mangrove.ts index 82c056b76..f679237a5 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -89,8 +89,8 @@ namespace Mangrove { }; export type OpenMarketInfo = { - base: { name: string; address: string; symbol: string; decimals: number }; - quote: { name: string; address: string; symbol: string; decimals: number }; + base: { id: string; address: string; symbol: string; decimals: number }; + quote: { id: string; address: string; symbol: string; decimals: number }; tickSpacing: ethers.BigNumber; asksConfig?: LocalConfig; bidsConfig?: LocalConfig; @@ -505,24 +505,16 @@ class Mangrove { /** Return MgvToken instance, fetching data (decimals) from chain if needed. */ async token( - name: string, + id: string, options?: MgvToken.ConstructorOptions, ): Promise { - return MgvToken.createToken(name, this, options); + return MgvToken.createToken(id, this, options); } async tokenFromAddress(address: string): Promise { return MgvToken.createTokenFromAddress(address, this); } - /** Return MgvToken instance reading only from configuration, not from chain. */ - tokenFromConfig( - name: string, - options?: MgvToken.ConstructorOptions, - ): MgvToken { - return new MgvToken(name, this, options); - } - /** * Read a contract address on the current network. * @@ -614,10 +606,10 @@ class Mangrove { } async approveMangrove( - tokenName: string, + tokenId: string, arg: ApproveArgs = {}, ): Promise { - const token = await this.token(tokenName); + const token = await this.token(tokenId); return token.approveMangrove(arg); } @@ -945,24 +937,21 @@ class Mangrove { // format return value return raw.markets.map(([tkn0, tkn1, tickSpacing]) => { // Use internal mgv name if defined; otherwise use the symbol. - const tkn0Name = this.getNameFromAddress(tkn0) ?? data[tkn0].symbol; - const tkn1Name = this.getNameFromAddress(tkn1) ?? data[tkn1].symbol; + const tkn0Id = this.getNameFromAddress(tkn0) ?? data[tkn0].symbol; + const tkn1Id = this.getNameFromAddress(tkn1) ?? data[tkn1].symbol; - const { baseName, quoteName } = Mangrove.toBaseQuoteByCashness( - tkn0Name, - tkn1Name, - ); - const [base, quote] = baseName === tkn0Name ? [tkn0, tkn1] : [tkn1, tkn0]; + const { baseId, quoteId } = this.toBaseQuoteByCashness(tkn0Id, tkn1Id); + const [base, quote] = baseId === tkn0Id ? [tkn0, tkn1] : [tkn1, tkn0]; return { base: { - name: baseName, + id: baseId, address: base, symbol: data[base].symbol, decimals: data[base].decimals, }, quote: { - name: quoteName, + id: quoteId, address: quote, symbol: data[quote].symbol, decimals: data[quote].decimals, @@ -1012,18 +1001,18 @@ class Mangrove { // TODO: fetch all semibook configs in one Multicall and dispatch to Semibook initializations (see openMarketsData) instead of firing multiple RPC calls. return Promise.all( openMarketsData.map(({ base, quote, tickSpacing }) => { - this.setAddress(base.name, base.address); - if (configuration.tokens.getDecimals(base.name) === undefined) { - configuration.tokens.setDecimals(base.name, base.decimals); + this.setAddress(base.id, base.address); + if (configuration.tokens.getDecimals(base.id) === undefined) { + configuration.tokens.setDecimals(base.id, base.decimals); } - this.setAddress(quote.name, quote.address); - if (configuration.tokens.getDecimals(quote.name) === undefined) { - configuration.tokens.setDecimals(quote.name, quote.decimals); + this.setAddress(quote.id, quote.address); + if (configuration.tokens.getDecimals(quote.id) === undefined) { + configuration.tokens.setDecimals(quote.id, quote.decimals); } return Market.connect({ mgv: this, - base: base.name, - quote: quote.name, + base: base.id, + quote: quote.id, tickSpacing: tickSpacing.toString(), bookOptions: bookOptions, noInit: noInit, @@ -1036,16 +1025,16 @@ class Mangrove { // toBaseQuoteByCashness orders tokens according to relative cashness. // Assume cashness of both to be 0 if cashness is undefined for at least one argument. // Ordering is lex order on cashness x (string order) - static toBaseQuoteByCashness(name0: string, name1: string) { - let cash0 = configuration.tokens.getCashness(name0); - let cash1 = configuration.tokens.getCashness(name1); + toBaseQuoteByCashness(tokenId0: string, tokenId1: string) { + let cash0 = configuration.tokens.getCashness(tokenId0); + let cash1 = configuration.tokens.getCashness(tokenId1); if (cash0 === undefined || cash1 === undefined) { cash0 = cash1 = 0; } - if (cash0 < cash1 || (cash0 === cash1 && name0 < name1)) { - return { baseName: name0, quoteName: name1 }; + if (cash0 < cash1 || (cash0 === cash1 && tokenId0 < tokenId1)) { + return { baseId: tokenId0, quoteId: tokenId1 }; } else { - return { baseName: name1, quoteName: name0 }; + return { baseId: tokenId1, quoteId: tokenId0 }; } } } diff --git a/src/mangroveEventSubscriber.ts b/src/mangroveEventSubscriber.ts index 51b2f8348..599f36257 100644 --- a/src/mangroveEventSubscriber.ts +++ b/src/mangroveEventSubscriber.ts @@ -92,14 +92,14 @@ class MangroveEventSubscriber extends LogSubscriber { @@ -120,7 +120,7 @@ class MangroveEventSubscriber extends LogSubscriber { - MgvToken.#applyOptions(name, mgv, options); + MgvToken.#applyOptions(id, mgv, options); - // Ensure decimals are known before token construction as it will otherwise fail. - await configuration.tokens.getOrFetchDecimals(name, mgv.provider); + // Ensure decimals and symbol are known before token construction as it will otherwise fail. + await configuration.tokens.getOrFetchDecimals(id, mgv.provider); + await configuration.tokens.getOrFetchSymbol(id, mgv.provider); - return new MgvToken(name, mgv, options); + return new MgvToken(id, mgv, options); } static async createTokenFromAddress( @@ -102,15 +108,17 @@ class MgvToken { mgv.provider, ); - const name = await contract.callStatic.symbol(); + const symbol = await contract.callStatic.symbol(); + const id = symbol ?? address; - return this.createToken(name, mgv, { + return this.createToken(id, mgv, { address, + symbol, }); } static #applyOptions( - name: string, + id: string, mgv: Mangrove, options?: MgvToken.ConstructorOptions, ) { @@ -119,21 +127,22 @@ class MgvToken { } if ("address" in options && options.address !== undefined) { - mgv.setAddress(name, options.address); + mgv.setAddress(id, options.address); } if ("decimals" in options && options.decimals !== undefined) { - configuration.tokens.setDecimals(name, options.decimals); + configuration.tokens.setDecimals(id, options.decimals); + } + + if ("symbol" in options && options.symbol !== undefined) { + configuration.tokens.setSymbol(id, options.symbol); } if ( "displayedDecimals" in options && options.displayedDecimals !== undefined ) { - configuration.tokens.setDisplayedDecimals( - name, - options.displayedDecimals, - ); + configuration.tokens.setDisplayedDecimals(id, options.displayedDecimals); } } diff --git a/src/offerLogic.ts b/src/offerLogic.ts index 8c4589a82..550bb47e7 100644 --- a/src/offerLogic.ts +++ b/src/offerLogic.ts @@ -53,14 +53,14 @@ class OfferLogic { * @param args optional `arg.amount` can be used if one wishes to approve a finite amount */ async approve( - tokenName: string, + tokenId: string, args?: { optSpender?: string; optAmount?: Bigish; optOverrides?: ethers.Overrides; }, ): Promise { - const token = await this.mgv.token(tokenName); + const token = await this.mgv.token(tokenId); const amount = args && args.optAmount != undefined ? token.toUnits(args.optAmount) diff --git a/src/semibook.ts b/src/semibook.ts index 804aa74f5..6f4c3581c 100644 --- a/src/semibook.ts +++ b/src/semibook.ts @@ -218,7 +218,7 @@ class Semibook canConstructSemibook = true; semibook = new Semibook(market, ba, eventListener, options); logger.debug( - `Semibook.connect() ${ba} ${market.base.name} / ${market.quote.name}`, + `Semibook.connect() ${ba} ${market.base.id} / ${market.quote.id}`, ); if (!market.mgv.shouldNotListenToNewEvents) { await market.mgv.mangroveEventSubscriber.subscribeToSemibook(semibook); diff --git a/src/util/test/mgvIntegrationTestUtil.ts b/src/util/test/mgvIntegrationTestUtil.ts index 504aa75fa..8ea970fea 100644 --- a/src/util/test/mgvIntegrationTestUtil.ts +++ b/src/util/test/mgvIntegrationTestUtil.ts @@ -472,7 +472,7 @@ const rawMint = async ( internalAmount: ethers.BigNumberish, ): Promise => { const deployer = await getAccount(AccountName.Deployer); - switch (token.name) { + switch (token.id) { case "TokenA": await waitForTransaction( deployer.connectedContracts.tokenA.mintTo( diff --git a/src/util/trade.ts b/src/util/trade.ts index 75baf6a4b..7dc5729a7 100644 --- a/src/util/trade.ts +++ b/src/util/trade.ts @@ -501,8 +501,8 @@ class Trade { logger.debug("Creating market order", { contextInfo: "market.marketOrder", data: { - outboundTkn: outboundTkn.name, - inboundTkn: inboundTkn.name, + outboundTkn: outboundTkn.id, + inboundTkn: inboundTkn.id, fillWants: fillWants, tick: tick.toString(), fillVolume: fillVolume.toString(), @@ -754,8 +754,8 @@ class Trade { logger.debug("Creating cleans", { contextInfo: "market.clean", data: { - outboundTkn: outboundTkn.name, - inboundTkn: inboundTkn.name, + outboundTkn: outboundTkn.id, + inboundTkn: inboundTkn.id, }, }); diff --git a/src/util/tradeEventManagement.ts b/src/util/tradeEventManagement.ts index 256818e86..f0f7e0b3c 100644 --- a/src/util/tradeEventManagement.ts +++ b/src/util/tradeEventManagement.ts @@ -171,8 +171,8 @@ class TradeEventManagement { if (olKeyHash != evt.args.olKeyHash) { logger.debug("OfferWrite for unknown market!", { contextInfo: "tradeEventManagement", - base: market.base.name, - quote: market.quote.name, + base: market.base.id, + quote: market.quote.id, tickSpacing: market.tickSpacing, data: { olKeyHash: evt.args.olKeyHash, diff --git a/test/integration/kandel/farm.integration.test.ts b/test/integration/kandel/farm.integration.test.ts index 38abd1fd6..a419f713a 100644 --- a/test/integration/kandel/farm.integration.test.ts +++ b/test/integration/kandel/farm.integration.test.ts @@ -116,13 +116,13 @@ describe(`${KandelFarm.prototype.constructor.name} integration tests suite`, fun const kandels = await farm.getKandels(); // Assert assert.equal(kandels.length, 5, "total count wrong"); - assert.equal(kandels.filter((x) => x.base?.name == "TokenA").length, 1); - assert.equal(kandels.filter((x) => x.base?.name == "WETH").length, 4); + assert.equal(kandels.filter((x) => x.base?.id == "TokenA").length, 1); + assert.equal(kandels.filter((x) => x.base?.id == "WETH").length, 4); assert.equal( kandels.filter((x) => x.baseAddress == mgv.getAddress("WETH")).length, 4, ); - assert.equal(kandels.filter((x) => x.quote?.name == "USDC").length, 3); + assert.equal(kandels.filter((x) => x.quote?.id == "USDC").length, 3); assert.equal( kandels.filter((x) => x.quoteAddress == mgv.getAddress("USDC")).length, 3, diff --git a/test/integration/kandel/seeder.integration.test.ts b/test/integration/kandel/seeder.integration.test.ts index 1ecdbbcf3..29c66fef4 100644 --- a/test/integration/kandel/seeder.integration.test.ts +++ b/test/integration/kandel/seeder.integration.test.ts @@ -103,8 +103,8 @@ describe(`${KandelSeeder.prototype.constructor.name} integration tests suite`, f // Assert const params = await kandel.getParameters(); - assert.equal("TokenA", kandel.getBase().name, "wrong base"); - assert.equal("TokenB", kandel.getQuote().name, "wrong base"); + assert.equal("TokenA", kandel.getBase().id, "wrong base"); + assert.equal("TokenB", kandel.getQuote().id, "wrong base"); assert.equal(market, kandel.market, "wrong market"); assert.equal( liquiditySharing && onAave diff --git a/test/integration/mangrove.integration.test.ts b/test/integration/mangrove.integration.test.ts index e91052fc5..9014fcb63 100644 --- a/test/integration/mangrove.integration.test.ts +++ b/test/integration/mangrove.integration.test.ts @@ -108,13 +108,13 @@ describe("Mangrove integration tests suite", function () { const tokenAData = { address: mgv.getAddress("TokenA"), decimals: 18, - name: "TokenA", + id: "TokenA", symbol: "TokenA", }; const tokenBData = { address: mgv.getAddress("TokenB"), decimals: 6, - name: "TokenB", + id: "TokenB", symbol: "TokenB", }; assert.deepEqual(marketData[0].base, tokenAData); diff --git a/test/integration/market.integration.test.ts b/test/integration/market.integration.test.ts index f8bdfa915..c8208a8fa 100644 --- a/test/integration/market.integration.test.ts +++ b/test/integration/market.integration.test.ts @@ -139,8 +139,8 @@ describe("Market integration tests suite", () => { // Act const result = market.getOutboundInbound("asks"); // Assert - assert.equal(result.outbound_tkn.name, "TokenA"); - assert.equal(result.inbound_tkn.name, "TokenB"); + assert.equal(result.outbound_tkn.id, "TokenA"); + assert.equal(result.inbound_tkn.id, "TokenB"); }); it("returns this.base as inbound and this.quote as outbound, when bids", async function () { @@ -153,8 +153,8 @@ describe("Market integration tests suite", () => { // Act const result = market.getOutboundInbound("bids"); // Assert - assert.equal(result.inbound_tkn.name, "TokenA"); - assert.equal(result.outbound_tkn.name, "TokenB"); + assert.equal(result.inbound_tkn.id, "TokenA"); + assert.equal(result.outbound_tkn.id, "TokenB"); }); }); diff --git a/test/integration/mgvtoken.integration.test.ts b/test/integration/mgvtoken.integration.test.ts index b43237fb2..02d5bc7c9 100644 --- a/test/integration/mgvtoken.integration.test.ts +++ b/test/integration/mgvtoken.integration.test.ts @@ -1,6 +1,7 @@ import assert from "assert"; import { afterEach, beforeEach, describe, it } from "mocha"; import { Mangrove, ethers } from "../../src"; +import { configuration } from "../../src/configuration"; import { Big } from "big.js"; import { @@ -22,6 +23,11 @@ describe("MGV Token integration tests suite", () => { privateKey: this.accounts.tester.key, }); + configuration.tokens.setDecimals("USDC", 6); + configuration.tokens.setDisplayedDecimals("USDC", 2); + configuration.tokens.setDecimals("WETH", 18); + configuration.tokens.setDisplayedDecimals("WETH", 4); + //shorten polling for faster tests // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -221,7 +227,7 @@ describe("MGV Token integration tests suite", () => { const usdc = await mgv.token("USDC"); const usdc2 = await mgv.tokenFromAddress(usdc.address); - assert.equal(usdc.name, usdc2.name); + assert.equal(usdc.id, usdc2.id); assert.equal(usdc.decimals, usdc2.decimals); }); }); diff --git a/test/integration/offermaker.integration.test.ts b/test/integration/offermaker.integration.test.ts index 3c11f2a11..672249f1d 100644 --- a/test/integration/offermaker.integration.test.ts +++ b/test/integration/offermaker.integration.test.ts @@ -420,7 +420,7 @@ describe("OfferMaker integration test suite", () => { const logic = onchain_lp.logic as OfferLogic; const signer_address = await logic.mgv.signer.getAddress(); - const tx = await logic.approve(base.name, { + const tx = await logic.approve(base.id, { optAmount: 42, optOverrides: { gasLimit: 80000 }, }); diff --git a/test/unit/kandel/kandelConfiguration.unit.test.ts b/test/unit/kandel/kandelConfiguration.unit.test.ts index cfc3ce01e..34ef239a2 100644 --- a/test/unit/kandel/kandelConfiguration.unit.test.ts +++ b/test/unit/kandel/kandelConfiguration.unit.test.ts @@ -74,13 +74,13 @@ describe(`${KandelConfiguration.prototype.constructor.name} unit tests suite`, ( const markets = sut.getConfiguredMarketsForNetwork("maticmum"); // Assert assert.deepStrictEqual(markets, [ - { base: "WETH", quote: "DAI" }, - { base: "WETH", quote: "USDC" }, - { base: "DAI", quote: "USDC" }, - { base: "WMATIC", quote: "USDT" }, - { base: "WBTC", quote: "USDT" }, - { base: "WBTC", quote: "DAI" }, - { base: "CRV", quote: "WBTC" }, + { base: "WETH.T/MGV", quote: "DAI.T/AAVEv3" }, + { base: "WETH.T/MGV", quote: "USDC.T/MGV" }, + { base: "DAI.T/AAVEv3", quote: "USDC.T/MGV" }, + { base: "WMATIC.T/MGV", quote: "USDT.T/MGV" }, + { base: "WBTC.T/AAVEv3", quote: "USDT.T/MGV" }, + { base: "WBTC.T/AAVEv3", quote: "DAI.T/AAVEv3" }, + { base: "CRV.T/AAVEv3", quote: "WBTC.T/AAVEv3" }, ]); }); From 2dd7a87998edf61912490e8019ed48fe30ff4bda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 13:40:56 +0100 Subject: [PATCH 07/22] feat: Read token decimals and symbol from context-addresses --- src/configuration.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 8b751b7d7..fae83bf25 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -40,7 +40,6 @@ export type AddressesConfig = Record; export type TokenConfig = { symbol?: tokenSymbol; - description?: string; decimals?: number; displayedDecimals?: number; displayedAsPriceDecimals?: number; @@ -546,7 +545,7 @@ export function resetConfiguration(): void { // 1. context-addresses addresses // 2. mangrove-deployments addresses // Last loaded address is used - readContextAddresses(); + readContextAddressesAndTokens(); readMangroveDeploymentAddresses(); } @@ -616,9 +615,9 @@ function readVersionDeploymentsAddresses( } } -function readContextAddresses() { +function readContextAddressesAndTokens() { readContextMulticallAddresses(); - readContextErc20Addresses(); + readContextErc20Tokens(); readContextAaveAddresses(); } @@ -632,10 +631,8 @@ function readContextMulticallAddresses() { } } -function readContextErc20Addresses() { - for (const [, /*tokenId*/ erc20] of Object.entries( - contextAddresses.getAllErc20s(), - )) { +function readContextErc20Tokens() { + for (const [, erc20] of Object.entries(contextAddresses.getAllErc20s())) { for (const [networkId, networkInstances] of Object.entries( erc20.networkInstances, )) { @@ -643,6 +640,9 @@ function readContextErc20Addresses() { for (const [erc20InstanceId, erc20Instance] of Object.entries( networkInstances, )) { + tokensConfiguration.setDecimals(erc20InstanceId, erc20.decimals); + tokensConfiguration.setSymbol(erc20InstanceId, erc20.symbol); + addressesConfiguration.setAddress( erc20InstanceId, erc20Instance.address, From 5eba8c3f8fc626d92f72d2773437e992642c8c7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 14:42:41 +0100 Subject: [PATCH 08/22] feat: Add functions to manage and create default token from symbol --- CHANGELOG.md | 1 + src/configuration.ts | 73 ++++++++++++++++++++++++++++++++++++++++++-- src/mangrove.ts | 20 ++++++++++-- src/mgvtoken.ts | 32 +++++++++++++++++-- 4 files changed, 120 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ac9fc439..21e12824b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - feat!: `Mangrove.getTokenAndAddress` has been removed. Instead, use `Mangrove.tokenFromAddress` and read the address from there. - feat!: Remove `Mangrove.tokenFromConfig`. Use `Mangrove.token` instead. - feat!/fix: Token `name` was misused: Sometimes it was assumed to be a symbol and sometimes an ID. It has therefore been replaced by `id` and `symbol` in all relevant places. Configuration files have been converted to use the token instance ID's from the context-addresses package to avoid ambiguity among (1) different tokens with the same symbol and (2) multiple token instances such as `USDC` (Circle issued) and `USDC.e` (bridged from Ethereum). + - Default token IDs can be registered for a symbol and network. And if there is only one ID for a given symbol on a network, it will be considered the default. `Mangrove.token()` will create an instance of the default token ID if found. # 2.0.0-4 diff --git a/src/configuration.ts b/src/configuration.ts index fae83bf25..3bd3e8185 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -140,6 +140,7 @@ export type Configuration = { addressesByNetwork: AddressesConfig; tokenDefaults: TokenDefaults; tokens: Record; + tokenSymbolDefaultIdsByNetwork: Record>; mangroveOrder: PartialMangroveOrderConfiguration; reliableEventSubscriber: ReliableEventSubscriberConfig; kandel: PartialKandelConfiguration; @@ -253,7 +254,7 @@ export const addressesConfiguration = { /// TOKENS -function getOrCreateTokenConfig(tokenId: tokenId) { +function getOrCreateTokenConfig(tokenId: tokenId): TokenConfig { let tokenConfig = config.tokens[tokenId]; if (tokenConfig === undefined) { config.tokens[tokenId] = tokenConfig = {}; @@ -261,7 +262,56 @@ function getOrCreateTokenConfig(tokenId: tokenId) { return tokenConfig; } +function getOrCreateDefaultIdsForSymbol( + symbol: tokenSymbol, +): Record { + let defaultIdsForSymbol = config.tokenSymbolDefaultIdsByNetwork[symbol]; + if (defaultIdsForSymbol === undefined) { + config.tokenSymbolDefaultIdsByNetwork[symbol] = defaultIdsForSymbol = {}; + } + return defaultIdsForSymbol; +} + export const tokensConfiguration = { + /** + * Returns true if the given token ID has been registered; otherwise, false. + */ + isTokenIdRegistered(tokenId: tokenId): boolean { + return config.tokens[tokenId] !== undefined; + }, + + /** + * Gets the default token ID for a given symbol and network if + * (1) any has been registered or + * (2) if there is only one token with that symbol. + */ + getDefaultIdForSymbolOnNetwork( + tokenSymbol: tokenSymbol, + network: network, + ): tokenId | undefined { + const registeredDefault = + getOrCreateDefaultIdsForSymbol(tokenSymbol)[network]; + if (registeredDefault !== undefined) { + return registeredDefault; + } + + // Loop through config.tokens to find the first token with the given symbol on the given network + let foundTokenId: tokenId | undefined; + for (const [tokenId, tokenConfig] of Object.entries(config.tokens)) { + if ( + tokenConfig.symbol === tokenSymbol && + addressesConfiguration.getAddress(tokenId, network) !== undefined + ) { + if (foundTokenId !== undefined) { + // If we already found a token with that symbol, we cannot decide which one is the default + return undefined; + } + foundTokenId = tokenId; + } + } + return foundTokenId; + }, + /** * Read decimals for `tokenId`. * To read decimals directly onchain, use `fetchDecimals`. @@ -383,6 +433,17 @@ export const tokensConfiguration = { return getOrCreateTokenConfig(tokenId).cashness; }, + /** + * Set the default token ID for a given symbol and network. + */ + setDefaultIdForSymbolOnNetwork( + tokenSymbol: tokenSymbol, + network: network, + tokenId: tokenId, + ): void { + getOrCreateDefaultIdsForSymbol(tokenSymbol)[network] = tokenId; + }, + /** * Set decimals for `tokenId` on the given network. */ @@ -499,6 +560,7 @@ export function resetConfiguration(): void { defaultDisplayedPriceDecimals: 6, }, tokens: clone(loadedTokens as Record), + tokenSymbolDefaultIdsByNetwork: {}, reliableEventSubscriber: { defaultBlockManagerOptions: { maxBlockCached: 50, @@ -648,8 +710,15 @@ function readContextErc20Tokens() { erc20Instance.address, networkName, ); - // Also register the default instance as the token symbol for convenience + if (erc20Instance.default) { + tokensConfiguration.setDefaultIdForSymbolOnNetwork( + erc20.symbol, + networkName, + erc20InstanceId, + ); + + // Also register the default instance as the token symbol for convenience addressesConfiguration.setAddress( erc20.symbol, erc20Instance.address, diff --git a/src/mangrove.ts b/src/mangrove.ts index f679237a5..f98940ea1 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -505,10 +505,26 @@ class Mangrove { /** Return MgvToken instance, fetching data (decimals) from chain if needed. */ async token( - id: string, + symbolOrId: string, options?: MgvToken.ConstructorOptions, ): Promise { - return MgvToken.createToken(id, this, options); + return MgvToken.createTokenFromSymbolOrId(symbolOrId, this, options); + } + + /** Return MgvToken instance, fetching data (decimals) from chain if needed. */ + async tokenFromSymbol( + symbol: string, + options?: MgvToken.ConstructorOptions, + ): Promise { + return MgvToken.createTokenFromSymbol(symbol, this, options); + } + + /** Return MgvToken instance, fetching data (decimals) from chain if needed. */ + async tokenFromId( + tokenId: string, + options?: MgvToken.ConstructorOptions, + ): Promise { + return MgvToken.createTokenFromId(tokenId, this, options); } async tokenFromAddress(address: string): Promise { diff --git a/src/mgvtoken.ts b/src/mgvtoken.ts index da9c21c9c..9b3001fdd 100644 --- a/src/mgvtoken.ts +++ b/src/mgvtoken.ts @@ -85,7 +85,20 @@ class MgvToken { } /** Create a MgvToken instance, fetching data (decimals) from chain if needed. */ - static async createToken( + static async createTokenFromSymbolOrId( + symbolOrId: string, + mgv: Mangrove, + options?: MgvToken.ConstructorOptions, + ): Promise { + if (configuration.tokens.isTokenIdRegistered(symbolOrId)) { + return this.createTokenFromId(symbolOrId, mgv, options); + } else { + return this.createTokenFromSymbol(symbolOrId, mgv, options); + } + } + + /** Create a MgvToken instance, fetching data (decimals) from chain if needed. */ + static async createTokenFromId( id: string, mgv: Mangrove, options?: MgvToken.ConstructorOptions, @@ -99,6 +112,21 @@ class MgvToken { return new MgvToken(id, mgv, options); } + /** Create a MgvToken instance, fetching data (decimals) from chain if needed. */ + static async createTokenFromSymbol( + symbol: string, + mgv: Mangrove, + options?: MgvToken.ConstructorOptions, + ): Promise { + const id = + configuration.tokens.getDefaultIdForSymbolOnNetwork( + symbol, + mgv.network.name, + ) ?? symbol; + + return this.createTokenFromId(id, mgv, { ...options, symbol }); + } + static async createTokenFromAddress( address: string, mgv: Mangrove, @@ -111,7 +139,7 @@ class MgvToken { const symbol = await contract.callStatic.symbol(); const id = symbol ?? address; - return this.createToken(id, mgv, { + return this.createTokenFromId(id, mgv, { address, symbol, }); From c4643335c7b2964ad8228cf2c1c3a60c342c9c20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 15:57:29 +0100 Subject: [PATCH 09/22] feat!: Replace getNameFromAddress with getTokenIdFromAddress `Mangrove.getNameFromAddress` has been removed: It was ambiguous (multiple names could be registered) and only used for resolving tokens which can now be done with the new `configuration.tokens.getTokenIdFromAddress()` function. --- CHANGELOG.md | 1 + src/configuration.ts | 48 +++++++++---------- src/kandelStrategies.ts | 11 ++++- src/mangrove.ts | 32 +++---------- test/integration/mangrove.integration.test.ts | 9 ---- 5 files changed, 40 insertions(+), 61 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 21e12824b..f1e9805fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - feat!: Remove `Mangrove.tokenFromConfig`. Use `Mangrove.token` instead. - feat!/fix: Token `name` was misused: Sometimes it was assumed to be a symbol and sometimes an ID. It has therefore been replaced by `id` and `symbol` in all relevant places. Configuration files have been converted to use the token instance ID's from the context-addresses package to avoid ambiguity among (1) different tokens with the same symbol and (2) multiple token instances such as `USDC` (Circle issued) and `USDC.e` (bridged from Ethereum). - Default token IDs can be registered for a symbol and network. And if there is only one ID for a given symbol on a network, it will be considered the default. `Mangrove.token()` will create an instance of the default token ID if found. +- feat!: `Mangrove.getNameFromAddress` has been removed: It was ambiguous (multiple names could be registered) and only used for resolving tokens which can now be done with the new `configuration.tokens.getTokenIdFromAddress()` function. # 2.0.0-4 diff --git a/src/configuration.ts b/src/configuration.ts index 3bd3e8185..1129bc8d7 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -226,30 +226,6 @@ export const addressesConfiguration = { } } }, - - /** - * Gets the name of an address on the current network. - * - * Note that this reads from the static `Mangrove` address registry which is shared across instances of this class. - */ - getNameFromAddress: ( - address: string, - network: string, - ): string | undefined => { - const networkAddresses = config.addressesByNetwork[network]; - address = ethers.utils.getAddress(address); // normalize - - if (networkAddresses) { - for (const [name, candidateAddress] of Object.entries( - networkAddresses, - ) as any) { - if (candidateAddress == address) { - return name; - } - } - } - return undefined; - }, }; /// TOKENS @@ -312,6 +288,30 @@ export const tokensConfiguration = { return foundTokenId; }, + /** + * Gets the token ID of an address on the given network. + */ + getTokenIdFromAddress: ( + address: string, + network: string, + ): tokenId | undefined => { + const networkAddresses = config.addressesByNetwork[network]; + address = ethers.utils.getAddress(address); // normalize + + if (networkAddresses) { + for (const [name, candidateAddress] of Object.entries( + networkAddresses, + ) as any) { + if (candidateAddress == address) { + if (tokensConfiguration.isTokenIdRegistered(name)) { + return name; + } + } + } + } + return undefined; + }, + /** * Read decimals for `tokenId`. * To read decimals directly onchain, use `fetchDecimals`. diff --git a/src/kandelStrategies.ts b/src/kandelStrategies.ts index 69e8fbf27..dc897ca52 100644 --- a/src/kandelStrategies.ts +++ b/src/kandelStrategies.ts @@ -10,6 +10,7 @@ import GeometricKandelLib from "./kandel/geometricKandel/geometricKandelLib"; import GeometricKandelInstance from "./kandel/geometricKandel/geometricKandelInstance"; import GeometricKandelDistributionHelper from "./kandel/geometricKandel/geometricKandelDistributionHelper"; import GeneralKandelDistributionHelper from "./kandel/generalKandelDistributionHelper"; +import configuration from "./configuration"; // eslint-disable-next-line @typescript-eslint/no-namespace namespace KandelStrategies {} @@ -58,11 +59,17 @@ class KandelStrategies { const market = params.market ?? ((baseAddress: string, quoteAddress: string, tickSpacing: Bigish) => { - const baseToken = this.mgv.getNameFromAddress(baseAddress); + const baseToken = configuration.tokens.getTokenIdFromAddress( + baseAddress, + this.mgv.network.name, + ); if (!baseToken) { throw new Error(`Unknown token at address ${baseAddress}`); } - const quoteToken = this.mgv.getNameFromAddress(quoteAddress); + const quoteToken = configuration.tokens.getTokenIdFromAddress( + quoteAddress, + this.mgv.network.name, + ); if (!quoteToken) { throw new Error(`Unknown token at address ${quoteAddress}`); } diff --git a/src/mangrove.ts b/src/mangrove.ts index f98940ea1..d19ad3dde 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -556,18 +556,6 @@ class Mangrove { ); } - /** - * Gets the name of an address on the current network. - * - * Note that this reads from the static `Mangrove` address registry which is shared across instances of this class. - */ - getNameFromAddress(address: string): string | undefined { - return configuration.addresses.getNameFromAddress( - address, - this.network.name || "mainnet", - ); - } - /** Convert public token amount to internal token representation. * * For convenience, has a static and an instance version. @@ -824,18 +812,6 @@ class Mangrove { configuration.addresses.setAddress(name, address, network); } - /** - * Gets the name of an address on the given network. - * - * Note that this reads from the static `Mangrove` address registry which is shared across instances of this class. - */ - static getNameFromAddress( - address: string, - network: string, - ): string | undefined { - return configuration.addresses.getNameFromAddress(address, network); - } - /** * Setup dev node necessary contracts if needed, register dev Multicall2 * address, listen to future additions (a script external to mangrove.js may @@ -953,8 +929,12 @@ class Mangrove { // format return value return raw.markets.map(([tkn0, tkn1, tickSpacing]) => { // Use internal mgv name if defined; otherwise use the symbol. - const tkn0Id = this.getNameFromAddress(tkn0) ?? data[tkn0].symbol; - const tkn1Id = this.getNameFromAddress(tkn1) ?? data[tkn1].symbol; + const tkn0Id = + configuration.tokens.getTokenIdFromAddress(tkn0, this.network.name) ?? + data[tkn0].symbol; + const tkn1Id = + configuration.tokens.getTokenIdFromAddress(tkn1, this.network.name) ?? + data[tkn1].symbol; const { baseId, quoteId } = this.toBaseQuoteByCashness(tkn0Id, tkn1Id); const [base, quote] = baseId === tkn0Id ? [tkn0, tkn1] : [tkn1, tkn0]; diff --git a/test/integration/mangrove.integration.test.ts b/test/integration/mangrove.integration.test.ts index 9014fcb63..621b01a8d 100644 --- a/test/integration/mangrove.integration.test.ts +++ b/test/integration/mangrove.integration.test.ts @@ -89,15 +89,6 @@ describe("Mangrove integration tests suite", function () { ); }); - it("has reverse lookup of address", function () { - const address = mgv.getAddress("TokenA"); - assert.equal(mgv.getNameFromAddress(address), "TokenA"); - assert.equal( - mgv.getNameFromAddress("0xdeaddeaddeaddaeddeaddeaddeaddeaddeaddead"), - null, - ); - }); - it("gets correct market info and updates with cashness", async function () { await mgv.readerContract.updateMarket({ tkn0: mgv.getAddress("TokenA"), From 71ff31d0d0e670c97c0a44f1656712ef5ed63839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 16:10:17 +0100 Subject: [PATCH 10/22] feat!: openMarkets uses MgvToken instead of a bespoke struct --- CHANGELOG.md | 1 + src/mangrove.ts | 84 +++++++++---------- test/integration/mangrove.integration.test.ts | 16 ++-- 3 files changed, 54 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1e9805fe..311e66ec2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - feat!/fix: Token `name` was misused: Sometimes it was assumed to be a symbol and sometimes an ID. It has therefore been replaced by `id` and `symbol` in all relevant places. Configuration files have been converted to use the token instance ID's from the context-addresses package to avoid ambiguity among (1) different tokens with the same symbol and (2) multiple token instances such as `USDC` (Circle issued) and `USDC.e` (bridged from Ethereum). - Default token IDs can be registered for a symbol and network. And if there is only one ID for a given symbol on a network, it will be considered the default. `Mangrove.token()` will create an instance of the default token ID if found. - feat!: `Mangrove.getNameFromAddress` has been removed: It was ambiguous (multiple names could be registered) and only used for resolving tokens which can now be done with the new `configuration.tokens.getTokenIdFromAddress()` function. +- feat!: The `Mangrove.openMarkets` function now uses `MgvToken` instead of a bespoke token data struct. # 2.0.0-4 diff --git a/src/mangrove.ts b/src/mangrove.ts index d19ad3dde..0657c2c6a 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -89,8 +89,8 @@ namespace Mangrove { }; export type OpenMarketInfo = { - base: { id: string; address: string; symbol: string; decimals: number }; - quote: { id: string; address: string; symbol: string; decimals: number }; + base: MgvToken; + quote: MgvToken; tickSpacing: ethers.BigNumber; asksConfig?: LocalConfig; bidsConfig?: LocalConfig; @@ -927,46 +927,46 @@ class Mangrove { tryDecodeSymbol(returnData.slice(addresses.length), "symbol"); // format return value - return raw.markets.map(([tkn0, tkn1, tickSpacing]) => { - // Use internal mgv name if defined; otherwise use the symbol. - const tkn0Id = - configuration.tokens.getTokenIdFromAddress(tkn0, this.network.name) ?? - data[tkn0].symbol; - const tkn1Id = - configuration.tokens.getTokenIdFromAddress(tkn1, this.network.name) ?? - data[tkn1].symbol; - - const { baseId, quoteId } = this.toBaseQuoteByCashness(tkn0Id, tkn1Id); - const [base, quote] = baseId === tkn0Id ? [tkn0, tkn1] : [tkn1, tkn0]; - - return { - base: { - id: baseId, - address: base, - symbol: data[base].symbol, - decimals: data[base].decimals, - }, - quote: { - id: quoteId, - address: quote, - symbol: data[quote].symbol, - decimals: data[quote].decimals, - }, - tickSpacing: tickSpacing, - asksConfig: params.configs - ? Semibook.rawLocalConfigToLocalConfig( - data[base].configs[quote], - data[base].decimals, - ) - : undefined, - bidsConfig: params.configs - ? Semibook.rawLocalConfigToLocalConfig( - data[quote].configs[base], - data[quote].decimals, - ) - : undefined, - }; - }); + return await Promise.all( + raw.markets.map(async ([tkn0, tkn1, tickSpacing]) => { + // Use internal mgv name if defined; otherwise use the symbol. + const tkn0Id = + configuration.tokens.getTokenIdFromAddress(tkn0, this.network.name) ?? + data[tkn0].symbol; + const tkn1Id = + configuration.tokens.getTokenIdFromAddress(tkn1, this.network.name) ?? + data[tkn1].symbol; + + const { baseId, quoteId } = this.toBaseQuoteByCashness(tkn0Id, tkn1Id); + const [base, quote] = baseId === tkn0Id ? [tkn0, tkn1] : [tkn1, tkn0]; + + return { + base: await this.tokenFromId(baseId, { + address: base, + symbol: data[base].symbol, + decimals: data[base].decimals, + }), + quote: await this.tokenFromId(quoteId, { + address: quote, + symbol: data[quote].symbol, + decimals: data[quote].decimals, + }), + tickSpacing: tickSpacing, + asksConfig: params.configs + ? Semibook.rawLocalConfigToLocalConfig( + data[base].configs[quote], + data[base].decimals, + ) + : undefined, + bidsConfig: params.configs + ? Semibook.rawLocalConfigToLocalConfig( + data[quote].configs[base], + data[quote].decimals, + ) + : undefined, + }; + }), + ); } /** diff --git a/test/integration/mangrove.integration.test.ts b/test/integration/mangrove.integration.test.ts index 621b01a8d..7b637efd9 100644 --- a/test/integration/mangrove.integration.test.ts +++ b/test/integration/mangrove.integration.test.ts @@ -5,7 +5,7 @@ import * as mgvTestUtil from "../../src/util/test/mgvIntegrationTestUtil"; import { toWei } from "../util/helpers"; import { serverType } from "../../src/util/node"; -import { Mangrove } from "../../src"; +import { Mangrove, MgvToken } from "../../src"; import { configuration } from "../../src/configuration"; import { Big } from "big.js"; @@ -108,14 +108,20 @@ describe("Mangrove integration tests suite", function () { id: "TokenB", symbol: "TokenB", }; - assert.deepEqual(marketData[0].base, tokenAData); - assert.deepEqual(marketData[0].quote, tokenBData); + const tokenToData = (token: MgvToken) => ({ + address: token.address, + decimals: token.decimals, + id: token.id, + symbol: token.symbol, + }); + assert.deepEqual(tokenToData(marketData[0].base), tokenAData); + assert.deepEqual(tokenToData(marketData[0].quote), tokenBData); configuration.tokens.setCashness("TokenA", 1000000); marketData = await mgv.openMarketsData(); - assert.deepEqual(marketData[0].base, tokenBData); - assert.deepEqual(marketData[0].quote, tokenAData); + assert.deepEqual(tokenToData(marketData[0].base), tokenBData); + assert.deepEqual(tokenToData(marketData[0].quote), tokenAData); }); }); describe("node utils", () => { From 67ce83d0a8f14480469baf5c36383e23934fa20e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 16:18:10 +0100 Subject: [PATCH 11/22] feat!: Rename MgvToken to Token --- CHANGELOG.md | 9 +++-- src/index.ts | 4 +- src/kandel/coreKandelInstance.ts | 2 +- src/mangrove.ts | 36 ++++++++--------- src/market.ts | 26 ++++++------ src/semibook.ts | 10 ++--- src/{mgvtoken.ts => token.ts} | 40 +++++++++---------- src/util/test/mgvIntegrationTestUtil.ts | 10 ++--- src/util/tradeEventManagement.ts | 18 +++------ test/integration/mangrove.integration.test.ts | 4 +- .../restingOrder.integration.test.ts | 6 +-- test/unit/trade.unit.test.ts | 34 ++++++++-------- test/unit/tradeEventManagement.unit.test.ts | 24 +++++------ test/util/helpers.ts | 14 +++---- 14 files changed, 113 insertions(+), 124 deletions(-) rename src/{mgvtoken.ts => token.ts} (92%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 311e66ec2..7b31d8b55 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,15 +7,16 @@ # 2.0.0-5 - feat: Add usage of Geometric Kandel's call-data-reducing function -- feat!: Removed `configuration.tokens.fetchDecimalsFromAddress`. Instead, use `MgvToken.createTokenFromAddress` and read the decimals from that token. -- feat!: `Mangrove.toUnits|fromUnits` no longer accepts a token name/symbol as this was ambiguous. Instead, use `MgvToken.createToken` and call `toUnits|fromUnits` on that. -- feat!: Static token configuration getters and setters have been removed from `Mangrove` and `MgvToken`. Instead, use the methods on `configuration.token`. +- feat!: `MgvToken` has been renamed to `Token`. +- feat!: Removed `configuration.tokens.fetchDecimalsFromAddress`. Instead, use `Token.createTokenFromAddress` and read the decimals from that token. +- feat!: `Mangrove.toUnits|fromUnits` no longer accepts a token name/symbol as this was ambiguous. Instead, use `Token.createToken` and call `toUnits|fromUnits` on that. +- feat!: Static token configuration getters and setters have been removed from `Mangrove` and `Token`. Instead, use the methods on `configuration.token`. - feat!: `Mangrove.getTokenAndAddress` has been removed. Instead, use `Mangrove.tokenFromAddress` and read the address from there. - feat!: Remove `Mangrove.tokenFromConfig`. Use `Mangrove.token` instead. - feat!/fix: Token `name` was misused: Sometimes it was assumed to be a symbol and sometimes an ID. It has therefore been replaced by `id` and `symbol` in all relevant places. Configuration files have been converted to use the token instance ID's from the context-addresses package to avoid ambiguity among (1) different tokens with the same symbol and (2) multiple token instances such as `USDC` (Circle issued) and `USDC.e` (bridged from Ethereum). - Default token IDs can be registered for a symbol and network. And if there is only one ID for a given symbol on a network, it will be considered the default. `Mangrove.token()` will create an instance of the default token ID if found. - feat!: `Mangrove.getNameFromAddress` has been removed: It was ambiguous (multiple names could be registered) and only used for resolving tokens which can now be done with the new `configuration.tokens.getTokenIdFromAddress()` function. -- feat!: The `Mangrove.openMarkets` function now uses `MgvToken` instead of a bespoke token data struct. +- feat!: The `Mangrove.openMarkets` function now uses `Token` instead of a bespoke token data struct. # 2.0.0-4 diff --git a/src/index.ts b/src/index.ts index 5de56c6d2..8299e0c15 100644 --- a/src/index.ts +++ b/src/index.ts @@ -11,7 +11,7 @@ import Mangrove from "./mangrove"; import Market from "./market"; import Semibook from "./semibook"; import OfferLogic from "./offerLogic"; -import MgvToken from "./mgvtoken"; +import Token from "./token"; import LiquidityProvider from "./liquidityProvider"; import KandelStrategies from "./kandelStrategies"; import * as mgvTestUtil from "./util/test/mgvIntegrationTestUtil"; @@ -39,7 +39,7 @@ export { Mangrove, Market, Semibook, - MgvToken, + Token, OfferLogic, LiquidityProvider, mgvTestUtil, diff --git a/src/kandel/coreKandelInstance.ts b/src/kandel/coreKandelInstance.ts index d5dc0e56a..a0defaaad 100644 --- a/src/kandel/coreKandelInstance.ts +++ b/src/kandel/coreKandelInstance.ts @@ -6,7 +6,7 @@ import * as KandelTypes from "../types/typechain/GeometricKandel"; import Big from "big.js"; import Market from "../market"; import UnitCalculations from "../util/unitCalculations"; -import { ApproveArgs } from "../mgvtoken"; +import { ApproveArgs } from "../token"; import KandelDistributionHelper, { OffersWithGives, } from "./kandelDistributionHelper"; diff --git a/src/mangrove.ts b/src/mangrove.ts index 0657c2c6a..0fa60d6ff 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -1,4 +1,4 @@ -import { LiquidityProvider, Market, MgvToken, OfferLogic, Semibook } from "."; +import { LiquidityProvider, Market, Token, OfferLogic, Semibook } from "."; import configuration, { Configuration as MangroveJsConfiguration, PartialConfiguration as PartialMangroveJsConfiguration, @@ -8,7 +8,7 @@ import DevNode from "./util/devNode"; import { Bigish, Provider, Signer, typechain } from "./types"; import { logdataLimiter, logger } from "./util/logger"; import { TypedDataSigner } from "@ethersproject/abstract-signer"; -import { ApproveArgs } from "./mgvtoken"; +import { ApproveArgs } from "./token"; import Big from "big.js"; // Configure big.js global constructor @@ -89,8 +89,8 @@ namespace Mangrove { }; export type OpenMarketInfo = { - base: MgvToken; - quote: MgvToken; + base: Token; + quote: Token; tickSpacing: ethers.BigNumber; asksConfig?: LocalConfig; bidsConfig?: LocalConfig; @@ -503,32 +503,32 @@ class Mangrove { } } - /** Return MgvToken instance, fetching data (decimals) from chain if needed. */ + /** Return Token instance, fetching data (decimals) from chain if needed. */ async token( symbolOrId: string, - options?: MgvToken.ConstructorOptions, - ): Promise { - return MgvToken.createTokenFromSymbolOrId(symbolOrId, this, options); + options?: Token.ConstructorOptions, + ): Promise { + return Token.createTokenFromSymbolOrId(symbolOrId, this, options); } - /** Return MgvToken instance, fetching data (decimals) from chain if needed. */ + /** Return Token instance, fetching data (decimals) from chain if needed. */ async tokenFromSymbol( symbol: string, - options?: MgvToken.ConstructorOptions, - ): Promise { - return MgvToken.createTokenFromSymbol(symbol, this, options); + options?: Token.ConstructorOptions, + ): Promise { + return Token.createTokenFromSymbol(symbol, this, options); } - /** Return MgvToken instance, fetching data (decimals) from chain if needed. */ + /** Return Token instance, fetching data (decimals) from chain if needed. */ async tokenFromId( tokenId: string, - options?: MgvToken.ConstructorOptions, - ): Promise { - return MgvToken.createTokenFromId(tokenId, this, options); + options?: Token.ConstructorOptions, + ): Promise { + return Token.createTokenFromId(tokenId, this, options); } - async tokenFromAddress(address: string): Promise { - return MgvToken.createTokenFromAddress(address, this); + async tokenFromAddress(address: string): Promise { + return Token.createTokenFromAddress(address, this); } /** diff --git a/src/market.ts b/src/market.ts index 4fa9222d4..85821e50b 100644 --- a/src/market.ts +++ b/src/market.ts @@ -1,7 +1,7 @@ import * as ethers from "ethers"; import { BigNumber } from "ethers"; // syntactic sugar import Mangrove from "./mangrove"; -import MgvToken from "./mgvtoken"; +import Token from "./token"; import Semibook from "./semibook"; import { Bigish, typechain } from "./types"; import Trade from "./util/trade"; @@ -291,8 +291,8 @@ namespace Market { */ class Market { mgv: Mangrove; - base: MgvToken; - quote: MgvToken; + base: Token; + quote: Token; tickSpacing: BigNumber; /** The OLKey for the base, quote offer list */ olKeyBaseQuote: OLKeyStruct; @@ -357,8 +357,8 @@ class Market { */ private constructor(params: { mgv: Mangrove; - base: MgvToken; - quote: MgvToken; + base: Token; + quote: Token; tickSpacing: BigNumber; }) { if (!canConstructMarket) { @@ -636,8 +636,8 @@ class Market { action: "buy" | "sell", data: Omit, ): Promise { - let outbound_tkn: MgvToken; - let inbound_tkn: MgvToken; + let outbound_tkn: Token; + let inbound_tkn: Token; if (action === "buy") { outbound_tkn = this.base; @@ -962,8 +962,8 @@ class Market { /** Determine which token will be Mangrove's outbound/inbound depending on whether you're working with bids or asks. */ getOutboundInbound(ba: Market.BA): { - outbound_tkn: MgvToken; - inbound_tkn: MgvToken; + outbound_tkn: Token; + inbound_tkn: Token; } { return Market.getOutboundInbound(ba, this.base, this.quote); } @@ -971,11 +971,11 @@ class Market { /** Determine which token will be Mangrove's outbound/inbound depending on whether you're working with bids or asks. */ static getOutboundInbound( ba: Market.BA, - base: MgvToken, - quote: MgvToken, + base: Token, + quote: Token, ): { - outbound_tkn: MgvToken; - inbound_tkn: MgvToken; + outbound_tkn: Token; + inbound_tkn: Token; } { return { outbound_tkn: ba === "asks" ? base : quote, diff --git a/src/semibook.ts b/src/semibook.ts index 6f4c3581c..705a0d6a2 100644 --- a/src/semibook.ts +++ b/src/semibook.ts @@ -2,7 +2,7 @@ import { Log } from "@ethersproject/providers"; import { Big } from "big.js"; import { BigNumber, ethers } from "ethers"; import clone from "just-clone"; -import { Mangrove, Market, MgvToken } from "."; +import { Mangrove, Market, Token } from "."; import { BlockManager, @@ -918,8 +918,8 @@ class Semibook } & OfferFailEvent, removedOffer: Market.Offer | undefined, state: Semibook.State, - outbound_tkn: MgvToken, - inbound_tkn: MgvToken, + outbound_tkn: Token, + inbound_tkn: Token, log: Log, ) { const id = Semibook.rawIdToId(event.args.id); @@ -952,8 +952,8 @@ class Semibook } & OfferSuccessEvent, removedOffer: Market.Offer | undefined, state: Semibook.State, - outbound_tkn: MgvToken, - inbound_tkn: MgvToken, + outbound_tkn: Token, + inbound_tkn: Token, log: Log, ) { const id = Semibook.rawIdToId(event.args.id); diff --git a/src/mgvtoken.ts b/src/token.ts similarity index 92% rename from src/mgvtoken.ts rename to src/token.ts index 9b3001fdd..de68fe711 100644 --- a/src/mgvtoken.ts +++ b/src/token.ts @@ -7,7 +7,7 @@ import UnitCalculations from "./util/unitCalculations"; import configuration from "./configuration"; // eslint-disable-next-line @typescript-eslint/no-namespace -namespace MgvToken { +namespace Token { export type ConstructorOptions = { address?: string; decimals?: number; @@ -51,7 +51,7 @@ function convertToApproveArgs(arg: ApproveArgs): { return amount === undefined ? { overrides } : { amount, overrides }; } -class MgvToken { +class Token { mgv: Mangrove; // ID which should be unique within a network. // Typically the id from the context-addresses package. @@ -64,14 +64,10 @@ class MgvToken { decimals: number; // Using most complete interface (burn, mint, blacklist etc.) to be able to access non standard ERC calls using ethers.js contract: typechain.TestToken; - constructor( - id: string, - mgv: Mangrove, - options?: MgvToken.ConstructorOptions, - ) { + constructor(id: string, mgv: Mangrove, options?: Token.ConstructorOptions) { this.mgv = mgv; this.id = id; - MgvToken.#applyOptions(id, mgv, options); + Token.#applyOptions(id, mgv, options); this.address = this.mgv.getAddress(this.id); this.decimals = configuration.tokens.getDecimalsOrFail(this.id); @@ -84,12 +80,12 @@ class MgvToken { ); } - /** Create a MgvToken instance, fetching data (decimals) from chain if needed. */ + /** Create a Token instance, fetching data (decimals) from chain if needed. */ static async createTokenFromSymbolOrId( symbolOrId: string, mgv: Mangrove, - options?: MgvToken.ConstructorOptions, - ): Promise { + options?: Token.ConstructorOptions, + ): Promise { if (configuration.tokens.isTokenIdRegistered(symbolOrId)) { return this.createTokenFromId(symbolOrId, mgv, options); } else { @@ -97,27 +93,27 @@ class MgvToken { } } - /** Create a MgvToken instance, fetching data (decimals) from chain if needed. */ + /** Create a Token instance, fetching data (decimals) from chain if needed. */ static async createTokenFromId( id: string, mgv: Mangrove, - options?: MgvToken.ConstructorOptions, - ): Promise { - MgvToken.#applyOptions(id, mgv, options); + options?: Token.ConstructorOptions, + ): Promise { + Token.#applyOptions(id, mgv, options); // Ensure decimals and symbol are known before token construction as it will otherwise fail. await configuration.tokens.getOrFetchDecimals(id, mgv.provider); await configuration.tokens.getOrFetchSymbol(id, mgv.provider); - return new MgvToken(id, mgv, options); + return new Token(id, mgv, options); } - /** Create a MgvToken instance, fetching data (decimals) from chain if needed. */ + /** Create a Token instance, fetching data (decimals) from chain if needed. */ static async createTokenFromSymbol( symbol: string, mgv: Mangrove, - options?: MgvToken.ConstructorOptions, - ): Promise { + options?: Token.ConstructorOptions, + ): Promise { const id = configuration.tokens.getDefaultIdForSymbolOnNetwork( symbol, @@ -130,7 +126,7 @@ class MgvToken { static async createTokenFromAddress( address: string, mgv: Mangrove, - ): Promise { + ): Promise { const contract = typechain.TestToken__factory.connect( address, mgv.provider, @@ -148,7 +144,7 @@ class MgvToken { static #applyOptions( id: string, mgv: Mangrove, - options?: MgvToken.ConstructorOptions, + options?: Token.ConstructorOptions, ) { if (options === undefined) { return; @@ -356,4 +352,4 @@ class MgvToken { } } -export default MgvToken; +export default Token; diff --git a/src/util/test/mgvIntegrationTestUtil.ts b/src/util/test/mgvIntegrationTestUtil.ts index 8ea970fea..3c5d51302 100644 --- a/src/util/test/mgvIntegrationTestUtil.ts +++ b/src/util/test/mgvIntegrationTestUtil.ts @@ -1,7 +1,7 @@ // TODO do not distribute in browser version // Utility functions for writing integration tests against Mangrove. import { BigNumber, ContractTransaction, ethers } from "ethers"; -import { Market, MgvToken, Mangrove } from "../.."; +import { Market, Token, Mangrove } from "../.."; import { typechain } from "../../types"; import { Provider, TransactionReceipt } from "@ethersproject/abstract-provider"; @@ -218,8 +218,8 @@ export const getTokens = ( market: Market, ba: Market.BA, ): { - inboundToken: MgvToken; - outboundToken: MgvToken; + inboundToken: Token; + outboundToken: Token; } => { return { inboundToken: ba === "asks" ? market.quote : market.base, @@ -467,7 +467,7 @@ export const setMgvGasPrice = async ( }; const rawMint = async ( - token: MgvToken, + token: Token, receiverAddress: string, internalAmount: ethers.BigNumberish, ): Promise => { @@ -496,7 +496,7 @@ const rawMint = async ( }; export const mint = async ( - token: MgvToken, + token: Token, receiver: Account, amount: number, ): Promise => { diff --git a/src/util/tradeEventManagement.ts b/src/util/tradeEventManagement.ts index f0f7e0b3c..661e3123d 100644 --- a/src/util/tradeEventManagement.ts +++ b/src/util/tradeEventManagement.ts @@ -4,7 +4,7 @@ import { BaseContract, BigNumber } from "ethers"; import { LogDescription } from "ethers/lib/utils"; import Market from "../market"; import Semibook from "../semibook"; -import MgvToken from "../mgvtoken"; +import Token from "../token"; import { OfferFailEvent, OfferFailWithPosthookDataEvent, @@ -88,7 +88,7 @@ class TradeEventManagement { restingOrderId?: number; }; }, - fillToken: MgvToken, + fillToken: Token, ): Market.OrderSummary { if ( (!event.args.tick && !event.args.maxTick) || @@ -108,11 +108,7 @@ class TradeEventManagement { }; } - createSuccessFromEvent( - evt: OfferSuccessEvent, - got: MgvToken, - gave: MgvToken, - ) { + createSuccessFromEvent(evt: OfferSuccessEvent, got: Token, gave: Token) { const success = { offerId: evt.args.id.toNumber(), got: got.fromUnits(evt.args.takerWants), @@ -121,11 +117,7 @@ class TradeEventManagement { return success; } - createTradeFailureFromEvent( - evt: OfferFailEvent, - got: MgvToken, - gave: MgvToken, - ) { + createTradeFailureFromEvent(evt: OfferFailEvent, got: Token, gave: Token) { const tradeFailure = { offerId: evt.args.id.toNumber(), reason: evt.args.mgvData, @@ -188,7 +180,7 @@ class TradeEventManagement { createSummaryFromOrderSummaryEvent( evt: MangroveOrderStartEvent, - fillToken: MgvToken, + fillToken: Token, ): Market.OrderSummary { return this.createSummaryFromEvent( { diff --git a/test/integration/mangrove.integration.test.ts b/test/integration/mangrove.integration.test.ts index 7b637efd9..cbe1dcf4f 100644 --- a/test/integration/mangrove.integration.test.ts +++ b/test/integration/mangrove.integration.test.ts @@ -5,7 +5,7 @@ import * as mgvTestUtil from "../../src/util/test/mgvIntegrationTestUtil"; import { toWei } from "../util/helpers"; import { serverType } from "../../src/util/node"; -import { Mangrove, MgvToken } from "../../src"; +import { Mangrove, Token } from "../../src"; import { configuration } from "../../src/configuration"; import { Big } from "big.js"; @@ -108,7 +108,7 @@ describe("Mangrove integration tests suite", function () { id: "TokenB", symbol: "TokenB", }; - const tokenToData = (token: MgvToken) => ({ + const tokenToData = (token: Token) => ({ address: token.address, decimals: token.decimals, id: token.id, diff --git a/test/integration/restingOrder.integration.test.ts b/test/integration/restingOrder.integration.test.ts index 2baf1f0f8..99ee7fa17 100644 --- a/test/integration/restingOrder.integration.test.ts +++ b/test/integration/restingOrder.integration.test.ts @@ -8,7 +8,7 @@ import { LiquidityProvider, Mangrove, Market, - MgvToken, + Token, OfferLogic, mgvTestUtil, } from "../../src"; @@ -26,8 +26,8 @@ Big.prototype[Symbol.for("nodejs.util.inspect.custom")] = function () { describe("RestingOrder", () => { let mgv: Mangrove; - let tokenA: MgvToken; - let tokenB: MgvToken; + let tokenA: Token; + let tokenB: Token; let orderLogic: OfferLogic; let orderLP: LiquidityProvider; let router: AbstractRouter; diff --git a/test/unit/trade.unit.test.ts b/test/unit/trade.unit.test.ts index d79fcadb8..1098e7230 100644 --- a/test/unit/trade.unit.test.ts +++ b/test/unit/trade.unit.test.ts @@ -12,7 +12,7 @@ import { verify, when, } from "ts-mockito"; -import { Market, MgvToken } from "../../src"; +import { Market, Token } from "../../src"; import { Bigish } from "../../src/types"; import Trade from "../../src/util/trade"; import { TickLib } from "../../src/util/coreCalculations/TickLib"; @@ -31,8 +31,8 @@ describe("Trade unit tests suite", () => { volume: 30, slippage: slippage, }; - const baseToken = mock(MgvToken); - const quoteToken = mock(MgvToken); + const baseToken = mock(Token); + const quoteToken = mock(Token); when(baseToken.toUnits(anything())).thenReturn( BigNumber.from(params.volume), ); @@ -88,8 +88,8 @@ describe("Trade unit tests suite", () => { total: 30, slippage: slippage, }; - const baseToken = mock(MgvToken); - const quoteToken = mock(MgvToken); + const baseToken = mock(Token); + const quoteToken = mock(Token); when(baseToken.toUnits(anything())).thenReturn( BigNumber.from(Big(params.total).div(price).toFixed(0)), ); @@ -136,8 +136,8 @@ describe("Trade unit tests suite", () => { fillVolume: 30, slippage: slippage, }; - const baseToken = mock(MgvToken); - const quoteToken = mock(MgvToken); + const baseToken = mock(Token); + const quoteToken = mock(Token); when(baseToken.toUnits(anything())).thenReturn( BigNumber.from(Big(params.fillVolume).toFixed(0)), ); @@ -180,8 +180,8 @@ describe("Trade unit tests suite", () => { fillWants: false, slippage: slippage, }; - const baseToken = mock(MgvToken); - const quoteToken = mock(MgvToken); + const baseToken = mock(Token); + const quoteToken = mock(Token); when(baseToken.toUnits(anything())).thenReturn( BigNumber.from(Big(params.fillVolume).toFixed(0)), ); @@ -272,8 +272,8 @@ describe("Trade unit tests suite", () => { volume: 30, slippage: slippage, }; - const baseToken = mock(MgvToken); - const quoteToken = mock(MgvToken); + const baseToken = mock(Token); + const quoteToken = mock(Token); when(baseToken.toUnits(anything())).thenReturn( BigNumber.from(params.volume), ); @@ -320,8 +320,8 @@ describe("Trade unit tests suite", () => { total: 30, slippage: slippage, }; - const baseToken = mock(MgvToken); - const quoteToken = mock(MgvToken); + const baseToken = mock(Token); + const quoteToken = mock(Token); when(quoteToken.toUnits(anything())).thenReturn( BigNumber.from(params.total), ); @@ -372,8 +372,8 @@ describe("Trade unit tests suite", () => { tick: 30, slippage: slippage, }; - const baseToken = mock(MgvToken); - const quoteToken = mock(MgvToken); + const baseToken = mock(Token); + const quoteToken = mock(Token); when(quoteToken.toUnits(anything())).thenReturn( BigNumber.from(params.fillVolume), ); @@ -415,8 +415,8 @@ describe("Trade unit tests suite", () => { fillWants: true, slippage: slippage, }; - const baseToken = mock(MgvToken); - const quoteToken = mock(MgvToken); + const baseToken = mock(Token); + const quoteToken = mock(Token); when(quoteToken.toUnits(anything())).thenReturn( BigNumber.from(params.fillVolume), ); diff --git a/test/unit/tradeEventManagement.unit.test.ts b/test/unit/tradeEventManagement.unit.test.ts index d420dc275..4b1b1e076 100644 --- a/test/unit/tradeEventManagement.unit.test.ts +++ b/test/unit/tradeEventManagement.unit.test.ts @@ -12,7 +12,7 @@ import { verify, when, } from "ts-mockito"; -import { Semibook, Market, MgvToken } from "../../src"; +import { Semibook, Market, Token } from "../../src"; import TradeEventManagement from "../../src/util/tradeEventManagement"; import UnitCalculations from "../../src/util/unitCalculations"; import { @@ -44,7 +44,7 @@ describe("TradeEventManagement unit tests suite", () => { const marketSide: Market.BA = "bids"; - const baseTokenMock = mock(MgvToken); + const baseTokenMock = mock(Token); const baseTokenDecimals: number = 3; when(baseTokenMock.decimals).thenReturn(baseTokenDecimals); @@ -54,7 +54,7 @@ describe("TradeEventManagement unit tests suite", () => { ); when(baseTokenMock.fromUnits(rawGives)).thenReturn(expectedGives); - const quoteTokenMock = mock(MgvToken); + const quoteTokenMock = mock(Token); const quoteTokenDecimals = 1; const tickPriceHelper = new TickPriceHelper(marketSide, { @@ -114,10 +114,10 @@ describe("TradeEventManagement unit tests suite", () => { const marketSide: Market.BA = "asks"; - const baseTokenMock = mock(MgvToken); + const baseTokenMock = mock(Token); const baseTokenDecimals: number = 3; - const quoteTokenMock = mock(MgvToken); + const quoteTokenMock = mock(Token); const quoteTokenDecimals = 1; const expectedGives = UnitCalculations.fromUnits( rawGives, @@ -195,7 +195,7 @@ describe("TradeEventManagement unit tests suite", () => { it("return summary with partialFill as true, when partialFill func returns true", async function () { //Arrange const tradeEventManagement = new TradeEventManagement(); - const mockedToken = mock(MgvToken); + const mockedToken = mock(Token); const token = instance(mockedToken); const evt: summaryEvent = { args: { @@ -227,7 +227,7 @@ describe("TradeEventManagement unit tests suite", () => { const tradeEventManagement = new TradeEventManagement(); const spyTradeEventManagement = spy(tradeEventManagement); - const mockedToken = mock(MgvToken); + const mockedToken = mock(Token); const token = instance(mockedToken); const event = instance(mock()); const summary: any = "summary"; @@ -253,7 +253,7 @@ describe("TradeEventManagement unit tests suite", () => { const tradeEventManagement = new TradeEventManagement(); const spyTradeEventManagement = spy(tradeEventManagement); const mockedEvent = mock(); - const mockedToken = mock(MgvToken); + const mockedToken = mock(Token); const token = instance(mockedToken); const event = instance(mockedEvent); const summary: Market.OrderSummary = { @@ -297,8 +297,8 @@ describe("TradeEventManagement unit tests suite", () => { takerWants: BigNumber.from(2), takerGives: BigNumber.from(3), }; - const gotToken = mock(MgvToken); - const gaveToken = mock(MgvToken); + const gotToken = mock(Token); + const gaveToken = mock(Token); const expectedGot = Big(args.takerWants.toNumber()); const expectedGave = Big(args.takerGives.toNumber()); @@ -333,8 +333,8 @@ describe("TradeEventManagement unit tests suite", () => { takerWants: BigNumber.from(2), takerGives: BigNumber.from(3), }; - const gotToken = mock(MgvToken); - const gaveToken = mock(MgvToken); + const gotToken = mock(Token); + const gaveToken = mock(Token); const expectedFailToDeliver = Big(args.takerWants.toNumber()); const expectedVolumeGiven = Big(args.takerGives.toNumber()); diff --git a/test/util/helpers.ts b/test/util/helpers.ts index 3f8c93940..74e6240d4 100644 --- a/test/util/helpers.ts +++ b/test/util/helpers.ts @@ -1,5 +1,5 @@ import { BigNumber, BigNumberish, ContractTransaction, utils } from "ethers"; -import Mangrove, { MgvToken } from "../../src"; +import Mangrove, { Token } from "../../src"; import { Bigish } from "../../src/types"; import Big from "big.js"; import assert from "assert"; @@ -96,17 +96,17 @@ export type OfferData = { async function getAmountAndAddress( mgv: Mangrove, - token: string | MgvToken, + token: string | Token, amount: string, ) { - const mgvToken = await getAddress(token, mgv); - return { address: mgvToken.address, value: mgvToken.toUnits(amount) }; + const Token = await getAddress(token, mgv); + return { address: Token.address, value: Token.toUnits(amount) }; } export const newOffer = async ( mgv: Mangrove, - outbound_tkn: string | MgvToken, - inbound_tkn: string | MgvToken, + outbound_tkn: string | Token, + inbound_tkn: string | Token, { gives, gasreq, gasprice, tick }: OfferData, ): Promise => { const outboundInfo = await getAmountAndAddress(mgv, outbound_tkn, gives); @@ -124,6 +124,6 @@ export const newOffer = async ( gasprice || 1, ); }; -async function getAddress(token: string | MgvToken, mgv: Mangrove) { +async function getAddress(token: string | Token, mgv: Mangrove) { return typeof token === "string" ? await mgv.token(token) : token; } From 16a7de60c8172d5c8642fac259ed25ec0e132d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 16:49:18 +0100 Subject: [PATCH 12/22] feat: Also accept `Token` as base and quote when connecting to a market --- CHANGELOG.md | 1 + src/mangrove.ts | 4 ++-- src/market.ts | 14 ++++++++++---- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b31d8b55..f70524bbd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - Default token IDs can be registered for a symbol and network. And if there is only one ID for a given symbol on a network, it will be considered the default. `Mangrove.token()` will create an instance of the default token ID if found. - feat!: `Mangrove.getNameFromAddress` has been removed: It was ambiguous (multiple names could be registered) and only used for resolving tokens which can now be done with the new `configuration.tokens.getTokenIdFromAddress()` function. - feat!: The `Mangrove.openMarkets` function now uses `Token` instead of a bespoke token data struct. +- feat: `Mangrove.market` and `Market.connect` now accept either symbol, token ID, or `Token` for base and quote. # 2.0.0-4 diff --git a/src/mangrove.ts b/src/mangrove.ts index 0fa60d6ff..e4918aa53 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -436,8 +436,8 @@ class Mangrove { To set your own token, use `setDecimals` and `setAddress`. */ async market(params: { - base: string; - quote: string; + base: string | Token; + quote: string | Token; tickSpacing: Bigish; bookOptions?: Market.BookOptions; }): Promise { diff --git a/src/market.ts b/src/market.ts index 85821e50b..18bbc6136 100644 --- a/src/market.ts +++ b/src/market.ts @@ -315,13 +315,19 @@ class Market { static async connect( params: { mgv: Mangrove; - base: string; - quote: string; + base: string | Token; + quote: string | Token; tickSpacing: Bigish; } & Partial, ): Promise { - const base = await params.mgv.token(params.base); - const quote = await params.mgv.token(params.quote); + const base = + typeof params.base === "string" + ? await params.mgv.token(params.base) + : params.base; + const quote = + typeof params.quote === "string" + ? await params.mgv.token(params.quote) + : params.quote; canConstructMarket = true; const market = new Market({ mgv: params.mgv, From be937e5a5a2c7a25abaaa8c25175219c3ae6f884 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 22:26:57 +0100 Subject: [PATCH 13/22] feat: Use data from configuration when creating token from address --- src/configuration.ts | 17 ++++++++++++++--- src/token.ts | 16 +++++++++++----- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 1129bc8d7..2d7ab2b1a 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -398,15 +398,26 @@ export const tokensConfiguration = { provider: Provider, ): Promise => { const network = await eth.getProviderNetwork(provider); - const token = typechain.IERC20__factory.connect( - addressesConfiguration.getAddress(tokenId, network.name), + const address = addressesConfiguration.getAddress(tokenId, network.name); + const symbol = await tokensConfiguration.fetchSymbolFromAddress( + address, provider, ); - const symbol = await token.symbol(); tokensConfiguration.setSymbol(tokenId, symbol); return symbol; }, + /** + * Read chain for symbol of `address` on current network. + */ + fetchSymbolFromAddress: async ( + address: address, + provider: Provider, + ): Promise => { + const token = typechain.IERC20__factory.connect(address, provider); + return await token.symbol(); + }, + /** * Read displayed decimals for `tokenId`. */ diff --git a/src/token.ts b/src/token.ts index de68fe711..5974f367e 100644 --- a/src/token.ts +++ b/src/token.ts @@ -127,15 +127,21 @@ class Token { address: string, mgv: Mangrove, ): Promise { - const contract = typechain.TestToken__factory.connect( + let tokenId = configuration.tokens.getTokenIdFromAddress( address, - mgv.provider, + mgv.network.name, ); + if (tokenId !== undefined) { + return this.createTokenFromId(tokenId, mgv, { address }); + } - const symbol = await contract.callStatic.symbol(); - const id = symbol ?? address; + const symbol = await configuration.tokens.fetchSymbolFromAddress( + address, + mgv.provider, + ); + tokenId = symbol ?? address; - return this.createTokenFromId(id, mgv, { + return this.createTokenFromId(tokenId, mgv, { address, symbol, }); From a9211506bc2ef33caf9a2d25d4ef0c5cbc530f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 23:05:33 +0100 Subject: [PATCH 14/22] feat: Use token configuration in Mangrove.openMarkets --- src/mangrove.ts | 129 +++++++++++++++--------------------------------- 1 file changed, 41 insertions(+), 88 deletions(-) diff --git a/src/mangrove.ts b/src/mangrove.ts index e4918aa53..f821605ff 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -846,7 +846,6 @@ class Mangrove { * @param from: start at market `from`. Default 0. * @param maxLen: max number of markets returned. Default all. * @param configs: fetch market's config information. Default true. - * @param tokenInfo: fetch token information (symbol, decimals) * @note If an open market has a token with no/bad decimals/symbol function, this function will revert. */ async openMarketsData( @@ -854,14 +853,12 @@ class Mangrove { from?: number; maxLen?: number | ethers.BigNumber; configs?: boolean; - tokenInfos?: boolean; } = {}, ): Promise { // set default params params.from ??= 0; params.maxLen ??= ethers.constants.MaxUint256; params.configs ??= true; - params.tokenInfos ??= true; // read open markets and their configs off mgvReader const raw = await this.readerContract["openMarkets(uint256,uint256,bool)"]( params.from, @@ -869,15 +866,15 @@ class Mangrove { params.configs, ); - // structure data object as address => (symbol,decimals,address=>config) + // structure data object as address => (token,address=>config) const data: Record< string, { - symbol: string; - decimals: number; + token: Token; configs: Record; } > = {}; + raw.markets.forEach(([tkn0, tkn1], i) => { (data[tkn0] as any) ??= { configs: {} }; (data[tkn1] as any) ??= { configs: {} }; @@ -888,85 +885,39 @@ class Mangrove { } }); + // TODO: Consider fetching missing decimals/symbols in one Multicall and dispatch to Token initializations instead of firing multiple RPC calls. const addresses = Object.keys(data); + await Promise.all( + addresses.map(async (address) => { + data[address].token = await this.tokenFromAddress(address); + }), + ); - //read decimals & symbol for each token using Multicall - const ierc20 = typechain.IERC20__factory.createInterface(); - - const tryDecodeDecimals = (ary: any[], fnName: "decimals") => { - return ary.forEach((returnData, i) => { - // will raise exception if call reverted - data[addresses[i]][fnName] = ierc20.decodeFunctionResult( - fnName as any, - returnData, - )[0] as number; - }); - }; - const tryDecodeSymbol = (ary: any[], fnName: "symbol") => { - return ary.forEach((returnData, i) => { - // will raise exception if call reverted - data[addresses[i]][fnName] = ierc20.decodeFunctionResult( - fnName as any, - returnData, - )[0] as string; - }); - }; + // format return value + return raw.markets.map(([tkn0, tkn1, tickSpacing]) => { + const { base, quote } = this.toBaseQuoteByCashness( + data[tkn0].token, + data[tkn1].token, + ); - /* Grab decimals for all contracts */ - const decimalArgs = addresses.map((addr) => { - return { target: addr, callData: ierc20.encodeFunctionData("decimals") }; - }); - const symbolArgs = addresses.map((addr) => { - return { target: addr, callData: ierc20.encodeFunctionData("symbol") }; + return { + base, + quote, + tickSpacing: tickSpacing, + asksConfig: params.configs + ? Semibook.rawLocalConfigToLocalConfig( + data[base.address].configs[quote.address], + base.decimals, + ) + : undefined, + bidsConfig: params.configs + ? Semibook.rawLocalConfigToLocalConfig( + data[quote.address].configs[base.address], + quote.decimals, + ) + : undefined, + }; }); - const { returnData } = await this.multicallContract.callStatic.aggregate([ - ...decimalArgs, - ...symbolArgs, - ]); - tryDecodeDecimals(returnData.slice(0, addresses.length), "decimals"); - tryDecodeSymbol(returnData.slice(addresses.length), "symbol"); - - // format return value - return await Promise.all( - raw.markets.map(async ([tkn0, tkn1, tickSpacing]) => { - // Use internal mgv name if defined; otherwise use the symbol. - const tkn0Id = - configuration.tokens.getTokenIdFromAddress(tkn0, this.network.name) ?? - data[tkn0].symbol; - const tkn1Id = - configuration.tokens.getTokenIdFromAddress(tkn1, this.network.name) ?? - data[tkn1].symbol; - - const { baseId, quoteId } = this.toBaseQuoteByCashness(tkn0Id, tkn1Id); - const [base, quote] = baseId === tkn0Id ? [tkn0, tkn1] : [tkn1, tkn0]; - - return { - base: await this.tokenFromId(baseId, { - address: base, - symbol: data[base].symbol, - decimals: data[base].decimals, - }), - quote: await this.tokenFromId(quoteId, { - address: quote, - symbol: data[quote].symbol, - decimals: data[quote].decimals, - }), - tickSpacing: tickSpacing, - asksConfig: params.configs - ? Semibook.rawLocalConfigToLocalConfig( - data[base].configs[quote], - data[base].decimals, - ) - : undefined, - bidsConfig: params.configs - ? Semibook.rawLocalConfigToLocalConfig( - data[quote].configs[base], - data[quote].decimals, - ) - : undefined, - }; - }), - ); } /** @@ -991,7 +942,6 @@ class Mangrove { delete params.bookOptions; const openMarketsData = await this.openMarketsData({ ...params, - tokenInfos: true, configs: false, }); // TODO: fetch all semibook configs in one Multicall and dispatch to Semibook initializations (see openMarketsData) instead of firing multiple RPC calls. @@ -1021,16 +971,19 @@ class Mangrove { // toBaseQuoteByCashness orders tokens according to relative cashness. // Assume cashness of both to be 0 if cashness is undefined for at least one argument. // Ordering is lex order on cashness x (string order) - toBaseQuoteByCashness(tokenId0: string, tokenId1: string) { - let cash0 = configuration.tokens.getCashness(tokenId0); - let cash1 = configuration.tokens.getCashness(tokenId1); + toBaseQuoteByCashness( + token0: Token, + token1: Token, + ): { base: Token; quote: Token } { + let cash0 = configuration.tokens.getCashness(token0.id); + let cash1 = configuration.tokens.getCashness(token1.id); if (cash0 === undefined || cash1 === undefined) { cash0 = cash1 = 0; } - if (cash0 < cash1 || (cash0 === cash1 && tokenId0 < tokenId1)) { - return { baseId: tokenId0, quoteId: tokenId1 }; + if (cash0 < cash1 || (cash0 === cash1 && token0.id < token1.id)) { + return { base: token0, quote: token1 }; } else { - return { baseId: tokenId1, quoteId: tokenId0 }; + return { base: token1, quote: token0 }; } } } From 99f3a08595c9d0c819164ec0c04df1bee7881d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Fri, 24 Nov 2023 23:39:35 +0100 Subject: [PATCH 15/22] feat: Add getTokenAddress(symbolOrId) --- examples/aaveV3Module.ts | 2 +- src/configuration.ts | 5 ++-- src/kandel/kandelFarm.ts | 4 +-- src/mangrove.ts | 9 ++++++ src/offerLogic.ts | 8 +++--- src/token.ts | 22 ++++++++++++++- src/util/test/TestMaker.ts | 4 +-- .../kandel/farm.integration.test.ts | 6 ++-- test/integration/mangrove.integration.test.ts | 28 +++++++++---------- 9 files changed, 60 insertions(+), 28 deletions(-) diff --git a/examples/aaveV3Module.ts b/examples/aaveV3Module.ts index f37ed8e10..7dde499ec 100644 --- a/examples/aaveV3Module.ts +++ b/examples/aaveV3Module.ts @@ -30,7 +30,7 @@ class AaveV3Module { tokenId: string, signer?: SignerOrProvider, ): Promise { - const asset_address = this.mgv.getAddress(tokenId); + const asset_address = this.mgv.getTokenAddress(tokenId); const debt_address = await this.contract.debtToken(asset_address); return typechain.ICreditDelegationToken__factory.connect( debt_address, diff --git a/src/configuration.ts b/src/configuration.ts index 2d7ab2b1a..3c8d1f6e6 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -259,7 +259,8 @@ export const tokensConfiguration = { /** * Gets the default token ID for a given symbol and network if * (1) any has been registered or - * (2) if there is only one token with that symbol. + * (2) if there is only one token with that symbol or + * (3) if there are no tokens with that symbol, then the symbol itself. */ getDefaultIdForSymbolOnNetwork( tokenSymbol: tokenSymbol, @@ -285,7 +286,7 @@ export const tokensConfiguration = { foundTokenId = tokenId; } } - return foundTokenId; + return foundTokenId ?? tokenSymbol; }, /** diff --git a/src/kandel/kandelFarm.ts b/src/kandel/kandelFarm.ts index 9381df2a5..03c6c120f 100644 --- a/src/kandel/kandelFarm.ts +++ b/src/kandel/kandelFarm.ts @@ -62,8 +62,8 @@ class KandelFarm { if (!olKey) { const offerList = filter?.baseQuoteOfferList; if (offerList) { - const baseAddress = this.mgv.getAddress(offerList.base); - const quoteAddress = this.mgv.getAddress(offerList.quote); + const baseAddress = this.mgv.getTokenAddress(offerList.base); + const quoteAddress = this.mgv.getTokenAddress(offerList.quote); const tickSpacing = offerList.tickSpacing ?? 0; olKey = { outbound_tkn: baseAddress, diff --git a/src/mangrove.ts b/src/mangrove.ts index f821605ff..4fea64c75 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -543,6 +543,15 @@ class Mangrove { ); } + /** + * Read a token address on the current network. + * + * Note that this reads from the static `Mangrove` address registry which is shared across instances of this class. + */ + getTokenAddress(symbolOrId: string): string { + return Token.getTokenAddress(symbolOrId, this.network.name || "mainnet"); + } + /** * Set a contract address on the current network. * diff --git a/src/offerLogic.ts b/src/offerLogic.ts index 550bb47e7..4f1ed231a 100644 --- a/src/offerLogic.ts +++ b/src/offerLogic.ts @@ -114,16 +114,16 @@ class OfferLogic { /** * @note (contract admin action) activates logic - * @param tokenNames the names of the tokens one wishes the logic to trade + * @param tokenSymbolsOrIds the symbols or IDs of the tokens one wishes the logic to trade * @param overrides The ethers overrides to use when calling the activate function. * @returns The transaction used to activate the OfferLogic. * */ activate( - tokenNames: string[], + tokenSymbolsOrIds: string[], overrides: ethers.Overrides = {}, ): Promise { - const tokenAddresses = tokenNames.map((tokenName) => - this.mgv.getAddress(tokenName), + const tokenAddresses = tokenSymbolsOrIds.map((symbolOrId) => + this.mgv.getTokenAddress(symbolOrId), ); return this.contract.activate(tokenAddresses, overrides); } diff --git a/src/token.ts b/src/token.ts index 5974f367e..ea8996ae3 100644 --- a/src/token.ts +++ b/src/token.ts @@ -69,7 +69,7 @@ class Token { this.id = id; Token.#applyOptions(id, mgv, options); - this.address = this.mgv.getAddress(this.id); + this.address = Token.getTokenAddress(this.id, mgv.network.name); this.decimals = configuration.tokens.getDecimalsOrFail(this.id); this.symbol = configuration.tokens.getSymbol(this.id); this.displayedDecimals = configuration.tokens.getDisplayedDecimals(this.id); @@ -147,6 +147,26 @@ class Token { }); } + /** + * Read a token address on the current network. + * + * Note that this reads from the static `Mangrove` address registry which is shared across instances of this class. + */ + static getTokenAddress(symbolOrId: string, network: string): string { + const tokenId = configuration.tokens.isTokenIdRegistered(symbolOrId) + ? symbolOrId + : configuration.tokens.getDefaultIdForSymbolOnNetwork( + symbolOrId, + network, + ); + if (tokenId === undefined) { + throw new Error( + `No token with symbol or ID ${symbolOrId} on network ${network}`, + ); + } + return configuration.addresses.getAddress(tokenId, network); + } + static #applyOptions( id: string, mgv: Mangrove, diff --git a/src/util/test/TestMaker.ts b/src/util/test/TestMaker.ts index 3c044034d..b813c5bcb 100644 --- a/src/util/test/TestMaker.ts +++ b/src/util/test/TestMaker.ts @@ -79,8 +79,8 @@ class TestMaker { static async create( p: TestMaker.CreateParams & Partial, ): Promise { - const baseAddress = p.mgv.getAddress(p.base); - const quoteAddress = p.mgv.getAddress(p.quote); + const baseAddress = p.mgv.getTokenAddress(p.base); + const quoteAddress = p.mgv.getTokenAddress(p.quote); const contract = await new typechain.SimpleTestMaker__factory( p.mgv.signer, ).deploy(p.mgv.address, { diff --git a/test/integration/kandel/farm.integration.test.ts b/test/integration/kandel/farm.integration.test.ts index a419f713a..f4a69ad42 100644 --- a/test/integration/kandel/farm.integration.test.ts +++ b/test/integration/kandel/farm.integration.test.ts @@ -119,12 +119,14 @@ describe(`${KandelFarm.prototype.constructor.name} integration tests suite`, fun assert.equal(kandels.filter((x) => x.base?.id == "TokenA").length, 1); assert.equal(kandels.filter((x) => x.base?.id == "WETH").length, 4); assert.equal( - kandels.filter((x) => x.baseAddress == mgv.getAddress("WETH")).length, + kandels.filter((x) => x.baseAddress == mgv.getTokenAddress("WETH")) + .length, 4, ); assert.equal(kandels.filter((x) => x.quote?.id == "USDC").length, 3); assert.equal( - kandels.filter((x) => x.quoteAddress == mgv.getAddress("USDC")).length, + kandels.filter((x) => x.quoteAddress == mgv.getTokenAddress("USDC")) + .length, 3, ); assert.equal(kandels.filter((x) => x.onAave).length, 2); diff --git a/test/integration/mangrove.integration.test.ts b/test/integration/mangrove.integration.test.ts index cbe1dcf4f..41c39bc5f 100644 --- a/test/integration/mangrove.integration.test.ts +++ b/test/integration/mangrove.integration.test.ts @@ -51,25 +51,25 @@ describe("Mangrove integration tests suite", function () { describe("getMarkets", function () { it("updates with mgvReader", async function () { await mgvAdmin.contract.deactivate({ - outbound_tkn: mgv.getAddress("TokenA"), - inbound_tkn: mgv.getAddress("TokenB"), + outbound_tkn: mgv.getTokenAddress("TokenA"), + inbound_tkn: mgv.getTokenAddress("TokenB"), tickSpacing: 1, }); await mgvAdmin.contract.deactivate({ - outbound_tkn: mgv.getAddress("TokenB"), - inbound_tkn: mgv.getAddress("TokenA"), + outbound_tkn: mgv.getTokenAddress("TokenB"), + inbound_tkn: mgv.getTokenAddress("TokenA"), tickSpacing: 1, }); await mgv.readerContract.updateMarket({ - tkn0: mgv.getAddress("TokenA"), - tkn1: mgv.getAddress("TokenB"), + tkn0: mgv.getTokenAddress("TokenA"), + tkn1: mgv.getTokenAddress("TokenB"), tickSpacing: 1, }); const marketsBefore = await mgv.openMarkets(); await mgvAdmin.contract.activate( { - outbound_tkn: mgv.getAddress("TokenA"), - inbound_tkn: mgv.getAddress("TokenB"), + outbound_tkn: mgv.getTokenAddress("TokenA"), + inbound_tkn: mgv.getTokenAddress("TokenB"), tickSpacing: 1, }, 1, @@ -77,8 +77,8 @@ describe("Mangrove integration tests suite", function () { 1, ); await mgv.readerContract.updateMarket({ - tkn0: mgv.getAddress("TokenA"), - tkn1: mgv.getAddress("TokenB"), + tkn0: mgv.getTokenAddress("TokenA"), + tkn1: mgv.getTokenAddress("TokenB"), tickSpacing: 1, }); const markets = await mgv.openMarkets(); @@ -91,19 +91,19 @@ describe("Mangrove integration tests suite", function () { it("gets correct market info and updates with cashness", async function () { await mgv.readerContract.updateMarket({ - tkn0: mgv.getAddress("TokenA"), - tkn1: mgv.getAddress("TokenB"), + tkn0: mgv.getTokenAddress("TokenA"), + tkn1: mgv.getTokenAddress("TokenB"), tickSpacing: 1, }); let marketData = await mgv.openMarketsData(); const tokenAData = { - address: mgv.getAddress("TokenA"), + address: mgv.getTokenAddress("TokenA"), decimals: 18, id: "TokenA", symbol: "TokenA", }; const tokenBData = { - address: mgv.getAddress("TokenB"), + address: mgv.getTokenAddress("TokenB"), decimals: 6, id: "TokenB", symbol: "TokenB", From a87f89d04913c719ea87b8755a6d9f2a74d861ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Sat, 25 Nov 2023 00:10:45 +0100 Subject: [PATCH 16/22] refactor: Simplify token construction --- src/token.ts | 101 +++++++++++++++++++-------------------------------- 1 file changed, 38 insertions(+), 63 deletions(-) diff --git a/src/token.ts b/src/token.ts index ea8996ae3..0d1620f17 100644 --- a/src/token.ts +++ b/src/token.ts @@ -52,28 +52,26 @@ function convertToApproveArgs(arg: ApproveArgs): { } class Token { - mgv: Mangrove; - // ID which should be unique within a network. - // Typically the id from the context-addresses package. - // May be the symbol if the symbol is unique. NB: This uniqueness is not enforced and duplicates will give undefined behavior. - id: string; - // Non-unique and optional symbol cf. ERC20 - symbol?: string; - address: string; - displayedDecimals: number; - decimals: number; // Using most complete interface (burn, mint, blacklist etc.) to be able to access non standard ERC calls using ethers.js contract: typechain.TestToken; - constructor(id: string, mgv: Mangrove, options?: Token.ConstructorOptions) { - this.mgv = mgv; - this.id = id; - Token.#applyOptions(id, mgv, options); - - this.address = Token.getTokenAddress(this.id, mgv.network.name); - this.decimals = configuration.tokens.getDecimalsOrFail(this.id); - this.symbol = configuration.tokens.getSymbol(this.id); - this.displayedDecimals = configuration.tokens.getDisplayedDecimals(this.id); + /** + * + * @param id ID which should be unique within a network, but can be used across networks. Typically the id from the context-addresses package. May be the symbol if the symbol is unique. NB: This uniqueness is not enforced and duplicates will give undefined behavior. + * @param address Address of the token contract. + * @param symbol Non-unique and optional symbol cf. ERC20. + * @param decimals Number of decimals used by the token. + * @param displayedDecimals Number of decimals to display in the UI. + * @param mgv The Mangrove instance this token is associated with. + */ + private constructor( + public id: string, + public address: string, + public symbol: string | undefined, + public decimals: number, + public displayedDecimals: number, + public mgv: Mangrove, + ) { this.contract = typechain.TestToken__factory.connect( this.address, this.mgv.signer, @@ -93,21 +91,6 @@ class Token { } } - /** Create a Token instance, fetching data (decimals) from chain if needed. */ - static async createTokenFromId( - id: string, - mgv: Mangrove, - options?: Token.ConstructorOptions, - ): Promise { - Token.#applyOptions(id, mgv, options); - - // Ensure decimals and symbol are known before token construction as it will otherwise fail. - await configuration.tokens.getOrFetchDecimals(id, mgv.provider); - await configuration.tokens.getOrFetchSymbol(id, mgv.provider); - - return new Token(id, mgv, options); - } - /** Create a Token instance, fetching data (decimals) from chain if needed. */ static async createTokenFromSymbol( symbol: string, @@ -123,6 +106,27 @@ class Token { return this.createTokenFromId(id, mgv, { ...options, symbol }); } + /** Create a Token instance, fetching data (decimals) from chain if needed. */ + static async createTokenFromId( + id: string, + mgv: Mangrove, + options?: Token.ConstructorOptions, + ): Promise { + const address = + options?.address ?? Token.getTokenAddress(id, mgv.network.name); + const decimals = + options?.decimals ?? + (await configuration.tokens.getOrFetchDecimals(id, mgv.provider)); + const symbol = + options?.symbol ?? + (await configuration.tokens.getOrFetchSymbol(id, mgv.provider)); + const displayedDecimals = + options?.displayedDecimals ?? + configuration.tokens.getDisplayedDecimals(id); + + return new Token(id, address, symbol, decimals, displayedDecimals, mgv); + } + static async createTokenFromAddress( address: string, mgv: Mangrove, @@ -167,35 +171,6 @@ class Token { return configuration.addresses.getAddress(tokenId, network); } - static #applyOptions( - id: string, - mgv: Mangrove, - options?: Token.ConstructorOptions, - ) { - if (options === undefined) { - return; - } - - if ("address" in options && options.address !== undefined) { - mgv.setAddress(id, options.address); - } - - if ("decimals" in options && options.decimals !== undefined) { - configuration.tokens.setDecimals(id, options.decimals); - } - - if ("symbol" in options && options.symbol !== undefined) { - configuration.tokens.setSymbol(id, options.symbol); - } - - if ( - "displayedDecimals" in options && - options.displayedDecimals !== undefined - ) { - configuration.tokens.setDisplayedDecimals(id, options.displayedDecimals); - } - } - /** * Convert base/quote from internal amount to public amount. * Uses each token's `decimals` parameter. From 393e9068cfbdf9edfb62224f4d63c753f8dde438 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Sat, 25 Nov 2023 00:20:25 +0100 Subject: [PATCH 17/22] feat: Add displayedAsPriceDecimals to Token --- CHANGELOG.md | 16 +++++++++------- src/configuration.ts | 15 +++++++++++++++ src/token.ts | 22 +++++++++++++++++++++- 3 files changed, 45 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f70524bbd..23cc9e43b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,5 @@ # Next version -# 2.0.0-7 - -# 2.0.0-6 - -# 2.0.0-5 - -- feat: Add usage of Geometric Kandel's call-data-reducing function - feat!: `MgvToken` has been renamed to `Token`. - feat!: Removed `configuration.tokens.fetchDecimalsFromAddress`. Instead, use `Token.createTokenFromAddress` and read the decimals from that token. - feat!: `Mangrove.toUnits|fromUnits` no longer accepts a token name/symbol as this was ambiguous. Instead, use `Token.createToken` and call `toUnits|fromUnits` on that. @@ -18,6 +11,15 @@ - feat!: `Mangrove.getNameFromAddress` has been removed: It was ambiguous (multiple names could be registered) and only used for resolving tokens which can now be done with the new `configuration.tokens.getTokenIdFromAddress()` function. - feat!: The `Mangrove.openMarkets` function now uses `Token` instead of a bespoke token data struct. - feat: `Mangrove.market` and `Market.connect` now accept either symbol, token ID, or `Token` for base and quote. +- feat: Added `displayName` and `displayedAsPriceDecimals` to `Token`. + +# 2.0.0-7 + +# 2.0.0-6 + +# 2.0.0-5 + +- feat: Add usage of Geometric Kandel's call-data-reducing function # 2.0.0-4 diff --git a/src/configuration.ts b/src/configuration.ts index 3c8d1f6e6..9b4ee771a 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -41,6 +41,7 @@ export type AddressesConfig = Record; export type TokenConfig = { symbol?: tokenSymbol; decimals?: number; + displayName?: string; displayedDecimals?: number; displayedAsPriceDecimals?: number; cashness?: number; @@ -419,6 +420,13 @@ export const tokensConfiguration = { return await token.symbol(); }, + /** + * Read display name for `tokenId`. + */ + getDisplayName: (tokenId: tokenId): string | undefined => { + return getOrCreateTokenConfig(tokenId).displayName; + }, + /** * Read displayed decimals for `tokenId`. */ @@ -470,6 +478,13 @@ export const tokensConfiguration = { getOrCreateTokenConfig(tokenId).symbol = symbol; }, + /** + * Set display name for `tokenId`. + */ + setDisplayName: (tokenId: tokenId, displayName: string): void => { + getOrCreateTokenConfig(tokenId).displayName = displayName; + }, + /** * Set displayed decimals for `tokenId` on the given network. */ diff --git a/src/token.ts b/src/token.ts index 0d1620f17..6f1b7ed94 100644 --- a/src/token.ts +++ b/src/token.ts @@ -12,7 +12,9 @@ namespace Token { address?: string; decimals?: number; symbol?: string; + displayName?: string; displayedDecimals?: number; + displayedAsPriceDecimals?: number; }; } @@ -61,7 +63,9 @@ class Token { * @param address Address of the token contract. * @param symbol Non-unique and optional symbol cf. ERC20. * @param decimals Number of decimals used by the token. + * @param displayName Optional display name for the token. * @param displayedDecimals Number of decimals to display in the UI. + * @param displayedAsPriceDecimals Number of decimals to display in the UI when showing a price. * @param mgv The Mangrove instance this token is associated with. */ private constructor( @@ -69,7 +73,9 @@ class Token { public address: string, public symbol: string | undefined, public decimals: number, + public displayName: string | undefined, public displayedDecimals: number, + public displayedAsPriceDecimals: number, public mgv: Mangrove, ) { this.contract = typechain.TestToken__factory.connect( @@ -120,11 +126,25 @@ class Token { const symbol = options?.symbol ?? (await configuration.tokens.getOrFetchSymbol(id, mgv.provider)); + const displayName = + options?.displayName ?? configuration.tokens.getDisplayName(id); const displayedDecimals = options?.displayedDecimals ?? configuration.tokens.getDisplayedDecimals(id); + const displayedAsPriceDecimals = + options?.displayedAsPriceDecimals ?? + configuration.tokens.getDisplayedPriceDecimals(id); - return new Token(id, address, symbol, decimals, displayedDecimals, mgv); + return new Token( + id, + address, + symbol, + decimals, + displayName, + displayedDecimals, + displayedAsPriceDecimals, + mgv, + ); } static async createTokenFromAddress( From ec4f4bb88caef77cbbba8bf27f4f0def3d6b30aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Sat, 25 Nov 2023 00:45:19 +0100 Subject: [PATCH 18/22] docs: Fix comments --- src/configuration.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 9b4ee771a..453b7e111 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -465,14 +465,14 @@ export const tokensConfiguration = { }, /** - * Set decimals for `tokenId` on the given network. + * Set decimals for `tokenId`. */ setDecimals: (tokenId: tokenId, dec: number): void => { getOrCreateTokenConfig(tokenId).decimals = dec; }, /** - * Set symbol for `tokenId` on the given network. + * Set symbol for `tokenId`. */ setSymbol: (tokenId: tokenId, symbol: tokenSymbol): void => { getOrCreateTokenConfig(tokenId).symbol = symbol; @@ -486,20 +486,20 @@ export const tokensConfiguration = { }, /** - * Set displayed decimals for `tokenId` on the given network. + * Set displayed decimals for `tokenId`. */ setDisplayedDecimals: (tokenId: tokenId, dec: number): void => { getOrCreateTokenConfig(tokenId).displayedDecimals = dec; }, /** - * Set displayed decimals for `tokenId` on the given network when displayed as a price. + * Set displayed decimals for `tokenId` when displayed as a price. */ setDisplayedPriceDecimals: (tokenId: tokenId, dec: number): void => { getOrCreateTokenConfig(tokenId).displayedAsPriceDecimals = dec; }, - /** Set the relative cashness of a token on the given network. This determines which token is base & which is quote in a {@link Market}. + /** Set the relative cashness of a token. This determines which token is base & which is quote in a {@link Market}. * Lower cashness is base, higher cashness is quote, tiebreaker is lexicographic ordering of name string (name is most likely the same as the symbol). */ setCashness: (tokenId: tokenId, cashness: number) => { From 2a3cd4f1b810d95f2ce134ca542e03786e04d1b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Mon, 27 Nov 2023 12:40:45 +0100 Subject: [PATCH 19/22] docs: Expand TODO comment --- src/mangrove.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mangrove.ts b/src/mangrove.ts index 4fea64c75..773e555a2 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -895,6 +895,7 @@ class Mangrove { }); // TODO: Consider fetching missing decimals/symbols in one Multicall and dispatch to Token initializations instead of firing multiple RPC calls. + // However, viem (and maybe ethers6) automatically batches multiple read requests as a multicall, so not sure this is worth pursuing. const addresses = Object.keys(data); await Promise.all( addresses.map(async (address) => { From 74f6a04ad96bc6cbfed0b70519a7160cfb7a5f47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Mon, 27 Nov 2023 12:42:29 +0100 Subject: [PATCH 20/22] refactor: Remove unnecessary awaits --- src/kandel/kandelFarm.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/kandel/kandelFarm.ts b/src/kandel/kandelFarm.ts index 03c6c120f..5a8d6f3cb 100644 --- a/src/kandel/kandelFarm.ts +++ b/src/kandel/kandelFarm.ts @@ -86,10 +86,10 @@ class KandelFarm { x.args.baseQuoteOlKeyHash, ); const baseToken = await this.mgv.tokenFromAddress( - await olKeyStruct!.outbound_tkn, + olKeyStruct!.outbound_tkn, ); const quoteToken = await this.mgv.tokenFromAddress( - await olKeyStruct!.inbound_tkn, + olKeyStruct!.inbound_tkn, ); return { kandelAddress: x.args.kandel, @@ -116,10 +116,10 @@ class KandelFarm { x.args.baseQuoteOlKeyHash, ); const baseToken = await this.mgv.tokenFromAddress( - await olKeyStruct!.outbound_tkn, + olKeyStruct!.outbound_tkn, ); const quoteToken = await this.mgv.tokenFromAddress( - await olKeyStruct!.inbound_tkn, + olKeyStruct!.inbound_tkn, ); return { kandelAddress: x.args.aaveKandel, From de49baf4209283b82085aca5df18052c16d7ee16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Mon, 27 Nov 2023 12:52:02 +0100 Subject: [PATCH 21/22] feat: Throw error if default token ID is undefined --- src/configuration.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/configuration.ts b/src/configuration.ts index 453b7e111..762e817e2 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -262,11 +262,13 @@ export const tokensConfiguration = { * (1) any has been registered or * (2) if there is only one token with that symbol or * (3) if there are no tokens with that symbol, then the symbol itself. + * + * If no default is registered and there are multiple tokens with that symbol an error is thrown. */ getDefaultIdForSymbolOnNetwork( tokenSymbol: tokenSymbol, network: network, - ): tokenId | undefined { + ): tokenId { const registeredDefault = getOrCreateDefaultIdsForSymbol(tokenSymbol)[network]; if (registeredDefault !== undefined) { @@ -282,7 +284,9 @@ export const tokensConfiguration = { ) { if (foundTokenId !== undefined) { // If we already found a token with that symbol, we cannot decide which one is the default - return undefined; + throw Error( + `No default token ID registered for symbol ${tokenSymbol} and multiple tokens defined on network ${network} with that symbol`, + ); } foundTokenId = tokenId; } From e34e47f27c83f1652451eee6baaa627f52bc5de3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Espen=20H=C3=B8jsgaard?= Date: Mon, 27 Nov 2023 13:07:41 +0100 Subject: [PATCH 22/22] feat: Throw error in `Token.getDecimals` if decimals are not on record --- CHANGELOG.md | 1 + src/configuration.ts | 12 ++---------- src/mangrove.ts | 8 ++------ test/unit/configuration.unit.test.ts | 10 +++++----- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 23cc9e43b..64771b08d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - feat!: The `Mangrove.openMarkets` function now uses `Token` instead of a bespoke token data struct. - feat: `Mangrove.market` and `Market.connect` now accept either symbol, token ID, or `Token` for base and quote. - feat: Added `displayName` and `displayedAsPriceDecimals` to `Token`. +- feat!: `Token.getDecimals` now throws an error instead of returning undefined if the decimals are not on record. # 2.0.0-7 diff --git a/src/configuration.ts b/src/configuration.ts index 762e817e2..29f14c015 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -318,20 +318,12 @@ export const tokensConfiguration = { return undefined; }, - /** - * Read decimals for `tokenId`. - * To read decimals directly onchain, use `fetchDecimals`. - */ - getDecimals: (tokenId: tokenId): number | undefined => { - return getOrCreateTokenConfig(tokenId).decimals; - }, - /** * Read decimals for `tokenId`. Fails if the decimals are not in the configuration. * To read decimals directly onchain, use `fetchDecimals`. */ - getDecimalsOrFail: (tokenId: tokenId): number => { - const decimals = tokensConfiguration.getDecimals(tokenId); + getDecimals: (tokenId: tokenId): number => { + const decimals = getOrCreateTokenConfig(tokenId).decimals; if (decimals === undefined) { throw Error(`No decimals on record for token ${tokenId}`); } diff --git a/src/mangrove.ts b/src/mangrove.ts index 773e555a2..1df54fa49 100644 --- a/src/mangrove.ts +++ b/src/mangrove.ts @@ -958,13 +958,9 @@ class Mangrove { return Promise.all( openMarketsData.map(({ base, quote, tickSpacing }) => { this.setAddress(base.id, base.address); - if (configuration.tokens.getDecimals(base.id) === undefined) { - configuration.tokens.setDecimals(base.id, base.decimals); - } + configuration.tokens.setDecimals(base.id, base.decimals); this.setAddress(quote.id, quote.address); - if (configuration.tokens.getDecimals(quote.id) === undefined) { - configuration.tokens.setDecimals(quote.id, quote.decimals); - } + configuration.tokens.setDecimals(quote.id, quote.decimals); return Market.connect({ mgv: this, base: base.id, diff --git a/test/unit/configuration.unit.test.ts b/test/unit/configuration.unit.test.ts index b7900aff7..2c4c25d13 100644 --- a/test/unit/configuration.unit.test.ts +++ b/test/unit/configuration.unit.test.ts @@ -8,7 +8,7 @@ describe("Configuration unit tests suite", () => { }); it("Can add token config of unknown token", () => { - assert.equal(configuration.tokens.getDecimals("UnknownToken"), undefined); + assert.throws(() => configuration.tokens.getDecimals("UnknownToken")); configuration.updateConfiguration({ tokens: { @@ -22,8 +22,8 @@ describe("Configuration unit tests suite", () => { }); it("Adding token config does not affect existing config", () => { - assert.equal(configuration.tokens.getDecimals("UnknownToken1"), undefined); - assert.equal(configuration.tokens.getDecimals("UnknownToken2"), undefined); + assert.throws(() => configuration.tokens.getDecimals("UnknownToken1")); + assert.throws(() => configuration.tokens.getDecimals("UnknownToken2")); configuration.updateConfiguration({ tokens: { @@ -49,7 +49,7 @@ describe("Configuration unit tests suite", () => { it("Reset of configuration reverts additions and changes", () => { assert.equal(configuration.tokens.getDecimals("TokenA"), 18); - assert.equal(configuration.tokens.getDecimals("UnknownToken"), undefined); + assert.throws(() => configuration.tokens.getDecimals("UnknownToken")); configuration.updateConfiguration({ tokens: { @@ -68,7 +68,7 @@ describe("Configuration unit tests suite", () => { configuration.resetConfiguration(); assert.equal(configuration.tokens.getDecimals("TokenA"), 18); - assert.equal(configuration.tokens.getDecimals("UnknownToken"), undefined); + assert.throws(() => configuration.tokens.getDecimals("UnknownToken")); }); it("can read mangroveOrder config", () => {