Summary
User deposits 1 ETH collateral so they can mint some EUROs. They mint 100 EUROs. If they try to repay and burn the 100 EUROs, the transaction fails because it will first burn the entire 100e18 EUROs balance, and then attempt to send the 0.5e18 fee, but there is no balance left to send, so SmartVaultV3::burn()
reverts anytime _amount = minted
. Burning the full EUROs amount should be a valid and quick way to repay all debt, but it reverts.
PoC
Put in smartVault.js
inside burning
test. Run with npx hardhat test --grep "cannot burn mintedValue"
describe('burning', async () => {
it('cannot burn mintedValue', async () => {
const collateral = ethers.utils.parseEther('1');
await user.sendTransaction({to: Vault.address, value: collateral});
const burnedValue = ethers.utils.parseEther('50');
let burn = Vault.connect(user).burn(burnedValue);
const mintedValue = ethers.utils.parseEther('100');
await Vault.connect(user).mint(user.address, mintedValue);
const mintingFee = mintedValue.mul(PROTOCOL_FEE_RATE).div(HUNDRED_PC);
await EUROs.connect(user).approve(Vault.address, mintingFee);
burn = Vault.connect(user).burn(mintedValue);
await expect(burn).to.be.reverted;
burn = Vault.connect(user).burn(ethers.utils.parseEther('99'));
await expect(burn).not.to.be.reverted;
});
});
Recommendations
Remove the ifMinted
modifier and replace with a require statement for the _amount + fee
passed in to be less than or equal to minted
.
- modifier ifMinted(uint256 _amount) {
- require(minted >= _amount, "err-insuff-minted");
- _;
- }
- function burn(uint256 _amount) external ifMinted(_amount) {
+ function burn(uint256 _amount) external {
uint256 fee = _amount * ISmartVaultManagerV3(manager).burnFeeRate() / ISmartVaultManagerV3(manager).HUNDRED_PC();
+ require(_amount + fee <= minted, "err-too-much-burn");
minted = minted - _amount;
EUROs.burn(msg.sender, _amount);
IERC20(address(EUROs)).safeTransferFrom(msg.sender, ISmartVaultManagerV3(manager).protocol(), fee);
emit EUROsBurned(_amount, fee);
}