Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/monitor-v2/src/bot-oo/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
22 changes: 22 additions & 0 deletions packages/monitor-v2/src/bot-oo/SettleOOv2Requests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 2 additions & 0 deletions packages/monitor-v2/src/bot-oo/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -24,6 +25,7 @@ export const initMonitoringParams = async (env: NodeJS.ProcessEnv): Promise<Moni

const botModes = {
settleRequestsEnabled: env.SETTLEMENTS_ENABLED === "true",
settleOnlyDisputed: env.SETTLE_ONLY_DISPUTED === "true",
};

if (!env.ORACLE_ADDRESS) throw new Error("ORACLE_ADDRESS must be defined in env");
Expand Down
76 changes: 76 additions & 0 deletions packages/monitor-v2/test/OptimisticOracleV2Bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,80 @@ describe("OptimisticOracleV2Bot", function () {
const subsequentLogs = spy.getCalls().filter((call) => 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");
});
});
3 changes: 2 additions & 1 deletion packages/monitor-v2/test/helpers/monitoring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down