Ethos Reserve contest - koxuan's results

A CDP-backed stablecoin platform designed to generate yield on underlying assets to establish a sustainable DeFi stable interest rate.

General Information

Platform: Code4rena

Start Date: 16/02/2023

Pot Size: $144,750 USDC

Total HM: 17

Participants: 154

Period: 19 days

Judge: Trust

Total Solo HM: 5

Id: 216

League: ETH

Ethos Reserve

Findings Distribution

Researcher Performance

Rank: 10/154

Findings: 3

Award: $3,838.19

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: hyh

Also found by: Koolex, Parad0x, chaduke, hansfriese, jasonxiale, koxuan

Labels

bug
3 (High Risk)
satisfactory
edited-by-warden
duplicate-288

Awards

1140.5041 USDC - $1,140.50

External Links

Lines of code

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultV2.sol#L399-L402 https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Vault/contracts/ReaperVaultV2.sol#L364-L366

Vulnerability details

Impact

At the end of _withdraw, if vault balance after withdrawal from strategies is not enough to cover for user share value, user share value will be capped at vault balance after withdrawal from strategies. I believe that the cap is meant to limit the maximum number of shares that user can withdraw depending on the availability of funds in vault and strategies. However, the limit is implemented incorrectly and instead causes loss of funds to user as the amount they receive is lesser than what their shares are worth.

Proof of Concept

In _withdraw for ReaperVaultV2, we can see here that value is capped at whatever vaultBalance has after withdrawing all available funds from all strategies. I believe the intention for this is to limit the number of shares user can redeem, but instead the shares are burned and the share value is capped to the available vaultBalance after withdrawal from strategies.

            vaultBalance = token.balanceOf(address(this));
            if (value > vaultBalance) {
                value = vaultBalance;
            }

Front part of withdraw that shows how user share value is calculated. Note that value has the loss of the strategies being deducted from it when withdrawing from strategies.

        require(_shares != 0, "Invalid amount");
        value = (_freeFunds() * _shares) / totalSupply();
        _burn(_owner, _shares);

Tools Used

Manual Review

Recommend reverting in the event user tries to withdraw an amount of share that is more than what is available. Another way is to calculate the amount available from all strategies and limit the user to only withdraw the number of shares that vault and strategies can allow.

#0 - c4-judge

2023-03-09T14:38:33Z

trust1995 marked the issue as duplicate of #723

#1 - c4-judge

2023-03-09T14:38:38Z

trust1995 marked the issue as satisfactory

Findings Information

🌟 Selected for report: koxuan

Also found by: fs0c

Labels

bug
2 (Med Risk)
downgraded by judge
primary issue
satisfactory
selected for report
M-10

Awards

2636.4342 USDC - $2,636.43

External Links

Lines of code

https://github.com/code-423n4/2023-02-ethos/blob/main/Ethos-Core/contracts/StabilityPool.sol#L575-L595

Vulnerability details

Impact

P is asserted to never be zero in _updateRewardSumAndProduct which is called for every liquidation. However, there is an edge case that can cause P to be zero, causing DOS to certain liquidations.

Proof of Concept

In _updateRewardSumAndProduct notice assert(newP > 0);. However, the SCALE_FACTOR check is insufficient in ensuring P is always more than zero. Three cycles of very small P can cause P to drop to zero and hence causing revert for that particular liquidation of troves. POC steps are as follows,

  1. First liquidation newProductFactor is 1e2, 1e18*1e2/1e18 = 1e2, since its less than scale factor. 1e9 is multiplied before dividing with 1e18. P = 1e11 after 1st liquidation cycle.

  2. Second liquidation newProductFactor is 1e3, 1e11*1e3/1e18 = 0, since its less than scale factor. 1e9 is multipled. P = 1e5

  3. Third liquidation newProductFactor is 1e3, 1e5*1e3/1e8 = 0, since its less than scale factor, 1e9 is multipled. However, even after 1e9 is multiplied it will result in 1e17, which is less than DECIMAL_PRECISION of 1e18. P = 0.

  4. Revert due to assert(newP > 0);.

        // If the Stability Pool was emptied, increment the epoch, and reset the scale and product P
        if (newProductFactor == 0) {
            currentEpoch = currentEpochCached.add(1);
            emit EpochUpdated(currentEpoch);
            currentScale = 0;
            emit ScaleUpdated(currentScale);
            newP = DECIMAL_PRECISION;


        // If multiplying P by a non-zero product factor would reduce P below the scale boundary, increment the scale
        } else if (currentP.mul(newProductFactor).div(DECIMAL_PRECISION) < SCALE_FACTOR) {
            newP = currentP.mul(newProductFactor).mul(SCALE_FACTOR).div(DECIMAL_PRECISION); 
            currentScale = currentScaleCached.add(1);
            emit ScaleUpdated(currentScale);
        } else {
            newP = currentP.mul(newProductFactor).div(DECIMAL_PRECISION);
        }


        assert(newP > 0);
        P = newP;


        emit P_Updated(newP);
    }

Reasons for putting high is because

  1. It breaks the invariant of P being > 0 which according to the docs would break deposit tracking when Pool is not empty.

  2. Even though in some lucky cases, smaller liquidation can be made if batch liquidation is done, in the event that P is at a precarious place whereby all permutations of liquidations done will result in P being 0, liquidation will be DOSed which can be detrimental to protocol as they cannot liquidate bad debt and hence might lead it to insolvency.

Tools Used

Manual Review

Recommend setting SCALE_FACTOR to 1e18, even though the docs did explain that 1e9 is used instead of 1e18 to ensure negligible precision loss, the alternative option is redesigning the mitigation mechanism of rounding error for P.

#0 - c4-judge

2023-03-10T10:11:11Z

trust1995 marked the issue as unsatisfactory: Invalid

#1 - trust1995

2023-03-10T10:12:11Z

Code will not allow P == 0 , so it's not really an issue. Even if they change to 1e18 SCALE_FACTOR, a similar situation could occur.

#2 - vkabc

2023-03-22T07:07:32Z

Hi @trust1995, this is a duplicate of #444. Both POCs are also similar whereby both requires 3 liquidations to result in newP being 0.

#3 - c4-judge

2023-03-22T10:26:15Z

trust1995 marked the issue as satisfactory

#4 - c4-judge

2023-03-22T10:26:25Z

trust1995 marked the issue as duplicate of #444

#5 - c4-judge

2023-03-22T10:34:14Z

trust1995 changed the severity to 2 (Med Risk)

#6 - c4-judge

2023-03-22T10:35:10Z

trust1995 marked the issue as selected for report

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter