From 9325e43a7a8650819d64d06ae0a0166a3517c2bb Mon Sep 17 00:00:00 2001 From: Miguel de Elias Date: Mon, 3 Nov 2025 16:41:04 -0300 Subject: [PATCH 1/5] fix: staking legacy contract call when slashing --- src/mappings/staking.ts | 37 ++++++++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/mappings/staking.ts b/src/mappings/staking.ts index 50126eb..9b207a4 100644 --- a/src/mappings/staking.ts +++ b/src/mappings/staking.ts @@ -161,21 +161,40 @@ export function handleStakeSlashed(event: StakeSlashed): void { let id = event.params.indexer.toHexString() let indexer = Indexer.load(id)! - indexer.stakedTokens = indexer.stakedTokens.minus(event.params.tokens) + let slashedTokens = event.params.tokens + + // When tokens are slashed, locked tokens might need to be unlocked if indexer overallocated + if (slashedTokens.gt(BigInt.fromI32(0))) { + let tokensUsed = indexer.allocatedTokens.plus(indexer.legacyLockedTokens) + let tokensAvailable = tokensUsed.gt(indexer.stakedTokens) + ? BigInt.fromI32(0) + : indexer.stakedTokens.minus(tokensUsed) + + if (slashedTokens.gt(tokensAvailable) && indexer.legacyLockedTokens.gt(BigInt.fromI32(0))) { + let tokensOverAllocated = slashedTokens.minus(tokensAvailable) + // Calculate min(tokensOverAllocated, lockedTokens) + let tokensToUnlock = tokensOverAllocated.lt(indexer.legacyLockedTokens) + ? tokensOverAllocated + : indexer.legacyLockedTokens + + indexer.legacyLockedTokens = indexer.legacyLockedTokens.minus(tokensToUnlock) + indexer.lockedTokens = indexer.lockedTokens.minus(tokensToUnlock) + + if (indexer.legacyLockedTokens.equals(BigInt.fromI32(0))) { + indexer.legacyTokensLockedUntil = 0 + indexer.tokensLockedUntil = 0 + } + } + } + + indexer.stakedTokens = indexer.stakedTokens.minus(slashedTokens) - // We need to call into stakes mapping, because locked tokens might have been - // decremented, and this is not released in the event - // To fix this we would need to indicate in the event how many locked tokens were released - let staking = Staking.bind(event.address) - let indexerStored = staking.stakes(event.params.indexer) - indexer.lockedTokens = indexerStored.tokensLocked - indexer.legacyLockedTokens = indexerStored.tokensLocked indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) indexer = calculateCapacities(indexer as Indexer) indexer.save() // Update graph network - graphNetwork.totalTokensStaked = graphNetwork.totalTokensStaked.minus(event.params.tokens) + graphNetwork.totalTokensStaked = graphNetwork.totalTokensStaked.minus(slashedTokens) graphNetwork.save() } From 3c959a608ca4a549823933b6016b6148360156d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Mon, 3 Nov 2025 16:57:20 -0300 Subject: [PATCH 2/5] fix: proper tracking of legacy locked tokens vs locked tokens MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- src/mappings/staking.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/mappings/staking.ts b/src/mappings/staking.ts index 9b207a4..4697884 100644 --- a/src/mappings/staking.ts +++ b/src/mappings/staking.ts @@ -98,6 +98,7 @@ export function handleStakeDeposited(event: StakeDeposited): void { /** * @dev handleStakeLocked + * Handler for legacy stake locking * - updated the Indexers stake * - note - the contracts work by not changing the tokensStaked amount, so here, capacity does not * get changed @@ -107,8 +108,6 @@ export function handleStakeLocked(event: StakeLocked): void { // update indexer let id = event.params.indexer.toHexString() let indexer = Indexer.load(id)! - indexer.lockedTokens = event.params.tokens - indexer.tokensLockedUntil = event.params.until.toI32() indexer.legacyLockedTokens = event.params.tokens indexer.legacyTokensLockedUntil = event.params.until.toI32() indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) @@ -119,7 +118,7 @@ export function handleStakeLocked(event: StakeLocked): void { graphNetwork.totalUnstakedTokensLocked = graphNetwork.totalUnstakedTokensLocked.plus( event.params.tokens, ) - if (indexer.stakedTokens == indexer.lockedTokens) { + if (indexer.stakedTokens == indexer.lockedTokens.plus(indexer.legacyLockedTokens)) { graphNetwork.stakedIndexersCount = graphNetwork.stakedIndexersCount - 1 } graphNetwork.save() @@ -127,6 +126,7 @@ export function handleStakeLocked(event: StakeLocked): void { /** * @dev handleStakeWithdrawn + * Handler for legacy stake withdrawal * - updated the Indexers stake * - updates the GraphNetwork total stake */ @@ -136,8 +136,6 @@ export function handleStakeWithdrawn(event: StakeWithdrawn): void { let id = event.params.indexer.toHexString() let indexer = Indexer.load(id)! indexer.stakedTokens = indexer.stakedTokens.minus(event.params.tokens) - indexer.lockedTokens = indexer.lockedTokens.minus(event.params.tokens) - indexer.tokensLockedUntil = 0 // always set to 0 when withdrawn indexer.legacyLockedTokens = indexer.legacyLockedTokens.minus(event.params.tokens) indexer.legacyTokensLockedUntil = 0 // always set to 0 when withdrawn indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) @@ -178,7 +176,6 @@ export function handleStakeSlashed(event: StakeSlashed): void { : indexer.legacyLockedTokens indexer.legacyLockedTokens = indexer.legacyLockedTokens.minus(tokensToUnlock) - indexer.lockedTokens = indexer.lockedTokens.minus(tokensToUnlock) if (indexer.legacyLockedTokens.equals(BigInt.fromI32(0))) { indexer.legacyTokensLockedUntil = 0 @@ -292,9 +289,7 @@ export function handleStakeDelegatedLocked(event: StakeDelegatedLocked): void { delegatedStake.unstakedTokens = delegatedStake.unstakedTokens.plus(event.params.tokens) delegatedStake.shareAmount = delegatedStake.shareAmount.minus(event.params.shares) - delegatedStake.lockedTokens = delegatedStake.lockedTokens.plus(event.params.tokens) delegatedStake.legacyLockedTokens = delegatedStake.legacyLockedTokens.plus(event.params.tokens) - delegatedStake.lockedUntil = event.params.until.toI32() // until always updates and overwrites the past lockedUntil time delegatedStake.legacyLockedUntil = event.params.until.toI32() // until always updates and overwrites the past lockedUntil time delegatedStake.lastUndelegatedAt = event.block.timestamp.toI32() @@ -332,9 +327,7 @@ export function handleStakeDelegatedWithdrawn(event: StakeDelegatedWithdrawn): v let delegatorID = event.params.delegator.toHexString() let id = joinID([delegatorID, indexerID]) let delegatedStake = DelegatedStake.load(id)! - delegatedStake.lockedTokens = BigInt.fromI32(0) delegatedStake.legacyLockedTokens = BigInt.fromI32(0) - delegatedStake.lockedUntil = 0 delegatedStake.legacyLockedUntil = 0 delegatedStake.save() } From f7158050eb60d60a7f460c837d8823fc836d13d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1s=20Migone?= Date: Mon, 3 Nov 2025 17:08:56 -0300 Subject: [PATCH 3/5] fix: track legacy allocated amount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomás Migone --- schema.graphql | 4 +++- src/mappings/helpers/helpers.ts | 1 + src/mappings/staking.ts | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/schema.graphql b/schema.graphql index df842a6..7f061e5 100644 --- a/schema.graphql +++ b/schema.graphql @@ -745,8 +745,10 @@ type Indexer @entity(immutable: false) { provisionedTokens: BigInt! "CURRENT tokens thawing from provisions to data services in the protocol. Only for Horizon" thawingTokens: BigInt! - "CURRENT tokens allocated on all subgraphs" + "CURRENT tokens allocated on all subgraphs - including legacy and horizon allocations" allocatedTokens: BigInt! + "[Legacy only] CURRENT tokens allocated on all subgraphs ONLY for legacy allocations" + legacyAllocatedTokens: BigInt! "NOT IMPLEMENTED - Tokens that have been unstaked and withdrawn" unstakedTokens: BigInt! # will be used for return % calcs "CURRENT tokens locked" diff --git a/src/mappings/helpers/helpers.ts b/src/mappings/helpers/helpers.ts index c2a590c..0d4d65b 100644 --- a/src/mappings/helpers/helpers.ts +++ b/src/mappings/helpers/helpers.ts @@ -146,6 +146,7 @@ export function createOrLoadIndexer(indexerAddress: Bytes, timestamp: BigInt ): indexer.provisionedTokens = BigInt.fromI32(0) indexer.thawingTokens = BigInt.fromI32(0) indexer.allocatedTokens = BigInt.fromI32(0) + indexer.legacyAllocatedTokens = BigInt.fromI32(0) indexer.lockedTokens = BigInt.fromI32(0) indexer.legacyLockedTokens = BigInt.fromI32(0) indexer.unstakedTokens = BigInt.fromI32(0) diff --git a/src/mappings/staking.ts b/src/mappings/staking.ts index 4697884..800d607 100644 --- a/src/mappings/staking.ts +++ b/src/mappings/staking.ts @@ -163,7 +163,7 @@ export function handleStakeSlashed(event: StakeSlashed): void { // When tokens are slashed, locked tokens might need to be unlocked if indexer overallocated if (slashedTokens.gt(BigInt.fromI32(0))) { - let tokensUsed = indexer.allocatedTokens.plus(indexer.legacyLockedTokens) + let tokensUsed = indexer.legacyAllocatedTokens.plus(indexer.legacyLockedTokens) let tokensAvailable = tokensUsed.gt(indexer.stakedTokens) ? BigInt.fromI32(0) : indexer.stakedTokens.minus(tokensUsed) @@ -349,6 +349,7 @@ export function handleAllocationCreated(event: AllocationCreated): void { // update indexer let indexer = Indexer.load(indexerID)! + indexer.legacyAllocatedTokens = indexer.legacyAllocatedTokens.plus(event.params.tokens) indexer.allocatedTokens = indexer.allocatedTokens.plus(event.params.tokens) indexer.totalAllocationCount = indexer.totalAllocationCount.plus(BigInt.fromI32(1)) indexer.allocationCount = indexer.allocationCount + 1 @@ -529,6 +530,7 @@ export function handleAllocationClosed(event: AllocationClosed): void { allocation.forceClosed = false } indexer.allocatedTokens = indexer.allocatedTokens.minus(event.params.tokens) + indexer.legacyAllocatedTokens = indexer.legacyAllocatedTokens.minus(event.params.tokens) indexer.allocationCount = indexer.allocationCount - 1 indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) indexer = calculateCapacities(indexer as Indexer) @@ -591,6 +593,7 @@ export function handleAllocationClosedCobbDouglas(event: AllocationClosed1): voi } else { allocation.forceClosed = false } + indexer.legacyAllocatedTokens = indexer.legacyAllocatedTokens.minus(event.params.tokens) indexer.allocatedTokens = indexer.allocatedTokens.minus(event.params.tokens) indexer.allocationCount = indexer.allocationCount - 1 indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) From eff88c476c85663e74a20e2e10fbcaef4f8941e1 Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Mon, 3 Nov 2025 19:57:35 -0300 Subject: [PATCH 4/5] feat: revert unecessary changes, updated package.json deploy scripts --- package.json | 8 ++++---- src/mappings/staking.ts | 13 ++++++++++++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 120774b..7cb10e5 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,10 @@ "prepublishOnly": "yarn & yarn build:ipfs", "build": "graph build", "build:ipfs:mainnet": "yarn && yarn prepare:mainnet && graph build --ipfs https://ipfs.network.thegraph.com", - "deploy-mainnet": "yarn && yarn prepare:mainnet && graph deploy --studio graph-network-ethereum", - "deploy-arbitrum": "yarn && yarn prepare:arbitrum && graph deploy --studio graph-network-arbitrum", - "deploy-sepolia": "yarn && yarn prepare:sepolia && graph deploy --studio graph-network-sepolia", - "deploy-arbitrum-sepolia": "yarn && yarn prepare:arbitrum-sepolia && graph deploy --studio graph-network-arbitrum-sepolia", + "deploy-mainnet": "yarn && yarn prepare:mainnet && graph deploy graph-network-ethereum", + "deploy-arbitrum": "yarn && yarn prepare:arbitrum && graph deploy graph-network-arbitrum", + "deploy-sepolia": "yarn && yarn prepare:sepolia && graph deploy graph-network-sepolia", + "deploy-arbitrum-sepolia": "yarn && yarn prepare:arbitrum-sepolia && graph deploy graph-network-arbitrum-sepolia", "deploy-arbitrum-sepolia-test": "yarn && yarn prepare:arbitrum-sepolia && graph deploy horizon-testing-arb-sepolia -l test-old", "deploy-studio": "yarn deploy-mainnet && yarn deploy-arbitrum && yarn deploy-sepolia && yarn deploy-arbitrum-sepolia", "prep:addresses:sepolia": "ts-node config/sepoliaAddressScript.ts && mustache ./config/generatedAddresses.json ./config/addresses.template.ts > ./config/addresses.ts", diff --git a/src/mappings/staking.ts b/src/mappings/staking.ts index 800d607..6272a62 100644 --- a/src/mappings/staking.ts +++ b/src/mappings/staking.ts @@ -108,6 +108,8 @@ export function handleStakeLocked(event: StakeLocked): void { // update indexer let id = event.params.indexer.toHexString() let indexer = Indexer.load(id)! + indexer.lockedTokens = event.params.tokens + indexer.tokensLockedUntil = event.params.until.toI32() indexer.legacyLockedTokens = event.params.tokens indexer.legacyTokensLockedUntil = event.params.until.toI32() indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) @@ -118,7 +120,7 @@ export function handleStakeLocked(event: StakeLocked): void { graphNetwork.totalUnstakedTokensLocked = graphNetwork.totalUnstakedTokensLocked.plus( event.params.tokens, ) - if (indexer.stakedTokens == indexer.lockedTokens.plus(indexer.legacyLockedTokens)) { + if (indexer.stakedTokens == indexer.lockedTokens) { graphNetwork.stakedIndexersCount = graphNetwork.stakedIndexersCount - 1 } graphNetwork.save() @@ -136,6 +138,8 @@ export function handleStakeWithdrawn(event: StakeWithdrawn): void { let id = event.params.indexer.toHexString() let indexer = Indexer.load(id)! indexer.stakedTokens = indexer.stakedTokens.minus(event.params.tokens) + indexer.lockedTokens = indexer.lockedTokens.minus(event.params.tokens) + indexer.tokensLockedUntil = 0 // always set to 0 when withdrawn indexer.legacyLockedTokens = indexer.legacyLockedTokens.minus(event.params.tokens) indexer.legacyTokensLockedUntil = 0 // always set to 0 when withdrawn indexer = updateLegacyAdvancedIndexerMetrics(indexer as Indexer) @@ -176,9 +180,12 @@ export function handleStakeSlashed(event: StakeSlashed): void { : indexer.legacyLockedTokens indexer.legacyLockedTokens = indexer.legacyLockedTokens.minus(tokensToUnlock) + indexer.lockedTokens = indexer.lockedTokens.minus(tokensToUnlock) if (indexer.legacyLockedTokens.equals(BigInt.fromI32(0))) { indexer.legacyTokensLockedUntil = 0 + } + if (indexer.lockedTokens.equals(BigInt.fromI32(0))) { indexer.tokensLockedUntil = 0 } } @@ -289,7 +296,9 @@ export function handleStakeDelegatedLocked(event: StakeDelegatedLocked): void { delegatedStake.unstakedTokens = delegatedStake.unstakedTokens.plus(event.params.tokens) delegatedStake.shareAmount = delegatedStake.shareAmount.minus(event.params.shares) + delegatedStake.lockedTokens = delegatedStake.lockedTokens.plus(event.params.tokens) delegatedStake.legacyLockedTokens = delegatedStake.legacyLockedTokens.plus(event.params.tokens) + delegatedStake.lockedUntil = event.params.until.toI32() // until always updates and overwrites the past lockedUntil time delegatedStake.legacyLockedUntil = event.params.until.toI32() // until always updates and overwrites the past lockedUntil time delegatedStake.lastUndelegatedAt = event.block.timestamp.toI32() @@ -327,7 +336,9 @@ export function handleStakeDelegatedWithdrawn(event: StakeDelegatedWithdrawn): v let delegatorID = event.params.delegator.toHexString() let id = joinID([delegatorID, indexerID]) let delegatedStake = DelegatedStake.load(id)! + delegatedStake.lockedTokens = BigInt.fromI32(0) delegatedStake.legacyLockedTokens = BigInt.fromI32(0) + delegatedStake.lockedUntil = 0 delegatedStake.legacyLockedUntil = 0 delegatedStake.save() } From e0bb7f93d5ad849f38c2de0df62003e760a00900 Mon Sep 17 00:00:00 2001 From: Juan Manuel Rodriguez Defago Date: Tue, 4 Nov 2025 01:39:02 -0300 Subject: [PATCH 5/5] fix: missing Legacy enum for dispute type --- schema.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/schema.graphql b/schema.graphql index 7f061e5..c597fad 100644 --- a/schema.graphql +++ b/schema.graphql @@ -1511,6 +1511,7 @@ enum DisputeType { SingleQuery Conflicting Indexing + Legacy } enum DisputeStatus {