Infinity NFT Marketplace contest - 0x1f8b's results

The world's most advanced NFT marketplace.

General Information

Platform: Code4rena

Start Date: 14/06/2022

Pot Size: $50,000 USDC

Total HM: 19

Participants: 99

Period: 5 days

Judge: HardlyDifficult

Total Solo HM: 4

Id: 136

League: ETH

Infinity NFT Marketplace

Findings Distribution

Researcher Performance

Rank: 33/99

Findings: 2

Award: $249.19

🌟 Selected for report: 0

🚀 Solo Findings: 0

Low

Centralized risks

Owner can change the thresholds wherever he wants.

Also he can change the penalties, and it could facilitate a front-running issues with bad actors.

PROTOCOL_FEE_BPS could be higher than factor (10_000) and it could facilitate a front-running or denial of services with bad actors.

Unfair staking increase

If the user makes any changes or changeDuration, the staking timestamp is reset and loses all the time already spent in the previous deposit.

Affected source code:

No compatible with fee tokens

The current lock logic does not contemplate ERC20 tokens with fee during the transferFrom, therefore, the amount received by BribeVault will be less than the expected.

Some tokens may implement a fee during transfers, this is the case of USDT, even though the project has currently set it to 0. So, the transferFrom function would return true despite receiving less than expected.

It's recommended to use balance difference in:

Ownable / Pausable

The contract InfinityStaker is Ownable and Pausable, so the owner could resign while the contract is paused, causing a Denial of Service. Owner resignation while paused should be avoided.

Affected source code:

Lack of ACK during owner change

It's possible to lose the ownership under specific circumstances.

Because an human error it's possible to set a new invalid owner. When you want to change the owner's address it's better to propose a new owner, and then accept this ownership with the new wallet.

Affected source code:

requestChange(ADMIN,X):

Ownable:

Lack of checks

The following methods have a lack checks if the received argument is an address, it's good practice in order to reduce human error to check that the address specified in the constructor or initialize is different than address(0).

Affected source code:

address(0):

Integer values:

Non critical

Update packages

The packages used are out of date, it is good practice to use the latest version of these packages:

"@openzeppelin/contracts": "4.5.0"

Use encode instead of encodePacked for hashig

Use of abi.encodePacked in ConnextMessage is safe, but unnecessary and not recommended. abi.encodePacked can result in hash collisions when used with two dynamic arguments (string/bytes).

There is also discussion of removing abi.encodePacked from future versions of Solidity (ethereum/solidity#11593), so using abi.encode now will ensure compatibility in the future.

Affected source code:

#0 - nneverlander

2022-06-23T12:39:27Z

Thanks

#1 - HardlyDifficult

2022-07-09T20:31:26Z

#2 - HardlyDifficult

2022-07-11T21:24:36Z

There's no need to set default values for variables.

If a variable is not set/initialized, the default value is assumed (0, false, 0x0 ... depending on the data type). You are simply wasting gas if you directly initialize it with its default value.

Affected source code:

Reduce math operations

Use scientific notation (e.g. 10E18) rather than exponentiation (e.g. 10**18)

1_000_000 * (10**decimals()) => 1_000_000 ether (check before)

10**18:

Use constant for uint256 PRECISION = 10**4;:

Use Custom Errors instead of Revert Strings to save Gas

Custom errors from Solidity 0.8.4 are cheaper than revert strings (cheaper deployment cost and runtime cost when the revert condition is met)

Source Custom Errors in Solidity:

Starting from Solidity v0.8.4, there is a convenient and gas-efficient way to explain to users why an operation failed through the use of custom errors. Until now, you could already use strings to give more information about failures (e.g., revert("Insufficient funds.");), but they are rather expensive, especially when it comes to deploy cost, and it is difficult to use dynamic information in them.

Custom errors are defined using the error statement, which can be used inside and outside of contracts (including interfaces and libraries).

Affected contracts:

++i costs less gas compared to i++ or i += 1

++i costs less gas compared to i++ or i += 1 for unsigned integer, as pre-increment is cheaper (about 5 gas per iteration). This statement is true even with the optimizer enabled.

i++ increments i and returns the initial value of i. Which means:

uint i = 1;
i++; // == 1 but i == 2

But ++i returns the actual incremented value:

uint i = 1;
++i; // == 2 and i == 2 too, so no need for a temporary variable

In the first case, the compiler has to create a temporary variable (when used) for returning 1 instead of 2 I suggest using ++i instead of i++ to increment the value of an uint variable. Same thing for --i and i--

Affected source code:

Wrong Visibility

The following methods are public, it's better to use private:

Logic change

PendingChange struct is not required, it's cheaper to avoid it changing TimelockConfig.sol#L106-L114 to:

function getPendingByIndex(uint256 index) external view returns (bytes32 configId, PendingChangeData memory pendingRequest) {
    configId = _pendingSet.at(index);
    pendingRequest = _pending[configId]; // Parse the return is the same, but the return it's cheaper
}

function getPending(bytes32 configId) external view returns (PendingChangeData memory pendingRequest) {
    require(_pendingSet.contains(configId), 'not pending');
    return _pending[configId]; // Not need to return configId, because it's the provided argument
}

Move the totalVested line:

- uint256 totalVested = noLock + threeMonthVested + sixMonthVested + twelveMonthVested; uint256 totalStaked = noLock + threeMonthLock + sixMonthLock + twelveMonthLock; require(totalStaked >= 0, 'nothing staked to rage quit'); + uint256 totalVested = noLock + threeMonthVested + sixMonthVested + twelveMonthVested;

Change the logic from InfinityStaker.sol#L213-L223 to:

if (totalPower < BRONZE_STAKE_THRESHOLD) return StakeLevel.NONE;    // The current code use <= but seems wrong.
if (totalPower < SILVER_STAKE_THRESHOLD) return StakeLevel.BRONZE;  // The current code use <= but seems wrong.
if (totalPower < GOLD_STAKE_THRESHOLD) return StakeLevel.SILVER;    // The current code use <= but seems wrong.
if (totalPower < PLATINUM_STAKE_THRESHOLD) return StakeLevel.GOLD;  // The current code use <= but seems wrong.
return StakeLevel.PLATINUM;

Speed up false returns changing InfinityOrderBookComplication.sol#L26-L57:

  function canExecMatchOneToOne(OrderTypes.MakerOrder calldata makerOrder1, OrderTypes.MakerOrder calldata makerOrder2)
    external
    view
    override
    returns (bool, uint256)
  {
    bool numItemsValid = makerOrder2.constraints[0] == makerOrder1.constraints[0] &&
      makerOrder2.constraints[0] == 1 &&
      makerOrder2.nfts.length == 1 &&
      makerOrder2.nfts[0].tokens.length == 1 &&
      makerOrder1.nfts.length == 1 &&
      makerOrder1.nfts[0].tokens.length == 1;
+   if (!numItemsValid) return false;
    bool _isTimeValid = makerOrder2.constraints[3] <= block.timestamp &&
      makerOrder2.constraints[4] >= block.timestamp &&
      makerOrder1.constraints[3] <= block.timestamp &&
      makerOrder1.constraints[4] >= block.timestamp;
+   if (!_isTimeValid) return false;
-   bool _isPriceValid = false;
    uint256 makerOrder1Price = _getCurrentPrice(makerOrder1);
    uint256 makerOrder2Price = _getCurrentPrice(makerOrder2);
    uint256 execPrice;
    if (makerOrder1.isSellOrder) {
-     _isPriceValid = makerOrder2Price >= makerOrder1Price;
+     if (makerOrder2Price < makerOrder1Price) return false;
      execPrice = makerOrder1Price;
    } else {
-     _isPriceValid = makerOrder1Price >= makerOrder2Price;
+     if (makerOrder1Price < makerOrder2Price) return false;
      execPrice = makerOrder2Price;
    }
    return (
-     numItemsValid && _isTimeValid && doItemsIntersect(makerOrder1.nfts, makerOrder2.nfts) && _isPriceValid,
+     doItemsIntersect(makerOrder1.nfts, makerOrder2.nfts),
      execPrice
    );
  }

Gas saving using immutable.

It's possible to avoid storage access a save gas using immutable keyword for the following variables:

Affected source code:

Delete optimization

Use delete instead of set to default value (false or 0)

Affected source code:

Use storage pointers or memory

Use storage keyword for save gas in order to cache a storage pointer.

The recurring use of the same register reading from storage is a task that can be easily optimized by using storage or memory, a clear example is access to userstakedAmounts[user] but it can be seen throughout all contracts.

Affected source code:

userstakedAmounts[msg.sender]:

userstakedAmounts[user]:

AuditHub

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

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter