The Standard

The Standard
DeFiHardhat
20,000 USDC
View results
Submission Details
Severity: low
Valid

Vault owners can mint without paying fee due to lack of check on minimum mint amount

Summary

It is possible for vault owners to mint stablecoins without paying minting fee, by minting a minimum amount. This leads to losses for the protocol that earns fees from loans.

Vulnerability Details

Whenever vault owners borrow stablecoins on the protocol, they pay a minting fee calculated by contracts/SmartVaultV3.sol from the mint function :

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);
}

in this way:

uint256 fee = _amount * ISmartVaultManagerV3(manager).mintFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();

However, it's possible for vault owners to bypass paying fee because this function does not consider such a scenario.

POC. Owners can achieve this by minting an amount that takes advantage of Solidity rounding down. This amount can be calculated from the above formula like this:

uint256 minimumMintAmount = (ISmartVaultManagerV3(manager).HUNDRED_PC() - 1)/ ISmartVaultManagerV3(manager).mintFeeRate();

This minimumMintAmount yields ZERO protocol fee.

Impact

The protocol owner looses fee that is earned from mints. This test demonstrates how this vulnerability can be exploited:

describe('mint without paying fees', async () => {
it('allows removal of native currency if owner and it will not undercollateralise vault', async () => {
const value = ethers.utils.parseEther('1');
const half = value.div(2);
await user.sendTransaction({to: Vault.address, value});
let { collateral, maxMintable } = await Vault.status();
expect(getCollateralOf('ETH', collateral).amount).to.equal(value);
// mint max euros
const minimumMintable = HUNDRED_PC.sub(1).div(PROTOCOL_FEE_RATE);
const mintingFee = minimumMintable.mul(PROTOCOL_FEE_RATE).div(HUNDRED_PC);
expect(mintingFee).to.equal(0);
console.log(minimumMintable);
await Vault.connect(user).mint(user.address, minimumMintable);
});
});

Tools Used

Manual review:

Recommendations

Add a minimum mint amount that always yields fee to be paid to the protocol.

  1. A possible recommendation would be to a certain percentage to the minimumMint amount as calculated from above e.g.

uint256 minimumMintAmount = (ISmartVaultManagerV3(manager).HUNDRED_PC() - 1)/ ISmartVaultManagerV3(manager).mintFeeRate();
require(_amount >= minimumMintAmount + (minimumMintAmount * MINIMUM_MINT_PERCENT),"Below Minimum Mint Amount");
  1. Have a hard-code minimum mint amount and check that this is not breached:

require(_amount >= minimumMintAmount ,"Below Minimum Mint Amount");
Updates

Lead Judging Commences

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

mint-precision

Support

FAQs

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