From fe369910b7297de371f2c4c0a73146a7b1799241 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 28 Aug 2023 19:06:15 +0700 Subject: [PATCH 1/5] feat: add caching by additional args, add more cache --- packages/sdk/src/common/decorators/cache.ts | 30 ++++++++++--- packages/sdk/src/core/core.ts | 4 +- packages/sdk/src/staking/staking.ts | 50 +++++++++------------ packages/sdk/src/staking/types.ts | 5 +-- 4 files changed, 50 insertions(+), 39 deletions(-) diff --git a/packages/sdk/src/common/decorators/cache.ts b/packages/sdk/src/common/decorators/cache.ts index a4fe3272..ad470d4a 100644 --- a/packages/sdk/src/common/decorators/cache.ts +++ b/packages/sdk/src/common/decorators/cache.ts @@ -1,6 +1,23 @@ import { callConsoleMessage } from "./utils.js"; -export const Cache = function (timeMs = 0) { +const serializeArgs = (...args: any[]) => + args.map((arg: any) => arg.toString()).join(":"); + +const getDecoratorArgsString = function (this: This, args?: string[]) { + if (!args) return ""; + + const argsString = args.map((arg) => { + const field = arg + .split(".") + .reduce((a, b) => (a as { [key: string]: any })[b], this); + + return arg && typeof field === "function" ? field.call(this) : field; + }); + + return serializeArgs(argsString); +}; + +export const Cache = function (timeMs = 0, cacheArgs?: string[]) { const cache = new Map(); return function CacheMethod( @@ -8,12 +25,13 @@ export const Cache = function (timeMs = 0) { context: ClassMethodDecoratorContext< This, (this: This, ...args: Args) => Return - > + >, ) { const methodName = String(context.name); const replacementMethod = function (this: This, ...args: Args): Return { - const hash = JSON.stringify(args); - const cacheKey = `${methodName}:${hash}`; + const decoratorArgsKey = getDecoratorArgsString.call(this, cacheArgs); + const argsKey = JSON.stringify(args); + const cacheKey = `${methodName}:${decoratorArgsKey}:${argsKey}`; if (cache.has(cacheKey)) { const cachedEntry = cache.get(cacheKey); @@ -22,13 +40,13 @@ export const Cache = function (timeMs = 0) { if (cachedEntry && currentTime - cachedEntry.timestamp <= timeMs) { callConsoleMessage( "Cache:", - `Using cache for method '${methodName}'.` + `Using cache for method '${methodName}'.`, ); return cachedEntry.data; } else { callConsoleMessage( "Cache:", - `Cache for method '${methodName}' has expired.` + `Cache for method '${methodName}' has expired.`, ); cache.delete(cacheKey); } diff --git a/packages/sdk/src/core/core.ts b/packages/sdk/src/core/core.ts index f878a2c7..01b39ef8 100644 --- a/packages/sdk/src/core/core.ts +++ b/packages/sdk/src/core/core.ts @@ -92,6 +92,7 @@ export default class LidoSDKCore { }); } + @Logger("Provider:") public createWeb3Provider(chain: Chain): WalletClient { return createWalletClient({ chain, @@ -120,6 +121,7 @@ export default class LidoSDKCore { // Balances @Logger("Balances:") + @Cache(10 * 1000, ["chain.id"]) public async balanceETH(address: Address): Promise { invariant(this.rpcProvider, "RPC provider is not defined"); @@ -164,7 +166,7 @@ export default class LidoSDKCore { @ErrorHandler("Utils:") @Logger("Utils:") - @Cache(60 * 60 * 1000) + @Cache(60 * 60 * 1000, ["chain.id"]) public async isContract(address: Address): Promise { invariant(this.rpcProvider, "RPC provider is not defined"); const { isContract } = await checkIsContract(this.rpcProvider, address); diff --git a/packages/sdk/src/staking/staking.ts b/packages/sdk/src/staking/staking.ts index f80d5249..3631e380 100644 --- a/packages/sdk/src/staking/staking.ts +++ b/packages/sdk/src/staking/staking.ts @@ -27,14 +27,10 @@ import { StakeProps, StakeResult, StakeEncodeDataProps, - StakePopulateTxProps, } from "./types.js"; export class LidoSDKStaking { readonly core: LidoSDKCore; - protected contractStETH: - | GetContractReturnType - | undefined; constructor(props: LidoSDKStakingProps) { const { core, ...rest } = props; @@ -46,6 +42,7 @@ export class LidoSDKStaking { // Balances @Logger("Balances:") + @Cache(10 * 1000) public balanceStETH(address: Address): Promise { return this.getContractStETH().read.balanceOf([address]); } @@ -53,28 +50,25 @@ export class LidoSDKStaking { // Contracts @Logger("Contracts:") - @Cache(60 * 1000) + @Cache(30 * 60 * 1000, ["core.chain.id"]) public contractAddressStETH(): Address { invariant(this.core.chain, "Chain is not defined"); return getTokenAddress(this.core.chain?.id, TOKENS.STETH) as Address; } @Logger("Contracts:") + @Cache(30 * 60 * 1000, ["core.chain.id", "contractAddressStETH"]) public getContractStETH(): GetContractReturnType< typeof abi, PublicClient, WalletClient > { - if (this.contractStETH) return this.contractStETH; - - this.contractStETH = getContract({ + return getContract({ address: this.contractAddressStETH(), abi: abi, publicClient: this.core.rpcProvider, walletClient: this.core.web3Provider, }); - - return this.contractStETH; } // Calls @@ -84,10 +78,9 @@ export class LidoSDKStaking { public async stake(props: StakeProps): Promise { invariant(this.core.web3Provider, "Web3 provider is not defined"); - const { callback } = props; + const { callback, account } = props; try { - const address = await this.core.getWeb3Address(); - const isContract = await this.core.isContract(address); + const isContract = await this.core.isContract(account); if (isContract) return await this.stakeMultisig(props); else return await this.stakeEOA(props); @@ -107,7 +100,7 @@ export class LidoSDKStaking { @ErrorHandler("Call:") @Logger("LOG:") private async stakeEOA(props: StakeProps): Promise { - const { value, callback, referralAddress = zeroAddress } = props; + const { value, callback, referralAddress = zeroAddress, account } = props; invariant(this.core.rpcProvider, "RPC provider is not defined"); invariant(this.core.web3Provider, "Web3 provider is not defined"); @@ -115,8 +108,9 @@ export class LidoSDKStaking { await this.validateStakeLimit(value); const { gasLimit, overrides } = await this.submitGasLimit( + account, value, - referralAddress + referralAddress, ); const address = await this.core.getWeb3Address(); @@ -130,7 +124,7 @@ export class LidoSDKStaking { chain: this.core.chain, gas: gasLimit, account: address, - } + }, ); callback?.({ stage: StakeCallbackStage.RECEIPT, payload: transaction }); @@ -170,7 +164,7 @@ export class LidoSDKStaking { value: parseEther(value), chain: this.core.chain, account: address, - } + }, ); callback?.({ stage: StakeCallbackStage.MULTISIG_DONE }); @@ -181,7 +175,7 @@ export class LidoSDKStaking { @ErrorHandler("Call:") @Logger("Call:") public async stakeSimulateTx( - props: StakePopulateTxProps + props: StakeProps, ): Promise { const { referralAddress = zeroAddress, value, account } = props; @@ -200,8 +194,8 @@ export class LidoSDKStaking { // Views @ErrorHandler("Views:") - @Cache(30 * 1000) @Logger("Views:") + @Cache(30 * 1000, ["core.chain.id"]) public getStakeLimitInfo() { return this.getContractStETH().read.getStakeLimitFullInfo(); } @@ -209,11 +203,12 @@ export class LidoSDKStaking { // Utils @ErrorHandler("Call:") - @Cache(30 * 1000) @Logger("Utils:") + @Cache(30 * 1000, ["core.chain.id"]) private async submitGasLimit( + account: Address, value: string, - referralAddress: Address = zeroAddress + referralAddress: Address = zeroAddress, ): Promise<{ gasLimit: bigint | undefined; overrides: { @@ -225,11 +220,10 @@ export class LidoSDKStaking { }> { invariant(this.core.web3Provider, "Web3 provider is not defined"); - const address = await this.core.getWeb3Address(); const feeData = await this.core.getFeeData(); const overrides = { - account: address, + account, value: parseEther(value), maxPriorityFeePerGas: feeData.maxPriorityFeePerGas ?? undefined, maxFeePerGas: feeData.maxFeePerGas ?? undefined, @@ -237,13 +231,13 @@ export class LidoSDKStaking { const originalGasLimit = await this.getContractStETH().estimateGas.submit( [referralAddress], - overrides + overrides, ); const gasLimit = originalGasLimit ? BigInt( Math.ceil( - Number(originalGasLimit) * SUBMIT_EXTRA_GAS_TRANSACTION_RATIO - ) + Number(originalGasLimit) * SUBMIT_EXTRA_GAS_TRANSACTION_RATIO, + ), ) : undefined; @@ -265,7 +259,7 @@ export class LidoSDKStaking { @ErrorHandler("Utils:") @Logger("Utils:") - public stakeEncodeData(props: StakeEncodeDataProps): Hash { + private stakeEncodeData(props: StakeEncodeDataProps): Hash { const { referralAddress = zeroAddress } = props; return encodeFunctionData({ @@ -278,7 +272,7 @@ export class LidoSDKStaking { @ErrorHandler("Utils:") @Logger("Utils:") public stakePopulateTx( - props: StakePopulateTxProps + props: StakeProps, ): Omit { const { referralAddress = zeroAddress, value, account } = props; diff --git a/packages/sdk/src/staking/types.ts b/packages/sdk/src/staking/types.ts index aa503e2f..768ce003 100644 --- a/packages/sdk/src/staking/types.ts +++ b/packages/sdk/src/staking/types.ts @@ -27,6 +27,7 @@ export type StakeStageCallback = (props: StakeCallbackProps) => void; export type StakeProps = { value: string; + account: Address; callback?: StakeStageCallback; referralAddress?: Address; }; @@ -40,7 +41,3 @@ export type StakeResult = { export type StakeEncodeDataProps = { referralAddress?: Address; }; - -export type StakePopulateTxProps = StakeProps & { - account: Address; -}; From 66d9c2b8a35a7e7bd5f30b0dfac56d43452e3104 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 28 Aug 2023 19:07:00 +0700 Subject: [PATCH 2/5] fix: fix parsing error --- packages/sdk/src/common/utils/getErrorMessage.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/sdk/src/common/utils/getErrorMessage.ts b/packages/sdk/src/common/utils/getErrorMessage.ts index 4c641cb6..2bc5a47b 100644 --- a/packages/sdk/src/common/utils/getErrorMessage.ts +++ b/packages/sdk/src/common/utils/getErrorMessage.ts @@ -8,7 +8,7 @@ export enum ErrorMessage { } export const getErrorMessage = ( - error: unknown + error: unknown, ): { message: ErrorMessage; code: string | number } => { try { console.error("TX_ERROR:", { error, error_string: JSON.stringify(error) }); @@ -40,7 +40,7 @@ export const getErrorMessage = ( // type safe error code extractor export const extractCodeFromError = ( error: unknown, - shouldDig = true + shouldDig = true, ): number | string => { // early exit on non object error if (!error || typeof error != "object") return 0; @@ -98,5 +98,9 @@ export const extractCodeFromError = ( return extractCodeFromError(error.error, false); } + if ("cause" in error && error.cause) { + return extractCodeFromError(error.cause, false); + } + return 0; }; From b1f503ab96a53efc290fa14f5966cfa22c6a9a63 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 28 Aug 2023 19:13:04 +0700 Subject: [PATCH 3/5] fix: fix cache for steth balance --- packages/sdk/src/staking/staking.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/sdk/src/staking/staking.ts b/packages/sdk/src/staking/staking.ts index 3631e380..5eeab599 100644 --- a/packages/sdk/src/staking/staking.ts +++ b/packages/sdk/src/staking/staking.ts @@ -42,7 +42,7 @@ export class LidoSDKStaking { // Balances @Logger("Balances:") - @Cache(10 * 1000) + @Cache(10 * 1000, ["core.chain.id"]) public balanceStETH(address: Address): Promise { return this.getContractStETH().read.balanceOf([address]); } From 8bbd092cbe0a076f8e771e99a6f42f3e03a2f251 Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 28 Aug 2023 19:19:14 +0700 Subject: [PATCH 4/5] feat: update readme --- README.md | 1 + packages/sdk/README.md | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6f8dca46..8599f79e 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ const stakeResult = await lidoSDK.staking.stake({ value, callback, referralAddress, + account, }); console.log(balanceETH.toString(), "ETH balance"); diff --git a/packages/sdk/README.md b/packages/sdk/README.md index 9eb1d046..e05d8796 100644 --- a/packages/sdk/README.md +++ b/packages/sdk/README.md @@ -110,6 +110,7 @@ const stakeResult = await lidoSDK.staking.stake({ value, callback, referralAddress, + account, }); console.log(balanceStETH.toString(), "stETH balance"); @@ -182,11 +183,12 @@ try { value, callback, referralAddress, + account, }); console.log( stakeResult, - "transaction hash, transaction receipt, confirmations" + "transaction hash, transaction receipt, confirmations", ); } catch (error) { console.log((error as SDKError).errorMessage, (error as SDKError).code); From 9637913175accea9c432a74b050451a798f3c7ea Mon Sep 17 00:00:00 2001 From: Andrei Date: Mon, 28 Aug 2023 19:27:17 +0700 Subject: [PATCH 5/5] fix: add serialization for internal method arguments --- packages/sdk/src/common/decorators/cache.ts | 2 +- packages/sdk/src/staking/staking.ts | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/packages/sdk/src/common/decorators/cache.ts b/packages/sdk/src/common/decorators/cache.ts index ad470d4a..6a9c028a 100644 --- a/packages/sdk/src/common/decorators/cache.ts +++ b/packages/sdk/src/common/decorators/cache.ts @@ -30,7 +30,7 @@ export const Cache = function (timeMs = 0, cacheArgs?: string[]) { const methodName = String(context.name); const replacementMethod = function (this: This, ...args: Args): Return { const decoratorArgsKey = getDecoratorArgsString.call(this, cacheArgs); - const argsKey = JSON.stringify(args); + const argsKey = serializeArgs(args); const cacheKey = `${methodName}:${decoratorArgsKey}:${argsKey}`; if (cache.has(cacheKey)) { diff --git a/packages/sdk/src/staking/staking.ts b/packages/sdk/src/staking/staking.ts index 5eeab599..3f51a06d 100644 --- a/packages/sdk/src/staking/staking.ts +++ b/packages/sdk/src/staking/staking.ts @@ -112,7 +112,6 @@ export class LidoSDKStaking { value, referralAddress, ); - const address = await this.core.getWeb3Address(); callback?.({ stage: StakeCallbackStage.SIGN }); @@ -123,7 +122,7 @@ export class LidoSDKStaking { value: parseEther(value), chain: this.core.chain, gas: gasLimit, - account: address, + account, }, ); @@ -152,9 +151,7 @@ export class LidoSDKStaking { @ErrorHandler("Call:") @Logger("LOG:") private async stakeMultisig(props: StakeProps): Promise { - const { value, callback, referralAddress = zeroAddress } = props; - - const address = await this.core.getWeb3Address(); + const { value, callback, referralAddress = zeroAddress, account } = props; callback?.({ stage: StakeCallbackStage.SIGN }); @@ -163,7 +160,7 @@ export class LidoSDKStaking { { value: parseEther(value), chain: this.core.chain, - account: address, + account, }, );