Nibbl contest - saian's results

NFT fractionalization protocol with guaranteed liquidity and price based buyout.

General Information

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

Nibbl

Findings Distribution

Researcher Performance

Rank: 46/96

Findings: 2

Award: $45.67

🌟 Selected for report: 0

🚀 Solo Findings: 0

Low severity findings

return value of ERC20 transfer not checked

Some ERC20 tokens may return false instead of reverting when transfer fails, so transaction will succeed even when token transfer fails

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L517

IERC20(_asset).transfer(_to, IERC20(_asset).balanceOf(address(this)));

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L526

IERC20(_assets[i]).transfer(_to, IERC20(_assets[i]).balanceOf(address(this)));

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L85

IERC20(_tokens[i]).transfer(msg.sender, IERC20(_tokens[i]).balanceOf(address(this)));

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L91

IERC20(_tokens[i]).transfer(msg.sender, IERC20(_tokens[i]).balanceOf(address(this)));
Mitigation

Add a require statement to check the return value, or use Openzeppelin's SafeERC20

Avoid payable.transfer

transfer method forwards a fixed amount of gas 2300, which may cause reverts if the payable callback consumes more than the fixed gas or if gas costs changes in the future

https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L80

_to.transfer(address(this).balance);
Mitigation

transfer can be replaced with sendValue which uses call method

Non critical findings

Lack of input validation

Values passed into the functions are not validated which may lead to accidentally setting variables to wrong values

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L23

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L485

function updateCurator(address _newCurator) external override { require(msg.sender == curator,"NibblVault: Only Curator"); curator = _newCurator; }

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Utilities/AccessControlMechanism.sol#L19

Mitigation

Add input validation statements

Event missing indexed fields

Event indexed parameters are stored in the topics part of the log which allows for quicker querying by off-chain monitoring tools and scripts,

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Interfaces/INibblVaultFactory.sol#L5

function createVault(address _assetAddress, address _curator, string memory _name, string memory _symbol, uint256 _assetTokenID, uint256 _initialSupply, uint256 _initialTokenPrice, uint256 _minBuyoutTime) external payable returns(address payable _proxyVault);

Missing events for critical parameter change

Events that change critical parameters should emit an event which helps users to easily monitor changes in the system

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L99

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L106

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L140

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L148

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L158

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L165

Missing comments

Some functions are missing natspec comments

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L77

function getVaults() public view returns(ProxyVault[] memory ) {

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L81

function createBasket(address _curator, string memory _mix) public override returns(address) {

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L88

function getBasketAddress(address _curator, string memory _mix) public override view returns(address _basket) {

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L552

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L41

function withdrawMultipleERC721(address[] memory _tokens, uint256[] memory _tokenId, address _to) external override

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L68

function withdrawMultipleERC1155(address[] memory _tokens, uint256[] memory _tokenIds, address _to) external override

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L91

Natspec comments are incomplete

Proof of concept

Missing @param comment

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L474

/// @notice Function for tokenholders to redeem their tokens for reserve token in case of buyout success /// @dev The redeemed reserve token are in proportion to the token supply someone owns /// @dev The amount available for redemption is contract balance - (total unsettled bid and curator fees accrued) function redeem(address payable _to) external override boughtOut returns(uint256 _amtOut)

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L52

/// @notice withdraw an ERC721 token from this contract into your wallet /// @param _token the address of the NFT you are withdrawing /// @param _tokenId the ID of the NFT you are withdrawing function withdrawERC721Unsafe(address _token, uint256 _tokenId, address _to) external override {

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L61

/// @notice withdraw an ERC721 token from this contract into your wallet /// @param _token the address of the NFT you are withdrawing /// @param _tokenId the ID of the NFT you are withdrawing function withdrawERC1155(address _token, uint256 _tokenId, address _to) external override

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L78

/// @notice withdraw ETH in the case a held NFT earned ETH (ie. euler beats) function withdrawETH(address payable _to) external override

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L85

/// @notice withdraw ERC20 in the case a held NFT earned ERC20 function withdrawERC20(address _token) external override

Commented out code

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L405

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L382

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L321

Mitigation

Remove commented out code

createBasket missing whenNotPaused modifier

Function createBasket is missing the whenNotPaused modifier, so basket can be created when the contract is paused

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L80

function createBasket(address _curator, string memory _mix) public override returns(address) {
Mitigation

Add whenNotPaused modifier to createBasket function

public functions can be external

public functions which are not accessed inside the contracts can be converted to external

Proof of concept

contracts/NibblVaultFactory.sol

function getVaultAddress( address _curator, address _assetAddress, uint256 _assetTokenID, uint256 _initialSupply, uint256 _initialTokenPrice) public view returns(address _vault) function getVaults() public view returns(ProxyVault[] memory ) function createBasket(address _curator, string memory _mix) public override returns(address) function getBasketAddress(address _curator, string memory _mix) public override view returns(address _basket) {

Check if arrays have equal length

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L545

function withdrawMultipleERC1155(address[] memory _assets, uint256[] memory _assetIDs, address _to)
Mitigation

require statement to check array lengths can be added

Unused import

Files imported are not used

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L9

#0 - HardlyDifficult

2022-07-04T19:03:03Z

Good feedback

Long revert string

Revert strings longer than 32 bytes will increase deployment gas as well as runtime gas when revert condition is met

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L48

require(msg.value >= MIN_INITIAL_RESERVE_BALANCE, "NibblVaultFactory: Initial reserve balance too low");

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L49

require(IERC721(_assetAddress).ownerOf(_assetTokenID) == msg.sender, "NibblVaultFactory: Invalid sender");

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L107

require(basketUpdateTime != 0 && block.timestamp >= basketUpdateTime, "NibblVaultFactory: UPDATE_TIME has not passed");

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L131

require(feeToUpdateTime != 0 && block.timestamp >= feeToUpdateTime, "NibblVaultFactory: UPDATE_TIME has not passed");

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L141

require(_newFee <= MAX_ADMIN_FEE, "NibblVaultFactory: Fee value greater than MAX_ADMIN_FEE");

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L149

require(feeAdminUpdateTime != 0 && block.timestamp >= feeAdminUpdateTime, "NibblVaultFactory: UPDATE_TIME has not passed");

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L166

require(vaultUpdateTime != 0 && block.timestamp >= vaultUpdateTime, "NibblVaultFactory: UPDATE_TIME has not passed");

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L147

require(buyoutEndTime <= block.timestamp, "NibblVault: buyoutEndTime <= now");

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L184

require(_secondaryReserveRatio >= MIN_SECONDARY_RESERVE_RATIO, "NibblVault: secResRatio too low");
Mitigation

Reduce the size of strings to 32 bytes or use custom errors

function parameter can be calldata

Function parameters that are read-only can be converted to calldata to avoid it being copied to memory which will consume more gas

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L38

function createVault( address _assetAddress, address _curator, string memory _name, string memory _symbol, uint256 _assetTokenID, uint256 _initialSupply, uint256 _initialTokenPrice, uint256 _minBuyoutTime ) external payable override whenNotPaused returns(address payable _proxyVault)

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L80

function createBasket(address _curator, string memory _mix) public override returns(address)

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVaultFactory.sol#L88

function getBasketAddress(address _curator, string memory _mix) public override view returns(address _basket)

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L173

function initialize( string memory _tokenName, string memory _tokenSymbol, address _assetAddress, uint256 _assetID, address _curator, uint256 _initialTokenSupply, uint256 _initialTokenPrice, uint256 _minBuyoutTime ) external override initializer payable

for-loops can be optimized

Initialising variables with default values is not necessary as variables already contains default values, array length can be cached in a temporary variable and reused in the for loops instead of querying length in each iteration post-increment can be replaced with pre-increment as pre-increment consumes less gas, and unchecked can be added to avoid the default overflow/underflow checks

Proof of concept

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L547

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L525

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L70

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L70

https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/Basket.sol#L43

Add unchecked to save gas

Adding unchecked to expression that dont overflow can avoid the default overflow/underflow checks introduced in 0.8 and save gas

Proof of concept
https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L319
_purchaseReturn = _initialTokenSupply - _totalSupply;
https://github.com/code-423n4/2022-06-nibbl/blob/8c3dbd6adf350f35c58b31723d42117765644110/contracts/NibblVault.sol#L378
uint256 _tokensPrimaryCurve = _totalSupply - _initialTokenSupply;
AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter