On 20 June, Whale Finance experienced two separate exploits on the project's stablecoin AMM contracts, which has led to ~$12k in losses. The attacker called the swap() function from the USDT/BUSD pool which had a vulnerability, which was primarily caused by an incorrect k invariant calculation when the swap pair is a stablecoin.
On the projects Discord, @coj337 who is the servers admin stated that due to the exploit, Whale.Finance has taken down the DEX whilst the vulnerability is being assessed. It has also been announced that no customer funds were lost due to the exploit.
Attacker Addresses & Exploit Transactions
Attacker contract addresses:
- For the first attack on Stable AMM - USDT/BUSD:
- For the second attack on Stable AMM - USDC/BUSD:
- On Stable AMM - USDT/BUSD:
- On Stable AMM - USDC/BUSD:
- Stable AMM - USDT/BUSD:
- Stable AMM - USDC/BUSD:
We take the first exploit transaction on Stable AMM - USDT/BUSD as an example.
- The attacker called the function swap() from the Stable AMM -USDT/BUSD contract. The input amount of USDT(BSC-USD) is 5964, which is the balance of the USDT in the Stable AMM -USDT/BUSD contract.
- The attacker sent back 0.6 USDT to the Stable AMM -USDT/BUSD contact.
- After the fee adjustment, the balance0Adjusted = 6022457770012534500304,
and the balance1Adjusted = 59471946427871433983220000,
which means the k invariant value is 1266806331900666880877818210684878792429048115.
- However, the reserve0 and reserve1 are 59646190399283805000316 and 5947194642787143398322 respectively. Their corresponding k invariant value is 2516642811824473716920890881639825. If it is multiplied by 10000**2 = 100_000_000, the result is still less than the k invariant given by the balance0Adjusted and balance1Adjusted. In this case, the k invariant validation was bypassed. Therefore, the Stable AMM - USDT/BUSD contract transferred the 5964 USDT to the attacker successfully.
5. Similarly, the attacker called the function swap() twice to swap the BUSD with input amount 5947 and 62 of BUSD, respectively.
- Due to the same vulnerability, the Stable AMM -USDT/BUSD contract transferred 5947 and 62 BUSD to the attacker directly.
- The attacker applied the same strategy to the Stable AMM-USDC/BUSD with 232 USDC and 232 BUSD as profit.
- In total, the attacker made a profit of $12K.
The root cause of the vulnerability is the incorrect k invariant calculation when the swap pair is stable coin. In contract WhaleswapPair.sol, the calculation of k invariant in function _k() while the boolean stable is set to true is x ^ 3 * y + y ^ 3 * x instead of the regular one k = x * y.
In function swap() from the Stable AMM-USDT/BUSD contract, the following require statement checks the k invariant returned by the aforementioned function _k() while stable is set to true. However, in the stablecoin case, the k equals to x ^ 3 * y + y ^ 3 * x, so the k invariant check should be using 10000 ^ 4 instead of 10000 ^ 2 because the formula calculating the k invariant is of degree 4. The correct implementation of the require statement should be require(_k(balance0Adjusted, balance1Adjusted) >= _k(_reserve0, _reserve1).mul(10000**4), 'Whaleswap: K').
Profit and Assets Tracing
At the time of writing, all funds have been transferred from two attacker contracts to the attacker account 0xd793ff8d744828c25da7f80123b88dd5c2bf7a50. The attacker account currently has the exploited 5964 USDT, 6178 BUSD, and 232 USDC.
Can the issue be found during the audit?
Yes, we will be able to spot this issue because the k invariants of non-stablecoin and stablecoin cases are different.