Notional is a lending and borrowing platform on Ethereum. Most operations for their platform are performed using their fCash token. These tokens are redeemable for positive or negative cash flow at a later date, acting as a receipt/IOU. The fCash token is also denoted in its underlying token, such as DAI or USDC. Once settled, it is given out in cToken (collateral tokens, such as cUSDC), which can be redeemed for the original token.
All users have a portfolio. This is an array of assets, which is governance limited. There is also an additional asset bitmap portfolio that is very handy for market makers. The name is bitmap, so I assume that the data is a literal bitmap. A user can only hold data in only of these data structures at a time though.
While switching from one bitmap to another, there is validation in place.
-
Bitmap Enabled:
-
Validate no assets are currently set in the bitmap.
-
Bitmap Disabled:
-
Require there are no assets in the array from the first mode of operation.
-
Call the function
setActiveCurrency. This ensuring that there is no double counting of the current collateral.
The bitmap currency is used to keep track of the current currencies being used by the account. This appears to be important for bookkeeping on how much a user has. These currencies can only be changed if there are no debts or credits in the bitmap asset.
The call to setActiveCurrency() in the else clause even has a comment about "... so there is no double counting during FreeCollateral.". However, this function is ONLY called during the else clause and NOT the IF statement. As a result, the activeCurrencies field is NOT cleared.
By calling external code that sets another currency, such as depositUnderlyingToken(), setActiveCurrency will be called for the account. To get two currencies to be active, we can call enableBitmapForAccoutn with a currency we do not care about then make a second call to enableBitmapForAccoutn after the deposit mentioned. Due to the logic error, the free collateral calcuations will run twice on the asset deposited: once for the bitmap and once for the activeCurrencies.
What does free collateral mean in this context though? To evaluate the position of the user, the debts are significantly over collateralized. To denote this, Notional uses free collateral to denominate the ETH that an account holds beyond what it needs to meet the minimum collateral requirements. If positive, the position is proper. If negative, it's eligible for liquidation.
If the amount of free collateral is doubled (with the bug above), an attacker could borrow more without actually holding enough of the currency. This means they can trade a small amount of one token and take a loan out for more with the protocol, making it possible to just never pay back the loan to make a profit!
To exploit the bug, the steps needs to be taken:
- Enable the bitmap portfolio for any currency.
- Deposit funds using
depositUnderlyingToken() on the currency you want to double.
- Enable the bitmap currency again. This performs the double count operation, making it believe we put in more collateral than we actually did.
- Transfer out the fCash. This will be worth more than our original collateral.
- Convert fCash into another token and make the money!