Mitigating the vulnerabilities
Now that we’ve covered the two most prominent vulnerabilities that are shared by ERC-4626 compliant vault contracts, non ERC-4626 vault contracts, and vault-like contracts, we will go over the mitigations for each vulnerability. In addition to presenting effective mitigations, we will review ineffective mitigation that still show up in smart contracts, so they may be identified and replaced with proper mitigations. For simplicity, when we refer to smart contracts in this post, we’re referring to all three categories of vault contracts.
Ineffective mitigations for share inflation
The trademark of ineffective mitigations for share inflation is that they may stop or block pieces of the exploit path, but they do not fully prevent the exploit from occurring. These mitigations may even create new exploit paths, thus being unsuccessful at preventing the original share inflation vulnerability.
Requiring a minimum initial deposit
Intuitively, this appears to solve the share inflation vulnerability, but in practice it may not be sufficient. The attempted mitigation prevents the first depositor from supplying a small amount of asset, such as 1 wei. By setting a minimum deposit that is orders of magnitude larger than 1 wei, an attacker would need to directly transfer a larger number of assets to shift the denominator, or the total amount of asset in the contract. For example:
- A new ERC-4626, non ERC-4626, or vault-like contract is deployed with the proposed mitigation, and requires the first depositor to deposit 1 WETH into the contract.
- Alice, the attacker, is the first depositor into the contract and therefore must deposit at least 1 WETH. Alice deposits 1 WETH and receives 1 * 1e18 shares.
If Alice were to attempt to skew the denominator in order to perform share inflation, she would need at least 1e18 * 1e18 (or 1e36) WETH, which is 18 orders of magnitude more WETH than she needed to supply for the original exploit! Therefore, at a first glance this appears to be an effective mitigation to the share inflation vulnerability. However, the exploit path can be modified to make the contract vulnerable once again:
- After depositing 1 WETH into the contract, Alice immediately withdraws 1 WETH - 1 wei from the contract. Now the ratio of shares-to-assets is 1 wei share:1 wei asset, and thus the contract is vulnerable again!
With a simple modification to the original exploit, Alice is able to circumvent the attempted mitigation. Therefore, allowing any untrusted user to be the first depositor makes this mitigation ineffective, as they could immediately withdraw from the contract once they have supplied the minimum initial deposit amount. We’ll discuss how this can be remedied later in the “effective mitigations for share inflation” section.
Requiring a non-zero amount of shares to be returned
By adding a requirement that any deposit must return at least 1 wei share, this prevents an attacker from being able to steal all of another user’s deposit. However, this mitigation does not prevent an attacker from manipulating another user’s deposit in other ways. For example:
- A new ERC-4626, non ERC-4626, or vault-like contract is deployed with the proposed mitigation that requires all deposits to return a non-zero amount of shares.
- Alice, the attacker, sets up the contract with a vulnerable ratio of 1 wei share:1 wei asset.
- Bob, the victim, attempts to deposit 2 WETH into the contract. If the deposit were to happen now, Bob would receive (1 wei share * 2 WETH) / 1 wei asset = 2e18 shares.
- Alice front-runs Bob’s deposit and directly transfers 1 WETH to the contract.
- The calculation for Bob’s deposit is now (1 wei share * 2 WETH) / (1 WETH + 1 wei). Due to decimal truncation, the calculation will return 1 wei share. This calculation passes the non-zero shares check.
- Alice redeems her 1 wei share, which is now worth ~1.5 WETH because there is 3 WETH + 1 wei in the contract and only 2 wei shares have been minted by the contract.
In the above attack scenario, Alice was able to recover her donated 1 WETH and steal 0.5 WETH of Bob’s deposit. While Alice was unable to steal all of Bob’s deposit due to the non-zero shares check, Solidity’s division truncation (which we have previously discussed) allowed her to steal part of the deposit. Therefore, this proposed mitigation does not effectively protect all of a user’s deposit from attackers.
Effective mitigations for share inflation
Now that we’ve gone over the ineffective mitigations, we can talk about the mitigations that are effective in preventing share inflation attacks.
Requiring a minimum initial deposit from a trusted party
From a contract standpoint, this mitigation may result in exactly the same code logic as the ineffective minimum initial deposit mitigation we discussed previously. However, the additional stipulation is what makes this an effective mitigation: a trusted party must make the initial deposit. This ensures the ratio of shares-to-assets amount in the contract is initialized correctly, and is not vulnerable to share inflation. This approach works because the party that made the initial deposit is trusted to not withdraw that deposit, thus keeping the contract away from the vulnerable initial deposit state. Typically, the trusted party is either the creator of the protocol or a trusted third-party liquidity provider. This mitigation can be accomplished in two different ways: the transaction that creates the ERC-4626, non ERC-4626, or vault-like contract and the transaction that provides the initial deposit are bundled together using a tool like Flashbots, or the developers may code additional logic into the protocol to ensure that only the trusted party can make the initial deposit.
‘Burn’ shares during the initial deposit
Another strategy that effectively mitigates share inflation is to ‘burn’ shares as part of the initial deposit. What we mean by ‘burn’ is that some shares that would be owed to the initial depositor are instead transferred (or otherwise accounted to) to an address that no one owns, such as the zero-address. This prevents all depositors from being able to redeem those shares, thus burning those shares. This effectively front-runs the first depositor of the contract, meaning that the first depositor will not be able to set up the contract in such a way that makes the shares-to-underlying-assets ratio vulnerable to share inflation.
As a consequence of burning shares, some amount of deposited assets will also be unredeemable. However, typically only a small number of shares will be burned relative to the magnitude of the underlying deposited asset. This keeps the amount of assets lost due to burning an initial number of shares relatively small.
The YieldBox way: virtual shares
The final effective mitigation for share inflation that we’ll cover is virtual shares. The virtual shares mitigation was first implemented by YieldBox, but was recently proposed and merged into OpenZeppelin’s ERC-4626 contract template implementation. The mitigation works by adding an “offset” to the amount of shares minted and burned by a contract relative to the amount of assets provided. The offset is manually set by the developers, where the offset increases the cost of the share inflation attack by an order of magnitude relative to its value. Therefore, the initial ratio of shares-to-assets for an ERC-4626 contract ends up being 1eX shares:1 wei assets, where X is the value of the offset. This effectively prevents the initial ratio of shares-to-assets in a contract from being skewed, as it will always start out with an offset.
Additionally, by starting with a ratio of shares-to-assets such that the number of shares is greater than the number of assets, those initial shares are effectively ‘burned’. This means the initial depositor loses out on the value of the initial shares burned, where the value lost is relative to the value of the offset. This further disincentivizes an attacker because they are guaranteed to lose out on the value that the burned shares hold.
A word on share inflation
Now that we’ve covered all the effective mitigations for share inflation, we would like to add a word of warning. All of the above mitigations do not completely prevent the share inflation vulnerability, they just make the attack more difficult. For example, if the total supply of a token is large, and an attacker had a large portion of the total supply, it could be possible for the attacker to still cause share inflation. Tokens with an unusually large value for their decimals will also need to take precautions to ensure that appropriately large initial minimum deposits, burned number of shares, or offset values are used.
Mitigating incorrect rounding
The mitigation for incorrect rounding is simple: use a template implementation of ERC-4626, such as the OpenZeppelin ERC-4626 contract template. An ERC-4626 contract template will handle all of the specified rounding directions for you. However, if you decide to implement a vault-like contract instead of a fully-compliant ERC-4626 contract, and need to implement the rounding directions yourself, we have created a visual chart of the ERC-4626 rounding specification to improve readability:
Tips for creating a vault-like contract
After reviewing the shared vulnerabilities and mitigations for ERC-4626 compliant vault contracts, non ERC-4626 vault contracts, and vault-like contracts, let’s now talk briefly about vault-like contracts. If you’re a development team that has fully assessed the risk of implementing a custom vault-like contract instead of using the ERC-4626 standard, and you still want to proceed with the custom implementation, here are a few development tips for you:
- Re-consider using ERC-4626 and relevant template contracts over a custom implementation: Implementing your own custom contract implementation that is vault-like should be considered the exception to the rule. Development teams should consider whether the increased attack surface and risk is worth it, and whether conforming to the ERC-4626 standard is still feasible by simply extending a base template implementation rather than building from the ground up.
- Use similar terminology to the ERC-4626 standard: By using similar terminology like “deposit/mint,” “withdraw/redeem,” and “assets/shares” for relevant functions and state variables, this allows auditors to quickly understand and make comparisons between your custom vault-like implementation and the ERC-4626 standard.
- Document logic that deviates from the ERC-4626 standard: Highlighting where your implementation differs decreases the amount of time that would otherwise be spent by auditors combing through the entire ERC-4626 standard to manually identify conforming and non-conforming implementations.
In this post, we covered various ineffective and effective mitigations for share inflation and incorrect rounding. Additionally, we provided advice for developers who want to implement their own vault-like contracts. This concludes our three part series on the shared vulnerabilities between ERC-4626 compliant vault contracts, non ERC-4626 vault contracts, and vault-like contracts.
About Arbitrary Execution
Arbitrary Execution (AE) is an engineering-focused organization that specializes in securing decentralized technology. Our team of security researchers leverage their offensive security expertise, tactics, techniques, and hacker mindset to help secure the crypto ecosystem. We have experience with a myriad of different security topics and standards, such as the ERC-4626 standard that was featured in this three part deep-dive. For more information on Arbitrary Execution's professional services, contact firstname.lastname@example.org. Follow us on Twitter and LinkedIn for updates on our latest projects and blog posts.