diff --git a/packages/monitor-v2/src/bot-oo/README.md b/packages/monitor-v2/src/bot-oo/README.md index e58748de04..acb6f294d6 100644 --- a/packages/monitor-v2/src/bot-oo/README.md +++ b/packages/monitor-v2/src/bot-oo/README.md @@ -34,6 +34,7 @@ node ./packages/monitor-v2/dist/bot-oo/index.js - `GAS_LIMIT_MULTIPLIER`: Percent multiplier on estimated gas (default `150`). - `SETTLE_DELAY`: Lookback period in seconds to detect settleable requests (default `300`). - `SETTLE_TIMEOUT`: Timeout in seconds for submitting settlement transactions in serverless mode (default `240`). +- `SETTLE_ONLY_DISPUTED`: When `true`, only settle requests that have been disputed (`false` by default). Supported for `OptimisticOracleV2` (including `ManagedOptimisticOracleV2`); ignored for `OptimisticOracle` and `SkinnyOptimisticOracle`. ## Behavior diff --git a/packages/monitor-v2/src/bot-oo/SettleOOv2Requests.ts b/packages/monitor-v2/src/bot-oo/SettleOOv2Requests.ts index 701738696e..21aa3df566 100644 --- a/packages/monitor-v2/src/bot-oo/SettleOOv2Requests.ts +++ b/packages/monitor-v2/src/bot-oo/SettleOOv2Requests.ts @@ -116,8 +116,30 @@ export async function settleOOv2Requests( const signerAddress = await params.signer.getAddress(); + // State.Resolved = 5: disputed and DVM price is available (settleable after dispute). + const STATE_RESOLVED = 5; + const settleableRequestsPromises = requestsToSettle.map(async (req) => { try { + // When settleOnlyDisputed is enabled, check on-chain state and skip undisputed requests. + if (params.botModes.settleOnlyDisputed) { + const state = await oo.getState( + req.args.requester, + req.args.identifier, + req.args.timestamp, + req.args.ancillaryData + ); + if (state !== STATE_RESOLVED) { + logger.debug({ + at: "OOv2Bot", + message: "Skipping non-disputed request (settleOnlyDisputed)", + requestKey: requestKey(req.args), + state, + }); + return null; + } + } + await oo.callStatic.settle(req.args.requester, req.args.identifier, req.args.timestamp, req.args.ancillaryData, { blockTag: params.settleableCheckBlock, from: signerAddress, diff --git a/packages/monitor-v2/src/bot-oo/common.ts b/packages/monitor-v2/src/bot-oo/common.ts index c274b74b10..7651510560 100644 --- a/packages/monitor-v2/src/bot-oo/common.ts +++ b/packages/monitor-v2/src/bot-oo/common.ts @@ -8,6 +8,7 @@ export type OracleType = "OptimisticOracle" | "SkinnyOptimisticOracle" | "Optimi export interface BotModes { settleRequestsEnabled: boolean; + settleOnlyDisputed: boolean; // Supported for OptimisticOracleV2 (incl. ManagedOOv2); ignored for OOv1 and SkinnyOO. } export interface MonitoringParams extends BaseMonitoringParams { @@ -24,6 +25,7 @@ export const initMonitoringParams = async (env: NodeJS.ProcessEnv): Promise call.lastArg?.message === "Price Request Settled ✅"); assert.equal(subsequentLogs.length, 0, "No settlement logs should be generated on subsequent runs"); }); + + it("settleOnlyDisputed skips undisputed expired proposals", async function () { + await ( + await optimisticOracleV2.requestPrice(defaultOptimisticOracleV2Identifier, 0, ancillaryData, bondToken.address, 0) + ).wait(); + + const proposeReceipt = await ( + await optimisticOracleV2 + .connect(proposer) + .proposePrice( + await requester.getAddress(), + defaultOptimisticOracleV2Identifier, + 0, + ancillaryData, + ethers.utils.parseEther("1") + ) + ).wait(); + + // Move timer past liveness — request is settleable but was never disputed. + await advanceTimerPastLiveness(timer, proposeReceipt.blockNumber!, defaultLiveness); + + const { spy, logger } = makeSpyLogger(); + const params = await makeMonitoringParamsOO("OptimisticOracleV2", optimisticOracleV2.address, { + settleRequestsEnabled: false, + settleOnlyDisputed: true, + }); + await gasEstimator.update(); + await settleRequests(logger, params, gasEstimator); + + const settlementLogs = spy.getCalls().filter((call) => call.lastArg?.message === "Price Request Settled ✅"); + assert.equal(settlementLogs.length, 0, "Undisputed request should not be settled when settleOnlyDisputed is true"); + }); + + it("settleOnlyDisputed settles disputed request once DVM resolved", async function () { + await ( + await optimisticOracleV2.requestPrice(defaultOptimisticOracleV2Identifier, 0, ancillaryData, bondToken.address, 0) + ).wait(); + + await ( + await optimisticOracleV2 + .connect(proposer) + .proposePrice( + await requester.getAddress(), + defaultOptimisticOracleV2Identifier, + 0, + ancillaryData, + ethers.utils.parseEther("1") + ) + ).wait(); + + await ( + await optimisticOracleV2 + .connect(disputer) + .disputePrice(await requester.getAddress(), defaultOptimisticOracleV2Identifier, 0, ancillaryData) + ).wait(); + + // Resolve in DVM via MockOracle. + const pending = await mockOracle.getPendingQueries(); + const last = pending[pending.length - 1]!; + await ( + await mockOracle.pushPrice(last.identifier, last.time, last.ancillaryData, ethers.utils.parseEther("1")) + ).wait(); + + const { spy, logger } = makeSpyLogger(); + const params = await makeMonitoringParamsOO("OptimisticOracleV2", optimisticOracleV2.address, { + settleRequestsEnabled: false, + settleOnlyDisputed: true, + }); + await gasEstimator.update(); + await settleRequests(logger, params, gasEstimator); + + const settledIndex = spy + .getCalls() + .findIndex((c) => c.lastArg?.message === "Price Request Settled ✅" && c.lastArg?.at === "OOv2Bot"); + assert.isAbove(settledIndex, -1, "Disputed request should be settled when settleOnlyDisputed is true"); + }); }); diff --git a/packages/monitor-v2/test/helpers/monitoring.ts b/packages/monitor-v2/test/helpers/monitoring.ts index 8c014894fe..a019757d68 100644 --- a/packages/monitor-v2/test/helpers/monitoring.ts +++ b/packages/monitor-v2/test/helpers/monitoring.ts @@ -24,7 +24,8 @@ export async function makeMonitoringParamsOO( const [signer] = await ethers.getSigners(); const defaultBotModes: BotModesOO = { settleRequestsEnabled: false, - } as BotModesOO; + settleOnlyDisputed: false, + }; const mergedBotModes = { ...defaultBotModes, ...botModes } as BotModesOO;