Platform: Code4rena
Start Date: 21/06/2022
Pot Size: $30,000 USDC
Total HM: 12
Participants: 96
Period: 3 days
Judge: HardlyDifficult
Total Solo HM: 5
Id: 140
League: ETH
Rank: 16/96
Findings: 2
Award: $258.54
🌟 Selected for report: 0
🚀 Solo Findings: 0
230.2266 USDC - $230.23
One can potentially buy vault tokens with paying less fees.
This diff file to the test/vaultTrade.test.ts compares fees for two cases.
hardhat test
The below snippet is the buy
code when token's supply is less then the initial token supply.
Instead of accounting in the buy function, one could use the _buySecondaryCurve
to do the accounting and charge fees. If the _buySecondaryCurve
should be used, however, one cannot just pass _lowerCurveDiff
as the argument, because the charge will be subtracted from the passed amount, which will result to buy less than the intended amount. It will lead to missing the fee amount in the secondaryReserveBalance. So, one should add the fee to be charged on top of the _lowerCurveDiff
.
Additional consideration is the if statement should also consider the fee. Or alternatively, calculate how much token will be bought for the msg.value
, and compare that to the (initial supply - total supply).
It is also good to add tests for some edge cases to make sure they do not revert. For example, when the msg.value is slightly under/over _lowerCrveDiff
, etc.
///// NibblVault.sol:315-323 if (_lowerCurveDiff >= msg.value) { _purchaseReturn = _buySecondaryCurve(msg.value, _totalSupply); } else { //Gas Optimization _purchaseReturn = _initialTokenSupply - _totalSupply; secondaryReserveBalance += _lowerCurveDiff; // _purchaseReturn = _buySecondaryCurve(_to, _lowerCurveDiff); _purchaseReturn += _buyPrimaryCurve(msg.value - _lowerCurveDiff, _totalSupply + _purchaseReturn); }
#0 - HardlyDifficult
2022-07-04T14:27:41Z
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x29A, 0x52, 0xNazgul, 0xNineDec, 0xc0ffEE, 0xf15ers, 0xkatana, BowTiedWardens, Chom, ElKu, Funen, GalloDaSballo, JC, JMukesh, JohnSmith, Lambda, Limbooo, MadWookie, MiloTruck, Nethermind, Noah3o6, Nyamcil, Picodes, PwnedNoMore, Randyyy, RoiEvenHaim, SmartSek, StErMi, Tadashi, TerrierLover, TomJ, Tomio, Treasure-Seeker, UnusualTurtle, Varun_Verma, Wayne, Waze, _Adam, apostle0x01, asutorufos, berndartmueller, c3phas, catchup, cccz, cloudjunky, codexploder, cryptphi, defsec, delfin454000, dipp, ellahi, exd0tpy, fatherOfBlocks, hansfriese, hyh, joestakey, kebabsec, kenta, masterchief, minhquanym, naps62, oyc_109, pashov, peritoflores, reassor, rfa, robee, sach1r0, saian, sashik_eth, shenwilly, simon135, slywaters, sorrynotsorry, sseefried, unforgiven, xiaoming90, ych18, zuhaibmohd, zzzitron
28.3068 USDC - $28.31
uint32 _secondaryReserveRatio = uint32((msg.value * SCALE * 1e18) / (_initialTokenSupply * _initialTokenPrice));
The casting to uint32 does not check if the value to cast is truncated. For some reason very small number for the _initialTokenSupply
or _initialTokenPrice
is given, it might go over the maximum value of the uint32. In that case, the curator will get very small share for the bigger amount of the value sent.
twavObservations[twavObservationsIndex] = TwavObservation(_blockTimestamp, _prevCumulativeValuation + (_valuation * _timeElapsed)); //add the previous observation to make it cumulative
Although, it is not likely to happen since the buyout rejection period is short (i.e. 5 days), the vumulativeValuation
might overflow if the rejection period should be changed to be longer. It is preventative to add unchecked block, so it will work no matter how long the rejection period might be. It will also save some gas.
If it should be changed, Keep in mind also to add another unchecked block to line40
_to.transfer(address(this).balance);
The usage of transfer
to send ETH is not anymore advised, due to its limited gas stipend.
about transfer
There are multiple cases with lacking zero address checks.
ProxyBasket
ProxyBasket
nor in NibbleValutFactory
upon updating implementation. Since deployed ProxyBasket
cannot change the implementation address, it is safer to check the address.NibblVault
safeTransferETH
will transfer to zero address. This is used through out in the code, also to transfer ETH for selling and redeeming as well. If the user should make a mistake to input zero address, the fund will be lost.NibblVaultFactory
feeTo
address is set to zero, the withdrawAdminFee
will send all the balance to the address.NibblVault
curator
is set to zero address, the curatorFee will be locked in the vault, since nobody can update the curator address anymore.NibblVaultFactory
vaultImplementation
, feeTo
, basketImplementation
can be set to zero.NibbleVault
Some values are meant to be fixed throughout the code, for example:
NibblVault
Also, note that the curator would want the reserve ratio of the primary curve to be a decent amount (50% in this example) so that his share of ownership doesn't get diluted too much
However, the primaryReserveRatio is hard-coded to be 20% and curator cannot set it.
According to the README:
All subsequent trading fees goes to the original artist/collector
The curve fee is stop charged after the secondary curve matches the primary curve.
#0 - HardlyDifficult
2022-07-04T19:28:58Z
Good report