Smoothy.finance Withdrawal Incident Analysis
- Smoothy.finance implements a dynamic cash reserve algorithm that deposits about 90% of stablecoins (USDT/USDC) to YFI yield pool to earn interest.
- YFI yield pool’s current strategy deposits all USDT to Aave V1
- Current Aave V1 USDT pool lends almost all USDT out with utility rate (borrow/deposit) > 99%, resulting in failure of Smoothy withdrawal of USDT if the amount is large.
- Although the current stable borrow APR of Aave V1 USDT is > 69%, the average stable borrow APR is about 8%, which means that the low borrow APR cannot effectively push borrowers to repay USDT.
- Further, although a borrower’s stable borrow APR can be forcibly increased by rebalancing, there seems to exist an exploit so that a whale can always circumvent the high stable borrow APR and enjoy the low stable borrow APR by repaying and borrowing a large amount of USDT repeatedly.
Smoothy.finance launched a stablecoin swap pool on BSC on Mar. 29, 2021, with an extra SMTY incentive. After that, a significant amount of stablecoins including USDT is moved from Smoothy.finance ETH pool to BSC pool, resulting in a large amount of withdrawal. However, after withdrawing about 20M USDT, community members found that USDT cannot be withdrawn from the pool.
Smoothy.finance implements a dynamic cash reserve algorithm in order to maximize LP return while maintaining a low swap gas fee — a unique feature of Smoothy.
The basic idea is that Smoothy.finance will reserve about 10% of the token as cash in the pool and deposit the rest 90% into the interest-earning protocols. If a swap results in
- The cash reserve is greater than 20%; or
- There is insufficient cash reserve to complete the swap.
Smoothy.finance will perform a rebalance so that
- If the cash reserve is greater than 20% of the token, 10% will be retained as cash, and the rest of the cash reserve is deposited into the underlying protocol to earn interest; or
- If the cash reserve is insufficient to complete the swap, extra tokens are withdrawn from the underlying protocol so that 10% of the token becomes reserved cash after the swap.
Currently, Smoothy.finance uses YFI USDT yield pool at https://etherscan.io/address/0x83f798e925bcd4017eb265844fddabb448f1707d to generate interest. The YFI pool further deposits all USDT to Aave V1. As of Apr. 1, 2021, the current status of Aave pool for USDT is
where we have a couple of interesting observations:
- 99.99% (utility rate) of USDT are lent out with only 16,321.89 USDT available for withdrawal. This means any withdrawal > 16,321.89 USDT will fail!
- Due to the high utility rate, the APY for both stable borrowing and variable borrow are >= 65%, which is much greater than deposit APY 11.73%.
- The high borrow APY should push the borrowers to repay USDT to prevent being liquidated. However, it seems to be not happening.
- Further, we notice that 94.28% of the borrows are stable borrowing
So the question comes up with is that what is the actual borrowing APY for stable borrowers? By querying the V1 lending contract at https://etherscan.io/address/0x398ec7346dcd622edc5ae82352f02be94c62d119#readProxyContract, we collect the following information:
To our surprise, the averageStableBorrowRate queried from the contract is 8.32% , which is much lower than current stable rate 69.4% even utility rate > 99%. Why?
To figure the reasons, we dive into Aave whitepaper and the code and find two possible reasons:
- First, the whitepaper tells us that “stable borrow rate” does NOT impact existing stable rate positions. This means that early stable borrower’s rates may be significantly lower than the current stable borrow rate.
- Second, from the code https://github.com/aave/aave-protocol/blob/master/contracts/lendingpool/LendingPoolCore.sol#L181, it seems that Aave uses pre-borrow-utility-rate to calculate the stable borrow rate.
Combining these two could bring great APY benefit (or exploit) for early whale stable borrowers. E.g., in USDT case, with Uoptimal = 90%, Mr = 3.5%, Rslope1 = 6%, Rslope2 = 60%, and U = 90%, a whale borrower can conveniently stable borrow the remaining 10% of USDT in pool at APR = 9.5% without entering the APY defined by slope2 (which ranges from 9.5% to 69.5%) — the high stable rate at U = 100% may never kick in!
One possible way to address the exploit is to manually call a public method rebalanceStableBorrowRate(token, account) to increase the stable borrow rate of a borrower to the current stable borrow rate. However, it seems that a whale can counter the rebalance by repaying the token so that U < Uoptimal, and then borrowing all the token at a favorable stable borrow rate Mr + Rslope1. In the case of USDT, where the total liquidity is about 160M, this means that as long as the whale has borrowed 16M of USDT, it could effectively counter the rebalance and maintain the stable borrow rate at 9.5%.
As the conclusion, by exploiting the stable borrow, a whale borrower can easily enjoy low APR by circumventing the high APR curve (slope2), the pool would run out of reserve (if the rate of Mr + slope1 is attractive in the market), and depositors can hardly withdraw the reserve from the pool.
To solve the exploit, one possible improvement direction is to use post-borrow-liquidity-rate to calculate the stable borrow APR. In USDT case, this means that the borrowers that stable borrow 1 — Uoptimal = 10% of USDT will have APY from 9.5% and 69.5%. If all the tokens are borrowed, then the average APY of the stable borrow of the last 10% USDT should be (9.5% + 69.5%) / 2 = 39.5%.
We would like to appreciate an anonymous community member to figure out the mismatch between the current and average stable borrow rate and discuss the rebalance method.