Frozen module coin store can cause chain halt
Description
The AllocateTokens
function is called by the distributions module before each block in the BeginBlocker
handler:
func (k Keeper) AllocateTokens(ctx context.Context, totalPreviousPower int64, bondedVotes []abci.VoteInfo) error {
if err := k.beforeAllocateTokens(ctx); err != nil {
return err
}
// fetch and clear the collected fees for distribution, since this is
// called in BeginBlock, collected fees will be from the previous block
// (and distributed to the previous proposer)
feeCollector := k.authKeeper.GetModuleAccount(ctx, k.feeCollectorName)
feesCollectedInt := k.bankKeeper.GetAllBalances(ctx, feeCollector.GetAddress())
feesCollected := sdk.NewDecCoinsFromCoins(feesCollectedInt...)
// transfer collected fees to the distribution module account
err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, feesCollectedInt)
if err != nil {
return err
}
This will fetch the balance for any coins sent to the fee collector and send it to the distribution model, which ends up calling 0x1::coin::transfer
in the Move VM.
The issue is that it is possible for someone to initialize a coin, freeze the distribution-module accounts store for that coin using the FreezeCapability
, and then mint some coins to the fee module, causing the call to SendCoinsFromModuleToModule
to fail.
Impact
A malicious user can cause the AllocateTokens
method to return an error, and since it occurs in a BeginBlocker
, it will cause a consensus failure and halt the chain.
Recommendations
The safest option to resolve this issue could be to have an allowlist of coins that the fee module can distribute instead of fetching all balances.