Arcade.xyz - ladboy233'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: 3/60

Findings: 4

Award: $8,181.31

QA:
grade-b

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: ladboy233

Also found by: Topmark

Labels

2 (Med Risk)
primary issue
satisfactory
selected for report
M-01

Awards

5771.7265 USDC - $5,771.73

External Links

Judge has assessed an item in Issue #513 as 2 risk. The relevant finding follows:

Issue 2

#0 - c4-judge

2023-08-10T20:21:26Z

0xean marked the issue as satisfactory

#1 - c4-judge

2023-08-10T20:21:47Z

0xean marked the issue as primary issue

#2 - c4-judge

2023-08-12T13:58:21Z

0xean marked the issue as selected for report

Findings Information

🌟 Selected for report: bin2chen

Also found by: 0xWaitress, caventa, ladboy233

Labels

bug
2 (Med Risk)
satisfactory
duplicate-85

Awards

1798.1148 USDC - $1,798.11

External Links

Lines of code

https://github.com/code-423n4/2023-07-arcade/blob/f8ac4e7c4fdea559b73d9dd5606f618d4e6c73cd/contracts/ArcadeTreasury.sol#L391

Vulnerability details

Line of code:

https://github.com/code-423n4/2023-07-arcade/blob/f8ac4e7c4fdea559b73d9dd5606f618d4e6c73cd/contracts/ArcadeTreasury.sol#L391

Impact

Lose of previous spending power for same address in TreasuryContract

Proof of Concept

the AracheTreasury, there are three spending relate function:

approveSmallSpend, approveMediumSpend, approveLargeSpend

both of these function call

 _approve(token, spender, amount, spendThresholds[token].small);

or the approved amount is spendThresholds[token].medium or spendThresholds[token].large

Then inside the function _approve, approve a certain amount

IERC20(token).approve(spender, amount);

emit TreasuryApproval(token, spender, amount);

However, if a spender approve a largeSpending amount, then approve the mdiumSpending amount, the unconsumed largeSpending amount is lost

for example largeSpending amount is 1000 ETH, mediumSpending amount is 300 ETH

a spender has 1000 ETH spending allowance after call approveLargeSpending

he never spend the allowance

then a proposal executes and caled approveMediumSpending

now the spender's largeSpending amount 1000 ETH is lost, he only has 300 ETH spending allowance left

Tools Used

Manual Review

Use increaseAllowance instead of calling approve

Assessed type

Token-Transfer

#0 - c4-pre-sort

2023-07-31T08:39:44Z

141345 marked the issue as duplicate of #85

#1 - c4-judge

2023-08-11T16:30:50Z

0xean marked the issue as satisfactory

Findings Information

🌟 Selected for report: Anirruth

Also found by: DadeKuma, Matin, MohammedRizwan, bart1e, giovannidisiena, ladboy233, rvierdiiev

Labels

bug
2 (Med Risk)
satisfactory
duplicate-70

Awards

589.8716 USDC - $589.87

External Links

Lines of code

https://github.com/code-423n4/2023-07-arcade/blob/f8ac4e7c4fdea559b73d9dd5606f618d4e6c73cd/contracts/external/council/CoreVoting.sol#L15

Vulnerability details

Impact

Incorrectly assume the block generation time is 13.3 seconds, while the block generation in ethereum in 12 seconds per block,

result in a gradually delayed voting period

Proof of Concept

ArcadeGSCCoreVoting.sol is in-scope and it is inherit from the CoreVoting contract

contract ArcadeGSCCoreVoting is CoreVoting {

In CoreVoting Contract:

the DAY_IN_BLOCK is hardcoded to 6496,

    // Assumes avg block time of 13.3 seconds. May be longer or shorter due
    // to ice ages or short term changes in hash power.
    // @audit
    // 12 seconds
    uint256 public constant DAY_IN_BLOCKS = 6496;

as the comment suggest, the code assume the avg block time is 13.3 seconds

In one day we assume it has 86400 seconds / 6496 block = 13.3 seconds

However, the block production time in post merge is 12 seconds

https://ethereum.org/en/developers/docs/consensus-mechanisms/pos/#:~:text=Whereas%20under%20proof%2Dof%2Dwork,block%20proposer%20in%20every%20slot

Then one can day can produce: 86400 / 12 = 7200 blocks

the parameter DAY_IN_BLOCKS is used when derving the lock duration and the voting time of the proposal

// minimum time a proposal must be active for before executing
// Default to 3 days, this avoids weekend surprise proposals
uint256 public lockDuration = DAY_IN_BLOCKS * 3;

// The number of blocks after the proposal is unlocked during which
// voting can continue. Max vote time = lockDuration + extraVoteTime
// Default to ~5 days of blocks, ie 8 days max vote time
uint256 public extraVoteTime = DAY_IN_BLOCKS * 5;

and the DAY_IN_BLOCKS is off by (7200 - 6496) = 704 blocks, which result in a much shorter lockDuration and voting time then expected

Tools Used

Manual Review

We recommend the protocol to use the block.timestamp (one day has 86400 seconds) instead of using the DAY_IN_BLOCK to measure the time elapse when deriving lockDuration and vote time in ArcadeGSCCoreVoting.sol

Assessed type

Other

#0 - c4-pre-sort

2023-07-29T13:47:33Z

141345 marked the issue as duplicate of #56

#1 - c4-judge

2023-08-11T16:35:05Z

0xean marked the issue as satisfactory

#2 - c4-judge

2023-08-14T16:26:09Z

0xean changed the severity to QA (Quality Assurance)

#3 - c4-judge

2023-08-14T16:29:25Z

0xean marked the issue as grade-b

#4 - c4-judge

2023-08-16T12:34:13Z

This previously downgraded issue has been upgraded by 0xean

#5 - c4-judge

2023-08-16T20:16:59Z

0xean marked the issue as not a duplicate

#6 - c4-judge

2023-08-16T20:17:21Z

0xean marked the issue as duplicate of #56

Awards

21.5977 USDC - $21.60

Labels

bug
grade-b
QA (Quality Assurance)
Q-04

External Links

Issue 1:

merkle leaf value should not be 64 bytes before hashing in MerkleReward Distribution

Issue 2:

_syncVotingPower can revert in underflow in NFTBoostVault.sol and ARCDVestingVault.sol


merkle leaf value should not be 64 bytes before hashing in MerkleReward Distribution

line of code:

https://github.com/code-423n4/2023-07-arcade/blob/f8ac4e7c4fdea559b73d9dd5606f618d4e6c73cd/contracts/external/council/libraries/MerkleRewards.sol#L85

Impact

merkle leaf value should not be 64 bytes before hashing

Proof of Concept

In merkle reward distribution code, we have to follow function:

   function _validateWithdraw(
        uint256 amount,
        uint256 totalGrant,
        bytes32[] memory merkleProof
    ) internal {
        // Hash the user plus the total grant amount
        bytes32 leafHash = keccak256(abi.encodePacked(msg.sender, totalGrant));

        // Verify the proof for this leaf
        require(
            MerkleProof.verify(merkleProof, rewardsRoot, leafHash),
            "Invalid Proof"
        );
        // Check that this claim won't give them more than the total grant then
        // increase the stored claim amount
        require(claimed[msg.sender] + amount <= totalGrant, "Claimed too much");
        claimed[msg.sender] += amount;
    }

note:

// Hash the user plus the total grant amount
bytes32 leafHash = keccak256(abi.encodePacked(msg.sender, totalGrant));

// Verify the proof for this leaf
require(
	MerkleProof.verify(merkleProof, rewardsRoot, leafHash),
	"Invalid Proof"
);

we cancat the address msg.sender and totalGrant as merkle tree, which is 64 bytes exactly

In Openzepplein library, we have the following comments

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/02ea01765a9964541dd9cdcffc4a7f8b403c2ff6/contracts/utils/cryptography/MerkleProof.sol#L13

 * WARNING: You should avoid using leaf values that are 64 bytes long prior to
 * hashing, or use a hash function other than keccak256 for hashing leaves.
 * This is because the concatenation of a sorted pair of internal nodes in
 * the merkle tree could be reinterpreted as a leaf value.

The merkle tree intermediary nodes are just concatenated and hashed during verification, and since they are also of size bytes32/uint256 this type of collision is possible because the leaf of the tree is of the same form in this case (address, uint256) as the internal nodes.

then if the attacker control two msg.sender address + the totalGrant, he can claim the reward for free

Tools Used

Manual Review

Add one bytes to the hashed leaf


_syncVotingPower can revert in underflow in NFTBoostVault.sol and ARCDVestingVault.sol

line of code:

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

https://github.com/code-423n4/2023-07-arcade/blob/f8ac4e7c4fdea559b73d9dd5606f618d4e6c73cd/contracts/ARCDVestingVault.sol#L352

Impact

_syncVotingPower can revert in underflow in NFTBoostVault.sol

Proof of Concept

note this function:

   function _syncVotingPower(address who, NFTBoostVaultStorage.Registration storage registration) internal {
        History.HistoricalBalances memory votingPower = _votingPower();
        uint256 delegateeVotes = votingPower.loadTop(registration.delegatee);

        uint256 newVotingPower = _currentVotingPower(registration);
        // get the change in voting power. Negative if the voting power is reduced

        int256 change = int256(newVotingPower) - int256(uint256(registration.latestVotingPower));

        // do nothing if there is no change
        if (change == 0) return;
        if (change > 0) {
            votingPower.push(registration.delegatee, delegateeVotes + uint256(change));
        } else {
            // if the change is negative, we multiply by -1 to avoid underflow when casting
            votingPower.push(registration.delegatee, delegateeVotes - uint256(change * -1));
        }

        registration.latestVotingPower = uint128(newVotingPower);

        emit VoteChange(who, registration.delegatee, change);
    }

We need to pay special attention to this line of code:

  votingPower.push(registration.delegatee, delegateeVotes - uint256(change * -1));

it is possible that delegateeVotes - changes can revert in underflow

this only happens when we try to reduce the voting power

In _withdrawNFT if a NFT with high multipler is removed in NFTBoostVault.sol and cause the voting power to decrease

// update the delegatee's voting power based on multiplier removal
_syncVotingPower(msg.sender, registration);

Or in ARCDVestingVault.sol when a admin try to revoke a user's grant

// update the grant's withdrawn amount
if (amount == withdrawable) {
	grant.withdrawn += uint128(withdrawable);
} else {
	grant.withdrawn += uint128(amount);
	withdrawable = amount;
}

// update the user's voting power
_syncVotingPower(msg.sender, grant);

Tools Used

Manual Review

Before calling delegateeVotes - uint256(change * -1), make sure delegateeVotes > uint256(change * -1), otherwise just set to 0

#0 - 141345

2023-07-31T11:26:31Z

#1 - c4-pre-sort

2023-08-01T14:38:04Z

141345 marked the issue as high quality report

#2 - liveactionllama

2023-08-02T17:43:04Z

After discussion with the lookout, removing the high quality label here, simply to focus usage of that label on the top few QA reports.

#3 - c4-judge

2023-08-10T22:39:45Z

0xean marked the issue as grade-c

#4 - c4-judge

2023-08-14T16:29:23Z

0xean marked the issue as grade-b

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