Fractional v2 contest - Ruhum's results

A collective ownership platform for NFTs on Ethereum.

General Information

Platform: Code4rena

Start Date: 07/07/2022

Pot Size: $75,000 USDC

Total HM: 32

Participants: 141

Period: 7 days

Judge: HardlyDifficult

Total Solo HM: 4

Id: 144

League: ETH

Fractional

Findings Distribution

Researcher Performance

Rank: 39/141

Findings: 6

Award: $328.95

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

Labels

bug
duplicate
3 (High Risk)
old-submission-method

Awards

81.2985 USDC - $81.30

External Links

Lines of code

https://github.com/code-423n4/2022-07-fractional/blob/main/src/modules/Migration.sol#L433

Vulnerability details

Impact

The migrateFractions() function can be called multiple times by the same user allowing them to withdraw more fractions than they should be able to.

Proof of Concept

Here's a test as the PoC:

// Migration.t.sol

    function testMigrationMultipleTimes() public {
        testSettle();
        (, , , , address newVault, , , , ) = migrationModule.migrationInfo(
            vault,
            1
        );
        (address newToken, uint256 id) = registry.vaultToToken(newVault);

        assertEq(getFractionBalance(alice.addr), 4000);
        alice.migrationModule.migrateFractions(vault, 1);
        assertEq(IERC1155(newToken).balanceOf(alice.addr, id), 6000);

        alice.migrationModule.migrateFractions(vault, 1);
        assertEq(IERC1155(newToken).balanceOf(alice.addr, id), 12000);

        alice.migrationModule.migrateFractions(vault, 1);
        assertEq(IERC1155(newToken).balanceOf(alice.addr, id), 18000);
    }

Tools Used

none

add mapping(uint => address) claimed where the key is the proposalID and the value is the user's address. Then, check whether the user has already claimed in the migrateFractions() function.

#0 - mehtaculous

2022-07-20T17:01:06Z

Duplicate of #460

#1 - HardlyDifficult

2022-08-11T17:18:22Z

Findings Information

🌟 Selected for report: 0xNineDec

Also found by: Franfran, Ruhum, neumo, oyc_109, pashov

Labels

bug
duplicate
2 (Med Risk)
old-submission-method

Awards

132.2028 USDC - $132.20

External Links

Lines of code

https://github.com/code-423n4/2022-07-fractional/blob/main/src/FERC1155.sol#L223

Vulnerability details

Impact

Generally, when accessing the contract's royalties, the caller will use the following schema:

uint amount = x;
(address receiver, uint royalty) = c.royaltyInfo(id, amount);
token.transfer(receiver, royalty);
token.transfer(owner, amount - royalty); // send remaining balance to the owner of the token.

If the royalty is larger than 100%, the last line will trigger an underflow.

Proof of Concept

https://github.com/code-423n4/2022-07-fractional/blob/main/src/FERC1155.sol#L223

Allows the controller to set whatever value they want.

Tools Used

none

Use a reasonable limit, e.g. 10%

#0 - 0x0aa0

2022-07-18T18:41:30Z

Duplicate of #166

Awards

1.3977 USDC - $1.40

Labels

bug
duplicate
2 (Med Risk)
old-submission-method

External Links

Lines of code

https://github.com/code-423n4/2022-07-fractional/blob/main/src/modules/Migration.sol#L325 https://github.com/code-423n4/2022-07-fractional/blob/main/src/modules/Migration.sol#L172

Vulnerability details

Impact

You should use call() instead of transfer(). If the recipient is a contract it might use more than 2300 gas which will make the transaction fail if the ETH was sent with transfer().

https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/

Proof of Concept

Tools Used

none

Use call() instead.

#0 - mehtaculous

2022-07-19T21:51:17Z

Duplicate of #325

#1 - HardlyDifficult

2022-07-28T15:47:53Z

Duping to #504

Findings Information

Awards

14.6423 USDC - $14.64

Labels

bug
duplicate
2 (Med Risk)
old-submission-method

External Links

Lines of code

https://github.com/code-423n4/2022-07-fractional/blob/main/src/modules/Buyout.sol#L57

Vulnerability details

Impact

As described in the module's docs, a vault can only have a single active Buyout proposal at any time. An attacker is able to DOS the module by repeatedly creating a proposal with a "weird" configuration. That would make the module unusable for honest participants.

Proof of Concept

The easiest "weird" configuration looks like this: The attacker doesn't have any fractional tokens. They create a proposal with 1 wei balance. Users are now able to sell their fractional for 1 wei. That's it. Since no one will probably do that, you effectively created an useless proposal:

// Buyout.t.sol

    function testDOS() public {
        initializeBuyout(alice, bob, TOTAL_SUPPLY, 0, true);

        assertEq(getFractionBalance(bob.addr), 0);
        assertEq(getFractionBalance(buyout), 0);
        assertEq(getETHBalance(bob.addr), INITIAL_BALANCE);
        assertEq(getETHBalance(buyout), 0 ether);

        bob.buyoutModule.start{value: 1}(vault);

        assertEq(getFractionBalance(bob.addr), 0);
        assertEq(getFractionBalance(buyout), 0);
        assertEq(getETHBalance(buyout), 1);
    }

This is possible because the ERC1155 contract you use doesn't block transfers of 0 tokens.

Even if it didn't you could still DOS the module with the cost of a single fractional token. If the attacker has enough ETH they can also create a proposal with an absurdly high fractionPrice. Blocking anybody from buying or selling tokens.

Tools Used

none

The issue is the limit of one buyout at a time. I'm not entirely sure, but would allowing multiple concurrent proposals break anything? As long as one of them reaches the limit of 51% you have the same outcome.

#0 - 0x0aa0

2022-07-18T16:23:07Z

Duplicate of #87

#1 - HardlyDifficult

2022-08-02T21:54:06Z

Report

Low

L-01: Buyout phases don't match with the documentation

According to the contract's comment, there are two phases for an auction, the proposal & rejection period. The former lasts 2 days and the other one lasts 4 days. Reading that, one would expect them to be two distinct phases. So in the first two days you're only able to sell your fractional tokens. Then, the next 4 days you're only allowed to buy fractional tokens. After that, the auction is finished (full duration is 6 days). Instead, you can sell only the first two days while you can buy from the beginning to the end (4 days).

L-02: Buyout auction success threshold doesn't match the one in the documentation

According to the contract's comment, the pool needs more than 51% of the total supply for it to succeed. But, the actual implementation expects only more than 50%. Either update the documentation or the implementation.

Non-Critical

N-01: emit an event when changing a contract's configuration

They should be accompanied by an event to allow third parties to easily track them.

Report

G-01: use calldata instead of memory for array parameters

The gas for calldata parameters is already paid. It'll be cheaper.

e.g.

G-02: use unchecked when incrementing the iterator of a for loop

Since i can't overflow because of the boundary set in the for loop, you can use unchecked to increment it. That will save a little gas

e.g.

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