Infinity NFT Marketplace contest - Kenshin'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: 41/99

Findings: 3

Award: $97.22

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

11.084 USDC - $11.08

Labels

bug
duplicate
3 (High Risk)

External Links

Lines of code

https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1229-L1232 https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L345-L348

Vulnerability details

Impact

The rescueETH() function cannot be used to rescue any ETH transfered/stuck on the contract as the comment written.

@dev used for rescuing exchange fees paid to the contract in tokens The function is just passing msg.value from caller to address destination, not any ETH in the contract.

Proof of Concept

  • If someone transferred 10 ETH to the contract no matter intentionally or not. The contract will not have 10 ETH balance.
  • The admin have to call rescueETH(address destination) with 10 ETH attatched as the value to SEND 10 ETH back to the destination.
  • The ETH balance of admin wallet will be decreased by 10 and the destination's wallet will be increased by that amount, the contract balance still be 10 ETH and cannot get it out.

This is a test script, include it to test/staker.js and run with npx hardhat test test/staker.js

describe('PoC rescueETH', () => { it.only('Cannot transfer ETH out of the contract', async function () { // send ETH to the contract let oneETH = ethers.utils.parseEther('1'); expect(await ethers.provider.getBalance(infinityStaker.address)).to.equal(0); let signer2BalanceBefore = await ethers.provider.getBalance(signer2.address) let tx = await signer2.sendTransaction({ to: infinityStaker.address, value: oneETH }) let signer2BalanceAfter = await ethers.provider.getBalance(signer2.address) expect(await ethers.provider.getBalance(infinityStaker.address)).to.equal(oneETH) expect(signer2BalanceBefore).to.be.at.least(signer2BalanceAfter.add(oneETH)) // before >= after, left some room for gas // admin rescue that ETH let adminBalanceBefore = await ethers.provider.getBalance(signer1.address) await infinityStaker.connect(signer1).rescueETH(signer2.address, {value: oneETH}) let adminBalanceAfter = await ethers.provider.getBalance(signer1.address) expect(await ethers.provider.getBalance(signer2.address)).to.equal(signer2BalanceAfter.add(oneETH)) // admin balance decreased while contract balance still the same. expect(adminBalanceBefore).to.be.at.least(adminBalanceAfter.add(oneETH)) // before >= after, left some room for gas expect(await ethers.provider.getBalance(infinityStaker.address)).to.equal(oneETH) }) })

Tools Used

  • Manual review
  • Hardhat

In order to make this function work as its name, the function must receive uint amount as another input to transfer that amount out of the contract to destination address. And the payable can be removed to prevent any callings with ETH attached.

#0 - nneverlander

2022-06-22T19:10:00Z

Duplicate

#2 - HardlyDifficult

2022-07-09T16:57:18Z

Low/QA

Missing Event on Important/State Changes Function

Description

Important or state changes function should emit events upon successful execution for off-chain tracking.

  1. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1220-L1226
  2. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1229-L1232
  3. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1235-L1237
  4. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1240-L1241
  5. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1245-L1246
  6. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1250-L1251
  7. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1255-L1256
  8. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L345-L348
  9. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L351-L361
  10. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L364-L372
  11. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L375-L377

Mitigation

An event of calling critical functions should be generated for security and off-chain monitoring purposes.


Missing Zero-address Validation

Description

NFT transfering function can be called with address zero (0x00...00) as the destination. This might cause unexpected behavior or unintentionally burn.

  1. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L371
  2. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1220
  3. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1229
  4. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1235
  5. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1240
  6. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1255
  7. https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L375-L377

Mitigation

It is recommended to validate that the destination address should not be address zero to prevent from unintentionally burn.


PROTOCOL_FEE_BPS Can Be Set To 100% or Above

Description

There is no maximum limit on how maximum the PROTOCOL_FEE_BPS can be, which might result in a fee rate at 100%, meaning the protocol will collect the entire trading amount. Additionally, it can be more than 10000 (100%) and will result in Denial of Service due to overflow reverting.

https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/core/InfinityExchange.sol#L1266-1269

Mitigation

It is recommended to determine how high the fee can be at maximum and add the validation to ensure that the fee cannot be set higher than the maximum value.


Tautology Require Statement

Description

The reported require statement is validating on tautology logic, the condition that will always be true. From the fact that the default value of uint in Solidity is 0, so, the condition like >= 0 will always be true in any circumstances.

https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L193

Mitigation

The condition can be changed from >= 0 to > 0.


Penalty Can Be Zero

Description

The rage-quit penalty can be set to zero which will causing rage quit always be reverted due to divide by zero.

https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L364-L372

Mitigation

A zero-value validation check should be included in the penalty setter function.

Gas Optimization

Custom Error Should Be Used For Gas-optimization

Description

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. Source: https://blog.soliditylang.org/2021/04/21/custom-errors/

InfinityExchange.sol
InfinityOrderBookComplication.sol
InfinityStaker.sol
InfinityToken'sol

Mitigation

Consider using custom errors instead if the contract uses Solidity version 0.8.4 or above.


Using String Bigger Than bytes32

Description

EVM is a stack machine with 256 bits (32 bytes) for each stack. Using unnecessary information that has size more than 32 bytes requires more than one stacks for storing, therefore using more gas unnecessarily.

InfinityExchange.sol
InfinityStaker.sol

Mitigation

Use a string that not bigger than 32 bytes or consider using custom errors instead if the contract uses solidity version 0.8.4 or above.


unchecked Block Can Be Used To Save More Gas

Description

From Solidity version 0.8.0 and above, the arithmetic overflow/underflow has been included in the compiler-level. Any arithmetic overflow/underflow will always be reverted by default, which mean it costs more gas by default for overflow/underflow validation. In case that any arithmetic operations can be gaurantee to not be overflown/underflown the unchecked block can be used to save more gas.

https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L99

Mitigation

The line 99 can be gaurantee to not be overflown/underflown by the require statement at line 93. Therefore, consider apply unchecked with the line to save more gas.


Reorder getVestedAmount() Logic Can Save More Gas For On-Chain Calling

Description

The getVestedAmount() will return right after entering if the stored retrieved timestamp is 0, in that case, the declared uint256 amount will never be used.

https://github.com/code-423n4/2022-06-infinity/blob/765376fa238bbccd8b1e2e12897c91098c7e5ac6/contracts/staking/InfinityStaker.sol#L260-L271

Mitigation

Reorder the line 261 that declaring and reading storage to be after the if-statement as demonstrated below.

function getVestedAmount(address user, Duration duration) public view returns (uint256) {
    uint256 timestamp = userstakedAmounts[user][duration].timestamp;
    // short circuit if no vesting for this duration
    if (timestamp == 0) {
      return 0;
    }
    uint256 amount = userstakedAmounts[user][duration].amount; // Move to after the if-statement
    uint256 durationInSeconds = _getDurationInSeconds(duration);
    uint256 secondsSinceStake = block.timestamp - timestamp;

    return secondsSinceStake >= durationInSeconds ? amount : 0;
  }
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