Hello everyone, this is Yannis Bollanos, Security Researcher at Dedaub. A few days ago, we published a tweet about the @thestandard_io exploit that took place on November 6th, 2023, which you can find here: https://twitter.com/dedaub/status/1734598398055981471.
The positive response from the X audience indicates a strong interest in the topic. As a result, I have decided to expand it into a blog post that can be used as a reference in the future.
Thestandard_io exploit occurred on November 6th, 2023, and according to Crypto.news, approximately 280K EUROs were at risk. Fortunately, most of the funds have been recovered, so this is a hack story with a happy ending.
After the excitement and tension of the moment subside, it is important to reflect on what happened and how we can prevent similar attacks in the future. It’s a great opportunity to re-emphasize that protocols should use defensive checks/assertions at every point their code interacts with a decentralized exchange (DEX).
The @thestandard_io protocol issues coins to users who open over-collateralized positions, helping the protocol's assets maintain a stable value by adjusting liquidity provision by actual market rates.
In the @thestandard_io attack scenario, a SmartVault contract oversees the management of each user's position, taking responsibility for adequately verifying the position's liquidity. Users can issue coins by calling `mint`:
The SmartVault allows the exchanging of deposited collateral tokens through Uniswap's V3 router (0xe592427a0aece92de3edee1f18e0157c05861564 on Arbitrum). Here is where things get interesting:
SmartVaultV2 - Arbitrum - Source code (0x2E9f9Cc46679DBb5D94a1397Bd922cA5F6dA99Cd) a smart contract deployed on Arbitrum.
One may inspect the source code in the (Dedaub) Contract Library for SmartVaultV2 - 0x2E9f9Cc46679DBb5D94a1397Bd922cA5F6dA99Cd. Below is the screenshot.
The things to note are:
With amountOutMinimum set to 0, the swap operation would succeed no matter the extent of the slippage incurred.
There were no other safeguards in place to ensure a fair exchange for the value provided in the contract.
This enabled the owner of the vault contract to initiate a swap on a pool that might have been maliciously 'tilted,' allowing for an exchange at an arbitrarily different price from the market price.
There are two ways to profit from this:
- (1) Utilize a flash loan and purposely sandwich the swap operation between a tilting and an un-tiling swap on the pool. This is a fairly typical attack pattern commonly used in exploits.
- (2) Have the swap operation occur on a pool, the liquidity of which (as well as the execution price) is entirely controlled by the attacker. This can be done only on freshly created pools or in pools with near-0 liquidity.
The attacker chose option (2) since a Uniswap V3 pool for PAXG-WBTC didn't exist then. Here's how everything is put together to form the attack:
- The attacker creates the Uniswap v3 PAXG-WBTC pool
- The attacker flash borrows 10 WBTC ( and a tiny extra amount to provide as initial liquidity)
- The attacker provides 10 WBTC as collateral and mints as many EUROs as possible
One may inspect the relevant transaction in the (Dedaub) Contract Library for 0x51293c1155a1d33d8fc9389721362044c3a67e0ac732b3a6ec7661d47b03df9f - Arbitrum. Below is the screenshot.
The attacker provides liquidity to the PAXG/WBTC pool. WBTC and PAXG are at a 1:1 ratio within the tick range in which liquidity is minted. This is over-valuing PAXG by a lot.
The attacker swaps the deposited WBTC for PAXG, and the swap operation goes through the attacker-controlled pool. The vault is now under-collateralized, in terms of real value: the PAXG it obtained has much less value than the EUROs issued.
The attacker then burns all of his liquidity on Uniswap, and he notably receives ~9.9 WBTC. At this point, the attacker still holds the originally minted EUROs.
The attacker swaps 10k of his EUROs for USDCs. Some USDCs are then employed to obtain the few remaining WBTCs needed to repay the flash loan.
In the end, the attacker walks away with 280k EUROs and ~8.5k USDC.
Fortunately, the attacker has returned ~240k EUROs back to the protocol:
One may inspect the relevant transaction in the (Dedaub) Contract Library for 0xb08633c44d5f7c6fc10ad5685642c54e97900165bd1d64a1d003c99d1eec9a4b - Arbitrum. Below is the screenshot.
Smart Contract developers should not solely rely on assumptions about on-chain liquidity/asset prices. The code should consistently enforce these assumptions (within a reasonable deviation).