The Standard

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

Not burning enough `EUROs` at liquidation of smartVault

Summary

runLiquidation is not burning enough EUROs at liquidation of SmartVault which will lead to undercollateralization

Vulnerability Details

When there is liquidation of a vault, we are setting minted = 0 then we should burn the amount of EUROs that vault had minted inorder to keep the protocol overcollateralized . We are taking % of EUROs from all the stakers to cover that minted EUROs, but that is not equal to the amount of minted EUROs in liquidated vault

function liquidate() external onlyVaultManager {
require(undercollateralised(), "err-not-liquidatable");
liquidated = true;
@> minted = 0;
liquidateNative();
ITokenManager.Token[] memory tokens = getTokenManager().getAcceptedTokens();
for (uint256 i = 0; i < tokens.length; i++) {
if (tokens[i].symbol != NATIVE) liquidateERC20(IERC20(tokens[i].addr));
}
}
function distributeAssets(ILiquidationPoolManager.Asset[] memory _assets, uint256 _collateralRate, uint256 _hundredPC) external payable {
consolidatePendingStakes();
//
// code......
//
(,int256 assetPriceUsd,,,) = Chainlink.AggregatorV3Interface(asset.token.clAddr).latestRoundData();
uint256 _portion = asset.amount * _positionStake / stakeTotal;
uint256 costInEuros = _portion * 10 ** (18 - asset.token.dec) * uint256(assetPriceUsd) / uint256(priceEurUsd)
* _hundredPC / _collateralRate;
if (costInEuros > _position.EUROs) {
_portion = _portion * _position.EUROs / costInEuros;
costInEuros = _position.EUROs;
}
_position.EUROs -= costInEuros;
rewards[abi.encodePacked(_position.holder, asset.token.symbol)] += _portion;
burnEuros += costInEuros;
if (asset.token.addr == address(0)) {
nativePurchased += _portion;
} else {
IERC20(asset.token.addr).safeTransferFrom(manager, address(this), _portion);
}
}
}
}
positions[holders[j]] = _position;
}
@> if (burnEuros > 0) IEUROs(EUROs).burn(address(this), burnEuros);
returnUnpurchasedNative(_assets, nativePurchased);
}

Here is how this will work(POC)

  1. User deposited 1 eth at 1000 usd price

  2. maxMintable will be 833.3 euro at 120% collateralRate and at 1.0 conversion rate from usd to euro(for simplicity)

  3. User max minted his euros ie 833.3

  4. Price of eth dropped by 10% to 900 usd and vault became undercollateralized
    Above 4 points can be anything but the core is vault should be undercolleralized

  5. Now, there are 2 staker in LiquidityPool, S1 has 1000 TST & 2000 EUROs while S2 has 4000 TST & 3000 EUROs

  6. portion that S1 will get is 1/4 ie 25% of 1 eth, similarly S2 will get 3/4 of 1 eth

uint256 _portion = asset.amount * _positionStake / stakeTotal;
  1. costInEuro for that portion of S1 will be 187.5 euro while for S2 will be 562.5 euro

uint256 costInEuros = _portion * 10 ** (18 - asset.token.dec) * uint256(assetPriceUsd) / uint256(priceEurUsd)
* _hundredPC / _collateralRate;
  1. total euro that stakers are giving is 187.5 + 562.5 = 750 euros, but vault have minted 833.3 euro which is ~10% less

if (burnEuros > 0) IEUROs(EUROs).burn(address(this), burnEuros);

Every time a vault will be liquidated, this will happen

Impact

We'll have more EUROs minted than backing it, system will eventually become overcollateralized

Tools Used

Manual Review

Recommendations

One thing that can be done is to reduce discount that stakers are receiving or protocol can pay the extra euros

Updates

Lead Judging Commences

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

fee-loss

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

Bad-debt

Support

FAQs

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