Platform: Code4rena
Start Date: 14/09/2022
Pot Size: $50,000 USDC
Total HM: 25
Participants: 110
Period: 5 days
Judge: hickuphh3
Total Solo HM: 9
Id: 162
League: ETH
Rank: 26/110
Findings: 4
Award: $253.02
🌟 Selected for report: 0
🚀 Solo Findings: 0
155.5605 USDC - $155.56
https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/Vault.sol#L407 https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/Vault.sol#L419
When division operation is done before multiplication, the resultant can be a zero value output.
entitledAmount = amount.divWadDown(idFinalTVL[id]).mulDivDown( idClaimTVL[id], 1 ether );
In above line of codes, division is performed before multilication.
Manual code review.
It is recommended to multiply before doing division.
#0 - HickupHH3
2022-10-29T03:30:58Z
partial credit as the math is actually doing multiplication before division: see https://github.com/code-423n4/2022-09-y2k-finance-findings/issues/190#issuecomment-1281775341
however, the issue is valid because the divisor can be large enough to cause the output to be zero, as #378 explains.
8.0071 USDC - $8.01
The calculated price1
could be negative or outdated one.
This could affects the codes places wherever the latestRoundData
is used to determine the price.
one of the place is in Controller.sol#L261 - function getLatestPrice(address _token)
function latestRoundData() public view returns ( uint80 roundID, int256 nowPrice, uint256 startedAt, uint256 timeStamp, uint80 answeredInRound ) { ( uint80 roundID1, int256 price1, uint256 startedAt1, uint256 timeStamp1, uint80 answeredInRound1 ) = priceFeed1.latestRoundData(); int256 price2 = getOracle2_Price(); if (price1 > price2) { nowPrice = (price2 * 10000) / price1; } else { nowPrice = (price1 * 10000) / price2; } int256 decimals10 = int256(10**(18 - priceFeed1.decimals())); nowPrice = nowPrice * decimals10; return ( roundID1, nowPrice / 1000000, startedAt1, timeStamp1, answeredInRound1 ); }
In above function, the price1
is directly get from the latestRoundData
of the chainlink
oracle.
The returned price value could be negative or outdated one.
VS code
To calculate the price1
, use the function getOracle1_Price
that is already implemented. It has all the checks for negative and outdated.
https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/oracles/PegOracle.sol#L89
#0 - 3xHarry
2022-09-21T12:04:11Z
@MiguelBits seems to be valid
#1 - MiguelBits
2022-09-21T18:25:06Z
It is, implementing this.
#2 - HickupHH3
2022-10-17T04:27:39Z
dup of #61
🌟 Selected for report: Respx
Also found by: 0x1f8b, 0xDecorativePineapple, 0xNazgul, 0xPanas, 0xSmartContract, 0xc0ffEE, 0xmuxyz, Aymen0909, Bahurum, Bnke0x0, CodingNameKiki, Deivitto, Jeiwan, Lambda, Picodes, PwnPatrol, R2, RaymondFam, Rolezn, Ruhum, Saintcode_, SooYa, Tointer, V_B, ajtra, ak1, async, auditor0517, brgltd, c3phas, carrotsmuggler, cccz, csanuragjain, datapunk, djxploit, durianSausage, eierina, erictee, gogo, imare, joestakey, jonatascm, kv, ladboy233, leosathya, lukris02, oyc_109, pashov, pauliax, rbserver, robee, rokinot, rvierdiiev, scaraven, simon135, unforgiven, wagmi, zzzitron
36.6223 USDC - $36.62
Due to buggy front end or user error, the deposited asset can be sent to zero address.
function deposit( uint256 id, uint256 assets, address receiver ) public override marketExists(id) epochHasNotStarted(id) nonReentrant returns (uint256 shares)
In above code snippet from deposit
function, receiver
will receive the minted share of tokens.
By chance, if the receiver
does not given as input, the minted tokens will end up in dead address (zero address).
Manual code review.
Add validation check for receiver
in the deposit
function.
#0 - HickupHH3
2022-10-29T03:32:45Z
sanity check should be QA. downgrading.
warden's primary QA.
🌟 Selected for report: pfapostol
Also found by: 0x040, 0x1f8b, 0x4non, 0xNazgul, 0xSmartContract, 0xc0ffEE, 0xkatana, Aymen0909, Bnke0x0, Deivitto, Diana, JAGADESH, KIntern_NA, Lambda, MiloTruck, R2, RaymondFam, Respx, ReyAdmirado, Rohan16, RoiEvenHaim, Rolezn, Ruhum, Saintcode_, Samatak, Sm4rty, SnowMan, Tomio, Tomo, WilliamAmbrozic, _Adam, __141345__, ajtra, ak1, async, c3phas, ch0bu, cryptostellar5, d3e4, delfin454000, dharma09, djxploit, durianSausage, eierina, erictee, fatherOfBlocks, gianganhnguyen, gogo, ignacio, imare, jag, jonatascm, leosathya, lukris02, malinariy, oyc_109, pashov, pauliax, peanuts, peiw, prasantgupta52, robee, rokinot, rotcivegaf, rvierdiiev, seyni, simon135, slowmoses, sryysryy, tnevler, zishansami
52.8286 USDC - $52.83
if( vault.strikePrice() < getLatestPrice(vault.tokenInsured()) ) revert PriceNotAtStrikePrice(getLatestPrice(vault.tokenInsured()));
call the getLatestPrice(vault.tokenInsured()
once and use the result value in other places.
#0 - HickupHH3
2022-11-08T02:35:13Z
First suggestion is pretty substantial in gas savings of about 11.8k
testFailEpochExpired() (gas: 11 (0.000%)) testFailEpochNotStarted() (gas: 11 (0.000%)) testWithdrawDepeg() (gas: 13 (0.000%)) testControllerDepeg() (gas: 13 (0.000%)) testTriggerEndEpoch() (gas: -11827 (-0.085%)) testSequencerDown() (gas: -11827 (-0.155%)) testFailPriceNotAtStrikePrice() (gas: -11902 (-0.202%)) testCreateController() (gas: -11827 (-0.754%)) Overall gas change: -47361 (-1.195%)
Second one as well, 2k gas
testAddressFactoryNotInController() (gas: -2209 (-0.052%)) testAddressNotAdmin() (gas: -2209 (-0.052%)) Overall gas change: -6627 (-0.155%)