From 0299b286814634dc91e444cf2320873b8c429a7d Mon Sep 17 00:00:00 2001 From: Pablo Maldonado Date: Wed, 28 Jan 2026 10:52:04 +0000 Subject: [PATCH 1/2] 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 c7a32e33050490fe25995cfe53fa2d9580fe4160 Mon Sep 17 00:00:00 2001 From: Reinis Martinsons Date: Mon, 2 Feb 2026 13:03:21 +0000 Subject: [PATCH 2/2] feat: mirror of prod moov2 proposals Signed-off-by: Reinis Martinsons --- README.md | 8 + packages/mirror-managed-oracle-v2/.gitignore | 4 + packages/mirror-managed-oracle-v2/README.md | 6 + .../abis/ManagedOracleV2.json | 2676 +++++++++++++++++ .../manifest/data/amoy.json | 10 + .../manifest/data/polygon.json | 10 + .../templates/ManagedOracleV2.template.yaml | 46 + .../manifest/templates/subgraph.template.yaml | 9 + .../mirror-managed-oracle-v2/matchstick.yaml | 2 + .../mirror-managed-oracle-v2/package.json | 41 + .../mirror-managed-oracle-v2/schema.graphql | 144 + .../scripts/build-manifest.sh | 15 + .../scripts/deploy.sh | 5 + .../scripts/remove-call-handlers.js | 8 + .../mirror-managed-oracle-v2/src/index.ts | 10 + .../src/mappings/managedOracleV2.ts | 108 + .../src/mappings/optimisticOracleV2.ts | 393 +++ .../src/utils/constants.ts | 9 + .../src/utils/decimals.ts | 25 + .../src/utils/helpers/index.ts | 2 + .../src/utils/helpers/managedOracleV2.ts | 36 + .../src/utils/helpers/optimisticOracle.ts | 39 + .../managed-oracle-v2/managedOracle.test.ts | 599 ++++ .../tests/managed-oracle-v2/utils.ts | 264 ++ .../mirror-managed-oracle-v2/tsconfig.json | 3 + 25 files changed, 4472 insertions(+) create mode 100644 packages/mirror-managed-oracle-v2/.gitignore create mode 100644 packages/mirror-managed-oracle-v2/README.md create mode 100644 packages/mirror-managed-oracle-v2/abis/ManagedOracleV2.json create mode 100644 packages/mirror-managed-oracle-v2/manifest/data/amoy.json create mode 100644 packages/mirror-managed-oracle-v2/manifest/data/polygon.json create mode 100644 packages/mirror-managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml create mode 100644 packages/mirror-managed-oracle-v2/manifest/templates/subgraph.template.yaml create mode 100644 packages/mirror-managed-oracle-v2/matchstick.yaml create mode 100644 packages/mirror-managed-oracle-v2/package.json create mode 100644 packages/mirror-managed-oracle-v2/schema.graphql create mode 100755 packages/mirror-managed-oracle-v2/scripts/build-manifest.sh create mode 100755 packages/mirror-managed-oracle-v2/scripts/deploy.sh create mode 100644 packages/mirror-managed-oracle-v2/scripts/remove-call-handlers.js create mode 100644 packages/mirror-managed-oracle-v2/src/index.ts create mode 100644 packages/mirror-managed-oracle-v2/src/mappings/managedOracleV2.ts create mode 100644 packages/mirror-managed-oracle-v2/src/mappings/optimisticOracleV2.ts create mode 100644 packages/mirror-managed-oracle-v2/src/utils/constants.ts create mode 100644 packages/mirror-managed-oracle-v2/src/utils/decimals.ts create mode 100644 packages/mirror-managed-oracle-v2/src/utils/helpers/index.ts create mode 100644 packages/mirror-managed-oracle-v2/src/utils/helpers/managedOracleV2.ts create mode 100644 packages/mirror-managed-oracle-v2/src/utils/helpers/optimisticOracle.ts create mode 100644 packages/mirror-managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts create mode 100644 packages/mirror-managed-oracle-v2/tests/managed-oracle-v2/utils.ts create mode 100644 packages/mirror-managed-oracle-v2/tsconfig.json diff --git a/README.md b/README.md index b07cb49..4a34681 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,14 @@ This subgraph indexes events and function calls by the "Managed Optimistic Oracl - TheGraph: - Goldsky: +## Mirror of Managed Optimistic Oracle V2 Events and Calls + +This subgraph indexes events and function calls by the "Managed Optimistic Oracle V2" contracts that mirror requests and proposals on production instance. We reuse the code from `packages/managed-oracle-v2` because the events emitted are the same. + +- Polygon + - TheGraph: + - Goldsky: + ## Financial Contract Events This subgraph indexes events emitted by the ExpiringMultiParty and Perpetual contracts. The code can be found in `packages/financial-contracts` diff --git a/packages/mirror-managed-oracle-v2/.gitignore b/packages/mirror-managed-oracle-v2/.gitignore new file mode 100644 index 0000000..91d9547 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +/generated +/build +subgraph.yaml \ No newline at end of file diff --git a/packages/mirror-managed-oracle-v2/README.md b/packages/mirror-managed-oracle-v2/README.md new file mode 100644 index 0000000..6fa5be4 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/README.md @@ -0,0 +1,6 @@ +# Managed Optimistic Oracle V2 Subgraph + +## Deploying the subgraph + +- Polygon: +- (staging) Amoy: diff --git a/packages/mirror-managed-oracle-v2/abis/ManagedOracleV2.json b/packages/mirror-managed-oracle-v2/abis/ManagedOracleV2.json new file mode 100644 index 0000000..0b31116 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/abis/ManagedOracleV2.json @@ -0,0 +1,2676 @@ +[ + { + "type": "constructor", + "inputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "CONFIG_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "DEFAULT_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "LOWEST_MINIMUM_DISPUTE_WINDOW", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "OO_ANCILLARY_DATA_LIMIT", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "REQUEST_MANAGER_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "RESOLVER_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "RESOLVER_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "TOO_EARLY_RESPONSE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "int256", + "internalType": "int256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UPGRADE_ADMIN_ROLE", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "UPGRADE_INTERFACE_VERSION", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "string", + "internalType": "string" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "acceptDefaultAdminTransfer", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "addRequestManager", + "inputs": [ + { + "name": "requestManager", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "addResolver", + "inputs": [ + { + "name": "resolver", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "allowedBondRanges", + "inputs": [ + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + } + ], + "outputs": [ + { + "name": "minimumBond", + "type": "uint128", + "internalType": "uint128" + }, + { + "name": "maximumBond", + "type": "uint128", + "internalType": "uint128" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "ancillaryBytesLimit", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "beginDefaultAdminTransfer", + "inputs": [ + { + "name": "newAdmin", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "cancelDefaultAdminTransfer", + "inputs": [], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "changeDefaultAdminDelay", + "inputs": [ + { + "name": "newDelay", + "type": "uint48", + "internalType": "uint48" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "claimDeferredPayout", + "inputs": [ + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "repaymentAddress", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "customBonds", + "inputs": [ + { + "name": "managedRequestId", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + } + ], + "outputs": [ + { + "name": "amount", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "isSet", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "customLivenessValues", + "inputs": [ + { + "name": "managedRequestId", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "liveness", + "type": "uint256", + "internalType": "uint256" + }, + { + "name": "isSet", + "type": "bool", + "internalType": "bool" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "customProposerWhitelists", + "inputs": [ + { + "name": "managedRequestId", + "type": "bytes32", + "internalType": "bytes32" + } + ], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract AddressWhitelistInterface" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "defaultAdmin", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "defaultAdminDelay", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "defaultAdminDelayIncreaseWait", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint48", + "internalType": "uint48" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "defaultLiveness", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "defaultProposerWhitelist", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract AddressWhitelistInterface" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "deferredPayouts", + "inputs": [ + { + "name": "currency", + "type": "address", + "internalType": "contract IERC20" + }, + { + "name": "deferredRecipient", + "type": "address", + "internalType": "address" + } + ], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "disputePrice", + "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": "totalBond", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "disputePriceFor", + "inputs": [ + { + "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" + } + ], + "outputs": [ + { + "name": "totalBond", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "finder", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "address", + "internalType": "contract FinderInterface" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getCurrentTime", + "inputs": [], + "outputs": [ + { + "name": "", + "type": "uint256", + "internalType": "uint256" + } + ], + "stateMutability": "view" + }, + { + "type": "function", + "name": "getCustomProposerWhitelist", + "inputs": [ + { + "name": "requester", + "type": "address", + "internalType": "address" + }, + { + "name": "identifier", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "ancillaryData", + "type": "bytes", + "internalType": "bytes" + } + ], + "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/mirror-managed-oracle-v2/manifest/data/amoy.json b/packages/mirror-managed-oracle-v2/manifest/data/amoy.json new file mode 100644 index 0000000..d59902e --- /dev/null +++ b/packages/mirror-managed-oracle-v2/manifest/data/amoy.json @@ -0,0 +1,10 @@ +{ + "network": "polygon-amoy", + "ManagedOracleV2DataSources": [ + { + "name": "ManagedOracleV2", + "address": "0xa3dE5F042EFD4C732498883100A2d319BbB3c1A1", + "startBlock": 24737019 + } + ] +} diff --git a/packages/mirror-managed-oracle-v2/manifest/data/polygon.json b/packages/mirror-managed-oracle-v2/manifest/data/polygon.json new file mode 100644 index 0000000..118fa6a --- /dev/null +++ b/packages/mirror-managed-oracle-v2/manifest/data/polygon.json @@ -0,0 +1,10 @@ +{ + "network": "matic", + "ManagedOracleV2DataSources": [ + { + "name": "ManagedOracleV2", + "address": "0x0a50b5028fef3f504115b282eef22e9cbef3d9bc", + "startBlock": 82287894 + } + ] +} diff --git a/packages/mirror-managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml b/packages/mirror-managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml new file mode 100644 index 0000000..9ad4618 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/manifest/templates/ManagedOracleV2.template.yaml @@ -0,0 +1,46 @@ +- kind: ethereum/contract + name: {{name}} + network: {{network}} + source: + address: "{{address}}" + abi: ManagedOracleV2 + startBlock: {{startBlock}} + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + file: ./src/index.ts + entities: + - OptimisticPriceRequest + - PriceIdentifier + - CustomLiveness + - CustomBond + - Resolver + - ResolverHistory + abis: + - name: ManagedOracleV2 + file: ./abis/ManagedOracleV2.json + eventHandlers: + - event: RequestPrice(indexed address,bytes32,uint256,bytes,address,uint256,uint256) + handler: handleOptimisticRequestPrice + - event: ProposePrice(indexed address,indexed address,bytes32,uint256,bytes,int256,uint256,address) + handler: handleOptimisticProposePrice + - event: DisputePrice(indexed address,indexed address,indexed address,bytes32,uint256,bytes,int256) + handler: handleOptimisticDisputePrice + - event: Settle(indexed address,indexed address,indexed address,bytes32,uint256,bytes,int256,uint256) + handler: handleOptimisticSettle + - event: CustomLivenessSet(indexed bytes32,indexed address,indexed bytes32,bytes,uint256) + 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 + - function: setBond(bytes32,uint256,bytes,uint256) + handler: handleSetBond + - function: setEventBased(bytes32,uint256,bytes) + handler: handleSetEventBased \ No newline at end of file diff --git a/packages/mirror-managed-oracle-v2/manifest/templates/subgraph.template.yaml b/packages/mirror-managed-oracle-v2/manifest/templates/subgraph.template.yaml new file mode 100644 index 0000000..2efe509 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/manifest/templates/subgraph.template.yaml @@ -0,0 +1,9 @@ +specVersion: 0.0.2 +description: Subgraph with support for the UMA Managed Optimistic Oracle V2 contract data +repository: https://github.com/UMAprotocol/subgraphs +schema: + file: ./schema.graphql +dataSources: +{{#ManagedOracleV2DataSources}} + {{> ManagedOracleV2.template.yaml}} +{{/ManagedOracleV2DataSources}} diff --git a/packages/mirror-managed-oracle-v2/matchstick.yaml b/packages/mirror-managed-oracle-v2/matchstick.yaml new file mode 100644 index 0000000..01fb91d --- /dev/null +++ b/packages/mirror-managed-oracle-v2/matchstick.yaml @@ -0,0 +1,2 @@ +testsFolder: tests +libsFolder: ../../node_modules # binaries istalled in root of monorepo diff --git a/packages/mirror-managed-oracle-v2/package.json b/packages/mirror-managed-oracle-v2/package.json new file mode 100644 index 0000000..abfbc98 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/package.json @@ -0,0 +1,41 @@ +{ + "name": "mirror-managed-optimistic-oracle-v2-subgraph", + "version": "0.1.0", + "description": "Mirror Managed Optimistic Oracle V2 Contract Events Subgraph", + "scripts": { + "ganache": "ganache-cli -h 0.0.0.0", + "codegen": "graph codegen", + "build": "graph build", + "remove-call-handlers": "node ./scripts/remove-call-handlers.js", + "prepare:polygon": "scripts/build-manifest.sh polygon && yarn remove-call-handlers && yarn codegen && yarn build", + "prepare-ci": "scripts/build-manifest.sh polygon && yarn remove-call-handlers && yarn codegen && yarn build", + "prepare:amoy": "scripts/build-manifest.sh amoy && yarn remove-call-handlers && yarn codegen && yarn build", + "deploy:polygon": "env STAGING=true scripts/deploy.sh polygon", + "deploy-prod:polygon": "env STUDIO=true scripts/deploy.sh polygon", + "deploy:amoy": "env STAGING=true scripts/deploy.sh amoy", + "deploy-prod:amoy": "env STUDIO=true scripts/deploy.sh amoy", + "deploy:goldsky:amoy": "env GOLDSKY=true scripts/deploy.sh amoy", + "deploy-prod:goldsky:polygon": "env GOLDSKY=true scripts/deploy.sh polygon", + "test": "graph test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/UMAprotocol/subgraphs.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/UMAprotocol/subgraphs/issues" + }, + "homepage": "https://github.com/UMAprotocol/subgraphs#readme", + "dependencies": { + "@graphprotocol/graph-cli": "^0.67.3", + "@graphprotocol/graph-ts": "^0.32.0", + "mustache": "^4.2.0", + "yaml": "^2.2.1" + }, + "devDependencies": { + "matchstick-as": "0.6.0" + } +} diff --git a/packages/mirror-managed-oracle-v2/schema.graphql b/packages/mirror-managed-oracle-v2/schema.graphql new file mode 100644 index 0000000..1bf74c8 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/schema.graphql @@ -0,0 +1,144 @@ +enum OptimisticPriceRequestState { + Invalid + Requested + Proposed + Expired + Disputed + Resolved + Settled +} + +type OptimisticPriceRequest @entity { + "ID is the PriceIdentifier ID + the timestamp + ancillaryData (if available)" + id: ID! + + identifier: String! + + ancillaryData: String! + + time: BigInt! + + requester: Bytes! + + currency: Bytes! + + reward: BigInt! + + finalFee: BigInt! + + proposer: Bytes + + proposedPrice: BigInt + + proposalExpirationTimestamp: BigInt + + disputer: Bytes + + settlementPrice: BigInt + + settlementPayout: BigInt + + settlementRecipient: Bytes + + state: OptimisticPriceRequestState + + requestTimestamp: BigInt + + requestBlockNumber: BigInt + + requestHash: Bytes + + requestLogIndex: BigInt + + proposalTimestamp: BigInt + + proposalBlockNumber: BigInt + + proposalHash: Bytes + + proposalLogIndex: BigInt + + disputeTimestamp: BigInt + + disputeBlockNumber: BigInt + + disputeHash: Bytes + + disputeLogIndex: BigInt + + settlementTimestamp: BigInt + + settlementBlockNumber: BigInt + + settlementHash: Bytes + + settlementLogIndex: BigInt + + customLiveness: BigInt + + bond: BigInt + + eventBased: Boolean +} + +type CustomBond @entity { + "ID is the hash of requester, identifier, ancillaryData, currency" + id: ID! + + managedRequestId: String! + + currency: Bytes! + + requester: Bytes! + + identifier: String! + + ancillaryData: String! + + customBond: BigInt! +} + +type CustomLiveness @entity { + "ID is managedRequestId, ie. the hash of requester, identifier, ancillaryData" + id: ID! + + requester: Bytes! + + identifier: String! + + ancillaryData: String! + + 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/mirror-managed-oracle-v2/scripts/build-manifest.sh b/packages/mirror-managed-oracle-v2/scripts/build-manifest.sh new file mode 100755 index 0000000..3b6dc85 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/scripts/build-manifest.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +NETWORK=$1 + +FILE=$NETWORK'.json' + +DATA=manifest/data/$FILE + +echo 'Generating manifest from data file: '$DATA +cat $DATA + +mustache \ + -p manifest/templates/ManagedOracleV2.template.yaml \ + $DATA \ + manifest/templates/subgraph.template.yaml > subgraph.yaml \ No newline at end of file diff --git a/packages/mirror-managed-oracle-v2/scripts/deploy.sh b/packages/mirror-managed-oracle-v2/scripts/deploy.sh new file mode 100755 index 0000000..29e5cc0 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/scripts/deploy.sh @@ -0,0 +1,5 @@ +#!/bin/bash +NETWORK=$1 +SUBGRAPH_NAME=$NETWORK'-mirror-managed-optimistic-oracle-v2' + +SUBGRAPH_NAME=$SUBGRAPH_NAME /bin/bash ../../scripts/deploy.sh diff --git a/packages/mirror-managed-oracle-v2/scripts/remove-call-handlers.js b/packages/mirror-managed-oracle-v2/scripts/remove-call-handlers.js new file mode 100644 index 0000000..fd437ee --- /dev/null +++ b/packages/mirror-managed-oracle-v2/scripts/remove-call-handlers.js @@ -0,0 +1,8 @@ +const fs = require("fs"); +const yaml = require("yaml"); + +const subgraphYaml = fs.readFileSync("subgraph.yaml", "utf8"); +const parsed = yaml.parse(subgraphYaml); +delete parsed.dataSources[0].mapping.callHandlers; +const stringified = yaml.stringify(parsed); +fs.writeFileSync("subgraph.yaml", stringified); diff --git a/packages/mirror-managed-oracle-v2/src/index.ts b/packages/mirror-managed-oracle-v2/src/index.ts new file mode 100644 index 0000000..5514799 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/src/index.ts @@ -0,0 +1,10 @@ +export { + handleOptimisticDisputePrice, + handleOptimisticProposePrice, + handleOptimisticRequestPrice, + handleOptimisticSettle, + handleSetBond, + handleSetCustomLiveness, + handleSetEventBased, +} from "./mappings/optimisticOracleV2"; +export { handleCustomBondSet, handleCustomLivenessSet, handleRoleGranted, handleRoleRevoked } from "./mappings/managedOracleV2"; diff --git a/packages/mirror-managed-oracle-v2/src/mappings/managedOracleV2.ts b/packages/mirror-managed-oracle-v2/src/mappings/managedOracleV2.ts new file mode 100644 index 0000000..a5fdd6f --- /dev/null +++ b/packages/mirror-managed-oracle-v2/src/mappings/managedOracleV2.ts @@ -0,0 +1,108 @@ +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. + * + * Creates or updates a CustomBond entity with a unique ID that includes the currency. + * This ensures that custom bonds are tied to specific currencies and can only be + * found when the request currency matches the custom bond currency. + */ +export function handleCustomBondSet(event: CustomBondSet): void { + const managedRequestId = event.params.managedRequestId.toHexString(); + log.debug("Custom Bond set event. Loading entity with managedRequestId, {}", [managedRequestId]); + + // Generate unique ID that includes currency - this ensures currency matching + const id = createCustomBondIdFromEvent(event); + + let entity = CustomBond.load(id); + + if (entity == null) { + entity = new CustomBond(id); + entity.managedRequestId = managedRequestId; + entity.requester = event.params.requester; + entity.identifier = event.params.identifier.toString(); + entity.ancillaryData = event.params.ancillaryData.toHex(); + entity.currency = event.params.currency; + } + + entity.customBond = event.params.bond; + entity.save(); +} + +export function handleCustomLivenessSet(event: CustomLivenessSet): void { + const managedRequestId = event.params.managedRequestId.toHexString(); + log.debug("Custom Liveness set event. Loading entity with managedRequestId, {}", [managedRequestId]); + + let entity = CustomLiveness.load(managedRequestId); + + if (entity == null) { + entity = new CustomLiveness(managedRequestId); + entity.requester = event.params.requester; + entity.identifier = event.params.identifier.toString(); + entity.ancillaryData = event.params.ancillaryData.toHex(); + } + + 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/mirror-managed-oracle-v2/src/mappings/optimisticOracleV2.ts b/packages/mirror-managed-oracle-v2/src/mappings/optimisticOracleV2.ts new file mode 100644 index 0000000..c0331a8 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/src/mappings/optimisticOracleV2.ts @@ -0,0 +1,393 @@ +import { + DisputePrice, + ManagedOracleV2, + ProposePrice, + RequestPrice, + SetBondCall, + SetCustomLivenessCall, + SetEventBasedCall, + Settle, +} from "../../generated/ManagedOracleV2/ManagedOracleV2"; +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. + * + * IMPORTANT: Custom bonds are stored with a unique ID that includes the currency. + * This means we can only find custom bonds that match the EXACT currency used in the request. + * If a custom bond was set for a different currency, it will NOT be found here. + * + * This ensures that custom bonds are only applied when the currency matches, + * preventing incorrect bond amounts from being applied to requests with different currencies. + */ +function getCustomBond( + requester: Address, + identifier: Bytes, + ancillaryData: Bytes, + currency: Bytes +): CustomBond | null { + const id = createCustomBondId(requester, identifier, ancillaryData, currency); + let customBondEntity = CustomBond.load(id); + return customBondEntity ? customBondEntity : null; +} + +function getCustomLiveness(requester: Address, identifier: Bytes, ancillaryData: Bytes): CustomLiveness | null { + const managedRequestId = getManagedRequestId(requester, identifier, ancillaryData).toHexString(); + let customLivenessEntity = CustomLiveness.load(managedRequestId); + return customLivenessEntity ? customLivenessEntity : null; +} + +function getState( + ooAddress: Address, + requester: Address, + identifier: Bytes, + timestamp: BigInt, + ancillaryData: Bytes +): string { + const states = [ + "Invalid", // Never requested. + "Requested", // Requested, no other actions taken. + "Proposed", // Proposed, but not expired or disputed yet. + "Expired", // Proposed, not disputed, past liveness. + "Disputed", // Disputed, but no DVM price returned yet. + "Resolved", // Disputed and DVM price is available. + "Settled", // Final price has been set in the contract (can get here from Expired or Resolved). + ]; + let oov2 = ManagedOracleV2.bind(ooAddress); + let state = oov2.try_getState(requester, identifier, timestamp, ancillaryData); + if (state.reverted) { + log.warning("getState call reverted, returning Invalid state", []); + return states[0]; // Return "Invalid" if the call fails + } + return states[state.value]; +} + +// - event: RequestPrice(indexed address,bytes32,uint256,bytes,address,uint256,uint256) +// handler: handleOptimisticRequestPrice +// - event RequestPrice( +// address indexed requester, +// bytes32 identifier, +// uint256 timestamp, +// bytes ancillaryData, +// address currency, +// uint256 reward, +// uint256 finalFee +// ); + +export function handleOptimisticRequestPrice(event: RequestPrice): void { + log.warning(`(ancillary) OOV2 PriceRequest params: {},{},{}`, [ + event.params.timestamp.toString(), + event.params.identifier.toString(), + event.params.ancillaryData.toHex(), + ]); + let requestId = createOptimisticPriceRequestId( + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + let request = getOrCreateOptimisticPriceRequest(requestId); + + request.identifier = event.params.identifier.toString(); + request.time = event.params.timestamp; + request.ancillaryData = event.params.ancillaryData.toHex(); + request.requester = event.params.requester; + request.currency = event.params.currency; + request.reward = event.params.reward; + request.finalFee = event.params.finalFee; + request.requestTimestamp = event.block.timestamp; + request.requestBlockNumber = event.block.number; + request.requestLogIndex = event.logIndex; + request.requestHash = event.transaction.hash; + + request.state = getState( + event.address, + event.params.requester, + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + // workaround for L2 chains that don't support `callHandlers` + // see readme for more info + if (!isMainnet && !isGoerli) { + let oov2 = ManagedOracleV2.bind(event.address); + let requestSettings = 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; + } + + // Look up custom bond and liveness values that may have been set before the request + // Custom bonds are stored with a unique ID that includes the currency, so we only find + // custom bonds that match the exact currency used in this request + let customBond = getCustomBond( + event.params.requester, + event.params.identifier, + event.params.ancillaryData, + event.params.currency + ); + if (customBond !== null) { + const bond = customBond.customBond; + const currency = customBond.currency; + log.debug("custom bond of {} of currency {} was set for request Id: {}", [ + bond.toString(), + currency.toHexString(), + requestId, + ]); + // Apply the custom bond amount - the currency is guaranteed to match since + // the custom bond ID includes the currency and we looked it up using the request's currency + request.bond = bond; + } + + let customLiveness = getCustomLiveness(event.params.requester, event.params.identifier, event.params.ancillaryData); + if (customLiveness !== null) { + const liveness = customLiveness.customLiveness; + log.debug("custom liveness of {} was set for request Id: {}", [liveness.toString(), requestId]); + request.customLiveness = customLiveness.customLiveness; + } + + request.save(); +} + +// - event: ProposePrice(indexed address,indexed address,bytes32,uint256,bytes,int256,uint256,address) +// handler: handleOptimisticProposePrice +// - event ProposePrice( +// address indexed requester, +// address indexed proposer, +// bytes32 identifier, +// uint256 timestamp, +// bytes ancillaryData, +// int256 proposedPrice, +// uint256 expirationTimestamp, +// address currency +// ); + +export function handleOptimisticProposePrice(event: ProposePrice): void { + log.warning(`(ancillary) OOV2 PriceProposed params: {},{},{}`, [ + event.params.timestamp.toString(), + event.params.identifier.toString(), + event.params.ancillaryData.toHex(), + ]); + let requestId = createOptimisticPriceRequestId( + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + let request = getOrCreateOptimisticPriceRequest(requestId); + + request.proposer = event.params.proposer; + request.proposedPrice = event.params.proposedPrice; + request.proposalExpirationTimestamp = event.params.expirationTimestamp; + + request.proposalTimestamp = event.block.timestamp; + request.proposalBlockNumber = event.block.number; + request.proposalLogIndex = event.logIndex; + request.proposalHash = event.transaction.hash; + + request.state = getState( + event.address, + event.params.requester, + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + // Look up custom bond and liveness values that may have been set before the request + // Custom bonds are stored with a unique ID that includes the currency, so we only find + // custom bonds that match the exact currency used in this request + let customBond = getCustomBond( + event.params.requester, + event.params.identifier, + event.params.ancillaryData, + event.params.currency + ); + if (customBond !== null) { + const bond = customBond.customBond; + const currency = customBond.currency; + log.debug("custom bond of {} of currency {} was set for request Id: {}", [ + bond.toString(), + currency.toHexString(), + requestId, + ]); + // Apply the custom bond amount - the currency is guaranteed to match since + // the custom bond ID includes the currency and we looked it up using the request's currency + request.bond = bond; + } + + let customLiveness = getCustomLiveness(event.params.requester, event.params.identifier, event.params.ancillaryData); + if (customLiveness !== null) { + const liveness = customLiveness.customLiveness; + log.debug("custom liveness of {} was set for request Id: {}", [liveness.toString(), requestId]); + request.customLiveness = customLiveness.customLiveness; + } + + request.save(); +} + +// - event: DisputePrice(indexed address,indexed address,indexed address,bytes32,uint256,bytes,int256) +// handler: handleOptimisticDisputePrice +// - event DisputePrice( +// address indexed requester, +// address indexed proposer, +// address indexed disputer, +// bytes32 identifier, +// uint256 timestamp, +// bytes ancillaryData, +// int256 proposedPrice +// ); + +export function handleOptimisticDisputePrice(event: DisputePrice): void { + log.warning(`(ancillary) OOV2 PriceDisputed params: {},{},{}`, [ + event.params.timestamp.toString(), + event.params.identifier.toString(), + event.params.ancillaryData.toHex(), + ]); + let requestId = createOptimisticPriceRequestId( + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + let request = getOrCreateOptimisticPriceRequest(requestId); + + request.disputer = event.params.disputer; + + request.disputeTimestamp = event.block.timestamp; + request.disputeBlockNumber = event.block.number; + request.disputeLogIndex = event.logIndex; + request.disputeHash = event.transaction.hash; + + request.state = getState( + event.address, + event.params.requester, + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + request.save(); +} + +// - event: Settle(indexed address,indexed address,indexed address,bytes32,uint256,bytes,int256,uint256) +// handler: handleOptimisticSettle +// - event Settle( +// address indexed requester, +// address indexed proposer, +// address indexed disputer, +// bytes32 identifier, +// uint256 timestamp, +// bytes ancillaryData, +// int256 price, +// uint256 payout +// ); + +export function handleOptimisticSettle(event: Settle): void { + log.warning(`(ancillary) OOV2 Settled params: {},{},{}`, [ + event.params.timestamp.toString(), + event.params.identifier.toString(), + event.params.ancillaryData.toHex(), + ]); + let requestId = createOptimisticPriceRequestId( + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + let request = getOrCreateOptimisticPriceRequest(requestId); + + request.settlementPrice = event.params.price; + request.settlementPayout = event.params.payout; + + if (!request.disputer || request.proposedPrice!.equals(event.params.price)) { + request.settlementRecipient = request.proposer; + } else { + request.settlementRecipient = request.disputer; + } + + request.settlementTimestamp = event.block.timestamp; + request.settlementBlockNumber = event.block.number; + request.settlementLogIndex = event.logIndex; + request.settlementHash = event.transaction.hash; + + request.state = getState( + event.address, + event.params.requester, + event.params.identifier, + event.params.timestamp, + event.params.ancillaryData + ); + + request.save(); +} + +export function handleSetCustomLiveness(call: SetCustomLivenessCall): void { + log.warning(`OOV2 set custom liveness inputs: {},{},{},{}`, [ + call.inputs.timestamp.toString(), + call.inputs.identifier.toString(), + call.inputs.ancillaryData.toHex(), + call.inputs.customLiveness.toString(), + ]); + let requestId = createOptimisticPriceRequestId( + call.inputs.identifier, + call.inputs.timestamp, + call.inputs.ancillaryData + ); + + let request = getOrCreateOptimisticPriceRequest(requestId); + request.customLiveness = call.inputs.customLiveness; + + request.save(); +} + +export function handleSetBond(call: SetBondCall): void { + log.warning(`OOV2 set bond inputs: {},{},{},{}`, [ + call.inputs.timestamp.toString(), + call.inputs.identifier.toString(), + call.inputs.ancillaryData.toHex(), + call.inputs.bond.toString(), + ]); + let requestId = createOptimisticPriceRequestId( + call.inputs.identifier, + call.inputs.timestamp, + call.inputs.ancillaryData + ); + + let request = getOrCreateOptimisticPriceRequest(requestId); + request.bond = call.inputs.bond; + + request.save(); +} + +export function handleSetEventBased(call: SetEventBasedCall): void { + log.warning(`OOV2 set event based inputs: {},{},{}`, [ + call.inputs.timestamp.toString(), + call.inputs.identifier.toString(), + call.inputs.ancillaryData.toHex(), + ]); + let requestId = createOptimisticPriceRequestId( + call.inputs.identifier, + call.inputs.timestamp, + call.inputs.ancillaryData + ); + + let request = getOrCreateOptimisticPriceRequest(requestId); + request.eventBased = true; + + request.save(); +} diff --git a/packages/mirror-managed-oracle-v2/src/utils/constants.ts b/packages/mirror-managed-oracle-v2/src/utils/constants.ts new file mode 100644 index 0000000..aefbdc1 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/src/utils/constants.ts @@ -0,0 +1,9 @@ +import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; +import { toDecimal } from "./decimals"; + +export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"; +export let BIGINT_ZERO = BigInt.fromI32(0); +export let BIGINT_ONE = BigInt.fromI32(1); +export let BIGDECIMAL_ZERO = new BigDecimal(BIGINT_ZERO); +export let BIGDECIMAL_ONE = toDecimal(BigInt.fromI32(10).pow(18)); +export let BIGDECIMAL_HUNDRED = toDecimal(BigInt.fromI32(10).pow(20)); diff --git a/packages/mirror-managed-oracle-v2/src/utils/decimals.ts b/packages/mirror-managed-oracle-v2/src/utils/decimals.ts new file mode 100644 index 0000000..38ca7d6 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/src/utils/decimals.ts @@ -0,0 +1,25 @@ +import { BigDecimal, BigInt } from "@graphprotocol/graph-ts"; + +export const DEFAULT_DECIMALS = 18; + +export function pow(base: BigDecimal, exponent: number): BigDecimal { + let result = base; + + if (exponent == 0) { + return BigDecimal.fromString("1"); + } + + for (let i = 2; i <= exponent; i++) { + result = result.times(base); + } + + return result; +} + +export function toDecimal(value: BigInt, decimals: number = DEFAULT_DECIMALS): BigDecimal { + let precision = BigInt.fromI32(10) + .pow(decimals) + .toBigDecimal(); + + return value.divDecimal(precision); +} diff --git a/packages/mirror-managed-oracle-v2/src/utils/helpers/index.ts b/packages/mirror-managed-oracle-v2/src/utils/helpers/index.ts new file mode 100644 index 0000000..3f9cdba --- /dev/null +++ b/packages/mirror-managed-oracle-v2/src/utils/helpers/index.ts @@ -0,0 +1,2 @@ +export { getOrCreateOptimisticPriceRequest, createOptimisticPriceRequestId } from "./optimisticOracle"; +export { getManagedRequestId } from "./managedOracleV2"; diff --git a/packages/mirror-managed-oracle-v2/src/utils/helpers/managedOracleV2.ts b/packages/mirror-managed-oracle-v2/src/utils/helpers/managedOracleV2.ts new file mode 100644 index 0000000..8fc0b9e --- /dev/null +++ b/packages/mirror-managed-oracle-v2/src/utils/helpers/managedOracleV2.ts @@ -0,0 +1,36 @@ +import { Address, ByteArray, Bytes, crypto } from "@graphprotocol/graph-ts"; +import { CustomBondSet } from "../../../generated/ManagedOracleV2/ManagedOracleV2"; + +export function getManagedRequestId(requester: Address, identifier: Bytes, ancillaryData: Bytes): ByteArray { + let packed = requester.concat(identifier).concat(ancillaryData); + return crypto.keccak256(packed); +} + +/** + * Creates a unique ID for a custom bond entity. + * + * Including the currency in the ID ensures that custom bonds are tied to specific currencies. + * This means that custom bonds set for different currencies will have different IDs, + * even if all other parameters (requester, identifier, ancillary data) are the same. + * + * This is crucial for the currency matching logic - we can only find custom bonds + * that match the exact currency used in a request. + */ +export function createCustomBondId(requester: Bytes, identifier: Bytes, ancillaryData: Bytes, currency: Bytes): string { + let packed = requester.concat(identifier).concat(ancillaryData).concat(currency); + return crypto.keccak256(packed).toHexString(); +} + +/** + * Creates a custom bond ID from a CustomBondSet event. + * This is a convenience function that extracts the parameters from the event + * and passes them to createCustomBondId. + */ +export function createCustomBondIdFromEvent(event: CustomBondSet): string { + return createCustomBondId( + event.params.requester, + event.params.identifier, + event.params.ancillaryData, + event.params.currency + ); +} diff --git a/packages/mirror-managed-oracle-v2/src/utils/helpers/optimisticOracle.ts b/packages/mirror-managed-oracle-v2/src/utils/helpers/optimisticOracle.ts new file mode 100644 index 0000000..8e081da --- /dev/null +++ b/packages/mirror-managed-oracle-v2/src/utils/helpers/optimisticOracle.ts @@ -0,0 +1,39 @@ +import { BigInt, Bytes, crypto } from "@graphprotocol/graph-ts"; +import { OptimisticPriceRequest } from "../../../generated/schema"; +import { BIGINT_ZERO } from "../constants"; + + +// Creates a unique ID for an OptimisticPriceRequest entity. +export function createOptimisticPriceRequestId( + identifier: Bytes, + timestamp: BigInt, + ancillaryData: Bytes +): string { + const ancillaryDataHash = crypto.keccak256(ancillaryData).toHexString(); + return identifier + .toString() + .concat("-") + .concat(timestamp.toString()) + .concat("-") + .concat(ancillaryDataHash); +} + +export function getOrCreateOptimisticPriceRequest( + id: string, + createIfNotFound: boolean = true +): OptimisticPriceRequest { + let request = OptimisticPriceRequest.load(id); + + if (request == null && createIfNotFound) { + request = new OptimisticPriceRequest(id); + request.identifier = ""; + request.ancillaryData = ""; + request.time = BIGINT_ZERO; + request.requester = Bytes.fromI32(0); + request.currency = Bytes.fromI32(0); + request.reward = BIGINT_ZERO; + request.finalFee = BIGINT_ZERO; + } + + return request as OptimisticPriceRequest; +} diff --git a/packages/mirror-managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts b/packages/mirror-managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts new file mode 100644 index 0000000..c857848 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/tests/managed-oracle-v2/managedOracle.test.ts @@ -0,0 +1,599 @@ +import { describe, test, clearStore, assert, log, afterEach } 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"; +import { createOptimisticPriceRequestId } from "../../src/utils/helpers/optimisticOracle"; +import { + createCustomLivenessSetEvent, + createCustomBondSetEvent, + createProposePriceEvent, + createRequestPriceEvent, + createRoleGrantedEvent, + createRoleRevokedEvent, + mockGetState, + State, + RESOLVER_ROLE, +} from "./utils"; +import { CustomLiveness, CustomBond, OptimisticPriceRequest, Resolver, ResolverHistory } from "../../generated/schema"; +import { BigInt, Bytes, Address } from "@graphprotocol/graph-ts"; + +// Tests structure (matchstick-as >=0.5.0) +// https://thegraph.com/docs/en/developer/matchstick/#tests-structure-0-5-0 + +describe("Managed OOv2", () => { + afterEach(() => { + clearStore(); + }); + + test("handleCustomLivenessSet creates CustomLiveness entity correctly", () => { + // Test variables + const managedRequestId = "0x8aed060a05dfbb279705824d8b544fc58a63ebc4a1c26380cbd90297c0a7e33c"; + const requester = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + const identifierHex = "0x00000000000000000000000000000000005945535f4f525f4e4f5f5155455259"; // "YES_OR_NO_QUERY" + const ancillaryData = "0x5945535f4f525f4e4f5f5155455259"; + const customLiveness = 1757286231; + + const customLivenessEvent = createCustomLivenessSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + customLiveness + ); + + handleCustomLivenessSet(customLivenessEvent); + + const managedRequestIdActual = customLivenessEvent.params.managedRequestId.toHexString(); + + const customLivenessEntity = CustomLiveness.load(managedRequestIdActual); + + assert.assertTrue(customLivenessEntity !== null, "CustomLiveness entity should be created"); + + if (customLivenessEntity === null) { + return; + } + + assert.addressEquals( + Address.fromBytes(customLivenessEntity.requester), + Address.fromString(requester), + "Requester should match" + ); + assert.stringEquals(customLivenessEntity.identifier, "YES_OR_NO_QUERY", "Identifier should match"); + assert.bytesEquals( + Bytes.fromHexString(customLivenessEntity.ancillaryData), + Bytes.fromHexString(ancillaryData), + "Ancillary data should match" + ); + assert.bigIntEquals( + customLivenessEntity.customLiveness, + BigInt.fromI32(customLiveness), + "Custom liveness should match" + ); + + log.info("Created CustomLiveness entity: {}", [customLivenessEntity.id]); + log.info("Requester: {}", [customLivenessEntity.requester.toHexString()]); + log.info("Identifier: {}", [customLivenessEntity.identifier]); + log.info("Ancillary Data: {}", [customLivenessEntity.ancillaryData]); + log.info("Custom Liveness: {}", [customLivenessEntity.customLiveness.toString()]); + }); + + test("handleCustomBondSet creates CustomBond entity correctly", () => { + // Test variables + const managedRequestId = "0x8aed060a05dfbb279705824d8b544fc58a63ebc4a1c26380cbd90297c0a7e33c"; + const requester = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + const identifierHex = "0x00000000000000000000000000000000005945535f4f525f4e4f5f5155455259"; // "YES_OR_NO_QUERY" + const identifierString = "YES_OR_NO_QUERY"; + const ancillaryData = "0x5945535f4f525f4e4f5f5155455259"; + const currency = "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"; + const customBond = 2000000; + + const customBondEvent = createCustomBondSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + currency, + customBond + ); + + handleCustomBondSet(customBondEvent); + + // Generate the same ID that the mapping function uses + const customBondId = createCustomBondIdFromEvent(customBondEvent); + + const customBondEntity = CustomBond.load(customBondId); + + assert.assertTrue(customBondEntity !== null, "CustomBond entity should be created"); + + if (customBondEntity === null) { + return; + } + + assert.addressEquals( + Address.fromBytes(customBondEntity.requester), + Address.fromString(requester), + "Requester should match" + ); + assert.stringEquals(customBondEntity.identifier, identifierString, "Identifier should match"); + assert.bytesEquals( + Bytes.fromHexString(customBondEntity.ancillaryData), + Bytes.fromHexString(ancillaryData), + "Ancillary data should match" + ); + assert.bigIntEquals(customBondEntity.customBond, BigInt.fromI32(customBond), "Custom bond should match"); + + log.info("Created CustomBond entity: {}", [customBondEntity.id]); + log.info("Requester: {}", [customBondEntity.requester.toHexString()]); + log.info("Identifier: {}", [customBondEntity.identifier]); + log.info("Ancillary Data: {}", [customBondEntity.ancillaryData]); + log.info("Custom Bond: {}", [customBondEntity.customBond.toString()]); + }); + + test("Custom bond and liveness are applied to RequestPrice entity at REQUEST time", () => { + // Test variables + const managedRequestId = "0x8aed060a05dfbb279705824d8b544fc58a63ebc4a1c26380cbd90297c0a7e33c"; + const requester = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + const identifierHex = "0x00000000000000000000000000000000005945535f4f525f4e4f5f5155455259"; // "YES_OR_NO_QUERY" + const identifierString = "YES_OR_NO_QUERY"; + const ancillaryData = "0x5945535f4f525f4e4f5f5155455259"; + const currency = "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"; + const customBond = 2000000; + const customLiveness = 1757286231; + const reward = 1000000; + const finalFee = 500000; + const timestamp = 1757284669; + + mockGetState(requester, identifierHex, timestamp, ancillaryData, State.Requested); + // Step 1: Set custom liveness + const customLivenessEvent = createCustomLivenessSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + customLiveness + ); + handleCustomLivenessSet(customLivenessEvent); + + // Step 2: Set custom bond + const customBondEvent = createCustomBondSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + currency, + customBond + ); + handleCustomBondSet(customBondEvent); + + // Step 3: Create 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 priceRequestEntity = OptimisticPriceRequest.load(requestId); + + assert.assertTrue(priceRequestEntity !== null, "OptimisticPriceRequest entity should be created"); + + if (priceRequestEntity === null) { + return; + } + + assert.stringEquals(priceRequestEntity.identifier, identifierString, "Identifier should match"); + assert.bigIntEquals(priceRequestEntity.time, BigInt.fromI32(timestamp), "Timestamp should match"); + assert.bytesEquals( + Bytes.fromHexString(priceRequestEntity.ancillaryData), + Bytes.fromHexString(ancillaryData), + "Ancillary data should match" + ); + assert.addressEquals( + Address.fromBytes(priceRequestEntity.requester), + Address.fromString(requester), + "Requester should match" + ); + assert.addressEquals( + Address.fromBytes(priceRequestEntity.currency), + Address.fromString(currency), + "Currency should match" + ); + assert.bigIntEquals(priceRequestEntity.reward, BigInt.fromI32(reward), "Reward should match"); + assert.bigIntEquals(priceRequestEntity.finalFee, BigInt.fromI32(finalFee), "Final fee should match"); + + // Assert custom values are applied + assert.bigIntEquals( + priceRequestEntity.customLiveness!, + BigInt.fromI32(customLiveness), + "Custom liveness should be applied to RequestPrice" + ); + + // Check if bond is set + assert.assertTrue(priceRequestEntity.bond !== null, "Bond should not be null - custom bond should be applied"); + if (priceRequestEntity.bond !== null) { + assert.bigIntEquals( + priceRequestEntity.bond!, + BigInt.fromI32(customBond), + "Custom bond should be applied to RequestPrice" + ); + } + + log.info("Created OptimisticPriceRequest entity: {}", [priceRequestEntity.id]); + log.info("Custom Liveness: {}", [priceRequestEntity.customLiveness!.toString()]); + if (priceRequestEntity.bond !== null) { + log.info("Custom Bond: {}", [priceRequestEntity.bond!.toString()]); + } + log.info("Custom Bond Currency: {}", [priceRequestEntity.currency!.toHexString()]); + log.info("State: {}", [priceRequestEntity.state!]); + }); + + test("Custom bond currency mismatch - bond not applied when currencies don't match", () => { + // Test variables + const managedRequestId = "0x8aed060a05dfbb279705824d8b544fc58a63ebc4a1c26380cbd90297c0a7e33c"; + const requester = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + const identifierHex = "0x00000000000000000000000000000000005945535f4f525f4e4f5f5155455259"; // "YES_OR_NO_QUERY" + const identifierString = "YES_OR_NO_QUERY"; + const ancillaryData = "0x5945535f4f525f4e4f5f5155455259"; + const currency = "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"; + const customBond = 2000000; + const customLiveness = 1757286231; + const reward = 1000000; + const finalFee = 500000; + const timestamp = 1757284669; + const customLiveness_2 = 123456; + const customBond_2 = 3000000; + const currency_2 = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; + + mockGetState(requester, identifierHex, timestamp, ancillaryData, State.Requested); + // Step 1: Create RequestPrice event + const requestPriceEvent = createRequestPriceEvent( + requester, + identifierHex, + timestamp, + ancillaryData, + currency, + reward, + finalFee + ); + handleOptimisticRequestPrice(requestPriceEvent); + + // Step 2: Set custom liveness + const customLivenessEvent = createCustomLivenessSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + customLiveness + ); + handleCustomLivenessSet(customLivenessEvent); + // Step 3: Set custom bond + const customBondEvent = createCustomBondSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + currency_2, + customBond_2 + ); + handleCustomBondSet(customBondEvent); + + mockGetState(requester, identifierHex, timestamp, ancillaryData, State.Proposed); + + // Step 4: Create ProposePrice event + const proposePriceEvent = createProposePriceEvent( + requester, + requester, + identifierHex, + timestamp, + ancillaryData, + 1, + timestamp + 3600, + currency // Use same currency as original request + ); + handleOptimisticProposePrice(proposePriceEvent); + + const requestId = createOptimisticPriceRequestId( + Bytes.fromHexString(identifierHex) as Bytes, + BigInt.fromI32(timestamp), + Bytes.fromHexString(ancillaryData) as Bytes + ); + + const priceRequestEntity = OptimisticPriceRequest.load(requestId); + + assert.assertTrue(priceRequestEntity !== null, "OptimisticPriceRequest entity should be created"); + + if (priceRequestEntity === null) { + return; + } + + // Assert custom values are applied + assert.addressEquals( + Address.fromBytes(priceRequestEntity.currency), + Address.fromString(currency), + "Currency should match original request currency" + ); + + // Check if bond is set - since custom bond was set for different currency, + // it should NOT be applied (currency mismatch) + assert.assertTrue( + priceRequestEntity.bond === null, + "Bond should be null - custom bond should NOT be applied due to currency mismatch" + ); + + assert.bigIntEquals( + priceRequestEntity.customLiveness!, + BigInt.fromI32(customLiveness), + "Custom liveness should be applied to RequestPrice" + ); + + log.info("Created OptimisticPriceRequest entity: {}", [priceRequestEntity.id]); + log.info("Custom Liveness: {}", [priceRequestEntity.customLiveness!.toString()]); + log.info("State: {}", [priceRequestEntity.state!]); + if (priceRequestEntity.bond !== null) { + log.info("Custom Bond: {}", [priceRequestEntity.bond!.toString()]); + } + log.info("Custom Bond Currency: {}", [priceRequestEntity.currency!.toHexString()]); + }); + + test("Custom bond currency matching - only matching currency bond is applied (no currency changes)", () => { + // Test variables + const managedRequestId = "0x8aed060a05dfbb279705824d8b544fc58a63ebc4a1c26380cbd90297c0a7e33c"; + const requester = "0x9A8f92a830A5cB89a3816e3D267CB7791c16b04D"; + const identifierHex = "0x00000000000000000000000000000000005945535f4f525f4e4f5f5155455259"; // "YES_OR_NO_QUERY" + const identifierString = "YES_OR_NO_QUERY"; + const ancillaryData = "0x5945535f4f525f4e4f5f5155455259"; + + // Two different currencies and bond amounts + const currencyA = "0x9b4A302A548c7e313c2b74C461db7b84d3074A84"; // Token A + const bondAmountA = 2000000; // Bond amount A for Token A + + const currencyB = "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"; // Token B + const bondAmountB = 3000000; // Bond amount B for Token B + + const reward = 1000000; + const finalFee = 500000; + const timestamp = 1757284669; + + mockGetState(requester, identifierHex, timestamp, ancillaryData, State.Requested); + + // Step 1: Set custom bond for Token A with bond amount A + const customBondEventA = createCustomBondSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + currencyA, + bondAmountA + ); + handleCustomBondSet(customBondEventA); + + // Step 2: Set custom bond for Token B with bond amount B + const customBondEventC = createCustomBondSetEvent( + managedRequestId, + requester, + identifierHex, + ancillaryData, + currencyB, + bondAmountB + ); + handleCustomBondSet(customBondEventC); + + // Step 3: Create RequestPrice event with Token B as currency + // This should only apply the custom bond for Token B (bond amount B) + const requestPriceEvent = createRequestPriceEvent( + requester, + identifierHex, + timestamp, + ancillaryData, + currencyB, // Using Token B as currency + reward, + finalFee + ); + handleOptimisticRequestPrice(requestPriceEvent); + + const requestId = createOptimisticPriceRequestId( + Bytes.fromHexString(identifierHex) as Bytes, + BigInt.fromI32(timestamp), + Bytes.fromHexString(ancillaryData) as Bytes + ); + + const priceRequestEntity = OptimisticPriceRequest.load(requestId); + + assert.assertTrue(priceRequestEntity !== null, "OptimisticPriceRequest entity should be created"); + + if (priceRequestEntity === null) { + return; + } + + // Verify the request was created with correct basic parameters + assert.stringEquals(priceRequestEntity.identifier, identifierString, "Identifier should match"); + assert.bigIntEquals(priceRequestEntity.time, BigInt.fromI32(timestamp), "Timestamp should match"); + assert.bytesEquals( + Bytes.fromHexString(priceRequestEntity.ancillaryData), + Bytes.fromHexString(ancillaryData), + "Ancillary data should match" + ); + assert.addressEquals( + Address.fromBytes(priceRequestEntity.requester), + Address.fromString(requester), + "Requester should match" + ); + assert.addressEquals( + Address.fromBytes(priceRequestEntity.currency), + Address.fromString(currencyB), + "Currency should be Token C" + ); + assert.bigIntEquals(priceRequestEntity.reward, BigInt.fromI32(reward), "Reward should match"); + assert.bigIntEquals(priceRequestEntity.finalFee, BigInt.fromI32(finalFee), "Final fee should match"); + + // Check if bond is null first + assert.assertTrue(priceRequestEntity.bond !== null, "Bond should not be null - custom bond should be applied"); + + if (priceRequestEntity.bond === null) { + return; + } + + // CRITICAL ASSERTION: Only the custom bond for Token B (bond amount B) should be applied + // The custom bond for Token A (bond amount A) should NOT be applied + assert.bigIntEquals( + priceRequestEntity.bond!, + BigInt.fromI32(bondAmountB), // Should be bond amount B (for Token B) + "Custom bond should be bond amount B (for Token B), not bond amount A (for Token A)" + ); + + // Verify that the bond amount is NOT the amount set for Token A + assert.assertTrue( + !priceRequestEntity.bond!.equals(BigInt.fromI32(bondAmountA)), + "Custom bond should NOT be bond amount A (for Token A)" + ); + + log.info("Created OptimisticPriceRequest entity: {}", [priceRequestEntity.id]); + if (priceRequestEntity.bond !== null) { + log.info("Applied Custom Bond: {} (should be bond amount B for Token B)", [priceRequestEntity.bond!.toString()]); + } + log.info("Currency: {} (should be Token B)", [priceRequestEntity.currency!.toHexString()]); + 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/mirror-managed-oracle-v2/tests/managed-oracle-v2/utils.ts b/packages/mirror-managed-oracle-v2/tests/managed-oracle-v2/utils.ts new file mode 100644 index 0000000..91385f7 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/tests/managed-oracle-v2/utils.ts @@ -0,0 +1,264 @@ +import { createMockedFunction, newMockEvent } from "matchstick-as"; +import { ethereum, BigInt, Address, Bytes } from "@graphprotocol/graph-ts"; +import { + CustomBondSet, + 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( + managedRequestId: string, // Bytes, + requester: string, // Address, + identifier: string, // Bytes, + ancillaryData: string, // Bytes, + customLiveness: i32 // BigInt +): CustomLivenessSet { + let customLivenessEvent = changetype(newMockEvent()); + customLivenessEvent.address = contractAddress; + customLivenessEvent.parameters = new Array(); + // managedRequestId + customLivenessEvent.parameters.push( + new ethereum.EventParam("managedRequestId", ethereum.Value.fromBytes(Bytes.fromHexString(managedRequestId))) + ); + // requester + customLivenessEvent.parameters.push( + new ethereum.EventParam("requester", ethereum.Value.fromAddress(Address.fromString(requester))) + ); + // identifier + customLivenessEvent.parameters.push( + new ethereum.EventParam("identifier", ethereum.Value.fromBytes(Bytes.fromHexString(identifier))) + ); + // ancillaryData + customLivenessEvent.parameters.push( + new ethereum.EventParam("ancillaryData", ethereum.Value.fromBytes(Bytes.fromHexString(ancillaryData))) + ); + // customLiveness + customLivenessEvent.parameters.push( + new ethereum.EventParam("customLiveness", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(customLiveness))) + ); + + return customLivenessEvent; +} + +export function createCustomBondSetEvent( + managedRequestId: string, // Bytes, + requester: string, // Address, + identifier: string, // Bytes, + ancillaryData: string, // Bytes, + currency: string, // Address, + bond: i32 // BigInt +): CustomBondSet { + let customBondEvent = changetype(newMockEvent()); + customBondEvent.address = contractAddress; + customBondEvent.parameters = new Array(); + // managedRequestId + customBondEvent.parameters.push( + new ethereum.EventParam("managedRequestId", ethereum.Value.fromBytes(Bytes.fromHexString(managedRequestId))) + ); + // requester + customBondEvent.parameters.push( + new ethereum.EventParam("requester", ethereum.Value.fromAddress(Address.fromString(requester))) + ); + // identifier + customBondEvent.parameters.push( + new ethereum.EventParam("identifier", ethereum.Value.fromBytes(Bytes.fromHexString(identifier))) + ); + // ancillaryData + customBondEvent.parameters.push( + new ethereum.EventParam("ancillaryData", ethereum.Value.fromBytes(Bytes.fromHexString(ancillaryData))) + ); + // currency + customBondEvent.parameters.push( + new ethereum.EventParam("currency", ethereum.Value.fromAddress(Address.fromString(currency))) + ); + // bond + customBondEvent.parameters.push( + new ethereum.EventParam("bond", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(bond))) + ); + + return customBondEvent; +} + +export function createRequestPriceEvent( + requester: string, // Address, + identifier: string, // Bytes, + timestamp: i32, // BigInt, + ancillaryData: string, // Bytes, + currency: string, // Address, + reward: i32, // BigInt, + finalFee: i32 // BigInt +): RequestPrice { + let requestPriceEvent = changetype(newMockEvent()); + requestPriceEvent.address = contractAddress; + + requestPriceEvent.parameters = new Array(); + // requester + requestPriceEvent.parameters.push( + new ethereum.EventParam("requester", ethereum.Value.fromAddress(Address.fromString(requester))) + ); + // identifier + requestPriceEvent.parameters.push( + new ethereum.EventParam("identifier", ethereum.Value.fromBytes(Bytes.fromHexString(identifier))) + ); + // timestamp + requestPriceEvent.parameters.push( + new ethereum.EventParam("timestamp", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(timestamp))) + ); + // ancillaryData + requestPriceEvent.parameters.push( + new ethereum.EventParam("ancillaryData", ethereum.Value.fromBytes(Bytes.fromHexString(ancillaryData))) + ); + // currency + requestPriceEvent.parameters.push( + new ethereum.EventParam("currency", ethereum.Value.fromAddress(Address.fromString(currency))) + ); + // reward + requestPriceEvent.parameters.push( + new ethereum.EventParam("reward", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(reward))) + ); + // finalFee + requestPriceEvent.parameters.push( + new ethereum.EventParam("finalFee", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(finalFee))) + ); + + return requestPriceEvent; +} + +export function createProposePriceEvent( + requester: string, // Address, + proposer: string, // Address, + identifier: string, // Bytes, + timestamp: i32, // BigInt, + ancillaryData: string, // Bytes, + proposedPrice: i32, // BigInt, + expirationTimestamp: i32, // BigInt, + currency: string // Address +): ProposePrice { + let proposePriceEvent = changetype(newMockEvent()); + proposePriceEvent.address = contractAddress; + proposePriceEvent.parameters = new Array(); + + // requester + proposePriceEvent.parameters.push( + new ethereum.EventParam("requester", ethereum.Value.fromAddress(Address.fromString(requester))) + ); + // proposer + proposePriceEvent.parameters.push( + new ethereum.EventParam("proposer", ethereum.Value.fromAddress(Address.fromString(proposer))) + ); + // identifier + proposePriceEvent.parameters.push( + new ethereum.EventParam("identifier", ethereum.Value.fromBytes(Bytes.fromHexString(identifier))) + ); + // timestamp + proposePriceEvent.parameters.push( + new ethereum.EventParam("timestamp", ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(timestamp))) + ); + // ancillaryData + proposePriceEvent.parameters.push( + new ethereum.EventParam("ancillaryData", ethereum.Value.fromBytes(Bytes.fromHexString(ancillaryData))) + ); + // proposedPrice + proposePriceEvent.parameters.push( + new ethereum.EventParam("proposedPrice", ethereum.Value.fromSignedBigInt(BigInt.fromI32(proposedPrice))) + ); + // expirationTimestamp + proposePriceEvent.parameters.push( + new ethereum.EventParam( + "expirationTimestamp", + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(expirationTimestamp)) + ) + ); + // currency + proposePriceEvent.parameters.push( + new ethereum.EventParam("currency", ethereum.Value.fromAddress(Address.fromString(currency))) + ); + + return proposePriceEvent; +} + +// https://github.com/UMAprotocol/protocol/blob/99b96247d27ec8a5ea9dbf3eef1dcd71beb0dc41/packages/core/contracts/optimistic-oracle-v2/interfaces/OptimisticOracleV2Interface.sol#L51 +export namespace State { + export const Invalid = 0; // Never requested + export const Requested = 1; // Requested, no other actions taken + export const Proposed = 2; // Proposed, but not expired or disputed yet + export const Expired = 3; // Proposed, not disputed, past liveness + export const Disputed = 4; // Disputed, but no DVM price returned yet + export const Resolved = 5; // Disputed and DVM price is available + export const Settled = 6; // Final price has been set in the contract (can get here from Expired or Resolved). +} + +export function mockGetState( + requester: string, // Address, + identifier: string, // Bytes, + timestamp: i32, // ethereum.Value, + ancillaryData: string, // Bytes + expectedState: i32 // i32 => State +): void { + createMockedFunction(contractAddress, "getState", "getState(address,bytes32,uint256,bytes):(uint8)") + .withArgs([ + ethereum.Value.fromAddress(Address.fromString(requester)), + ethereum.Value.fromFixedBytes(Bytes.fromHexString(identifier)), + ethereum.Value.fromUnsignedBigInt(BigInt.fromI32(timestamp)), + ethereum.Value.fromBytes(Bytes.fromHexString(ancillaryData)), + ]) + .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; +} diff --git a/packages/mirror-managed-oracle-v2/tsconfig.json b/packages/mirror-managed-oracle-v2/tsconfig.json new file mode 100644 index 0000000..a6d2ae8 --- /dev/null +++ b/packages/mirror-managed-oracle-v2/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "@graphprotocol/graph-ts/types/tsconfig.base.json" +}