Category: Coding Mistakes
Distributing zero coins causes chain halt
High Severity
High Impact
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↗.