Skip to content

Commit

Permalink
chore: merge things from main
Browse files Browse the repository at this point in the history
  • Loading branch information
doomsower committed Jul 30, 2024
1 parent 968fc78 commit d411e41
Show file tree
Hide file tree
Showing 6 changed files with 731 additions and 716 deletions.
26 changes: 13 additions & 13 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
14 changes: 13 additions & 1 deletion src/services/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -69,6 +71,11 @@ type AnvilRPCSchema = [
Parameters: [];
ReturnType: AnvilNodeInfo;
},
{
Method: "evm_mine_detailed";
Parameters: [Hex];
ReturnType: Block<Hex>[];
},
];

const CHAINS: Record<NetworkType, Chain> = {
Expand Down Expand Up @@ -107,7 +114,12 @@ export default class Client {
public async launch(): Promise<void> {
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],
Expand Down
76 changes: 53 additions & 23 deletions src/services/RedstoneServiceV3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
encodeFunctionData,
parseAbiParameters,
toBytes,
toHex,
} from "viem";

import type { Config } from "../config/index.js";
Expand All @@ -38,10 +39,16 @@ export type RedstonePriceFeed = Extract<
{ type: PriceFeedType.REDSTONE_ORACLE }
>;

const HISTORICAL_BLOCKLIST = new Set<string>([
// "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;
Expand All @@ -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<string, PriceOnDemandExtras> = new Map();
Expand All @@ -69,22 +76,35 @@ 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)`,
);
}
}

public async updatesForTokens(
tokens: string[],
activeOnly: boolean,
logContext: Record<string, any> = {},
): Promise<PriceOnDemandExtras[]> {
const logger = this.logger.child(logContext);
const redstoneFeeds = this.oracle.getRedstoneFeeds(activeOnly);
const tickers = tickerInfoTokensByNetwork[this.config.network];

Expand All @@ -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({
Expand All @@ -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`,
);
}
Expand All @@ -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(
Expand All @@ -131,6 +151,7 @@ export class RedstoneServiceV3 {
"redstone-primary-prod",
dataFeedId,
REDSTONE_SIGNERS.signersThreshold,
logContext,
),
),
);
Expand All @@ -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)}`);
}
}

Expand Down Expand Up @@ -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,
Expand All @@ -214,26 +239,31 @@ export class RedstoneServiceV3 {
dataServiceId: string,
dataFeedId: string,
uniqueSignersCount: number,
logContext: Record<string, any> = {},
): Promise<PriceOnDemandExtras> {
const logger = this.logger.child(logContext);
const cacheAllowed = this.config.optimistic;
const key = redstoneCacheKey(
token,
reserve,
dataServiceId,
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(
Expand Down Expand Up @@ -277,7 +307,7 @@ export class RedstoneServiceV3 {
ts: result[0][1],
};

if (this.config.optimistic) {
if (cacheAllowed) {
this.#optimisticCache.set(key, response);
}

Expand Down
39 changes: 16 additions & 23 deletions src/services/liquidate/SingularPartialLiquidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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<TokenPriceInfo> {
/**
Expand All @@ -55,7 +55,7 @@ export default class SingularPartialLiquidator extends SingularLiquidator<Partia
protected readonly adverb = "partially";

#partialLiquidator?: Address;
#priceHelper?: IPriceHelperContract;
#priceHelper?: Address;
#configuratorAddr?: Address;
#registeredCMs: Record<Address, boolean> = {};

Expand All @@ -71,12 +71,8 @@ export default class SingularPartialLiquidator extends SingularLiquidator<Partia
contractsByNetwork[this.config.network].AAVE_V3_LENDING_POOL;
this.logger.debug(`router=${router}, bot=${bot}, aave pool = ${aavePool}`);

this.#priceHelper = await this.#deployPriceHelper();
this.#partialLiquidator = await this.#deployPartialLiquidator(
router,
bot,
aavePool,
);
await this.#deployPriceHelper();
await this.#deployPartialLiquidator(router, bot, aavePool);
await this.#configurePartialLiquidator(router, bot);
}

Expand Down Expand Up @@ -234,10 +230,12 @@ export default class SingularPartialLiquidator extends SingularLiquidator<Partia
const priceUpdates = await this.redstone.dataCompressorUpdates(ca);
// this helper contract fetches prices while trying to ignore failed price feeds
// prices here are not critical, as they're used for sorting and estimation and only in optimistic mode
const tokens = await this.priceHelper.simulate.previewTokens([
ca.addr,
priceUpdates,
]);
const tokens = await this.client.pub.simulateContract({
address: this.priceHelper,
abi: [...iPriceHelperAbi, ...exceptionsAbis],
functionName: "previewTokens",
args: [ca.addr, priceUpdates as any],
});
// Sort by weighted value descending, but underlying token comes last
return tokens.result
.map(
Expand Down Expand Up @@ -355,7 +353,7 @@ export default class SingularPartialLiquidator extends SingularLiquidator<Partia
router: Address,
bot: Address,
aavePool: Address,
): Promise<Address> {
): Promise<void> {
let partialLiquidatorAddress = this.config.partialLiquidatorAddress;
if (!partialLiquidatorAddress) {
this.logger.debug("deploying partial liquidator");
Expand Down Expand Up @@ -426,10 +424,10 @@ export default class SingularPartialLiquidator extends SingularLiquidator<Partia
this.logger.info(
`partial liquidator contract addesss: ${partialLiquidatorAddress}`,
);
return partialLiquidatorAddress;
this.#partialLiquidator = partialLiquidatorAddress;
}

async #deployPriceHelper(): Promise<IPriceHelperContract | undefined> {
async #deployPriceHelper(): Promise<void> {
if (!this.config.optimistic) {
return undefined;
}
Expand All @@ -452,12 +450,7 @@ export default class SingularPartialLiquidator extends SingularLiquidator<Partia
this.logger.debug(
`deployed PriceHelper at ${priceHelperAddr} in tx ${hash}`,
);

return getContract({
abi: iPriceHelperAbi,
address: priceHelperAddr,
client: this.client.pub,
});
this.#priceHelper = priceHelperAddr;
}

async #configurePartialLiquidator(
Expand Down Expand Up @@ -680,7 +673,7 @@ export default class SingularPartialLiquidator extends SingularLiquidator<Partia
return this.#partialLiquidator;
}

private get priceHelper(): IPriceHelperContract {
private get priceHelper(): Address {
if (!this.config.optimistic) {
throw new Error("price helper is only available in optimistic mode");
}
Expand Down
Loading

0 comments on commit d411e41

Please sign in to comment.