From 346997aa45d8ec25274c5cced03fd4bffd31da6c Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Thu, 29 Aug 2024 11:36:52 +0200 Subject: [PATCH 1/5] perf: avoid initializing ethers contracts during construction --- package.json | 31 ++++++++++++++++--------------- src/curve.ts | 6 +++++- src/pools/PoolTemplate.ts | 5 ++--- src/pools/poolConstructor.ts | 12 ++++++------ src/utils.ts | 22 +++++++++------------- 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/package.json b/package.json index 22b18f7a..a45b66a7 100644 --- a/package.json +++ b/package.json @@ -18,24 +18,25 @@ }, "type": "module", "devDependencies": { - "@types/chai": "^4.3.4", - "@types/memoizee": "^0.4.7", - "@types/mocha": "^10.0.1", - "@types/node": "^14.14.37", - "@typescript-eslint/eslint-plugin": "^4.33.0", - "@typescript-eslint/parser": "^4.20.0", + "@types/chai": "^4.3.19", + "@types/memoizee": "^0.4.11", + "@types/mocha": "^10.0.7", + "@types/node": "^22.5.1", + "@typescript-eslint/eslint-plugin": "^8.3.0", + "@typescript-eslint/parser": "^8.3.0", "babel-eslint": "^10.1.0", - "chai": "^4.3.7", - "eslint": "^7.32.0", - "mocha": "^10.2.0", - "typescript": "^4.5.2", - "vue-eslint-parser": "^7.6.0" + "chai": "^5.1.1", + "eslint": "^9.9.1", + "mocha": "^10.7.3", + "typescript": "^5.5.4", + "vue-eslint-parser": "^9.4.3" }, "dependencies": { - "axios": "^0.21.1", - "bignumber.js": "^9.0.1", "@curvefi/ethcall": "6.0.7", - "ethers": "^6.11.0", - "memoizee": "^0.4.15" + "abitype": "^1.0.6", + "axios": "^0.21.1", + "bignumber.js": "^9.1.2", + "ethers": "^6.13.2", + "memoizee": "^0.4.17" } } diff --git a/src/curve.ts b/src/curve.ts index f011c592..86d19a88 100644 --- a/src/curve.ts +++ b/src/curve.ts @@ -9,6 +9,7 @@ import { JsonRpcProvider, Signer, } from "ethers"; +import type { Abi } from "abitype"; import { Provider as MulticallProvider, Contract as MulticallContract } from "@curvefi/ethcall"; import { getFactoryPoolData } from "./factory/factory.js"; import { getFactoryPoolsDataFromApi } from "./factory/factory-api.js"; @@ -419,13 +420,16 @@ export const NETWORK_CONSTANTS: { [index: number]: any } = { const OLD_CHAINS = [1, 10, 56, 100, 137, 250, 1284, 2222, 8453, 42161, 42220, 43114, 1313161554]; // these chains have non-ng pools + +export type ContractItem = { contract: Contract, multicallContract: MulticallContract, abi: Abi }; + class Curve implements ICurve { provider: ethers.BrowserProvider | ethers.JsonRpcProvider; multicallProvider: MulticallProvider; signer: ethers.Signer | null; signerAddress: string; chainId: IChainId; - contracts: { [index: string]: { contract: Contract, multicallContract: MulticallContract } }; + contracts: { [index: string]: ContractItem }; feeData: { gasPrice?: number, maxFeePerGas?: number, maxPriorityFeePerGas?: number }; constantOptions: { gasLimit?: number }; options: { gasPrice?: number | bigint, maxFeePerGas?: number | bigint, maxPriorityFeePerGas?: number | bigint }; diff --git a/src/pools/PoolTemplate.ts b/src/pools/PoolTemplate.ts index a93719a9..ac702e8d 100644 --- a/src/pools/PoolTemplate.ts +++ b/src/pools/PoolTemplate.ts @@ -27,7 +27,7 @@ import { smartNumber, DIGas, _getAddress, - isMethodExist, + findAbiFunction, getVolumeApiController, } from '../utils.js'; import {IDict, IReward, IProfit, IPoolType} from '../interfaces'; @@ -2323,8 +2323,7 @@ export class PoolTemplate { } //for crvusd and stable-ng implementations - const isUseStoredRates = isMethodExist(curve.contracts[this.address].contract, 'stored_rates') && this.isPlain; - if (isUseStoredRates) { + if (findAbiFunction(curve.contracts[this.address].abi, 'stored_rates').length > 0 && this.isPlain) { const _stored_rates: bigint[] = await curve.contracts[this.address].contract.stored_rates(); return _stored_rates.map((_r, i) => toBN(_r, 36 - this.wrappedDecimals[i])); } diff --git a/src/pools/poolConstructor.ts b/src/pools/poolConstructor.ts index d27228c0..b0a67466 100644 --- a/src/pools/poolConstructor.ts +++ b/src/pools/poolConstructor.ts @@ -20,7 +20,7 @@ import { swapWrappedMixin, swapWrappedRequiredMixin, } from "./mixins/swapWrappedMixins.js"; -import { getCountArgsOfMethodByContract } from "../utils.js"; +import { getCountArgsOfMethodByAbi, findAbiSignature } from "../utils.js"; export const getPool = (poolId: string): PoolTemplate => { @@ -59,7 +59,7 @@ export const getPool = (poolId: string): PoolTemplate => { } } else if (poolDummy.zap && poolId !== 'susd') { Object.assign(Pool.prototype, depositZapMixin); - } else if (getCountArgsOfMethodByContract(curve.contracts[poolDummy.address].contract, 'add_liquidity') > 2) { + } else if (getCountArgsOfMethodByAbi(curve.contracts[poolDummy.address].abi, 'add_liquidity') > 2) { Object.assign(Pool.prototype, depositLendingOrCryptoMixin); } else { Object.assign(Pool.prototype, depositPlainMixin); @@ -92,7 +92,7 @@ export const getPool = (poolId: string): PoolTemplate => { } } else if (poolDummy.zap && poolId !== 'susd') { Object.assign(Pool.prototype, withdrawZapMixin); - } else if (getCountArgsOfMethodByContract(curve.contracts[poolDummy.address].contract, 'remove_liquidity') > 2) { + } else if (getCountArgsOfMethodByAbi(curve.contracts[poolDummy.address].abi, 'remove_liquidity') > 2) { Object.assign(Pool.prototype, withdrawLendingOrCryptoMixin); } else { Object.assign(Pool.prototype, withdrawPlainMixin); @@ -151,7 +151,7 @@ export const getPool = (poolId: string): PoolTemplate => { } } else if (poolDummy.zap) { // including susd Object.assign(Pool.prototype, withdrawOneCoinZapMixin); - } else if (getCountArgsOfMethodByContract(curve.contracts[poolDummy.address].contract, 'remove_liquidity_one_coin') > 3) { + } else if (getCountArgsOfMethodByAbi(curve.contracts[poolDummy.address].abi, 'remove_liquidity_one_coin') > 3) { Object.assign(Pool.prototype, withdrawOneCoinLendingOrCryptoMixin); } else { Object.assign(Pool.prototype, withdrawOneCoinPlainMixin); @@ -176,7 +176,7 @@ export const getPool = (poolId: string): PoolTemplate => { } // swap and swapEstimateGas - if ('exchange(uint256,uint256,uint256,uint256,bool)' in curve.contracts[poolDummy.address].contract && + if (findAbiSignature(curve.contracts[poolDummy.address].abi, 'exchange', 'uint256,uint256,uint256,uint256,bool') && !(curve.chainId === 100 && poolDummy.id === "tricrypto")) { // tricrypto2 (eth), tricrypto (arbitrum), avaxcrypto (avalanche); 100 is xDAI Object.assign(Pool.prototype, swapTricrypto2Mixin); } else if (poolDummy.isMetaFactory && (getPool(poolDummy.basePool).isLending || getPool(poolDummy.basePool).isFake || poolDummy.isCrypto)) { @@ -193,7 +193,7 @@ export const getPool = (poolId: string): PoolTemplate => { if (!poolDummy.isPlain && !poolDummy.isFake) { Object.assign(Pool.prototype, swapWrappedExpectedAndApproveMixin); Object.assign(Pool.prototype, swapWrappedRequiredMixin); - if ('exchange(uint256,uint256,uint256,uint256,bool)' in curve.contracts[poolDummy.address].contract) { // tricrypto2 (eth), tricrypto (arbitrum) + if (findAbiSignature(curve.contracts[poolDummy.address].abi, 'exchange', 'uint256,uint256,uint256,uint256,bool')) { // tricrypto2 (eth), tricrypto (arbitrum) Object.assign(Pool.prototype, swapWrappedTricrypto2Mixin); } else { Object.assign(Pool.prototype, swapWrappedMixin); diff --git a/src/utils.ts b/src/utils.ts index 64071c89..e7163790 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,7 @@ import axios from 'axios'; import {Contract} from 'ethers'; import {Contract as MulticallContract} from "@curvefi/ethcall"; +import type { Abi, AbiFunction } from "abitype"; import BigNumber from 'bignumber.js'; import { IBasePoolShortItem, @@ -634,6 +635,7 @@ export const getVolume = async (network: INetworkName | IChainId = curve.chainId export const _setContracts = (address: string, abi: any): void => { curve.contracts[address] = { + abi, contract: new Contract(address, abi, curve.signer || curve.provider), multicallContract: new MulticallContract(address, abi), } @@ -709,17 +711,13 @@ export const getCoinsData = async (...coins: string[] | string[][]): Promise<{na export const hasDepositAndStake = (): boolean => curve.constants.ALIASES.deposit_and_stake !== curve.constants.ZERO_ADDRESS; export const hasRouter = (): boolean => curve.constants.ALIASES.router !== curve.constants.ZERO_ADDRESS; -export const getCountArgsOfMethodByContract = (contract: Contract, methodName: string): number => { - const func = contract.interface.fragments.find((item: any) => item.name === methodName); - if(func) { - return func.inputs.length; - } else { - return -1; - } -} +export const findAbiFunction = (abi: Abi, methodName: string) => + abi.filter((item) => item.type == 'function' && item.name === methodName) as AbiFunction[] -export const isMethodExist = (contract: Contract, methodName: string): boolean => - contract.interface.fragments.find((item: any) => item.name === methodName) !== undefined +export const getCountArgsOfMethodByAbi = (abi: Abi, methodName: string): number => findAbiFunction(abi, methodName)[0]?.inputs.length ?? -1 + +export const findAbiSignature = (abi: Abi, methodName: string, signature: string) => + findAbiFunction(abi, methodName).find((func) => func.inputs.map(i => `${i.type}`).join(',') == signature) export const getPoolName = (name: string): string => { const separatedName = name.split(": ") @@ -730,9 +728,7 @@ export const getPoolName = (name: string): string => { } } -export const isStableNgPool = (name: string): boolean => { - return name.includes('factory-stable-ng') -} +export const isStableNgPool = (name: string): boolean => name.includes('factory-stable-ng') export const assetTypeNameHandler = (assetTypeName: string): REFERENCE_ASSET => { if (assetTypeName.toUpperCase() === 'UNKNOWN') { From f2a151164b72e3b2df4e96486c3a95790de92a47 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Thu, 29 Aug 2024 12:03:36 +0200 Subject: [PATCH 2/5] Avoid initializing ethers contract: hotspot for pools page --- src/pools/PoolTemplate.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pools/PoolTemplate.ts b/src/pools/PoolTemplate.ts index ac702e8d..08795dc4 100644 --- a/src/pools/PoolTemplate.ts +++ b/src/pools/PoolTemplate.ts @@ -256,9 +256,8 @@ export class PoolTemplate { public rewardsOnly(): boolean { if (curve.chainId === 2222 || curve.chainId === 324) return true; // TODO remove this for Kava and ZkSync if (this.gauge.address === curve.constants.ZERO_ADDRESS) throw Error(`${this.name} doesn't have gauge`); - const gaugeContract = curve.contracts[this.gauge.address].contract; - - return !('inflation_rate()' in gaugeContract || 'inflation_rate(uint256)' in gaugeContract); + return !findAbiFunction(curve.contracts[this.gauge.address].abi, 'inflation_rate') + .find((func) => ['', 'uint256'].includes(func.inputs.map(a => `${a.type}`).join(','))) } private statsParameters = async (): Promise<{ From f57ab093acd81656ef19c333523796dbe6142d10 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Fri, 30 Aug 2024 13:45:32 +0200 Subject: [PATCH 3/5] Revert eslint upgrade --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index a45b66a7..a346b33a 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,14 @@ "@types/memoizee": "^0.4.11", "@types/mocha": "^10.0.7", "@types/node": "^22.5.1", - "@typescript-eslint/eslint-plugin": "^8.3.0", - "@typescript-eslint/parser": "^8.3.0", + "@typescript-eslint/eslint-plugin": "^4.33.0", + "@typescript-eslint/parser": "^4.20.0", "babel-eslint": "^10.1.0", "chai": "^5.1.1", - "eslint": "^9.9.1", + "eslint": "^7.32.0", "mocha": "^10.7.3", "typescript": "^5.5.4", - "vue-eslint-parser": "^9.4.3" + "vue-eslint-parser": "^7.6.0" }, "dependencies": { "@curvefi/ethcall": "6.0.7", From a7861745e74b43d583a5e1ae49b2695fa68e9d0d Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 3 Sep 2024 13:03:45 +0200 Subject: [PATCH 4/5] Revert eslint upgrade, add lint workflow --- .github/workflows/lint.yml | 15 +++++++++++++++ package.json | 26 +++++++++++++------------- src/curve.ts | 3 +-- src/interfaces.ts | 22 +++++++++++++++++++++- src/pools/PoolTemplate.ts | 2 +- src/utils.ts | 4 ++-- 6 files changed, 53 insertions(+), 19 deletions(-) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..4169a713 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,15 @@ +name: Lint +on: + push: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: '20' + - run: npm install + - run: npm run build + - run: npm run lint diff --git a/package.json b/package.json index a346b33a..943770c6 100644 --- a/package.json +++ b/package.json @@ -14,29 +14,29 @@ "url": "https://github.com/curvefi/curve-js/issues" }, "scripts": { - "build": "rm -rf lib && tsc -p tsconfig.build.json" + "build": "rm -rf lib && tsc -p tsconfig.build.json", + "lint": "eslint src --ext .ts" }, "type": "module", "devDependencies": { - "@types/chai": "^4.3.19", - "@types/memoizee": "^0.4.11", - "@types/mocha": "^10.0.7", - "@types/node": "^22.5.1", + "@types/chai": "^4.3.4", + "@types/memoizee": "^0.4.7", + "@types/mocha": "^10.0.1", + "@types/node": "^14.14.37", "@typescript-eslint/eslint-plugin": "^4.33.0", "@typescript-eslint/parser": "^4.20.0", "babel-eslint": "^10.1.0", - "chai": "^5.1.1", + "chai": "^4.3.7", "eslint": "^7.32.0", - "mocha": "^10.7.3", - "typescript": "^5.5.4", + "mocha": "^10.2.0", + "typescript": "^4.5.2", "vue-eslint-parser": "^7.6.0" }, "dependencies": { - "@curvefi/ethcall": "6.0.7", - "abitype": "^1.0.6", "axios": "^0.21.1", - "bignumber.js": "^9.1.2", - "ethers": "^6.13.2", - "memoizee": "^0.4.17" + "bignumber.js": "^9.0.1", + "@curvefi/ethcall": "6.0.7", + "ethers": "^6.11.0", + "memoizee": "^0.4.15" } } diff --git a/src/curve.ts b/src/curve.ts index 86d19a88..667f906e 100644 --- a/src/curve.ts +++ b/src/curve.ts @@ -9,13 +9,12 @@ import { JsonRpcProvider, Signer, } from "ethers"; -import type { Abi } from "abitype"; import { Provider as MulticallProvider, Contract as MulticallContract } from "@curvefi/ethcall"; import { getFactoryPoolData } from "./factory/factory.js"; import { getFactoryPoolsDataFromApi } from "./factory/factory-api.js"; import { getCryptoFactoryPoolData } from "./factory/factory-crypto.js"; import { getTricryptoFactoryPoolData } from "./factory/factory-tricrypto.js"; -import { IPoolData, IDict, ICurve, INetworkName, IChainId, IFactoryPoolType } from "./interfaces"; +import {IPoolData, IDict, ICurve, INetworkName, IChainId, IFactoryPoolType, Abi} from "./interfaces"; import ERC20Abi from './constants/abis/ERC20.json' assert { type: 'json' }; import cERC20Abi from './constants/abis/cERC20.json' assert { type: 'json' }; import yERC20Abi from './constants/abis/yERC20.json' assert { type: 'json' }; diff --git a/src/interfaces.ts b/src/interfaces.ts index ee733716..945ca146 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -270,4 +270,24 @@ export interface IBasePoolShortItem { token: string, } -export type TVoteType = "PARAMETER" | "OWNERSHIP" \ No newline at end of file +export type TVoteType = "PARAMETER" | "OWNERSHIP" + +export type AbiParameter = { type: string, name?:string, components?: readonly AbiParameter[] } +type CtorMutability = 'payable' | 'nonpayable'; +export type AbiStateMutability = 'pure' | 'view' | CtorMutability +export type AbiFunction = { + type: 'function' + constant?: boolean + gas?: number + inputs: readonly AbiParameter[] + name: string + outputs: readonly AbiParameter[] + payable?: boolean | undefined + stateMutability: AbiStateMutability +} +export type AbiConstructor = { type: 'constructor', inputs: readonly AbiParameter[], payable?: boolean, stateMutability: CtorMutability } +export type AbiFallback = { type: 'fallback', payable?: boolean, stateMutability: CtorMutability } +export type AbiReceive = {type: 'receive', stateMutability: Extract} +export type AbiEvent = {type: 'event', anonymous?: boolean, inputs: readonly AbiParameter[], name: string} +export type AbiError = {type: 'error', inputs: readonly AbiParameter[], name: string} +export type Abi = (AbiConstructor | AbiError | AbiEvent | AbiFallback | AbiFunction | AbiReceive)[] diff --git a/src/pools/PoolTemplate.ts b/src/pools/PoolTemplate.ts index 08795dc4..32583f6a 100644 --- a/src/pools/PoolTemplate.ts +++ b/src/pools/PoolTemplate.ts @@ -257,7 +257,7 @@ export class PoolTemplate { if (curve.chainId === 2222 || curve.chainId === 324) return true; // TODO remove this for Kava and ZkSync if (this.gauge.address === curve.constants.ZERO_ADDRESS) throw Error(`${this.name} doesn't have gauge`); return !findAbiFunction(curve.contracts[this.gauge.address].abi, 'inflation_rate') - .find((func) => ['', 'uint256'].includes(func.inputs.map(a => `${a.type}`).join(','))) + .find((func) => ['', 'uint256'].includes(func.inputs.map((a) => `${a.type}`).join(','))) } private statsParameters = async (): Promise<{ diff --git a/src/utils.ts b/src/utils.ts index e7163790..71b02fa3 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,9 +1,9 @@ import axios from 'axios'; import {Contract} from 'ethers'; import {Contract as MulticallContract} from "@curvefi/ethcall"; -import type { Abi, AbiFunction } from "abitype"; import BigNumber from 'bignumber.js'; import { + Abi, AbiFunction, IBasePoolShortItem, IChainId, IDict, @@ -717,7 +717,7 @@ export const findAbiFunction = (abi: Abi, methodName: string) => export const getCountArgsOfMethodByAbi = (abi: Abi, methodName: string): number => findAbiFunction(abi, methodName)[0]?.inputs.length ?? -1 export const findAbiSignature = (abi: Abi, methodName: string, signature: string) => - findAbiFunction(abi, methodName).find((func) => func.inputs.map(i => `${i.type}`).join(',') == signature) + findAbiFunction(abi, methodName).find((func) => func.inputs.map((i) => `${i.type}`).join(',') == signature) export const getPoolName = (name: string): string => { const separatedName = name.split(": ") From 73b38aace2e45b9d0c26659d31cebb248dda27e1 Mon Sep 17 00:00:00 2001 From: Daniel Schiavini Date: Tue, 3 Sep 2024 15:43:24 +0200 Subject: [PATCH 5/5] Bump version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 943770c6..09406a0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@curvefi/api", - "version": "2.63.1", + "version": "2.63.2", "description": "JavaScript library for curve.fi", "main": "lib/index.js", "author": "Macket",