GithubHelp home page GithubHelp logo

2021-05-nftx-findings's People

Contributors

c4-staff avatar code423n4 avatar sockdrawermoney avatar

Stargazers

 avatar

Watchers

 avatar  avatar

2021-05-nftx-findings's Issues

This is a test submission and can be deleted

Handle

adamavenir

Vulnerability details

Impact

Detailed description of the impact of this finding.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

Tools Used

Recommended Mitigation Steps

struct Config

Handle

paulius.eth

Vulnerability details

Impact

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;
}

Recommended Mitigation Steps

Remove unused structs or use it where intended.

function swapTo doesn't have a re-entrancy modifier

Handle

paulius.eth

Vulnerability details

Impact

function swap has a nonReentrant modifier but function swapTo doesn't. swapTo is a public function so it can be invoked directly.

Recommended Mitigation Steps

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.

RewardDistributionTokenUpgradeable should implement IRewardDistributionToken

Handle

maplesyrup

Vulnerability details

Impact

In order to ensure that an implementation matches its publicly expected behavior, the implementation should implement any interfaces it is expected to support. Failure to do so allows for possible version drift between deployments, particularly of an upgradeable contract.

Proof of Concept

https://github.com/code-423n4/2021-05-nftx/blob/f6d793c136d110774de259d9f3b25d003c4f8098/nftx-protocol-v2/contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#L18

should implement

https://github.com/code-423n4/2021-05-nftx/blob/f6d793c136d110774de259d9f3b25d003c4f8098/nftx-protocol-v2/contracts/solidity/interface/IRewardDistributionToken.sol#L5

Tools Used

Slither

Recommended Mitigation Steps

Simply implement the interface to ensure the declared behavior and the implementation of it match in public signatures.

Missing parameter validation

Handle

@cmichelio

Vulnerability details

Vulnerability Details

Missing parameter validation for functions:

  • NFTXEligiblityManager.addModule, updateModule
  • NFTXFeeDistributor all setter functions (setTreasuryAddress, ...)
  • NFTXVaultUpgradeable.setManager

Impact

Some wallets still default to zero addresses for a missing input which can lead to breaking critical functionality like setting the manager to the zero address and being locked out.

Recommended Mitigation Steps

Validate the parameters.

accessing storage in a loop

Handle

paulius.eth

Vulnerability details

Impact

accessing storage in a loop, for example, _feeReceivers length can be extracted into a variable and used where necessary to reduce the number of storage reads.

Recommended Mitigation Steps

change this:
for (uint256 i = 0; i < _feeReceivers.length; i++) {
to this:
uint256 feeReceiversLength = _feeReceivers.length;
for (uint256 i = 0; i < feeReceiversLength; i++) {

Fee Distribution Re-Entrancy

Handle

0xsomeone

Vulnerability details

Impact

The distribute function of NFTXFeeDistributor has no access control and will invoke a fallback on the fee receivers, meaning that a fee receiver can re-enter via this function to acquire their allocation repeatedly potentially draining the full balance and sending zero amounts to the rest of the recipients.

Proof of Concept

A smart contract with a malicious receiveRewards function can re-enter the distribute function with the same vault ID thereby causing the exploit.

Tools Used

Manual review.

Recommended Mitigation Steps

Re-entrancy protection should be incorporated into the distribute function. I should note that a seemingly innocuous contract can cause this re-entrancy by simply asking the owners of the project to include an upgrade-able contract that is then replaced for a malicious implementation.

Solidity version 0.6.8 requires explicit use of ABIEncoderV2

Handle

maplesyrup

Vulnerability details

Impact

The ABIEncoderV2 pragma is not included and so presumably will not be properly used.

Proof of Concept

https://github.com/code-423n4/2021-05-nftx/blob/f6d793c136d110774de259d9f3b25d003c4f8098/nftx-protocol-v2/contracts/solidity/NFTXEligiblityManager.sol#L1

Note there is a bug in the encoder V2:

https://blog.ethereum.org/2019/03/26/solidity-optimizer-and-abiencoderv2-bug/

but the code does not appear to be impacted.

Tools Used

Slither

Recommended Mitigation Steps

Include 'pragma experimental ABIEncoderV2;' in the above referenced file.

Missing allValidNFTs and afterRedeemHook with swapTo?

Handle

gpersoon

Vulnerability details

Impact

The function swapTo of NFTXVaultUpgradeable.sol is kind of a combination of mintTo and redeemTo (the code looks very similar to a combination of mintTo and redeemTo).
Before receiveNFTs I would expect a call to allValidNFTs, like in mintTo.
This is to make sure only eligible NFTs are transferred. Without this check any NFT could be transferred.

After withdrawNFTsTo I would expect a call to afterRedeemHook, like in redeemTo.
The afterRedeemHook fixes the administration for the eligability. Without this the eligibility administrations would be flawed.

This way swapTo would circumvent the checks of mintTo and redeemTo and would allow any NFT to be transferred (even the one's that are not eligible)

Proof of Concept

NFTXVaultUpgradeable.sol

function mintTo(..)
...
require(allValidNFTs(tokenIds), "NFTXVault: not eligible");
uint256 count = receiveNFTs(tokenIds, amounts);

function redeemTo(uint256 amount, uint256[] memory specificIds, address to)
....
uint256[] memory redeemedIds = withdrawNFTsTo(amount, specificIds, to);
afterRedeemHook(redeemedIds);

function swapTo(..)
...
uint256 count = receiveNFTs(tokenIds, amounts);
...
uint256[] memory ids = withdrawNFTsTo(count, specificIds, to);

Tools Used

Editor

Recommended Mitigation Steps

Check if it necessary to also add the following statements in swapTo:

  • require(allValidNFTs(tokenIds), "NFTXVault: not eligible");
  • afterRedeemHook(redeemedIds);

withdrawNFTsTo & afterRedeemHook inefficient for ERC1155

Handle

gpersoon

Vulnerability details

Impact

If you want to redeem or swap ERC1155 NFTs, the function withdrawNFTsTo (of NFTXVaultUpgradeable.sol) is used.
When using specificIds, this function does not explicitly do something with the fact that you can have multiple NFTs of the same tokenId in ERC1155 and transfers them 1 by 1.
(to be able to transfer 10 items of the same tokenId, you even have to specify 10x the same tokenId in the specificIds)

Also the function afterRedeemHook isn't optimized for ERC1155 NFTs, it will call _setUniqueEligibilities with a perhaps large array with multiple of the same values using more gas than necessary.

In contrast the function receiveNFTs is optimized for ERC1155 NFTs.

Proof of Concept

NFTXVaultUpgradeable.sol
function withdrawNFTsTo(..)
for (uint256 i = 0; i < amount; i++) {
...
if (_is1155) {
IERC1155Upgradeable(_assetAddress).safeTransferFrom(address(this),to,tokenId,1,"");
NFTXDenyEligibility.sol
function afterRedeemHook(uint256[] calldata tokenIds) external override virtual {
...
_setUniqueEligibilities(tokenIds, true);
}
}

UniqueEligibility.sol
function _setUniqueEligibilities(.. ) internal virtual {
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 tokenId = tokenIds[i];
...

Tools Used

Editor

Recommended Mitigation Steps

Consider optimizing the functions withdrawNFTsTo & afterRedeemHook for ERC1155 NFTs

[INFO] function publicMint is for testing only

Handle

paulius.eth

Vulnerability details

Impact

[This is for informational purposes only]
function publicMint has a comment that says it is meant for testing only:
// For testing
function publicMint(address to, uint256 tokenId, uint256 amount) public virtual {
_mint(to, tokenId, amount, "");
}

Recommended Mitigation Steps

Do not forget to comment or delete this code before going to production.

simpler way to suppress compiler warning

Handle

gpersoon

Vulnerability details

Impact

In the function function flashFee of ERC20FlashMintUpgradeable.sol, the variable amount is referenced to suppress a compiler warning. There is a simpler way to do this, by commenting out the variable name.

Proof of Concept

function flashFee(address token, uint256 amount) public view virtual override returns (uint256) {
require(token == address(this), "ERC20FlashMint: wrong token");
// silence warning about unused variable without the addition of bytecode.
amount;
return 0;

Tools Used

Editor

Recommended Mitigation Steps

Use the following code:
function flashFee(address token, uint256 /amount/) public view virtual override returns (uint256) {
require(token == address(this), "ERC20FlashMint: wrong token");
return 0;

Missing overflow check in `flashLoan`

Handle

@cmichelio

Vulnerability details

Vulnerability Details

ERC20FlashMintUpgradeable.flashLoan does not check for an overflow when adding the fees to the flashloan amount.
The functionality might have been copied from https://eips.ethereum.org/EIPS/eip-3156 but this one already has overflow checks as it uses solidity 0.8.0.

Impact

This leads to an issue where the attacker does not need to pay back the flashloan as they will burn 0 tokens:

_burn(address(receiver), amount + fee);

They end up with a huge profit.

Luckily, this is currently not exploitable as the fee is set to 0 so there's no possibility to overflow. However, if governance decides to change the flashloan fee, flashloans can be taken without having to repay them.

Recommended Mitigation Steps

Use SafeMath.

Unchecked external calls in `NFTXLPStaking`

Handle

@cmichelio

Vulnerability details

Vulnerability Details

The emergencyExit/emergencyExitAndClaim functions take the staking and reward tokens as parameters and trust them for the withdrawal.

Impact

This does not lead to a critical issue (like being able to withdraw all funds) as one cannot deploy a fake reward smart contract to a _rewardDistributionTokenAddr and a random address without a smart contract will fail because of the dist.balanceOf(msg.sender) call not returning any data.
However, checking if the distribution token exists is still recommended.

Recommended Mitigation Steps

Require isContract(dist).

Unused events

Handle

paulius.eth

Vulnerability details

Impact

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.

Recommended Mitigation Steps

Either remove unused events or use where they were intended.

Missing documentation for flashloan paused number

Handle

gpersoon

Vulnerability details

Impact

The contract PausableUpgradeable.sol documents the paused variables 0..3.
However onlyOwnerIfPaused is also used with a parameter of 4. This is used for flashloans.

Proof of Concept

PausableUpgradeable.sol:
// 0 : createFund
// 1 : mint
// 2 : redeem
// 3 : mintAndRedeem
.\NFTXVaultFactoryUpgradeable.sol: onlyOwnerIfPaused(0);
.\NFTXVaultUpgradeable.sol: onlyOwnerIfPaused(1);
.\NFTXVaultUpgradeable.sol: onlyOwnerIfPaused(2);
.\NFTXVaultUpgradeable.sol: onlyOwnerIfPaused(3);
.\NFTXVaultUpgradeable.sol: onlyOwnerIfPaused(4);

Tools Used

Editor, grep

Recommended Mitigation Steps

Add documentation for #4.
Even better, create constants or enums.

flashLoan does not have a return statement

Handle

paulius.eth

Vulnerability details

Impact

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).

Recommended Mitigation Steps

It should return super.flashLoan(...).

function flashLoan is vulnerable to overflow/underflow and maxFlashLoan is not used

Handle

paulius.eth

Vulnerability details

Impact

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.

Recommended Mitigation Steps

Either use SafeMath here or make sure to never introduce a fee > 0. Also, make use of maxFlashLoan function.

Unused imports

Handle

paulius.eth

Vulnerability details

Impact

Remove unused imports, for example, contract NFTXVaultUpgradeable:
import "./interface/IPrevNftxContract.sol";
import "./interface/IRewardDistributionToken.sol";
interfaces IVaultTokenUpgradeable, ITransparentUpgradeableProxy are not used anywhere.

Recommended Mitigation Steps

Remove unused code to reduce deployment costs.

getPseudoRand is easily manipulatable

Handle

paulius.eth

Vulnerability details

Impact

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.

Not checked if within array bounds

Handle

gpersoon

Vulnerability details

Impact

In the function updateModule and deployEligibility of NFTXEligiblityManager.sol, the array modules is used without checking if the index is within bounds.
If index would be out of bounds, the function will revert, but it's more difficult to troubleshoot.

Proof of Concept

NFTXEligiblityManager.sol:
function updateModule(uint256 index, address implementation) public onlyOwner {
modules[index].impl = implementation;
}

function deployEligibility(uint256 moduleIndex, bytes calldata configData) external virtual returns (address) {
address eligImpl = modules[moduleIndex].impl;
...
}

Tools Used

Editor

Recommended Mitigation Steps

Add something like:
require(index < modules.length,"out or range");

Testing NFTX finding submission form

Handle

adamavenir

Vulnerability details

Impact

Provide a detailed description of the impact this bug/vulnerability has on the overall system under test.

Proof of Concept

Provide screenshots, logs, or any other relevant proof that illustrates the concept of the bug/vulnerability you have identified.

Tools Used

Describe the tools used throughout your testing and analysis process.

Recommended Mitigation Steps

Describe the recommended steps that a project should use to mitigate the bugs or vulnerabilities you have identified.

Inconsistent solidity pragma

Handle

maplesyrup

Vulnerability details

Impact

The source files have different solidity compiler ranges referenced. This leads to potential security flaws between deployed contracts depending on the compiler version chosen for any particular file. It also greatly increases the cost of maintenance as different compiler versions have different semantics and behavior.

Proof of Concept

This defect has numerous surfaces at https://github.com/code-423n4/2021-05-nftx/tree/main/nftx-protocol-v2/contracts/solidity

Different versions of Solidity are used in :
- Version used: ['0.6.8', '>=0.4.22<0.9.0', '>=0.4.24<0.7.0', '>=0.6.0<0.8.0', '>=0.6.2<0.8.0', '^0.6.0', '^0.6.8']
- 0.6.8 (contracts/solidity/NFTXEligiblityManager.sol#2)
- ABIEncoderV2 (contracts/solidity/NFTXEligiblityManager.sol#3)
- ^0.6.8 (contracts/solidity/NFTXFeeDistributor.sol#3)
- 0.6.8 (contracts/solidity/NFTXLPStaking.sol#3)
- 0.6.8 (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#3)
- 0.6.8 (contracts/solidity/NFTXVaultUpgradeable.sol#3)
- 0.6.8 (contracts/solidity/StakingTokenProvider.sol#3)
- 0.6.8 (contracts/solidity/eligibility/NFTXDeferEligibility.sol#3)
- 0.6.8 (contracts/solidity/eligibility/NFTXDenyEligibility.sol#3)
- 0.6.8 (contracts/solidity/eligibility/NFTXEligibility.sol#3)
- 0.6.8 (contracts/solidity/eligibility/NFTXListEligibility.sol#3)
- 0.6.8 (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#3)
- 0.6.8 (contracts/solidity/eligibility/NFTXRangeEligibility.sol#3)
- 0.6.8 (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#3)
- 0.6.8 (contracts/solidity/eligibility/UniqueEligibility.sol#2)
- >=0.6.0<0.8.0 (contracts/solidity/interface/IERC165Upgradeable.sol#3)
- 0.6.8 (contracts/solidity/interface/IERC3156Upgradeable.sol#3)
- 0.6.8 (contracts/solidity/interface/INFTXEligibility.sol#2)
- 0.6.8 (contracts/solidity/interface/INFTXEligibilityManager.sol#1)
- ^0.6.8 (contracts/solidity/interface/INFTXFeeDistributor.sol#3)
- 0.6.8 (contracts/solidity/interface/INFTXLPStaking.sol#3)
- 0.6.8 (contracts/solidity/interface/INFTXVault.sol#3)
- 0.6.8 (contracts/solidity/interface/INFTXVaultFactory.sol#3)
- 0.6.8 (contracts/solidity/interface/IPrevNftxContract.sol#3)
- 0.6.8 (contracts/solidity/interface/IRewardDistributionToken.sol#3)
- 0.6.8 (contracts/solidity/interface/IVaultTokenUpgradeable.sol#3)
- 0.6.8 (contracts/solidity/proxy/BeaconProxy.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/proxy/ClonesUpgradeable.sol#3)
- 0.6.8 (contracts/solidity/proxy/IBeacon.sol#3)
- >=0.4.24<0.7.0 (contracts/solidity/proxy/Initializable.sol#3)
- 0.6.8 (contracts/solidity/proxy/Proxy.sol#3)
- 0.6.8 (contracts/solidity/proxy/UpgradeableBeacon.sol#3)
- 0.6.8 (contracts/solidity/testing/MockStakingProvider.sol#3)
- 0.6.8 (contracts/solidity/testing/MockVault.sol#2)
- ^0.6.0 (contracts/solidity/token/ERC1155HolderUpgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/token/ERC20BurnableUpgradeable.sol#3)
- 0.6.8 (contracts/solidity/token/ERC20FlashMintUpgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/token/ERC20Upgradeable.sol#3)
- ^0.6.0 (contracts/solidity/token/ERC721HolderUpgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/token/IERC1155ReceiverUpgradeable.sol#3)
- >=0.6.2<0.8.0 (contracts/solidity/token/IERC1155Upgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/token/IERC20Upgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/token/IERC721ReceiverUpgradeable.sol#3)
- >=0.6.2<0.8.0 (contracts/solidity/token/IERC721Upgradeable.sol#3)
- 0.6.8 (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#2)
- 0.6.8 (contracts/solidity/util/Address.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/util/ContextUpgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/util/EnumerableSetUpgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/util/OwnableUpgradeable.sol#3)
- 0.6.8 (contracts/solidity/util/PausableUpgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/util/ReentrancyGuardUpgradeable.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/util/SafeERC20Upgradeable.sol#3)
- 0.6.8 (contracts/solidity/util/SafeMathInt.sol#3)
- >=0.6.0<0.8.0 (contracts/solidity/util/SafeMathUpgradeable.sol#3)
- >=0.4.22<0.9.0 (node_modules/hardhat/console.sol#2)

Tools Used

Slither

Recommended Mitigation Steps

Fix a definite compiler range that is consistent between contracts and upgrade any affected contracts to conform to the specified compiler.

Don't evaluate the same calculations over and over again

Handle

paulius.eth

Vulnerability details

Impact

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)

Public function that could be declared external

Handle

@maplesyrup

Vulnerability details

Impact

This vulnerability does not cause any immediate risk to the contract and its safety.

This is strictly a Gas Optimization recommendation.

Proof of Concept

Public functions that are never called by the contract should be declared external to save gas.

Mentions external:
External functions are part of the contract interface, which means they can be called from other contracts and via transactions. An external function f cannot be called internally (i.e. f() does not work, but this.f() works). External functions are sometimes more efficient when they receive large arrays of data.

  • Also External calls use calldata which is more efficient

  • Below is the functions found that can be set to external to help save gas:

"__NFTXEligibilityManager_init() should be declared external:\n\t- NFTXEligibilityManager.__NFTXEligibilityManager_init() (contracts/solidity/NFTXEligiblityManager.sol#18-20)\n",

"addModule(address) should be declared external:\n\t- NFTXEligibilityManager.addModule(address) (contracts/solidity/NFTXEligiblityManager.sol#22-25)\n",

"updateModule(uint256,address) should be declared external:\n\t- NFTXEligibilityManager.updateModule(uint256,address) (contracts/solidity/NFTXEligiblityManager.sol#27-29)\n",

The functions in the following contract should be declared as external:

NFTXEligiblityManager.sol:

Line 17:
function __NFTXEligibilityManager_init() public initializer {…}. <---- Should be declared as external

Line 21:
function addModule(address implementation) public onlyOwner {…} <---- Should be declared as external

Line 26:
function updateModule(uint256 index, address implementation) public onlyOwner {..} <---- Should be declared as external


"__FeeDistributor__init__(address,address) should be declared external:\n\t- NFTXFeeDistributor.__FeeDistributor__init__(address,address) (contracts/solidity/NFTXFeeDistributor.sol#35-41)\n",

The functions in the following contract should be declared as external:

NFTXFeeDistributor.sol:

Line 34:
function FeeDistributor__init(address _lpStaking, address _treasury) public override initializer {…} <---- Should be declared as external


"__NFTXVaultFactory_init(address,address,address) should be declared external:\n\t- NFTXVaultFactoryUpgradeable.__NFTXVaultFactory_init(address,address,address) (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#33-39)\n",

"createVault(string,string,address,bool,bool) should be declared external:\n\t- NFTXVaultFactoryUpgradeable.createVault(string,string,address,bool,bool) (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#41-60)\n",

"setFeeReceiver(address) should be declared external:\n\t- NFTXVaultFactoryUpgradeable.setFeeReceiver(address) (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#62-66)\n",

The functions in the following contract should be declared as external:

NFTXVaultFactoryUpgradeable.sol:

Line 33:
function __NFTXVaultFactory_init(address _vaultImpl, address _prevContract, address _feeReceiver) public override initializer {…} <---- Should be declared as external

Line 41:
function createVault (
string memory name,
string memory symbol,
address _assetAddress,
bool is1155,
bool allowAllItems
) public virtual override returns (uint256) {…} <---- Should be declared as external

Line 62:
function setFeeReceiver(address _feeReceiver) public onlyOwner virtual override {…} <---- Should be declared as external


"__NFTXVault_init(string,string,address,bool,bool) should be declared external:\n\t- NFTXVaultUpgradeable.__NFTXVault_init(string,string,address,bool,bool) (contracts/solidity/NFTXVaultUpgradeable.sol#100-117)\n",

The functions in the following contract should be declared as external:

NFTXVaultUpgradeable.sol:

Line 100:
function __NFTXVault_init(
string memory _name,
string memory _symbol,
address _assetAddress,
bool _is1155,
bool _allowAllItems
) public initializer { … } <---- Should be declared as external


"__StakingTokenProvider_init(address,address,string) should be declared external:\n\t- StakingTokenProvider.__StakingTokenProvider_init(address,address,string) (contracts/solidity/StakingTokenProvider.sol#23-28)\n",

The functions in the following contract should be declared as external:

StakingTokenProvider.sol:

Line 23:
function __StakingTokenProvider_init(address _uniLikeExchange, address _defaultPairedtoken, string memory _defaultPrefix) public initializer {…} <---- Should be declared as external


"name() should be declared external:\n\t- NFTXDeferEligibility.name() (contracts/solidity/eligibility/NFTXDeferEligibility.sol#10-12)\n\t- NFTXDenyEligibility.name() (contracts/solidity/eligibility/NFTXDenyEligibility.sol#9-11)\n\t- NFTXEligibility.name() (contracts/solidity/eligibility/NFTXEligibility.sol#10)\n\t- NFTXListEligibility.name() (contracts/solidity/eligibility/NFTXListEligibility.sol#12-14)\n\t- NFTXMintRequestEligibility.name() (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#24-26)\n\t- NFTXRangeEligibility.name() (contracts/solidity/eligibility/NFTXRangeEligibility.sol#18-20)\n\t- NFTXUniqueEligibility.name() (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#18-20)\n"

The function in the following contract(s) should be declared as external:

function name() public view override virtual returns (string memory) { … } <---- Should be declared as external

NFTXDeferEligibility.sol (Line 10)
NFTXDenyEligibility.sol (Line 9)
NFTXEligibility.sol (Line 10)
NFTXListEligibility.sol (Line 12)
NFTXMintRequestEligibility.sol (Line 24)
NFTXRangeEligibility.sol (Line 18)
NFTXUniqueEligibility.sol (Line 18)


"__NFTXEligibility_init_bytes(bytes) should be declared external:\n\t- NFTXDeferEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXDeferEligibility.sol#32-37)\n\t- NFTXEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXEligibility.sol#13)\n\t- NFTXListEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXListEligibility.sol#28-33)\n\t- NFTXMintRequestEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#58-68)\n\t- NFTXRangeEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXRangeEligibility.sol#44-54)\n\t- NFTXUniqueEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#47-57)\n",

The function in the following contract(s) should be declared as external:

function __NFTXEligibility_init_bytes(
bytes memory configData
) public override virtual initializer {…} <---- Should be declared as external

NFTXDeferEligibility.sol (Line 32)
NFTXEligibility.sol (Line 13)
NFTXListEligibility.sol (Line 28)
NFTXMintRequestEligibility.sol (Line 58)
NFTXRangeEligibility.sol (Line 44)
NFTXUniqueEligibility.sol (Line 47)


"setUniqueEligibilities(uint256[],bool) should be declared external:\n\t- NFTXMintRequestEligibility.setUniqueEligibilities(uint256[],bool) (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#218-226)\n",

The function in the following contract(s) should be declared as external:

NFTXMintRequestEligibility.sol:

Line 218:
function setUniqueEligibilities(uint256[] memory tokenIds, bool _isEligible)
public
virtual {…} <---- Should be declared as external


"setEligibilityPreferences(bool) should be declared external:\n\t- NFTXUniqueEligibility.setEligibilityPreferences(bool) (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#85-91)\n",

"setUniqueEligibilities(uint256[],bool) should be declared external:\n\t- NFTXUniqueEligibility.setUniqueEligibilities(uint256[],bool) (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#93-99)\n",

The function(s) in the following contract(s) should be declared as external:

NFTXUniqueEligibility.sol

Line 85:
function setEligibilityPreferences(bool _reverseEligOnRedeem)
public
onlyOwner
{…} <---- Should be declared as external

Line 93:
function setUniqueEligibilities(uint256[] memory tokenIds, bool _isEligible)
public
virtual
onlyOwner
{…} <---- Should be declared as external


"upgradeTo(address) should be declared external:\n\t- UpgradeableBeacon.upgradeTo(address) (contracts/solidity/proxy/UpgradeableBeacon.sol#48-51)\n",

The function(s) in the following contract(s) should be declared as external:

UpgradeableBeacon.sol:

Line 48:
function upgradeTo(address newImplementation) public virtual onlyOwner {…} <---- Should be declared as external

"mintTo(uint256[],uint256[],address) should be declared external:\n\t- MockVault.mintTo(uint256[],uint256[],address) (contracts/solidity/testing/MockVault.sol#15-21)\n",

The function(s) in the following contract(s) should be declared as external:

MockVault.sol:

Line 15:
function mintTo(
uint256[] memory tokenIds,
uint256[] memory amounts,
address to
) public returns (uint256) {…} <---- Should be declared as external

"onERC1155Received(address,address,uint256,uint256,bytes) should be declared external:\n\t- ERC1155HolderUpgradeable.onERC1155Received(address,address,uint256,uint256,bytes) (contracts/solidity/token/ERC1155HolderUpgradeable.sol#9-17)\n",

"onERC1155BatchReceived(address,address,uint256[],uint256[],bytes) should be declared external:\n\t- ERC1155HolderUpgradeable.onERC1155BatchReceived(address,address,uint256[],uint256[],bytes) (contracts/solidity/token/ERC1155HolderUpgradeable.sol#19-27)\n",

The function(s) in the following contract(s) should be declared as external:

ERC1155HolderUpgradeable.sol:

Line 9:
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {…} <---- Should be declared as external

Line 19:
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {…} <---- Should be declared as external


"burn(uint256) should be declared external:\n\t- ERC20BurnableUpgradeable.burn(uint256) (contracts/solidity/token/ERC20BurnableUpgradeable.sol#30-32)\n\t- RewardDistributionTokenUpgradeable.burn(uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#94-96)\n",

The function(s) in the following contract(s) should be declared as external:

ERC20BurnableUpgradeable.sol

Line 30:
function burn(uint256 amount) public virtual {…} <---- Should be declared as external

RewardDistributionTokenUpgradeable.sol:

Line 94:
function burn(uint256 amount) public virtual override {…} <---- Should be declared as external


"maxFlashLoan(address) should be declared external:\n\t- ERC20FlashMintUpgradeable.maxFlashLoan(address) (contracts/solidity/token/ERC20FlashMintUpgradeable.sol#31-33)\n",

The function(s) in the following contract(s) should be declared as external:

ERC20FlashMintUpgradeable.sol:

Line 31:
function maxFlashLoan(address token) public view override returns (uint256) {…} <---- Should be declared as external


"name() should be declared external:\n\t- ERC20Upgradeable.name() (contracts/solidity/token/ERC20Upgradeable.sol#80-82)\n",

"symbol() should be declared external:\n\t- ERC20Upgradeable.symbol() (contracts/solidity/token/ERC20Upgradeable.sol#88-90)\n",

"decimals() should be declared external:\n\t- ERC20Upgradeable.decimals() (contracts/solidity/token/ERC20Upgradeable.sol#105-107)\n",

The function(s) in the following contract(s) should be declared as external:

ERC20Upgradeable.sol

Line 80: function name() public view override virtual returns (string memory) {…} <---- Should be declared as external

Line 88: function symbol() public view override virtual returns (string memory) {…} <---- Should be declared as external

Line 105: function decimals() public view virtual returns (uint8) {…} <---- Should be declared as external


"transfer(address,uint256) should be declared external:\n\t- ERC20Upgradeable.transfer(address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#137-145)\n\t- RewardDistributionTokenUpgradeable.transfer(address,uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#53-61)\n",

The function(s) in the following contract(s) should be declared as external:

ERC20Upgradeable.sol:

Line 137:
function transfer(address recipient, uint256 amount)
public
virtual
override
returns (bool)
{…} <---- Should be declared as external

RewardDistributionTokenUpgradeable.sol:

Line 53:
function transfer(address recipient, uint256 amount)
public
virtual
override
returns (bool)
{…} <---- Should be declared as external


"approve(address,uint256) should be declared external:\n\t- ERC20Upgradeable.approve(address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#167-175)\n",

The function(s) in the following contract(s) should be declared as external:

ERC20Upgradeable.sol:

Line 167:

function approve(address spender, uint256 amount)
public
virtual
override
returns (bool)
{…} <---- Should be declared as external


"transferFrom(address,address,uint256) should be declared external:\n\t- ERC20Upgradeable.transferFrom(address,address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#190-206)\n\t- RewardDistributionTokenUpgradeable.transferFrom(address,address,uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#76-92)\n",

The function(s) in the following contract(s) should be declared as external:

ERC20Upgradeable.sol

Line 190:

function transferFrom(address sender, address recipient, uint256 amount)
public
virtual
override
returns (bool)
{…} <---- Should be declared as external

RewardDistributionTokenUpgradeable.sol:

Line 76:

function transferFrom(address sender, address recipient, uint256 amount)
public
virtual
override
returns (bool)
{…} <---- Should be declared as external


"increaseAllowance(address,uint256) should be declared external:\n\t- ERC20Upgradeable.increaseAllowance(address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#220-231)\n",

"decreaseAllowance(address,uint256) should be declared external:\n\t- ERC20Upgradeable.decreaseAllowance(address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#247-261)\n",

The function(s) in the following contract(s) should be declared as external:

ERC20Upgradeable.sol:

Line 220:
function increaseAllowance(address spender, uint256 addedValue)
public
virtual
returns (bool)
{…} <---- Should be declared as external

Line 247:
function decreaseAllowance(address spender, uint256 subtractedValue)
public
virtual
returns (bool)
{…} <---- Should be declared as external


"onERC721Received(address,address,uint256,bytes) should be declared external:\n\t- ERC721HolderUpgradeable.onERC721Received(address,address,uint256,bytes) (contracts/solidity/token/ERC721HolderUpgradeable.sol#19-26)\n",

The function(s) in the following contract(s) should be declared as external:

ERC721HolderUpgradeable.sol:

Line 19:
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {…} <---- Should be declared as external


"__RewardDistributionToken_init(IERC20Upgradeable,string,string) should be declared external:\n\t- RewardDistributionTokenUpgradeable.__RewardDistributionToken_init(IERC20Upgradeable,string,string) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#46-51)\n",

"mint(address,uint256) should be declared external:\n\t- RewardDistributionTokenUpgradeable.mint(address,uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#98-100)\n",


"burnFrom(address,uint256) should be declared external:\n\t- RewardDistributionTokenUpgradeable.burnFrom(address,uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#113-118)\n",


"dividendOf(address) should be declared external:\n\t- RewardDistributionTokenUpgradeable.dividendOf(address) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#159-161)\n",

"withdrawnRewardOf(address) should be declared external:\n\t- RewardDistributionTokenUpgradeable.withdrawnRewardOf(address) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#173-175)\n",

The function(s) in the following contract(s) should be declared as external:

RewardDistributionTokenUpgradeable.sol:

Line 46:
function __RewardDistributionToken_init(IERC20Upgradeable _target, string memory _name, string memory _symbol) public initializer {…} <---- Should be declared as external

Line 98:
function mint(address account, uint256 amount) public onlyOwner virtual {…} <---- Should be declared as external

Line 113:
function burnFrom(address account, uint256 amount) public onlyOwner virtual {…} <---- Should be declared as external

Line 159:
function dividendOf(address _owner) public view returns(uint256) {…} <---- Should be declared as external

Line 173:
function withdrawnRewardOf(address _owner) public view returns(uint256) {…} <---- Should be declared as external


"unpause(uint256) should be declared external:\n\t- PausableUpgradeable.unpause(uint256) (contracts/solidity/util/PausableUpgradeable.sol#28-35)\n",

"pause(uint256) should be declared external:\n\t- PausableUpgradeable.pause(uint256) (contracts/solidity/util/PausableUpgradeable.sol#37-41)\n",

"setIsGuardian(address,bool) should be declared external:\n\t- PausableUpgradeable.setIsGuardian(address,bool) (contracts/solidity/util/PausableUpgradeable.sol#43-46)\n"

The function(s) in the following contract(s) should be declared as external:

PausableUpgradeable.sol

Line 28:
function unpause(uint256 lockId)
public
virtual
onlyOwner
{…} <---- Should be declared as external

Line 37: function pause(uint256 lockId) public virtual {…} <---- Should be declared as external

Line 43: function setIsGuardian(address addr, bool _isGuardian) public virtual onlyOwner {…} <---- Should be declared as external

  • Slither Log Output:

INFO:Detectors:
__NFTXEligibilityManager_init() should be declared external:
- NFTXEligibilityManager.__NFTXEligibilityManager_init() (contracts/solidity/NFTXEligiblityManager.sol#17-19)
addModule(address) should be declared external:
- NFTXEligibilityManager.addModule(address) (contracts/solidity/NFTXEligiblityManager.sol#21-24)
updateModule(uint256,address) should be declared external:
- NFTXEligibilityManager.updateModule(uint256,address) (contracts/solidity/NFTXEligiblityManager.sol#26-28)
FeeDistributor__init(address,address) should be declared external:
- NFTXFeeDistributor.FeeDistributor__init(address,address) (contracts/solidity/NFTXFeeDistributor.sol#35-41)
__NFTXVaultFactory_init(address,address,address) should be declared external:
- NFTXVaultFactoryUpgradeable.__NFTXVaultFactory_init(address,address,address) (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#33-39)
createVault(string,string,address,bool,bool) should be declared external:
- NFTXVaultFactoryUpgradeable.createVault(string,string,address,bool,bool) (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#41-60)
setFeeReceiver(address) should be declared external:
- NFTXVaultFactoryUpgradeable.setFeeReceiver(address) (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#62-66)
__NFTXVault_init(string,string,address,bool,bool) should be declared external:
- NFTXVaultUpgradeable.__NFTXVault_init(string,string,address,bool,bool) (contracts/solidity/NFTXVaultUpgradeable.sol#100-117)
__StakingTokenProvider_init(address,address,string) should be declared external:
- StakingTokenProvider.__StakingTokenProvider_init(address,address,string) (contracts/solidity/StakingTokenProvider.sol#23-28)
name() should be declared external:
- NFTXDeferEligibility.name() (contracts/solidity/eligibility/NFTXDeferEligibility.sol#10-12)
- NFTXDenyEligibility.name() (contracts/solidity/eligibility/NFTXDenyEligibility.sol#9-11)
- NFTXEligibility.name() (contracts/solidity/eligibility/NFTXEligibility.sol#10)
- NFTXListEligibility.name() (contracts/solidity/eligibility/NFTXListEligibility.sol#12-14)
- NFTXMintRequestEligibility.name() (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#24-26)
- NFTXRangeEligibility.name() (contracts/solidity/eligibility/NFTXRangeEligibility.sol#18-20)
- NFTXUniqueEligibility.name() (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#18-20)
__NFTXEligibility_init_bytes(bytes) should be declared external:
- NFTXDeferEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXDeferEligibility.sol#32-37)
- NFTXEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXEligibility.sol#13)
- NFTXListEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXListEligibility.sol#28-33)
- NFTXMintRequestEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#58-68)
- NFTXRangeEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXRangeEligibility.sol#44-54)
- NFTXUniqueEligibility.__NFTXEligibility_init_bytes(bytes) (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#47-57)
setUniqueEligibilities(uint256[],bool) should be declared external:
- NFTXMintRequestEligibility.setUniqueEligibilities(uint256[],bool) (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#218-226)
setEligibilityPreferences(bool) should be declared external:
- NFTXUniqueEligibility.setEligibilityPreferences(bool) (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#85-91)
setUniqueEligibilities(uint256[],bool) should be declared external:
- NFTXUniqueEligibility.setUniqueEligibilities(uint256[],bool) (contracts/solidity/eligibility/NFTXUniqueEligibility.sol#93-99)
upgradeTo(address) should be declared external:
- UpgradeableBeacon.upgradeTo(address) (contracts/solidity/proxy/UpgradeableBeacon.sol#48-51)
mintTo(uint256[],uint256[],address) should be declared external:
- MockVault.mintTo(uint256[],uint256[],address) (contracts/solidity/testing/MockVault.sol#14-20)
onERC1155Received(address,address,uint256,uint256,bytes) should be declared external:
- ERC1155HolderUpgradeable.onERC1155Received(address,address,uint256,uint256,bytes) (contracts/solidity/token/ERC1155HolderUpgradeable.sol#9-17)
onERC1155BatchReceived(address,address,uint256[],uint256[],bytes) should be declared external:
- ERC1155HolderUpgradeable.onERC1155BatchReceived(address,address,uint256[],uint256[],bytes) (contracts/solidity/token/ERC1155HolderUpgradeable.sol#19-27)
burn(uint256) should be declared external:
- ERC20BurnableUpgradeable.burn(uint256) (contracts/solidity/token/ERC20BurnableUpgradeable.sol#30-32)
- RewardDistributionTokenUpgradeable.burn(uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#94-96)
maxFlashLoan(address) should be declared external:
- ERC20FlashMintUpgradeable.maxFlashLoan(address) (contracts/solidity/token/ERC20FlashMintUpgradeable.sol#31-33)
name() should be declared external:
- ERC20Upgradeable.name() (contracts/solidity/token/ERC20Upgradeable.sol#80-82)
symbol() should be declared external:
- ERC20Upgradeable.symbol() (contracts/solidity/token/ERC20Upgradeable.sol#88-90)
decimals() should be declared external:
- ERC20Upgradeable.decimals() (contracts/solidity/token/ERC20Upgradeable.sol#105-107)
transfer(address,uint256) should be declared external:
- ERC20Upgradeable.transfer(address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#137-145)
- RewardDistributionTokenUpgradeable.transfer(address,uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#53-61)
approve(address,uint256) should be declared external:
- ERC20Upgradeable.approve(address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#167-175)
transferFrom(address,address,uint256) should be declared external:
- ERC20Upgradeable.transferFrom(address,address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#190-206)
- RewardDistributionTokenUpgradeable.transferFrom(address,address,uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#76-92)
increaseAllowance(address,uint256) should be declared external:
- ERC20Upgradeable.increaseAllowance(address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#220-231)
decreaseAllowance(address,uint256) should be declared external:
- ERC20Upgradeable.decreaseAllowance(address,uint256) (contracts/solidity/token/ERC20Upgradeable.sol#247-261)
onERC721Received(address,address,uint256,bytes) should be declared external:
- ERC721HolderUpgradeable.onERC721Received(address,address,uint256,bytes) (contracts/solidity/token/ERC721HolderUpgradeable.sol#19-26)
__RewardDistributionToken_init(IERC20Upgradeable,string,string) should be declared external:
- RewardDistributionTokenUpgradeable.__RewardDistributionToken_init(IERC20Upgradeable,string,string) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#46-51)
mint(address,uint256) should be declared external:
- RewardDistributionTokenUpgradeable.mint(address,uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#98-100)
burnFrom(address,uint256) should be declared external:
- RewardDistributionTokenUpgradeable.burnFrom(address,uint256) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#113-118)
dividendOf(address) should be declared external:
- RewardDistributionTokenUpgradeable.dividendOf(address) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#159-161)
withdrawnRewardOf(address) should be declared external:
- RewardDistributionTokenUpgradeable.withdrawnRewardOf(address) (contracts/solidity/token/RewardDistributionTokenUpgradeable.sol#173-175)
unpause(uint256) should be declared external:
- PausableUpgradeable.unpause(uint256) (contracts/solidity/util/PausableUpgradeable.sol#28-35)
pause(uint256) should be declared external:
- PausableUpgradeable.pause(uint256) (contracts/solidity/util/PausableUpgradeable.sol#37-41)
setIsGuardian(address,bool) should be declared external:
- PausableUpgradeable.setIsGuardian(address,bool) (contracts/solidity/util/PausableUpgradeable.sol#43-46)
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external

Tools Used

Compiled, Tested, and Deployed contracts on a local Hardhat network.

Ran Slither-analyzer for further detecting and testing.

Recommended Mitigation Steps

(Worked best under a python virtual environment)

  1. Clone project repository
  2. Run project against hardhat network;
    compile and run default test on contracts.
  3. Installed slither analyzer:
    https://github.com/crytic/slither
  4. Ran [$ slither .] against all contracts

EIP-721 / EIP-1155 Re-Entrancy Vulnerability (Resubmission)

Handle

0xsomeone

Vulnerability details

This submission acts as additional material to the original submission. I would like to note that the "Areas of Review" chapter of the contest explicitly states that "Vaults should always maintain 1:1 ratio..." which the re-entrancy breaks.

Too similar naming

Handle

maplesyrup

Vulnerability details

Impact

Similar method parameter and contract state variable names are error prone.

Proof of Concept

https://github.com/code-423n4/2021-05-nftx/blob/f6d793c136d110774de259d9f3b25d003c4f8098/nftx-protocol-v2/contracts/solidity/proxy/UpgradeableBeacon.sol#L16

and

https://github.com/code-423n4/2021-05-nftx/blob/f6d793c136d110774de259d9f3b25d003c4f8098/nftx-protocol-v2/contracts/solidity/proxy/UpgradeableBeacon.sol#L27

have implementation and implementation names.

Tools Used

Slither

Recommended Mitigation Steps

Simply rename the method parameter as "impl" or some other variant that is more clearly distinguishable.

Join functions

Handle

paulius.eth

Vulnerability details

Impact

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.

Recommended Mitigation Steps

Join functions getRandomTokenIdFromFund and getPseudoRand.

immutable variables

Handle

paulius.eth

Vulnerability details

Impact

Marking variables that do not change as 'immutable' greatly reduces gas costs. Some examples where variables can be marked as immutable:
contract NFTXLPStaking variable rewardDistTokenImpl.
contract NFTXVaultFactoryUpgradeable variable prevContract.
contract NFTXVaultUpgradeable variable vaultId.

Recommended Mitigation Steps

Mark such variables as 'immutable'.

getRandomTokenIdFromFund not really random

Handle

gpersoon

Vulnerability details

Impact

The function getRandomTokenIdFromFund of NFTXVaultUpgradeable.sol is not really random, as noted in the name of the function getPseudoRand.
The value of the blockhash(block.number - 1) is fully determined for al the transactions in the same block.
The result is that the retrieval of NFS's (via redeemTo, swapTo, withdrawNFTsTo, while enableDirectRedeem==false) can be manipulated.
You can make a smart contract that tries to call redeemTo or swapTo, checks the resulting NFTs and reverts if it has NFTs that are unwanted.

Proof of Concept

function getPseudoRand(uint256 modulus) internal virtual returns (uint256) {
    randNonce += 1;
    return
        uint256(
            keccak256(
                abi.encodePacked(blockhash(block.number - 1), randNonce)
            )
        ) %
        modulus;
}

Tools Used

Editor

Recommended Mitigation Steps

If the value or desirability of the NFT's are different then it's important to use other ways to randomize, for example via a random oracle or a commit/reveal schema.

`distribute` DoS on missing `receiveRewards` implementation

Handle

@cmichelio

Vulnerability details

Vulnerability Details

NFTXEligiblityManager._sendForReceiver should check returnData.length == 1 before decoding, otherwise if it returns no return data, the abi.decode call and with it the whole distribute function will revert.

Impact

A single badly implemented feeReceiver can break the whole distribute function and do a denial of service by reverting the transaction.

Recommended Mitigation Steps

Change to: bool tokensReceived = returnData.length == 1 && abi.decode(returnData, (bool));.

Missing usage of SafeMath

Handle

@cmichelio

Vulnerability details

Vulnerability Details

The following code does not use SafeMath and can potentially lead to overflows:

  • NFTXFeeDistributor.distribute
  • NFTXFeeDistributor._sendForReceiver

Impact

While looping through all _feeReceivers it could be that a broken vault was whitelisted that allows an attacker to perform an external call and break the invariant that always 1000 tokens are left in the contract.

Recommended Mitigation Steps

Add SafeMath to _sendForReceiver even though one would expect the math to be safe.

Testing submissions

Handle

adamavenir

Vulnerability details

Impact

Detailed description of the impact of this finding.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

Tools Used

Recommended Mitigation Steps

eligibilityManager is always 0x0

Handle

paulius.eth

Vulnerability details

Impact

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.

Recommended Mitigation Steps

Either add a setter for eligibilityManager or refactor function deployEligibilityStorage to work in such case.

getRandomTokenIdFromFund unfair for ERC1155 tokens

Handle

gpersoon

Vulnerability details

Impact

In NFTXVaultUpgradeable.sol the received ERC1155 NFT's are stored in the array holdings.
The number of NFT's of a specific type are kept in quantity1155.
Suppose there is one NFT (#1) with 10 instances and 9 NFT's with 1 instance. (#2..#10)
When you randomly retrieve a NFT via getRandomTokenIdFromFund it takes a random number from the holdings array, without taking in account the number of instances.
So in the example the probability of getting a NFT of type #1 is about 10%
While if you take in account all the NFT's + instances then the change should be more than 50%.

Proof of Concept

function getRandomTokenIdFromFund() internal virtual returns (uint256) {
    uint256 randomIndex = getPseudoRand(holdings.length());
    return holdings.at(randomIndex);
}

Tools Used

Editor

Recommended Mitigation Steps

If the unfairness is considered problematic then the code of getRandomTokenIdFromFund should be enhanced.

erc1155 are redeemed one by one

Handle

paulius.eth

Vulnerability details

Impact

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.

Recommended Mitigation Steps

I would suggest somehow combine redeem logic with balanceOf function to avoid this issue.

EIP-721 / EIP-1155 Re-Entrancy Vulnerability

Handle

0xsomeone

Vulnerability details

Impact

The impact of this finding is difficult to estimate as the contract system within scope is limited in how the various components are meant to be utilized.

A definitive side-effect of this re-entrancy is the delayed application of the afterRedeemHook which, in some implementations, renders NFTs illegible which would not be the case during the re-entrancy's execution. Another side-effect is that the quantity1155 or holdings would be out-of-sync and would indicate the NFT / EIP-1155 token amount to still be "in the system" when it is not.

Proof of Concept

The safeTransferFrom implementations of both ERC1155 and ERC721 in withdrawNFTsTo contain a callback hook on the recipient of the transfer in case they are a contract as the standard dictates that smart contract recipients should be aware of the transfer.

While re-entrancies are prevented via the nonReentrant modifier for most system functions, they are not done so for swapTo (and consequently swap) invocations meaning that it is still possible to re-enter the system at this stage. Additionally, re-entrancy is still possible in other segments of the codebase i.e. ones that rely on the eligibility contracts.

Tools Used

Manual Review.

Recommended Mitigation Steps

The afterRedeemHook paradigm should be changed to a beforeRedeemHook paradigm to ensure that all state changes are applied prior to external calls according to the Checks-Effects-Interactions pattern. Additionally, the state changes within withdrawNFTsTo should occur prior to the safeTransferFrom invocations.

SPDX License identifier missing

Unbounded iteration in `NFTXEligiblityManager.distribute` over `_feeReceivers`

Handle

@cmichelio

Vulnerability details

Vulnerability Details

NFTXEligiblityManager.distribute iterates over all _feeReceivers.

Impact

If the number of _feeReceivers gets too big, the transaction's gas cost could exceed the block gas limit and make it impossible to call distribute at all.

Recommended Mitigation Steps

Keep the number of _feeReceivers small.

Missing access restriction on `NFTXVaultUpgradeable.finalizeFund`

Handle

@cmichelio

Vulnerability details

Vulnerability Details

Missing access restriction on NFTXVaultUpgradeable.finalizeFund.

Impact

Anyone can lock out the manager by calling finalizeFund which sets the manager to 0.
This griefing attack can prevent managers from setting correct fees, vault features, eligibility modules, etc. on the Vault which then needs to be redeployed (but users could have already deposited tokens) or go through a lengthy governance process and let the owner restore the manager but then finalizeFund can just be called again.

Recommended Mitigation Steps

Make finalizeFund only callable by the manager.

no check _rangeStart<=_rangeEnd

Handle

gpersoon

Vulnerability details

Impact

In NFTXRangeEligibility.sol a range is defined via __NFTXEligibility_init and setEligibilityPreferences.
No check is done to make sure _rangeStart<=_rangeEnd, so one could accidentally define as range that is effectively empty.

Proof of Concept

function setEligibilityPreferences(uint256 _rangeStart, uint256 _rangeEnd) externalvirtual onlyOwner {
rangeStart = _rangeStart;
rangeEnd = _rangeEnd;
emit RangeSet(_rangeStart, _rangeEnd);
}

Tools Used

Editor

Recommended Mitigation Steps

Consider adding a check to make sure _rangeStart<=_rangeEnd

Explicitly check _rangeEnd >= _rangeStart

Handle

paulius.eth

Vulnerability details

Impact

function setEligibilityPreferences should check that _rangeEnd >= _rangeStart. While this does not cause any serious harm and values can be changed anytime, I think it would make sense if the contracts do the explicit check that range end is not lower than range start.

Recommended Mitigation Steps

require _rangeEnd >= _rangeStart.

Weak PRNG

Handle

maplesyrup

Vulnerability details

Impact

Using blockhash/blocknumber and randNone are subject to attack, particularly by malicious miners:

https://medium.com/coinmonks/attack-on-pseudo-random-number-generator-prng-used-in-1000-guess-an-ethereum-lottery-game-7b76655f953d

This could be used to the behavior of getRandomTokenIdFromFund to cause a preferential TokenId to be returned. It allows for gaming of the system by miners or a savvy attacker.

Proof of Concept

Code is at - https://github.com/code-423n4/2021-05-nftx/blob/f6d793c136d110774de259d9f3b25d003c4f8098/nftx-protocol-v2/contracts/solidity/NFTXVaultUpgradeable.sol#L418

Tools Used

Slither

Recommended Mitigation Steps

Use of an on-chain oracle for true randomness:

ChainLink- https://blog.chain.link/verifiable-random-functions-vrf-random-number-generation-rng-feature/
RanDAO - https://github.com/randao/randao
Provable - https://provable.xyz/

Useless calculations when the flash fee is 0

Handle

paulius.eth

Vulnerability details

Impact

contract ERC20FlashMintUpgradeable always returns 0 as flashFee thus function flashLoan does useless calculations of adding/subtracting 0 from the amount.

Recommended Mitigation Steps

To reduce gas, there are several possibilities:

  1. function flashLoan can explicitly assume that the fee is 0 (not gonna work if the fee will be introduced later).
  2. it can check if fee > 0 before doing other calculations.
  3. extract it to a new variable and use that where necessary:
    uint256 amountWithFee = amount + fee;

function receiveNFTs does not check if amount > 0

Handle

paulius.eth

Vulnerability details

Impact

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.

Recommended Mitigation Steps

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.

Unused variables

Handle

paulius.eth

Vulnerability details

Impact

Unused variables:
contract NFTXVaultUpgradeable, string public description;
contract NFTXMintRequestEligibility, address public manager;

Recommended Mitigation Steps

Delete unused variables to reduce the deployment costs.

State variables that could be declared constant

Handle

@maplesyrup

Vulnerability details

Impact

Gas Optimizations
Does not affect the contract in any critical way. Provides efficiency in gas consumption.

Proof of Concept

According to the Slither analyzer detector documentation and others:
https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant

https://betterprogramming.pub/how-to-write-smart-contracts-that-optimize-gas-spent-on-ethereum-30b5e9c5db85

Adding the constant attributes to state variables that never change can help save gas. This falls under using proper data types in Solidity as some data types can be more costly than others. Using constant attributes to state variables that never change is the proper data type assignment.

When running the analyzer on code, the following variables were found in the following contracts that can be stated as constant:

"NFTXMintRequestEligibility.manager (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#28) should be constant\n",

NFTXMintRequestEligibility.sol:
line 28:
address public manager; <----- [should be stated as constant]


"NFTXVaultFactoryUpgradeable.eligibilityManager (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#24) should be constant\n",

NFTXVaultFactoryUpgradeable.sol:
line 24:
address public override eligibilityManager; <----- [should be stated as constant]


"NFTXVaultUpgradeable.description
(contracts/solidity/NFTXVaultUpgradeable.sol#59) should be constant\n"

NFTXVaultUpgradeable.sol:
line 59:

string public description; <----- [should be stated as constant]

Slither log:

(nftx) Eriks-Air:nftx-protocol-v2 thisguy$ slither .

INFO:Detectors:
NFTXMintRequestEligibility.manager (contracts/solidity/eligibility/NFTXMintRequestEligibility.sol#28) should be constant
NFTXVaultFactoryUpgradeable.eligibilityManager (contracts/solidity/NFTXVaultFactoryUpgradeable.sol#24) should be constant
NFTXVaultUpgradeable.description (contracts/solidity/NFTXVaultUpgradeable.sol#59) should be constant
Reference: https://github.com/crytic/slither/wiki/Detector-Documentation#state-variables-that-could-be-declared-constant

Tools Used

Compiled, Tested, and Deployed contracts on a local Hardhat network.

Ran Slither-analyzer for further detecting and testing.

Recommended Mitigation Steps

(Worked best under a python virtual environment)

  1. Clone project repository
  2. Run project against hardhat network;
    compile and run default test on contracts.
  3. Installed sliither analyzer:
    https://github.com/crytic/slither
  4. Ran [$ slither .] against all contracts

immutable not used

Handle

gpersoon

Vulnerability details

Impact

The keyword immutable is available since solidity 6.5
It can prevent mistakes by only allowing a variable to be assigned once.
This is applicable in several locations in the code, for example with the variables in function __NFTXVault_init.

Proof of Concept

NFTXVaultUpgradeable.sol
uint256 public vaultId;
address public assetAddress;
INFTXVaultFactory public vaultFactory;
bool public is1155;
bool public allowAllItems;

function __NFTXVault_init(...) public initializer {
...
assetAddress = _assetAddress;
vaultFactory = INFTXVaultFactory(msg.sender);
vaultId = vaultFactory.numVaults();
is1155 = _is1155;
allowAllItems = _allowAllItems;
...
}

Tools Used

Editor, Grep

Recommended Mitigation Steps

Use immutable where possible

struct with 1 field

Handle

paulius.eth

Vulnerability details

Impact

No need to use a struct with 1 field here, consumes extra gas:
struct EligibilityModule {
address impl;
}
EligibilityModule[] public modules;

Recommended Mitigation Steps

can be refactored to:
address[] public modules;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.