Inverse Finance contest - Jeiwan's results

Rethink the way you borrow.

General Information

Platform: Code4rena

Start Date: 25/10/2022

Pot Size: $50,000 USDC

Total HM: 18

Participants: 127

Period: 5 days

Judge: 0xean

Total Solo HM: 9

Id: 175

League: ETH

Inverse Finance

Findings Distribution

Researcher Performance

Rank: 4/127

Findings: 3

Award: $3,554.50

🌟 Selected for report: 1

πŸš€ Solo Findings: 1

Findings Information

🌟 Selected for report: Jeiwan

Labels

bug
2 (Med Risk)
satisfactory
sponsor confirmed
selected for report
M-11

Awards

3397.8465 USDC - $3,397.85

External Links

Lines of code

https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Oracle.sol#L91

Vulnerability details

Impact

Oracle's viewPrice function doesn't report a dampened price until getPrice is called and today's price is updated. This will impact the public read-only functions that call it:

These functions are used to get on-chain state and prepare values for write calls (e.g. calculate withdrawal amount before withdrawing or calculate a user's debt that can be liquidated before liquidating it). Thus, wrong values returned by these functions can cause withdrawal of a wrong amount or liquidation of a wrong debt or cause reverts.

Proof of Concept

// src/test/Oracle.t.sol
function test_viewPriceNoDampenedPrice_AUDIT() public {
    uint collateralFactor = market.collateralFactorBps();
    uint day = block.timestamp / 1 days;
    uint feedPrice = ethFeed.latestAnswer();

    //1600e18 price saved as daily low
    oracle.getPrice(address(WETH), collateralFactor);
    assertEq(oracle.dailyLows(address(WETH), day), feedPrice);

    vm.warp(block.timestamp + 1 days);
    uint newPrice = 1200e18;
    ethFeed.changeAnswer(newPrice);
    //1200e18 price saved as daily low
    oracle.getPrice(address(WETH), collateralFactor);
    assertEq(oracle.dailyLows(address(WETH), ++day), newPrice);

    vm.warp(block.timestamp + 1 days);
    newPrice = 3000e18;
    ethFeed.changeAnswer(newPrice);

    //1200e18 should be twoDayLow, 3000e18 is current price. We should receive dampened price here.
    // Notice that viewPrice is called before getPrice.
    uint viewPrice = oracle.viewPrice(address(WETH), collateralFactor);
    uint price = oracle.getPrice(address(WETH), collateralFactor);
    assertEq(oracle.dailyLows(address(WETH), ++day), newPrice);

    assertEq(price, 1200e18 * 10_000 / collateralFactor);

    // View price wasn't dampened.
    assertEq(viewPrice, 3000e18);
}

Tools Used

Manual review

Consider this change:

--- a/src/Oracle.sol
+++ b/src/Oracle.sol
@@ -89,6 +89,9 @@ contract Oracle {
             uint day = block.timestamp / 1 days;
             // get today's low
             uint todaysLow = dailyLows[token][day];
+            if(todaysLow == 0 || normalizedPrice < todaysLow) {
+                todaysLow = normalizedPrice;
+            }
             // get yesterday's low
             uint yesterdaysLow = dailyLows[token][day - 1];
             // calculate new borrowing power based on collateral factor

#0 - 0xean

2022-11-05T21:47:32Z

well written report that explains the impact of this unlike the others. will leave open for review.

#1 - c4-sponsor

2022-11-15T13:38:13Z

08xmt marked the issue as sponsor confirmed

#2 - 08xmt

2022-11-15T13:39:49Z

#3 - c4-judge

2022-11-28T18:21:00Z

0xean marked the issue as satisfactory

#4 - c4-judge

2022-12-01T15:54:37Z

0xean marked the issue as selected for report

Findings Information

🌟 Selected for report: rbserver

Also found by: 0xRobocop, Ch_301, ElKu, Jeiwan, MiloTruck, Picodes, sam_cunningham

Labels

bug
2 (Med Risk)
disagree with severity
downgraded by judge
satisfactory
sponsor confirmed
duplicate-583

Awards

156.2673 USDC - $156.27

External Links

Lines of code

https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Market.sol#L353-L363

Vulnerability details

Impact

A borrower can quit their position and withdraw full collateral before their DBR deficit is replenished. This leaves the protocol with a bad debt. The borrower can borrow again using a different address and an escrow to avoid replenishing their DBR deficit.

Proof of Concept

// src/test/Market.t.sol
function testQuitWithDBRDeficit_AUDIT() public {
    gibWeth(user, wethTestAmount);
    gibDBR(user, wethTestAmount);

    vm.startPrank(user);
    deposit(wethTestAmount);
    uint borrowAmount = getMaxBorrowAmount(wethTestAmount);
    market.borrow(borrowAmount);

    // Simulating DBR deficit accruing.
    dbr.transfer(address(0), dbr.balanceOf(user));
    vm.warp(block.timestamp + 100);
    uint256 deficit = dbr.deficitOf(user);
    assertEq(deficit, 4312531709791983);

    // User's DBR deficit hasn't been replenished.

    // User repays full debt.
    market.repay(user, market.debts(user));

    // User withdraws full collateral.
    uint256 withdrawAmount = market.getWithdrawalLimit(user);
    market.withdraw(withdrawAmount);
    assertEq(DOLA.balanceOf(user), 0);
    assertEq(WETH.balanceOf(user), wethTestAmount);
    // User has 0 collateral.
    assertEq(market.getCollateralValue(user), 0);
    // User has 0 debt.
    assertEq(market.debts(user), 0);

    // However, user still has the deficit of DBR.
    assertEq(dbr.deficitOf(user), deficit);

    vm.stopPrank();

    // The deficit cannot be replenished because user has 0 collateral.
    vm.expectRevert("Exceeded collateral value");
    market.forceReplenish(user, deficit);
}

Tools Used

Manual review

Consider this change:

--- a/src/Market.sol
+++ b/src/Market.sol
@@ -458,6 +458,7 @@ contract Market {
     @param amount The amount being withdrawn.
     */
     function withdrawInternal(address from, address to, uint amount) internal {
+        require(dbr.deficitOf(from) == 0, "Unreplenished DBR deficit");
         uint limit = getWithdrawalLimitInternal(from);
         require(limit >= amount, "Insufficient withdrawal limit");
         IEscrow escrow = getEscrow(from);

#0 - 0xean

2022-11-05T18:43:15Z

I believe the intention is that someone would call forceReplenish on a user, but certainly isn't guaranteed. Will let sponsor comment before final judging.

#1 - c4-judge

2022-11-05T18:44:02Z

0xean marked the issue as primary issue

#2 - c4-sponsor

2022-11-14T11:13:41Z

08xmt marked the issue as disagree with severity

#3 - 08xmt

2022-11-14T12:08:55Z

This is a Low Severity risk, as it's expected force replenishers will be using private transaction relayers, making them impossible to front run. There is a risk of small amounts of DBR not being replenished (Amounts where incentive is below transaction fee costs).

#4 - c4-sponsor

2022-11-14T12:09:13Z

08xmt marked the issue as sponsor acknowledged

#5 - c4-sponsor

2022-11-25T11:13:29Z

08xmt marked the issue as sponsor confirmed

#6 - 08xmt

2022-11-25T11:15:35Z

#7 - 0xean

2022-11-28T16:19:28Z

@08xmt - while the front running issue is part of it, there is no guarantee that someone calls forceReplenish as you state. I think this issue is highlighting that problem in particular and suggest a simple way to handle the problem.

I am unable to see your PR (since its private), but based on the fact that a fix was implemented, this seems more like a M severity issue. Will circle back but welcome comments before a final ruling.

#8 - c4-judge

2022-11-28T17:55:52Z

0xean changed the severity to 2 (Med Risk)

#9 - c4-judge

2022-11-28T19:35:25Z

0xean marked the issue as satisfactory

#10 - 08xmt

2022-11-30T11:24:37Z

@0xean Fix makes it impossible to withdraw collateral when you have a DBR deficit, which should make shenanigans like this impossible.

#11 - c4-judge

2022-12-07T08:16:07Z

Simon-Busch marked the issue as duplicate of #583

Lines of code

https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Oracle.sol#L6 https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Oracle.sol#L82 https://github.com/code-423n4/2022-10-inverse/blob/3e81f0f5908ea99b36e6ab72f13488bbfe622183/src/Oracle.sol#L116

Vulnerability details

Impact

The Oracle contract calls latestAnswer function of a Chainlink oracle, which was deprecated. In case Chainling stops supporting the oracles with deprecated functions, the Oracle contract will has to be patched and redeployed.

Proof of Concept

Oracle.sol#L4-L7:

interface IChainlinkFeed {
    function decimals() external view returns (uint8);
    function latestAnswer() external view returns (uint); // @audit deprecated function
}

latestAnswer in the API reference documentation of Chainlink

Tools Used

Manual review

Consider updating the interface and using latestRoundData instead of latestAnswer.

#0 - neumoxx

2022-10-31T08:42:12Z

Duplicate of #601

#1 - c4-judge

2022-11-05T17:49:09Z

0xean marked the issue as duplicate

#2 - Simon-Busch

2022-12-05T15:28:18Z

Issue marked as satisfactory as requested by 0xean

#3 - c4-judge

2022-12-07T08:14:13Z

Simon-Busch marked the issue as duplicate of #584

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