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: 8/96
Findings: 3
Award: $1,312.51
π Selected for report: 1
π Solo Findings: 0
230.2266 USDC - $230.23
In the buy function of the NibblVault contract, when msg.value > _lowerCurveDiff, the fee for SecondaryCurve part is not charged.
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); }
None
The fee for the SecondaryCurve part is complex to charge. I implemented the _caculateFeeSecondaryCurve and _reverseFeeSecondaryCurve functions to do the relevant calculations. The _caculateFeeSecondaryCurve function is used to calculate the value after the fee is charged, but not to actually charge the fee. The _reverseFeeSecondaryCurve function is used to calculate the value before the fee is charged.
if (_lowerCurveDiff >= _caculateFeeSecondaryCurve(msg.value)) { _purchaseReturn = _buySecondaryCurve(msg.value, _totalSupply); } else { //Gas Optimization _purchaseReturn = _initialTokenSupply - _totalSupply; secondaryReserveBalance += _lowerCurveDiff; uint256 _amount = _reverseFeeSecondaryCurve(_lowerCurveDiff); _chargeFeeSecondaryCurve(_amount); // _purchaseReturn = _buySecondaryCurve(_to, _lowerCurveDiff); _purchaseReturn += _buyPrimaryCurve(msg.value - _amount, _totalSupply + _purchaseReturn); }
function _caculateFeeSecondaryCurve(uint256 _amount) private returns(uint256) { address payable _factory = factory; uint256 _adminFeeAmt = NibblVaultFactory(_factory).feeAdmin(); uint256 _feeAdmin = (_amount * _adminFeeAmt) / SCALE ; uint256 _feeCurator = (_amount * curatorFee) / SCALE ; return _amount - (_feeAdmin + _feeCurator); } function _reverseFeeSecondaryCurve(uint256 _amount) private returns(uint256) { address payable _factory = factory; uint256 _adminFeeAmt = NibblVaultFactory(_factory).feeAdmin(); return _amount * SCALE / (SCALE - (_adminFeeAmt + curatorFee)); }
#0 - HardlyDifficult
2022-07-03T15:48:31Z
Failing to collect fees is a form of leaking value for the curator and therefore falls into the Medium risk definition -- keeping the severity as reported.
1052.705 USDC - $1,052.70
Basket contracts are deployed using a proxy, but Basket inherits a non-upgradable version of the ERC721 contract, which makes it impossible to set the contract's name and symbol.
constructor(string memory name_, string memory symbol_) { _name = name_; _symbol = symbol_; }
https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L6-L7 https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L13-L14 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L44-L47
None
Using upgradable version of the ERC721 contract https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/token/ERC721/ERC721Upgradeable.sol
#0 - mundhrakeshav
2022-06-30T09:17:24Z
#1 - HardlyDifficult
2022-07-03T16:17:01Z
Confirmed this is an issue.
Assets are not at risk, and the function of the protocol is not impacted. All baskets created will have an empty name/symbol but generally there is no requirement that these values are populated. It's mostly for a better experience on frontends including etherscan. Downgrading and merging with the warden's QA report #160
#2 - HardlyDifficult
2022-07-05T19:44:26Z
π 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
29.5841 USDC - $29.58
In the initiateBuyout function of the NibblVault contract, the second parameter of the BuyoutInitiated event is _buyoutBid instead of _currentValuation, and since the excess Ether in _buyoutBid is transferred to the user, the actual buyout price for the user is the _currentValuation variable. The user can use a large amount of Ether to get a large _buyoutBid variable, however the actual amount of Ether spent by the user is _currentValuation. Events emitted by the smart contract are used off-chain, and incorrect event parameters may have an impact on the user's trading behavior
None
- emit BuyoutInitiated(msg.sender, _buyoutBid); + emit BuyoutInitiated(msg.sender, _currentValuation);
#0 - HardlyDifficult
2022-07-03T16:05:05Z
Agree. However issues with events falls into the non-critical definition: "off-chain monitoring (events, etc)". Downgrading and converting this into a QA report for the warden.
#1 - HardlyDifficult
2022-07-03T16:12:38Z
#2 - HardlyDifficult
2022-07-03T16:15:48Z
#3 - HardlyDifficult
2022-07-03T16:20:07Z
#4 - HardlyDifficult
2022-07-04T15:34:20Z
Good low risk & best practice suggestions.