Detailed analysis of the $31 Million MonoX Protocol Hack
On November 30, 2021, our team at SlowMist was notified of an attack on the Defi protocol MonoX. Over $18 million in WETH and $10.5 million in Matic tokens were drained by the attacker. Several other tokens, including Wrapped Bitcoin, Chainlink, Unit Protocol, Aavegotchi, and Immutable X, were also lost stolen, bringing the total loss to over $31 million. We investigated the attack, and this is our detailed analysis of the incident.
The Root Cause of the Attack
The leading cause of this attack was that the swap contract did not check whether the transferred and outgoing tokens in the pool were the same. By taking advantage of this exploit in the price update function, the hacker can transfer the same tokens as the outgoing tokens to artificially pump up the price of the MONO token. They then used the MONO token to exchange for other assets within the MonoX protocol.
MonoX is a new DeFi protocol using a single token design for liquidity pools (instead of using pool pairs). This is made possible by grouping deposited tokens into a virtual pair with the vCASH stablecoin. The first use case for single token liquidity pools is the automatic market maker system-Monoswap, launched in October 2021.
Attacker address 1: 0xecbe385f78041895c311070f344b55bfaa953258
Attacker address 2 : 0x8f6a86f3ab015f4d03ddb13abb02710e6d7ab31b
Attack contract 1 : 0xf079d7911c13369e7fd85607970036d2883afcfd
Attack contract 2: 0x119914de3ae03256fd58b66cd6b8c6a12c70cfb2
Step By Step Analysis
- First, the hacker uses the Monoswap.swapExactTokenForToken function to swap 0.1 WETH for 79.986094311542621010 MONO.
- Using a contract they created, the hackers can drain all the assets within a pool and replace them with their liquidity to cause the most damage.
Let’s examine the codes that allowed this to happen. On lines 471–510 in Monoswap.sol, when liquidity in the pool is removed, the _removeLiquidityHelper function is called through the removeLiquidity function. Since neither function is called, it passes the parameter without any authentication and allows the contract to remove any user’s liquidity directly in the pool.
- Remove the liquidity of 0x7b9aa6, transfer 1670.7572297649224 MONO and 6.8622171986812230290 vCASH to 0x7b9aa6;
- Remove the liquidity of cowrie.eth, transfer 152.9745213857155 MONO and 0.628300423692773565 vCASH to cowrie.eth;
- Remove the liquidity of 0xab5167, transfer 99940.7413658327 MONO and 410.478879590637971405 vCASH to 0xab5167;
They then provided liquidity to the MONO token liquidity pool for contract 1
3. The attacker then called Monoswap.swapExactTokenForToken 55 times to inflate the price of MONO continuously.
The swapExactTokenForToken function in Monoswap.sol is the main reason for this incident. The attacker deposits MONO tokens into the pool, knowing that the tokenIn and tokenOut are the same tokens.
Looking at the swapIn function, we can see that it’s calculated from the getAmountOut function. Following the getAmountOut function, we discovered that the _getNewPrice function is used to calculate the tokenInPrice and the tokenOutPrice.
Looking at the _getNewprice function, we can see that the txType parameter passed when calculating tokenInPrice is TxType.SELL, at this time:
When calculating the tokenOutPrice function, the txType parameter passed the TxType.BUY, at this time:
Because the four variables in the price calculation formula are the same, the incoming and exiting tokens are the same. We can now assume that tokenOutPrice will be greater than tokenInPrice.
Since tokenIn and tokenOut are the same tokens, the swapIn function will call the _updateTokenInfo function again after calculating the price. The update of tokenOutPrice will overwrite the update tokenInPrice, which leads to an increase in the price of this token.
4. Finally, the attacker calls the swapTokenForExactToken function to swap MONO for other tokens in the pool.
To determine the price, the swapOut function is called within the swapTokenForExactToken function, while the getAmountIn function is used to call the swapOut function.
The tokenInPoolPrice represents the price of MONO tokens in the pool. Due to the recent pump in price, the tokenInPrice also increased. This causes the amountIn to decrease in value and is the MONO token to exchange for other tokens within the MonoX protocol.
5. The attacker finally transfers the stolen gains to this address 0x8f6a86f3ab015f4d03ddb13abb02710e6d7ab31b.
This is our analysis of the attack on the Ethereum network. The hacker also used the same method on the Polygon network.
According to our SlowMist AML statistics, MonoX Finance lost close to $34 million, which included about 2.1k WETH, 1.9m WMATIC, 36.1 WBTC, 143.4k MONO, $8.2m USDC, $9.1m USDT, 1.2k LINK, 3.1k GHST, 5.1m DUCK, 4.1k MIM, and 274.9 IMX.
The hacker exploited the MonoX protocol because the same token is used for the tokenIn and tokenOut functions. Since they were able to use the same tokens in the swapTokenFORExactToken function, the updated tokenOut will supersede the tokenIn’s price update. This led MONO token price to increase and be used to purchase most of the assets in the protocol.