From 3446af0114dd125c061776aa938a3194fb98d9b9 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 30 Aug 2023 12:31:19 +0200 Subject: [PATCH 1/5] refactor: move llm bots to separate package Signed-off-by: Pablo Maldonado --- packages/llm-bot/src/OptimisticOracleV2.ts | 220 +++++++++++ packages/llm-bot/src/common.ts | 354 ++++++++++++++++++ packages/llm-bot/src/examples.ts | 119 ++++++ .../llm-bot/test/OptimisticOracleV2LLM.ts | 4 + packages/llm-bot/utils/constants.ts | 41 ++ packages/llm-bot/utils/contracts.ts | 60 +++ packages/llm-bot/utils/logger.ts | 29 ++ 7 files changed, 827 insertions(+) create mode 100644 packages/llm-bot/src/OptimisticOracleV2.ts create mode 100644 packages/llm-bot/src/common.ts create mode 100644 packages/llm-bot/src/examples.ts create mode 100644 packages/llm-bot/utils/constants.ts create mode 100644 packages/llm-bot/utils/contracts.ts create mode 100644 packages/llm-bot/utils/logger.ts diff --git a/packages/llm-bot/src/OptimisticOracleV2.ts b/packages/llm-bot/src/OptimisticOracleV2.ts new file mode 100644 index 0000000000..1adaf0f72c --- /dev/null +++ b/packages/llm-bot/src/OptimisticOracleV2.ts @@ -0,0 +1,220 @@ +import { Provider } from "@ethersproject/abstract-provider"; +import { paginatedEventQuery } from "@uma/common"; +import { + RequestPriceEvent, + ProposePriceEvent, + SettleEvent, +} from "@uma/contracts-node/typechain/core/ethers/OptimisticOracleV2"; +import { OptimisticOracleV2Ethers } from "@uma/contracts-node"; +import { BigNumber, Event, EventFilter, ethers } from "ethers"; +import { blockDefaults } from "../utils/constants"; +import { getContractInstanceWithProvider, tryHexToUtf8String } from "../utils/contracts"; +import { + BlockRange, + OptimisticOracleClient, + OptimisticOracleClientFilter, + OptimisticOracleRequest, + OptimisticOracleRequestDisputable, + OptimisticOracleType, + calculateRequestId, +} from "./common"; + +export class OptimisticOracleClientV2 extends OptimisticOracleClient { + constructor( + _provider: Provider, + _requests: Map = new Map(), + _fetchedBlockRange?: BlockRange + ) { + super(_provider, _requests, _fetchedBlockRange); + } + + async getEventsWithPagination( + filter: EventFilter, + fromBlock: number, + toBlock: number + ): Promise { + const ooV2Contract = await getContractInstanceWithProvider( + "OptimisticOracleV2", + this.provider + ); + + const chainId = await this.provider.getNetwork().then((network) => network.chainId); + + const maxBlockLookBack = + Number(process.env.MAX_BLOCK_LOOKBACK) || + blockDefaults[chainId.toString() as keyof typeof blockDefaults]?.maxBlockLookBack || + blockDefaults.other.maxBlockLookBack; + + const searchConfig = { + fromBlock: fromBlock, + toBlock: toBlock, + maxBlockLookBack: maxBlockLookBack, + }; + + return paginatedEventQuery(ooV2Contract, filter, searchConfig); + } + + protected async applyRequestPriceEvent( + requestPriceEvent: RequestPriceEvent, + requestsToUpdate: Map + ): Promise { + const body = tryHexToUtf8String(requestPriceEvent.args.ancillaryData); + const identifier = ethers.utils.parseBytes32String(requestPriceEvent.args.identifier); + const timestamp = requestPriceEvent.args.timestamp.toNumber(); + const requester = requestPriceEvent.args.requester; + const requestId = calculateRequestId(body, identifier, timestamp, requester); + + const newRequest = new OptimisticOracleRequest({ + requestData: { + requester, + identifier, + timestamp, + requestTx: requestPriceEvent.transactionHash, + type: OptimisticOracleType.PriceRequest, + body, + rawBody: requestPriceEvent.args.ancillaryData, + blockNumber: requestPriceEvent.blockNumber, + transactionIndex: requestPriceEvent.transactionIndex, + }, + }); + requestsToUpdate.set(requestId, newRequest); + } + + async applyProposePriceEvent( + proposePriceEvent: ProposePriceEvent, + requestsToUpdate: Map + ): Promise { + const body = tryHexToUtf8String(proposePriceEvent.args.ancillaryData); + const identifier = ethers.utils.parseBytes32String(proposePriceEvent.args.identifier); + const timestamp = proposePriceEvent.args.timestamp.toNumber(); + const requester = proposePriceEvent.args.requester; + const requestId = calculateRequestId(body, identifier, timestamp, requester); + + requestsToUpdate.set( + requestId, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + requestsToUpdate.get(requestId)!.update({ + proposalData: { + proposer: proposePriceEvent.args.proposer, + proposedValue: proposePriceEvent.args.proposedPrice, + proposeTx: proposePriceEvent.transactionHash, + disputableUntil: proposePriceEvent.args.expirationTimestamp.toNumber(), + }, + }) + ); + } + + async applySettleEvent( + settleEvent: SettleEvent, + requestsToUpdate: Map + ): Promise { + const body = tryHexToUtf8String(settleEvent.args.ancillaryData); + const identifier = ethers.utils.parseBytes32String(settleEvent.args.identifier); + const timestamp = settleEvent.args.timestamp.toNumber(); + const requester = settleEvent.args.requester; + const requestId = calculateRequestId(body, identifier, timestamp, requester); + + requestsToUpdate.set( + requestId, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + requestsToUpdate.get(requestId)!.update({ + resolutionData: { + resolvedValue: settleEvent.args.price, + resolveTx: settleEvent.transactionHash, + }, + }) + ); + } + + protected async updateOracleRequests(newRange: BlockRange): Promise> { + const requestsCopy = new Map(this.requests); + const ooV2Contract = await getContractInstanceWithProvider( + "OptimisticOracleV2", + this.provider + ); + + const requestPriceEvents = await this.getEventsWithPagination( + ooV2Contract.filters.RequestPrice(), + newRange[0], + newRange[1] + ); + + const proposePriceEvents = await this.getEventsWithPagination( + ooV2Contract.filters.ProposePrice(), + newRange[0], + newRange[1] + ); + + const settleEvents = await this.getEventsWithPagination( + ooV2Contract.filters.Settle(), + newRange[0], + newRange[1] + ); + + await Promise.all( + requestPriceEvents.map((requestPriceEvent) => { + return this.applyRequestPriceEvent(requestPriceEvent, requestsCopy); + }) + ); + + await Promise.all([ + ...proposePriceEvents.map(async (proposePriceEvent) => { + return this.applyProposePriceEvent(proposePriceEvent, requestsCopy); + }), + ]); + + await Promise.all([ + ...settleEvents.map(async (settleEvent) => { + return this.applySettleEvent(settleEvent, requestsCopy); + }), + ]); + + return requestsCopy; + } + + protected createClientInstance( + requests: Map, + fetchedBlockRange: BlockRange + ): OptimisticOracleClientV2 { + return new OptimisticOracleClientV2(this.provider, requests, fetchedBlockRange); + } +} + +export class OptimisticOracleClientV2FilterDisputeable + implements OptimisticOracleClientFilter { + // TODO interpret price values considering UMIPS and magic numbers + async filter(optimisticOracleRequests: OptimisticOracleRequest[]): Promise { + return optimisticOracleRequests.filter((request) => { + return typeof request.disputableUntil == "number" && request.disputableUntil > Date.now() / 1000; + }); + } +} + +export class DisputerStrategy { + static process(request: OptimisticOracleRequest): Promise { + return Promise.resolve( + new OptimisticOracleRequestDisputable({ + requestData: request.data.requestData, + proposalData: request.data.proposalData, + disputeData: request.data.disputeData, + resolutionData: request.data.resolutionData, + disputableData: { + correctAnswer: ethers.utils.parseEther("1"), + rawLLMInput: "", + rawLLMOutput: "", + shouldDispute: true, + }, + }) + ); + } +} + +export class Backtest { + static test(request: OptimisticOracleRequestDisputable): boolean { + if (typeof request.resolvedValue === "boolean") { + return request.resolvedValue === request.data.disputableData.correctAnswer; + } + // At this point, we assume request.resolvedValue is of type BigNumber + return (request.resolvedValue as BigNumber).eq(request.data.disputableData.correctAnswer as BigNumber); + } +} diff --git a/packages/llm-bot/src/common.ts b/packages/llm-bot/src/common.ts new file mode 100644 index 0000000000..9f8314db42 --- /dev/null +++ b/packages/llm-bot/src/common.ts @@ -0,0 +1,354 @@ +import { Provider } from "@ethersproject/abstract-provider"; +import { BigNumber, ethers } from "ethers"; +import { OptimisticOracleV2Ethers } from "@uma/contracts-node"; + +/** + * Calculate the unique ID for a request. + * @param body The body of the request. + * @param identifier The identifier of the request. + * @param timestamp The timestamp of the request. + * @param requester The address of the requester. + * @returns The unique ID. + */ +export function calculateRequestId(body: string, identifier: string, timestamp: number, requester: string): string { + return ethers.utils.solidityKeccak256( + ["string", "string", "uint256", "string"], + [body, identifier, timestamp, requester] + ); +} + +/** + * Type representing a block range. + */ +export type BlockRange = [number, number]; + +/** + * Enum representing the type of an Optimistic Oracle request. + */ +export enum OptimisticOracleType { + PriceRequest = "PriceRequest", + Assertion = "Assertion", +} + +interface RequestData { + readonly body: string; // Human-readable request body. + readonly rawBody: string; // Raw request body. + readonly type: OptimisticOracleType; // Type of the request. + readonly timestamp: number; // Timestamp in seconds of the request. + readonly identifier: string; // Identifier of the request. + readonly requester: string; // Address of the requester. + readonly requestTx: string; // Transaction hash of the request. + readonly blockNumber: number; // Block number of the request update. + readonly transactionIndex: number; // Transaction index in the block. +} + +interface ProposalData { + readonly proposer: string; // Address of the proposer. + readonly proposedValue: BigNumber | boolean; // Proposed value. + readonly proposeTx: string; // Transaction hash of the proposal. + readonly disputableUntil: number; // Timestamp in seconds until the request can be disputed. +} + +interface DisputeData { + readonly disputer: string; // Address of the disputer. + readonly disputeTx: string; // Transaction hash of the dispute. +} + +interface ResolutionData { + readonly resolvedValue: BigNumber | boolean; // Resolved value. + readonly resolveTx: string; // Transaction hash of the resolution. +} + +/** + * Interface representing the data of an Optimistic Oracle request. + * Note: this is structured to reduce replication and copying of data by storing the request data, proposal data, and resolution data in separate + * references. + */ +export interface OptimisticOracleRequestData { + readonly requestData: RequestData; + readonly proposalData?: ProposalData; + readonly disputeData?: DisputeData; + readonly resolutionData?: ResolutionData; +} + +/** + * Represents an Optimistic Oracle request. + */ +export class OptimisticOracleRequest { + protected isEventBased = false; // Whether the request is event-based. False by default and eventually only true if + // the request is a OptimisticOracleV2 priceRequest. + /** + * Creates a new instance of OptimisticOracleRequest. + * @param data The data of the request. + */ + constructor(readonly data: OptimisticOracleRequestData) {} + + async fetchIsEventBased(ooV2Contract: OptimisticOracleV2Ethers): Promise { + if (this.type !== OptimisticOracleType.PriceRequest) return Promise.resolve(false); + + if (this.isEventBased) return Promise.resolve(this.isEventBased); + + this.isEventBased = await ooV2Contract + .getRequest( + this.data.requestData.requester, + this.data.requestData.identifier, + this.data.requestData.timestamp, + this.data.requestData.rawBody + ) + .then((r) => r.requestSettings.eventBased); + return this.isEventBased; + } + + get body(): string { + return this.data.requestData.body; + } + + get type(): OptimisticOracleType { + return this.data.requestData.type; + } + + get timestamp(): number { + return this.data.requestData.timestamp; + } + + get identifier(): string { + return this.data.requestData.identifier; + } + + get requester(): string { + return this.data.requestData.requester; + } + + get requestTx(): string { + return this.data.requestData.requestTx; + } + + get proposer(): string | undefined { + return this.data.proposalData?.proposer; + } + + get proposedValue(): BigNumber | boolean | undefined { + return this.data.proposalData?.proposedValue; + } + + get proposeTx(): string | undefined { + return this.data.proposalData?.proposeTx; + } + + get disputableUntil(): number | undefined { + return this.data.proposalData?.disputableUntil; + } + + get resolvedValue(): BigNumber | boolean | undefined { + return this.data.resolutionData?.resolvedValue; + } + + get resolveTx(): string | undefined { + return this.data.resolutionData?.resolveTx; + } + + get disputeTx(): string | undefined { + return this.data.disputeData?.disputeTx; + } + + get disputer(): string | undefined { + return this.data.disputeData?.disputer; + } + + get blockNumber(): number | undefined { + return this.data.requestData.blockNumber; + } + + get transactionIndex(): number | undefined { + return this.data.requestData.transactionIndex; + } + + get id(): string { + return calculateRequestId(this.body, this.identifier, this.timestamp, this.requester); + } + + update(data: Partial): OptimisticOracleRequest { + // Override old data with new data. Note: this will only copy or override top-level properties. + return new OptimisticOracleRequest({ ...this.data, ...data }); + } +} + +/** + * Interface representing the additional data fields for a disputable oracle request. + */ +interface DisputableData { + correctAnswer: boolean | BigNumber; + rawLLMInput: string; + rawLLMOutput: string; + shouldDispute: boolean; +} + +/** + * Interface extending the base oracle request data to include disputable data. + */ +export interface OptimisticOracleRequestDisputableData extends OptimisticOracleRequestData { + readonly disputableData: DisputableData; +} + +/** + * Class representing an oracle request that can potentially be disputed. + * It extends the base OptimisticOracleRequest class and includes additional disputable data. + */ +export class OptimisticOracleRequestDisputable extends OptimisticOracleRequest { + constructor(readonly data: OptimisticOracleRequestDisputableData) { + super(data); + } + + get correctAnswer(): boolean | BigNumber { + return this.data.disputableData.correctAnswer; + } + + get rawLLMInput(): string { + return this.data.disputableData.rawLLMInput; + } + + get rawLLMOutput(): string { + return this.data.disputableData.rawLLMOutput; + } + + get shouldDispute(): boolean { + return this.data.disputableData.shouldDispute; + } + + get isDisputable(): boolean { + return this.disputableUntil !== undefined && this.disputableUntil > Date.now() / 1000; + } +} + +const EMPTY_BLOCK_RANGE: BlockRange = [0, 0]; + +/** + * Abstract class representing a client to interact with an Optimistic Oracle and store the requests. + */ +export abstract class OptimisticOracleClient { + protected provider: Provider; + readonly requests: ReadonlyMap; + readonly fetchedBlockRange: BlockRange; + + /** + * Constructs a new instance of OptimisticOracleClient. + * @param _provider The provider used for interacting with the blockchain. + * @param _requests (Optional) The map of Optimistic Oracle requests. + * @param _fetchedBlockRanges (Optional) The block ranges of the fetched requests. + * @dev requests are stored in a map for faster access and to avoid duplicates. + */ + protected constructor( + _provider: Provider, + _requests: Map = new Map(), + _fetchedBlockRange: BlockRange = EMPTY_BLOCK_RANGE + ) { + this.provider = _provider; + this.requests = _requests; + this.fetchedBlockRange = _fetchedBlockRange; + } + + /** + * Returns a copy of the OptimisticOracleClient + * @returns A copy of the OptimisticOracleClient + * @dev This is a deep copy. + */ + copy(): OptimisticOracleClient { + return this.createClientInstance(new Map(this.requests), this.fetchedBlockRange); + } + + /** + * Creates a new instance of the OptimisticOracleClient with the specified requests. + * Must be implemented by the derived class. + * @param requests The requests to be set on the new instance. + * @param fetchedBlockRange The block range of the fetched requests. + * @returns A new instance of OptimisticOracleClient. + */ + protected abstract createClientInstance( + requests: Map, + fetchedBlockRanges: BlockRange + ): OptimisticOracleClient; + + /** + * Returns a copy of the updated Requests by fetching new Oracle requests updates. + * @param blockRange The new blockRange to fetch requests from. + * @returns A Promise that resolves to a copy of the updated Requests map. + */ + protected abstract updateOracleRequests(blockRange: BlockRange): Promise>; + + /** + * Updates the OptimisticOracleClient instance by fetching new Oracle requests within the specified block range. Returns a new instance. + * @param blockRange (Optional) The block range to fetch new requests from. + * @returns A Promise that resolves to a new OptimisticOracleClient instance with updated requests. + */ + async updateWithBlockRange(blockRange?: BlockRange): Promise> { + let range: BlockRange; + if (blockRange) { + if (blockRange[0] > blockRange[1]) + throw new Error("Start block number should be less than or equal to end block number"); + range = blockRange; + } else { + // Calculate the next block range to fetch + const latestBlock = await this.provider.getBlockNumber(); + const nextStartBlock = this.fetchedBlockRange[1] + 1; + if (nextStartBlock > latestBlock) return this; // no new blocks to fetch + range = [nextStartBlock, latestBlock]; + } + const [startBlock, endBlock] = range; + + // Throw an error if the new range doesn't directly follow the last fetched range + const lastFetchedEndBlock = this.fetchedBlockRange[1]; + if (lastFetchedEndBlock != 0 && startBlock !== lastFetchedEndBlock + 1) + throw new Error( + "New block range does not follow the last fetched block range, there is a gap between the ranges" + ); + + // We enforce the creation of a new instance of the client to avoid mutating the current instance + const newRequests = await this.updateOracleRequests([startBlock, endBlock]); + + return this.createClientInstance(newRequests, [startBlock, endBlock]); + } + + /** + * Returns the block ranges of the fetched requests. + * @returns An array of pairs of numbers representing the block ranges. + */ + getFetchedBlockRange(): BlockRange { + return this.fetchedBlockRange; + } + + /** + * Returns the provider used for interacting with the blockchain. + * @returns The provider object. + */ + getProvider(): Provider { + return this.provider; + } +} + +/** + * Represents a filtering strategy for an Optimistic Oracle client price requests. + * @template I The type of the input OptimisticOracleRequest. + * @template O The type of the output OptimisticOracleRequest. + */ +export interface OptimisticOracleClientFilter { + /** + * Filters and/or augments Optimistic Oracle requests. + * @param optimisticOracleRequests The Optimistic Oracle requests to be filtered. + * @returns A Promise that resolves to the filtered Optimistic Oracle requests. + */ + filter(optimisticOracleRequests: I[]): Promise; +} + +/** + * Abstract class representing a strategy for processing Optimistic Oracle requests. + * @template I The type of the input OptimisticOracleRequest. + * @template R The type of the result, based on OptimisticOracleRequestDisputable. + */ +export interface LLMDisputerStrategy { + /** + * Processes Optimistic Oracle requests using the strategy implementation. + * @param request The Optimistic Oracle request to be processed. + * @returns A Promise that resolves to the result of the processing. + */ + process(request: I): Promise; +} diff --git a/packages/llm-bot/src/examples.ts b/packages/llm-bot/src/examples.ts new file mode 100644 index 0000000000..59235befc2 --- /dev/null +++ b/packages/llm-bot/src/examples.ts @@ -0,0 +1,119 @@ +import { Provider } from "@ethersproject/abstract-provider"; +import { ethers } from "ethers"; +import { + BlockRange, + OptimisticOracleClient, + OptimisticOracleClientFilter, + OptimisticOracleRequest, + OptimisticOracleRequestData, +} from "./common"; + +export class OptimisticOracleClientV2 extends OptimisticOracleClient { + constructor( + _provider: Provider, + _requests: Map = new Map(), + _fetchedBlockRange?: BlockRange + ) { + super(_provider, _requests, _fetchedBlockRange); + } + + protected async updateOracleRequests(blockRange: BlockRange): Promise> { + // TODO: Implement this for the OptimisticOracleV2 + blockRange; + return new Map(); + } + + protected createClientInstance( + requests: Map, + fetchedBlockRange: BlockRange + ): OptimisticOracleClientV2 { + return new OptimisticOracleClientV2(this.provider, requests, fetchedBlockRange); + } +} + +class OptimisticOracleRequestPolymarket extends OptimisticOracleRequest { + readonly polymarketQuestionTitle: string; + + constructor(data: OptimisticOracleRequestData & { polymarketQuestionTitle: string }) { + super(data); + this.polymarketQuestionTitle = data.polymarketQuestionTitle; + } +} + +export class OptimisticOracleClientV2Polymarket extends OptimisticOracleClient { + constructor( + _provider: Provider, + _requests: Map = new Map(), + _fetchedBlockRange?: BlockRange + ) { + super(_provider, _requests, _fetchedBlockRange); + } + + protected async updateOracleRequests( + blockRange: BlockRange + ): Promise> { + // TODO: Implement this for the OptimisticOracleV2 + blockRange; + return new Map(); + } + + protected createClientInstance( + requests: Map, + fetchedBlockRange: BlockRange + ): OptimisticOracleClientV2Polymarket { + return new OptimisticOracleClientV2Polymarket(this.provider, requests, fetchedBlockRange); + } +} + +export class OptimisticOracleClientV3 extends OptimisticOracleClient { + constructor( + _provider: Provider, + _requests: Map = new Map(), + _fetchedBlockRange?: BlockRange + ) { + super(_provider, _requests, _fetchedBlockRange); + } + + protected async updateOracleRequests(blockRange: BlockRange): Promise> { + // TODO: Implement this for the OptimisticOracleV3 + blockRange; + return new Map(); + } + + protected createClientInstance( + requests: Map, + fetchedBlockRange: BlockRange + ): OptimisticOracleClientV3 { + return new OptimisticOracleClientV3(this.provider, requests, fetchedBlockRange); + } +} + +export class OptimisticOracleClientFilterV2ToPolymarket + implements OptimisticOracleClientFilter { + async filter(optimisticOracleRequests: OptimisticOracleRequest[]): Promise { + // Filtering logic for price requests + const filteredRequests = optimisticOracleRequests.map((request) => { + return new OptimisticOracleRequestPolymarket({ + ...request.data, + polymarketQuestionTitle: "What is the price of ETH?", + }); + }); + + return filteredRequests; + } +} + +const main = async () => { + const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545"); + + const oov2 = new OptimisticOracleClientV2(provider); + + const oov2_updated = await oov2.updateWithBlockRange(); + + const oov2_filtered = await new OptimisticOracleClientFilterV2ToPolymarket().filter( + Array.from(oov2_updated.requests.values()) + ); + oov2_filtered; +}; + +main(); diff --git a/packages/llm-bot/test/OptimisticOracleV2LLM.ts b/packages/llm-bot/test/OptimisticOracleV2LLM.ts index f34af61205..9b2019bdce 100644 --- a/packages/llm-bot/test/OptimisticOracleV2LLM.ts +++ b/packages/llm-bot/test/OptimisticOracleV2LLM.ts @@ -5,7 +5,11 @@ import { DisputerStrategy, OptimisticOracleClientV2, OptimisticOracleClientV2FilterDisputeable, +<<<<<<< HEAD } from "../src/core/OptimisticOracleV2"; +======= +} from "../src/OptimisticOracleV2"; +>>>>>>> 746ff5a88 (refactor: move llm bots to separate package) import { defaultOptimisticOracleV2Identifier } from "./constants"; import { optimisticOracleV2Fixture } from "./fixtures/OptimisticOracleV2.Fixture"; import { Signer, hre, toUtf8Bytes } from "./utils"; diff --git a/packages/llm-bot/utils/constants.ts b/packages/llm-bot/utils/constants.ts new file mode 100644 index 0000000000..b695804c9e --- /dev/null +++ b/packages/llm-bot/utils/constants.ts @@ -0,0 +1,41 @@ +interface BlockConfig { + oneHour: number; + maxBlockLookBack: number; +} + +/** + * Default configuration for different blockchain networks. + * Each network is identified by its chain ID. + */ +export const blockDefaults: Record = { + "1": { + // Mainnet configuration + oneHour: 300, // Approximate number of blocks mined in one hour (12 seconds per block) + maxBlockLookBack: 20000, // Maximum number of blocks to look back for events + }, + "137": { + // Polygon (Matic) configuration + oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) + maxBlockLookBack: 3499, // Maximum number of blocks to look back for events + }, + "10": { + // Optimism configuration + oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) + maxBlockLookBack: 10000, // Maximum number of blocks to look back for events + }, + "42161": { + // Arbitrum configuration + oneHour: 240, // Approximate number of blocks mined in one hour (15 seconds per block) + maxBlockLookBack: 10000, // Maximum number of blocks to look back for events + }, + "43114": { + // Avalanche configuration + oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) + maxBlockLookBack: 2000, // Maximum number of blocks to look back for events + }, + other: { + // Default configuration for other networks + oneHour: 240, // Approximate number of blocks mined in one hour (15 seconds per block) + maxBlockLookBack: 1000, // Maximum number of blocks to look back for events + }, +}; diff --git a/packages/llm-bot/utils/contracts.ts b/packages/llm-bot/utils/contracts.ts new file mode 100644 index 0000000000..c8732ab892 --- /dev/null +++ b/packages/llm-bot/utils/contracts.ts @@ -0,0 +1,60 @@ +import { ContractName, ERC20Ethers, getAbi, getAddress } from "@uma/contracts-node"; +import { Contract } from "ethers"; +import { Provider } from "@ethersproject/abstract-provider"; +import { utils } from "ethers"; + +export const getContractInstanceWithProvider = async ( + contractName: ContractName, + provider: Provider, + address?: string +): Promise => { + const networkId = (await provider.getNetwork()).chainId; + const contractAddress = address || (await getAddress(contractName, networkId)); + const contractAbi = getAbi(contractName); + return new Contract(contractAddress, contractAbi, provider) as T; +}; + +export const tryHexToUtf8String = (ancillaryData: string): string => { + try { + return utils.toUtf8String(ancillaryData); + } catch (err) { + return ancillaryData; + } +}; + +export const getCurrencyDecimals = async (provider: Provider, currencyAddress: string): Promise => { + const currencyContract = await getContractInstanceWithProvider("ERC20", provider, currencyAddress); + try { + return await currencyContract.decimals(); + } catch (err) { + return 18; + } +}; + +export const getCurrencySymbol = async (provider: Provider, currencyAddress: string): Promise => { + const currencyContract = await getContractInstanceWithProvider("ERC20", provider, currencyAddress); + try { + return await currencyContract.symbol(); + } catch (err) { + // Try to get the symbol as bytes32 (e.g. MKR uses this). + try { + const bytes32SymbolIface = new utils.Interface(["function symbol() view returns (bytes32 symbol)"]); + const bytes32Symbol = await provider.call({ + to: currencyAddress, + data: bytes32SymbolIface.encodeFunctionData("symbol"), + }); + return utils.parseBytes32String(bytes32SymbolIface.decodeFunctionResult("symbol", bytes32Symbol).symbol); + } catch (err) { + return ""; + } + } +}; + +// Gets the topic of an event from its name. In case of overloaded events, the first one found is returned. +export const getEventTopic = (contractName: ContractName, eventName: string): string => { + const contractAbi = getAbi(contractName); + const iface = new utils.Interface(contractAbi); + const eventKey = Object.keys(iface.events).find((key) => iface.events[key].name === eventName); + if (!eventKey) throw new Error(`Event ${eventName} not found in contract ${contractName}`); + return utils.keccak256(utils.toUtf8Bytes(eventKey)); +}; diff --git a/packages/llm-bot/utils/logger.ts b/packages/llm-bot/utils/logger.ts new file mode 100644 index 0000000000..18f5de9a75 --- /dev/null +++ b/packages/llm-bot/utils/logger.ts @@ -0,0 +1,29 @@ +import { TenderlySimulationResult } from "@uma/common"; + +const optimisticOracleV2UIBaseUrl = "https://oracle.uma.xyz"; +const testnetOptimisticOracleV2UIBaseUrl = "https://testnet.oracle.uma.xyz"; + +// monitor-v2 package is only using Optimistic Oracle V3, so currently there is no need to generalize this. +export const generateOOv3UILink = (transactionHash: string, eventIndex: number, chainId?: number): string => { + // Currently testnet UI supports only goerli, so assume any other chain is production. + const baseUrl = chainId === 5 ? testnetOptimisticOracleV2UIBaseUrl : optimisticOracleV2UIBaseUrl; + return `<${baseUrl}/?transactionHash=${transactionHash}&eventIndex=${eventIndex}|View in UI>`; +}; + +export const createSnapshotProposalLink = (baseUrl: string, space: string, proposalId: string): string => { + return `<${baseUrl}/#/${space}/proposal/${proposalId}|Snapshot UI>`; +}; + +export const createTenderlySimulationLink = (simulationResult?: TenderlySimulationResult): string => { + if (simulationResult === undefined) { + return "No Tenderly simulation available"; + } else if (simulationResult.status) { + return `<${simulationResult.resultUrl.url}|Tenderly simulation successful${ + !simulationResult.resultUrl.public ? " (private)" : "" + }>`; + } else { + return `<${simulationResult.resultUrl.url}|Tenderly simulation reverted${ + !simulationResult.resultUrl.public ? " (private)" : "" + }>`; + } +}; From 0d3a8cef189a91097888f5a2d130b88ab9e75d34 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 30 Aug 2023 12:45:53 +0200 Subject: [PATCH 2/5] refactor: folders Signed-off-by: Pablo Maldonado --- packages/llm-bot/src/OptimisticOracleV2.ts | 220 ----------- packages/llm-bot/src/common.ts | 354 ------------------ packages/llm-bot/src/examples.ts | 119 ------ packages/llm-bot/{ => src}/utils/logger.ts | 0 .../llm-bot/test/OptimisticOracleV2LLM.ts | 4 + packages/llm-bot/utils/constants.ts | 41 -- packages/llm-bot/utils/contracts.ts | 60 --- 7 files changed, 4 insertions(+), 794 deletions(-) delete mode 100644 packages/llm-bot/src/OptimisticOracleV2.ts delete mode 100644 packages/llm-bot/src/common.ts delete mode 100644 packages/llm-bot/src/examples.ts rename packages/llm-bot/{ => src}/utils/logger.ts (100%) delete mode 100644 packages/llm-bot/utils/constants.ts delete mode 100644 packages/llm-bot/utils/contracts.ts diff --git a/packages/llm-bot/src/OptimisticOracleV2.ts b/packages/llm-bot/src/OptimisticOracleV2.ts deleted file mode 100644 index 1adaf0f72c..0000000000 --- a/packages/llm-bot/src/OptimisticOracleV2.ts +++ /dev/null @@ -1,220 +0,0 @@ -import { Provider } from "@ethersproject/abstract-provider"; -import { paginatedEventQuery } from "@uma/common"; -import { - RequestPriceEvent, - ProposePriceEvent, - SettleEvent, -} from "@uma/contracts-node/typechain/core/ethers/OptimisticOracleV2"; -import { OptimisticOracleV2Ethers } from "@uma/contracts-node"; -import { BigNumber, Event, EventFilter, ethers } from "ethers"; -import { blockDefaults } from "../utils/constants"; -import { getContractInstanceWithProvider, tryHexToUtf8String } from "../utils/contracts"; -import { - BlockRange, - OptimisticOracleClient, - OptimisticOracleClientFilter, - OptimisticOracleRequest, - OptimisticOracleRequestDisputable, - OptimisticOracleType, - calculateRequestId, -} from "./common"; - -export class OptimisticOracleClientV2 extends OptimisticOracleClient { - constructor( - _provider: Provider, - _requests: Map = new Map(), - _fetchedBlockRange?: BlockRange - ) { - super(_provider, _requests, _fetchedBlockRange); - } - - async getEventsWithPagination( - filter: EventFilter, - fromBlock: number, - toBlock: number - ): Promise { - const ooV2Contract = await getContractInstanceWithProvider( - "OptimisticOracleV2", - this.provider - ); - - const chainId = await this.provider.getNetwork().then((network) => network.chainId); - - const maxBlockLookBack = - Number(process.env.MAX_BLOCK_LOOKBACK) || - blockDefaults[chainId.toString() as keyof typeof blockDefaults]?.maxBlockLookBack || - blockDefaults.other.maxBlockLookBack; - - const searchConfig = { - fromBlock: fromBlock, - toBlock: toBlock, - maxBlockLookBack: maxBlockLookBack, - }; - - return paginatedEventQuery(ooV2Contract, filter, searchConfig); - } - - protected async applyRequestPriceEvent( - requestPriceEvent: RequestPriceEvent, - requestsToUpdate: Map - ): Promise { - const body = tryHexToUtf8String(requestPriceEvent.args.ancillaryData); - const identifier = ethers.utils.parseBytes32String(requestPriceEvent.args.identifier); - const timestamp = requestPriceEvent.args.timestamp.toNumber(); - const requester = requestPriceEvent.args.requester; - const requestId = calculateRequestId(body, identifier, timestamp, requester); - - const newRequest = new OptimisticOracleRequest({ - requestData: { - requester, - identifier, - timestamp, - requestTx: requestPriceEvent.transactionHash, - type: OptimisticOracleType.PriceRequest, - body, - rawBody: requestPriceEvent.args.ancillaryData, - blockNumber: requestPriceEvent.blockNumber, - transactionIndex: requestPriceEvent.transactionIndex, - }, - }); - requestsToUpdate.set(requestId, newRequest); - } - - async applyProposePriceEvent( - proposePriceEvent: ProposePriceEvent, - requestsToUpdate: Map - ): Promise { - const body = tryHexToUtf8String(proposePriceEvent.args.ancillaryData); - const identifier = ethers.utils.parseBytes32String(proposePriceEvent.args.identifier); - const timestamp = proposePriceEvent.args.timestamp.toNumber(); - const requester = proposePriceEvent.args.requester; - const requestId = calculateRequestId(body, identifier, timestamp, requester); - - requestsToUpdate.set( - requestId, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - requestsToUpdate.get(requestId)!.update({ - proposalData: { - proposer: proposePriceEvent.args.proposer, - proposedValue: proposePriceEvent.args.proposedPrice, - proposeTx: proposePriceEvent.transactionHash, - disputableUntil: proposePriceEvent.args.expirationTimestamp.toNumber(), - }, - }) - ); - } - - async applySettleEvent( - settleEvent: SettleEvent, - requestsToUpdate: Map - ): Promise { - const body = tryHexToUtf8String(settleEvent.args.ancillaryData); - const identifier = ethers.utils.parseBytes32String(settleEvent.args.identifier); - const timestamp = settleEvent.args.timestamp.toNumber(); - const requester = settleEvent.args.requester; - const requestId = calculateRequestId(body, identifier, timestamp, requester); - - requestsToUpdate.set( - requestId, - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - requestsToUpdate.get(requestId)!.update({ - resolutionData: { - resolvedValue: settleEvent.args.price, - resolveTx: settleEvent.transactionHash, - }, - }) - ); - } - - protected async updateOracleRequests(newRange: BlockRange): Promise> { - const requestsCopy = new Map(this.requests); - const ooV2Contract = await getContractInstanceWithProvider( - "OptimisticOracleV2", - this.provider - ); - - const requestPriceEvents = await this.getEventsWithPagination( - ooV2Contract.filters.RequestPrice(), - newRange[0], - newRange[1] - ); - - const proposePriceEvents = await this.getEventsWithPagination( - ooV2Contract.filters.ProposePrice(), - newRange[0], - newRange[1] - ); - - const settleEvents = await this.getEventsWithPagination( - ooV2Contract.filters.Settle(), - newRange[0], - newRange[1] - ); - - await Promise.all( - requestPriceEvents.map((requestPriceEvent) => { - return this.applyRequestPriceEvent(requestPriceEvent, requestsCopy); - }) - ); - - await Promise.all([ - ...proposePriceEvents.map(async (proposePriceEvent) => { - return this.applyProposePriceEvent(proposePriceEvent, requestsCopy); - }), - ]); - - await Promise.all([ - ...settleEvents.map(async (settleEvent) => { - return this.applySettleEvent(settleEvent, requestsCopy); - }), - ]); - - return requestsCopy; - } - - protected createClientInstance( - requests: Map, - fetchedBlockRange: BlockRange - ): OptimisticOracleClientV2 { - return new OptimisticOracleClientV2(this.provider, requests, fetchedBlockRange); - } -} - -export class OptimisticOracleClientV2FilterDisputeable - implements OptimisticOracleClientFilter { - // TODO interpret price values considering UMIPS and magic numbers - async filter(optimisticOracleRequests: OptimisticOracleRequest[]): Promise { - return optimisticOracleRequests.filter((request) => { - return typeof request.disputableUntil == "number" && request.disputableUntil > Date.now() / 1000; - }); - } -} - -export class DisputerStrategy { - static process(request: OptimisticOracleRequest): Promise { - return Promise.resolve( - new OptimisticOracleRequestDisputable({ - requestData: request.data.requestData, - proposalData: request.data.proposalData, - disputeData: request.data.disputeData, - resolutionData: request.data.resolutionData, - disputableData: { - correctAnswer: ethers.utils.parseEther("1"), - rawLLMInput: "", - rawLLMOutput: "", - shouldDispute: true, - }, - }) - ); - } -} - -export class Backtest { - static test(request: OptimisticOracleRequestDisputable): boolean { - if (typeof request.resolvedValue === "boolean") { - return request.resolvedValue === request.data.disputableData.correctAnswer; - } - // At this point, we assume request.resolvedValue is of type BigNumber - return (request.resolvedValue as BigNumber).eq(request.data.disputableData.correctAnswer as BigNumber); - } -} diff --git a/packages/llm-bot/src/common.ts b/packages/llm-bot/src/common.ts deleted file mode 100644 index 9f8314db42..0000000000 --- a/packages/llm-bot/src/common.ts +++ /dev/null @@ -1,354 +0,0 @@ -import { Provider } from "@ethersproject/abstract-provider"; -import { BigNumber, ethers } from "ethers"; -import { OptimisticOracleV2Ethers } from "@uma/contracts-node"; - -/** - * Calculate the unique ID for a request. - * @param body The body of the request. - * @param identifier The identifier of the request. - * @param timestamp The timestamp of the request. - * @param requester The address of the requester. - * @returns The unique ID. - */ -export function calculateRequestId(body: string, identifier: string, timestamp: number, requester: string): string { - return ethers.utils.solidityKeccak256( - ["string", "string", "uint256", "string"], - [body, identifier, timestamp, requester] - ); -} - -/** - * Type representing a block range. - */ -export type BlockRange = [number, number]; - -/** - * Enum representing the type of an Optimistic Oracle request. - */ -export enum OptimisticOracleType { - PriceRequest = "PriceRequest", - Assertion = "Assertion", -} - -interface RequestData { - readonly body: string; // Human-readable request body. - readonly rawBody: string; // Raw request body. - readonly type: OptimisticOracleType; // Type of the request. - readonly timestamp: number; // Timestamp in seconds of the request. - readonly identifier: string; // Identifier of the request. - readonly requester: string; // Address of the requester. - readonly requestTx: string; // Transaction hash of the request. - readonly blockNumber: number; // Block number of the request update. - readonly transactionIndex: number; // Transaction index in the block. -} - -interface ProposalData { - readonly proposer: string; // Address of the proposer. - readonly proposedValue: BigNumber | boolean; // Proposed value. - readonly proposeTx: string; // Transaction hash of the proposal. - readonly disputableUntil: number; // Timestamp in seconds until the request can be disputed. -} - -interface DisputeData { - readonly disputer: string; // Address of the disputer. - readonly disputeTx: string; // Transaction hash of the dispute. -} - -interface ResolutionData { - readonly resolvedValue: BigNumber | boolean; // Resolved value. - readonly resolveTx: string; // Transaction hash of the resolution. -} - -/** - * Interface representing the data of an Optimistic Oracle request. - * Note: this is structured to reduce replication and copying of data by storing the request data, proposal data, and resolution data in separate - * references. - */ -export interface OptimisticOracleRequestData { - readonly requestData: RequestData; - readonly proposalData?: ProposalData; - readonly disputeData?: DisputeData; - readonly resolutionData?: ResolutionData; -} - -/** - * Represents an Optimistic Oracle request. - */ -export class OptimisticOracleRequest { - protected isEventBased = false; // Whether the request is event-based. False by default and eventually only true if - // the request is a OptimisticOracleV2 priceRequest. - /** - * Creates a new instance of OptimisticOracleRequest. - * @param data The data of the request. - */ - constructor(readonly data: OptimisticOracleRequestData) {} - - async fetchIsEventBased(ooV2Contract: OptimisticOracleV2Ethers): Promise { - if (this.type !== OptimisticOracleType.PriceRequest) return Promise.resolve(false); - - if (this.isEventBased) return Promise.resolve(this.isEventBased); - - this.isEventBased = await ooV2Contract - .getRequest( - this.data.requestData.requester, - this.data.requestData.identifier, - this.data.requestData.timestamp, - this.data.requestData.rawBody - ) - .then((r) => r.requestSettings.eventBased); - return this.isEventBased; - } - - get body(): string { - return this.data.requestData.body; - } - - get type(): OptimisticOracleType { - return this.data.requestData.type; - } - - get timestamp(): number { - return this.data.requestData.timestamp; - } - - get identifier(): string { - return this.data.requestData.identifier; - } - - get requester(): string { - return this.data.requestData.requester; - } - - get requestTx(): string { - return this.data.requestData.requestTx; - } - - get proposer(): string | undefined { - return this.data.proposalData?.proposer; - } - - get proposedValue(): BigNumber | boolean | undefined { - return this.data.proposalData?.proposedValue; - } - - get proposeTx(): string | undefined { - return this.data.proposalData?.proposeTx; - } - - get disputableUntil(): number | undefined { - return this.data.proposalData?.disputableUntil; - } - - get resolvedValue(): BigNumber | boolean | undefined { - return this.data.resolutionData?.resolvedValue; - } - - get resolveTx(): string | undefined { - return this.data.resolutionData?.resolveTx; - } - - get disputeTx(): string | undefined { - return this.data.disputeData?.disputeTx; - } - - get disputer(): string | undefined { - return this.data.disputeData?.disputer; - } - - get blockNumber(): number | undefined { - return this.data.requestData.blockNumber; - } - - get transactionIndex(): number | undefined { - return this.data.requestData.transactionIndex; - } - - get id(): string { - return calculateRequestId(this.body, this.identifier, this.timestamp, this.requester); - } - - update(data: Partial): OptimisticOracleRequest { - // Override old data with new data. Note: this will only copy or override top-level properties. - return new OptimisticOracleRequest({ ...this.data, ...data }); - } -} - -/** - * Interface representing the additional data fields for a disputable oracle request. - */ -interface DisputableData { - correctAnswer: boolean | BigNumber; - rawLLMInput: string; - rawLLMOutput: string; - shouldDispute: boolean; -} - -/** - * Interface extending the base oracle request data to include disputable data. - */ -export interface OptimisticOracleRequestDisputableData extends OptimisticOracleRequestData { - readonly disputableData: DisputableData; -} - -/** - * Class representing an oracle request that can potentially be disputed. - * It extends the base OptimisticOracleRequest class and includes additional disputable data. - */ -export class OptimisticOracleRequestDisputable extends OptimisticOracleRequest { - constructor(readonly data: OptimisticOracleRequestDisputableData) { - super(data); - } - - get correctAnswer(): boolean | BigNumber { - return this.data.disputableData.correctAnswer; - } - - get rawLLMInput(): string { - return this.data.disputableData.rawLLMInput; - } - - get rawLLMOutput(): string { - return this.data.disputableData.rawLLMOutput; - } - - get shouldDispute(): boolean { - return this.data.disputableData.shouldDispute; - } - - get isDisputable(): boolean { - return this.disputableUntil !== undefined && this.disputableUntil > Date.now() / 1000; - } -} - -const EMPTY_BLOCK_RANGE: BlockRange = [0, 0]; - -/** - * Abstract class representing a client to interact with an Optimistic Oracle and store the requests. - */ -export abstract class OptimisticOracleClient { - protected provider: Provider; - readonly requests: ReadonlyMap; - readonly fetchedBlockRange: BlockRange; - - /** - * Constructs a new instance of OptimisticOracleClient. - * @param _provider The provider used for interacting with the blockchain. - * @param _requests (Optional) The map of Optimistic Oracle requests. - * @param _fetchedBlockRanges (Optional) The block ranges of the fetched requests. - * @dev requests are stored in a map for faster access and to avoid duplicates. - */ - protected constructor( - _provider: Provider, - _requests: Map = new Map(), - _fetchedBlockRange: BlockRange = EMPTY_BLOCK_RANGE - ) { - this.provider = _provider; - this.requests = _requests; - this.fetchedBlockRange = _fetchedBlockRange; - } - - /** - * Returns a copy of the OptimisticOracleClient - * @returns A copy of the OptimisticOracleClient - * @dev This is a deep copy. - */ - copy(): OptimisticOracleClient { - return this.createClientInstance(new Map(this.requests), this.fetchedBlockRange); - } - - /** - * Creates a new instance of the OptimisticOracleClient with the specified requests. - * Must be implemented by the derived class. - * @param requests The requests to be set on the new instance. - * @param fetchedBlockRange The block range of the fetched requests. - * @returns A new instance of OptimisticOracleClient. - */ - protected abstract createClientInstance( - requests: Map, - fetchedBlockRanges: BlockRange - ): OptimisticOracleClient; - - /** - * Returns a copy of the updated Requests by fetching new Oracle requests updates. - * @param blockRange The new blockRange to fetch requests from. - * @returns A Promise that resolves to a copy of the updated Requests map. - */ - protected abstract updateOracleRequests(blockRange: BlockRange): Promise>; - - /** - * Updates the OptimisticOracleClient instance by fetching new Oracle requests within the specified block range. Returns a new instance. - * @param blockRange (Optional) The block range to fetch new requests from. - * @returns A Promise that resolves to a new OptimisticOracleClient instance with updated requests. - */ - async updateWithBlockRange(blockRange?: BlockRange): Promise> { - let range: BlockRange; - if (blockRange) { - if (blockRange[0] > blockRange[1]) - throw new Error("Start block number should be less than or equal to end block number"); - range = blockRange; - } else { - // Calculate the next block range to fetch - const latestBlock = await this.provider.getBlockNumber(); - const nextStartBlock = this.fetchedBlockRange[1] + 1; - if (nextStartBlock > latestBlock) return this; // no new blocks to fetch - range = [nextStartBlock, latestBlock]; - } - const [startBlock, endBlock] = range; - - // Throw an error if the new range doesn't directly follow the last fetched range - const lastFetchedEndBlock = this.fetchedBlockRange[1]; - if (lastFetchedEndBlock != 0 && startBlock !== lastFetchedEndBlock + 1) - throw new Error( - "New block range does not follow the last fetched block range, there is a gap between the ranges" - ); - - // We enforce the creation of a new instance of the client to avoid mutating the current instance - const newRequests = await this.updateOracleRequests([startBlock, endBlock]); - - return this.createClientInstance(newRequests, [startBlock, endBlock]); - } - - /** - * Returns the block ranges of the fetched requests. - * @returns An array of pairs of numbers representing the block ranges. - */ - getFetchedBlockRange(): BlockRange { - return this.fetchedBlockRange; - } - - /** - * Returns the provider used for interacting with the blockchain. - * @returns The provider object. - */ - getProvider(): Provider { - return this.provider; - } -} - -/** - * Represents a filtering strategy for an Optimistic Oracle client price requests. - * @template I The type of the input OptimisticOracleRequest. - * @template O The type of the output OptimisticOracleRequest. - */ -export interface OptimisticOracleClientFilter { - /** - * Filters and/or augments Optimistic Oracle requests. - * @param optimisticOracleRequests The Optimistic Oracle requests to be filtered. - * @returns A Promise that resolves to the filtered Optimistic Oracle requests. - */ - filter(optimisticOracleRequests: I[]): Promise; -} - -/** - * Abstract class representing a strategy for processing Optimistic Oracle requests. - * @template I The type of the input OptimisticOracleRequest. - * @template R The type of the result, based on OptimisticOracleRequestDisputable. - */ -export interface LLMDisputerStrategy { - /** - * Processes Optimistic Oracle requests using the strategy implementation. - * @param request The Optimistic Oracle request to be processed. - * @returns A Promise that resolves to the result of the processing. - */ - process(request: I): Promise; -} diff --git a/packages/llm-bot/src/examples.ts b/packages/llm-bot/src/examples.ts deleted file mode 100644 index 59235befc2..0000000000 --- a/packages/llm-bot/src/examples.ts +++ /dev/null @@ -1,119 +0,0 @@ -import { Provider } from "@ethersproject/abstract-provider"; -import { ethers } from "ethers"; -import { - BlockRange, - OptimisticOracleClient, - OptimisticOracleClientFilter, - OptimisticOracleRequest, - OptimisticOracleRequestData, -} from "./common"; - -export class OptimisticOracleClientV2 extends OptimisticOracleClient { - constructor( - _provider: Provider, - _requests: Map = new Map(), - _fetchedBlockRange?: BlockRange - ) { - super(_provider, _requests, _fetchedBlockRange); - } - - protected async updateOracleRequests(blockRange: BlockRange): Promise> { - // TODO: Implement this for the OptimisticOracleV2 - blockRange; - return new Map(); - } - - protected createClientInstance( - requests: Map, - fetchedBlockRange: BlockRange - ): OptimisticOracleClientV2 { - return new OptimisticOracleClientV2(this.provider, requests, fetchedBlockRange); - } -} - -class OptimisticOracleRequestPolymarket extends OptimisticOracleRequest { - readonly polymarketQuestionTitle: string; - - constructor(data: OptimisticOracleRequestData & { polymarketQuestionTitle: string }) { - super(data); - this.polymarketQuestionTitle = data.polymarketQuestionTitle; - } -} - -export class OptimisticOracleClientV2Polymarket extends OptimisticOracleClient { - constructor( - _provider: Provider, - _requests: Map = new Map(), - _fetchedBlockRange?: BlockRange - ) { - super(_provider, _requests, _fetchedBlockRange); - } - - protected async updateOracleRequests( - blockRange: BlockRange - ): Promise> { - // TODO: Implement this for the OptimisticOracleV2 - blockRange; - return new Map(); - } - - protected createClientInstance( - requests: Map, - fetchedBlockRange: BlockRange - ): OptimisticOracleClientV2Polymarket { - return new OptimisticOracleClientV2Polymarket(this.provider, requests, fetchedBlockRange); - } -} - -export class OptimisticOracleClientV3 extends OptimisticOracleClient { - constructor( - _provider: Provider, - _requests: Map = new Map(), - _fetchedBlockRange?: BlockRange - ) { - super(_provider, _requests, _fetchedBlockRange); - } - - protected async updateOracleRequests(blockRange: BlockRange): Promise> { - // TODO: Implement this for the OptimisticOracleV3 - blockRange; - return new Map(); - } - - protected createClientInstance( - requests: Map, - fetchedBlockRange: BlockRange - ): OptimisticOracleClientV3 { - return new OptimisticOracleClientV3(this.provider, requests, fetchedBlockRange); - } -} - -export class OptimisticOracleClientFilterV2ToPolymarket - implements OptimisticOracleClientFilter { - async filter(optimisticOracleRequests: OptimisticOracleRequest[]): Promise { - // Filtering logic for price requests - const filteredRequests = optimisticOracleRequests.map((request) => { - return new OptimisticOracleRequestPolymarket({ - ...request.data, - polymarketQuestionTitle: "What is the price of ETH?", - }); - }); - - return filteredRequests; - } -} - -const main = async () => { - const provider = new ethers.providers.JsonRpcProvider("http://localhost:8545"); - - const oov2 = new OptimisticOracleClientV2(provider); - - const oov2_updated = await oov2.updateWithBlockRange(); - - const oov2_filtered = await new OptimisticOracleClientFilterV2ToPolymarket().filter( - Array.from(oov2_updated.requests.values()) - ); - oov2_filtered; -}; - -main(); diff --git a/packages/llm-bot/utils/logger.ts b/packages/llm-bot/src/utils/logger.ts similarity index 100% rename from packages/llm-bot/utils/logger.ts rename to packages/llm-bot/src/utils/logger.ts diff --git a/packages/llm-bot/test/OptimisticOracleV2LLM.ts b/packages/llm-bot/test/OptimisticOracleV2LLM.ts index 9b2019bdce..5b5199fde0 100644 --- a/packages/llm-bot/test/OptimisticOracleV2LLM.ts +++ b/packages/llm-bot/test/OptimisticOracleV2LLM.ts @@ -6,10 +6,14 @@ import { OptimisticOracleClientV2, OptimisticOracleClientV2FilterDisputeable, <<<<<<< HEAD +<<<<<<< HEAD } from "../src/core/OptimisticOracleV2"; ======= } from "../src/OptimisticOracleV2"; >>>>>>> 746ff5a88 (refactor: move llm bots to separate package) +======= +} from "../src/core/OptimisticOracleV2"; +>>>>>>> 99d3cdc95 (refactor: folders) import { defaultOptimisticOracleV2Identifier } from "./constants"; import { optimisticOracleV2Fixture } from "./fixtures/OptimisticOracleV2.Fixture"; import { Signer, hre, toUtf8Bytes } from "./utils"; diff --git a/packages/llm-bot/utils/constants.ts b/packages/llm-bot/utils/constants.ts deleted file mode 100644 index b695804c9e..0000000000 --- a/packages/llm-bot/utils/constants.ts +++ /dev/null @@ -1,41 +0,0 @@ -interface BlockConfig { - oneHour: number; - maxBlockLookBack: number; -} - -/** - * Default configuration for different blockchain networks. - * Each network is identified by its chain ID. - */ -export const blockDefaults: Record = { - "1": { - // Mainnet configuration - oneHour: 300, // Approximate number of blocks mined in one hour (12 seconds per block) - maxBlockLookBack: 20000, // Maximum number of blocks to look back for events - }, - "137": { - // Polygon (Matic) configuration - oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) - maxBlockLookBack: 3499, // Maximum number of blocks to look back for events - }, - "10": { - // Optimism configuration - oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) - maxBlockLookBack: 10000, // Maximum number of blocks to look back for events - }, - "42161": { - // Arbitrum configuration - oneHour: 240, // Approximate number of blocks mined in one hour (15 seconds per block) - maxBlockLookBack: 10000, // Maximum number of blocks to look back for events - }, - "43114": { - // Avalanche configuration - oneHour: 1800, // Approximate number of blocks mined in one hour (2 seconds per block) - maxBlockLookBack: 2000, // Maximum number of blocks to look back for events - }, - other: { - // Default configuration for other networks - oneHour: 240, // Approximate number of blocks mined in one hour (15 seconds per block) - maxBlockLookBack: 1000, // Maximum number of blocks to look back for events - }, -}; diff --git a/packages/llm-bot/utils/contracts.ts b/packages/llm-bot/utils/contracts.ts deleted file mode 100644 index c8732ab892..0000000000 --- a/packages/llm-bot/utils/contracts.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ContractName, ERC20Ethers, getAbi, getAddress } from "@uma/contracts-node"; -import { Contract } from "ethers"; -import { Provider } from "@ethersproject/abstract-provider"; -import { utils } from "ethers"; - -export const getContractInstanceWithProvider = async ( - contractName: ContractName, - provider: Provider, - address?: string -): Promise => { - const networkId = (await provider.getNetwork()).chainId; - const contractAddress = address || (await getAddress(contractName, networkId)); - const contractAbi = getAbi(contractName); - return new Contract(contractAddress, contractAbi, provider) as T; -}; - -export const tryHexToUtf8String = (ancillaryData: string): string => { - try { - return utils.toUtf8String(ancillaryData); - } catch (err) { - return ancillaryData; - } -}; - -export const getCurrencyDecimals = async (provider: Provider, currencyAddress: string): Promise => { - const currencyContract = await getContractInstanceWithProvider("ERC20", provider, currencyAddress); - try { - return await currencyContract.decimals(); - } catch (err) { - return 18; - } -}; - -export const getCurrencySymbol = async (provider: Provider, currencyAddress: string): Promise => { - const currencyContract = await getContractInstanceWithProvider("ERC20", provider, currencyAddress); - try { - return await currencyContract.symbol(); - } catch (err) { - // Try to get the symbol as bytes32 (e.g. MKR uses this). - try { - const bytes32SymbolIface = new utils.Interface(["function symbol() view returns (bytes32 symbol)"]); - const bytes32Symbol = await provider.call({ - to: currencyAddress, - data: bytes32SymbolIface.encodeFunctionData("symbol"), - }); - return utils.parseBytes32String(bytes32SymbolIface.decodeFunctionResult("symbol", bytes32Symbol).symbol); - } catch (err) { - return ""; - } - } -}; - -// Gets the topic of an event from its name. In case of overloaded events, the first one found is returned. -export const getEventTopic = (contractName: ContractName, eventName: string): string => { - const contractAbi = getAbi(contractName); - const iface = new utils.Interface(contractAbi); - const eventKey = Object.keys(iface.events).find((key) => iface.events[key].name === eventName); - if (!eventKey) throw new Error(`Event ${eventName} not found in contract ${contractName}`); - return utils.keccak256(utils.toUtf8Bytes(eventKey)); -}; From bbce5d69845dc13feaadf5c5c837733eae567dac Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 30 Aug 2023 16:50:38 +0200 Subject: [PATCH 3/5] feat: add llm bot base Signed-off-by: Pablo Maldonado --- .../dispute-bot/DisputeDisputableRequests.ts | 31 +++++++ packages/llm-bot/src/dispute-bot/README.md | 27 ++++++ packages/llm-bot/src/dispute-bot/common.ts | 71 +++++++++++++++ packages/llm-bot/src/dispute-bot/index.ts | 54 ++++++++++++ packages/llm-bot/test/DisputeRequests.ts | 87 +++++++++++++++++++ 5 files changed, 270 insertions(+) create mode 100644 packages/llm-bot/src/dispute-bot/DisputeDisputableRequests.ts create mode 100644 packages/llm-bot/src/dispute-bot/README.md create mode 100644 packages/llm-bot/src/dispute-bot/common.ts create mode 100644 packages/llm-bot/src/dispute-bot/index.ts create mode 100644 packages/llm-bot/test/DisputeRequests.ts diff --git a/packages/llm-bot/src/dispute-bot/DisputeDisputableRequests.ts b/packages/llm-bot/src/dispute-bot/DisputeDisputableRequests.ts new file mode 100644 index 0000000000..0fb9e49a64 --- /dev/null +++ b/packages/llm-bot/src/dispute-bot/DisputeDisputableRequests.ts @@ -0,0 +1,31 @@ +import { + DisputerStrategy, + OptimisticOracleClientV2, + OptimisticOracleClientV2FilterDisputeable, +} from "../core/OptimisticOracleV2"; +import { Logger, MonitoringParams } from "./common"; + +export async function disputeDisputableRequests(logger: typeof Logger, params: MonitoringParams): Promise { + const oov2 = new OptimisticOracleClientV2(params.provider); + + // Update the client with the latest block range. + const oov2ClientUpdated = await oov2.updateWithBlockRange(); + + const requests = Array.from(oov2ClientUpdated.requests.values()); + const oov2FilterDisputable = new OptimisticOracleClientV2FilterDisputeable(); + + const filteredRequests = await oov2FilterDisputable.filter(requests); + + const disputable = await Promise.all(filteredRequests.map(DisputerStrategy.process)); + + for (const request of disputable) { + logger.info({ + at: "LLMDisputeBot", + message: "Disputing request", + request, + }); + // TODO: Dispute the request. + } + + console.log("Done speeding up prices."); +} diff --git a/packages/llm-bot/src/dispute-bot/README.md b/packages/llm-bot/src/dispute-bot/README.md new file mode 100644 index 0000000000..6e85af2222 --- /dev/null +++ b/packages/llm-bot/src/dispute-bot/README.md @@ -0,0 +1,27 @@ +# LLM Dispute Bot + +The LLM Dispute Bot can check for disputable requests with LLM logic and dispute them. + +The main entry point to LLM Dispute Bot is running: + +``` +node ./packages/llm-bot/dist/dispute-bot/index.js +``` + +All the configuration should be provided with following environment variables: + +- `CHAIN_ID` is network number. +- `NODE_URLS_X` is an array of RPC node URLs replacing `X` in variable name with network number from `CHAIN_ID`. +- `NODE_URL_X` is a single RPC node URL replacing `X` in variable name with network number from `CHAIN_ID`. This is considered only if matching `NODE_URLS_X` is not provided. +- `MNEMONIC` is a mnemonic for a wallet that has enough funds to pay for transactions. +- `GCKMS_WALLET` is a GCKMS wallet that has enough funds to pay for transactions. If this is provided, `MNEMONIC` is ignored. +- `NODE_RETRIES` is the number of retries to make when a node request fails (defaults to `2`). +- `NODE_RETRY_DELAY` is the delay in seconds between retries (defaults to `1`). +- `NODE_TIMEOUT` is the timeout in seconds for node requests (defaults to `60`). +- `POLLING_DELAY` is value in seconds for delay between consecutive runs, defaults to 1 minute. If set to 0 then running in serverless mode will exit after the loop. +- `DISPUTE_DISPUTABLE_REQUESTS` is boolean enabling/disabling disputes with LLM bot (`false` by default). +- `SLACK_CONFIG` is a JSON object containing `defaultWebHookUrl` for the default Slack webhook URL. +- `BLOCK_LOOKBACK` is the number of blocks to look back from the current block to look for past resolution events. + See default values in blockDefaults in index.ts +- `MAX_BLOCK_LOOKBACK` is the maximum number of blocks to look back per query. + See default values in blockDefaults in index.ts diff --git a/packages/llm-bot/src/dispute-bot/common.ts b/packages/llm-bot/src/dispute-bot/common.ts new file mode 100644 index 0000000000..5763fb9d18 --- /dev/null +++ b/packages/llm-bot/src/dispute-bot/common.ts @@ -0,0 +1,71 @@ +import { getGckmsSigner, getMnemonicSigner, getRetryProvider } from "@uma/common"; +import { Signer, Wallet } from "ethers"; + +import type { Provider } from "@ethersproject/abstract-provider"; + +export { OptimisticOracleV3Ethers } from "@uma/contracts-node"; +export { Logger } from "@uma/financial-templates-lib"; +export { getContractInstanceWithProvider } from "../utils/contracts"; + +export const ARBITRUM_CHAIN_ID = 42161; +export const OPTIMISM_CHAIN_ID = 10; +export const POLYGON_CHAIN_ID = 137; + +export interface BotModes { + disputeDisputableRequests: boolean; +} + +export interface BlockRange { + start: number; + end: number; +} + +export interface MonitoringParams { + chainId: number; + provider: Provider; + botModes: BotModes; + signer: Signer; + blockLookback: number; + maxBlockLookBack: number; + pollingDelay: number; +} + +export const initMonitoringParams = async (env: NodeJS.ProcessEnv): Promise => { + if (!env.CHAIN_ID) throw new Error("CHAIN_ID must be defined in env"); + const chainId = Number(env.CHAIN_ID); + + // Creating provider will check for other chainId specific env variables. + const provider = getRetryProvider(chainId) as Provider; + + // Default to 1 minute polling delay. + const pollingDelay = env.POLLING_DELAY ? Number(env.POLLING_DELAY) : 60; + + let signer; + if (process.env.GCKMS_WALLET) { + signer = ((await getGckmsSigner()) as Wallet).connect(provider); + } else { + // Throws if MNEMONIC env var is not defined. + signer = (getMnemonicSigner() as Signer).connect(provider); + } + + const botModes = { + disputeDisputableRequests: env.DISPUTE_DISPUTABLE_REQUESTS === "true", + }; + + const blockLookback = Number(env.BLOCK_LOOKBACK); + const maxBlockLookBack = Number(env.MAX_BLOCK_LOOKBACK); + + return { + chainId, + provider, + botModes, + signer, + blockLookback, + maxBlockLookBack, + pollingDelay, + }; +}; + +export const startupLogLevel = (params: MonitoringParams): "debug" | "info" => { + return params.pollingDelay === 0 ? "debug" : "info"; +}; diff --git a/packages/llm-bot/src/dispute-bot/index.ts b/packages/llm-bot/src/dispute-bot/index.ts new file mode 100644 index 0000000000..861a8b759b --- /dev/null +++ b/packages/llm-bot/src/dispute-bot/index.ts @@ -0,0 +1,54 @@ +import { delay, waitForLogger } from "@uma/financial-templates-lib"; +import { BotModes, initMonitoringParams, Logger, startupLogLevel } from "./common"; +import { disputeDisputableRequests } from "./DisputeDisputableRequests"; + +const logger = Logger; + +async function main() { + const params = await initMonitoringParams(process.env); + + logger[startupLogLevel(params)]({ + at: "LLMDisputeBot", + message: "LLMDisputeBot started 🤖", + botModes: params.botModes, + }); + + const cmds = { + disputeRequestsEnabled: disputeDisputableRequests, + }; + + for (;;) { + const runCmds = Object.entries(cmds) + .filter(([mode]) => params.botModes[mode as keyof BotModes]) + .map(([, cmd]) => cmd(logger, { ...params })); + + for (const cmd of runCmds) { + await cmd; + } + + if (params.pollingDelay !== 0) { + await delay(params.pollingDelay); + } else { + await delay(5); // Set a delay to let the transports flush fully. + await waitForLogger(logger); + break; + } + } +} + +main().then( + () => { + process.exit(0); + }, + async (error) => { + logger.error({ + at: "LLMDisputeBot", + message: "LLMDisputeBot error🚨", + error, + }); + // Wait 5 seconds to allow logger to flush. + await delay(5); + await waitForLogger(logger); + process.exit(1); + } +); diff --git a/packages/llm-bot/test/DisputeRequests.ts b/packages/llm-bot/test/DisputeRequests.ts new file mode 100644 index 0000000000..f2192354d2 --- /dev/null +++ b/packages/llm-bot/test/DisputeRequests.ts @@ -0,0 +1,87 @@ +import { ExpandedERC20Ethers, OptimisticOracleV2Ethers } from "@uma/contracts-node"; +import { SpyTransport, createNewLogger } from "@uma/financial-templates-lib"; +import { assert } from "chai"; +import sinon from "sinon"; +import { disputeDisputableRequests } from "../src/dispute-bot/DisputeDisputableRequests"; +import { BotModes, MonitoringParams } from "../src/dispute-bot/common"; +import { defaultOptimisticOracleV2Identifier } from "./constants"; +import { optimisticOracleV2Fixture } from "./fixtures/OptimisticOracleV2.Fixture"; +import { Provider, Signer, hre, toUtf8Bytes } from "./utils"; + +const ethers = hre.ethers; + +const createMonitoringParams = async (): Promise => { + // get chain id + const chainId = await hre.ethers.provider.getNetwork().then((network) => network.chainId); + // get hardhat signer + const [signer] = await ethers.getSigners(); + // Bot modes are not used as we are calling monitor modules directly. + const botModes: BotModes = { + disputeDisputableRequests: true, + }; + return { + chainId: chainId, + provider: ethers.provider as Provider, + pollingDelay: 0, + botModes, + signer, + maxBlockLookBack: 1000, + blockLookback: 1000, + }; +}; + +describe("LLMDisputeDisputableRequests", function () { + let bondToken: ExpandedERC20Ethers; + let optimisticOracleV2: OptimisticOracleV2Ethers; + let requester: Signer; + let proposer: Signer; + let disputer: Signer; + + const bond = ethers.utils.parseEther("1000"); + + const question = "This is just a test question"; + const ancillaryData = toUtf8Bytes(question); + + beforeEach(async function () { + // Signer from ethers and hardhat-ethers are not version compatible, thus, we cannot use the SignerWithAddress. + [requester, proposer, disputer] = (await ethers.getSigners()) as Signer[]; + + // Get contract instances. + const optimisticOracleV2Contracts = await optimisticOracleV2Fixture(); + + bondToken = optimisticOracleV2Contracts.bondToken; + optimisticOracleV2 = optimisticOracleV2Contracts.optimisticOracleV2; + + // Fund proposer and disputer with bond amount and approve Optimistic Oracle V2 to spend bond tokens. + await bondToken.addMinter(await requester.getAddress()); + await bondToken.mint(await proposer.getAddress(), bond); + await bondToken.mint(await disputer.getAddress(), bond); + await bondToken.connect(proposer).approve(optimisticOracleV2.address, bond); + await bondToken.connect(disputer).approve(optimisticOracleV2.address, bond); + }); + + it("Disputes disputable requests", async function () { + await ( + await optimisticOracleV2.requestPrice(defaultOptimisticOracleV2Identifier, 0, ancillaryData, bondToken.address, 0) + ).wait(); + + await (await bondToken.connect(proposer).approve(optimisticOracleV2.address, bond)).wait(); + await ( + await optimisticOracleV2 + .connect(proposer) + .proposePrice( + await requester.getAddress(), + defaultOptimisticOracleV2Identifier, + 0, + ancillaryData, + ethers.utils.parseEther("1") + ) + ).wait(); + + const spy = sinon.spy(); + const spyLogger = createNewLogger([new SpyTransport({}, { spy: spy })]); + + await disputeDisputableRequests(spyLogger, await createMonitoringParams()); + assert.equal(spy.getCall(0).lastArg.at, "LLMDisputeBot"); + }); +}); From d75162e5fc66dda02d67953b4f89c4c4dc0d572e Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Sun, 3 Sep 2023 21:01:06 +0200 Subject: [PATCH 4/5] refactor: rename init monitoring params Signed-off-by: Pablo Maldonado --- .../llm-bot/src/dispute-bot/DisputeDisputableRequests.ts | 4 ++-- packages/llm-bot/src/dispute-bot/common.ts | 6 +++--- packages/llm-bot/src/dispute-bot/index.ts | 4 ++-- packages/llm-bot/test/OptimisticOracleV2LLM.ts | 8 -------- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/packages/llm-bot/src/dispute-bot/DisputeDisputableRequests.ts b/packages/llm-bot/src/dispute-bot/DisputeDisputableRequests.ts index 0fb9e49a64..854711478f 100644 --- a/packages/llm-bot/src/dispute-bot/DisputeDisputableRequests.ts +++ b/packages/llm-bot/src/dispute-bot/DisputeDisputableRequests.ts @@ -3,9 +3,9 @@ import { OptimisticOracleClientV2, OptimisticOracleClientV2FilterDisputeable, } from "../core/OptimisticOracleV2"; -import { Logger, MonitoringParams } from "./common"; +import { Logger, BotParams } from "./common"; -export async function disputeDisputableRequests(logger: typeof Logger, params: MonitoringParams): Promise { +export async function disputeDisputableRequests(logger: typeof Logger, params: BotParams): Promise { const oov2 = new OptimisticOracleClientV2(params.provider); // Update the client with the latest block range. diff --git a/packages/llm-bot/src/dispute-bot/common.ts b/packages/llm-bot/src/dispute-bot/common.ts index 5763fb9d18..dda32c4842 100644 --- a/packages/llm-bot/src/dispute-bot/common.ts +++ b/packages/llm-bot/src/dispute-bot/common.ts @@ -20,7 +20,7 @@ export interface BlockRange { end: number; } -export interface MonitoringParams { +export interface BotParams { chainId: number; provider: Provider; botModes: BotModes; @@ -30,7 +30,7 @@ export interface MonitoringParams { pollingDelay: number; } -export const initMonitoringParams = async (env: NodeJS.ProcessEnv): Promise => { +export const initBotParams = async (env: NodeJS.ProcessEnv): Promise => { if (!env.CHAIN_ID) throw new Error("CHAIN_ID must be defined in env"); const chainId = Number(env.CHAIN_ID); @@ -66,6 +66,6 @@ export const initMonitoringParams = async (env: NodeJS.ProcessEnv): Promise { +export const startupLogLevel = (params: BotParams): "debug" | "info" => { return params.pollingDelay === 0 ? "debug" : "info"; }; diff --git a/packages/llm-bot/src/dispute-bot/index.ts b/packages/llm-bot/src/dispute-bot/index.ts index 861a8b759b..0ca30bae0a 100644 --- a/packages/llm-bot/src/dispute-bot/index.ts +++ b/packages/llm-bot/src/dispute-bot/index.ts @@ -1,11 +1,11 @@ import { delay, waitForLogger } from "@uma/financial-templates-lib"; -import { BotModes, initMonitoringParams, Logger, startupLogLevel } from "./common"; +import { BotModes, initBotParams, Logger, startupLogLevel } from "./common"; import { disputeDisputableRequests } from "./DisputeDisputableRequests"; const logger = Logger; async function main() { - const params = await initMonitoringParams(process.env); + const params = await initBotParams(process.env); logger[startupLogLevel(params)]({ at: "LLMDisputeBot", diff --git a/packages/llm-bot/test/OptimisticOracleV2LLM.ts b/packages/llm-bot/test/OptimisticOracleV2LLM.ts index 5b5199fde0..f34af61205 100644 --- a/packages/llm-bot/test/OptimisticOracleV2LLM.ts +++ b/packages/llm-bot/test/OptimisticOracleV2LLM.ts @@ -5,15 +5,7 @@ import { DisputerStrategy, OptimisticOracleClientV2, OptimisticOracleClientV2FilterDisputeable, -<<<<<<< HEAD -<<<<<<< HEAD } from "../src/core/OptimisticOracleV2"; -======= -} from "../src/OptimisticOracleV2"; ->>>>>>> 746ff5a88 (refactor: move llm bots to separate package) -======= -} from "../src/core/OptimisticOracleV2"; ->>>>>>> 99d3cdc95 (refactor: folders) import { defaultOptimisticOracleV2Identifier } from "./constants"; import { optimisticOracleV2Fixture } from "./fixtures/OptimisticOracleV2.Fixture"; import { Signer, hre, toUtf8Bytes } from "./utils"; From 12a02dc91302f2b47b4f19ae2b98f03906f48656 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Sun, 3 Sep 2023 21:02:51 +0200 Subject: [PATCH 5/5] refator: remove unused file Signed-off-by: Pablo Maldonado --- packages/llm-bot/src/utils/logger.ts | 29 ---------------------------- 1 file changed, 29 deletions(-) delete mode 100644 packages/llm-bot/src/utils/logger.ts diff --git a/packages/llm-bot/src/utils/logger.ts b/packages/llm-bot/src/utils/logger.ts deleted file mode 100644 index 18f5de9a75..0000000000 --- a/packages/llm-bot/src/utils/logger.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { TenderlySimulationResult } from "@uma/common"; - -const optimisticOracleV2UIBaseUrl = "https://oracle.uma.xyz"; -const testnetOptimisticOracleV2UIBaseUrl = "https://testnet.oracle.uma.xyz"; - -// monitor-v2 package is only using Optimistic Oracle V3, so currently there is no need to generalize this. -export const generateOOv3UILink = (transactionHash: string, eventIndex: number, chainId?: number): string => { - // Currently testnet UI supports only goerli, so assume any other chain is production. - const baseUrl = chainId === 5 ? testnetOptimisticOracleV2UIBaseUrl : optimisticOracleV2UIBaseUrl; - return `<${baseUrl}/?transactionHash=${transactionHash}&eventIndex=${eventIndex}|View in UI>`; -}; - -export const createSnapshotProposalLink = (baseUrl: string, space: string, proposalId: string): string => { - return `<${baseUrl}/#/${space}/proposal/${proposalId}|Snapshot UI>`; -}; - -export const createTenderlySimulationLink = (simulationResult?: TenderlySimulationResult): string => { - if (simulationResult === undefined) { - return "No Tenderly simulation available"; - } else if (simulationResult.status) { - return `<${simulationResult.resultUrl.url}|Tenderly simulation successful${ - !simulationResult.resultUrl.public ? " (private)" : "" - }>`; - } else { - return `<${simulationResult.resultUrl.url}|Tenderly simulation reverted${ - !simulationResult.resultUrl.public ? " (private)" : "" - }>`; - } -};