Skip to content

Latest commit

 

History

History
109 lines (82 loc) · 3.81 KB

038.md

File metadata and controls

109 lines (82 loc) · 3.81 KB

Slow Misty Iguana

Medium

Some block reward tokens will be permanently locked in the module account

Summary

Some block reward tokens will be permanently locked in the module account

Root Cause

The HandleRewarding function skips over the Finalized blocks

Internal Pre-conditions

  1. Some blocks did not enter the Finalized state whenHandleRewarding.

External Pre-conditions

Attack Path

Impact

The funds are locked in the module account and cannot be used.

PoC

HandleRewarding is used to distribute reward, called when each block Endblock:

x/finality/abci.go/EndBlocker() -> HandleRewarding()

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/finality/keeper/rewarding.go#L33-L35

// HandleRewarding calls the reward to stakers if the block is finalized
func (k Keeper) HandleRewarding(ctx context.Context, targetHeight int64) {
        ......
	// targetHeight = heightToExamine = HeaderInfo().Height - k.GetParams(ctx).FinalitySigTimeout
	for height := nextHeightToReward; height <= uint64(targetHeight); height++ {
		block, err := k.GetBlock(ctx, height)
		if err != nil {
			panic(err)
		}
->		if !block.Finalized {
->			continue
		}
		k.rewardBTCStaking(ctx, height)
		nextHeightToReward = height + 1
	}

	if nextHeightToReward != copiedNextHeightToReward {
		k.SetNextHeightToReward(ctx, nextHeightToReward)
	}
}

Therefore, no reward will be assigned to the Finalized block,

The problem is that the reward for these blocks is already in the module account, and the proceeds that are not distributed will remain in the module account forever and cannot be used.

x/incentive/abci.go/BeginBlocker() -> HandleCoinsInFeeCollector():

// HandleCoinsInFeeCollector intercepts a portion of coins in fee collector, and distributes
// them to BTC staking gauge of the current height.
// It is invoked upon every `BeginBlock`.
// adapted from https://github.com/cosmos/cosmos-sdk/blob/release/v0.47.x/x/distribution/keeper/allocation.go#L15-L26
func (k Keeper) HandleCoinsInFeeCollector(ctx context.Context) {
	params := k.GetParams(ctx)

	// find the fee collector account
	feeCollector := k.accountKeeper.GetModuleAccount(ctx, k.feeCollectorName)
	// get all balances in the fee collector account,
	// where the balance includes minted tokens in the previous block
	feesCollectedInt := k.bankKeeper.GetAllBalances(ctx, feeCollector.GetAddress())

	// don't intercept if there is no fee in fee collector account
	if !feesCollectedInt.IsAllPositive() {
		return
	}

	// record BTC staking gauge for the current height, and transfer corresponding amount
	// from fee collector account to incentive module account
	// TODO: maybe we should not transfer reward to BTC staking gauge before BTC staking is activated
	// this is tricky to implement since finality module will depend on incentive and incentive cannot
	// depend on finality module due to cyclic dependency
	btcStakingPortion := params.BTCStakingPortion()
	btcStakingReward := types.GetCoinsPortion(feesCollectedInt, btcStakingPortion)
	k.accumulateBTCStakingReward(ctx, btcStakingReward)
}

func (k Keeper) accumulateBTCStakingReward(ctx context.Context, btcStakingReward sdk.Coins) {
	// update BTC staking gauge
	height := uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)
	gauge := types.NewGauge(btcStakingReward...)
	k.SetBTCStakingGauge(ctx, height, gauge)

	// transfer the BTC staking reward from fee collector account to incentive module account
->	err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, btcStakingReward)
	if err != nil {
		// this can only be programming error and is unrecoverable
		panic(err)
	}
}

https://github.com/sherlock-audit/2024-12-babylon/blob/main/babylon/x/incentive/keeper/btc_staking_gauge.go#L72

Mitigation

Added an administrator function to transfer locked funds.