From 004ec9074564fc63b33dc2454008d50a54d12e5b Mon Sep 17 00:00:00 2001 From: PaulieB14 <94752445+PaulieB14@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:34:29 -0400 Subject: [PATCH 1/2] fix: track indexing issuance through the IssuanceAllocator (GIP-0089) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `GraphNetwork.networkGRTIssuancePerBlock` has read 120.73 GRT/block on Arbitrum since GIP-0089 activated on 2026-09-01, when the rate the RewardsManager actually issues at became 96.584. Every consumer computing indexing-reward APR from this field — the schema documents it for exactly that — currently overstates by 120.73/96.584 = 1.25x. Two independent causes, either sufficient on its own: 1. WRONG GETTER. rewardsManager.ts read `issuancePerBlock()`. Once the IssuanceAllocator was wired up (GIP-0076/GIP-0088) the RewardsManager mints only its own allocation, and that legacy storage slot stopped being the rate it issues at. On Arbitrum One today the slot still returns 120.73e18 while `getAllocatedIssuancePerBlock()` returns 96.584e18. The getter was also absent from abis/RewardsManagerStitched.json, so the mapping could not have called it. 2. NO TRIGGER. The value is only refreshed by the RewardsManager's `ParameterUpdated('issuancePerBlock')`. GIP-0089 changed the split on the ALLOCATOR, so no such event was emitted and the field was never re-read. Reproducible — the same block-scoped query either side of the change: { before: graphNetwork(id:"1", block:{number:500600000}) { networkGRTIssuancePerBlock } after: graphNetwork(id:"1", block:{number:501148942}) { networkGRTIssuancePerBlock } } Both return 120730000000000000000. GIP-0089 took effect at Arbitrum block 500,701,945, between them. On chain at the same moment: RewardsManager 0x971B9d3d0Ae3ECa029CAB5eA1fB0F72c85e6a525 getAllocatedIssuancePerBlock() 0xe208d721 -> 96.584e18 issuancePerBlock() 0x6c080f18 -> 120.730e18 IssuanceAllocator 0xb64f29b2d81140ffc3a135e319561a1bd03b1a7e getTargetAllocation(RewardsManager).selfMintingRate -> 96.584e18 CHANGES - New IssuanceAllocator data source handling `TargetAllocationUpdated(address,uint256,uint256)`. When the target is the RewardsManager, `newSelfMintingRate` is the rate it will issue at, so the event carries the value and no contract call is needed. This is the only place a future re-split is observable: GIP-0088 Phase 3 moves a further 6 GRT/block to the Recurring Agreement Manager, taking the RewardsManager to 90.584, and would otherwise go unnoticed in the same way. - rewardsManager.ts prefers `getAllocatedIssuancePerBlock()`, via try_ so it still works on deployments predating the upgrade where the getter is absent. - abis/RewardsManagerStitched.json gains the two getters it was missing. - The allocator address is resolved from @graphprotocol/address-book, which required bumping 1.1.0 -> ^1.3.0: 1.1.0 neither ships issuance/addresses.json nor exports the path, while 1.3.0 exports "./*/addresses.json". It is deployed on Arbitrum One and Arbitrum Sepolia only, and resolves empty elsewhere, matching how subgraphService is handled. VERIFIED `yarn prepare:arbitrum` and `yarn prepare:arbitrum-sepolia` both resolve the allocator (0xb64f29b2… and 0x76a0d756… respectively) and `yarn build` compiles. The CI test-prep step (testAddressesL1/L2 + mustache) still produces a valid config/addresses.ts on both layers. Note for reviewers: picking up the 2026-09-01 allocation requires reindexing from a start block at or before it, since the handler is event-driven. Whether that warrants a resync of the deployed subgraphs is a maintainer call. --- abis/IssuanceAllocator.json | 46 ++++++++++++++++++++++++++ abis/RewardsManagerStitched.json | 28 +++++++++++++++- config/addresses.template.ts | 2 ++ config/arbitrumSepoliaAddressScript.ts | 9 ++++- config/localNetworkAddressScript.ts | 1 + config/mainnetArbitrumAddressScript.ts | 9 ++++- config/testAddressesL1.ts | 1 + config/testAddressesL2.ts | 1 + package.json | 2 +- src/mappings/issuanceAllocator.ts | 34 +++++++++++++++++++ src/mappings/rewardsManager.ts | 11 +++++- subgraph.template.yaml | 25 ++++++++++++++ yarn.lock | 8 ++--- 13 files changed, 168 insertions(+), 9 deletions(-) create mode 100644 abis/IssuanceAllocator.json create mode 100644 src/mappings/issuanceAllocator.ts diff --git a/abis/IssuanceAllocator.json b/abis/IssuanceAllocator.json new file mode 100644 index 00000000..40b5b5fa --- /dev/null +++ b/abis/IssuanceAllocator.json @@ -0,0 +1,46 @@ +[ + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "target", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "newAllocatorMintingRate", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newSelfMintingRate", "type": "uint256" } + ], + "name": "TargetAllocationUpdated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "oldIssuancePerBlock", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newIssuancePerBlock", "type": "uint256" } + ], + "name": "IssuancePerBlockUpdated", + "type": "event" + }, + { + "inputs": [], + "name": "getIssuancePerBlock", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "target", "type": "address" }], + "name": "getTargetAllocation", + "outputs": [ + { + "components": [ + { "internalType": "uint256", "name": "totalAllocationRate", "type": "uint256" }, + { "internalType": "uint256", "name": "allocatorMintingRate", "type": "uint256" }, + { "internalType": "uint256", "name": "selfMintingRate", "type": "uint256" } + ], + "internalType": "struct IIssuanceAllocationStatus.Allocation", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + } +] diff --git a/abis/RewardsManagerStitched.json b/abis/RewardsManagerStitched.json index 4c58dd58..3efbe0ce 100644 --- a/abis/RewardsManagerStitched.json +++ b/abis/RewardsManagerStitched.json @@ -603,5 +603,31 @@ ], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [], + "name": "getAllocatedIssuancePerBlock", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getIssuanceAllocator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" } -] +] \ No newline at end of file diff --git a/config/addresses.template.ts b/config/addresses.template.ts index 94537328..8c0ff97c 100644 --- a/config/addresses.template.ts +++ b/config/addresses.template.ts @@ -22,6 +22,7 @@ export class Addresses { l2GraphTokenGateway: string ethereumDIDRegistry: string subgraphService: string + issuanceAllocator: string graphPayments: string paymentsEscrow: string graphTallyCollector: string @@ -52,6 +53,7 @@ export let addresses: Addresses = { l2GraphTokenGateway: '{{l2GraphTokenGateway}}', ethereumDIDRegistry: '{{ethereumDIDRegistry}}', subgraphService: '{{subgraphService}}', + issuanceAllocator: '{{issuanceAllocator}}', graphPayments: '{{graphPayments}}', paymentsEscrow: '{{paymentsEscrow}}', graphTallyCollector: '{{graphTallyCollector}}', diff --git a/config/arbitrumSepoliaAddressScript.ts b/config/arbitrumSepoliaAddressScript.ts index 378cfae0..5b2913ea 100644 --- a/config/arbitrumSepoliaAddressScript.ts +++ b/config/arbitrumSepoliaAddressScript.ts @@ -5,6 +5,7 @@ import { Addresses } from './addresses.template' const horizonAddresses = require('@graphprotocol/address-book/horizon/addresses.json') const subgraphServiceAddresses = require('@graphprotocol/address-book/subgraph-service/addresses.json') +const issuanceAddresses = require('@graphprotocol/address-book/issuance/addresses.json') // mustache doesn't like numbered object keys // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -15,10 +16,15 @@ renameHorizonAddresses['arbsep'] = horizonAddresses['421614'] let renameSubgraphServiceAddresses: any = subgraphServiceAddresses renameSubgraphServiceAddresses['arbsep'] = subgraphServiceAddresses['421614'] +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let renameIssuanceAddresses: any = issuanceAddresses +renameIssuanceAddresses['arbsep'] = issuanceAddresses['421614'] || {} + const combinedAddresses = { arbsep: { ...renameHorizonAddresses['arbsep'], - ...renameSubgraphServiceAddresses['arbsep'] + ...renameSubgraphServiceAddresses['arbsep'], + ...renameIssuanceAddresses['arbsep'] } } @@ -45,6 +51,7 @@ export let addresses: Addresses = { l2GraphTokenGateway: '{{arbsep.L2GraphTokenGateway.address}}', ethereumDIDRegistry: '{{arbsep.EthereumDIDRegistry.address}}', subgraphService: '{{arbsep.SubgraphService.address}}', + issuanceAllocator: '{{arbsep.IssuanceAllocator.address}}', graphPayments: '{{arbsep.GraphPayments.address}}', paymentsEscrow: '{{arbsep.PaymentsEscrow.address}}', graphTallyCollector: '{{arbsep.GraphTallyCollector.address}}', diff --git a/config/localNetworkAddressScript.ts b/config/localNetworkAddressScript.ts index 0efd624b..7932d785 100644 --- a/config/localNetworkAddressScript.ts +++ b/config/localNetworkAddressScript.ts @@ -10,6 +10,7 @@ import { Addresses } from './addresses.template' let renameAddresses: any = { horizon: horizonAddresses['1337'], subgraphService: subgraphServiceAddresses['1337'], + issuanceAllocator: '', // IssuanceAllocator is only deployed on Arbitrum } export let addresses: Addresses = { diff --git a/config/mainnetArbitrumAddressScript.ts b/config/mainnetArbitrumAddressScript.ts index a61504bc..cc848da9 100644 --- a/config/mainnetArbitrumAddressScript.ts +++ b/config/mainnetArbitrumAddressScript.ts @@ -4,6 +4,7 @@ import { Addresses } from './addresses.template' const horizonAddresses = require('@graphprotocol/address-book/horizon/addresses.json') const subgraphServiceAddresses = require('@graphprotocol/address-book/subgraph-service/addresses.json') +const issuanceAddresses = require('@graphprotocol/address-book/issuance/addresses.json') // mustache doesn't like numbered object keys // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -14,10 +15,15 @@ renameHorizonAddresses['arbitrum'] = horizonAddresses['42161'] || {} let renameSubgraphServiceAddresses: any = subgraphServiceAddresses renameSubgraphServiceAddresses['arbitrum'] = subgraphServiceAddresses['42161'] || {} +// eslint-disable-next-line @typescript-eslint/no-explicit-any +let renameIssuanceAddresses: any = issuanceAddresses +renameIssuanceAddresses['arbitrum'] = issuanceAddresses['42161'] || {} + const combinedAddresses = { arbitrum: { ...renameHorizonAddresses['arbitrum'], - ...renameSubgraphServiceAddresses['arbitrum'] + ...renameSubgraphServiceAddresses['arbitrum'], + ...renameIssuanceAddresses['arbitrum'] } } @@ -44,6 +50,7 @@ export let addresses: Addresses = { l2GraphTokenGateway: '{{arbitrum.L2GraphTokenGateway.address}}', ethereumDIDRegistry: '{{arbitrum.IEthereumDIDRegistry.address}}', subgraphService: '{{arbitrum.SubgraphService.address}}', + issuanceAllocator: '{{arbitrum.IssuanceAllocator.address}}', graphPayments: '{{arbitrum.GraphPayments.address}}', paymentsEscrow: '{{arbitrum.PaymentsEscrow.address}}', graphTallyCollector: '{{arbitrum.GraphTallyCollector.address}}', diff --git a/config/testAddressesL1.ts b/config/testAddressesL1.ts index 8de91df5..a5c59b8f 100644 --- a/config/testAddressesL1.ts +++ b/config/testAddressesL1.ts @@ -44,6 +44,7 @@ export let addresses: Addresses = { l2GraphTokenGateway: '', ethereumDIDRegistry: '0x0000000000000000000000000000000000000000', subgraphService: '0x0000000000000000000000000000000000000000', + issuanceAllocator: '', graphPayments: '0x0000000000000000000000000000000000000000', paymentsEscrow: '0x0000000000000000000000000000000000000000', graphTallyCollector: '0x0000000000000000000000000000000000000000', diff --git a/config/testAddressesL2.ts b/config/testAddressesL2.ts index ce8d1e34..07df5144 100644 --- a/config/testAddressesL2.ts +++ b/config/testAddressesL2.ts @@ -44,6 +44,7 @@ export let addresses: Addresses = { l2GraphTokenGateway: '0x0000000000000000000000000000000000000000', ethereumDIDRegistry: '0x0000000000000000000000000000000000000000', subgraphService: '0x0000000000000000000000000000000000000000', + issuanceAllocator: '', graphPayments: '0x0000000000000000000000000000000000000000', paymentsEscrow: '0x0000000000000000000000000000000000000000', graphTallyCollector: '0x0000000000000000000000000000000000000000', diff --git a/package.json b/package.json index dc5e698f..42d116fa 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "prepare:test-l2": "yarn && yarn prep:addresses:test:l2 && mustache ./config/generatedAddresses.json subgraph.template.yaml > subgraph.yaml && graph codegen --output-dir src/types/" }, "devDependencies": { - "@graphprotocol/address-book": "1.1.0", + "@graphprotocol/address-book": "^1.3.0", "@graphprotocol/contracts": "6.2.0", "@graphprotocol/graph-cli": "0.97.0", "@graphprotocol/graph-ts": "0.36.0", diff --git a/src/mappings/issuanceAllocator.ts b/src/mappings/issuanceAllocator.ts new file mode 100644 index 00000000..9eb48910 --- /dev/null +++ b/src/mappings/issuanceAllocator.ts @@ -0,0 +1,34 @@ +import { TargetAllocationUpdated } from '../types/IssuanceAllocator/IssuanceAllocator' +import { createOrLoadGraphNetwork } from './helpers/helpers' +import { addresses } from '../../config/addresses' +import { Address } from '@graphprotocol/graph-ts' + +/** + * @dev handleTargetAllocationUpdated + * - Keeps GraphNetwork.networkGRTIssuancePerBlock in step with the share of + * issuance the RewardsManager actually receives. + * + * Since GIP-0076/GIP-0088 the RewardsManager no longer owns the issuance rate: + * the IssuanceAllocator splits total issuance across targets and the + * RewardsManager mints only its own allocation. GIP-0089 exercised that for the + * first time on 2026-09-01, moving 24.146 GRT/block (20%) to the Foundation's + * Innovation Allocation and leaving the RewardsManager 96.584 of the unchanged + * 120.73 total. + * + * That change is invisible to the RewardsManager's own ParameterUpdated event, + * so without this handler networkGRTIssuancePerBlock stays at whatever the + * legacy `issuancePerBlock` storage slot last held — 120.73 — and every + * downstream APR calculation built on it overstates indexing rewards by 25%. + * + * The RewardsManager is an allocator target that mints its own issuance, so + * `newSelfMintingRate` is the rate it will issue at. The event carries it, so + * no contract call is needed here. + */ +export function handleTargetAllocationUpdated(event: TargetAllocationUpdated): void { + if (event.params.target != Address.fromString(addresses.rewardsManager)) { + return + } + let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address) + graphNetwork.networkGRTIssuancePerBlock = event.params.newSelfMintingRate + graphNetwork.save() +} diff --git a/src/mappings/rewardsManager.ts b/src/mappings/rewardsManager.ts index d66f33ed..52c29327 100644 --- a/src/mappings/rewardsManager.ts +++ b/src/mappings/rewardsManager.ts @@ -69,7 +69,16 @@ export function handleParameterUpdated(event: ParameterUpdated): void { if (parameter == 'issuanceRate') { graphNetwork.networkGRTIssuance = rewardsManager.issuanceRate() } else if (parameter == 'issuancePerBlock') { - graphNetwork.networkGRTIssuancePerBlock = rewardsManager.issuancePerBlock() + // Read the allocator-aware rate, not the legacy `issuancePerBlock` slot. + // Since the IssuanceAllocator was wired up (GIP-0088) the RewardsManager + // issues only its own allocation, and the legacy slot is no longer that + // number — on Arbitrum it still reads 120.73 while the allocated rate is + // 96.584. try_ so the call is safe on deployments predating the upgrade, + // where the getter does not exist. + let allocated = rewardsManager.try_getAllocatedIssuancePerBlock() + graphNetwork.networkGRTIssuancePerBlock = allocated.reverted + ? rewardsManager.issuancePerBlock() + : allocated.value } else if (parameter == 'subgraphAvailabilityOracle') { graphNetwork.subgraphAvailabilityOracle = rewardsManager.subgraphAvailabilityOracle() } diff --git a/subgraph.template.yaml b/subgraph.template.yaml index 09ffddb4..601cff61 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -529,6 +529,31 @@ dataSources: EpochManager.blockNum: EpochManager[{{epochManager}}].blockNum() - event: AllowedLockedVerifierSet(indexed address,bool) handler: handleAllowedLockedVerifierSet + # The IssuanceAllocator owns the split of protocol issuance across targets + # (GIP-0076, deployed by GIP-0088). The RewardsManager is one target and mints + # only its own allocation, so this is the only place a change to the indexing + # reward rate is observable — the RewardsManager itself emits nothing when the + # split moves. Arbitrum only; the address resolves empty elsewhere. + - kind: ethereum/contract + name: IssuanceAllocator + network: {{network}} + source: + address: "{{issuanceAllocator}}" + abi: IssuanceAllocator + startBlock: {{blockNumber}} + mapping: + kind: ethereum/events + apiVersion: 0.0.9 + language: wasm/assemblyscript + file: ./src/mappings/issuanceAllocator.ts + entities: + - GraphNetwork + abis: + - name: IssuanceAllocator + file: ./abis/IssuanceAllocator.json + eventHandlers: + - event: TargetAllocationUpdated(indexed address,uint256,uint256) + handler: handleTargetAllocationUpdated - kind: ethereum/contract name: SubgraphService network: {{network}} diff --git a/yarn.lock b/yarn.lock index 0f6cd4e1..034e1077 100644 --- a/yarn.lock +++ b/yarn.lock @@ -818,10 +818,10 @@ graphql-import-node "^0.0.5" js-yaml "^4.1.0" -"@graphprotocol/address-book@1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@graphprotocol/address-book/-/address-book-1.1.0.tgz" - integrity sha512-38NiutGOWdDIYgB/3kGp/7LxPRCNomipjo1hGlaj6RvyydySooVSmb3A+j84faUso+uhYxt8+9y1EFN7XVnT7Q== +"@graphprotocol/address-book@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@graphprotocol/address-book/-/address-book-1.3.0.tgz#234de18cd3931c309cf0385d46f88cc6d3a14355" + integrity sha512-rkrgBOOxM5WLNsoVrr6Gvg6DYvDes+P13vL5k1/QYWc5/z/G6pt4DBgzR/7wJV5xhIMevCumpg6L7ZuVQwS0ug== "@graphprotocol/common-ts@^2.0.7": version "2.0.11" From 93bff46da62a831eed10d2e077b0fb22d1b97818 Mon Sep 17 00:00:00 2001 From: PaulieB14 <94752445+PaulieB14@users.noreply.github.com> Date: Wed, 2 Sep 2026 21:39:18 -0400 Subject: [PATCH 2/2] fix: use the zero address for IssuanceAllocator where it is not deployed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI caught this: `Path: dataSources > 9 > source > address / Contract address is invalid` on both L1 and L2 test prep. I had used an empty string as the placeholder on networks without an allocator, but graph-cli requires 40 hex characters and every other absent contract in these fixtures — subgraphService, graphPayments, paymentsEscrow, graphTallyCollector — uses the zero address. Verified by running the exact CI steps rather than assuming: testAddressesL1 and testAddressesL2 both now complete mustache + codegen, and the matchstick suite passes 88/88. --- config/localNetworkAddressScript.ts | 2 +- config/testAddressesL1.ts | 2 +- config/testAddressesL2.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/localNetworkAddressScript.ts b/config/localNetworkAddressScript.ts index 7932d785..ee55619c 100644 --- a/config/localNetworkAddressScript.ts +++ b/config/localNetworkAddressScript.ts @@ -10,7 +10,7 @@ import { Addresses } from './addresses.template' let renameAddresses: any = { horizon: horizonAddresses['1337'], subgraphService: subgraphServiceAddresses['1337'], - issuanceAllocator: '', // IssuanceAllocator is only deployed on Arbitrum + issuanceAllocator: '0x0000000000000000000000000000000000000000', // IssuanceAllocator is only deployed on Arbitrum } export let addresses: Addresses = { diff --git a/config/testAddressesL1.ts b/config/testAddressesL1.ts index a5c59b8f..23741222 100644 --- a/config/testAddressesL1.ts +++ b/config/testAddressesL1.ts @@ -44,7 +44,7 @@ export let addresses: Addresses = { l2GraphTokenGateway: '', ethereumDIDRegistry: '0x0000000000000000000000000000000000000000', subgraphService: '0x0000000000000000000000000000000000000000', - issuanceAllocator: '', + issuanceAllocator: '0x0000000000000000000000000000000000000000', graphPayments: '0x0000000000000000000000000000000000000000', paymentsEscrow: '0x0000000000000000000000000000000000000000', graphTallyCollector: '0x0000000000000000000000000000000000000000', diff --git a/config/testAddressesL2.ts b/config/testAddressesL2.ts index 07df5144..10108fbe 100644 --- a/config/testAddressesL2.ts +++ b/config/testAddressesL2.ts @@ -44,7 +44,7 @@ export let addresses: Addresses = { l2GraphTokenGateway: '0x0000000000000000000000000000000000000000', ethereumDIDRegistry: '0x0000000000000000000000000000000000000000', subgraphService: '0x0000000000000000000000000000000000000000', - issuanceAllocator: '', + issuanceAllocator: '0x0000000000000000000000000000000000000000', graphPayments: '0x0000000000000000000000000000000000000000', paymentsEscrow: '0x0000000000000000000000000000000000000000', graphTallyCollector: '0x0000000000000000000000000000000000000000',