From 0299b286814634dc91e444cf2320873b8c429a7d Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 28 Jan 2026 10:52:04 +0000 Subject: [PATCH 1/3] feat: add resolver tracking for MOOV2 early resolver support - Update ABI with new contract interface - Add Resolver and ResolverHistory entities - Add handlers for RoleGranted/RoleRevoked events (RESOLVER_ROLE only) - Add tests for resolver permission tracking State handling unchanged - getState() already calls on-chain, so past-liveness requests will return Proposed instead of Expired after the contract upgrade. --- .../abis/ManagedOracleV2.json | 2858 +++++++++++++++-- .../templates/ManagedOracleV2.template.yaml | 6 + packages/managed-oracle-v2/schema.graphql | 32 + packages/managed-oracle-v2/src/index.ts | 2 +- .../src/mappings/managedOracleV2.ts | 64 +- .../managed-oracle-v2/managedOracle.test.ts | 139 +- .../tests/managed-oracle-v2/utils.ts | 55 + 7 files changed, 2856 insertions(+), 300 deletions(-) diff --git a/packages/managed-oracle-v2/abis/ManagedOracleV2.json b/packages/managed-oracle-v2/abis/ManagedOracleV2.json index 3b07fd9..0b31116 100644 --- a/packages/managed-oracle-v2/abis/ManagedOracleV2.json +++ b/packages/managed-oracle-v2/abis/ManagedOracleV2.json @@ -1,406 +1,2676 @@ [ { - "inputs": [ - { "internalType": "uint256", "name": "_liveness", "type": "uint256" }, - { "internalType": "address", "name": "_finderAddress", "type": "address" }, - { "internalType": "address", "name": "_timerAddress", "type": "address" } - ], - "stateMutability": "nonpayable", - "type": "constructor" + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "proposer", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "disputer", "type": "address" }, - { "indexed": false, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "indexed": false, "internalType": "int256", "name": "proposedPrice", "type": "int256" } + "type": "function", + "name": "CONFIG_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], - "name": "DisputePrice", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "proposer", "type": "address" }, - { "indexed": false, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "indexed": false, "internalType": "int256", "name": "proposedPrice", "type": "int256" }, - { "indexed": false, "internalType": "uint256", "name": "expirationTimestamp", "type": "uint256" }, - { "indexed": false, "internalType": "address", "name": "currency", "type": "address" } + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], - "name": "ProposePrice", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, - { "indexed": false, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "indexed": false, "internalType": "address", "name": "currency", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "reward", "type": "uint256" }, - { "indexed": false, "internalType": "uint256", "name": "finalFee", "type": "uint256" } + "type": "function", + "name": "LOWEST_MINIMUM_DISPUTE_WINDOW", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "RequestPrice", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "proposer", "type": "address" }, - { "indexed": true, "internalType": "address", "name": "disputer", "type": "address" }, - { "indexed": false, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "indexed": false, "internalType": "int256", "name": "price", "type": "int256" }, - { "indexed": false, "internalType": "uint256", "name": "payout", "type": "uint256" } + "type": "function", + "name": "OO_ANCILLARY_DATA_LIMIT", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "Settle", - "type": "event" + "stateMutability": "view" }, { + "type": "function", + "name": "REQUEST_MANAGER_ROLE", "inputs": [], - "name": "OO_ANCILLARY_DATA_LIMIT", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "RESOLVER_ADMIN_ROLE", "inputs": [], - "name": "TOO_EARLY_RESPONSE", - "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "RESOLVER_ROLE", "inputs": [], - "name": "ancillaryBytesLimit", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "TOO_EARLY_RESPONSE", "inputs": [], - "name": "defaultLiveness", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "int256", + "internalType": "int256" + } + ], + "stateMutability": "view" }, { - "inputs": [ - { "internalType": "address", "name": "requester", "type": "address" }, - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + "type": "function", + "name": "UPGRADE_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } ], - "name": "disputePrice", - "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "view" }, { - "inputs": [ - { "internalType": "address", "name": "disputer", "type": "address" }, - { "internalType": "address", "name": "requester", "type": "address" }, - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } ], - "name": "disputePriceFor", - "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "view" }, { + "type": "function", + "name": "acceptDefaultAdminTransfer", "inputs": [], - "name": "finder", - "outputs": [{ "internalType": "contract FinderInterface", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "outputs": [], + "stateMutability": "nonpayable" }, { - "inputs": [], - "name": "getCurrentTime", - "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], - "stateMutability": "view", - "type": "function" + "type": "function", + "name": "addRequestManager", + "inputs": [ + { + "name": "requestManager", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "addResolver", "inputs": [ - { "internalType": "address", "name": "requester", "type": "address" }, - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + { + "name": "resolver", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "allowedBondRanges", + "inputs": [ + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + } ], - "name": "getRequest", "outputs": [ { - "components": [ - { "internalType": "address", "name": "proposer", "type": "address" }, - { "internalType": "address", "name": "disputer", "type": "address" }, - { "internalType": "contract IERC20", "name": "currency", "type": "address" }, - { "internalType": "bool", "name": "settled", "type": "bool" }, - { - "components": [ - { "internalType": "bool", "name": "eventBased", "type": "bool" }, - { "internalType": "bool", "name": "refundOnDispute", "type": "bool" }, - { "internalType": "bool", "name": "callbackOnPriceProposed", "type": "bool" }, - { "internalType": "bool", "name": "callbackOnPriceDisputed", "type": "bool" }, - { "internalType": "bool", "name": "callbackOnPriceSettled", "type": "bool" }, - { "internalType": "uint256", "name": "bond", "type": "uint256" }, - { "internalType": "uint256", "name": "customLiveness", "type": "uint256" } - ], - "internalType": "struct OptimisticOracleV2Interface.RequestSettings", - "name": "requestSettings", - "type": "tuple" - }, - { "internalType": "int256", "name": "proposedPrice", "type": "int256" }, - { "internalType": "int256", "name": "resolvedPrice", "type": "int256" }, - { "internalType": "uint256", "name": "expirationTime", "type": "uint256" }, - { "internalType": "uint256", "name": "reward", "type": "uint256" }, - { "internalType": "uint256", "name": "finalFee", "type": "uint256" } - ], - "internalType": "struct OptimisticOracleV2Interface.Request", - "name": "", - "type": "tuple" + "name": "minimumBond", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "maximumBond", + "type": "uint128", + "internalType": "uint128" } ], - "stateMutability": "view", - "type": "function" + "stateMutability": "view" }, { - "inputs": [ - { "internalType": "address", "name": "requester", "type": "address" }, - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + "type": "function", + "name": "ancillaryBytesLimit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "getState", - "outputs": [{ "internalType": "enum OptimisticOracleV2Interface.State", "name": "", "type": "uint8" }], - "stateMutability": "view", - "type": "function" + "stateMutability": "view" }, { + "type": "function", + "name": "beginDefaultAdminTransfer", "inputs": [ - { "internalType": "address", "name": "requester", "type": "address" }, - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + { + "name": "newAdmin", + "type": "address", + "internalType": "address" + } ], - "name": "hasPrice", - "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], - "stateMutability": "view", - "type": "function" + "outputs": [], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "cancelDefaultAdminTransfer", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "changeDefaultAdminDelay", "inputs": [ - { "internalType": "address", "name": "requester", "type": "address" }, - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "internalType": "int256", "name": "proposedPrice", "type": "int256" } + { + "name": "newDelay", + "type": "uint48", + "internalType": "uint48" + } ], - "name": "proposePrice", - "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "claimDeferredPayout", "inputs": [ - { "internalType": "address", "name": "proposer", "type": "address" }, - { "internalType": "address", "name": "requester", "type": "address" }, - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "internalType": "int256", "name": "proposedPrice", "type": "int256" } + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "repaymentAddress", + "type": "address", + "internalType": "address" + } ], - "name": "proposePriceFor", - "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "customBonds", "inputs": [ - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "internalType": "contract IERC20", "name": "currency", "type": "address" }, - { "internalType": "uint256", "name": "reward", "type": "uint256" } + { + "name": "managedRequestId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + } ], - "name": "requestPrice", - "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [ + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "isSet", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" }, { - "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], - "name": "requests", + "type": "function", + "name": "customLivenessValues", + "inputs": [ + { + "name": "managedRequestId", + "type": "bytes32", + "internalType": "bytes32" + } + ], "outputs": [ - { "internalType": "address", "name": "proposer", "type": "address" }, - { "internalType": "address", "name": "disputer", "type": "address" }, - { "internalType": "contract IERC20", "name": "currency", "type": "address" }, - { "internalType": "bool", "name": "settled", "type": "bool" }, { - "components": [ - { "internalType": "bool", "name": "eventBased", "type": "bool" }, - { "internalType": "bool", "name": "refundOnDispute", "type": "bool" }, - { "internalType": "bool", "name": "callbackOnPriceProposed", "type": "bool" }, - { "internalType": "bool", "name": "callbackOnPriceDisputed", "type": "bool" }, - { "internalType": "bool", "name": "callbackOnPriceSettled", "type": "bool" }, - { "internalType": "uint256", "name": "bond", "type": "uint256" }, - { "internalType": "uint256", "name": "customLiveness", "type": "uint256" } - ], - "internalType": "struct OptimisticOracleV2Interface.RequestSettings", - "name": "requestSettings", - "type": "tuple" + "name": "liveness", + "type": "uint256", + "internalType": "uint256" }, - { "internalType": "int256", "name": "proposedPrice", "type": "int256" }, - { "internalType": "int256", "name": "resolvedPrice", "type": "int256" }, - { "internalType": "uint256", "name": "expirationTime", "type": "uint256" }, - { "internalType": "uint256", "name": "reward", "type": "uint256" }, - { "internalType": "uint256", "name": "finalFee", "type": "uint256" } + { + "name": "isSet", + "type": "bool", + "internalType": "bool" + } ], - "stateMutability": "view", - "type": "function" + "stateMutability": "view" }, { + "type": "function", + "name": "customProposerWhitelists", "inputs": [ - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "internalType": "uint256", "name": "bond", "type": "uint256" } + { + "name": "managedRequestId", + "type": "bytes32", + "internalType": "bytes32" + } ], - "name": "setBond", - "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract AddressWhitelistInterface" + } + ], + "stateMutability": "view" }, { - "inputs": [ - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "internalType": "bool", "name": "callbackOnPriceProposed", "type": "bool" }, - { "internalType": "bool", "name": "callbackOnPriceDisputed", "type": "bool" }, - { "internalType": "bool", "name": "callbackOnPriceSettled", "type": "bool" } + "type": "function", + "name": "defaultAdmin", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } ], - "name": "setCallbacks", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "view" }, { - "inputs": [{ "internalType": "uint256", "name": "time", "type": "uint256" }], - "name": "setCurrentTime", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "type": "function", + "name": "defaultAdminDelay", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" }, { - "inputs": [ - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "internalType": "uint256", "name": "customLiveness", "type": "uint256" } + "type": "function", + "name": "defaultAdminDelayIncreaseWait", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint48", + "internalType": "uint48" + } ], - "name": "setCustomLiveness", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "view" }, { - "inputs": [ - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + "type": "function", + "name": "defaultLiveness", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "setEventBased", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "view" }, { - "inputs": [ - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + "type": "function", + "name": "defaultProposerWhitelist", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract AddressWhitelistInterface" + } ], - "name": "setRefundOnDispute", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" + "stateMutability": "view" }, { + "type": "function", + "name": "deferredPayouts", "inputs": [ - { "internalType": "address", "name": "requester", "type": "address" }, - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "deferredRecipient", + "type": "address", + "internalType": "address" + } ], - "name": "settle", - "outputs": [{ "internalType": "uint256", "name": "payout", "type": "uint256" }], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" }, { + "type": "function", + "name": "disputePrice", "inputs": [ - { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } ], - "name": "settleAndGetPrice", - "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], - "stateMutability": "nonpayable", - "type": "function" + "outputs": [ + { + "name": "totalBond", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "disputePriceFor", "inputs": [ - { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "internalType": "address", "name": "requester", "type": "address" } + { + "name": "disputer", + "type": "address", + "internalType": "address" + }, + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } ], - "name": "stampAncillaryData", - "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], - "stateMutability": "pure", - "type": "function" + "outputs": [ + { + "name": "totalBond", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" }, { + "type": "function", + "name": "finder", "inputs": [], - "name": "timerAddress", - "outputs": [{ "internalType": "address", "name": "", "type": "address" }], - "stateMutability": "view", - "type": "function" + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract FinderInterface" + } + ], + "stateMutability": "view" }, { - "anonymous": false, - "inputs": [ - { "indexed": true, "internalType": "bytes32", "name": "managedRequestId", "type": "bytes32" }, - { "indexed": false, "internalType": "address", "name": "requester", "type": "address" }, - { "indexed": true, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "indexed": true, "internalType": "contract IERC20", "name": "currency", "type": "address" }, - { "indexed": false, "internalType": "uint256", "name": "bond", "type": "uint256" } + "type": "function", + "name": "getCurrentTime", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } ], - "name": "CustomBondSet", - "type": "event" + "stateMutability": "view" }, { - "anonymous": false, + "type": "function", + "name": "getCustomProposerWhitelist", "inputs": [ - { "indexed": true, "internalType": "bytes32", "name": "managedRequestId", "type": "bytes32" }, - { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, - { "indexed": true, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, - { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, - { "indexed": false, "internalType": "uint256", "name": "customLiveness", "type": "uint256" } + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } ], - "name": "CustomLivenessSet", - "type": "event" + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract AddressWhitelistInterface" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getManagedRequestId", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "getProposerWhitelistWithEnabledStatus", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "allowedProposers", + "type": "address[]", + "internalType": "address[]" + }, + { + "name": "isEnabled", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRequest", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "tuple", + "internalType": "struct OptimisticOracleV2Interface.Request", + "components": [ + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "disputer", + "type": "address", + "internalType": "address" + }, + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "settled", + "type": "bool", + "internalType": "bool" + }, + { + "name": "requestSettings", + "type": "tuple", + "internalType": "struct OptimisticOracleV2Interface.RequestSettings", + "components": [ + { + "name": "eventBased", + "type": "bool", + "internalType": "bool" + }, + { + "name": "refundOnDispute", + "type": "bool", + "internalType": "bool" + }, + { + "name": "callbackOnPriceProposed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "callbackOnPriceDisputed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "callbackOnPriceSettled", + "type": "bool", + "internalType": "bool" + }, + { + "name": "bond", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "customLiveness", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "name": "proposedPrice", + "type": "int256", + "internalType": "int256" + }, + { + "name": "resolvedPrice", + "type": "int256", + "internalType": "int256" + }, + { + "name": "expirationTime", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "reward", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "finalFee", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "proposalTime", + "type": "uint256", + "internalType": "uint256" + } + ] + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getRoleAdmin", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getState", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "uint8", + "internalType": "enum OptimisticOracleV2Interface.State" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "grantRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "hasPrice", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "hasRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_defaultLiveness", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_finderAddress", + "type": "address", + "internalType": "address" + }, + { + "name": "_defaultProposerWhitelist", + "type": "address", + "internalType": "address" + }, + { + "name": "_requesterWhitelist", + "type": "address", + "internalType": "address" + }, + { + "name": "_allowedBondRanges", + "type": "tuple[]", + "internalType": "struct ManagedOptimisticOracleV2.CurrencyBondRange[]", + "components": [ + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "range", + "type": "tuple", + "internalType": "struct ManagedOptimisticOracleV2.BondRange", + "components": [ + { + "name": "minimumBond", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "maximumBond", + "type": "uint128", + "internalType": "uint128" + } + ] + } + ] + }, + { + "name": "configAdmin", + "type": "address", + "internalType": "address" + }, + { + "name": "upgradeAdmin", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "initialize", + "inputs": [ + { + "name": "_liveness", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "_finderAddress", + "type": "address", + "internalType": "address" + }, + { + "name": "upgradeAdmin", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "initializeV2", + "inputs": [ + { + "name": "_minimumDisputeWindow", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "resolverAdmin", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "minimumDisputeWindow", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "multicall", + "inputs": [ + { + "name": "data", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "outputs": [ + { + "name": "results", + "type": "bytes[]", + "internalType": "bytes[]" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "owner", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pendingDefaultAdmin", + "inputs": [], + "outputs": [ + { + "name": "newAdmin", + "type": "address", + "internalType": "address" + }, + { + "name": "schedule", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "pendingDefaultAdminDelay", + "inputs": [], + "outputs": [ + { + "name": "newDelay", + "type": "uint48", + "internalType": "uint48" + }, + { + "name": "schedule", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "proposePrice", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "proposedPrice", + "type": "int256", + "internalType": "int256" + } + ], + "outputs": [ + { + "name": "totalBond", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proposePriceFor", + "inputs": [ + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "proposedPrice", + "type": "int256", + "internalType": "int256" + } + ], + "outputs": [ + { + "name": "totalBond", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "proxiableUUID", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "removeRequestManager", + "inputs": [ + { + "name": "requestManager", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "removeResolver", + "inputs": [ + { + "name": "resolver", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "renounceRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "requestManagerSetBond", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "bond", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "requestManagerSetCustomLiveness", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "customLiveness", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "requestManagerSetProposerWhitelist", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "whitelist", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "requestPrice", + "inputs": [ + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "reward", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "totalBond", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "requesterWhitelist", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract AddressWhitelistInterface" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "requests", + "inputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "proposer", + "type": "address", + "internalType": "address" + }, + { + "name": "disputer", + "type": "address", + "internalType": "address" + }, + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "settled", + "type": "bool", + "internalType": "bool" + }, + { + "name": "requestSettings", + "type": "tuple", + "internalType": "struct OptimisticOracleV2Interface.RequestSettings", + "components": [ + { + "name": "eventBased", + "type": "bool", + "internalType": "bool" + }, + { + "name": "refundOnDispute", + "type": "bool", + "internalType": "bool" + }, + { + "name": "callbackOnPriceProposed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "callbackOnPriceDisputed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "callbackOnPriceSettled", + "type": "bool", + "internalType": "bool" + }, + { + "name": "bond", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "customLiveness", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "name": "proposedPrice", + "type": "int256", + "internalType": "int256" + }, + { + "name": "resolvedPrice", + "type": "int256", + "internalType": "int256" + }, + { + "name": "expirationTime", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "reward", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "finalFee", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "proposalTime", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "revokeRole", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "rollbackDefaultAdminDelay", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setAllowedBondRange", + "inputs": [ + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "newRange", + "type": "tuple", + "internalType": "struct ManagedOptimisticOracleV2.BondRange", + "components": [ + { + "name": "minimumBond", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "maximumBond", + "type": "uint128", + "internalType": "uint128" + } + ] + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setBond", + "inputs": [ + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "bond", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [ + { + "name": "totalBond", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setCallbacks", + "inputs": [ + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "callbackOnPriceProposed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "callbackOnPriceDisputed", + "type": "bool", + "internalType": "bool" + }, + { + "name": "callbackOnPriceSettled", + "type": "bool", + "internalType": "bool" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setCustomLiveness", + "inputs": [ + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "customLiveness", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setDefaultProposerWhitelist", + "inputs": [ + { + "name": "whitelist", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setEventBased", + "inputs": [ + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setMinimumDisputeWindow", + "inputs": [ + { + "name": "_minimumDisputeWindow", + "type": "uint256", + "internalType": "uint256" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setRefundOnDispute", + "inputs": [ + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "setRequesterWhitelist", + "inputs": [ + { + "name": "whitelist", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "settle", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "payout", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "settleAndGetPrice", + "inputs": [ + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [ + { + "name": "", + "type": "int256", + "internalType": "int256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "stampAncillaryData", + "inputs": [ + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "requester", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + } + ], + "stateMutability": "pure" + }, + { + "type": "function", + "name": "supportsInterface", + "inputs": [ + { + "name": "interfaceId", + "type": "bytes4", + "internalType": "bytes4" + } + ], + "outputs": [ + { + "name": "", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "upgradeToAndCall", + "inputs": [ + { + "name": "newImplementation", + "type": "address", + "internalType": "address" + }, + { + "name": "data", + "type": "bytes", + "internalType": "bytes" + } + ], + "outputs": [], + "stateMutability": "payable" + }, + { + "type": "event", + "name": "AllowedBondRangeUpdated", + "inputs": [ + { + "name": "currency", + "type": "address", + "indexed": true, + "internalType": "contract IERC20" + }, + { + "name": "newMinimumBond", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "newMaximumBond", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ClaimedDeferredPayout", + "inputs": [ + { + "name": "currency", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "deferredRecipient", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "repaymentAddress", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CustomBondSet", + "inputs": [ + { + "name": "managedRequestId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "requester", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "currency", + "type": "address", + "indexed": true, + "internalType": "contract IERC20" + }, + { + "name": "bond", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CustomLivenessSet", + "inputs": [ + { + "name": "managedRequestId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "requester", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "customLiveness", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "CustomProposerWhitelistSet", + "inputs": [ + { + "name": "managedRequestId", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "requester", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "newWhitelist", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DefaultAdminDelayChangeCanceled", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "DefaultAdminDelayChangeScheduled", + "inputs": [ + { + "name": "newDelay", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + }, + { + "name": "effectSchedule", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DefaultAdminTransferCanceled", + "inputs": [], + "anonymous": false + }, + { + "type": "event", + "name": "DefaultAdminTransferScheduled", + "inputs": [ + { + "name": "newAdmin", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "acceptSchedule", + "type": "uint48", + "indexed": false, + "internalType": "uint48" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DefaultProposerWhitelistUpdated", + "inputs": [ + { + "name": "newWhitelist", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "DisputePrice", + "inputs": [ + { + "name": "requester", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "proposer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "disputer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "proposedPrice", + "type": "int256", + "indexed": false, + "internalType": "int256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Initialized", + "inputs": [ + { + "name": "version", + "type": "uint64", + "indexed": false, + "internalType": "uint64" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "MinimumDisputeWindowUpdated", + "inputs": [ + { + "name": "newMinimumDisputeWindow", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "PayoutDeferred", + "inputs": [ + { + "name": "currency", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "deferredRecipient", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "amount", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "ProposePrice", + "inputs": [ + { + "name": "requester", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "proposer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "proposedPrice", + "type": "int256", + "indexed": false, + "internalType": "int256" + }, + { + "name": "expirationTimestamp", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "currency", + "type": "address", + "indexed": false, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RequestPrice", + "inputs": [ + { + "name": "requester", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "currency", + "type": "address", + "indexed": false, + "internalType": "address" + }, + { + "name": "reward", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "finalFee", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RequesterWhitelistUpdated", + "inputs": [ + { + "name": "newWhitelist", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleAdminChanged", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "previousAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "newAdminRole", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleGranted", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "RoleRevoked", + "inputs": [ + { + "name": "role", + "type": "bytes32", + "indexed": true, + "internalType": "bytes32" + }, + { + "name": "account", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "sender", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Settle", + "inputs": [ + { + "name": "requester", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "proposer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "disputer", + "type": "address", + "indexed": true, + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "indexed": false, + "internalType": "bytes32" + }, + { + "name": "timestamp", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + }, + { + "name": "ancillaryData", + "type": "bytes", + "indexed": false, + "internalType": "bytes" + }, + { + "name": "price", + "type": "int256", + "indexed": false, + "internalType": "int256" + }, + { + "name": "payout", + "type": "uint256", + "indexed": false, + "internalType": "uint256" + } + ], + "anonymous": false + }, + { + "type": "event", + "name": "Upgraded", + "inputs": [ + { + "name": "implementation", + "type": "address", + "indexed": true, + "internalType": "address" + } + ], + "anonymous": false + }, + { + "type": "error", + "name": "AccessControlBadConfirmation", + "inputs": [] + }, + { + "type": "error", + "name": "AccessControlEnforcedDefaultAdminDelay", + "inputs": [ + { + "name": "schedule", + "type": "uint48", + "internalType": "uint48" + } + ] + }, + { + "type": "error", + "name": "AccessControlEnforcedDefaultAdminRules", + "inputs": [] + }, + { + "type": "error", + "name": "AccessControlInvalidDefaultAdmin", + "inputs": [ + { + "name": "defaultAdmin", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "AccessControlUnauthorizedAccount", + "inputs": [ + { + "name": "account", + "type": "address", + "internalType": "address" + }, + { + "name": "neededRole", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "AddressEmptyCode", + "inputs": [ + { + "name": "target", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "AncillaryDataTooLong", + "inputs": [] + }, + { + "type": "error", + "name": "BondBelowMinimumBond", + "inputs": [] + }, + { + "type": "error", + "name": "BondExceedsMaximumBond", + "inputs": [] + }, + { + "type": "error", + "name": "CannotProposeTooEarly", + "inputs": [] + }, + { + "type": "error", + "name": "DisputerAddressCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "ERC1967InvalidImplementation", + "inputs": [ + { + "name": "implementation", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "ERC1967NonPayable", + "inputs": [] + }, + { + "type": "error", + "name": "FailedCall", + "inputs": [] + }, + { + "type": "error", + "name": "InvalidInitialization", + "inputs": [] + }, + { + "type": "error", + "name": "LivenessCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "LivenessTooLarge", + "inputs": [] + }, + { + "type": "error", + "name": "LivenessTooLow", + "inputs": [] + }, + { + "type": "error", + "name": "MinimumBondAboveMaximumBond", + "inputs": [] + }, + { + "type": "error", + "name": "MinimumDisputeWindowTooLarge", + "inputs": [] + }, + { + "type": "error", + "name": "MinimumDisputeWindowTooSmall", + "inputs": [] + }, + { + "type": "error", + "name": "NoDeferredPayoutToClaim", + "inputs": [] + }, + { + "type": "error", + "name": "NotInitializing", + "inputs": [] + }, + { + "type": "error", + "name": "ProposerAddressCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "ProposerNotWhitelisted", + "inputs": [] + }, + { + "type": "error", + "name": "ReentrancyGuardReentrantCall", + "inputs": [] + }, + { + "type": "error", + "name": "RepaymentAddressCannotBeZero", + "inputs": [] + }, + { + "type": "error", + "name": "RequestNotSettleable", + "inputs": [] + }, + { + "type": "error", + "name": "RequestNotSettled", + "inputs": [] + }, + { + "type": "error", + "name": "RequestStateNotInvalid", + "inputs": [] + }, + { + "type": "error", + "name": "RequestStateNotProposed", + "inputs": [] + }, + { + "type": "error", + "name": "RequestStateNotRequested", + "inputs": [] + }, + { + "type": "error", + "name": "RequesterNotWhitelisted", + "inputs": [] + }, + { + "type": "error", + "name": "SafeCastOverflowedUintDowncast", + "inputs": [ + { + "name": "bits", + "type": "uint8", + "internalType": "uint8" + }, + { + "name": "value", + "type": "uint256", + "internalType": "uint256" + } + ] + }, + { + "type": "error", + "name": "SafeERC20FailedOperation", + "inputs": [ + { + "name": "token", + "type": "address", + "internalType": "address" + } + ] + }, + { + "type": "error", + "name": "SenderNotWhitelisted", + "inputs": [] + }, + { + "type": "error", + "name": "TimestampInFuture", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnauthorizedCallContext", + "inputs": [] + }, + { + "type": "error", + "name": "UUPSUnsupportedProxiableUUID", + "inputs": [ + { + "name": "slot", + "type": "bytes32", + "internalType": "bytes32" + } + ] + }, + { + "type": "error", + "name": "UnsupportedCurrency", + "inputs": [] + }, + { + "type": "error", + "name": "UnsupportedIdentifier", + "inputs": [] + }, + { + "type": "error", + "name": "UnsupportedWhitelistInterface", + "inputs": [] + }, + { + "type": "error", + "name": "ZeroBondNotAllowed", + "inputs": [] } ] diff --git a/packages/managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml b/packages/managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml index 3939e25..9ad4618 100644 --- a/packages/managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml +++ b/packages/managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml @@ -15,6 +15,8 @@ - PriceIdentifier - CustomLiveness - CustomBond + - Resolver + - ResolverHistory abis: - name: ManagedOracleV2 file: ./abis/ManagedOracleV2.json @@ -31,6 +33,10 @@ handler: handleCustomLivenessSet - event: CustomBondSet(indexed bytes32,address,indexed bytes32,bytes,indexed address,uint256) handler: handleCustomBondSet + - event: RoleGranted(indexed bytes32,indexed address,indexed address) + handler: handleRoleGranted + - event: RoleRevoked(indexed bytes32,indexed address,indexed address) + handler: handleRoleRevoked callHandlers: - function: setCustomLiveness(bytes32,uint256,bytes,uint256) handler: handleSetCustomLiveness diff --git a/packages/managed-oracle-v2/schema.graphql b/packages/managed-oracle-v2/schema.graphql index 536150e..1bf74c8 100644 --- a/packages/managed-oracle-v2/schema.graphql +++ b/packages/managed-oracle-v2/schema.graphql @@ -110,3 +110,35 @@ type CustomLiveness @entity { customLiveness: BigInt! } + +type Resolver @entity { + "ID is the resolver address" + id: ID! + + address: Bytes! + + isActive: Boolean! + + addedAt: BigInt + + addedTx: Bytes + + removedAt: BigInt + + removedTx: Bytes +} + +type ResolverHistory @entity { + "ID is tx hash + log index" + id: ID! + + resolver: Bytes! + + action: String! + + timestamp: BigInt! + + blockNumber: BigInt! + + transactionHash: Bytes! +} diff --git a/packages/managed-oracle-v2/src/index.ts b/packages/managed-oracle-v2/src/index.ts index 8f3ff26..5514799 100644 --- a/packages/managed-oracle-v2/src/index.ts +++ b/packages/managed-oracle-v2/src/index.ts @@ -7,4 +7,4 @@ export { handleSetCustomLiveness, handleSetEventBased, } from "./mappings/optimisticOracleV2"; -export { handleCustomBondSet, handleCustomLivenessSet } from "./mappings/managedOracleV2"; +export { handleCustomBondSet, handleCustomLivenessSet, handleRoleGranted, handleRoleRevoked } from "./mappings/managedOracleV2"; diff --git a/packages/managed-oracle-v2/src/mappings/managedOracleV2.ts b/packages/managed-oracle-v2/src/mappings/managedOracleV2.ts index 9ee7c5c..a5fdd6f 100644 --- a/packages/managed-oracle-v2/src/mappings/managedOracleV2.ts +++ b/packages/managed-oracle-v2/src/mappings/managedOracleV2.ts @@ -1,8 +1,11 @@ -import { log } from "@graphprotocol/graph-ts"; -import { CustomBond, CustomLiveness } from "../../generated/schema"; -import { CustomBondSet, CustomLivenessSet } from "../../generated/ManagedOracleV2/ManagedOracleV2"; +import { log, Bytes, crypto } from "@graphprotocol/graph-ts"; +import { CustomBond, CustomLiveness, Resolver, ResolverHistory } from "../../generated/schema"; +import { CustomBondSet, CustomLivenessSet, RoleGranted, RoleRevoked } from "../../generated/ManagedOracleV2/ManagedOracleV2"; import { createCustomBondIdFromEvent } from "../utils/helpers/managedOracleV2"; +// RESOLVER_ROLE = keccak256("RESOLVER_ROLE") +const RESOLVER_ROLE = Bytes.fromHexString("0x92a19c77d2ea87c7f81d50c74403cb2f401780f3ad919571121efe2bdb427eb1"); + /** * Handles CustomBondSet events from the ManagedOracleV2 contract. * @@ -48,3 +51,58 @@ export function handleCustomLivenessSet(event: CustomLivenessSet): void { entity.customLiveness = event.params.customLiveness; entity.save(); } + +export function handleRoleGranted(event: RoleGranted): void { + if (event.params.role != RESOLVER_ROLE) return; + + const id = event.params.account.toHexString(); + let resolver = Resolver.load(id); + + if (resolver == null) { + resolver = new Resolver(id); + resolver.address = event.params.account; + } + + resolver.isActive = true; + resolver.addedAt = event.block.timestamp; + resolver.addedTx = event.transaction.hash; + resolver.save(); + + // Create history entry + const historyId = event.transaction.hash.toHexString() + "-" + event.logIndex.toString(); + let history = new ResolverHistory(historyId); + history.resolver = event.params.account; + history.action = "added"; + history.timestamp = event.block.timestamp; + history.blockNumber = event.block.number; + history.transactionHash = event.transaction.hash; + history.save(); + + log.info("Resolver added: {}", [id]); +} + +export function handleRoleRevoked(event: RoleRevoked): void { + if (event.params.role != RESOLVER_ROLE) return; + + const id = event.params.account.toHexString(); + let resolver = Resolver.load(id); + + if (resolver != null) { + resolver.isActive = false; + resolver.removedAt = event.block.timestamp; + resolver.removedTx = event.transaction.hash; + resolver.save(); + } + + // Create history entry + const historyId = event.transaction.hash.toHexString() + "-" + event.logIndex.toString(); + let history = new ResolverHistory(historyId); + history.resolver = event.params.account; + history.action = "removed"; + history.timestamp = event.block.timestamp; + history.blockNumber = event.block.number; + history.transactionHash = event.transaction.hash; + history.save(); + + log.info("Resolver removed: {}", [id]); +} 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 f40c61f..c857848 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,5 +1,5 @@ import { describe, test, clearStore, assert, log, afterEach } from "matchstick-as/assembly/index"; -import { handleCustomLivenessSet, handleCustomBondSet } from "../../src/mappings/managedOracleV2"; +import { handleCustomLivenessSet, handleCustomBondSet, handleRoleGranted, handleRoleRevoked } from "../../src/mappings/managedOracleV2"; import { handleOptimisticProposePrice, handleOptimisticRequestPrice } from "../../src/mappings/optimisticOracleV2"; import { createCustomBondIdFromEvent } from "../../src/utils/helpers/managedOracleV2"; import { createOptimisticPriceRequestId } from "../../src/utils/helpers/optimisticOracle"; @@ -8,10 +8,13 @@ import { createCustomBondSetEvent, createProposePriceEvent, createRequestPriceEvent, + createRoleGrantedEvent, + createRoleRevokedEvent, mockGetState, State, + RESOLVER_ROLE, } from "./utils"; -import { CustomLiveness, CustomBond, OptimisticPriceRequest } from "../../generated/schema"; +import { CustomLiveness, CustomBond, OptimisticPriceRequest, Resolver, ResolverHistory } from "../../generated/schema"; import { BigInt, Bytes, Address } from "@graphprotocol/graph-ts"; // Tests structure (matchstick-as >=0.5.0) @@ -462,3 +465,135 @@ describe("Managed OOv2", () => { log.info("State: {}", [priceRequestEntity.state!]); }); }); + +describe("Resolver Tracking", () => { + afterEach(() => { + clearStore(); + }); + + test("handleRoleGranted creates Resolver entity when RESOLVER_ROLE is granted", () => { + const resolverAddress = "0x1234567890123456789012345678901234567890"; + const sender = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + + const roleGrantedEvent = createRoleGrantedEvent(RESOLVER_ROLE, resolverAddress, sender); + handleRoleGranted(roleGrantedEvent); + + const resolver = Resolver.load(resolverAddress.toLowerCase()); + + assert.assertTrue(resolver !== null, "Resolver entity should be created"); + + if (resolver === null) { + return; + } + + assert.assertTrue(resolver.isActive, "Resolver should be active"); + assert.bytesEquals( + resolver.address, + Address.fromString(resolverAddress), + "Resolver address should match" + ); + assert.assertTrue(resolver.addedAt !== null, "addedAt should be set"); + assert.assertTrue(resolver.addedTx !== null, "addedTx should be set"); + + log.info("Created Resolver entity: {}", [resolver.id]); + log.info("isActive: {}", [resolver.isActive.toString()]); + }); + + test("handleRoleGranted creates ResolverHistory entry", () => { + const resolverAddress = "0x1234567890123456789012345678901234567890"; + const sender = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + + const roleGrantedEvent = createRoleGrantedEvent(RESOLVER_ROLE, resolverAddress, sender); + handleRoleGranted(roleGrantedEvent); + + const historyId = roleGrantedEvent.transaction.hash.toHexString() + "-" + roleGrantedEvent.logIndex.toString(); + const history = ResolverHistory.load(historyId); + + assert.assertTrue(history !== null, "ResolverHistory entity should be created"); + + if (history === null) { + return; + } + + assert.stringEquals(history.action, "added", "Action should be 'added'"); + assert.bytesEquals( + history.resolver, + Address.fromString(resolverAddress), + "Resolver address should match" + ); + + log.info("Created ResolverHistory entity: {}", [history.id]); + log.info("action: {}", [history.action]); + }); + + test("handleRoleRevoked updates Resolver entity to inactive", () => { + const resolverAddress = "0x1234567890123456789012345678901234567890"; + const sender = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + + // First grant the role + const roleGrantedEvent = createRoleGrantedEvent(RESOLVER_ROLE, resolverAddress, sender); + handleRoleGranted(roleGrantedEvent); + + // Then revoke the role + const roleRevokedEvent = createRoleRevokedEvent(RESOLVER_ROLE, resolverAddress, sender); + handleRoleRevoked(roleRevokedEvent); + + const resolver = Resolver.load(resolverAddress.toLowerCase()); + + assert.assertTrue(resolver !== null, "Resolver entity should exist"); + + if (resolver === null) { + return; + } + + assert.assertTrue(!resolver.isActive, "Resolver should be inactive after revocation"); + assert.assertTrue(resolver.removedAt !== null, "removedAt should be set"); + assert.assertTrue(resolver.removedTx !== null, "removedTx should be set"); + + log.info("Updated Resolver entity: {}", [resolver.id]); + log.info("isActive: {}", [resolver.isActive.toString()]); + }); + + test("handleRoleGranted ignores non-RESOLVER_ROLE events", () => { + const resolverAddress = "0x1234567890123456789012345678901234567890"; + const sender = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + // Some other role hash (not RESOLVER_ROLE) + const otherRole = Bytes.fromHexString("0x0000000000000000000000000000000000000000000000000000000000000001"); + + const roleGrantedEvent = createRoleGrantedEvent(otherRole, resolverAddress, sender); + handleRoleGranted(roleGrantedEvent); + + const resolver = Resolver.load(resolverAddress.toLowerCase()); + + assert.assertTrue(resolver === null, "Resolver entity should NOT be created for non-RESOLVER_ROLE"); + + log.info("Correctly ignored non-RESOLVER_ROLE event", []); + }); + + test("handleRoleRevoked creates ResolverHistory entry", () => { + const resolverAddress = "0x1234567890123456789012345678901234567890"; + const sender = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + + // First grant the role + const roleGrantedEvent = createRoleGrantedEvent(RESOLVER_ROLE, resolverAddress, sender); + handleRoleGranted(roleGrantedEvent); + + // Then revoke the role + const roleRevokedEvent = createRoleRevokedEvent(RESOLVER_ROLE, resolverAddress, sender); + handleRoleRevoked(roleRevokedEvent); + + const historyId = roleRevokedEvent.transaction.hash.toHexString() + "-" + roleRevokedEvent.logIndex.toString(); + const history = ResolverHistory.load(historyId); + + assert.assertTrue(history !== null, "ResolverHistory entity should be created for revocation"); + + if (history === null) { + return; + } + + assert.stringEquals(history.action, "removed", "Action should be 'removed'"); + + log.info("Created ResolverHistory entity for revocation: {}", [history.id]); + log.info("action: {}", [history.action]); + }); +}); 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 a409904..91385f7 100644 --- a/packages/managed-oracle-v2/tests/managed-oracle-v2/utils.ts +++ b/packages/managed-oracle-v2/tests/managed-oracle-v2/utils.ts @@ -5,8 +5,13 @@ import { CustomLivenessSet, ProposePrice, RequestPrice, + RoleGranted, + RoleRevoked, } from "../../generated/ManagedOracleV2/ManagedOracleV2"; +// RESOLVER_ROLE = keccak256("RESOLVER_ROLE") +export const RESOLVER_ROLE = Bytes.fromHexString("0x92a19c77d2ea87c7f81d50c74403cb2f401780f3ad919571121efe2bdb427eb1"); + export const contractAddress = Address.fromString("0x89205A3A3b2A69De6Dbf7f01ED13B2108B2c43e7"); // Default test contract address export function createCustomLivenessSetEvent( @@ -207,3 +212,53 @@ export function mockGetState( ]) .returns([ethereum.Value.fromI32(expectedState)]); } + +export function createRoleGrantedEvent( + role: Bytes, + account: string, + sender: string +): RoleGranted { + let roleGrantedEvent = changetype(newMockEvent()); + roleGrantedEvent.address = contractAddress; + roleGrantedEvent.parameters = new Array(); + + // role + roleGrantedEvent.parameters.push( + new ethereum.EventParam("role", ethereum.Value.fromFixedBytes(role)) + ); + // account + roleGrantedEvent.parameters.push( + new ethereum.EventParam("account", ethereum.Value.fromAddress(Address.fromString(account))) + ); + // sender + roleGrantedEvent.parameters.push( + new ethereum.EventParam("sender", ethereum.Value.fromAddress(Address.fromString(sender))) + ); + + return roleGrantedEvent; +} + +export function createRoleRevokedEvent( + role: Bytes, + account: string, + sender: string +): RoleRevoked { + let roleRevokedEvent = changetype(newMockEvent()); + roleRevokedEvent.address = contractAddress; + roleRevokedEvent.parameters = new Array(); + + // role + roleRevokedEvent.parameters.push( + new ethereum.EventParam("role", ethereum.Value.fromFixedBytes(role)) + ); + // account + roleRevokedEvent.parameters.push( + new ethereum.EventParam("account", ethereum.Value.fromAddress(Address.fromString(account))) + ); + // sender + roleRevokedEvent.parameters.push( + new ethereum.EventParam("sender", ethereum.Value.fromAddress(Address.fromString(sender))) + ); + + return roleRevokedEvent; +} From d3fbf91a75b3ff5459b3e517ce24222b650041fc Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Thu, 12 Feb 2026 11:43:25 +0000 Subject: [PATCH 2/3] feat: add dual-ABI getRequest fallback for L2 chains The new MOOV2 ABI (from the resolver upgrade) adds a `proposalTime` field to the `Request` struct returned by `getRequest`. Contracts not yet upgraded still use the legacy struct without that field. On L2 chains (where callHandlers are unsupported), the subgraph calls `getRequest` directly to read bond, eventBased, and customLiveness. This now tries the new ABI first and falls back to the legacy ABI if it reverts, preventing indexing failures on mixed-version deployments. Also moves network detection inside the handler to avoid module-level side effects, and adds tests covering both ABI paths and the fallback when both revert. --- .../abis/ManagedOracleV2Legacy.json | 406 ++++++++++++++++++ .../templates/ManagedOracleV2.template.yaml | 2 + .../src/mappings/optimisticOracleV2.ts | 43 +- .../managed-oracle-v2/managedOracle.test.ts | 206 ++++++++- .../tests/managed-oracle-v2/utils.ts | 124 ++++++ 5 files changed, 770 insertions(+), 11 deletions(-) create mode 100644 packages/managed-oracle-v2/abis/ManagedOracleV2Legacy.json diff --git a/packages/managed-oracle-v2/abis/ManagedOracleV2Legacy.json b/packages/managed-oracle-v2/abis/ManagedOracleV2Legacy.json new file mode 100644 index 0000000..3b07fd9 --- /dev/null +++ b/packages/managed-oracle-v2/abis/ManagedOracleV2Legacy.json @@ -0,0 +1,406 @@ +[ + { + "inputs": [ + { "internalType": "uint256", "name": "_liveness", "type": "uint256" }, + { "internalType": "address", "name": "_finderAddress", "type": "address" }, + { "internalType": "address", "name": "_timerAddress", "type": "address" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "proposer", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "disputer", "type": "address" }, + { "indexed": false, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "indexed": false, "internalType": "int256", "name": "proposedPrice", "type": "int256" } + ], + "name": "DisputePrice", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "proposer", "type": "address" }, + { "indexed": false, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "indexed": false, "internalType": "int256", "name": "proposedPrice", "type": "int256" }, + { "indexed": false, "internalType": "uint256", "name": "expirationTimestamp", "type": "uint256" }, + { "indexed": false, "internalType": "address", "name": "currency", "type": "address" } + ], + "name": "ProposePrice", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, + { "indexed": false, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "indexed": false, "internalType": "address", "name": "currency", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "reward", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "finalFee", "type": "uint256" } + ], + "name": "RequestPrice", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "proposer", "type": "address" }, + { "indexed": true, "internalType": "address", "name": "disputer", "type": "address" }, + { "indexed": false, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "indexed": false, "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "indexed": false, "internalType": "int256", "name": "price", "type": "int256" }, + { "indexed": false, "internalType": "uint256", "name": "payout", "type": "uint256" } + ], + "name": "Settle", + "type": "event" + }, + { + "inputs": [], + "name": "OO_ANCILLARY_DATA_LIMIT", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "TOO_EARLY_RESPONSE", + "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "ancillaryBytesLimit", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "defaultLiveness", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "disputePrice", + "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "disputer", "type": "address" }, + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "disputePriceFor", + "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "finder", + "outputs": [{ "internalType": "contract FinderInterface", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getCurrentTime", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "getRequest", + "outputs": [ + { + "components": [ + { "internalType": "address", "name": "proposer", "type": "address" }, + { "internalType": "address", "name": "disputer", "type": "address" }, + { "internalType": "contract IERC20", "name": "currency", "type": "address" }, + { "internalType": "bool", "name": "settled", "type": "bool" }, + { + "components": [ + { "internalType": "bool", "name": "eventBased", "type": "bool" }, + { "internalType": "bool", "name": "refundOnDispute", "type": "bool" }, + { "internalType": "bool", "name": "callbackOnPriceProposed", "type": "bool" }, + { "internalType": "bool", "name": "callbackOnPriceDisputed", "type": "bool" }, + { "internalType": "bool", "name": "callbackOnPriceSettled", "type": "bool" }, + { "internalType": "uint256", "name": "bond", "type": "uint256" }, + { "internalType": "uint256", "name": "customLiveness", "type": "uint256" } + ], + "internalType": "struct OptimisticOracleV2Interface.RequestSettings", + "name": "requestSettings", + "type": "tuple" + }, + { "internalType": "int256", "name": "proposedPrice", "type": "int256" }, + { "internalType": "int256", "name": "resolvedPrice", "type": "int256" }, + { "internalType": "uint256", "name": "expirationTime", "type": "uint256" }, + { "internalType": "uint256", "name": "reward", "type": "uint256" }, + { "internalType": "uint256", "name": "finalFee", "type": "uint256" } + ], + "internalType": "struct OptimisticOracleV2Interface.Request", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "getState", + "outputs": [{ "internalType": "enum OptimisticOracleV2Interface.State", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "hasPrice", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "internalType": "int256", "name": "proposedPrice", "type": "int256" } + ], + "name": "proposePrice", + "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "proposer", "type": "address" }, + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "internalType": "int256", "name": "proposedPrice", "type": "int256" } + ], + "name": "proposePriceFor", + "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "internalType": "contract IERC20", "name": "currency", "type": "address" }, + { "internalType": "uint256", "name": "reward", "type": "uint256" } + ], + "name": "requestPrice", + "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "requests", + "outputs": [ + { "internalType": "address", "name": "proposer", "type": "address" }, + { "internalType": "address", "name": "disputer", "type": "address" }, + { "internalType": "contract IERC20", "name": "currency", "type": "address" }, + { "internalType": "bool", "name": "settled", "type": "bool" }, + { + "components": [ + { "internalType": "bool", "name": "eventBased", "type": "bool" }, + { "internalType": "bool", "name": "refundOnDispute", "type": "bool" }, + { "internalType": "bool", "name": "callbackOnPriceProposed", "type": "bool" }, + { "internalType": "bool", "name": "callbackOnPriceDisputed", "type": "bool" }, + { "internalType": "bool", "name": "callbackOnPriceSettled", "type": "bool" }, + { "internalType": "uint256", "name": "bond", "type": "uint256" }, + { "internalType": "uint256", "name": "customLiveness", "type": "uint256" } + ], + "internalType": "struct OptimisticOracleV2Interface.RequestSettings", + "name": "requestSettings", + "type": "tuple" + }, + { "internalType": "int256", "name": "proposedPrice", "type": "int256" }, + { "internalType": "int256", "name": "resolvedPrice", "type": "int256" }, + { "internalType": "uint256", "name": "expirationTime", "type": "uint256" }, + { "internalType": "uint256", "name": "reward", "type": "uint256" }, + { "internalType": "uint256", "name": "finalFee", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "internalType": "uint256", "name": "bond", "type": "uint256" } + ], + "name": "setBond", + "outputs": [{ "internalType": "uint256", "name": "totalBond", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "internalType": "bool", "name": "callbackOnPriceProposed", "type": "bool" }, + { "internalType": "bool", "name": "callbackOnPriceDisputed", "type": "bool" }, + { "internalType": "bool", "name": "callbackOnPriceSettled", "type": "bool" } + ], + "name": "setCallbacks", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "time", "type": "uint256" }], + "name": "setCurrentTime", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "internalType": "uint256", "name": "customLiveness", "type": "uint256" } + ], + "name": "setCustomLiveness", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "setEventBased", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "setRefundOnDispute", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "requester", "type": "address" }, + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "settle", + "outputs": [{ "internalType": "uint256", "name": "payout", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "internalType": "uint256", "name": "timestamp", "type": "uint256" }, + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" } + ], + "name": "settleAndGetPrice", + "outputs": [{ "internalType": "int256", "name": "", "type": "int256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "internalType": "address", "name": "requester", "type": "address" } + ], + "name": "stampAncillaryData", + "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "timerAddress", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "bytes32", "name": "managedRequestId", "type": "bytes32" }, + { "indexed": false, "internalType": "address", "name": "requester", "type": "address" }, + { "indexed": true, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "indexed": true, "internalType": "contract IERC20", "name": "currency", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "bond", "type": "uint256" } + ], + "name": "CustomBondSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "bytes32", "name": "managedRequestId", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "requester", "type": "address" }, + { "indexed": true, "internalType": "bytes32", "name": "identifier", "type": "bytes32" }, + { "indexed": false, "internalType": "bytes", "name": "ancillaryData", "type": "bytes" }, + { "indexed": false, "internalType": "uint256", "name": "customLiveness", "type": "uint256" } + ], + "name": "CustomLivenessSet", + "type": "event" + } +] diff --git a/packages/managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml b/packages/managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml index 9ad4618..0803ef4 100644 --- a/packages/managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml +++ b/packages/managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml @@ -20,6 +20,8 @@ abis: - name: ManagedOracleV2 file: ./abis/ManagedOracleV2.json + - name: ManagedOracleV2Legacy + file: ./abis/ManagedOracleV2Legacy.json eventHandlers: - event: RequestPrice(indexed address,bytes32,uint256,bytes,address,uint256,uint256) handler: handleOptimisticRequestPrice diff --git a/packages/managed-oracle-v2/src/mappings/optimisticOracleV2.ts b/packages/managed-oracle-v2/src/mappings/optimisticOracleV2.ts index c0331a8..5b74943 100644 --- a/packages/managed-oracle-v2/src/mappings/optimisticOracleV2.ts +++ b/packages/managed-oracle-v2/src/mappings/optimisticOracleV2.ts @@ -8,17 +8,13 @@ import { SetEventBasedCall, Settle, } from "../../generated/ManagedOracleV2/ManagedOracleV2"; +import { ManagedOracleV2Legacy } from "../../generated/ManagedOracleV2/ManagedOracleV2Legacy"; import { createOptimisticPriceRequestId, getManagedRequestId, getOrCreateOptimisticPriceRequest } from "../utils/helpers"; import { CustomBond, CustomLiveness } from "../../generated/schema"; import { Address, BigInt, Bytes, dataSource, log } from "@graphprotocol/graph-ts"; import { createCustomBondId } from "../utils/helpers/managedOracleV2"; -let network = dataSource.network(); - -let isMainnet = network == "mainnet"; -let isGoerli = network == "goerli"; - /** * Retrieves a custom bond entity if one exists for the given parameters. * @@ -84,6 +80,10 @@ function getState( // ); export function handleOptimisticRequestPrice(event: RequestPrice): void { + let network = dataSource.network(); + let isMainnet = network == "mainnet"; + let isGoerli = network == "goerli"; + log.warning(`(ancillary) OOV2 PriceRequest params: {},{},{}`, [ event.params.timestamp.toString(), event.params.identifier.toString(), @@ -121,15 +121,38 @@ export function handleOptimisticRequestPrice(event: RequestPrice): void { // see readme for more info if (!isMainnet && !isGoerli) { let oov2 = ManagedOracleV2.bind(event.address); - let requestSettings = oov2.try_getRequest( + let result = oov2.try_getRequest( event.params.requester, event.params.identifier, event.params.timestamp, event.params.ancillaryData - ).value.requestSettings; - request.bond = requestSettings.bond; - request.eventBased = requestSettings.eventBased; - request.customLiveness = requestSettings.customLiveness; + ); + + if (!result.reverted) { + // New ABI succeeded + let requestSettings = result.value.requestSettings; + request.bond = requestSettings.bond; + request.eventBased = requestSettings.eventBased; + request.customLiveness = requestSettings.customLiveness; + } else { + // Try legacy ABI (for contracts not yet upgraded) + let oov2Legacy = ManagedOracleV2Legacy.bind(event.address); + let legacyResult = oov2Legacy.try_getRequest( + event.params.requester, + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + if (!legacyResult.reverted) { + let requestSettings = legacyResult.value.requestSettings; + request.bond = requestSettings.bond; + request.eventBased = requestSettings.eventBased; + request.customLiveness = requestSettings.customLiveness; + } else { + log.warning("Both new and legacy getRequest calls failed for request {}", [requestId]); + } + } } // Look up custom bond and liveness values that may have been set before the request 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 c857848..13829e7 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,4 +1,4 @@ -import { describe, test, clearStore, assert, log, afterEach } from "matchstick-as/assembly/index"; +import { describe, test, clearStore, assert, log, afterEach, beforeEach, dataSourceMock } from "matchstick-as/assembly/index"; import { handleCustomLivenessSet, handleCustomBondSet, handleRoleGranted, handleRoleRevoked } from "../../src/mappings/managedOracleV2"; import { handleOptimisticProposePrice, handleOptimisticRequestPrice } from "../../src/mappings/optimisticOracleV2"; import { createCustomBondIdFromEvent } from "../../src/utils/helpers/managedOracleV2"; @@ -11,6 +11,10 @@ import { createRoleGrantedEvent, createRoleRevokedEvent, mockGetState, + mockGetRequestNewABI, + mockGetRequestNewABIReverts, + mockGetRequestLegacyABI, + mockGetRequestLegacyABIReverts, State, RESOLVER_ROLE, } from "./utils"; @@ -597,3 +601,203 @@ describe("Resolver Tracking", () => { log.info("action: {}", [history.action]); }); }); + +describe("Dual-ABI getRequest fallback (L2 chains)", () => { + // Common test variables + const requester = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + const identifierHex = "0x00000000000000000000000000000000005945535f4f525f4e4f5f5155455259"; + const ancillaryData = "0x5945535f4f525f4e4f5f5155455259"; + const currency = "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"; + const reward = 1000000; + const finalFee = 500000; + const timestamp = 1757284669; + const mockBond = 5000000; + const mockCustomLiveness = 7200; + + beforeEach(() => { + // Set network to matic (Polygon) so the getRequest branch is exercised + dataSourceMock.setNetwork("matic"); + }); + + afterEach(() => { + clearStore(); + dataSourceMock.resetValues(); + }); + + test("New ABI getRequest succeeds - populates bond, eventBased, customLiveness from contract", () => { + mockGetState(requester, identifierHex, timestamp, ancillaryData, State.Requested); + mockGetRequestNewABI(requester, identifierHex, timestamp, ancillaryData, mockBond, true, mockCustomLiveness); + + const requestPriceEvent = createRequestPriceEvent( + requester, + identifierHex, + timestamp, + ancillaryData, + currency, + reward, + finalFee + ); + handleOptimisticRequestPrice(requestPriceEvent); + + const requestId = createOptimisticPriceRequestId( + Bytes.fromHexString(identifierHex) as Bytes, + BigInt.fromI32(timestamp), + Bytes.fromHexString(ancillaryData) as Bytes + ); + + const entity = OptimisticPriceRequest.load(requestId); + assert.assertTrue(entity !== null, "Entity should be created"); + if (entity === null) return; + + assert.bigIntEquals(entity.bond!, BigInt.fromI32(mockBond), "Bond should be populated from new ABI getRequest"); + assert.assertTrue(entity.eventBased!, "eventBased should be true from new ABI getRequest"); + assert.bigIntEquals( + entity.customLiveness!, + BigInt.fromI32(mockCustomLiveness), + "customLiveness should be populated from new ABI getRequest" + ); + }); + + test("Legacy ABI fallback when new ABI reverts - populates bond, eventBased, customLiveness", () => { + mockGetState(requester, identifierHex, timestamp, ancillaryData, State.Requested); + // New ABI reverts (e.g. contract from opt-in-early-resolution without proposalTime) + mockGetRequestNewABIReverts(requester, identifierHex, timestamp, ancillaryData); + // Legacy ABI succeeds + mockGetRequestLegacyABI(requester, identifierHex, timestamp, ancillaryData, mockBond, true, mockCustomLiveness); + + const requestPriceEvent = createRequestPriceEvent( + requester, + identifierHex, + timestamp, + ancillaryData, + currency, + reward, + finalFee + ); + handleOptimisticRequestPrice(requestPriceEvent); + + const requestId = createOptimisticPriceRequestId( + Bytes.fromHexString(identifierHex) as Bytes, + BigInt.fromI32(timestamp), + Bytes.fromHexString(ancillaryData) as Bytes + ); + + const entity = OptimisticPriceRequest.load(requestId); + assert.assertTrue(entity !== null, "Entity should be created"); + if (entity === null) return; + + assert.bigIntEquals(entity.bond!, BigInt.fromI32(mockBond), "Bond should be populated from legacy ABI fallback"); + assert.assertTrue(entity.eventBased!, "eventBased should be true from legacy ABI fallback"); + assert.bigIntEquals( + entity.customLiveness!, + BigInt.fromI32(mockCustomLiveness), + "customLiveness should be populated from legacy ABI fallback" + ); + }); + + test("Both ABIs fail - request created without contract-derived settings", () => { + mockGetState(requester, identifierHex, timestamp, ancillaryData, State.Requested); + // Both ABIs revert + mockGetRequestNewABIReverts(requester, identifierHex, timestamp, ancillaryData); + mockGetRequestLegacyABIReverts(requester, identifierHex, timestamp, ancillaryData); + + const requestPriceEvent = createRequestPriceEvent( + requester, + identifierHex, + timestamp, + ancillaryData, + currency, + reward, + finalFee + ); + handleOptimisticRequestPrice(requestPriceEvent); + + const requestId = createOptimisticPriceRequestId( + Bytes.fromHexString(identifierHex) as Bytes, + BigInt.fromI32(timestamp), + Bytes.fromHexString(ancillaryData) as Bytes + ); + + const entity = OptimisticPriceRequest.load(requestId); + assert.assertTrue(entity !== null, "Entity should still be created even when both ABIs fail"); + if (entity === null) return; + + // Basic event data should still be populated + assert.addressEquals( + Address.fromBytes(entity.requester), + Address.fromString(requester), + "Requester should still be set from event params" + ); + assert.bigIntEquals(entity.reward, BigInt.fromI32(reward), "Reward should still be set from event params"); + + // Contract-derived settings should be null (not populated) + assert.assertTrue(entity.bond === null, "Bond should be null when both ABIs fail"); + assert.assertTrue(entity.eventBased === null, "eventBased should be null when both ABIs fail"); + assert.assertTrue(entity.customLiveness === null, "customLiveness should be null when both ABIs fail"); + }); + + test("Custom bond/liveness entities override contract getRequest values", () => { + const managedRequestId = "0x8aed060a05dfbb279705824d8b544fc58a63ebc4a1c26380cbd90297c0a7e33c"; + const customBondAmount = 9999999; + const customLivenessAmount = 86400; + + mockGetState(requester, identifierHex, timestamp, ancillaryData, State.Requested); + // Contract returns some values via getRequest + mockGetRequestNewABI(requester, identifierHex, timestamp, ancillaryData, mockBond, false, mockCustomLiveness); + + // Set custom bond and liveness entities BEFORE the request + const customBondEvent = createCustomBondSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + currency, + customBondAmount + ); + handleCustomBondSet(customBondEvent); + + const customLivenessEvent = createCustomLivenessSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + customLivenessAmount + ); + handleCustomLivenessSet(customLivenessEvent); + + // Now fire the RequestPrice event + const requestPriceEvent = createRequestPriceEvent( + requester, + identifierHex, + timestamp, + ancillaryData, + currency, + reward, + finalFee + ); + handleOptimisticRequestPrice(requestPriceEvent); + + const requestId = createOptimisticPriceRequestId( + Bytes.fromHexString(identifierHex) as Bytes, + BigInt.fromI32(timestamp), + Bytes.fromHexString(ancillaryData) as Bytes + ); + + const entity = OptimisticPriceRequest.load(requestId); + assert.assertTrue(entity !== null, "Entity should be created"); + if (entity === null) return; + + // Custom bond entity should override the getRequest value + assert.bigIntEquals( + entity.bond!, + BigInt.fromI32(customBondAmount), + "Custom bond entity should override contract getRequest bond" + ); + // Custom liveness entity should override the getRequest value + assert.bigIntEquals( + entity.customLiveness!, + BigInt.fromI32(customLivenessAmount), + "Custom liveness entity should override contract getRequest customLiveness" + ); + }); +}); 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 91385f7..4df343a 100644 --- a/packages/managed-oracle-v2/tests/managed-oracle-v2/utils.ts +++ b/packages/managed-oracle-v2/tests/managed-oracle-v2/utils.ts @@ -238,6 +238,130 @@ export function createRoleGrantedEvent( return roleGrantedEvent; } +// Function signatures used by the generated ABI bindings +const GET_REQUEST_NEW_SIG = "getRequest(address,bytes32,uint256,bytes):((address,address,address,bool,(bool,bool,bool,bool,bool,uint256,uint256),int256,int256,uint256,uint256,uint256,uint256))"; +const GET_REQUEST_LEGACY_SIG = "getRequest(address,bytes32,uint256,bytes):((address,address,address,bool,(bool,bool,bool,bool,bool,uint256,uint256),int256,int256,uint256,uint256,uint256))"; + +function getRequestArgs( + requester: string, + identifier: string, + timestamp: i32, + ancillaryData: string +): ethereum.Value[] { + return [ + ethereum.Value.fromAddress(Address.fromString(requester)), + ethereum.Value.fromFixedBytes(Bytes.fromHexString(identifier)), + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(timestamp)), + ethereum.Value.fromBytes(Bytes.fromHexString(ancillaryData)), + ]; +} + +function buildRequestSettingsTuple( + bond: i32, + eventBased: bool, + customLiveness: i32 +): ethereum.Tuple { + let settings = new ethereum.Tuple(); + settings.push(ethereum.Value.fromBoolean(eventBased)); + settings.push(ethereum.Value.fromBoolean(false)); // refundOnDispute + settings.push(ethereum.Value.fromBoolean(false)); // callbackOnPriceProposed + settings.push(ethereum.Value.fromBoolean(false)); // callbackOnPriceDisputed + settings.push(ethereum.Value.fromBoolean(false)); // callbackOnPriceSettled + settings.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(bond))); + settings.push(ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(customLiveness))); + return settings; +} + +/** + * Mocks getRequest with the new ABI (includes proposalTime field). + * Used when simulating a contract deployed from audit-permissioned-resolver. + */ +export function mockGetRequestNewABI( + requester: string, + identifier: string, + timestamp: i32, + ancillaryData: string, + bond: i32, + eventBased: bool, + customLiveness: i32 +): void { + let requestTuple = new ethereum.Tuple(); + requestTuple.push(ethereum.Value.fromAddress(Address.zero())); // proposer + requestTuple.push(ethereum.Value.fromAddress(Address.zero())); // disputer + requestTuple.push(ethereum.Value.fromAddress(Address.zero())); // currency + requestTuple.push(ethereum.Value.fromBoolean(false)); // settled + requestTuple.push(ethereum.Value.fromTuple(buildRequestSettingsTuple(bond, eventBased, customLiveness))); + requestTuple.push(ethereum.Value.fromSignedBigInt(BigInt.zero())); // proposedPrice + requestTuple.push(ethereum.Value.fromSignedBigInt(BigInt.zero())); // resolvedPrice + requestTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.zero())); // expirationTime + requestTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.zero())); // reward + requestTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.zero())); // finalFee + requestTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.zero())); // proposalTime + + createMockedFunction(contractAddress, "getRequest", GET_REQUEST_NEW_SIG) + .withArgs(getRequestArgs(requester, identifier, timestamp, ancillaryData)) + .returns([ethereum.Value.fromTuple(requestTuple)]); +} + +/** + * Mocks getRequest with the new ABI to revert. + * Simulates calling a contract that doesn't have the proposalTime field. + */ +export function mockGetRequestNewABIReverts( + requester: string, + identifier: string, + timestamp: i32, + ancillaryData: string +): void { + createMockedFunction(contractAddress, "getRequest", GET_REQUEST_NEW_SIG) + .withArgs(getRequestArgs(requester, identifier, timestamp, ancillaryData)) + .reverts(); +} + +/** + * Mocks getRequest with the legacy ABI (no proposalTime field). + * Used when simulating a pre-upgrade or opt-in-early-resolution contract. + */ +export function mockGetRequestLegacyABI( + requester: string, + identifier: string, + timestamp: i32, + ancillaryData: string, + bond: i32, + eventBased: bool, + customLiveness: i32 +): void { + let requestTuple = new ethereum.Tuple(); + requestTuple.push(ethereum.Value.fromAddress(Address.zero())); // proposer + requestTuple.push(ethereum.Value.fromAddress(Address.zero())); // disputer + requestTuple.push(ethereum.Value.fromAddress(Address.zero())); // currency + requestTuple.push(ethereum.Value.fromBoolean(false)); // settled + requestTuple.push(ethereum.Value.fromTuple(buildRequestSettingsTuple(bond, eventBased, customLiveness))); + requestTuple.push(ethereum.Value.fromSignedBigInt(BigInt.zero())); // proposedPrice + requestTuple.push(ethereum.Value.fromSignedBigInt(BigInt.zero())); // resolvedPrice + requestTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.zero())); // expirationTime + requestTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.zero())); // reward + requestTuple.push(ethereum.Value.fromUnsignedBigInt(BigInt.zero())); // finalFee + + createMockedFunction(contractAddress, "getRequest", GET_REQUEST_LEGACY_SIG) + .withArgs(getRequestArgs(requester, identifier, timestamp, ancillaryData)) + .returns([ethereum.Value.fromTuple(requestTuple)]); +} + +/** + * Mocks getRequest with the legacy ABI to revert. + */ +export function mockGetRequestLegacyABIReverts( + requester: string, + identifier: string, + timestamp: i32, + ancillaryData: string +): void { + createMockedFunction(contractAddress, "getRequest", GET_REQUEST_LEGACY_SIG) + .withArgs(getRequestArgs(requester, identifier, timestamp, ancillaryData)) + .reverts(); +} + export function createRoleRevokedEvent( role: Bytes, account: string, From b8457a0f5e6cd1a51fb9b38468e529b13313ecb9 Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Thu, 12 Feb 2026 16:52:02 +0100 Subject: [PATCH 3/3] docs: update managed oracle v2 polygon goldsky endpoint to 1.0.5 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b07cb49..8a8e352 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ This subgraph indexes events and function calls by the "Managed Optimistic Oracl - Goldsky: - Polygon - TheGraph: - - Goldsky: + - Goldsky: ## Financial Contract Events