The Standard

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

Funds can be borrowed for free as min checks are missing in the `mint`, `burn` and `swap` function

Summary

There is no interest implemented into the system, but when a borrower wants to take a loan via the mint function, pay it back via the burn function or trade with the locked up collateral via the swap function, a fee is charged. Borrowers can avoid paying this fee by minting, burning, or swapping a dust amount multiple times and therefore borrowing funds for free and for an infinite amount of time, as long as the given collateral is sufficient.

Vulnerability Details

Here we can see the mint function and the fee calculation:

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

As we can see, there is no minimum amount check implemented, and therefore the fee amount will round down to zero if the given amount is low enough.

The following POC can be implemented in the smartVault test file and showcases how fees can be avoided by minting a dust amount multiple times:

describe("mint_dust_amount", async () => {
it("mint_dust_amount", async () => {
const collateral = ethers.utils.parseEther("1");
await user.sendTransaction({ to: Vault.address, value: collateral });
for (let i = 0; i < 100; i++) {
await Vault.connect(user).mint(user.address, 100);
}
// the full amount was minted without fees
const { minted } = await Vault.status();
expect(minted).to.equal(100 * 100);
});
});

The same applies to the burn and swap function:

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);
}
function swap(bytes32 _inToken, bytes32 _outToken, uint256 _amount) external onlyOwner {
uint256 swapFee = _amount * ISmartVaultManagerV3(manager).swapFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
address inToken = getSwapAddressFor(_inToken);
uint256 minimumAmountOut = calculateMinimumAmountOut(_inToken, _outToken, _amount);
ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
tokenIn: inToken,
tokenOut: getSwapAddressFor(_outToken),
fee: 3000,
recipient: address(this),
deadline: block.timestamp,
amountIn: _amount - swapFee,
amountOutMinimum: minimumAmountOut,
sqrtPriceLimitX96: 0
});
inToken == ISmartVaultManagerV3(manager).weth() ?
executeNativeSwapAndFee(params, swapFee) :
executeERC20SwapAndFee(params, swapFee);
}

Impact

Borrowers can avoid paying fees and as there is no interest implemented in the protocol borrow funds for free and for an infinite amount of time, as long as the given collateral does not go below the collateral rate.

Recommendations

Add minimum amount checks to the mint, burn and swap functions.

Updates

Lead Judging Commences

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

fee-loss

cosine Submitter
over 1 year ago
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.