On June 7, 2022 Osmosis suffered a funds loss of around $5M due to a code pitfall in function
MaximalExactRatioJoin that issued more LP shares than expected. Although we did audit some of the contracts from Osmosis, the file x/gamm/pool-models/internal/cfmm_common/lp.go was not part of the audit.
On Tuesday, June 7th, 2022, Osmosis suffered a loss of around $5M due to a code pitfall in function
MaximalExactRatioJoin that is called by
CalJoinPoolShares to calculate the shares based on the input amount of tokens. The flaw in the
MaximalExactRatioJoin causes more LP shares to be provided to the users by
CalJoinPoolShares once the users provide the liquidity by
JoinPool. Essentially, the function would give 50% too many LP shares for a join. So if one should have gotten 10 LP shares, 15 would be achieved.
Osmosis announced on their social media platforms that the bug had been identified and a fix is in motion. The stolen funds have been linked to CEX accounts and that they have notified law enforcement of these exploiters. They also announced that four of the individuals who exploited Osmosis have been identified, and that two of the four will return the exploited amounts. The other two exploiters are in contact with Osmosis and waiting for further communication.
All losses will be covered. These funds will come from the strategic reserve and not the community pool, and a high rate of recovery is expected for the exploited funds. The chain is set to remain halted for at least another two days, and may be subject to change.
There are many transaction pairs used to exploit the project. A sample transaction is as follows. Attacker 1: Interchain Explorer by Cosmostation
Related Exploit Transactions:
Join Pool (2022-06-07 22:18:01)
Exit Pool (2022-06-07 22:18:38)
Related Exploit Transactions:
Join Pool (2022-06-07 22:49:23):
Exit Pool (2022-06-07 22:49:42):
The following analysis takes these two exploited transactions from Attack1 as an example.
The attackers called the JoinPool() to add liquidity to the GAMM pools(in this example, it’s pool_id 678) with 29.95 USDC and 26.03 OSMO. The minted LP shares are 8.79 GAMM-678.
After that, the attackers called the ExitPool() from pool 678 to remove the liquidity using the previous shares.
Due to the miscalculation of the shares from remaining tokens, the attacker was able to withdraw the amount of tokens that are approximately 1.5 times the deposited amounts.
The attackers repeated the attack flow multiple times with larger deposited token amounts to make even bigger profit.
The root cause of this exploit lies in the miscalculation of shares from the remaining tokens. When users invoke the function JoinPool() in x/gamm/keeper/msg_server.go by message Msg.JoinPool,
the function calls function JoinPoolNoSwap() in x/gamm/keeper/pool_service.go on line 95. In function JoinPoolNoSwap(), after the calculation of needed liquidity for the amount of shareOutAmount by calling function getMaximalNoSwapLPAmount() on line 192, function JoinPoolNoSwap() will call pool.JoinPool() in osmosis/x/gamm/pool-models/balancer/amm.go on line 286. Function pool.JoinPool() will then call function pool.CalcJoinPoolShares() on line 252, and cfmm_common.MaximalExactRatioJoin() will be called on line 283 during the execution of pool.CalcJoinPoolShares(). Function cfmm_common.MaximalExactRatioJoin() will calculate the usedAmount(needed liquidity) while adding liquidity, it multiplies the share ratio with the amount of tokens user put in (Line 90). However, the needed liquidity should equal to minShareRatio * total liquidity in pools. Normally, the total liquidity is larger than the the tokens one user deposited, which means that the usedAmount is less than expected. Notice that the returned value remCoins records the remaining tokens after liquidity addition. The value of remCoins is larger than expected since the usedAmount is smaller than expected. After the execution of function cfmm_common.MaximalExactRatioJoin(), these remaining tokens remCoins will be used to add more liquidity to the pool on lines 300-310 by calling function pool.calSingleAssetJoin(). However, because the amount remCoins is larger than expected, more shares will be provided to users than expected.
Yes, we would find out the miscalculation of the shares from remaining tokens. The file x/gamm/pool-models/internal/cfmm_common/lp.go was not included in the files we audited.