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
Rank: 41/127
Findings: 3
Award: $87.22
🌟 Selected for report: 1
🚀 Solo Findings: 0
🌟 Selected for report: adriro
Also found by: 8olidity, BClabs, CertoraInc, Chom, Franfran, Lambda, RaoulSchaffranek, Ruhum, codexploder, cryptphi, eierina, joestakey, kaden, neumo, pashov, rvierdiiev, sorrynotsorry
31.4814 USDC - $31.48
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
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.
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
🌟 Selected for report: 0x1f8b
Also found by: 0xNazgul, 0xSmartContract, Aymen0909, B2, Bnke0x0, Deivitto, Diana, Dinesh11G, ElKu, JC, Josiah, Rahoz, RaymondFam, ReyAdmirado, Rolezn, Waze, __141345__, adriro, aphak5010, brgltd, c3phas, c7e7eff, carlitox477, cducrest, ch0bu, chrisdior4, cryptonue, cryptostellar5, cylzxje, d3e4, delfin454000, enckrish, evmwanderer, fatherOfBlocks, gogo, hansfriese, horsefacts, immeas, leosathya, lukris02, neumo, oyc_109, pedr02b2, rbserver, robee, rotcivegaf, rvierdiiev, sakshamguruji, shark, simon135, tnevler, trustindistrust, wagmi
36.7345 USDC - $36.73
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.
viewPrices
and getPrices
functionsConsider 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.
onlyGov
/ onlyChair
modifier in FED
contractMost 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.
gov
and chair
addresses in the FED
contract are initialized and non-emptyCheck that gov
and chair
are not zero/empty (address(0)
) in the contract's constructor and in the functions changeGov
and changeChair
.
Both balanceOf
functions of IERC20
and IDolaBorrowingRights
interfaces aren't used in the scope of this contract and can be safely removed.
_replenishmentIncentiveBps
is greater than 0 in Market
constructorValidate _replenishmentIncentiveBps
is greater than 0 during initialization to match the semantics defined in the setter setReplenismentIncentiveBps
.
gov
address is initialized and non-empty in the Market
contractValidate _gov != address(0)
in the contract's constructor and the setGov
setter. A wrong value here will lead to the contract being ungovernable.
setReplenismentIncentiveBps
function of Market
contractMissing h in the function's name.
_totalSupply
to private in DolaBorrowingRights
contractThe 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.
_operator
address is initialized and non-empty in the DolaBorrowingRights
contractValidate _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
🌟 Selected for report: pfapostol
Also found by: 0x1f8b, 0xRoxas, 0xSmartContract, Amithuddar, Aymen0909, B2, Bnke0x0, Chandr, CloudX, Deivitto, Diana, Dinesh11G, ElKu, HardlyCodeMan, JC, JrNet, KoKo, Mathieu, Ozy42, Rahoz, RaymondFam, ReyAdmirado, Rolezn, Shinchan, __141345__, adriro, ajtra, aphak5010, ballx, c3phas, carlitox477, ch0bu, chaduke, cryptostellar5, djxploit, durianSausage, enckrish, exolorkistis, fatherOfBlocks, gogo, horsefacts, kaden, karanctf, leosathya, martin, mcwildy, oyc_109, ret2basic, robee, sakman, sakshamguruji, shark, skyle, tnevler
19.0072 USDC - $19.01
getPrice
functionThe 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.
expansion
function of FED
contract to save gasThe 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);
contraction
of FED
contractLines 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.
Market
and DBR
contractsThe 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.
debts[borrower] += amount
in borrowInternal
function of Market
contractThe 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 gasThese 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.
transfer
and transferFrom
functions of DolaBorrowingRights
contractThe 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.
lastUpdated[user]
locally to prevent a second read in function accrueDueTokens
of DolaBorrowingRights
contractValue is loaded in line 286 and read again in the next line. Store the first read locally to avoid a second sload.
accrueDueTokens
to skip unnecessary calculations and save gasIf debt is zero in the accrueDueTokens
function (line 285), then operations in lines 287, 288 and 290 can be skipped to save gas.
_burn
function of DolaBorrowingRights
contractThe 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