Skip to content

Latest commit

 

History

History
82 lines (58 loc) · 2.83 KB

003.md

File metadata and controls

82 lines (58 loc) · 2.83 KB

Bitter Khaki Iguana

Medium

BeforeValidatorSlashed does not apply fraction

Summary

BeforeValidatorSlashed calculates thisVotingPower without applying fraction.

Root Cause

A validator can be partially slashed (fraction) depending on the infraction, as such the calculation of threshold of slashed voting power would be incorrect.

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/epoching/keeper/hooks.go#L50C84-L50C92

// BeforeValidatorSlashed records the slash event
func (h Hooks) BeforeValidatorSlashed(ctx context.Context, valAddr sdk.ValAddress, fraction math.LegacyDec) error {
    sdkCtx := sdk.UnwrapSDKContext(ctx)
    thresholds := []float64{float64(1) / float64(3), float64(2) / float64(3)}

    epochNumber := h.k.GetEpoch(ctx).EpochNumber
    totalVotingPower := h.k.GetTotalVotingPower(ctx, epochNumber)

    // calculate total slashed voting power
    slashedVotingPower := h.k.GetSlashedVotingPower(ctx, epochNumber)
    // voting power of this validator
@>    thisVotingPower, err := h.k.GetValidatorVotingPower(ctx, epochNumber, valAddr)
    thisVal := types.Validator{Addr: valAddr, Power: thisVotingPower}
    if err != nil {
        // It's possible that the most powerful validator outside the validator set enrols to the validator after this validator is slashed.
        // Consequently, here we cannot find this validator in the validatorSet map.
        // As we consider the validator set in the epoch beginning to be the validator set throughout this epoch, we consider this new validator in the edge to have no voting power and return directly here.
        return err
    }

    for _, threshold := range thresholds {
        // if a certain threshold voting power is slashed in a single epoch, emit event and trigger hook
@>        if float64(slashedVotingPower) < float64(totalVotingPower)*threshold && float64(totalVotingPower)*threshold <= float64(slashedVotingPower+thisVotingPower) {
            slashedVals := h.k.GetSlashedValidators(ctx, epochNumber)
            slashedVals = append(slashedVals, thisVal)
            event := types.NewEventSlashThreshold(slashedVotingPower, totalVotingPower, slashedVals)
            if err := sdkCtx.EventManager().EmitTypedEvent(&event); err != nil {
                panic(err)
            }
            h.k.BeforeSlashThreshold(ctx, slashedVals)
        }
    }

    // add the validator address to the set
    if err := h.k.AddSlashedValidator(ctx, valAddr); err != nil {
        // same as above error case
        return err
    }

    return nil
}

Internal Pre-conditions

No response

External Pre-conditions

No response

Attack Path

  1. Validator gets partially slashed by the staking module.

Impact

Wrong threshold calculations in order to trigger hook BeforeSlashThreshold.

PoC

No response

Mitigation

No response