|
| 1 | +# Broken Bookkeeping |
| 2 | + |
| 3 | +The `x/bank` module is the standard way to manage tokens in a cosmos-sdk based applications. The module allows to mint, burn, and transfer coins between both users' and modules' accounts. If an application implements its own, internal bookkeeping, it must carefully use the `x/bank`'s features. |
| 4 | + |
| 5 | +## Example |
| 6 | + |
| 7 | +An application enforces the following invariant as a sanity-check: amount of tokens owned by a module equals to the amount of tokens deposited by users via the custom `x/hodl` module. |
| 8 | + |
| 9 | +```go |
| 10 | +func BalanceInvariant(k Keeper) sdk.Invariant { |
| 11 | + return func(ctx sdk.Context) (string, bool) { |
| 12 | + weAreFine := true |
| 13 | + msg := "hodling hard" |
| 14 | + |
| 15 | + weHold := k.bankKeeper.SpendableCoins(authtypes.NewModuleAddress(types.ModuleName)).AmountOf("BTC") |
| 16 | + usersDeposited := k.GetTotalDeposited("BTC") |
| 17 | + |
| 18 | + if weHold != usersDeposited { |
| 19 | + msg = fmt.Sprintf("%dBTC missing! Halting chain.\n", usersDeposited - weHold) |
| 20 | + weAreFine = false |
| 21 | + } |
| 22 | + |
| 23 | + return sdk.FormatInvariant(types.ModuleName, "hodl-balance",), weAreFine |
| 24 | + } |
| 25 | +} |
| 26 | +``` |
| 27 | + |
| 28 | +A spiteful user can simply transfer a tiny amount of BTC tokens directly to the `x/hodl` module via a message to the `x/bank` module. That would bypass accounting of the `x/hodl`, so the `GetTotalDeposited` function would report a not-updated amount, smaller than the module's `SpendableCoins`. |
| 29 | + |
| 30 | +Because an invariant's failure stops the chain, the bug constitutes a simple Denial-of-Service attack vector. |
| 31 | + |
| 32 | +## Example 2 |
| 33 | + |
| 34 | +An example application implements a lending platform. It allows users to deposit Tokens in exchange for xTokens - similarly to the [Compound's cTokens](https://compound.finance/docs/ctokens#exchange-rate). Token:xToken exchange rate is calculated as `(amount of Tokens borrowed + amount of Tokens held by the module account) / (amount of uTokens in circulation)`. |
| 35 | + |
| 36 | +Implementation of the `GetExchangeRate` method computing an exchange rate is presented below. |
| 37 | + |
| 38 | +```go |
| 39 | +func (k Keeper) GetExchangeRate(tokenDenom string) sdk.Coin { |
| 40 | + uTokenDenom := createUDenom(tokenDenom) |
| 41 | + |
| 42 | + tokensHeld := k.bankKeeper.SpendableCoins(authtypes.NewModuleAddress(types.ModuleName)).AmountOf(tokenDenom).ToDec() |
| 43 | + tokensBorrowed := k.GetTotalBorrowed(tokenDenom) |
| 44 | + uTokensInCirculation := k.bankKeeper.GetSupply(uTokenDenom).Amount |
| 45 | + |
| 46 | + return (tokensHeld + tokensBorrowed) / uTokensInCirculation |
| 47 | +} |
| 48 | + |
| 49 | +``` |
| 50 | + |
| 51 | +A malicious user can screw an exchange rate in two ways: |
| 52 | + |
| 53 | +* by force-sending Tokens to the module, changing the `tokensHeld` value |
| 54 | +* by transferring uTokens to another chain via IBC, chaning `uTokensInCirculation` value |
| 55 | + |
| 56 | +The first "attack" could be pulled of by sending [`MsgSend`](https://docs.cosmos.network/main/modules/bank/03_messages.html#msgsend) message. However, it would be not profitable (probably), as executing it would irreversibly decrease an attacker's resources. |
| 57 | + |
| 58 | +The second one works because the IBC module [burns transferred coins in the source chain](https://github.com/cosmos/ibc-go/blob/48a6ae512b4ea42c29fdf6c6f5363f50645591a2/modules/apps/transfer/keeper/relay.go#L135-L136) and mints corresponding tokens in the destination chain. Therefore, it will decrease the supply reported by the `x/bank` module, increasing the exchange rate. After the attack the malicious user can just transfer back uTokens. |
| 59 | + |
| 60 | + |
| 61 | +## Mitigations |
| 62 | + |
| 63 | +- Use [`Blocklist` to prevent unexpected token transfers](https://docs.cosmos.network/v0.45/modules/bank/02_keepers.html#blocklisting-addresses) to specific addresses |
| 64 | +- Use [`SendEnabled` parameter to prevent unexpected transfers](https://docs.cosmos.network/v0.45/modules/bank/05_params.html#parameters) of specific tokens (denominations) |
| 65 | +- Ensure that the blocklist is explicitly checked [whenever a new functionality allowing for tokens transfers is implemented](https://github.com/cosmos/cosmos-sdk/issues/8463#issuecomment-801046285) |
| 66 | + |
| 67 | +## External examples |
| 68 | + |
| 69 | +- [Umee was vulnerable to the token:uToken exchange rate manipulation](https://github.com/trailofbits/publications/blob/master/reviews/Umee.pdf) (search for finding TOB-UMEE-21). |
| 70 | +- [Desmos incorrectly blocklisted addresses](https://github.com/desmos-labs/desmos/blob/e3c89e2f9ddd5dfde5d11c3ad5319e3c249cacb3/CHANGELOG.md#version-0154) (check app.go file in [the commits diff](https://github.com/desmos-labs/desmos/compare/v0.15.3...v0.15.4)) |
| 71 | + |
0 commit comments