Platform: Code4rena
Start Date: 06/05/2021
Pot Size: $66,000 USDC
Total HM: 16
Participants: 11
Period: 6 days
Judge: cemozer
Total Solo HM: 9
Id: 8
League: ETH
Rank: 2/11
Findings: 7
Award: $10,343.65
🌟 Selected for report: 9
🚀 Solo Findings: 1
paulius.eth
function flashLoan is vulnerable to overflow/underflow when the fee is not 0. Although currently the fee is set to 0, there is a comment: "By default there is no fee, but this can be changed by overriding {flashFee}" As these contracts are upgradeable, I cannot assume that this fee will always stay 0, thus I want you to be aware of this possible issue. function flashLoan does not use SafeMath when doing the calculations and accepts an arbitrary value for the amount parameter. When the fee is above 0, it is possible to pass such a value for an amount that (amount + fee) will overflow/underflow. For example, if the fee is set to 1 and I invoke a flashLoan with an amount of max_uint, I will be minted max_uint of tokens and will need to return (burn) 0 tokens. Also, there is a function maxFlashLoan which returns the maximum amount of tokens available for a loan, however, this function is never used. I assume the intention was to limit the amount you can borrow but currently it has no effect.
Either use SafeMath here or make sure to never introduce a fee > 0. Also, make use of maxFlashLoan function.
#0 - cemozerr
2021-05-25T23:28:45Z
999.994 USDC - $999.99
paulius.eth
function swap has a nonReentrant modifier but function swapTo doesn't. swapTo is a public function so it can be invoked directly.
I guess it was meant to be the opposite as swap just invokes swapTo so it could automatically inherit nonReentrant from swapTo. Move nonReentrant modifier from swap to swapTo.
#0 - cemozerr
2021-05-25T23:25:38Z
224.9987 USDC - $225.00
paulius.eth
function getPseudoRand uses a very poor source of randomness so it is easily replicable on another smart contract. When enableDirectRedeem is turned off, you can't specify specificIds, however, it does not stop advanced users to write a custom smart contract that exploits this randomness or reverts if the final output is not what was intended. I know that you are aware that this random generation is not safe (thus named 'pseudo') but still I think this is worth pointing out as it gives an unfair advantage to those that know smart contracts and can build a service on top of it. Also, it helps to avoid directRedeemFee.
#0 - cemozerr
2021-05-25T23:21:50Z
🌟 Selected for report: pauliax
paulius.eth
When is1155 is true, function receiveNFTs iterates over all the tokens and updates holdings and quantity1155. If the quantity1155 is 0 for that token, it adds this token to the holdings set. However, it does not check that the amount is greater than 0, thus it is possible to push the same token to the hodlings multiple times as quantity1155 still remains 0.
Solution: check that amount > 0 if is1155 is true. Also, it would be a good practice to check that the amounts array is empty when it is erc721 as amounts are ignored for ERC721 vaults.
#0 - 0xKiwi
2021-05-21T00:02:55Z
Nice find!
paulius.eth
Contract NFTXMintRequestEligibility function requestMint sets mintRequests to the amount that was minted, however, it does not check that amount[i] > 0, so it is possible that when the token is not erc1155, the amount has a value of 0 but the token is still transferred from the sender to the contract and because mintRequests remains 0, this token cannot later be redeemed as all these claim functions have a condition: require(amount > 0).
Either always require that amount[i] > 0 or explicitly set mintRequests to 1 when erc721 is deposited.
paulius.eth
function flashLoan in contract NFTXVaultUpgradeable does not return a boolean. flashLoan is declared as a function that should return a boolean value, however, in contract NFTXVaultUpgradeable there is no return statement so it always gets a default value of false (while base function always returns the opposite, a.k.a true).
It should return super.flashLoan(...).
#0 - 0xKiwi
2021-05-21T00:12:05Z
Nice catch!
paulius.eth
contract NFTXVaultFactoryUpgradeable, variable eligibilityManager is never set thus it gets a default value of 0x0. So function deployEligibilityStorage should always fail as the eligibility manager does not exist on address 0x0.
Either add a setter for eligibilityManager or refactor function deployEligibilityStorage to work in such case.
#0 - 0xKiwi
2021-05-21T00:01:43Z
Nice find!
paulius.eth
unused event in contract NFTXListEligibility: event ReverseEligilityOnRedeemSet(bool reverseElig); events declared in the interface INFTXVault are all unused: FundPreferencesUpdated, Mint, Redeem, ManagerSet. Event ManagerSet has 2 parameters in INFTXVault but contract NFTXVaultUpgradeable declares the same event with 1 parameter.
Either remove unused events or use where they were intended.
#0 - cemozerr
2021-05-25T23:19:57Z
paulius.eth
When depositing erc1155s amounts array is used and tokens are sent in bulk (safeBatchTransferFrom), however, when redeeming it iterates over the amount and redeems it one by one. It is not convenient when the amount is large. Let's say I deposited 1000 arrows (erc1155), the deposit is done in one tx one function (mintTo) and is relatively cheap. To later redeem it, I need to call redeemTo hundreds of times to redeem in small batches as every unit consumes extra gas. I think that is not an appropriate handle of erc1155s as its primary difference between erc721 is that it can be transferred in batches. The example was given with 1000 items but imagine what happens when the amount >100k, I expect such items would be left in the vault forever as redeeming it one by one was infeasible.
I would suggest somehow combine redeem logic with balanceOf function to avoid this issue.
#0 - cemozerr
2021-05-25T23:23:04Z
🌟 Selected for report: pauliax
paulius.eth
Unused variables: contract NFTXVaultUpgradeable, string public description; contract NFTXMintRequestEligibility, address public manager;
Delete unused variables to reduce the deployment costs.
🌟 Selected for report: pauliax
323.8167 USDC - $323.82
paulius.eth
No need to use a struct with 1 field here, consumes extra gas: struct EligibilityModule { address impl; } EligibilityModule[] public modules;
can be refactored to: address[] public modules;
🌟 Selected for report: pauliax
323.8167 USDC - $323.82
paulius.eth
Some statements inside functions an be extracted to a constant variables so it won't need to evaluate it over and over again. For example, inside function nameForStakingToken: solidity keccak256(abi.encode(address(0)) inside function distribute: 10**9 (calculates exponential every time)
🌟 Selected for report: pauliax
323.8167 USDC - $323.82
paulius.eth
Every function call costs extra gas. Functions getRandomTokenIdFromFund and getPseudoRand can be joined together to save some gas as there is no point in having them separate if it's used only in one place.
Join functions getRandomTokenIdFromFund and getPseudoRand.
🌟 Selected for report: pauliax
323.8167 USDC - $323.82
paulius.eth
Remove unused imports, for example, contract NFTXVaultUpgradeable: import "./interface/IPrevNftxContract.sol"; import "./interface/IRewardDistributionToken.sol"; interfaces IVaultTokenUpgradeable, ITransparentUpgradeableProxy are not used anywhere.
Remove unused code to reduce deployment costs.
🌟 Selected for report: pauliax
323.8167 USDC - $323.82
paulius.eth
functions approveMintRequests and claimUnminted can be refactored to first collect all the tokens into _tokenIds and _amounts arrays and only then call vault.mintTo. This will reduce the number of calls and thus improves gas efficiency.
🌟 Selected for report: pauliax
323.8167 USDC - $323.82
paulius.eth
There are 5 structs named "Config" which are not used anywhere. For example, in contract NFTXMintRequestEligibility: struct Config { address owner; address vaultAddress; bool reverseEligOnRedeem; uint256[] tokenIds; }
Remove unused structs or use it where intended.