diff --git a/src/clients/TokenClient.ts b/src/clients/TokenClient.ts index 72a91ec4f..e0b102afa 100644 --- a/src/clients/TokenClient.ts +++ b/src/clients/TokenClient.ts @@ -15,6 +15,7 @@ import { runTransaction, toBN, winston, + getRedisCache, } from "../utils"; type TokenDataType = { [chainId: number]: { [token: string]: { balance: BigNumber; allowance: BigNumber } } }; @@ -183,7 +184,7 @@ export class TokenClient { const [balanceInfo, bondToken] = await Promise.all([ Promise.all(Object.values(this.spokePoolClients).map((spokePoolClient) => this.fetchTokenData(spokePoolClient))), - this.hubPoolClient.hubPool.bondToken(), + this._getBondToken(), ]); this.bondToken = new Contract(bondToken, ERC20.abi, this.hubPoolClient.hubPool.signer); @@ -223,10 +224,7 @@ export class TokenClient { await Promise.all( tokens.map(async (token) => { const balance: BigNumber = await token.balanceOf(this.relayerAddress, { blockTag }); - const allowance: BigNumber = await token.allowance(this.relayerAddress, spokePoolClient.spokePool.address, { - blockTag, - }); - + const allowance = await this._getAllowance(spokePoolClient, token, blockTag); return [token.address, { balance, allowance }]; }) ) @@ -235,6 +233,57 @@ export class TokenClient { return { tokenData, chainId: spokePoolClient.chainId }; } + private async _getAllowanceCacheKey(spokePoolClient: SpokePoolClient, originToken: string): Promise { + return `l2TokenAllowance_${ + spokePoolClient.chainId + }_${originToken}_${await spokePoolClient.spokePool.signer.getAddress()}_targetContract:${ + spokePoolClient.spokePool.address + }`; + } + + private async _getAllowance( + spokePoolClient: SpokePoolClient, + token: Contract, + blockTag: number | "latest" + ): Promise { + const redis = await getRedisCache(this.logger); + if (redis) { + const result = await redis.get(await this._getAllowanceCacheKey(spokePoolClient, token.address)); + if (result !== null) { + return toBN(result); + } + } + const allowance: BigNumber = await token.allowance(this.relayerAddress, spokePoolClient.spokePool.address, { + blockTag, + }); + if (allowance.gte(MAX_SAFE_ALLOWANCE) && redis) { + // Save allowance in cache with no TTL as these should be exhausted. + await redis.set(await this._getAllowanceCacheKey(spokePoolClient, token.address), MAX_SAFE_ALLOWANCE); + } + return allowance; + } + + _getBondTokenCacheKey(): string { + return `bondToken_${this.hubPoolClient.hubPool.address}`; + } + + private async _getBondToken(): Promise { + const redis = await getRedisCache(this.logger); + if (redis) { + const cachedBondToken = await redis.get(this._getBondTokenCacheKey()); + if (cachedBondToken !== null) { + return cachedBondToken; + } + } + const bondToken: string = await this.hubPoolClient.hubPool.bondToken(); + if (redis) { + // The bond token should not change, and using the wrong bond token will be immediately obvious, so cache with + // infinite TTL. + await redis.set(this._getBondTokenCacheKey(), bondToken); + } + return bondToken; + } + private _hasTokenPairData(chainId: number, token: string) { const hasData = !!this.tokenData?.[chainId]?.[token]; if (!hasData) {