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.
We take the first exploit transaction on Stable AMM - USDT/BUSD as an example.
5. Similarly, the attacker called the function swap() twice to swap the BUSD with input amount 5947 and 62 of BUSD, respectively.
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').
At the time of writing, all funds have been transferred from two attacker contracts to the attacker account 0xd793f. The attacker account currently has the exploited 5964 USDT, 6178 BUSD, and 232 USDC.
Yes, we will be able to spot this issue because the k invariants of non-stablecoin and stablecoin cases are different.