2021-05-nftx-findings's People
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
should implement
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
allsetter
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
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)
Gas optimization for `StakingTokenProvider.nameForStakingToken`
Handle
@cmichelio
Vulnerability details
Vulnerability Details
StakingTokenProvider.nameForStakingToken
: if (keccak256(abi.encode(_pairedPrefix)) == keccak256(abi.encode(address(0))))
can be simplified to if(bytes(_pairedPrefix).length== 0)
Public function that could be declared external
Handle
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
- According to Slither detector documentation:
https://github.com/crytic/slither/wiki/Detector-Documentation#public-function-that-could-be-declared-external
Public functions that are never called by the contract should be declared external to save gas.
- Solidity Documentation
https://docs.soliditylang.org/en/v0.4.24/contracts.html
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)
- Clone project repository
- Run project against hardhat network;
compile and run default test on contracts. - Installed slither analyzer:
https://github.com/crytic/slither - 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
and
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
Handle
maplesyrup
Vulnerability details
Impact
SPDX Licensing declaration was missing from several files. This impacted compilation and also makes the licensing of certain contracts unclear.
Proof of Concept
Tools Used
Slither
Recommended Mitigation Steps
Simply include
// SPDX-License-Identifier: MIT
as the first line of the above referenced files.
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:
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
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:
- function flashLoan can explicitly assume that the fee is 0 (not gonna work if the fee will be introduced later).
- it can check if fee > 0 before doing other calculations.
- 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
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
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)
- Clone project repository
- Run project against hardhat network;
compile and run default test on contracts. - Installed sliither analyzer:
https://github.com/crytic/slither - 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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.