From 87cd6d0a4e288ed9fef62ce4edd757ad573dda4b Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Mon, 19 Jan 2026 02:34:44 -0300 Subject: [PATCH 1/7] feat: apply same fix as in network subgraph --- abis/RewardsManagerStitched.json | 25 +++++ src/mappings/rewardsManager.ts | 173 ++++++++++++++++++++----------- subgraph.template.yaml | 2 + 3 files changed, 137 insertions(+), 63 deletions(-) diff --git a/abis/RewardsManagerStitched.json b/abis/RewardsManagerStitched.json index dd92f5e..4c58dd5 100644 --- a/abis/RewardsManagerStitched.json +++ b/abis/RewardsManagerStitched.json @@ -62,6 +62,31 @@ "name": "RewardsAssigned", "type": "event" }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "indexer", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "allocationID", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "HorizonRewardsAssigned", + "type": "event" + }, { "anonymous": false, "inputs": [ diff --git a/src/mappings/rewardsManager.ts b/src/mappings/rewardsManager.ts index 60c83ff..e3879d2 100644 --- a/src/mappings/rewardsManager.ts +++ b/src/mappings/rewardsManager.ts @@ -2,6 +2,7 @@ import { Address, BigInt } from '@graphprotocol/graph-ts' import { Indexer, Allocation, SubgraphDeployment, GraphNetwork } from '../types/schema' import { RewardsAssigned, + HorizonRewardsAssigned, ParameterUpdated, RewardsManagerStitched as RewardsManager, RewardsDenylistUpdated, @@ -21,22 +22,111 @@ import { import { addresses } from '../../config/addresses' export function handleRewardsAssigned(event: RewardsAssigned): void { - let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address) - let indexerID = event.params.indexer.toHexString() + processRewardsAssigned( + event.params.indexer, + event.params.allocationID.toHexString(), + event.params.amount, + event.block.number, + event.block.timestamp, + event.address, + ) +} + +/** + * @dev handleHorizonRewardsAssigned + * - Handles the HorizonRewardsAssigned event emitted after Horizon upgrade + * - Only processes rewards for legacy allocations (created via old Staking contract) + * - New allocations (via SubgraphService) are handled by IndexingRewardsCollected instead + */ +export function handleHorizonRewardsAssigned(event: HorizonRewardsAssigned): void { let allocationID = event.params.allocationID.toHexString() + let allocation = Allocation.load(allocationID) + + // Skip if allocation doesn't exist or is not a legacy allocation + // New allocations have their rewards tracked via IndexingRewardsCollected from SubgraphService + if (allocation == null || !allocation.isLegacy) { + return + } + + processRewardsAssigned( + event.params.indexer, + allocationID, + event.params.amount, + event.block.number, + event.block.timestamp, + event.address, + ) +} + +/** + * @dev handleParameterUpdated + * - handlers updating all parameters + */ +export function handleParameterUpdated(event: ParameterUpdated): void { + let parameter = event.params.param + let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address) + let rewardsManager = RewardsManager.bind(event.address as Address) + + if (parameter == 'issuanceRate') { + graphNetwork.networkGRTIssuance = rewardsManager.issuanceRate() + } else if (parameter == 'issuancePerBlock') { + graphNetwork.networkGRTIssuancePerBlock = rewardsManager.issuancePerBlock() + } else if (parameter == 'subgraphAvailabilityOracle') { + graphNetwork.subgraphAvailabilityOracle = rewardsManager.subgraphAvailabilityOracle() + } + graphNetwork.save() + + getAndUpdateGraphNetworkDailyData(graphNetwork as GraphNetwork, event.block.timestamp) +} + +// export function handleImplementationUpdated(event: ImplementationUpdated): void { +// let graphNetwork = GraphNetwork.load('1') +// let implementations = graphNetwork.rewardsManagerImplementations +// implementations.push(event.params.newImplementation) +// graphNetwork.rewardsManagerImplementations = implementations +// graphNetwork.save() +// } + +export function handleRewardsDenyListUpdated(event: RewardsDenylistUpdated): void { + let subgraphDeployment = SubgraphDeployment.load(event.params.subgraphDeploymentID.toHexString()) + if (subgraphDeployment != null) { + if (event.params.sinceBlock.toI32() == 0) { + subgraphDeployment.deniedAt = 0 + } else { + subgraphDeployment.deniedAt = event.params.sinceBlock.toI32() + } + subgraphDeployment.save() + + getAndUpdateSubgraphDeploymentDailyData(subgraphDeployment as SubgraphDeployment, event.block.timestamp) + } + // We might need to handle the case where the subgraph deployment doesn't exists later +} + +/** + * @dev processRewardsAssigned + * - Common logic for processing rewards assigned events (both legacy RewardsAssigned and HorizonRewardsAssigned) + */ +function processRewardsAssigned( + indexerAddress: Address, + allocationID: string, + amount: BigInt, + blockNumber: BigInt, + blockTimestamp: BigInt, + eventAddress: Address, +): void { + let graphNetwork = createOrLoadGraphNetwork(blockNumber, eventAddress) + let indexerID = indexerAddress.toHexString() // update indexer let indexer = Indexer.load(indexerID)! - indexer.rewardsEarned = indexer.rewardsEarned.plus(event.params.amount) + indexer.rewardsEarned = indexer.rewardsEarned.plus(amount) // If the delegation pool has zero tokens, the contracts don't give away any rewards let indexerIndexingRewards = indexer.delegatedTokens == BigInt.fromI32(0) - ? event.params.amount - : event.params.amount - .times(BigInt.fromI32(indexer.legacyIndexingRewardCut)) - .div(BigInt.fromI32(1000000)) + ? amount + : amount.times(BigInt.fromI32(indexer.legacyIndexingRewardCut)).div(BigInt.fromI32(1000000)) - let delegatorIndexingRewards = event.params.amount.minus(indexerIndexingRewards) + let delegatorIndexingRewards = amount.minus(indexerIndexingRewards) indexer.delegatorIndexingRewards = indexer.delegatorIndexingRewards.plus(delegatorIndexingRewards) indexer.indexerIndexingRewards = indexer.indexerIndexingRewards.plus(indexerIndexingRewards) @@ -51,7 +141,7 @@ export function handleRewardsAssigned(event: RewardsAssigned): void { // update allocation // no status updated, Claimed happens when RebateClaimed, and it is done let allocation = Allocation.load(allocationID)! - allocation.indexingRewards = allocation.indexingRewards.plus(event.params.amount) + allocation.indexingRewards = allocation.indexingRewards.plus(amount) allocation.indexingIndexerRewards = allocation.indexingIndexerRewards.plus(indexerIndexingRewards) allocation.indexingDelegatorRewards = allocation.indexingDelegatorRewards.plus( delegatorIndexingRewards, @@ -59,8 +149,11 @@ export function handleRewardsAssigned(event: RewardsAssigned): void { allocation.save() // Update epoch - let epoch = createOrLoadEpoch(addresses.isL1 ? event.block.number : graphNetwork.currentL1BlockNumber!, graphNetwork) - epoch.totalRewards = epoch.totalRewards.plus(event.params.amount) + let epoch = createOrLoadEpoch( + addresses.isL1 ? blockNumber : graphNetwork.currentL1BlockNumber!, + graphNetwork, + ) + epoch.totalRewards = epoch.totalRewards.plus(amount) epoch.totalIndexerRewards = epoch.totalIndexerRewards.plus(indexerIndexingRewards) epoch.totalDelegatorRewards = epoch.totalDelegatorRewards.plus(delegatorIndexingRewards) epoch.save() @@ -69,12 +162,10 @@ export function handleRewardsAssigned(event: RewardsAssigned): void { let subgraphDeploymentID = allocation.subgraphDeployment let subgraphDeployment = createOrLoadSubgraphDeployment( subgraphDeploymentID, - event.block.timestamp, + blockTimestamp, graphNetwork, ) - subgraphDeployment.indexingRewardAmount = subgraphDeployment.indexingRewardAmount.plus( - event.params.amount, - ) + subgraphDeployment.indexingRewardAmount = subgraphDeployment.indexingRewardAmount.plus(amount) subgraphDeployment.indexingIndexerRewardAmount = subgraphDeployment.indexingIndexerRewardAmount.plus( indexerIndexingRewards, ) @@ -84,7 +175,7 @@ export function handleRewardsAssigned(event: RewardsAssigned): void { subgraphDeployment.save() // update graph network - graphNetwork.totalIndexingRewards = graphNetwork.totalIndexingRewards.plus(event.params.amount) + graphNetwork.totalIndexingRewards = graphNetwork.totalIndexingRewards.plus(amount) graphNetwork.totalIndexingIndexerRewards = graphNetwork.totalIndexingIndexerRewards.plus( indexerIndexingRewards, ) @@ -94,51 +185,7 @@ export function handleRewardsAssigned(event: RewardsAssigned): void { graphNetwork.totalDelegatedTokens = graphNetwork.totalDelegatedTokens.plus(delegatorIndexingRewards) graphNetwork.save() - getAndUpdateIndexerDailyData(indexer as Indexer, event.block.timestamp) - getAndUpdateSubgraphDeploymentDailyData(subgraphDeployment as SubgraphDeployment, event.block.timestamp) - getAndUpdateGraphNetworkDailyData(graphNetwork as GraphNetwork, event.block.timestamp) -} - -/** - * @dev handleParameterUpdated - * - handlers updating all parameters - */ -export function handleParameterUpdated(event: ParameterUpdated): void { - let parameter = event.params.param - let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address) - let rewardsManager = RewardsManager.bind(event.address as Address) - - if (parameter == 'issuanceRate') { - graphNetwork.networkGRTIssuance = rewardsManager.issuanceRate() - } else if (parameter == 'issuancePerBlock') { - graphNetwork.networkGRTIssuancePerBlock = rewardsManager.issuancePerBlock() - } else if (parameter == 'subgraphAvailabilityOracle') { - graphNetwork.subgraphAvailabilityOracle = rewardsManager.subgraphAvailabilityOracle() - } - graphNetwork.save() - - getAndUpdateGraphNetworkDailyData(graphNetwork as GraphNetwork, event.block.timestamp) -} - -// export function handleImplementationUpdated(event: ImplementationUpdated): void { -// let graphNetwork = GraphNetwork.load('1') -// let implementations = graphNetwork.rewardsManagerImplementations -// implementations.push(event.params.newImplementation) -// graphNetwork.rewardsManagerImplementations = implementations -// graphNetwork.save() -// } - -export function handleRewardsDenyListUpdated(event: RewardsDenylistUpdated): void { - let subgraphDeployment = SubgraphDeployment.load(event.params.subgraphDeploymentID.toHexString()) - if (subgraphDeployment != null) { - if (event.params.sinceBlock.toI32() == 0) { - subgraphDeployment.deniedAt = 0 - } else { - subgraphDeployment.deniedAt = event.params.sinceBlock.toI32() - } - subgraphDeployment.save() - - getAndUpdateSubgraphDeploymentDailyData(subgraphDeployment as SubgraphDeployment, event.block.timestamp) - } - // We might need to handle the case where the subgraph deployment doesn't exists later + getAndUpdateIndexerDailyData(indexer as Indexer, blockTimestamp) + getAndUpdateSubgraphDeploymentDailyData(subgraphDeployment as SubgraphDeployment, blockTimestamp) + getAndUpdateGraphNetworkDailyData(graphNetwork as GraphNetwork, blockTimestamp) } diff --git a/subgraph.template.yaml b/subgraph.template.yaml index 2913aca..1cbb05e 100644 --- a/subgraph.template.yaml +++ b/subgraph.template.yaml @@ -756,6 +756,8 @@ dataSources: eventHandlers: - event: RewardsAssigned(indexed address,indexed address,uint256,uint256) handler: handleRewardsAssigned + - event: HorizonRewardsAssigned(indexed address,indexed address,uint256) + handler: handleHorizonRewardsAssigned - event: RewardsDenylistUpdated(indexed bytes32,uint256) handler: handleRewardsDenyListUpdated # - event: ImplementationUpdated(address,address) From 1824c703d7af584e4f1ea700c399e40a4bed83bc Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Mon, 19 Jan 2026 02:53:16 -0300 Subject: [PATCH 2/7] feat: align daily data IDs to use ID type --- schema.graphql | 16 ++++++++-------- src/mappings/helpers/daily-data.ts | 30 +++++++++++------------------- 2 files changed, 19 insertions(+), 27 deletions(-) diff --git a/schema.graphql b/schema.graphql index 7957aaf..de1466d 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1806,7 +1806,7 @@ Analytics daily snapshots """ type IndexerDailyData @entity(immutable: false) { "-" - id: Bytes! + id: ID! "Timestamp for the start of the day that this entity represents. UTC+0" dayStart: BigInt! @@ -1945,7 +1945,7 @@ type IndexerDailyData @entity(immutable: false) { type DelegatorDailyData @entity(immutable: false) { "-" - id: Bytes! + id: ID! "Timestamp for the start of the day that this entity represents. UTC+0" dayStart: BigInt! @@ -1986,7 +1986,7 @@ type DelegatorDailyData @entity(immutable: false) { type DelegatedStakeDailyData @entity(immutable: false) { "--" - id: Bytes! + id: ID! "Timestamp for the start of the day that this entity represents. UTC+0" dayStart: BigInt! @@ -2051,7 +2051,7 @@ type DelegatedStakeDailyData @entity(immutable: false) { type DelegatorDelegatedStakeDailyRelation @entity(immutable: false) { "[DEPRECATED] Auxiliary entity retained for compatibility." - id: Bytes! + id: ID! "Timestamp for the start of the day that this entity represents. UTC+0" dayStart: BigInt! @@ -2077,7 +2077,7 @@ type DelegatorDelegatedStakeDailyRelation @entity(immutable: false) { type SubgraphDeploymentDailyData @entity(immutable: false) { "-" - id: Bytes! + id: ID! "Timestamp for the start of the day that this entity represents. UTC+0" dayStart: BigInt! @@ -2145,7 +2145,7 @@ type SubgraphDeploymentDailyData @entity(immutable: false) { type GraphNetworkDailyData @entity(immutable: false) { "-" - id: Bytes! + id: ID! "Timestamp for the start of the day that this entity represents. UTC+0" dayStart: BigInt! @@ -2323,7 +2323,7 @@ type GraphNetworkDailyData @entity(immutable: false) { type ProvisionDailyData @entity(immutable: false) { "-" - id: Bytes! + id: ID! "Timestamp for the start of the day that this entity represents. UTC+0" dayStart: BigInt! @@ -2435,7 +2435,7 @@ type ProvisionDailyData @entity(immutable: false) { type DataServiceDailyData @entity(immutable: false) { "-" - id: Bytes! + id: ID! "Timestamp for the start of the day that this entity represents. UTC+0" dayStart: BigInt! diff --git a/src/mappings/helpers/daily-data.ts b/src/mappings/helpers/daily-data.ts index 254cb2a..5ef8c89 100644 --- a/src/mappings/helpers/daily-data.ts +++ b/src/mappings/helpers/daily-data.ts @@ -1,4 +1,4 @@ -import { BigInt, BigDecimal, Bytes, ByteArray } from '@graphprotocol/graph-ts' +import { BigInt, BigDecimal } from '@graphprotocol/graph-ts' import { GraphNetwork, GraphNetworkDailyData, @@ -15,13 +15,13 @@ import { DataService, DataServiceDailyData, } from '../../types/schema' +import { joinID } from './helpers' const SECONDS_PER_DAY = 86400 const LAUNCH_DAY = 18613 // 1608163200 / 86400 const BIGINT_ZERO = BigInt.fromI32(0) const BIGDECIMAL_ZERO = BigDecimal.fromString('0') -const bytesSeparator = Bytes.fromHexString('0xABCDEF') function dayStart(timestamp: BigInt): BigInt { let seconds = timestamp.toI32() @@ -36,16 +36,8 @@ function toDayNumber(timestamp: BigInt): i32 { return timestamp.toI32() / SECONDS_PER_DAY - LAUNCH_DAY } -function asBytesFromDay(dayNumber: i32): Bytes { - return Bytes.fromI32(dayNumber) -} - -function bytesFromString(value: string): Bytes { - return changetype(ByteArray.fromUTF8(value)) -} - -function compoundId(prefix: string, dayBytes: Bytes): Bytes { - return bytesFromString(prefix).concat(bytesSeparator).concat(dayBytes) +function dailyDataId(prefix: string, dayNumber: i32): string { + return joinID([prefix, dayNumber.toString()]) } export function getAndUpdateGraphNetworkDailyData( @@ -53,7 +45,7 @@ export function getAndUpdateGraphNetworkDailyData( timestamp: BigInt, ): GraphNetworkDailyData { let dayNumber = toDayNumber(timestamp) - let id = compoundId(entity.id, asBytesFromDay(dayNumber)) + let id = dailyDataId(entity.id, dayNumber) let dailyData = new GraphNetworkDailyData(id) dailyData.dayStart = dayStart(timestamp) @@ -130,7 +122,7 @@ export function getAndUpdateIndexerDailyData( timestamp: BigInt, ): IndexerDailyData { let dayNumber = toDayNumber(timestamp) - let id = compoundId(entity.id, asBytesFromDay(dayNumber)) + let id = dailyDataId(entity.id, dayNumber) let dailyData = new IndexerDailyData(id) dailyData.dayStart = dayStart(timestamp) @@ -192,7 +184,7 @@ export function getAndUpdateDelegatorDailyData( timestamp: BigInt, ): DelegatorDailyData { let dayNumber = toDayNumber(timestamp) - let id = compoundId(entity.id, asBytesFromDay(dayNumber)) + let id = dailyDataId(entity.id, dayNumber) let dailyData = new DelegatorDailyData(id) dailyData.dayStart = dayStart(timestamp) @@ -219,7 +211,7 @@ export function getAndUpdateDelegatedStakeDailyData( timestamp: BigInt, ): DelegatedStakeDailyData { let dayNumber = toDayNumber(timestamp) - let id = compoundId(entity.id, asBytesFromDay(dayNumber)) + let id = dailyDataId(entity.id, dayNumber) let dailyData = new DelegatedStakeDailyData(id) dailyData.dayStart = dayStart(timestamp) @@ -254,7 +246,7 @@ export function getAndUpdateSubgraphDeploymentDailyData( timestamp: BigInt, ): SubgraphDeploymentDailyData { let dayNumber = toDayNumber(timestamp) - let id = compoundId(entity.id, asBytesFromDay(dayNumber)) + let id = dailyDataId(entity.id, dayNumber) let dailyData = new SubgraphDeploymentDailyData(id) dailyData.dayStart = dayStart(timestamp) @@ -291,7 +283,7 @@ export function getAndUpdateProvisionDailyData( timestamp: BigInt, ): ProvisionDailyData { let dayNumber = toDayNumber(timestamp) - let id = compoundId(entity.id, asBytesFromDay(dayNumber)) + let id = dailyDataId(entity.id, dayNumber) let dailyData = new ProvisionDailyData(id) dailyData.dayStart = dayStart(timestamp) @@ -343,7 +335,7 @@ export function getAndUpdateDataServiceDailyData( timestamp: BigInt, ): DataServiceDailyData { let dayNumber = toDayNumber(timestamp) - let id = compoundId(entity.id, asBytesFromDay(dayNumber)) + let id = dailyDataId(entity.id, dayNumber) let dailyData = new DataServiceDailyData(id) dailyData.dayStart = dayStart(timestamp) From d72e470a49daab1f0ac733afa8870962e7dd2565 Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Thu, 29 Jan 2026 03:53:39 -0300 Subject: [PATCH 3/7] feat: calculateCapacitites, DelegatedStake ID refactor --- schema.graphql | 10 ++++++++- src/mappings/helpers/daily-data.ts | 1 + src/mappings/helpers/helpers.ts | 36 ++++++++++++++++++++++++++++-- src/mappings/horizonStaking.ts | 16 ++++++++----- src/mappings/l1staking.ts | 20 ++++++++--------- src/mappings/rewardsManager.ts | 4 +++- src/mappings/staking.ts | 14 +++++++++--- 7 files changed, 79 insertions(+), 22 deletions(-) diff --git a/schema.graphql b/schema.graphql index de1466d..24ab19e 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1245,7 +1245,7 @@ type Delegator @entity(immutable: false) { Delegator stake for a single Indexer """ type DelegatedStake @entity(immutable: false) { - "Concatenation of Delegator address and Indexer address" + "Concatenation of Delegator address and Provision ID. For compatibility purposes for Horizon, Legacy entities will have the SubgraphService address hardcoded such that is the provision address" id: ID! "Indexer the stake is delegated to" indexer: Indexer! @@ -1305,6 +1305,8 @@ type DelegatedStake @entity(immutable: false) { idOnL2: String "ID of the delegation on L1. Null if it's not transferred" idOnL1: String + "Whether the delegation is still legacy/hasn't had any Horizon native interactions yet" + isLegacy: Boolean! } """ @@ -2012,6 +2014,9 @@ type DelegatedStakeDailyData @entity(immutable: false) { "Data service for this delegation, if Horizon delegation" dataService: DataService + "Whether the delegation is still legacy/hasn't had any Horizon native interactions yet" + isLegacy: Boolean! + "[CURRENT] Amount of staked tokens for this day and this indexer" stakedTokens: BigInt! @@ -2049,6 +2054,9 @@ type DelegatedStakeDailyData @entity(immutable: false) { stakedTokensTransferredToL2: BigInt! } +""" +[DEPRECATED] Auxiliary entity retained for compatibility +""" type DelegatorDelegatedStakeDailyRelation @entity(immutable: false) { "[DEPRECATED] Auxiliary entity retained for compatibility." id: ID! diff --git a/src/mappings/helpers/daily-data.ts b/src/mappings/helpers/daily-data.ts index 5ef8c89..5c508a2 100644 --- a/src/mappings/helpers/daily-data.ts +++ b/src/mappings/helpers/daily-data.ts @@ -222,6 +222,7 @@ export function getAndUpdateDelegatedStakeDailyData( dailyData.indexer = entity.indexer dailyData.provision = entity.provision dailyData.dataService = entity.dataService + dailyData.isLegacy = entity.isLegacy dailyData.stakedTokens = entity.stakedTokens dailyData.unstakedTokens = entity.unstakedTokens diff --git a/src/mappings/helpers/helpers.ts b/src/mappings/helpers/helpers.ts index 19e90dc..41e76c4 100644 --- a/src/mappings/helpers/helpers.ts +++ b/src/mappings/helpers/helpers.ts @@ -369,13 +369,36 @@ export function createOrLoadDelegator(delegatorAddress: Bytes, timestamp: BigInt return delegator as Delegator } +export function getHorizonDelegatedStakeID(delegator: string, indexer: string, dataService: string): string { + return joinID([delegator, indexer, dataService]) +} + +export function getHorizonDelegatedStakeIDFromLegacy(delegator: string, indexer: string): string { + return joinID([delegator, indexer, addresses.subgraphService]) +} + +export function getHorizonDelegatedStake(delegator: string, indexer: string, dataService: string): DelegatedStake { + let provisionId = joinID([indexer, dataService]) + let id = getHorizonDelegatedStakeID(delegator, indexer, dataService) + let delegatedStake = DelegatedStake.load(id)! + // In case the delegation was created before Horizon, once it's get loaded on a Horizon event, we add the missing fields. + // This can happen due to keeping IDs compatible across + if (delegatedStake.dataService == null) { + delegatedStake.dataService = dataService + delegatedStake.provision = provisionId + delegatedStake.isLegacy = false + } + return delegatedStake as DelegatedStake +} + export function createOrLoadDelegatedStake( delegator: string, indexer: string, timestamp: i32, graphNetwork: GraphNetwork, ): DelegatedStake { - let id = joinID([delegator, indexer]) + // Hardcoding the subgraph service to the id, so that legacy and Horizon delegatedStake entities share the same id. + let id = getHorizonDelegatedStakeIDFromLegacy(delegator, indexer) let delegatedStake = DelegatedStake.load(id) if (delegatedStake == null) { delegatedStake = new DelegatedStake(id) @@ -399,6 +422,7 @@ export function createOrLoadDelegatedStake( delegatedStake.originalDelegation = BigDecimal.fromString('0') delegatedStake.currentDelegation = BigDecimal.fromString('0') delegatedStake.createdAt = timestamp + delegatedStake.isLegacy = true delegatedStake.save() @@ -420,7 +444,7 @@ export function createOrLoadDelegatedStakeForProvision( graphNetwork: GraphNetwork, ): DelegatedStake { let provisionId = joinID([indexer, dataService]) - let id = joinID([delegator, provisionId]) + let id = getHorizonDelegatedStakeID(delegator, indexer, dataService) let delegatedStake = DelegatedStake.load(id) if (delegatedStake == null) { delegatedStake = new DelegatedStake(id) @@ -446,6 +470,7 @@ export function createOrLoadDelegatedStakeForProvision( delegatedStake.originalDelegation = BigDecimal.fromString('0') delegatedStake.currentDelegation = BigDecimal.fromString('0') delegatedStake.createdAt = timestamp + delegatedStake.isLegacy = false delegatedStake.save() @@ -456,6 +481,13 @@ export function createOrLoadDelegatedStakeForProvision( graphNetwork.delegationCount = graphNetwork.delegationCount + 1 graphNetwork.save() } + // In case the delegation was created before Horizon, once it's get loaded on a Horizon event, we add the missing fields. + // This can happen due to keeping IDs compatible across + if (delegatedStake.dataService == null) { + delegatedStake.dataService = dataService + delegatedStake.provision = provisionId + delegatedStake.isLegacy = false + } return delegatedStake as DelegatedStake } diff --git a/src/mappings/horizonStaking.ts b/src/mappings/horizonStaking.ts index fced4ad..e3d2947 100644 --- a/src/mappings/horizonStaking.ts +++ b/src/mappings/horizonStaking.ts @@ -2,7 +2,7 @@ import { BigInt, BigDecimal } from '@graphprotocol/graph-ts' import { addresses } from '../../config/addresses' import { AllowedLockedVerifierSet, DelegatedTokensWithdrawn, DelegationFeeCutSet, DelegationSlashed, DelegationSlashingEnabled, HorizonStakeDeposited, HorizonStakeLocked, HorizonStakeWithdrawn, MaxThawingPeriodSet, OperatorSet, StakeDelegatedWithdrawn, ThawingPeriodCleared, TokensDelegated, TokensDeprovisioned, TokensToDelegationPoolAdded, TokensUndelegated } from '../types/HorizonStaking/HorizonStaking' import { DataService, DelegatedStake, Delegator, GraphNetwork, Indexer, Provision, ThawRequest } from '../types/schema' -import { calculateCapacities, createOrLoadDataService, createOrLoadDelegatedStakeForProvision, createOrLoadDelegator, createOrLoadEpoch, createOrLoadGraphAccount, createOrLoadGraphNetwork, createOrLoadHorizonOperator, createOrLoadIndexer, createOrLoadProvision, joinID, loadGraphNetwork, updateAdvancedIndexerMetrics, updateAdvancedProvisionMetrics, updateDelegationExchangeRate, updateDelegationExchangeRateForProvision } from './helpers/helpers' +import { calculateCapacities, createOrLoadDataService, createOrLoadDelegatedStakeForProvision, createOrLoadDelegator, createOrLoadEpoch, createOrLoadGraphAccount, createOrLoadGraphNetwork, createOrLoadHorizonOperator, createOrLoadIndexer, createOrLoadProvision, getHorizonDelegatedStake, getHorizonDelegatedStakeID, loadGraphNetwork, updateAdvancedIndexerMetrics, updateAdvancedProvisionMetrics, updateDelegationExchangeRate, updateDelegationExchangeRateForProvision } from './helpers/helpers' import { getAndUpdateGraphNetworkDailyData, getAndUpdateIndexerDailyData, @@ -582,8 +582,11 @@ export function handleTokensUndelegated(event: TokensUndelegated): void { // update delegated stake let delegatorID = event.params.delegator.toHexString() - let id = joinID([delegatorID, provision.id]) - let delegatedStake = DelegatedStake.load(id)! + let delegatedStake = getHorizonDelegatedStake( + event.params.delegator.toHexString(), + event.params.serviceProvider.toHexString(), + event.params.verifier.toHexString() + ) let isStakeBecomingInactive = !delegatedStake.shareAmount.isZero() && delegatedStake.shareAmount == event.params.shares @@ -663,8 +666,11 @@ export function handleDelegatedTokensWithdrawn(event: DelegatedTokensWithdrawn): // update delegated stake let delegatorID = event.params.delegator.toHexString() - let id = joinID([delegatorID, provision.id]) - let delegatedStake = DelegatedStake.load(id)! + let delegatedStake = getHorizonDelegatedStake( + event.params.delegator.toHexString(), + event.params.serviceProvider.toHexString(), + event.params.verifier.toHexString() + ) let delegator = Delegator.load(delegatorID)! delegator.lockedTokens = delegator.lockedTokens.minus(event.params.tokens) delegator.save() diff --git a/src/mappings/l1staking.ts b/src/mappings/l1staking.ts index 5ab4c13..0a6ddea 100644 --- a/src/mappings/l1staking.ts +++ b/src/mappings/l1staking.ts @@ -6,7 +6,7 @@ import { } from '../types/L1Staking/L1Staking' import { Indexer, DelegatedStake, GraphNetwork, Delegator } from '../types/schema' -import { calculateCapacities, createOrLoadGraphNetwork, joinID, updateLegacyAdvancedIndexerMetrics, updateDelegationExchangeRate, loadGraphNetwork } from './helpers/helpers' +import { calculateCapacities, createOrLoadGraphNetwork, updateLegacyAdvancedIndexerMetrics, updateDelegationExchangeRate, loadGraphNetwork, getHorizonDelegatedStakeIDFromLegacy } from './helpers/helpers' import { getAndUpdateGraphNetworkDailyData, getAndUpdateIndexerDailyData, @@ -63,14 +63,14 @@ export function handleIndexerStakeTransferredToL2(event: IndexerStakeTransferred ); */ export function handleDelegationTransferredToL2(event: DelegationTransferredToL2): void { - let delegationID = joinID([ + let delegationID = getHorizonDelegatedStakeIDFromLegacy( event.params.delegator.toHexString(), - event.params.indexer.toHexString(), - ]) - let delegationIDL2 = joinID([ + event.params.indexer.toHexString() + ) + let delegationIDL2 = getHorizonDelegatedStakeIDFromLegacy( event.params.l2Delegator.toHexString(), - event.params.l2Indexer.toHexString(), - ]) + event.params.l2Indexer.toHexString() + ) let delegation = DelegatedStake.load(delegationID)! let delegatorSharesBefore = delegation.shareAmount; delegation.stakedTokensTransferredToL2 = delegation.stakedTokensTransferredToL2.plus( @@ -125,10 +125,10 @@ export function handleStakeDelegatedUnlockedDueToL2Transfer( event: StakeDelegatedUnlockedDueToL2Transfer, ): void { let graphNetwork = loadGraphNetwork() - let delegationID = joinID([ + let delegationID = getHorizonDelegatedStakeIDFromLegacy( event.params.delegator.toHexString(), - event.params.indexer.toHexString(), - ]) + event.params.indexer.toHexString() + ) let delegation = DelegatedStake.load(delegationID)! delegation.lockedUntil = graphNetwork.currentEpoch delegation.legacyLockedUntil = graphNetwork.currentEpoch diff --git a/src/mappings/rewardsManager.ts b/src/mappings/rewardsManager.ts index e3879d2..ee48a4b 100644 --- a/src/mappings/rewardsManager.ts +++ b/src/mappings/rewardsManager.ts @@ -12,7 +12,8 @@ import { createOrLoadEpoch, updateLegacyAdvancedIndexerMetrics, updateDelegationExchangeRate, - createOrLoadGraphNetwork + createOrLoadGraphNetwork, + calculateCapacities } from './helpers/helpers' import { getAndUpdateGraphNetworkDailyData, @@ -136,6 +137,7 @@ function processRewardsAssigned( indexer = updateDelegationExchangeRate(indexer as Indexer) } indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) + indexer = calculateCapacities(indexer as Indexer) indexer.save() // update allocation diff --git a/src/mappings/staking.ts b/src/mappings/staking.ts index 2942301..5f529f2 100644 --- a/src/mappings/staking.ts +++ b/src/mappings/staking.ts @@ -37,7 +37,6 @@ import { createOrLoadLegacyIndexer, createOrLoadPool, createOrLoadEpoch, - joinID, createOrLoadDelegator, createOrLoadDelegatedStake, createOrLoadGraphAccount, @@ -50,6 +49,7 @@ import { createOrLoadIndexerQueryFeePaymentAggregation, createOrLoadPaymentSource, loadGraphNetwork, + getHorizonDelegatedStakeIDFromLegacy, } from './helpers/helpers' import { getAndUpdateGraphNetworkDailyData, @@ -336,7 +336,10 @@ export function handleStakeDelegatedLocked(event: StakeDelegatedLocked): void { // update delegated stake let delegatorID = event.params.delegator.toHexString() - let id = joinID([delegatorID, indexerID]) + let id = getHorizonDelegatedStakeIDFromLegacy( + event.params.delegator.toHexString(), + event.params.indexer.toHexString() + ) let delegatedStake = DelegatedStake.load(id)! let isStakeBecomingInactive = @@ -395,7 +398,10 @@ export function handleStakeDelegatedLocked(event: StakeDelegatedLocked): void { export function handleStakeDelegatedWithdrawn(event: StakeDelegatedWithdrawn): void { let indexerID = event.params.indexer.toHexString() let delegatorID = event.params.delegator.toHexString() - let id = joinID([delegatorID, indexerID]) + let id = getHorizonDelegatedStakeIDFromLegacy( + event.params.delegator.toHexString(), + event.params.indexer.toHexString() + ) let delegatedStake = DelegatedStake.load(id)! let lockedBefore = delegatedStake.lockedTokens if (!lockedBefore.isZero()) { @@ -764,6 +770,7 @@ export function handleRebateClaimed(event: RebateClaimed): void { indexer = updateDelegationExchangeRate(indexer as Indexer) } indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) + indexer = calculateCapacities(indexer as Indexer) indexer.save() // update allocation let allocation = Allocation.load(allocationID)! @@ -837,6 +844,7 @@ export function handleRebateCollected(event: RebateCollected): void { indexer = updateDelegationExchangeRate(indexer as Indexer) } indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) + indexer = calculateCapacities(indexer as Indexer) indexer.save() // Replicate for payment source specific aggregation From 0ddb66ec6dc5d14fb4be80f3142c288d49fe79d0 Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Sun, 1 Feb 2026 21:55:19 -0300 Subject: [PATCH 4/7] fix: daily data issues --- schema.graphql | 30 ++++++++++++ src/mappings/helpers/daily-data.ts | 13 +++++ src/mappings/helpers/helpers.ts | 2 +- src/mappings/horizonStaking.ts | 79 ++++++++++++++++-------------- src/mappings/l1staking.ts | 2 + src/mappings/staking.ts | 1 + 6 files changed, 89 insertions(+), 38 deletions(-) diff --git a/schema.graphql b/schema.graphql index 24ab19e..7704883 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1943,6 +1943,9 @@ type IndexerDailyData @entity(immutable: false) { "[CURRENT] Latest thawing until timestamp for the indexer" thawingUntil: BigInt! + + "DelegatedStake daily snapshots referencing this Indexer snapshot" + delegatedStakesDailyData: [DelegatedStakeDailyData!]! @derivedFrom(field: "indexerDailyData") } type DelegatorDailyData @entity(immutable: false) { @@ -1984,6 +1987,9 @@ type DelegatorDailyData @entity(immutable: false) { "[CURRENT] Amount of active DelegatedStake entities this Delegator had at this point in time" activeStakesCount: Int! + + "DelegatedStake daily snapshots referencing this Delegator snapshot" + delegatedStakesDailyData: [DelegatedStakeDailyData!]! @derivedFrom(field: "delegatorDailyData") } type DelegatedStakeDailyData @entity(immutable: false) { @@ -2014,6 +2020,18 @@ type DelegatedStakeDailyData @entity(immutable: false) { "Data service for this delegation, if Horizon delegation" dataService: DataService + "Delegator daily snapshot for this day, if available" + delegatorDailyData: DelegatorDailyData + + "Indexer daily snapshot for this day, if available" + indexerDailyData: IndexerDailyData + + "Provision daily snapshot for this day, if available" + provisionDailyData: ProvisionDailyData + + "DataService daily snapshot for this day, if available" + dataServiceDailyData: DataServiceDailyData + "Whether the delegation is still legacy/hasn't had any Horizon native interactions yet" isLegacy: Boolean! @@ -2348,6 +2366,15 @@ type ProvisionDailyData @entity(immutable: false) { indexer: Indexer! dataService: DataService! + "Indexer daily snapshot for this day, if available" + indexerDailyData: IndexerDailyData + + "DataService daily snapshot for this day, if available" + dataServiceDailyData: DataServiceDailyData + + "DelegatedStake daily snapshots referencing this Provision snapshot" + delegatedStakesDailyData: [DelegatedStakeDailyData!]! @derivedFrom(field: "provisionDailyData") + "[CURRENT] Tokens provisioned on this provision" tokensProvisioned: BigInt! @@ -2457,6 +2484,9 @@ type DataServiceDailyData @entity(immutable: false) { "Original entity that the daily data entity tracks" dataService: DataService! + "Provision daily snapshots referencing this DataService snapshot" + provisionsDailyData: [ProvisionDailyData!]! @derivedFrom(field: "dataServiceDailyData") + "[CURRENT] Total tokens provisioned for this data service" totalTokensProvisioned: BigInt! diff --git a/src/mappings/helpers/daily-data.ts b/src/mappings/helpers/daily-data.ts index 5c508a2..2a5335f 100644 --- a/src/mappings/helpers/daily-data.ts +++ b/src/mappings/helpers/daily-data.ts @@ -224,6 +224,16 @@ export function getAndUpdateDelegatedStakeDailyData( dailyData.dataService = entity.dataService dailyData.isLegacy = entity.isLegacy + dailyData.delegatorDailyData = dailyDataId(entity.delegator, dayNumber) + dailyData.indexerDailyData = dailyDataId(entity.indexer, dayNumber) + if (entity.provision != null) { + dailyData.provisionDailyData = dailyDataId(entity.provision!, dayNumber) + } + + if (entity.dataService != null) { + dailyData.dataServiceDailyData = dailyDataId(entity.dataService!, dayNumber) + } + dailyData.stakedTokens = entity.stakedTokens dailyData.unstakedTokens = entity.unstakedTokens dailyData.lockedTokens = entity.lockedTokens @@ -294,6 +304,9 @@ export function getAndUpdateProvisionDailyData( dailyData.indexer = entity.indexer dailyData.dataService = entity.dataService + dailyData.indexerDailyData = dailyDataId(entity.indexer, dayNumber) + dailyData.dataServiceDailyData = dailyDataId(entity.dataService, dayNumber) + dailyData.tokensProvisioned = entity.tokensProvisioned dailyData.tokensThawing = entity.tokensThawing dailyData.tokensAllocated = entity.tokensAllocated diff --git a/src/mappings/helpers/helpers.ts b/src/mappings/helpers/helpers.ts index 41e76c4..38c18cf 100644 --- a/src/mappings/helpers/helpers.ts +++ b/src/mappings/helpers/helpers.ts @@ -374,7 +374,7 @@ export function getHorizonDelegatedStakeID(delegator: string, indexer: string, d } export function getHorizonDelegatedStakeIDFromLegacy(delegator: string, indexer: string): string { - return joinID([delegator, indexer, addresses.subgraphService]) + return joinID([delegator, indexer, addresses.subgraphService.toLowerCase()]) } export function getHorizonDelegatedStake(delegator: string, indexer: string, dataService: string): DelegatedStake { diff --git a/src/mappings/horizonStaking.ts b/src/mappings/horizonStaking.ts index e3d2947..7a08c3d 100644 --- a/src/mappings/horizonStaking.ts +++ b/src/mappings/horizonStaking.ts @@ -340,44 +340,48 @@ export function handleThawRequestCreated(event: ThawRequestCreated): void { request.save() if (request.type == 'Provision') { - // update latest thawingUntil for provision and indexer - let provision = createOrLoadProvision( - event.params.serviceProvider, - event.params.verifier, - event.block.timestamp, - ) - provision.thawingUntil = - event.params.thawingUntil > provision.thawingUntil - ? event.params.thawingUntil - : provision.thawingUntil - provision.save() - - indexer.thawingUntil = - event.params.thawingUntil > indexer.thawingUntil - ? event.params.thawingUntil - : indexer.thawingUntil - indexer = calculateCapacities(indexer as Indexer) - indexer.save() - - getAndUpdateProvisionDailyData(provision as Provision, event.block.timestamp) - getAndUpdateIndexerDailyData(indexer as Indexer, event.block.timestamp) + // update latest thawingUntil for provision and indexer + let provision = createOrLoadProvision( + event.params.serviceProvider, + event.params.verifier, + event.block.timestamp, + ) + provision.thawingUntil = + event.params.thawingUntil > provision.thawingUntil + ? event.params.thawingUntil + : provision.thawingUntil + provision.save() + + indexer.thawingUntil = + event.params.thawingUntil > indexer.thawingUntil + ? event.params.thawingUntil + : indexer.thawingUntil + indexer = calculateCapacities(indexer as Indexer) + indexer.save() + + getAndUpdateProvisionDailyData(provision as Provision, event.block.timestamp) + getAndUpdateIndexerDailyData(indexer as Indexer, event.block.timestamp) + getAndUpdateDataServiceDailyData(dataService as DataService, event.block.timestamp) } else { - // update delegated stake for delegation thaw request - let delegatedStake = createOrLoadDelegatedStakeForProvision( - owner.id, - indexer.id, - dataService.id, - event.block.timestamp.toI32(), - graphNetwork, - ) - - delegatedStake.lockedUntil = - event.params.thawingUntil.toI32() > delegatedStake.lockedUntil - ? event.params.thawingUntil.toI32() - : delegatedStake.lockedUntil - delegatedStake.save() - - getAndUpdateDelegatedStakeDailyData(delegatedStake as DelegatedStake, event.block.timestamp) + // update delegated stake for delegation thaw request + let delegatedStake = createOrLoadDelegatedStakeForProvision( + owner.id, + indexer.id, + dataService.id, + event.block.timestamp.toI32(), + graphNetwork, + ) + + delegatedStake.lockedUntil = + event.params.thawingUntil.toI32() > delegatedStake.lockedUntil + ? event.params.thawingUntil.toI32() + : delegatedStake.lockedUntil + delegatedStake.save() + + getAndUpdateDelegatedStakeDailyData(delegatedStake as DelegatedStake, event.block.timestamp) + getAndUpdateIndexerDailyData(indexer as Indexer, event.block.timestamp) + getAndUpdateDataServiceDailyData(dataService as DataService, event.block.timestamp) + getAndUpdateGraphNetworkDailyData(graphNetwork as GraphNetwork, event.block.timestamp) } } @@ -680,6 +684,7 @@ export function handleDelegatedTokensWithdrawn(event: DelegatedTokensWithdrawn): getAndUpdateProvisionDailyData(provision as Provision, event.block.timestamp) getAndUpdateIndexerDailyData(indexer as Indexer, event.block.timestamp) + getAndUpdateDelegatorDailyData(delegator as Delegator, event.block.timestamp) getAndUpdateDelegatedStakeDailyData(delegatedStake as DelegatedStake, event.block.timestamp) } diff --git a/src/mappings/l1staking.ts b/src/mappings/l1staking.ts index 0a6ddea..6ef4d3f 100644 --- a/src/mappings/l1staking.ts +++ b/src/mappings/l1staking.ts @@ -11,6 +11,7 @@ import { getAndUpdateGraphNetworkDailyData, getAndUpdateIndexerDailyData, getAndUpdateDelegatedStakeDailyData, + getAndUpdateDelegatorDailyData, } from './helpers/daily-data' /* @@ -103,6 +104,7 @@ export function handleDelegationTransferredToL2(event: DelegationTransferredToL2 delegator.stakedTokens = delegator.stakedTokens.minus(event.params.transferredDelegationTokens) delegator.totalUnstakedTokens = delegator.totalUnstakedTokens.plus(event.params.transferredDelegationTokens) delegator.save() + getAndUpdateDelegatorDailyData(delegator as Delegator, event.block.timestamp) } // upgrade graph network diff --git a/src/mappings/staking.ts b/src/mappings/staking.ts index 5f529f2..8674938 100644 --- a/src/mappings/staking.ts +++ b/src/mappings/staking.ts @@ -408,6 +408,7 @@ export function handleStakeDelegatedWithdrawn(event: StakeDelegatedWithdrawn): v let delegator = Delegator.load(delegatorID)! delegator.lockedTokens = delegator.lockedTokens.minus(lockedBefore) delegator.save() + getAndUpdateDelegatorDailyData(delegator as Delegator, event.block.timestamp) } delegatedStake.lockedTokens = BigInt.fromI32(0) delegatedStake.legacyLockedTokens = BigInt.fromI32(0) From 1cd25499000a0181d7c681e1a90e5f8de183cfff Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Fri, 6 Feb 2026 16:00:48 -0300 Subject: [PATCH 5/7] feat: delegation pool initialization on provision --- src/mappings/helpers/helpers.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/mappings/helpers/helpers.ts b/src/mappings/helpers/helpers.ts index 38c18cf..51f49ba 100644 --- a/src/mappings/helpers/helpers.ts +++ b/src/mappings/helpers/helpers.ts @@ -245,9 +245,17 @@ export function createOrLoadProvision(indexerAddress: Bytes, verifierAddress: By provision.queryFeesCollected = BigInt.fromI32(0) provision.indexerQueryFees = BigInt.fromI32(0) provision.delegatorQueryFees = BigInt.fromI32(0) - provision.delegatedTokens = BigInt.fromI32(0) - provision.delegatedThawingTokens = BigInt.fromI32(0) - provision.delegatorShares = BigInt.fromI32(0) + // Initialize from indexer if verifier == Subgraph Service + if(verifierAddress.toHexString() == addresses.subgraphService.toLowerCase()) { + let indexer = Indexer.load(indexerAddress.toHexString()) + provision.delegatedTokens = indexer != null ? indexer.delegatedTokens : BigInt.fromI32(0) + provision.delegatedThawingTokens = indexer != null ? indexer.delegatedThawingTokens : BigInt.fromI32(0) + provision.delegatorShares = indexer != null ? indexer.delegatorShares : BigInt.fromI32(0) + } else { + provision.delegatedTokens = BigInt.fromI32(0) + provision.delegatedThawingTokens = BigInt.fromI32(0) + provision.delegatorShares = BigInt.fromI32(0) + } provision.delegationExchangeRate = BigInt.fromI32(0).toBigDecimal() provision.thawingUntil = BigInt.fromI32(0) provision.ownStakeRatio = BigInt.fromI32(0).toBigDecimal() From 2ffe6f20fe96a8a13efa556704aea19866289392 Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Tue, 17 Feb 2026 13:31:28 -0300 Subject: [PATCH 6/7] fix: exchange rate desync and missing updates on rewardsManager --- src/mappings/helpers/helpers.ts | 3 ++- src/mappings/rewardsManager.ts | 22 ++++++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/mappings/helpers/helpers.ts b/src/mappings/helpers/helpers.ts index 51f49ba..fbb0772 100644 --- a/src/mappings/helpers/helpers.ts +++ b/src/mappings/helpers/helpers.ts @@ -251,12 +251,13 @@ export function createOrLoadProvision(indexerAddress: Bytes, verifierAddress: By provision.delegatedTokens = indexer != null ? indexer.delegatedTokens : BigInt.fromI32(0) provision.delegatedThawingTokens = indexer != null ? indexer.delegatedThawingTokens : BigInt.fromI32(0) provision.delegatorShares = indexer != null ? indexer.delegatorShares : BigInt.fromI32(0) + provision.delegationExchangeRate = indexer != null ? indexer.delegationExchangeRate : BigInt.fromI32(0).toBigDecimal() } else { provision.delegatedTokens = BigInt.fromI32(0) provision.delegatedThawingTokens = BigInt.fromI32(0) provision.delegatorShares = BigInt.fromI32(0) + provision.delegationExchangeRate = BigInt.fromI32(0).toBigDecimal() } - provision.delegationExchangeRate = BigInt.fromI32(0).toBigDecimal() provision.thawingUntil = BigInt.fromI32(0) provision.ownStakeRatio = BigInt.fromI32(0).toBigDecimal() provision.delegatedStakeRatio = BigInt.fromI32(0).toBigDecimal() diff --git a/src/mappings/rewardsManager.ts b/src/mappings/rewardsManager.ts index ee48a4b..755f8ca 100644 --- a/src/mappings/rewardsManager.ts +++ b/src/mappings/rewardsManager.ts @@ -1,5 +1,5 @@ import { Address, BigInt } from '@graphprotocol/graph-ts' -import { Indexer, Allocation, SubgraphDeployment, GraphNetwork } from '../types/schema' +import { Indexer, Allocation, SubgraphDeployment, GraphNetwork, Provision } from '../types/schema' import { RewardsAssigned, HorizonRewardsAssigned, @@ -13,7 +13,10 @@ import { updateLegacyAdvancedIndexerMetrics, updateDelegationExchangeRate, createOrLoadGraphNetwork, - calculateCapacities + calculateCapacities, + updateDelegationExchangeRateForProvision, + updateAdvancedProvisionMetrics, + joinID } from './helpers/helpers' import { getAndUpdateGraphNetworkDailyData, @@ -140,6 +143,21 @@ function processRewardsAssigned( indexer = calculateCapacities(indexer as Indexer) indexer.save() + // update provision manually only if it exists + let provisionId = joinID([indexerID, addresses.subgraphService.toLowerCase()]) + let provision = Provision.load(provisionId) + if (provision != null) { + provision.delegatorIndexingRewards = provision.delegatorIndexingRewards.plus(delegatorIndexingRewards) + provision.indexerIndexingRewards = provision.indexerIndexingRewards.plus(indexerIndexingRewards) + provision.delegatedTokens = provision.delegatedTokens.plus(delegatorIndexingRewards) + + if (provision.delegatorShares != BigInt.fromI32(0)) { + provision = updateDelegationExchangeRateForProvision(provision as Provision) + } + provision = updateAdvancedProvisionMetrics(provision as Provision) + provision.save() + } + // update allocation // no status updated, Claimed happens when RebateClaimed, and it is done let allocation = Allocation.load(allocationID)! From d55de86fb508614ff706816b9d14116e7cc01774 Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Thu, 19 Feb 2026 04:11:47 -0300 Subject: [PATCH 7/7] fix: query fee collection, effective cuts --- src/mappings/helpers/helpers.ts | 18 ++++++++++----- src/mappings/horizonStaking.ts | 4 ++-- src/mappings/subgraphService.ts | 41 ++++++++++++++++++--------------- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/src/mappings/helpers/helpers.ts b/src/mappings/helpers/helpers.ts index fbb0772..f41ed35 100644 --- a/src/mappings/helpers/helpers.ts +++ b/src/mappings/helpers/helpers.ts @@ -1052,14 +1052,16 @@ export function calculateDelegatedStakeRatio(indexer: Indexer): BigDecimal { } export function calculateIndexingRewardEffectiveCut(indexer: Indexer): BigDecimal { - let delegatorCut = BigInt.fromI32(indexer.indexingRewardCut).toBigDecimal() / BigDecimal.fromString('1000000') + let delegatorCut = + BigInt.fromI32(1000000 - indexer.indexingRewardCut).toBigDecimal() / BigDecimal.fromString('1000000') return indexer.delegatedStakeRatio == BigDecimal.fromString('0') ? BigDecimal.fromString('0') : BigDecimal.fromString('1') - delegatorCut / indexer.delegatedStakeRatio } export function calculateQueryFeeEffectiveCut(indexer: Indexer): BigDecimal { - let delegatorCut = BigInt.fromI32(indexer.queryFeeCut).toBigDecimal() / BigDecimal.fromString('1000000') + let delegatorCut = + BigInt.fromI32(1000000 - indexer.queryFeeCut).toBigDecimal() / BigDecimal.fromString('1000000') return indexer.delegatedStakeRatio == BigDecimal.fromString('0') ? BigDecimal.fromString('0') : BigDecimal.fromString('1') - delegatorCut / indexer.delegatedStakeRatio @@ -1067,21 +1069,23 @@ export function calculateQueryFeeEffectiveCut(indexer: Indexer): BigDecimal { export function calculateIndexerRewardOwnGenerationRatio(indexer: Indexer): BigDecimal { let rewardCut = - BigInt.fromI32(1000000 -indexer.indexingRewardCut).toBigDecimal() / BigDecimal.fromString('1000000') + BigInt.fromI32(indexer.indexingRewardCut).toBigDecimal() / BigDecimal.fromString('1000000') return indexer.ownStakeRatio == BigDecimal.fromString('0') ? BigDecimal.fromString('0') : rewardCut / indexer.ownStakeRatio } export function calculateLegacyIndexingRewardEffectiveCut(indexer: Indexer): BigDecimal { - let delegatorCut = BigInt.fromI32(indexer.legacyIndexingRewardCut).toBigDecimal() / BigDecimal.fromString('1000000') + let delegatorCut = + BigInt.fromI32(1000000 - indexer.legacyIndexingRewardCut).toBigDecimal() / BigDecimal.fromString('1000000') return indexer.delegatedStakeRatio == BigDecimal.fromString('0') ? BigDecimal.fromString('0') : BigDecimal.fromString('1') - delegatorCut / indexer.delegatedStakeRatio } export function calculateLegacyQueryFeeEffectiveCut(indexer: Indexer): BigDecimal { - let delegatorCut = BigInt.fromI32(indexer.legacyQueryFeeCut).toBigDecimal() / BigDecimal.fromString('1000000') + let delegatorCut = + BigInt.fromI32(1000000 - indexer.legacyQueryFeeCut).toBigDecimal() / BigDecimal.fromString('1000000') return indexer.delegatedStakeRatio == BigDecimal.fromString('0') ? BigDecimal.fromString('0') : BigDecimal.fromString('1') - delegatorCut / indexer.delegatedStakeRatio @@ -1089,7 +1093,7 @@ export function calculateLegacyQueryFeeEffectiveCut(indexer: Indexer): BigDecima export function calculateLegacyIndexerRewardOwnGenerationRatio(indexer: Indexer): BigDecimal { let rewardCut = - BigInt.fromI32(1000000 - indexer.legacyIndexingRewardCut).toBigDecimal() / BigDecimal.fromString('1000000') + BigInt.fromI32(indexer.legacyIndexingRewardCut).toBigDecimal() / BigDecimal.fromString('1000000') return indexer.ownStakeRatio == BigDecimal.fromString('0') ? BigDecimal.fromString('0') : rewardCut / indexer.ownStakeRatio @@ -1200,6 +1204,7 @@ export function updateAdvancedProvisionMetrics(provision: Provision): Provision export function updateDelegationExchangeRate(indexer: Indexer): Indexer { indexer.delegationExchangeRate = indexer.delegatedTokens + .minus(indexer.delegatedThawingTokens) .toBigDecimal() .div(indexer.delegatorShares.toBigDecimal()) .truncate(18) @@ -1208,6 +1213,7 @@ export function updateDelegationExchangeRate(indexer: Indexer): Indexer { export function updateDelegationExchangeRateForProvision(provision: Provision): Provision { provision.delegationExchangeRate = provision.delegatedTokens + .minus(provision.delegatedThawingTokens) .toBigDecimal() .div(provision.delegatorShares.toBigDecimal()) .truncate(18) diff --git a/src/mappings/horizonStaking.ts b/src/mappings/horizonStaking.ts index 7a08c3d..39b84c0 100644 --- a/src/mappings/horizonStaking.ts +++ b/src/mappings/horizonStaking.ts @@ -565,10 +565,10 @@ export function handleTokensUndelegated(event: TokensUndelegated): void { let beforeUpdateDelegationExchangeRate = provision.delegationExchangeRate provision.delegatorShares = provision.delegatorShares.minus(event.params.shares) + provision.delegatedThawingTokens = provision.delegatedThawingTokens.plus(event.params.tokens) if (provision.delegatorShares != BigInt.fromI32(0)) { provision = updateDelegationExchangeRateForProvision(provision as Provision) } - provision.delegatedThawingTokens = provision.delegatedThawingTokens.plus(event.params.tokens) provision = updateAdvancedProvisionMetrics(provision as Provision) provision.save() @@ -576,10 +576,10 @@ export function handleTokensUndelegated(event: TokensUndelegated): void { let indexerID = event.params.serviceProvider.toHexString() let indexer = Indexer.load(indexerID)! indexer.delegatorShares = indexer.delegatorShares.minus(event.params.shares) + indexer.delegatedThawingTokens = indexer.delegatedThawingTokens.plus(event.params.tokens) if (indexer.delegatorShares != BigInt.fromI32(0)) { indexer = updateDelegationExchangeRate(indexer as Indexer) } - indexer.delegatedThawingTokens = indexer.delegatedThawingTokens.plus(event.params.tokens) indexer = updateAdvancedIndexerMetrics(indexer as Indexer) indexer = calculateCapacities(indexer as Indexer) indexer.save() diff --git a/src/mappings/subgraphService.ts b/src/mappings/subgraphService.ts index b1136bf..baa3073 100644 --- a/src/mappings/subgraphService.ts +++ b/src/mappings/subgraphService.ts @@ -367,43 +367,48 @@ export function handleQueryFeesCollected(event: QueryFeesCollected): void { let indexerID = event.params.serviceProvider.toHexString() let allocationID = event.params.allocationId.toHexString() let paymentAddress = event.params.payer + let tokensRemaining = event.params.tokensCollected + + // Calculate protocol tokens and subtract protocol tokens + curator tokens from total + let _protocolTokens = tokensRemaining.times(BigInt.fromI32(graphNetwork.protocolFeePercentage)).div(BigInt.fromI32(1000000)) + tokensRemaining = tokensRemaining.minus(_protocolTokens).minus(event.params.tokensCurators) // update provision let provision = createOrLoadProvision(event.params.serviceProvider, event.address, event.block.timestamp) - let delegationPoolQueryFees = + let indexerQueryFees = provision.delegatedTokens == BigInt.fromI32(0) - ? event.params.tokensCollected - : event.params.tokensCollected + ? tokensRemaining + : tokensRemaining .times(provision.queryFeeCut) .div(BigInt.fromI32(1000000)) - let indexerQueryFees = event.params.tokensCollected.minus(delegationPoolQueryFees) + let delegationPoolQueryFees = tokensRemaining.minus(indexerQueryFees) - provision.queryFeesCollected = provision.queryFeesCollected.plus(event.params.tokensCollected) + provision.queryFeesCollected = provision.queryFeesCollected.plus(tokensRemaining) provision.indexerQueryFees = provision.indexerQueryFees.plus(indexerQueryFees) provision.delegatorQueryFees = provision.delegatorQueryFees.plus(delegationPoolQueryFees) provision.save() // update indexer let indexer = Indexer.load(indexerID)! - indexer.queryFeesCollected = indexer.queryFeesCollected.plus(event.params.tokensCollected) + indexer.queryFeesCollected = indexer.queryFeesCollected.plus(tokensRemaining) indexer.queryFeeRebates = indexer.queryFeeRebates.plus(indexerQueryFees) indexer.delegatorQueryFees = indexer.delegatorQueryFees.plus(delegationPoolQueryFees) indexer.save() // Replicate for payment source specific aggregation let paymentAggregation = createOrLoadIndexerQueryFeePaymentAggregation(paymentAddress, event.params.serviceProvider) - paymentAggregation.queryFeesCollected = paymentAggregation.queryFeesCollected.plus(event.params.tokensCollected) + paymentAggregation.queryFeesCollected = paymentAggregation.queryFeesCollected.plus(tokensRemaining) paymentAggregation.queryFeeRebates = paymentAggregation.queryFeeRebates.plus(indexerQueryFees) paymentAggregation.delegatorQueryFees = paymentAggregation.delegatorQueryFees.plus(delegationPoolQueryFees) paymentAggregation.save() // update allocation let allocation = Allocation.load(allocationID)! - allocation.queryFeesCollected = allocation.queryFeesCollected.plus(event.params.tokensCollected) + allocation.queryFeesCollected = allocation.queryFeesCollected.plus(tokensRemaining) allocation.curatorRewards = allocation.curatorRewards.plus(event.params.tokensCurators) allocation.queryFeeRebates = allocation.queryFeeRebates.plus(indexerQueryFees) - allocation.distributedRebates = allocation.distributedRebates.plus(event.params.tokensCollected) + allocation.distributedRebates = allocation.distributedRebates.plus(tokensRemaining) allocation.delegationFees = allocation.delegationFees.plus(delegationPoolQueryFees) allocation.save() @@ -412,19 +417,19 @@ export function handleQueryFeesCollected(event: QueryFeesCollected): void { addresses.isL1 ? event.block.number : graphNetwork.currentL1BlockNumber!, graphNetwork ) - epoch.totalQueryFees = epoch.totalQueryFees.plus(event.params.tokensCollected).plus(event.params.tokensCurators) - epoch.queryFeesCollected = epoch.queryFeesCollected.plus(event.params.tokensCollected) + epoch.totalQueryFees = epoch.totalQueryFees.plus(tokensRemaining).plus(event.params.tokensCurators) + epoch.queryFeesCollected = epoch.queryFeesCollected.plus(tokensRemaining) epoch.curatorQueryFees = epoch.curatorQueryFees.plus(event.params.tokensCurators) - epoch.queryFeeRebates = epoch.queryFeeRebates.plus(event.params.tokensCollected) + epoch.queryFeeRebates = epoch.queryFeeRebates.plus(tokensRemaining) epoch.save() // update subgraph deployment let deployment = SubgraphDeployment.load(subgraphDeploymentID)! - deployment.queryFeesAmount = deployment.queryFeesAmount.plus(event.params.tokensCollected) + deployment.queryFeesAmount = deployment.queryFeesAmount.plus(tokensRemaining) deployment.signalledTokens = deployment.signalledTokens.plus(event.params.tokensCurators) deployment.curatorFeeRewards = deployment.curatorFeeRewards.plus(event.params.tokensCurators) deployment.pricePerShare = calculatePricePerShare(deployment as SubgraphDeployment) - deployment.queryFeeRebates = deployment.queryFeeRebates.plus(indexerQueryFees) + deployment.queryFeeRebates = deployment.queryFeeRebates.plus(indexerQueryFees) deployment.delegatorQueryFees = deployment.delegatorQueryFees.plus(delegationPoolQueryFees) deployment.delegatorsQueryFeeRebates = deployment.delegatorsQueryFeeRebates.plus(delegationPoolQueryFees) deployment.save() @@ -432,9 +437,9 @@ export function handleQueryFeesCollected(event: QueryFeesCollected): void { batchUpdateSubgraphSignalledTokens(deployment as SubgraphDeployment) // update graph network - graphNetwork.totalQueryFees = graphNetwork.totalQueryFees.plus(event.params.tokensCollected).plus(event.params.tokensCurators) + graphNetwork.totalQueryFees = graphNetwork.totalQueryFees.plus(tokensRemaining).plus(event.params.tokensCurators) graphNetwork.totalIndexerQueryFeesCollected = graphNetwork.totalIndexerQueryFeesCollected.plus( - event.params.tokensCollected, + tokensRemaining, ) graphNetwork.totalCuratorQueryFees = graphNetwork.totalCuratorQueryFees.plus( event.params.tokensCurators, @@ -449,9 +454,9 @@ export function handleQueryFeesCollected(event: QueryFeesCollected): void { // Replicate for payment source specific data let paymentSource = createOrLoadPaymentSource(paymentAddress) - paymentSource.totalQueryFees = paymentSource.totalQueryFees.plus(event.params.tokensCollected).plus(event.params.tokensCurators) + paymentSource.totalQueryFees = paymentSource.totalQueryFees.plus(tokensRemaining).plus(event.params.tokensCurators) paymentSource.totalIndexerQueryFeesCollected = paymentSource.totalIndexerQueryFeesCollected.plus( - event.params.tokensCollected, + tokensRemaining, ) paymentSource.totalCuratorQueryFees = paymentSource.totalCuratorQueryFees.plus( event.params.tokensCurators,