Alchemix Missing Solvency Check Bugfix Review

Immunefi Editor
Immunefi
Published in
7 min readNov 30, 2023

Summary

On September 28, 2023, the security researcher @KoiushSec submitted a critical vulnerability to Alchemix via Immunefi, which consisted of missing solvency checks at liquidation. At the time of the submission, the vulnerability would have allowed an attacker with significant upfront capital to create over 7k of unbacked alETH in 2 hours (equivalent to $11,662,140 USD), if it had gone unnoticed.

It is noted that this vulnerability cannot be performed with a flash loan, but requires significant investment from the attacker itself.

After receiving Koiush’s report, the bug was quickly neutralized by Alchemix’s team with no impact to user funds. Alchemix promptly awarded a bounty of 9,100 ACLX ($116,513) to Koiush for this finding.

Immunefi is pleased to have facilitated this responsible disclosure with our platform. Our goal is to make web3 safer by incentivizing whitehats to responsibly disclose bugs and receive clean money and reputation in exchange.

Introduction to Alchemix

Alchemix Finance is a synthetic asset protocol that centers around the idea of tokenizing future yield. It also boasts a governance DAO with a vibrant community. The protocol allows you to access future yield from various yield farming strategies in DeFi. It does this via the issuing of a synthetic token, which represents a fungible claim on valuable assets in the Alchemix protocol left by depositors.

Depositors may claim these assets, which form the basis of underlying collateral backing the synthetic assets issued by the protocol.

The Alchemix protocol features “Alchemists”, which are the central smart contracts powered by AlchemistV2.sol. This allows users to deposit yield-bearing or underlying assets as collateral in the protocol. Multiple yield strategies are supported in this format, but each “Alchemist” is dedicated to issuing a specific alAsset, such as alUSD.

Alchemix smart contracts allows users to borrow only up to 50% of their collateral’s value, ensuring that all borrowing across the protocol is collateralized by at least 200% in valuable yield-bearing assets. The accrued yield is periodically harvested from these assets, which is then put to work reducing the user’s debt or increasing the user’s borrowing limit if no debt is taken.

Users would usually interact with the protocol by depositing assets, borrowing against them, and repaying their debt to unlock their collateral. They can repay their debt using alUSD or other supported stablecoins, or they can take the option of partially liquidating their collateral to repay alUSD debt. This is how the Alchemix protocol provides a flexible line of credit to users based on future yield, giving users the option to access yield without being locked into long-term commitments on their capital.

Synthetic Debt Tokens

Alchemix synthetic debt tokens, or alAssets, represent a user’s future yield and are backed by corresponding underlying assets (e.g., ETH for alETH, or stablecoins for alUSD). Users with Alchemix loans can repay their debt by using either alAssets, or the underlying assets. This helps alAsset prices maintain a near-equivalence to price of the underlying asset.

There are two main ways that make the underlying value of an alAsset realizable:

  1. For individuals that don’t borrow from Alchemix, the value of an alAsset can be realized through the Transmuter, which redeems the underlying asset (like ETH) of an alAsset over time.
  2. Alternatively, alAssets can be sold on the open market to borrowers who want to repay their Alchemix loans. Consequently, alAssets usually trade at a market discount compared to the underlying assets.

The discount from the 1:1 value can be seen as the cost for depositors to access their future yield immediately. The Transmuter, Elixir AMO, and treasury operations work together to ensure sufficient liquidity of alAssets and aim to minimize the market discount, following the guidelines set by governance.

Liquidations

Even if users find themselves without immediate funds to repay a loan, they can still comfortably manage and exit their positions. This is made possible by allowing users to liquidate their collateral assets to repay outstanding debts.

The liquidate function helps do exactly this, ensuring an efficient way to manage liabilities. Given that Alchemix loans are overcollateralized, users should always have sufficient funds within their accounts to cover their debts.

During liquidation, part of the collateral is sold on the market in order to cover the borrower’s debt, or shares. In this process, the amount of yield tokens to be liquidated is calculated with convertSharesToYieldTokens, and is passed to the _unwrap function. The _unwrap function makes a call to the specified yield token’s TokenAdapter contract, which handles the actual liquidation of the yield bearing collateral token to the underlying token of a Vault.

To understand how the TokenAdapter contract handles liquidations, we must first read the storage of the AlchemixV2 contract to locate the adapter contract. The _yieldTokens mapping variable is private, but since all storage on the Ethereum block chain is public, we can still read this value with a little knowledge of how Solidity calculates storage slots.

By running cast storage 0x062Bf725dC4cDF947aa79Ca2aaCCD4F385b13b5c 79363705814783874054998580889613166171273376691577549683321331733153129938612, we get 0x8ec81eb82488a754ffcf21e7387f350e44854ab5 as the address of the rETH adapter.

If we decompile the code with Dedaub’s contract library, we can see an external call is being made to Balancer contract to perform a swap. Normally, a user would pass a proper minimumAmountOut in the liquidation call to ensure the swap returns a sufficient amount of tokens and prevent an attacker from sandwich attacking the liquidation stealing the swapped funds. However, the case where a user sets the minimumAmountOut to 0 and sandwiches their own liquidation was critically overlooked.

Vulnerability Analysis

The vulnerability reported by Koiush describes an issue where liquidation can happen without proper checks to ensure that the outstanding loan does not become undercollateralized, which can cause insolvency.

We can demonstrate the vulnerable scenario in the following steps:

  1. A user starts by depositing 100 stETH into the Alchemix contract and proceeds to mint 50 alETH against it.
  2. The user then attempts to liquidate their stETH collateral by invoking the liquidate function, setting the minimum amount out parameter to 0, which means they are willing to accept any amount of the underlying asset in return.
  3. When the liquidate function is called, it internally calls the unwrap() function with 50 stETH as the input. However, the unwrap function is susceptible to being sandwiched due to the 0 minimum amount out specified.
  4. This leads to a situation where the user can sandwich their own liquidation and cause the amount of the underlying asset received from the unwrap function to be close to 0.

At this juncture, the liquidate() function, which is supposed to use the received assets to pay off the user’s loan, finds itself in a bind as there are virtually no assets to pay off the loan with but continues execution. The result of the _unwrap function is stored in amountUnderlyingTokens, which is checked to not be zero, but can still be very small amounts.

Critically, there is no check to ensure the solvency of the account after the liquidation occurs. There are no checks after the call to _unwrap that ensure the users remaining collateral is enough to cover the outstanding debt.

This means the protocol is still left with 50 alETH of the user’s debt unpaid, despite the liquidation. There would only be 50 stETH remaining as collateral to back that position, since the stETH liquidated in order to pay the debt was sandwich attacked by the user themself.

This leads to the final step where, since the user has sandwich attacked their own liquidation, this essentially allows an attacker to withdraw collateral in a way that bypasses typical solvency checks, and allows an attacker’s collateralization ratio to reach an unhealthy position. Due to deposit and mint limits, and since minimum collateralization requirements limit the profit for each attack to 50% of the collateral value, an attacker must execute the attack multiple times with large amounts of capital to quickly capitalize on the bug and get away with ~3000 alETH per hour. If the vulnerability had gone unnoticed, it would allow for essentially unlimited minting of alETH (although capital for the attack is still required).

Proof of Concept (PoC):

The Immunefi team prepared the following PoC to demonstrate the vulnerability. The attack first manipulates the Balancer pool which is utilized for the unwrapping of rETH in the Alchemix adapter. Then, 20 different accounts are utilized for the attack to bypass individual deposit limits. Each account deposits up to the limit, mints the maximum amount of alETH they can, and then liquidates their own position while the Balancer rate is manipulated. In the real attack, these transactions would need to be individually sandwiched with the Balancer manipulation since Alchemix does not allow non-whitelisted contracts to interact with the liquidation function. However, for simplicity, the PoC uses the single manipulation event to account for all liquidations. After borrowing with all accounts up to the limit and executing liquidations for each account until the collateral value is near zero, the attacker can now manipulate the balancer rate back down to recover the collateral deposited for each account, and is now left with the minted alETH, and original collateral (minus swap fees).

Vulnerability Fix

Alchemix introduced a call to _validate in the liquidation function in an upgrade to the AlchemixV2 contract which ensures the position is not undercollateralized after the unwrapping occurs.

The validate function first checks the updated debt of the account owner, and if any debt exists, ensures the remaining collateral in the account divided by the debt is above the minimum collateralization ratio.

Acknowledgments

We would like to thank Koiush for doing an amazing job and responsibly disclosing such an important bug. Big props also to the Alchemix team who responded quickly to the report and patched the bug.

This bugfix review was written by Immunefi triager, Alejandro Muñoz-McDonald.

If you’d like to start bug hunting, we got you. Check out the Web3 Security Library, and start earning rewards on Immunefi — the leading bug bounty platform for web3 with the world’s biggest payouts.

And if you’re feeling good about your skillset and want to see if you will find bugs in the code, check out the bug bounty program from Alchemix.

--

--

Immunefi Editor
Immunefi

Writing for the premier bug bounty platform of Web3.