Smart Vault holds user's collateral at a specified collaterization rate. A user can withdraw their collateral until the minimum collaterization limit is met using SmartVaultV3::removeCollateralNative()
and SmartVaultV3::removeCollateral()
functions. But the checks in the contract does not allow the user to pull the collateral in one transaction, and it can require multiple transactions to pull them all out. This can mislead the user into thinking that either their collateral cannot be withdrawn, and if they somehow understand that it requires multiple transactions, it will require the user to spend a lot of gas and craft a brute force approach to actually pull all of the collateral out.
A user can deposit collateral into SmartVaultV3
, and mint EUROs against it. The amount of EUROs that can be minted will need to ensure that the collaterization is 120% of the minted value at least.
So, if a user deposits X ETH worth of collateral, then the vault will allow minting of Y EUROs token, such that:
So the user should be allow to withdraw any excess collateral based on the number of EURO's token that were minted. There are two functions that let users do this - SmartVaultV3::removeCollateralNative()
and SmartVaultV3::removeCollateral()
.
The two functions use SmartVaultV3::canRemoveCollateral()
to calculate if the vault will be undercollaterized. It does so by following the steps as outlined below:
Calculate the max amount of EUROs that can be minted
Calculate the EUROs equivalent amount of the collateral that the user wants to withdraw
If the first amount is atleast the second amount, AS WELL AS, if the difference between the amounts is atleast the already minted amount, then its ok to withdraw the collateral
The problem is in the first condition, where it ensures that the first amoutn is atleast the second amount.
The first amount takes into account the collaterization rate and the current balances, which can come out to be less value that the EUROs amount of the collaterization that the user wants to remove. This is best explained with an example below:
Withdraw max collateral in a single attempt:
Withdraw max collateral in multiple attempts:
The above example demonstrate how the accounting is incorrect to ensure that minimum collaterization requirements are met when the user wants to withdraw their collateral.
Drop the below test in test/smartVault.js
(The test is using describe.only
, so when you run the test, then only this test will be run)
There are varying impact of this issue based on the user:
A user might attempt to remove their collateral once, and the vault might just return part of their collateral. The uninformed user might not realize that they will need to execute multiple transactions to get their collateral
A user will need to spend a lot of gas, and creative brute forcing to get all of their collateral out
Manual Review
The logic that verified that the vault will not be undercollaterized upon removal of collateral will need revision. This might also impact other functions of the contract, and I will encourage the protocol to test it thoroughly as this is one of the core logic that will be updated. Below is a suggestion on the alternative approach:
When the user requests for removal of the collateral, then evaluate if the final state of the vault meets the collaterization requirement.
Below is how the code can look like for checking ETH collateral. This will need to be further adapted to work with all the supported ERC20 tokens.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.