From d411e4168cd283243a4a77d442579228886fea5a Mon Sep 17 00:00:00 2001 From: doomsower <12031673+doomsower@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:48:57 -0400 Subject: [PATCH] chore: merge things from main --- package.json | 26 +- src/services/Client.ts | 14 +- src/services/RedstoneServiceV3.ts | 76 +- .../liquidate/SingularPartialLiquidator.ts | 39 +- src/utils/formatters.ts | 12 +- yarn.lock | 1280 ++++++++--------- 6 files changed, 731 insertions(+), 716 deletions(-) diff --git a/package.json b/package.json index 7b54975..e489391 100644 --- a/package.json +++ b/package.json @@ -18,44 +18,44 @@ "test": "vitest" }, "dependencies": { - "pino-pretty": "^11.2.1", - "node-pty": "^1.0.0" + "node-pty": "^1.0.0", + "pino-pretty": "^11.2.2" }, "devDependencies": { - "@aws-sdk/client-s3": "^3.614.0", + "@aws-sdk/client-s3": "^3.620.1", "@commitlint/cli": "^19.3.0", "@commitlint/config-conventional": "^19.2.2", "@flashbots/ethers-provider-bundle": "^1.0.0", "@gearbox-protocol/eslint-config": "2.0.0-next.2", "@gearbox-protocol/liquidator-v2-contracts": "^2.1.0-next.17", "@gearbox-protocol/prettier-config": "2.0.0-next.0", - "@gearbox-protocol/sdk-gov": "^2.10.1", + "@gearbox-protocol/sdk-gov": "^2.13.0", "@gearbox-protocol/types": "^1.9.2", - "@redstone-finance/evm-connector": "^0.5.4", - "@types/node": "^20.14.10", + "@redstone-finance/evm-connector": "^0.6.1", + "@types/node": "^22.0.0", "@uniswap/sdk-core": "^5.3.1", "@uniswap/v3-sdk": "^3.13.1", "@vlad-yakovlev/telegram-md": "^2.0.0", "abitype": "^1.0.5", "axios": "^1.7.2", - "axios-retry": "^4.4.0", + "axios-retry": "^4.4.2", "date-fns": "^3.6.0", "di-at-home": "^0.0.7", "dotenv": "^16.4.5", "esbuild": "^0.23.0", "eslint": "^8.57.0", - "ethers": "^6.13.1", - "husky": "^9.0.11", + "ethers": "^6.13.2", + "husky": "^9.1.4", "lint-staged": "^15.2.7", "nanoid": "^5.0.7", "node-pty": "^1.0.0", - "pino": "^9.3.0", + "pino": "^9.3.2", "prettier": "^3.3.3", "redstone-protocol": "^1.0.5", "tsx": "^4.16.2", - "typescript": "^5.5.3", - "viem": "^2.17.4", - "vitest": "^2.0.3" + "typescript": "^5.5.4", + "viem": "^2.18.5", + "vitest": "^2.0.4" }, "prettier": "@gearbox-protocol/prettier-config", "lint-staged": { diff --git a/src/services/Client.ts b/src/services/Client.ts index d189a04..cd74e09 100644 --- a/src/services/Client.ts +++ b/src/services/Client.ts @@ -5,10 +5,12 @@ import { PERCENTAGE_FACTOR } from "@gearbox-protocol/sdk-gov"; import type { Abi } from "abitype"; import type { Address, + Block, Chain, ContractFunctionArgs, ContractFunctionName, EncodeFunctionDataParameters, + Hex, PrivateKeyAccount, PublicClient, SimulateContractParameters, @@ -69,6 +71,11 @@ type AnvilRPCSchema = [ Parameters: []; ReturnType: AnvilNodeInfo; }, + { + Method: "evm_mine_detailed"; + Parameters: [Hex]; + ReturnType: Block[]; + }, ]; const CHAINS: Record = { @@ -107,7 +114,12 @@ export default class Client { public async launch(): Promise { const { ethProviderRpcs, chainId, network, optimistic, privateKey } = this.config; - const rpcs = ethProviderRpcs.map(url => http(url, { timeout: 120_000 })); + const rpcs = ethProviderRpcs.map(url => + http(url, { + timeout: optimistic ? 240_000 : 10_000, + retryCount: optimistic ? 3 : undefined, + }), + ); const transport = rpcs.length > 1 && !optimistic ? fallback(rpcs) : rpcs[0]; const chain = defineChain({ ...CHAINS[network], diff --git a/src/services/RedstoneServiceV3.ts b/src/services/RedstoneServiceV3.ts index dd3af97..4c20bc4 100644 --- a/src/services/RedstoneServiceV3.ts +++ b/src/services/RedstoneServiceV3.ts @@ -16,6 +16,7 @@ import { encodeFunctionData, parseAbiParameters, toBytes, + toHex, } from "viem"; import type { Config } from "../config/index.js"; @@ -38,10 +39,16 @@ export type RedstonePriceFeed = Extract< { type: PriceFeedType.REDSTONE_ORACLE } >; +const HISTORICAL_BLOCKLIST = new Set([ + // "rsETH_FUNDAMENTAL", + // "weETH_FUNDAMENTAL", + // "ezETH_FUNDAMENTAL", +]); + @DI.Injectable(DI.Redstone) export class RedstoneServiceV3 { @Logger("Redstone") - log!: ILogger; + logger!: ILogger; @DI.Inject(DI.Config) config!: Config; @@ -56,7 +63,7 @@ export class RedstoneServiceV3 { client!: Client; /** - * Timestamp to use to get historical data instead in optimistic mode, so that we use the same redstone data for all the liquidations + * Timestamp (in ms) to use to get historical data instead in optimistic mode, so that we use the same redstone data for all the liquidations */ #optimisticTimestamp?: number; #optimisticCache: Map = new Map(); @@ -69,14 +76,25 @@ export class RedstoneServiceV3 { blockNumber: this.client.anvilForkBlock, }); if (!block) { - throw new Error(`cannot get latest block`); + throw new Error("cannot get latest block"); } + // https://github.com/redstone-finance/redstone-oracles-monorepo/blob/c7569a8eb7da1d3ad6209dfcf59c7ca508ea947b/packages/sdk/src/request-data-packages.ts#L82 // we round the timestamp to full minutes for being compatible with // oracle-nodes, which usually work with rounded 10s and 60s intervals - this.#optimisticTimestamp = - 10 * Math.floor(Number(block.timestamp) / 10) * 1000; - this.log.info( - `will use optimistic timestamp: ${this.#optimisticTimestamp}`, + // + // Also, when forking anvil->anvil (when running on testnets) block.timestamp can be in future because min ts for block is 1 seconds, + // and scripts can take dozens of blocks (hundreds for faucet). So we take min value; + const nowMs = new Date().getTime(); + const redstoneIntervalMs = 60_000; + const anvilTsMs = + redstoneIntervalMs * + Math.floor((Number(block.timestamp) * 1000) / redstoneIntervalMs); + const fromNowTsMs = + redstoneIntervalMs * Math.floor(nowMs / redstoneIntervalMs - 1); + this.#optimisticTimestamp = Math.min(anvilTsMs, fromNowTsMs); + const deltaS = Math.floor((nowMs - this.#optimisticTimestamp) / 1000); + this.logger.info( + `will use optimistic timestamp: ${new Date(this.#optimisticTimestamp)} (${this.#optimisticTimestamp}, delta: ${deltaS}s)`, ); } } @@ -84,7 +102,9 @@ export class RedstoneServiceV3 { public async updatesForTokens( tokens: string[], activeOnly: boolean, + logContext: Record = {}, ): Promise { + const logger = this.logger.child(logContext); const redstoneFeeds = this.oracle.getRedstoneFeeds(activeOnly); const tickers = tickerInfoTokensByNetwork[this.config.network]; @@ -100,8 +120,8 @@ export class RedstoneServiceV3 { const ticker = tickers[symb]; if (ticker) { if (this.oracle.hasFeed(ticker.address)) { - this.log.debug( - ticker, + logger.debug( + { ticker }, `will update redstone ticker ${ticker.symbol} for ${symb}`, ); redstoneUpdates.push({ @@ -110,7 +130,7 @@ export class RedstoneServiceV3 { reserve: false, // tickers are always added as main feed }); } else { - this.log.debug( + logger.debug( `ticker ${ticker.symbol} for ${symb} is not registered in price oracle, skipping`, ); } @@ -120,7 +140,7 @@ export class RedstoneServiceV3 { return []; } - this.log?.debug( + logger.debug( `need to update ${redstoneUpdates.length} redstone feeds: ${printFeeds(redstoneUpdates)}`, ); const result = await Promise.all( @@ -131,6 +151,7 @@ export class RedstoneServiceV3 { "redstone-primary-prod", dataFeedId, REDSTONE_SIGNERS.signersThreshold, + logContext, ), ), ); @@ -148,20 +169,20 @@ export class RedstoneServiceV3 { const realtimeDelta = Math.floor( new Date().getTime() / 1000 - redstoneTs, ); - this.log.debug( + logger.debug( { tag: "timing" }, `redstone delta ${delta} (realtime ${realtimeDelta}) for block ${formatTs(block)}: ${result.map(formatTs)}`, ); if (delta < 0) { - this.log?.debug( + logger.debug( { tag: "timing" }, `warp, because block ts ${formatTs(block)} < ${formatTs(redstoneTs)} redstone ts (${Math.ceil(-delta / 60)} min)`, ); - await this.client.anvil.setNextBlockTimestamp({ - timestamp: BigInt(redstoneTs), + [block] = await this.client.anvil.request({ + method: "evm_mine_detailed", + params: [toHex(redstoneTs)], }); - block = await this.client.pub.getBlock({ blockTag: "latest" }); - this.log?.debug({ tag: "timing" }, `new block ts: ${formatTs(block)}`); + logger.debug({ tag: "timing" }, `new block ts: ${formatTs(block)}`); } } @@ -200,7 +221,11 @@ export class RedstoneServiceV3 { accTokens.push(token); } } - const priceUpdates = await this.updatesForTokens(accTokens, activeOnly); + const priceUpdates = await this.updatesForTokens(accTokens, activeOnly, { + account: ca.addr, + borrower: ca.borrower, + manager: ca.managerName, + }); return priceUpdates.map(({ token, reserve, callData }) => ({ token: token as Address, reserve, @@ -214,7 +239,10 @@ export class RedstoneServiceV3 { dataServiceId: string, dataFeedId: string, uniqueSignersCount: number, + logContext: Record = {}, ): Promise { + const logger = this.logger.child(logContext); + const cacheAllowed = this.config.optimistic; const key = redstoneCacheKey( token, reserve, @@ -222,18 +250,20 @@ export class RedstoneServiceV3 { dataFeedId, uniqueSignersCount, ); - if (this.config.optimistic) { + if (cacheAllowed) { if (this.#optimisticCache.has(key)) { - this.log.debug(`using cached response for ${key}`); + logger.debug(`using cached response for ${key}`); return this.#optimisticCache.get(key)!; } } const dataPayload = await new DataServiceWrapper({ dataServiceId, - dataFeeds: [dataFeedId], + dataPackagesIds: [dataFeedId], uniqueSignersCount, - historicalTimestamp: this.#optimisticTimestamp, + historicalTimestamp: HISTORICAL_BLOCKLIST.has(dataFeedId) + ? undefined + : this.#optimisticTimestamp, }).prepareRedstonePayload(true); const { signedDataPackages, unsignedMetadata } = RedstonePayload.parse( @@ -277,7 +307,7 @@ export class RedstoneServiceV3 { ts: result[0][1], }; - if (this.config.optimistic) { + if (cacheAllowed) { this.#optimisticCache.set(key, response); } diff --git a/src/services/liquidate/SingularPartialLiquidator.ts b/src/services/liquidate/SingularPartialLiquidator.ts index aae9cfc..0e51770 100644 --- a/src/services/liquidate/SingularPartialLiquidator.ts +++ b/src/services/liquidate/SingularPartialLiquidator.ts @@ -27,7 +27,7 @@ import { iDegenDistributorV3Abi, } from "@gearbox-protocol/types/abi"; import type { Address, SimulateContractReturnType } from "viem"; -import { getContract, parseEther } from "viem"; +import { parseEther } from "viem"; import { type CreditAccountData, @@ -41,7 +41,7 @@ import type { MerkleDistributorInfo, PartialLiquidationPreview, } from "./types.js"; -import type { IPriceHelperContract, TokenPriceInfo } from "./viem-types.js"; +import type { TokenPriceInfo } from "./viem-types.js"; interface TokenBalance extends ExcludeArrayProps { /** @@ -55,7 +55,7 @@ export default class SingularPartialLiquidator extends SingularLiquidator = {}; @@ -71,12 +71,8 @@ export default class SingularPartialLiquidator extends SingularLiquidator { + ): Promise { let partialLiquidatorAddress = this.config.partialLiquidatorAddress; if (!partialLiquidatorAddress) { this.logger.debug("deploying partial liquidator"); @@ -426,10 +424,10 @@ export default class SingularPartialLiquidator extends SingularLiquidator { + async #deployPriceHelper(): Promise { if (!this.config.optimistic) { return undefined; } @@ -452,12 +450,7 @@ export default class SingularPartialLiquidator extends SingularLiquidator