Summary

The IsSendEnabledCoins function is responsible for ensuring coins with SendEnabled as false cannot be sent via MsgSend and MsgMultiSend. These are also enforced when creating vesting accounts to prevent users from transferring send-disabled coins to a recipient.

However, it is not enforced within the SendCoinsFromModuleToAccount, SendCoinsFromModuleToModule, and SendCoinsFromAccountToModule functions.

This may allow a user to transfer send-disabled coins via the following scenario:

  1. Send coins to a module via SendCoinsFromModuleToAccount.
  2. (optional) transfer between modules via SendCoinsFromModuleToModule.
  3. Receive coins from the module via SendCoinsFromAccountToModule.

Steps to reproduce

  1. Alice wants to transfer send-disabled coins (SendEnabled is false) to Bob.
  2. Bob calls MsgCreateValidator with msg.Commission.Rate to be 100%.
  3. Alice calls MsgDepositValidatorRewardsPool with msg.ValidatorAddress as Bob’s address and msg.Amount as the send-disabled coin.
    1. This would cause the send-disabled coin to be transferred into the distribution module via the SendCoinsFromAccountToModule function.
    2. The send-disabled coin will be allocated to the validator via the AllocateTokensToValidator function. Since the commission is set to 100% in step 2, Bob will receive all the tokens and update via the SetValidatorAccumulatedCommission function.
  4. Bob calls MsgWithdrawValidatorCommission to transfer the send-disabled coin from the distribution module to their address via the SendCoinsFromModuleToAccount function.
    1. Bob receives the send-disabled coins from Alice.
  5. What if Bob wants to transfer the send-disabled tokens to someone else (e.g., Carol)? Bob can call MsgSetWithdrawAddress to set their withdrawal address to Carol’s address.
    1. This would cause the send-disabled coins to be transferred to Carol’s address due to GetDelegatorWithdrawAddr.
  6. As a result, the IsSendEnabledCoins validation can be bypassed. With the above steps, an attacker can send the send-disabled coins to themselves or other recipients.

Here is another instance that allows sending send-disabled coins:

  1. Alice wants to distribute a send-disabled coin to everyone.
  2. Alice calls MsgFundCommunityPool to fund the community pool with the send-disabled coin.
    1. This would cause the send-disabled coin to be transferred into the distribution module via the SendCoinsFromAccountToModule function.
  3. Governance can distribute the send-disabled coin to any address with MsgCommunityPoolSpend via the SendCoinsFromModuleToAccount function.

Workarounds

Consider implementing the IsSendEnabledCoins validation in the SendCoinsFromModuleToAccount function.