Skip to content

Commit

Permalink
Merge pull request #390 from curvefi/pool-ctor2
Browse files Browse the repository at this point in the history
perf: avoid initializing ethers contracts during construction
  • Loading branch information
Macket authored Sep 3, 2024
2 parents cd36cb4 + 73b38aa commit cbd848f
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 30 deletions.
15 changes: 15 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -14,7 +14,8 @@
"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": {
Expand Down
7 changes: 5 additions & 2 deletions src/curve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ 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' };
Expand Down Expand Up @@ -419,13 +419,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 };
Expand Down
22 changes: 21 additions & 1 deletion src/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -270,4 +270,24 @@ export interface IBasePoolShortItem {
token: string,
}

export type TVoteType = "PARAMETER" | "OWNERSHIP"
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<AbiStateMutability, 'payable'>}
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)[]
10 changes: 4 additions & 6 deletions src/pools/PoolTemplate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {
smartNumber,
DIGas,
_getAddress,
isMethodExist,
findAbiFunction,
getVolumeApiController,
} from '../utils.js';
import {IDict, IReward, IProfit, IPoolType} from '../interfaces';
Expand Down Expand Up @@ -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<{
Expand Down Expand Up @@ -2323,8 +2322,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]));
}
Expand Down
12 changes: 6 additions & 6 deletions src/pools/poolConstructor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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)) {
Expand All @@ -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);
Expand Down
22 changes: 9 additions & 13 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Contract} from 'ethers';
import {Contract as MulticallContract} from "@curvefi/ethcall";
import BigNumber from 'bignumber.js';
import {
Abi, AbiFunction,
IBasePoolShortItem,
IChainId,
IDict,
Expand Down Expand Up @@ -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),
}
Expand Down Expand Up @@ -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(": ")
Expand All @@ -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') {
Expand Down

0 comments on commit cbd848f

Please sign in to comment.