The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: medium
Invalid

Lack Of Collateralisation Check In `burn()` Function:

Summary

A user can burn their EUROs if their collateral ratio falls below the minimum collateral requirement.

Vulnerability Details

In SmartVaultV3, the burn() function does not check if the position is healthy or not. A user could burn its EUROs before being liquidated. The user can then remove their collateral or open a new position.

Bob deposits WBTC as collateral and mints EUROs.
Bob monitors the mempool to front-run any call to the runLiquidation() function with the tokenId of his vault as an argument.
The WBTC price falls sharply, lowering Bob’s collateral value, and now he is under-collateralized.
Alice calls runLiquidation() to liquidate Bob’s vault, but Bob front-runs Alice’s transaction and burns his EUROs.
Alice’s call reverts because vault.underCollateralised() in liquidateVault() function returns false.
Bob successfully avoids liquidation and can then either mint new EUROs from a newly created vault or remove his collateral.

Impact

By front-running any call to the runLiquidation() function with the tokenId of their own vault, a user can constantly avoid liquidation, enabling the user to engage in a kind of 'risk-free trade'. This practice prevents the distribution of fees and liquidation bonuses, which are intended to incentivize liquidators to act promptly, thereby avoiding any bad debt to the system.

Proof of Concept

Overview:

Briefly describe the vulnerability.

Actors:

  • user: User is under-collateralized, removes his collateral by front-runnning the liquidation transaction.

  • protocol: Tries to liquidate user's unhealthy position.

Working Test Case

Add the following test to smartVault.js inside describe('burning') block.

it('allows burning of EUROs if undercollateralised', async () => {
const collateral = ethers.utils.parseEther('1');
const mintedValue = ethers.utils.parseEther('100');
// Mint 100 EUROs after depositing 1 ETH as collateral
await user.sendTransaction({to: Vault.address, value: collateral});
await Vault.connect(user).mint(user.address, mintedValue);
// Drop price, now vault is liquidable
await ClEthUsd.setPrice(100000000000);
// Burn EUROs to avoid liquidation
const burningFee = mintedValue.mul(PROTOCOL_FEE_RATE).div(HUNDRED_PC);
const burnedValue = mintedValue.sub(burningFee);
await EUROs.connect(user).approve(Vault.address, burningFee);
burn = Vault.connect(user).burn(burnedValue);
await expect(burn).not.to.be.reverted;
// Not liquidable even if vault was under-collateralized
await expect(VaultManager.connect(protocol).liquidateVault(1)).to.be.revertedWith('vault-not-undercollateralised');
});

Tools Used

Manual review

Recommendations

Add the following check in the burn() function to avoid any frontrunning during the liquidation process:

require(!undercollateralised(), "err-liquidatable");
Updates

Lead Judging Commences

hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Validated
Assigned finding tags:

access-control

ljj Auditor
over 1 year ago
billobaggebilleyan Auditor
over 1 year ago
0xasen Auditor
over 1 year ago
georgishishkov Auditor
over 1 year ago
00xSEV Auditor
over 1 year ago
hrishibhat Lead Judge
over 1 year ago
hrishibhat Lead Judge over 1 year ago
Submission Judgement Published
Invalidated
Reason: Design choice

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.