Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions src/mappings/helpers/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1010,10 +1010,12 @@ function max(a: BigDecimal, b: BigDecimal): BigDecimal {
return a > b ? a : b
}

// Own stake that still backs the indexer: excludes legacy locked (unstaking) tokens and
// Horizon provision tokens that are thawing. Both are zero when they don't apply.
export function getActiveIndexerStake(indexer: Indexer): BigInt {
return indexer.stakedTokens.minus(indexer.lockedTokens).minus(indexer.thawingTokens)
// Horizon backing excludes idle stake. Legacy handlers retain their previous stake basis;
// isLegacy cannot select it because migrated indexers can still carry that flag.
export function getActiveIndexerStake(indexer: Indexer, useLegacyStake: boolean = false): BigInt {
return useLegacyStake
? indexer.stakedTokens.minus(indexer.lockedTokens).minus(indexer.thawingTokens)
: indexer.provisionedTokens.minus(indexer.thawingTokens)
}

// Delegation that still earns rewards and fees. Horizon keeps undelegated tokens in the pool
Expand All @@ -1022,8 +1024,8 @@ export function getActiveIndexerDelegation(indexer: Indexer): BigInt {
return indexer.delegatedTokens.minus(indexer.delegatedThawingTokens)
}

export function calculateOwnStakeRatio(indexer: Indexer): BigDecimal {
let stakedTokensBD = getActiveIndexerStake(indexer).toBigDecimal()
export function calculateOwnStakeRatio(indexer: Indexer, useLegacyStake: boolean = false): BigDecimal {
let stakedTokensBD = getActiveIndexerStake(indexer, useLegacyStake).toBigDecimal()
let delegatedTokensBD = getActiveIndexerDelegation(indexer).toBigDecimal()
let graphNetwork = loadGraphNetwork()
let delegationRatioBD = BigInt.fromI32(graphNetwork.delegationRatio).toBigDecimal()
Expand Down Expand Up @@ -1088,8 +1090,8 @@ export function calculateLegacyIndexerRewardOwnGenerationRatio(indexer: Indexer)
: rewardCut / indexer.ownStakeRatio
}

export function calculateOverdelegationDilution(indexer: Indexer): BigDecimal {
let stakedTokensBD = getActiveIndexerStake(indexer).toBigDecimal()
export function calculateOverdelegationDilution(indexer: Indexer, useLegacyStake: boolean = false): BigDecimal {
let stakedTokensBD = getActiveIndexerStake(indexer, useLegacyStake).toBigDecimal()
let delegatedTokensBD = getActiveIndexerDelegation(indexer).toBigDecimal()
let graphNetwork = loadGraphNetwork()
let delegationRatioBD = BigInt.fromI32(graphNetwork.delegationRatio).toBigDecimal()
Expand All @@ -1113,14 +1115,14 @@ export function updateAdvancedIndexerMetrics(indexer: Indexer): Indexer {
}

export function updateLegacyAdvancedIndexerMetrics(indexer: Indexer): Indexer {
indexer.ownStakeRatio = calculateOwnStakeRatio(indexer as Indexer)
indexer.ownStakeRatio = calculateOwnStakeRatio(indexer as Indexer, true)
indexer.delegatedStakeRatio = calculateDelegatedStakeRatio(indexer as Indexer)
indexer.legacyIndexingRewardEffectiveCut = calculateLegacyIndexingRewardEffectiveCut(indexer as Indexer)
indexer.legacyQueryFeeEffectiveCut = calculateLegacyQueryFeeEffectiveCut(indexer as Indexer)
indexer.legacyIndexerRewardsOwnGenerationRatio = calculateLegacyIndexerRewardOwnGenerationRatio(
indexer as Indexer,
)
indexer.overDelegationDilution = calculateOverdelegationDilution(indexer as Indexer)
indexer.overDelegationDilution = calculateOverdelegationDilution(indexer as Indexer, true)
return indexer as Indexer
}

Expand Down
33 changes: 27 additions & 6 deletions src/mappings/horizonStaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,22 +258,29 @@ export function handleProvisionSlashed(event: ProvisionSlashed): void {
let indexer = Indexer.load(event.params.serviceProvider.toHexString())!
let provision = createOrLoadProvision(event.params.serviceProvider, event.params.verifier, event.block.timestamp)

// Due to thawing tokens potentially getting cancelled, we will need to figure the thawing situation
// The contract slashes active and thawing tokens proportionally, rounding thawing down.
let remainingTokens = provision.tokensProvisioned.minus(event.params.tokens)
let remainingThawing = provision.tokensThawing.times(remainingTokens).div(provision.tokensProvisioned)
let thawingSlashed = provision.tokensThawing.minus(remainingThawing)

indexer.provisionedTokens = indexer.provisionedTokens.minus(event.params.tokens)
indexer.stakedTokens = indexer.stakedTokens.minus(event.params.tokens)
indexer.thawingTokens = indexer.thawingTokens.minus(thawingSlashed)
indexer = updateAdvancedIndexerMetrics(indexer as Indexer)
indexer = calculateCapacities(indexer as Indexer)
indexer.save()

dataService.totalTokensProvisioned = dataService.totalTokensProvisioned.minus(event.params.tokens)
dataService.totalTokensThawing = dataService.totalTokensThawing.minus(thawingSlashed)
dataService.save()

graphNetwork.totalTokensProvisioned = graphNetwork.totalTokensProvisioned.minus(event.params.tokens)
graphNetwork.totalTokensStaked = graphNetwork.totalTokensStaked.minus(event.params.tokens)
graphNetwork.totalTokensThawing = graphNetwork.totalTokensThawing.minus(thawingSlashed)
graphNetwork.save()

provision.tokensProvisioned = provision.tokensProvisioned.minus(event.params.tokens)
// To DO, update thawing tokens according to the accounting calculation from the contract
provision.tokensProvisioned = remainingTokens
provision.tokensThawing = remainingThawing
provision.tokensSlashedServiceProvider = provision.tokensSlashedServiceProvider.plus(event.params.tokens)
provision = updateAdvancedProvisionMetrics(provision as Provision)
provision.save()
Expand Down Expand Up @@ -465,7 +472,13 @@ export function handleDelegationSlashed(event: DelegationSlashed): void {

// update provision
let provision = createOrLoadProvision(event.params.serviceProvider, event.params.verifier, event.block.timestamp)
provision.delegatedTokens = provision.delegatedTokens.minus(event.params.tokens)
let remainingTokens = provision.delegatedTokens.minus(event.params.tokens)
let remainingThawing = provision.delegatedThawingTokens.times(remainingTokens).div(provision.delegatedTokens)
let thawingSlashed = provision.delegatedThawingTokens.minus(remainingThawing)
let activeSlashed = event.params.tokens.minus(thawingSlashed)

provision.delegatedTokens = remainingTokens
provision.delegatedThawingTokens = remainingThawing
provision.tokensSlashedDelegationPool = provision.tokensSlashedDelegationPool.plus(event.params.tokens)
if (provision.delegatorShares != BigInt.fromI32(0)) {
provision = updateDelegationExchangeRateForProvision(provision as Provision)
Expand All @@ -477,14 +490,22 @@ export function handleDelegationSlashed(event: DelegationSlashed): void {
let indexerID = event.params.serviceProvider.toHexString()
let indexer = Indexer.load(indexerID)!
indexer.delegatedTokens = indexer.delegatedTokens.minus(event.params.tokens)
indexer.delegatedThawingTokens = indexer.delegatedThawingTokens.minus(thawingSlashed)
if (indexer.delegatorShares != BigInt.fromI32(0)) {
indexer = updateDelegationExchangeRate(indexer as Indexer)
}
indexer = updateAdvancedIndexerMetrics(indexer as Indexer)
indexer = calculateCapacities(indexer as Indexer)
indexer.save()

// upgrade graph network
// These totals exclude thawing delegation, which was removed on TokensUndelegated.
let graphNetwork = createOrLoadGraphNetwork(event.block.number, event.address)
graphNetwork.totalDelegatedTokens = graphNetwork.totalDelegatedTokens.minus(event.params.tokens)
graphNetwork.totalDelegatedTokens = graphNetwork.totalDelegatedTokens.minus(activeSlashed)
graphNetwork.save()

let dataService = createOrLoadDataService(event.params.verifier)
dataService.totalTokensDelegated = dataService.totalTokensDelegated.minus(activeSlashed)
dataService.save()
}

export function handleTokensUndelegated(event: TokensUndelegated): void {
Expand Down
66 changes: 66 additions & 0 deletions tests/factories/horizonStaking.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import { newMockEvent } from 'matchstick-as/assembly/index'
import {
DelegatedTokensWithdrawn,
DelegationFeeCutSet,
DelegationSlashed,
HorizonStakeDeposited,
HorizonStakeWithdrawn,
ProvisionCreated,
ProvisionIncreased,
ProvisionSlashed,
ProvisionThawed,
TokensDelegated,
TokensDeprovisioned,
Expand All @@ -18,6 +23,67 @@ function uintParam(name: string, value: BigInt): ethereum.EventParam {
return new ethereum.EventParam(name, ethereum.Value.fromUnsignedBigInt(value))
}

export function mockProvisionSlashed(
serviceProvider: Address,
verifier: Address,
tokens: BigInt,
): ProvisionSlashed {
let event = changetype<ProvisionSlashed>(newMockEvent())
event.parameters = [
addressParam('serviceProvider', serviceProvider),
addressParam('verifier', verifier),
uintParam('tokens', tokens),
]
return event
}

export function mockDelegationSlashed(
serviceProvider: Address,
verifier: Address,
tokens: BigInt,
): DelegationSlashed {
let event = changetype<DelegationSlashed>(newMockEvent())
event.parameters = [
addressParam('serviceProvider', serviceProvider),
addressParam('verifier', verifier),
uintParam('tokens', tokens),
]
return event
}

export function mockProvisionIncreased(
serviceProvider: Address,
verifier: Address,
tokens: BigInt,
): ProvisionIncreased {
let event = changetype<ProvisionIncreased>(newMockEvent())
event.parameters = [
addressParam('serviceProvider', serviceProvider),
addressParam('verifier', verifier),
uintParam('tokens', tokens),
]
return event
}

export function mockHorizonStakeDeposited(
serviceProvider: Address,
tokens: BigInt,
): HorizonStakeDeposited {
let event = changetype<HorizonStakeDeposited>(newMockEvent())
event.block.number = BigInt.fromI32(5)
event.parameters = [addressParam('serviceProvider', serviceProvider), uintParam('tokens', tokens)]
return event
}

export function mockHorizonStakeWithdrawn(
serviceProvider: Address,
tokens: BigInt,
): HorizonStakeWithdrawn {
let event = changetype<HorizonStakeWithdrawn>(newMockEvent())
event.parameters = [addressParam('serviceProvider', serviceProvider), uintParam('tokens', tokens)]
return event
}

export function mockProvisionCreated(
serviceProvider: Address,
verifier: Address,
Expand Down
Loading
Loading