Inverse Finance contest - adriro'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: 41/127

Findings: 3

Award: $87.22

QA:
grade-b
Gas:
grade-b

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

Awards

31.4814 USDC - $31.48

Labels

bug
2 (Med Risk)
primary issue
satisfactory
sponsor confirmed
selected for report
M-15

External Links

Lines of code

https://github.com/code-423n4/2022-10-inverse/blob/main/src/Oracle.sol#L87 https://github.com/code-423n4/2022-10-inverse/blob/main/src/Oracle.sol#L121

Vulnerability details

Impact

The Oracle contract normalizes prices in both viewPrices and getPrices functions to adjust for potential decimal differences between feed and token decimals and the expected return value.

However these functions assume that feedDecimals and tokenDecimals won't exceed 18 since the normalization calculation is 36 - feedDecimals - tokenDecimals, or that at worst case the sum of both won't exceed 36.

This assumption should be safe for certain cases, for example WETH is 18 decimals and the ETH/USD chainlink is 8 decimals, but may cause an overflow (and a revert) for the general case, rendering the Oracle useless in these cases.

Proof of Concept

If feedDecimals + tokenDecimals > 36 then the expression 36 - feedDecimals - tokenDecimals will be negative and (due to Solidity 0.8 default checked math) will cause a revert.

In case feedDecimals + tokenDecimals exceeds 36, then the proper normalization procedure would be to divide the price by 10 ** decimals. Something like this:

uint normalizedPrice; if (feedDecimals + tokenDecimals > 36) { uint decimals = feedDecimals + tokenDecimals - 36; normalizedPrice = price / (10 ** decimals) } else { uint8 decimals = 36 - feedDecimals - tokenDecimals; normalizedPrice = price * (10 ** decimals); }

#0 - c4-judge

2022-11-04T23:38:04Z

0xean marked the issue as duplicate

#1 - c4-judge

2022-11-28T15:57:31Z

0xean marked the issue as selected for report

#2 - c4-judge

2022-11-28T19:35:23Z

0xean marked the issue as satisfactory

#3 - neumoxx

2022-11-29T16:36:41Z

@0xean This issue is marked as duplicate of #540 but both are open. Is this a mistake? #540 (my issue btw) also depicts the underflow when feedDecimals + tokenDecimals > 36.

#4 - 0xean

2022-11-29T16:38:39Z

@neumoxx - the extension has some weird behavior where it marks it a dupe but then if its selected for the report, it re-opens it.

I will pass it along to the extension folks, but not an error that I am aware of.

#5 - c4-judge

2022-12-07T08:17:21Z

Simon-Busch marked the issue as not a duplicate

#6 - c4-judge

2022-12-07T08:17:30Z

Simon-Busch marked the issue as primary issue

#7 - 08xmt

2022-12-14T14:17:27Z

#8 - c4-sponsor

2022-12-14T14:17:44Z

08xmt marked the issue as sponsor confirmed

#9 - 08xmt

2022-12-14T14:17:56Z

Also pretty sure this is a dupe

Validate operator is not address(0) in setOperator function of BorrowController

Validate the new operator is not address(0) so that the operator role remains accessible in case of mistakenly setting a zero/empty value.

Duplicated code in Oracle viewPrices and getPrices functions

Consider refactoring duplicate code between these two functions since most of the code is exactly the same except for the difference in the update to the daily low.

Consider using a onlyGov / onlyChair modifier in FED contract

Most of the protected functions in the FED contract have the explicit require to check the sender's role. Consider adding a onlyGov and onlyChair modifier to protect these functions.

Ensure gov and chair addresses in the FED contract are initialized and non-empty

Check that gov and chair are not zero/empty (address(0)) in the contract's constructor and in the functions changeGov and changeChair.

Unused interface functions in Market contract

Both balanceOf functions of IERC20 and IDolaBorrowingRights interfaces aren't used in the scope of this contract and can be safely removed.

Validate _replenishmentIncentiveBps is greater than 0 in Market constructor

Validate _replenishmentIncentiveBps is greater than 0 during initialization to match the semantics defined in the setter setReplenismentIncentiveBps.

Validate gov address is initialized and non-empty in the Market contract

Validate _gov != address(0) in the contract's constructor and the setGov setter. A wrong value here will lead to the contract being ungovernable.

Typo in setReplenismentIncentiveBps function of Market contract

Missing h in the function's name.

Change _totalSupply to private in DolaBorrowingRights contract

The storage variable _totalSupply is defined as public, which will define a _totalSupply() getter, which could cause confusion with the totalSupply() function that properly tracks debt.

Validate _operator address is initialized and non-empty in the DolaBorrowingRights contract

Validate _operator != address(0) in the contract's constructor. A wrong value here will leave role protected functions inaccessible.

#0 - c4-judge

2022-11-08T00:40:42Z

0xean marked the issue as grade-b

Unneeded check in Oracle getPrice function

The twoDayLow > 0 check in line 136 isn't needed since twoDayLow will always take at least the current normalizedPrice value, which is greater than 0 due to the check in line 117.

Move check condition in expansion function of FED contract to save gas

The check in line 93 can be moved up in the stack to early exit the function in case the condition fails in order to save gas.

uint256 newGlobalSupply = globalSupply + amount; require(newGlobalSupply <= supplyCeiling); ... globalSupply = newGlobalSupply; emit Expansion(market, amount);

Use unchecked math in function contraction of FED contract

Lines 110 and 111 can be placed under an unchecked math group since amount <= supply due to check in line 107 and can't possibly underflow these two updates.

Force replenish checks are done twice in Market and DBR contracts

The checks around the deficit value (deficit > 0 and deficit >= amount) are done twice and repeated over the Market and DBR contracts in the functions forceReplenish and onForceReplenish respectively.

Store result of debts[borrower] += amount in borrowInternal function of Market contract

The calculation executed in line 395 is stored in storage and re-read from storage in the next line to check against the credit limit. Store the calculation locally to prevent an unnecessary re-read to storage.

name and symbol variables in DolaBorrowingRights contract can be changed to immutable to save gas

These two variables are defined at construction time and can't be updated. Consider defining them as immutable to avoid reading from storage to save gas.

Use unchecked math in transfer and transferFrom functions of DolaBorrowingRights contract

The updates in lines 172 and 196 can be done using unchecked math since balances[msg.sender] >= amount and balances[from] >= amount due to the checks in lines 171 and 195 respectively.

Store lastUpdated[user] locally to prevent a second read in function accrueDueTokens of DolaBorrowingRights contract

Value is loaded in line 286 and read again in the next line. Store the first read locally to avoid a second sload.

Check if debt is zero in accrueDueTokens to skip unnecessary calculations and save gas

If debt is zero in the accrueDueTokens function (line 285), then operations in lines 287, 288 and 290 can be skipped to save gas.

Use unchecked math in _burn function of DolaBorrowingRights contract

The update in line 374 can be done using unchecked math since balances[from] >= amount due to the check in line 373.

#0 - c4-judge

2022-11-05T23:53:12Z

0xean marked the issue as grade-b

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