Description
In Horizon mode, the Indexer.delegatedTokens field in the subgraph does not match the actual delegation.tokens value from the HorizonStaking contract's delegation pool. This causes tokenCapacity to be underestimated.
Observed Behavior
For indexer 0xf92f430dd8567b0d466358c79594ab58d919a6d4:
| Source |
Value (GRT) |
Subgraph Indexer.delegatedTokens |
116,157,557.38 |
Contract getDelegationPool().tokens |
117,126,781.76 |
| Difference |
969,224.39 (~0.83%) |
This propagates to tokenCapacity:
| Source |
Value (GRT) |
Subgraph tokenCapacity |
122,223,783.22 |
Contract getTokensAvailable() |
123,193,007.61 |
| Difference |
969,224.39 |
Root Cause Analysis
The subgraph calculates delegationExchangeRate = delegatedTokens / delegatorShares.
Comparing exchange rates:
- Subgraph:
1.772679126585520324
- Contract (actual):
1.787470448629024056
The subgraph's exchange rate is stale. With 65.5M shares, this rate difference of ~0.0148 produces the ~969k GRT discrepancy.
Hypothesis
In Horizon mode, rewards (indexing rewards + query fees) accumulate in the delegation pool and increase delegation.tokens in the contract. The subgraph updates delegatedTokens via TokensToDelegationPoolAdded events in handleTokensToDelegationPoolAdded, but it appears that:
- Some reward accumulation events may not be triggering the update, OR
- The
Indexer.delegatedTokens is not being updated when Provision.delegatedTokens changes, OR
- There's a timing/synchronization issue between when rewards are added to the contract pool and when corresponding events are emitted
Steps to Reproduce
- Query the subgraph for an active indexer with delegations:
{
indexer(id: "0xf92f430dd8567b0d466358c79594ab58d919a6d4") {
delegatedTokens
delegatorShares
delegationExchangeRate
tokenCapacity
}
}
- Query the contract directly:
# Get delegation pool
cast call 0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF03 \
"getDelegationPool(address,address)((uint256,uint256,uint256,uint256,uint256))" \
0xf92f430dd8567b0d466358c79594ab58d919a6d4 \
0xb2Bb92d0DE618878E438b55D5846cfecD9301105 \
--rpc-url https://arb1.arbitrum.io/rpc
# Get tokens available
cast call 0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF03 \
"getTokensAvailable(address,address,uint32)(uint256)" \
0xf92f430dd8567b0d466358c79594ab58d919a6d4 \
0xb2Bb92d0DE618878E438b55D5846cfecD9301105 \
16 \
--rpc-url https://arb1.arbitrum.io/rpc
- Compare
delegatedTokens from subgraph with delegation.tokens from contract
Expected Behavior
Indexer.delegatedTokens should match the contract's getDelegationPool().tokens value (minus any thawing tokens that are excluded).
Impact
tokenCapacity is underestimated in the subgraph
availableStake calculations are incorrect
- Any tooling relying on subgraph data for capacity planning will have inaccurate data
Environment
- Network: Arbitrum One
- Subgraph: Graph Network Subgraph
- Contracts: HorizonStaking at
0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF03
- SubgraphService at
0xb2Bb92d0DE618878E438b55D5846cfecD9301105
Description
In Horizon mode, the
Indexer.delegatedTokensfield in the subgraph does not match the actualdelegation.tokensvalue from the HorizonStaking contract's delegation pool. This causestokenCapacityto be underestimated.Observed Behavior
For indexer
0xf92f430dd8567b0d466358c79594ab58d919a6d4:Indexer.delegatedTokensgetDelegationPool().tokensThis propagates to
tokenCapacity:tokenCapacitygetTokensAvailable()Root Cause Analysis
The subgraph calculates
delegationExchangeRate = delegatedTokens / delegatorShares.Comparing exchange rates:
1.7726791265855203241.787470448629024056The subgraph's exchange rate is stale. With 65.5M shares, this rate difference of ~0.0148 produces the ~969k GRT discrepancy.
Hypothesis
In Horizon mode, rewards (indexing rewards + query fees) accumulate in the delegation pool and increase
delegation.tokensin the contract. The subgraph updatesdelegatedTokensviaTokensToDelegationPoolAddedevents inhandleTokensToDelegationPoolAdded, but it appears that:Indexer.delegatedTokensis not being updated whenProvision.delegatedTokenschanges, ORSteps to Reproduce
{ indexer(id: "0xf92f430dd8567b0d466358c79594ab58d919a6d4") { delegatedTokens delegatorShares delegationExchangeRate tokenCapacity } }delegatedTokensfrom subgraph withdelegation.tokensfrom contractExpected Behavior
Indexer.delegatedTokensshould match the contract'sgetDelegationPool().tokensvalue (minus any thawing tokens that are excluded).Impact
tokenCapacityis underestimated in the subgraphavailableStakecalculations are incorrectEnvironment
0x00669A4CF01450B64E8A2A20E9b1FCB71E61eF030xb2Bb92d0DE618878E438b55D5846cfecD9301105