The Standard

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

Users can't ever `burn` all the `EUROs` they have minted

Impact

In some scenarios, the issue may result in the vault's owner not being able to burn enough EUROs to stay safe from getting liquidated

Proof of concept

https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/SmartVaultV3.sol#L160-L167

https://github.com/Cyfrin/2023-12-the-standard/blob/main/contracts/SmartVaultV3.sol#L169-L175

When using the SmartVaultV3::mint() function, the minted storage variable, used to account for the amount of EUROs minted by the vault, is increased by the _amount given as a parameter with a fee value calculated internally.

function mint(address _to, uint256 _amount) external onlyOwner ifNotLiquidated {
uint256 fee = _amount * ISmartVaultManagerV3(manager).mintFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
require(fullyCollateralised(_amount + fee), UNDER_COLL);
minted = minted + _amount + fee;
EUROs.mint(_to, _amount);
EUROs.mint(ISmartVaultManagerV3(manager).protocol(), fee);
emit EUROsMinted(_to, _amount, fee);
}

Only the provided _amount is minted to the user, the fee is minted to the protocol directly.

Let's say for now, the user has minted 100 EUROs and the fees minted to the protocol is 1% (1 EUROs) meaning the minted value equals 101

The function SmartVaultV3::burn() takes a given _amount as parameter and calculates burning fees based upon it with some other fee rates.

Then the minted storage variable is reduced by the given _amount before being burn.

After that, the fees calculated before are sent to the protocol

function burn(uint256 _amount) external ifMinted(_amount) {
uint256 fee = _amount * ISmartVaultManagerV3(manager).burnFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
minted = minted - _amount;
EUROs.burn(msg.sender, _amount);
IERC20(address(EUROs)).safeTransferFrom(msg.sender, ISmartVaultManagerV3(manager).protocol(), fee);
emit EUROsBurned(_amount, fee);
}

The user can't ever get its minted value reach 0 because they will always have to transfer some dust to the protocol resulting in the "loss" of more EUROs than expected.

Let's say the user wants to burn all the EUROs he has minted to remove all his collateral.

Providing 100 EUROs as _amount will result in the minted variable to equal 1 (it still stores the fee minted for the protocol) but most importantly, the transaction will revert as the safeTransferFrom() function will try to transfer 1 EUROs worth of fee from the user which he doesn't have

This results in a bad accounting of the minted value and since this variable is used in the undercollateralised() function, responsible to determine if the vault is eligible to liquidation, a vault might get liquidated earlier than it should.

Tools used

Manual analysis

Recommended mitigation steps

In the burn() function, subtract the fee variable from the minted value and subtract the _amount being burn with the fee

minted = minted - _amount - fee;
EUROs.burn(msg.sender, _amount - fee);
Updates

Lead Judging Commences

hrishibhat Lead Judge almost 2 years ago
Submission Judgement Published
Validated
Assigned finding tags:

fee-loss

hrishibhat Lead Judge almost 2 years ago
Submission Judgement Published
Invalidated
Reason: Design choice
Assigned finding tags:

fee-loss

Support

FAQs

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

Give us feedback!