From be2bb1a0fde1b093c8b7e97bf96ee4b353b0edd6 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Tue, 9 Sep 2025 22:38:13 +0200 Subject: [PATCH 1/3] set custom liveness and bond at propose time Signed-off-by: Gerhard Steenkamp --- .../src/mappings/optimisticOracleV2.ts | 21 +++++ .../managed-oracle-v2/managedOracle.test.ts | 83 ++++++++++++++++++- .../tests/managed-oracle-v2/utils.ts | 60 +++++++++++++- 3 files changed, 161 insertions(+), 3 deletions(-) diff --git a/packages/managed-oracle-v2/src/mappings/optimisticOracleV2.ts b/packages/managed-oracle-v2/src/mappings/optimisticOracleV2.ts index a4526ef..8f945fb 100644 --- a/packages/managed-oracle-v2/src/mappings/optimisticOracleV2.ts +++ b/packages/managed-oracle-v2/src/mappings/optimisticOracleV2.ts @@ -186,6 +186,27 @@ export function handleOptimisticProposePrice(event: ProposePrice): void { event.params.ancillaryData ); + // Look up custom bond and liveness values that may have been set before the request + let customBond = getCustomBond(event.params.requester, event.params.identifier, event.params.ancillaryData); + if (customBond !== null) { + const bond = customBond.customBond; + const currency = customBond.currency; + log.debug("custom bond of {} of currency {} was set for request Id: {}", [ + bond.toString(), + currency.toHexString(), + requestId, + ]); + request.bond = bond; + request.currency = currency; + } + + let customLiveness = getCustomLiveness(event.params.requester, event.params.identifier, event.params.ancillaryData); + if (customLiveness !== null) { + const liveness = customLiveness.customLiveness; + log.debug("custom liveness of {} was set for request Id: {}", [liveness.toString(), requestId]); + request.customLiveness = customLiveness.customLiveness; + } + request.save(); } diff --git a/packages/managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts b/packages/managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts index 0809cc5..59d6fa0 100644 --- a/packages/managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts +++ b/packages/managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts @@ -1,9 +1,10 @@ import { describe, test, clearStore, afterAll, assert, log, afterEach } from "matchstick-as/assembly/index"; import { handleCustomLivenessSet, handleCustomBondSet } from "../../src/mappings/managedOracleV2"; -import { handleOptimisticRequestPrice } from "../../src/mappings/optimisticOracleV2"; +import { handleOptimisticProposePrice, handleOptimisticRequestPrice } from "../../src/mappings/optimisticOracleV2"; import { createCustomLivenessSetEvent, createCustomBondSetEvent, + createProposePriceEvent, createRequestPriceEvent, mockGetState, State, @@ -26,6 +27,7 @@ namespace Constants { export const reward = 1000000; export const finalFee = 500000; export const timestamp = 1757284669; + export const customLiveness_2 = 123456; } describe("Managed OOv2", () => { @@ -120,7 +122,7 @@ describe("Managed OOv2", () => { log.info("Custom Bond: {}", [customBondEntity.customBond.toString()]); }); - test("Custom bond and liveness are applied to RequestPrice entity", () => { + test("Custom bond and liveness are applied to RequestPrice entity at REQUEST time", () => { mockGetState( Constants.requester, Constants.identifierHex, @@ -212,4 +214,81 @@ describe("Managed OOv2", () => { log.info("Custom Bond: {}", [priceRequestEntity.bond!.toString()]); log.info("State: {}", [priceRequestEntity.state!]); }); + + test("Custom bond and liveness are applied to RequestPrice entity at PROPOSE time", () => { + mockGetState( + Constants.requester, + Constants.identifierHex, + Constants.timestamp, + Constants.ancillaryData, + State.Requested + ); + // Step 1: Create RequestPrice event + const requestPriceEvent = createRequestPriceEvent( + Constants.requester, + Constants.identifierHex, + Constants.timestamp, + Constants.ancillaryData, + Constants.currency, + Constants.reward, + Constants.finalFee + ); + handleOptimisticRequestPrice(requestPriceEvent); + + // Step 2: Set custom liveness + const customLivenessEvent = createCustomLivenessSetEvent( + Constants.managedRequestId, + Constants.requester, + Constants.identifierHex, + Constants.ancillaryData, + Constants.customLiveness + ); + handleCustomLivenessSet(customLivenessEvent); + + mockGetState( + Constants.requester, + Constants.identifierHex, + Constants.timestamp, + Constants.ancillaryData, + State.Proposed + ); + + // Step 3: Create ProposePrice event + const proposePriceEvent = createProposePriceEvent( + Constants.requester, + Constants.requester, + Constants.identifierHex, + Constants.timestamp, + Constants.ancillaryData, + 1, + Constants.timestamp + 3600, + Constants.currency + ); + handleOptimisticProposePrice(proposePriceEvent); + + const requestId = Constants.identifierString + .concat("-") + .concat(Constants.timestamp.toString()) + .concat("-") + .concat(Constants.ancillaryData); + + const priceRequestEntity = OptimisticPriceRequest.load(requestId); + + assert.assertTrue(priceRequestEntity !== null, "OptimisticPriceRequest entity should be created"); + + if (priceRequestEntity === null) { + return; + } + + // Assert custom liveness is applied + assert.bigIntEquals( + priceRequestEntity.customLiveness!, + BigInt.fromI32(Constants.customLiveness), + "Custom liveness should be applied to RequestPrice" + ); + + log.info("Created OptimisticPriceRequest entity: {}", [priceRequestEntity.id]); + log.info("Custom Liveness: {}", [priceRequestEntity.customLiveness!.toString()]); + log.info("State: {}", [priceRequestEntity.state!]); + }); }); diff --git a/packages/managed-oracle-v2/tests/managed-oracle-v2/utils.ts b/packages/managed-oracle-v2/tests/managed-oracle-v2/utils.ts index 8945edd..a409904 100644 --- a/packages/managed-oracle-v2/tests/managed-oracle-v2/utils.ts +++ b/packages/managed-oracle-v2/tests/managed-oracle-v2/utils.ts @@ -1,6 +1,11 @@ import { createMockedFunction, newMockEvent } from "matchstick-as"; import { ethereum, BigInt, Address, Bytes } from "@graphprotocol/graph-ts"; -import { CustomBondSet, CustomLivenessSet, RequestPrice } from "../../generated/ManagedOracleV2/ManagedOracleV2"; +import { + CustomBondSet, + CustomLivenessSet, + ProposePrice, + RequestPrice, +} from "../../generated/ManagedOracleV2/ManagedOracleV2"; export const contractAddress = Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7"); // Default test contract address @@ -122,6 +127,59 @@ export function createRequestPriceEvent( return requestPriceEvent; } +export function createProposePriceEvent( + requester: string, // Address, + proposer: string, // Address, + identifier: string, // Bytes, + timestamp: i32, // BigInt, + ancillaryData: string, // Bytes, + proposedPrice: i32, // BigInt, + expirationTimestamp: i32, // BigInt, + currency: string // Address +): ProposePrice { + let proposePriceEvent = changetype(newMockEvent()); + proposePriceEvent.address = contractAddress; + proposePriceEvent.parameters = new Array(); + + // requester + proposePriceEvent.parameters.push( + new ethereum.EventParam("requester", ethereum.Value.fromAddress(Address.fromString(requester))) + ); + // proposer + proposePriceEvent.parameters.push( + new ethereum.EventParam("proposer", ethereum.Value.fromAddress(Address.fromString(proposer))) + ); + // identifier + proposePriceEvent.parameters.push( + new ethereum.EventParam("identifier", ethereum.Value.fromBytes(Bytes.fromHexString(identifier))) + ); + // timestamp + proposePriceEvent.parameters.push( + new ethereum.EventParam("timestamp", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(timestamp))) + ); + // ancillaryData + proposePriceEvent.parameters.push( + new ethereum.EventParam("ancillaryData", ethereum.Value.fromBytes(Bytes.fromHexString(ancillaryData))) + ); + // proposedPrice + proposePriceEvent.parameters.push( + new ethereum.EventParam("proposedPrice", ethereum.Value.fromSignedBigInt(BigInt.fromI32(proposedPrice))) + ); + // expirationTimestamp + proposePriceEvent.parameters.push( + new ethereum.EventParam( + "expirationTimestamp", + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(expirationTimestamp)) + ) + ); + // currency + proposePriceEvent.parameters.push( + new ethereum.EventParam("currency", ethereum.Value.fromAddress(Address.fromString(currency))) + ); + + return proposePriceEvent; +} + // https://github.com/UMAprotocol/protocol/blob/99b96247d27ec8a5ea9dbf3eef1dcd71beb0dc41/packages/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol#L51 export namespace State { export const Invalid = 0; // Never requested From 015c6b98a09931d590bb8ca500772b3520ecd5d1 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Wed, 10 Sep 2025 14:10:42 +0200 Subject: [PATCH 2/3] test custom bond set at proposal time Signed-off-by: Gerhard Steenkamp --- .../managed-oracle-v2/managedOracle.test.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts b/packages/managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts index 59d6fa0..738db80 100644 --- a/packages/managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts +++ b/packages/managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts @@ -28,6 +28,8 @@ namespace Constants { export const finalFee = 500000; export const timestamp = 1757284669; export const customLiveness_2 = 123456; + export const customBond_2 = 3000000; + export const currency_2 = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; } describe("Managed OOv2", () => { @@ -212,6 +214,7 @@ describe("Managed OOv2", () => { log.info("Created OptimisticPriceRequest entity: {}", [priceRequestEntity.id]); log.info("Custom Liveness: {}", [priceRequestEntity.customLiveness!.toString()]); log.info("Custom Bond: {}", [priceRequestEntity.bond!.toString()]); + log.info("Custom Bond Currency: {}", [priceRequestEntity.currency!.toHexString()]); log.info("State: {}", [priceRequestEntity.state!]); }); @@ -244,6 +247,16 @@ describe("Managed OOv2", () => { Constants.customLiveness ); handleCustomLivenessSet(customLivenessEvent); + // Step 3: Set custom bond + const customBondEvent = createCustomBondSetEvent( + Constants.managedRequestId, + Constants.requester, + Constants.identifierHex, + Constants.ancillaryData, + Constants.currency_2, + Constants.customBond_2 + ); + handleCustomBondSet(customBondEvent); mockGetState( Constants.requester, @@ -253,7 +266,7 @@ describe("Managed OOv2", () => { State.Proposed ); - // Step 3: Create ProposePrice event + // Step 4: Create ProposePrice event const proposePriceEvent = createProposePriceEvent( Constants.requester, Constants.requester, @@ -280,7 +293,19 @@ describe("Managed OOv2", () => { return; } - // Assert custom liveness is applied + // Assert custom values are applied + assert.addressEquals( + Address.fromBytes(priceRequestEntity.currency), + Address.fromString(Constants.currency_2), + "Currency should match" + ); + + assert.bigIntEquals( + priceRequestEntity.bond!, + BigInt.fromI32(Constants.customBond_2), + "Custom bond should be applied to RequestPrice" + ); + assert.bigIntEquals( priceRequestEntity.customLiveness!, BigInt.fromI32(Constants.customLiveness), @@ -290,5 +315,7 @@ describe("Managed OOv2", () => { log.info("Created OptimisticPriceRequest entity: {}", [priceRequestEntity.id]); log.info("Custom Liveness: {}", [priceRequestEntity.customLiveness!.toString()]); log.info("State: {}", [priceRequestEntity.state!]); + log.info("Custom Bond: {}", [priceRequestEntity.bond!.toString()]); + log.info("Custom Bond Currency: {}", [priceRequestEntity.currency!.toHexString()]); }); }); From 975a0826f2786f5c358c321dbba15399b5edd3d7 Mon Sep 17 00:00:00 2001 From: Gerhard Steenkamp Date: Thu, 11 Sep 2025 10:35:13 +0200 Subject: [PATCH 3/3] update deployments in readme Signed-off-by: Gerhard Steenkamp --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0dd8d47..abce00c 100644 --- a/README.md +++ b/README.md @@ -146,10 +146,10 @@ This subgraph indexes events and function calls by the "Managed Optimistic Oracl - Amoy - TheGraph: - - Goldsky: + - Goldsky: - Polygon - TheGraph: - - Goldsky: + - Goldsky: ## Financial Contract Events