Revolution Protocol - zhaojie's results

A protocol to empower communities to raise funds, fairly distribute governance, and maximize their impact in the world.

General Information

Platform: Code4rena

Start Date: 13/12/2023

Pot Size: $36,500 USDC

Total HM: 18

Participants: 110

Period: 8 days

Judge: 0xTheC0der

Id: 311

League: ETH

Collective

Findings Distribution

Researcher Performance

Rank: 5/110

Findings: 3

Award: $1,220.27

🌟 Selected for report: 1

🚀 Solo Findings: 0

Findings Information

Labels

bug
2 (Med Risk)
downgraded by judge
satisfactory
sufficient quality report
duplicate-409

Awards

51.1381 USDC - $51.14

External Links

Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L226 https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L313

Vulnerability details

Impact

In CultureIndex, malicious users buy governance tokens immediately after createPiece to allow voting to pass early, because quorumVotes are lower than expected.

Proof of Concept

quorumVotes are calculated using the totalVotesSupply of governance tokens:

    function createPiece(
        ArtPieceMetadata calldata metadata,
        CreatorBps[] calldata creatorArray
    ) public returns (uint256) {
        .....
        newPiece.creationBlock = block.number;
        newPiece.totalVotesSupply = _calculateVoteWeight(
            erc20VotingToken.totalSupply(),
            erc721VotingToken.totalSupply()
        );
        ....
        newPiece.quorumVotes = (quorumVotesBPS * newPiece.totalVotesSupply) / 10_000;
    }

If the malicious user mint token after createPiece, totalSupply has actually increased, but the quorumVotes calculated previously still use the old value, so quorumVotes are lower than they actually are.

A malicious user can create piece in the same block first and then buy tokens, so that his Piece will be more advantageous, In the early days of the protocol's launch, when totalSupply is low, malicious users can mint tokens that exceed the threshold of quorumVotes and can immediately pass the vote.

When voting, the BlockNumber used at the time of piece creation is used to obtain a snapshot of voting power, so minted's token(voting power) after createPiece in the same block can be used for voting.

    function _vote(uint256 pieceId, address voter) internal {
        require(pieceId < _currentPieceId, "Invalid piece ID");
        require(voter != address(0), "Invalid voter address");
        require(!pieces[pieceId].isDropped, "Piece has already been dropped");
        require(!(votes[pieceId][voter].voterAddress != address(0)), "Already voted");

@>      uint256 weight = _getPastVotes(voter, pieces[pieceId].creationBlock);
        require(weight > minVoteWeight, "Weight must be greater than minVoteWeight");

        votes[pieceId][voter] = Vote(voter, weight);
        totalVoteWeights[pieceId] += weight;

        uint256 totalWeight = totalVoteWeights[pieceId];

        maxHeap.updateValue(pieceId, totalWeight);
        emit VoteCast(pieceId, voter, weight, totalWeight);
    }

Tools Used

vscode manual

    function _vote(uint256 pieceId, address voter) internal {
        .....
-        uint256 weight = _getPastVotes(voter, pieces[pieceId].creationBlock);
+        uint256 weight = _getPastVotes(voter, pieces[pieceId].creationBlock - 1);
    }

Assessed type

Governance

#0 - c4-pre-sort

2023-12-22T07:59:17Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-12-22T07:59:26Z

raymondfam marked the issue as duplicate of #16

#2 - c4-pre-sort

2023-12-24T15:11:13Z

raymondfam marked the issue as duplicate of #449

#3 - c4-judge

2024-01-06T15:56:35Z

MarioPoneder changed the severity to 2 (Med Risk)

#4 - c4-judge

2024-01-06T16:01:29Z

MarioPoneder marked the issue as not a duplicate

#5 - c4-judge

2024-01-06T16:01:38Z

MarioPoneder marked the issue as duplicate of #409

#6 - c4-judge

2024-01-07T00:23:21Z

MarioPoneder marked the issue as satisfactory

Awards

25.1638 USDC - $25.16

Labels

bug
2 (Med Risk)
satisfactory
sufficient quality report
duplicate-397

External Links

Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L152

Vulnerability details

Impact

ERC20TokenEmitter#buyToken lacks slippage protection, users may pay a higher price than expected.

Proof of Concept

buyToken uses variable price (VRGDAC), when the total supply increases, the price is likely to increase, especially when the threshold is reached, the price will increase very quickly (according to VRGDAC).

For example, after the user initiates a transaction, a large transaction is inserted in front of the total supply increases a lot, because the large transaction is executed before the user's transaction, so the number of tokens obtained by the user will be far less than the expected value, which is the user does not want to see.

Therefore, it is necessary to set a minimum value when purchasing tokens. If the price changes cause the purchased tokens to be less than this minimum value, the transaction will be cancelled.

Tools Used

vscode manual

add slippage protection

Assessed type

Error

#0 - c4-pre-sort

2023-12-22T08:00:21Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-12-22T08:00:29Z

raymondfam marked the issue as duplicate of #26

#2 - c4-pre-sort

2023-12-24T06:00:47Z

raymondfam marked the issue as duplicate of #397

#3 - c4-judge

2024-01-06T16:30:11Z

MarioPoneder marked the issue as satisfactory

Findings Information

🌟 Selected for report: zhaojie

Also found by: jerseyjoewalcott

Labels

bug
2 (Med Risk)
primary issue
satisfactory
selected for report
sponsor confirmed
sufficient quality report
M-06

Awards

1143.9691 USDC - $1,143.97

External Links

Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L258

Vulnerability details

Impact

The timeSinceStart in the vrgdac.xToY function will revert over a certain value, resulting in the ERC20TokenEmitter#buyToken function always revert.

Proof of Concept

Initialize the VRGDAC using the parameters in the test code.

    VRGDAC vrgdac = new VRGDAC(1 ether, 1e18 / 10, 1_000 * 1e18);

The timeSinceStart is set to 394 days in the test code:

    function testVRGDAC_time() public {
        VRGDAC vrgdac = new VRGDAC(1 ether, 1e18 / 10, 1_000 * 1e18);
        int256 x = vrgdac.yToX({
            timeSinceStart: toDaysWadUnsafe(86400 * 400),
            sold: 1000 ether,
            amount: 1 ether
        });
        uint256 xx = uint256(x);
        console.log(xx + 1);
        console.log(xx / 1e18);
    }

Run the forge test -vvvv, console to output:

[FAIL. Reason: UNDEFINED] testVRGDAC_time() (gas: 554525) Traces: [106719] CounterTest::setUp() ├─ [49499] → new Counter@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f │ └─ ← 247 bytes of code ├─ [2390] Counter::setNumber(0) │ └─ ← () └─ ← () [554525] CounterTest::testVRGDAC_time() ├─ [517512] → new VRGDAC@0x2e234DAe75C793f67A35089C9d99245E1C58470b │ └─ ← 2578 bytes of code ├─ [3617] VRGDAC::yToX(400000000000000000000, 1000000000000000000000, 1000000000000000000) [staticcall] │ └─ ← "UNDEFINED" └─ ← "UNDEFINED"

Changing the timeSinceStart to toDaysWadUnsafe(86400 * 365) will work.

When this function is used in ERC20TokenEmitter#buyToken, the timeSinceStart is: block.timestamp-startTime

    function buyTokenQuote(uint256 amount) public view returns (int spentY) {
        require(amount > 0, "Amount must be greater than 0");
        return
            vrgdac.xToY({
                timeSinceStart: toDaysWadUnsafe(block.timestamp - startTime),
                sold: emittedTokenWad,
                amount: int(amount)
            });
    }

startTime is set during the initialization of the ERC20TokenEmitter contract:

    function initialize(
        address _initialOwner,
        address _erc20Token,
        address _treasury,
        address _vrgdac,
        address _creatorsAddress
    ) external initializer {
        .....
        startTime = block.timestamp;
    }

In other words, if the ERC20TokenEmitter contract is deployed and initialized and becomes unavailable after 400 days(400 days is the test value, the actual value will be affected by other parameters), calling the buyToken function will always revert.

Tools Used

vscode manual

Optimize the vrgdac.yToX function, or set a minimum timeSinceStart value, which is used when the minimum is exceeded.

Assessed type

Math

#0 - c4-pre-sort

2023-12-22T07:59:49Z

raymondfam marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-12-22T08:00:11Z

raymondfam marked the issue as duplicate of #357

#2 - c4-judge

2024-01-06T00:41:10Z

MarioPoneder marked the issue as satisfactory

#3 - c4-judge

2024-01-06T00:41:19Z

MarioPoneder marked the issue as selected for report

#4 - rocketman-21

2024-01-06T21:06:49Z

Looked into this extensively, it operates under the assumption that the token emission schedule (# sold) will be way off schedule. eg: over a year, 75%+ lower than expected according to the target sale per time unit

In that case, after a prolonged amount of time, the math in the VRGDA will break due to a param reaching 0 in the available fixed point decimals. given the VRGDA pricing mechanism decreases the price, my assumption is that in a rational market we will never reach the above case, because tokens will become cheaper and cheaper. or the DAO/community will be dead and no one wants to buy tokens anymore

#5 - MarioPoneder

2024-01-06T21:10:43Z

@rocketman-21 Thanks for digging deeper into this. I would have missed this since it seems perfectly valid based on the test.

#6 - c4-judge

2024-01-06T21:11:17Z

MarioPoneder marked the issue as unsatisfactory: Invalid

#7 - rocketman-21

2024-01-08T23:21:21Z

upon second look here - I do think this is valid

I was able to reproduce the VRGDA breaking in multiple different scenarios due to precision loss in the VRGDA math

even if the supply was way off schedule, there could be scenarios imho where the community sets a large perTimeUnit and targetPrice and never meets that volume, and after 1+ years their vrgda will likely break

fix here: https://github.com/collectivexyz/revolution-protocol/commit/2bd0b35df870097be166a0d57af0a1f0d62a7518

#8 - MarioPoneder

2024-01-08T23:28:56Z

@rocketman-21 Thanks again for this insight, as far as I can reproduce the PoCs it's hard to distinguish if this can have an impact on the protocol or just arises under unrealistic assumptions.
Nevertheless, it seems like there could be realistic scenarios where this is a problem, therefore restoring severity.

#9 - c4-judge

2024-01-08T23:29:23Z

MarioPoneder removed the grade

#10 - c4-judge

2024-01-08T23:29:30Z

MarioPoneder marked the issue as satisfactory

#11 - c4-judge

2024-01-08T23:29:53Z

MarioPoneder marked the issue as selected for report

#12 - c4-sponsor

2024-01-09T15:38:14Z

rocketman-21 (sponsor) confirmed

#13 - bart1e

2024-01-10T18:41:40Z

I was able to reproduce the VRGDA breaking in multiple different scenarios due to precision loss in the VRGDA math

even if the supply was way off schedule, there could be scenarios imho where the community sets a large perTimeUnit and targetPrice and never meets that volume, and after 1+ years their vrgda will likely break

Could we ask Wardens who created this submission or its duplicate to share an example scenario where this happens? Or possibly @rocketman-21 could share such an example if he was able to find it? Because protocol being dead for 400 days without any activity whatsoever and then out of a sudden someone buying some tokens doesn't sound very realistic (as already confirmed in the previous comments). And both submissions show only this case.

Moreover, in the contest's README there was the following information:

If you create the VRGDA with shoddy parameters you can get bad outputs and errors. Will add checks on the deployer/manager contract to ensure valid params.

If I understand this correctly, we were expected to assume that VRGDA will be deployed with reasonable parameters. If the example of protocol breaking would be only when:

the community sets a large perTimeUnit and targetPrice and never meets that volume

then I think this is an example of "shoddy parameters" and it could be unfair for Wardens who assumed correct parameters (and didn't want to submit an unrealistic scenario of protocol not being touched for 400 days) to judge this as Medium, especially that the exploit scenario was in fact found by the Sponsor himself.

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