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: 37/96
Findings: 2
Award: $46.17
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 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.9386 USDC - $28.94
_isApprovedOrOwner()
can be checked using a single modifier in basket.sol
modifier isApprovedOnwer() { require(_isApprovedOrOwner(msg.sender, 0), "withdraw:not allowed") } function withdrawERC721(....) isApprovedOnwer{ ..} function withdrawMultipleERC721(....) isApprovedOnwer{ ..} function withdrawERC721Unsafe(....) isApprovedOnwer{ ..} function withdrawERC1155(....) isApprovedOnwer{ ..} ........ ......
NibblVault.sol
for require(msg.sender == bidder,"NibblVault: Only winner")
sSome require statements dont have revert strings, using short descriptive message is good practice
./contracts/NibblVaultFactory.sol:114: require(_success); ./contracts/Bancor/BancorFormula.sol:259: require(_baseN < MAX_NUM); ./contracts/Bancor/BancorFormula.sol:356: require(false);
Some external function take arrays from users and iterate thorugh them. If a user sends very large loop the transaction may be too big to fit in a single block. Checking for a resonable array size in such case is a best pracitce.
withdrawMultipleERC1155()
, withdrawMultipleERC20()
, etc..initialize()
in NibblVault.sol
Check for zero address in the initialize function, so it doesn't gets set to 0 accidently.
The token withdraw methods for ERC20, ERC721 and ERC1155 take _to
address to which these tokens are transferred, but this address may be accidently sent to zero address and the tokens may be lost forever.
withdrawUnsettledBids
, redeem
, redeemCuratorFee
, updateCurator
, withdrawERC721
, withdrawMultipleERC721
, withdrawERC20
, withdrawMultipleERC20
, withdrawERC1155
, withdrawMultipleERC1155
This is very important for critical role changes such as updateCurator()
buy()
function is not making any external calls, so reentrancy guard lock
maynot be neededreceive()
should revertAccessControlMechanism.sol
pragma solidity ^0.8.0;
#0 - mundhrakeshav
2022-06-26T12:44:55Z
https://github.com/code-423n4/2022-06-nibbl-findings/issues/2, https://github.com/code-423n4/2022-06-nibbl-findings/issues/3, https://github.com/code-423n4/2022-06-nibbl-findings/issues/6, https://github.com/code-423n4/2022-06-nibbl-findings/issues/7, https://github.com/code-423n4/2022-06-nibbl-findings/issues/8, https://github.com/code-423n4/2022-06-nibbl-findings/issues/15
#1 - HardlyDifficult
2022-07-01T00:30:54Z
#2 - HardlyDifficult
2022-07-01T00:44:55Z
#3 - HardlyDifficult
2022-07-04T15:00:12Z
Good & relevant feedback. Low & NC suggestions.
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x29A, 0xKitsune, 0xNazgul, 0xf15ers, 0xkatana, 8olidity, ACai, BowTiedWardens, Chandr, Chom, ElKu, Fitraldys, Funen, IgnacioB, JC, Lambda, Limbooo, MiloTruck, Noah3o6, Nyamcil, Picodes, Randyyy, SmartSek, StErMi, TerrierLover, TomJ, Tomio, UnusualTurtle, Waze, _Adam, ajtra, c3phas, cRat1st0s, catchup, codexploder, cryptphi, defsec, delfin454000, ellahi, exd0tpy, fatherOfBlocks, hansfriese, joestakey, kebabsec, kenta, m_Rassska, minhquanym, oyc_109, pashov, reassor, rfa, robee, sach1r0, saian, sashik_eth, simon135, slywaters, ych18, ynnad, zuhaibmohd
17.2283 USDC - $17.23
Using prefix increment saves small amount of gas compared to postifix incremeent since they return the updated value and dont have to store intermediate value. Changing postfix increment to prefix increment will save gas in loops where this is used in every iteration.
/// Change for (uint256 i = 0; i < _tokens.length; i++) { /// To for (uint256 i = 0; i < _tokens.length; ++i) {
./contracts/Basket.sol:43: for (uint256 i = 0; i < _tokens.length; i++) { ./contracts/Basket.sol:70: for (uint256 i = 0; i < _tokens.length; i++) { ./contracts/Basket.sol:93: for (uint256 i = 0; i < _tokens.length; i++) { ./contracts/NibblVault.sol:506: for (uint256 i = 0; i < _assetAddresses.length; i++) { ./contracts/NibblVault.sol:525: for (uint256 i = 0; i < _assets.length; i++) { ./contracts/NibblVault.sol:547: for (uint256 i = 0; i < _assets.length; i++) { ./contracts/NibblVault.sol:562: bytes32 structHash = keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)); ./contracts/Test/UpgradedNibblVault.sol:452: for (uint256 i = 0; i < _assetAddresses.length; i++) { ./contracts/Test/UpgradedNibblVault.sol:469: for (uint256 i = 0; i < _assets.length; i++) { ./contracts/Test/UpgradedNibblVault.sol:486: for (uint256 i = 0; i < _assets.length; i++) { ./contracts/Test/UpgradedNibblVault.sol:493: upgradedNewVariable++; ./contracts/Test/UpgradedNibblVault.sol:515: bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline));
The loop termination condition evaluates the length of the loop on every iteration. Caching it will save gas.
/// Change for (uint256 i = 0; i < _tokens.length; i++) { /// To uint256 len = _tokens.length; for (uint256 i = 0; i < len; i++) {
./contracts/Basket.sol:43: for (uint256 i = 0; i < _tokens.length; i++) { ./contracts/Basket.sol:70: for (uint256 i = 0; i < _tokens.length; i++) { ./contracts/Basket.sol:93: for (uint256 i = 0; i < _tokens.length; i++) { ./contracts/NibblVault.sol:506: for (uint256 i = 0; i < _assetAddresses.length; i++) { ./contracts/NibblVault.sol:525: for (uint256 i = 0; i < _assets.length; i++) { ./contracts/NibblVault.sol:547: for (uint256 i = 0; i < _assets.length; i++) {
Some public functions are never callled in the contract itself, such functions can be converted to external to save some gas
./contracts/NibblVaultFactory.sol:69: uint256 _initialTokenPrice) public view returns(address _vault) { ./contracts/NibblVaultFactory.sol:76: function getVaults() public view returns(ProxyVault[] memory ) { ./contracts/NibblVaultFactory.sol:80: function createBasket(address _curator, string memory _mix) public override returns(address) { ./contracts/NibblVaultFactory.sol:88: function getBasketAddress(address _curator, string memory _mix) public override view returns(address _basket) {
immutable
for expressions using keccak256()
NibblVault.sol
,bytes32 public constant FEE_ROLE = keccak256("FEE_ROLE"); bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); bytes32 public constant IMPLEMENTER_ROLE = keccak256("IMPLEMENTER_ROLE"); bytes32 private constant PERMIT_TYPEHASH = keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")
Use of revert strings longer than 32 bytes will require extra deploymenht gas, minimize them to save gas on deployment
./contracts/NibblVaultFactory.sol:107: require(basketUpdateTime != 0 && block.timestamp >= basketUpdateTime, "NibblVaultFactory: UPDATE_TIME has not passed"); ./contracts/NibblVaultFactory.sol:131: require(feeToUpdateTime != 0 && block.timestamp >= feeToUpdateTime, "NibblVaultFactory: UPDATE_TIME has not passed"); ./contracts/NibblVaultFactory.sol:141: require(_newFee <= MAX_ADMIN_FEE, "NibblVaultFactory: Fee value greater than MAX_ADMIN_FEE"); ./contracts/NibblVaultFactory.sol:149: require(feeAdminUpdateTime != 0 && block.timestamp >= feeAdminUpdateTime, "NibblVaultFactory: UPDATE_TIME has not passed"); ./contracts/NibblVaultFactory.sol:166: require(vaultUpdateTime != 0 && block.timestamp >= vaultUpdateTime, "NibblVaultFactory: UPDATE_TIME has not passed");
solidity 0.8.4 introduces custom errors which are cheaper than using revert strings in terms of gas Use the custom error patterns to reduce gas cost.
for eg.
// Before require(condition, "Revert strings"); // After error CustomError(); if (!condition) { revert CustomError(); }
more details can be found here
#0 - mundhrakeshav
2022-06-26T12:57:38Z
https://github.com/code-423n4/2022-06-nibbl-findings/issues/2, https://github.com/code-423n4/2022-06-nibbl-findings/issues/3, https://github.com/code-423n4/2022-06-nibbl-findings/issues/6, https://github.com/code-423n4/2022-06-nibbl-findings/issues/7, https://github.com/code-423n4/2022-06-nibbl-findings/issues/8, https://github.com/code-423n4/2022-06-nibbl-findings/issues/15