Skip to content

Commit

Permalink
Merge pull request #13 from lidofinance/feature/si-739-improve-cache
Browse files Browse the repository at this point in the history
Feature/si 739 improve cache
  • Loading branch information
DiRaiks authored Aug 28, 2023
2 parents 8f2651f + 9637913 commit ca38a02
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 48 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ const stakeResult = await lidoSDK.staking.stake({
value,
callback,
referralAddress,
account,
});

console.log(balanceETH.toString(), "ETH balance");
Expand Down
4 changes: 3 additions & 1 deletion packages/sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ const stakeResult = await lidoSDK.staking.stake({
value,
callback,
referralAddress,
account,
});

console.log(balanceStETH.toString(), "stETH balance");
Expand Down Expand Up @@ -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);
Expand Down
30 changes: 24 additions & 6 deletions packages/sdk/src/common/decorators/cache.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
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: 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<string, { data: any; timestamp: number }>();

return function CacheMethod<This, Args extends any[], Return>(
originalMethod: (this: This, ...args: Args) => Return,
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 = serializeArgs(args);
const cacheKey = `${methodName}:${decoratorArgsKey}:${argsKey}`;

if (cache.has(cacheKey)) {
const cachedEntry = cache.get(cacheKey);
Expand All @@ -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);
}
Expand Down
8 changes: 6 additions & 2 deletions packages/sdk/src/common/utils/getErrorMessage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) });
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
};
4 changes: 3 additions & 1 deletion packages/sdk/src/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export default class LidoSDKCore {
});
}

@Logger("Provider:")
public createWeb3Provider(chain: Chain): WalletClient {
return createWalletClient({
chain,
Expand Down Expand Up @@ -120,6 +121,7 @@ export default class LidoSDKCore {
// Balances

@Logger("Balances:")
@Cache(10 * 1000, ["chain.id"])
public async balanceETH(address: Address): Promise<bigint> {
invariant(this.rpcProvider, "RPC provider is not defined");

Expand Down Expand Up @@ -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<boolean> {
invariant(this.rpcProvider, "RPC provider is not defined");
const { isContract } = await checkIsContract(this.rpcProvider, address);
Expand Down
59 changes: 25 additions & 34 deletions packages/sdk/src/staking/staking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,10 @@ import {
StakeProps,
StakeResult,
StakeEncodeDataProps,
StakePopulateTxProps,
} from "./types.js";

export class LidoSDKStaking {
readonly core: LidoSDKCore;
protected contractStETH:
| GetContractReturnType<typeof abi, PublicClient, WalletClient>
| undefined;

constructor(props: LidoSDKStakingProps) {
const { core, ...rest } = props;
Expand All @@ -46,35 +42,33 @@ export class LidoSDKStaking {
// Balances

@Logger("Balances:")
@Cache(10 * 1000, ["core.chain.id"])
public balanceStETH(address: Address): Promise<bigint> {
return this.getContractStETH().read.balanceOf([address]);
}

// 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
Expand All @@ -84,10 +78,9 @@ export class LidoSDKStaking {
public async stake(props: StakeProps): Promise<StakeResult> {
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);
Expand All @@ -107,18 +100,18 @@ export class LidoSDKStaking {
@ErrorHandler("Call:")
@Logger("LOG:")
private async stakeEOA(props: StakeProps): Promise<StakeResult> {
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");
// Checking the daily protocol staking limit
await this.validateStakeLimit(value);

const { gasLimit, overrides } = await this.submitGasLimit(
account,
value,
referralAddress
referralAddress,
);
const address = await this.core.getWeb3Address();

callback?.({ stage: StakeCallbackStage.SIGN });

Expand All @@ -129,8 +122,8 @@ export class LidoSDKStaking {
value: parseEther(value),
chain: this.core.chain,
gas: gasLimit,
account: address,
}
account,
},
);

callback?.({ stage: StakeCallbackStage.RECEIPT, payload: transaction });
Expand Down Expand Up @@ -158,9 +151,7 @@ export class LidoSDKStaking {
@ErrorHandler("Call:")
@Logger("LOG:")
private async stakeMultisig(props: StakeProps): Promise<StakeResult> {
const { value, callback, referralAddress = zeroAddress } = props;

const address = await this.core.getWeb3Address();
const { value, callback, referralAddress = zeroAddress, account } = props;

callback?.({ stage: StakeCallbackStage.SIGN });

Expand All @@ -169,8 +160,8 @@ export class LidoSDKStaking {
{
value: parseEther(value),
chain: this.core.chain,
account: address,
}
account,
},
);

callback?.({ stage: StakeCallbackStage.MULTISIG_DONE });
Expand All @@ -181,7 +172,7 @@ export class LidoSDKStaking {
@ErrorHandler("Call:")
@Logger("Call:")
public async stakeSimulateTx(
props: StakePopulateTxProps
props: StakeProps,
): Promise<WriteContractParameters> {
const { referralAddress = zeroAddress, value, account } = props;

Expand All @@ -200,20 +191,21 @@ export class LidoSDKStaking {
// Views

@ErrorHandler("Views:")
@Cache(30 * 1000)
@Logger("Views:")
@Cache(30 * 1000, ["core.chain.id"])
public getStakeLimitInfo() {
return this.getContractStETH().read.getStakeLimitFullInfo();
}

// 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: {
Expand All @@ -225,25 +217,24 @@ 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,
};

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;

Expand All @@ -265,7 +256,7 @@ export class LidoSDKStaking {

@ErrorHandler("Utils:")
@Logger("Utils:")
public stakeEncodeData(props: StakeEncodeDataProps): Hash {
private stakeEncodeData(props: StakeEncodeDataProps): Hash {
const { referralAddress = zeroAddress } = props;

return encodeFunctionData({
Expand All @@ -278,7 +269,7 @@ export class LidoSDKStaking {
@ErrorHandler("Utils:")
@Logger("Utils:")
public stakePopulateTx(
props: StakePopulateTxProps
props: StakeProps,
): Omit<FormattedTransactionRequest, "type"> {
const { referralAddress = zeroAddress, value, account } = props;

Expand Down
5 changes: 1 addition & 4 deletions packages/sdk/src/staking/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type StakeStageCallback = (props: StakeCallbackProps) => void;

export type StakeProps = {
value: string;
account: Address;
callback?: StakeStageCallback;
referralAddress?: Address;
};
Expand All @@ -40,7 +41,3 @@ export type StakeResult = {
export type StakeEncodeDataProps = {
referralAddress?: Address;
};

export type StakePopulateTxProps = StakeProps & {
account: Address;
};

0 comments on commit ca38a02

Please sign in to comment.