Arcade.xyz - LaScaloneta's results

The first of its kind Web3 platform to enable liquid lending markets for NFTs.

General Information

Platform: Code4rena

Start Date: 21/07/2023

Pot Size: $90,500 USDC

Total HM: 8

Participants: 60

Period: 7 days

Judge: 0xean

Total Solo HM: 2

Id: 264

League: ETH

Arcade.xyz

Findings Distribution

Researcher Performance

Rank: 45/60

Findings: 1

Award: $253.08

🌟 Selected for report: 1

🚀 Solo Findings: 0

Awards

253.0813 USDC - $253.08

Labels

bug
grade-a
high quality report
QA (Quality Assurance)
selected for report
sponsor confirmed
edited-by-warden
Q-10

External Links

QA Report

Low Findings

L01 - The merkle root wont revert if is set to 0x00

If merkle root is set to 0x00 could leave the system in an insecure state where system will accept any message that it has never seen before and process it as if it were genuine.

This has happen before in the nomad bridge incident.

The Nomad team initialized the trusted root to be 0x00. To be clear, using zero values as initialization values is a common practice.

Instances:

L02 - delegate() can be called with no voting power

The delegate() functions in ARCDVestingVault and NFTBoostVault can be called by anyone, despite them not having any voting power.

This leads to changes in the storage variables, like pushing empty entries to the votingPower, and setting a delegatee, despite not delegating anything.

As it will set a registration.delegatee, it will bypass the validations if (registration.delegatee == address(0)) revert NBV_NoRegistration(); on addTokens(), and updateNft(). Making it possible to call those functions without "initializing" the corresponding registration storage via addNftAndDelegate().

This can lead to unexpected effects.

Instances:

Verify that _currentVotingPower(registration) > 0

L03 - pure functions should be reading or writing memory

From solidity lang pure functions

Pure functions promise not to read from or modify the state.

Instances:

L04 - Use a two-step transfer function for the Minter role in ArcadeToken

The ArcadeToken has a very important Minter role which can be changed via a setMinter() function.

In order to prevent the role from mistakenly being transferred to an address that cannot handle it (e.g. due to a typo in the address), require that the recipient of the new minter role actively accept via a contract call of its own.

Instances:

L05 - NFTBoostVault::updateNft() allows to deposit any NFT

addNftAndDelegate reverts when depositing any random NFT because of the if (multiplier == 0) revert NBV_NoMultiplierSet(); in _registerAndDelegate().

But updateNft() does not have that check, and allows anyone to update and deposit any NFT, leading to a multiplier of 0, and thus a voting power of 0, despite the user having deposited any other tokens.

This might also be combined with other issues for a bigger severity issue. We recommend that the same check to revert on multiplier == 0 is applied on updateNft().

Instances:

L06 - If token has hooks the grant owner can revert when admin try to revoke role

A evil granted owner can avoid being revoke, he can revert triggering a DOS to avoid manager to revoke role.

L07 - If token has hooks the manager could reenter to revokeGrant draining the contract

A token with hook could leave a reentrancy issue, letting manager to draind funds.

L08 - The storage slots/hashes have known pre-images.

Knowledge of pre-images might allow manipulation of specific mapping parameters by crafting special keys. This is possible if a mapping parameter's storage slot lands on a specific slot.

It might be best to decrement them by one so that finding the pre-image would be harder.

Instances:

Example:

bytes32 typehash = keccak256("mapping(address => AddressUint)");
bytes32 offset = keccak256(abi.encodePacked(typehash, name));
assembly {
    data.slot := sub(offset, 1) // keccak256(NAME) - 1, to avoid preimage attack
}

Also please review this eip-1967

L09 - Type of tokenId in ReputationBadge and NftBoostVault do not match, and can lead to tokens not being usable as multipliers

ReputationBadge tokens are meant to be used as multipliers of voting power in NftBoostVault.

Users can mint badges with tokenId up to uint256, but NftBoostVault only allows to set tokenId up to uint128 to work as multipliers.

Tokens with tokenId > type(uint128).max won't be able to be used as multipliers.

tokenId in ReputationBadge (uint256):

    function mint(
        address recipient,
@>      uint256 tokenId,
        uint256 amount,
        uint256 totalClaimable,
        bytes32[] calldata merkleProof
    ) external payable {

https://github.com/code-423n4/2023-07-arcade/blob/main/contracts/nft/ReputationBadge.sol#L100

tokenId in NftBoostVault (uint128):

@>  function setMultiplier(address tokenAddress, uint128 tokenId, uint128 multiplierValue) public override onlyManager {

https://github.com/code-423n4/2023-07-arcade/blob/main/contracts/NFTBoostVault.sol#L363

Consider setting the same tokenId type for the expected NFTs to be used in the contract.

L10 - You can bypass the ERC1155 check and deposit anything if tokenId == 0

The check on NFTBoostVault.sol#L472 can be easily bypass if the ERC1155 token id is 0: if (_tokenAddress != address(0) && _tokenId != 0) {

POC:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "../contracts/NFTBoostVault.sol";
import "../contracts/token/ArcadeToken.sol";

contract ExampleTest is Test {
    NFTBoostVault public boostVault;
    ArcadeToken public token;

    function setUp() public {
        vm.roll(10);
        token = new ArcadeToken(address(this), address(this));
        boostVault = new NFTBoostVault(IERC20(address(token)), 1, address(this), address(this));
    }

    function testDepositAnyERC1155() public {
        // create a fake erc1155 (could also happen with a )
        FakeERC1155 f = new FakeERC1155();

        token.approve(address(boostVault),100);
        boostVault.addNftAndDelegate(
            1,
            0, // uint128 tokenId,
            address(f), //address tokenAddress,
            address(0xdead) // delegate
        );

        token.transfer(address(0x02), 1);
        vm.prank(address(0x02));
        token.approve(address(boostVault),100);

        vm.prank(address(0x02));
        boostVault.addNftAndDelegate(
            1,
            0, // uint128 tokenId,
            address(f), //address tokenAddress,
            address(0xdead01)
        );

        vm.prank(address(0xdead));
        boostVault.delegate(address(0x01));

        vm.prank(address(0xdead01));
        boostVault.delegate(address(0x01));

    }
}

contract FakeERC1155 {
    fallback() external {}
}

L11 - Approve zero first

Some ERC20 tokens (like USDT) do not work when changing the allowance from an existing non-zero allowance value. For example Tether (USDT)'s approve() function will revert if the current approval is not zero, to protect against front-running changes of approvals.

Instances:

Non-Critical Findings

NC01 - NFTBoostVault sets an unused initialized value in storage

The "initialized" storage variable is set on the constructor, but not used by the NFTBoostVault, nor other contract, and can't be accessed by any public or external method.

Recommendation: Verify if it is actually needed, or remove it in case it is not.

Storage.set(Storage.uint256Ptr("initialized"), 1); // @audit check

https://github.com/code-423n4/2023-07-arcade/blob/main/contracts/NFTBoostVault.sol#L91

NC02 - ARCDVestingVault inherits twice from HashedStorageReentrancyBlock

ARCDVestingVault inherits both from HashedStorageReentrancyBlock, and BaseVotingVault.

But BaseVotingVault is already inheriting from HashedStorageReentrancyBlock, as well

Remove the unncessary inheritance from HashedStorageReentrancyBlock on ARCDVestingVault.

Instances:

#0 - 141345

2023-07-30T15:45:39Z

#1 - c4-pre-sort

2023-08-01T14:33:33Z

141345 marked the issue as high quality report

#2 - c4-sponsor

2023-08-02T23:15:29Z

PowVT marked the issue as sponsor confirmed

#3 - PowVT

2023-08-02T23:15:55Z

There are some good valid fixes in this report, we will address.

#4 - c4-judge

2023-08-10T22:43:16Z

0xean marked the issue as selected for report

#5 - c4-judge

2023-08-10T23:22:16Z

0xean marked the issue as grade-a

#6 - 0xean

2023-08-15T19:44:30Z

L03 - looks invalid and includes out of scope contracts. L04 - is largely already covered in the bot report, this is slightly different only its location in the codebase so doesn't hurt to include in report L11 - is in the bot report already

Otherwise severities look reasonable to me.

#7 - thebrittfactor

2023-08-30T20:11:44Z

Just a note that C4 is excluding the invalid entries from the official report.

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