Category: Coding Mistakes
Distributing zero coins causes chain halt
High Impact
High Severity
High Likelihood
Description
The oracle module uses an AfterEpochEnd hook, which allocates rewards for validators. This hook is inside the BeginBlocker.
func (h Hooks) AfterEpochEnd(ctx sdk.Context, epochIdentifier string, _ uint64) {
[...]
balances := h.bankKeeper.GetAllBalances(ctx, account.GetAddress())
for _, balance := range balances {
validatorFees := balance.Amount.ToDec().Mul(params.ValidatorFeeRatio).TruncateInt()
rest := balance.Amount.Sub(validatorFees)
totalValidatorFees = append(totalValidatorFees, sdk.NewCoin(balance.Denom, validatorFees))
totalRest = append(totalRest, sdk.NewCoin(balance.Denom, rest))
}
[...]
err = h.k.AllocateRewards(
ctx,
perptypes.FeePoolModuleAccount,
totalValidatorFees,
1,
)
if err != nil {
panic(err)
}The issue here is that validatorFees could be zero for very small positions. This means AllocateRewards could be called with one or more coins with a zero amount.
Impact
The AllocateRewards function in turn calls bankKeeper.SendCoinsFromModuleToModule, which will fail if any of the coins have a nonpositive amount.
func (coins Coins) Validate() error {
[...]
if err := ValidateDenom(coins[0].Denom); err != nil {
return err
}
if !coins[0].IsPositive() {
return fmt.Errorf("coin %s amount is not positive", coins[0])
}Since the AfterEpochEnd hook is inside the BeginBlocker, this will cause the chain to halt.
Recommendations
If the final value of totalValidatorFees is not greater than zero then the call to h.k.AllocateRewards should not be made.
Remediation
This issue has been acknowledged by Nibiru, and a fix was implemented in commit c430556a↗.