Rounding bugs that lead to massive loss of funds have alluded me for a while. I see them in large hacks but don't understand where they're useful and how to find them. This post is a good step for me on that journey through the lends of accounting in the share and token model.
In Solidity, there is only fixed precision. For instance, you can either have 1 or 2 but nothing in between. Some things, like ERC20, use the first 18 digits as being this decimal point though. Most of the time, 1 or 2 may not be a big different. However, given the right circumstances, it can make a huge difference.
Many systems, such as ERC 4626 tokenized vaults, use the token to share model. When passing in tokens, the user gets back a share or percentage model of the system. These shares directly correspond to the value provided but may help with rewards as well. Over time, these shares can accrue more and more value.
The authors give an example. Say we have shares to tokens with a one to one ratio to beginning this and start with 1000 shares. After accuring fees, the new ratio is 1001 tokens to 1000 shares. If a user takes out 999 of these shares, we run into a precision problem. Should they get 1000 tokens or 999 tokens?
This demonstrates the first of the issues: rounding direction. Generally speaking, we should round against the user. So, we'll round to 999 tokens in this case. The direction of rounding is an important thing to consider, as small discrepancies can lead to lost value over time if exploited millions of times.
Now, back to the weird situation from above with only 2 token being left in the vault. The ratio is now 2:1 for tokens to share! If a token is donated, this becomes even higher at 3:1. There are many cases of this inflation leading to weird situations that led to stolen funds.
For Radiant Capital, who was hacked for 4.5M, it's a simple thing like we described above. If the inflation was done to make each share worth 1000 each, then the user withdraw $1999 dollars, only a single share could be burned. This gives them a free $999 because they still have the one share left.
In Wise lending, there was a rounding issue in liquidation code - you shouldn't be able to bankrupt yourself. This is best explained by example. We have one share worth $1000 and try to borrow $500. Now, we try to withdraw $1 of our collateral. The code will round up to use our one share worth $1000 dollars, making it liquidatable. This is rounding against the user but stills causes problems. If they round down, they also have a bug, since we could steal money this way. What do? Force shares to be withdrawn and not the token itself.
In the case of DeFi, these are a few important things to consider when exploiting these rounding attacks. First, the share value needs to be inflatable via the methods described above. Next, we need a nearly empty pool. Finally, we need the weird rounding or accounting to occur. To prevent this, the easiest way is to add assets to the vault at deployment or have an artificial shares at the beginning.
If we zoom out from the vault case, there are some bigger takeaways. First, having large and small values interaction can cause major issues with rounding. Second, rounding directions are important to consider. By default, things will round down. So, evaluating each division and the consequence of the round seems like a good takeaway. Finally, edge cases are where the exploitability of these things occur. Good post!