xanoutas/solidity-security-findings huggingface dataset:
Users receive fewer tokens due to inaccuracy in calculation
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/libs/VRGDAC.sol#L57-L81


# Vulnerability details

## Impact
There is a loss of precision in the `VRGDAC.yToX` function, because in several places division occurs first, and then the result is multiplied. This results in users receiving fewer tokens. According to test data, the difference can be 8 digits. The choice of vulnerability severity takes into account the high probability of the event and the possibility of even more significant damage.

## Proof of Concept
There are two places where division occurs first at the `VRGDAC.yToX` function. This can be easily fixed according to `Recommended Mitigation Steps`. The difference in calculation accuracy can be verified using an existing test `ERC20TokenEmitterTest.testBuyingLaterIsBetter`. The test requires adding logging of the amount of tokens.
```diff
    function testBuyingLaterIsBetter() public {
        vm.startPrank(address(0));

        int256 initAmount = erc20TokenEmitter.getTokenQuoteForEther(1e18);

        int256 firstPrice = erc20TokenEmitter.buyTokenQuote(1e19);

        // solhint-disable-next-line not-rely-on-time
        vm.warp(block.timestamp + (10 days));

        int256 secondPrice = erc20TokenEmitter.buyTokenQuote(1e19);

        emit log_int(firstPrice);
        emit log_int(secondPrice);

        int256 laterAmount = erc20TokenEmitter.getTokenQuoteForEther(1e18);

+       emit log_int(initAmount);
+       emit log_int(laterAmount);
        
        assertGt(laterAmount, initAmount, "Later amount should be greater than initial amount");

        assertLt(secondPrice, firstPrice, "Second price should be less than first price");
    }
```
Test result of the existing implementation:
```bash
@collectivexyz/revolution:test: [PASS] testBuyingLaterIsBetter() (gas: 77863)
@collectivexyz/revolution:test: Logs:
@collectivexyz/revolution:test:   10005269876410031943
@collectivexyz/revolution:test:   3488621893286168550
@collectivexyz/revolution:test:   999947323442167000
@collectivexyz/revolution:test:   2867538769039488000
```
Test result with changes made:
```bash
@collectivexyz/revolution:test: [PASS] testBuyingLaterIsBetter() (gas: 77863)
@collectivexyz/revolution:test: Logs:
@collectivexyz/revolution:test:   10005269876410031943
@collectivexyz/revolution:test:   3488621893286168550
@collectivexyz/revolution:test:   999947323442167590
@collectivexyz/revolution:test:   2867538769068152103
```

## Tools Used
Manual review

## Recommended Mitigation Steps
Concider usind division after multiplying in all places where it possible:
```diff
    function yToX(int256 timeSinceStart, int256 sold, int256 amount) public view virtual returns (int256) {
        int256 soldDifference = wadMul(perTimeUnit, timeSinceStart) - sold;
        unchecked {
            return
+               wadDiv(
                    wadMul(
                        perTimeUnit,
Ether will get stuck in ERC20TokenEmitter
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

`ERC20TokenEmitter.sol:buyToken` distributes the funds sent to purchase the tokens in the following way:

- 2.5% of the funds are [sent to the `RevolutionProtocolRewards` contract](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L165).
- From the remaining funds, a percentage dependent on the `creatorRateBps` variable is [sent to the `treasury`](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L191).
- The other percentage is split into two parts, according to the `entropyRateBps` variable. One part is [sent to the `creatorsAddress`](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L196).

This leaves in the contract the remainder of funds, which is equal to:

```js
((msg.value * 0.975) * creatorRateBps / 10_000) * (10_000 - entropyRateBps) / 10_000;
```

Given that there is no withdrawal mechanism to get this remainder out of the contract, the funds are stuck in the contract.

## Impact

Ether will get stuck in the contract.

## Proof of Concept

```solidity
function testAuditBuyToken() public {
    uint256 creatorRate = 8_000; // 80%
    uint256 entropyRate = 4_000; // 40%

    // Setup
    vm.startPrank(address(dao));
    erc20TokenEmitter.setCreatorRateBps(creatorRate);
    erc20TokenEmitter.setEntropyRateBps(entropyRate);
    erc20TokenEmitter.setCreatorsAddress(address(80));
    vm.stopPrank();

    // Purchase data
    address[] memory recipients = new address[](1);
    recipients[0] = address(1);
    uint256[] memory bps = new uint256[](1);
    bps[0] = 10_000; // 100%
    uint256 valueSent = 1 ether;

    // Perform token purchase
    uint256 erc20TokenEmitterInitialBalance = address(erc20TokenEmitter).balance;
    vm.startPrank(address(this));
    erc20TokenEmitter.buyToken{ value: valueSent }(
        recipients,
        bps,
        IERC20TokenEmitter.ProtocolRewardAddresses({
            builder: address(0),
            purchaseReferral: address(0),
            deployer: address(0)
        })
    );

    // Distribution calculations
    uint256 rewardAmount = erc20TokenEmitter.computeTotalReward(valueSent);
    uint256 toPayTreasury = ((valueSent - rewardAmount) * (10_000 - creatorRate)) / 10_000;
    uint256 creatorDirectPayment = ((valueSent - rewardAmount - toPayTreasury) * entropyRate) / 10_000;
    uint256 valueLocked = valueSent - rewardAmount - toPayTreasury - creatorDirectPayment;

    assertEq(rewardAmount, 0.025 ether);
    assertEq(toPayTreasury, 0.195 ether);
    assertEq(toPayTreasury, erc20TokenEmitter.treasury().balance);
Some buyers wont get expected tokens minted due to precision loss
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
- The `ERC20TokenEmitter.buyToken` mints tokens according to the configured bps per address.
- This is due to the below code's implementation in `buyToken()` function.

```solidity
    for (uint256 i = 0; i < addresses.length; i++) {
        if (totalTokensForBuyers > 0) {
            // transfer tokens to address
            _mint(addresses[i], uint256((totalTokensForBuyers * int(basisPointSplits[i])) / 10_000)); 
        }
        bpsSum += basisPointSplits[i];
    }
```
- if the numerator is less than  10_000, then the recipient will get zero tokens.

## Proof of Concept

- A user want to spread his tokens across 10 addresses with 1000 bps per address. And if (`totalTokensForBuyers` < 10), there will be zero amounts minted.
- Now there's a limit of max addresses as recipients == 100. So, this loss will impact those users a lot.

## Tools Used

- Manual review

## Recommended Mitigation Steps

- Follow high precision divider `1e18` instead of `10_000`

```diff
    for (uint256 i = 0; i < addresses.length; i++) {
        if (totalTokensForBuyers > 0) {
            // transfer tokens to address
-           _mint(addresses[i], uint256((totalTokensForBuyers * int(basisPointSplits[i])) / 10_000)); 
+           _mint(addresses[i], uint256((totalTokensForBuyers * int(basisPointSplits[i])) / 1e18)); 
        }
        bpsSum += basisPointSplits[i];
    }
```





## Assessed type

Decimal
`CultureIndex.sol#_vote()` - Creators of certain `piece` can vote for their `piece`
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

In `CultureIndex` there is a function `vote()` that allows users to vote for a piece to get sold on the auction house. Each piece has creators that get cut of the sale. The problem is that there is no checks if the user voting for a certain piece is it's own creator. This means that a creator can vote for their own piece which would cause an unfair advantage over other pieces.

Unfair advantage for other creators.

For extra explanation view the comments in the test below.

## Proof of Concept

Paste the following test inside `CultureIndex.t.sol` in `CultureIndexTestSuite` contract and run `forge test --match-contract "CultureIndexTestSuite" --match-test "test_ProofOfConcept" -vv`

```js
        vm.stopPrank();

        // mint tokens to the voters
        address voter1 = vm.addr(0xD);
        address voter2 = vm.addr(0xB);
        address voter3 = vm.addr(0xB);
        address creator = vm.addr(0xC);

        vm.startPrank(address(erc20TokenEmitter));
        erc20Token.mint(voter1, 1000);
        erc20Token.mint(voter2, 1000);
        erc20Token.mint(voter3, 1000);
        erc20Token.mint(creator, 1000);
        vm.stopPrank();

        vm.roll(block.number + 1);

        // creation of Piece
        vm.startPrank(address(creator));
        uint256 pieceId0 = createArtPiece(
            "Mona Lisa",
            "A masterpiece",
            ICultureIndex.MediaType.IMAGE,
            "ipfs://legends",
            "",
            "",
            address(creator),
            10000
        );
        vm.stopPrank();

        vm.roll(block.number + 1); // roll forward to ensure votes are snapshotted

        ICultureIndex.ArtPiece memory createdPieceWithId = cultureIndex.getPieceById(pieceId0);

        // Nobody likes the newly created piece but the creator has enough voting power to reach the quorumVotes for his piece
        vm.startPrank(address(creator));
        cultureIndex.vote(pieceId0);
        vm.stopPrank();

        console.log("quorumVotes for the currecnt piece: ", createdPieceWithId.quorumVotes);
        uint256 totalVoteWeight = cultureIndex.totalVoteWeights(pieceId0);
        assertEq(totalVoteWeight, 1000); // currently the piece that nobody likes, reach his quorumVotes and can be dropped and move to an auction


        vm.startPrank(address(erc721Token));
        cultureIndex.dropTopVotedPiece(); // the piece that nobody likes is successfully dropped

        // This vulnerability can be even with higher likelihood if there are several creators
```

## Tools Used

Manual review
Foundry

## Recommended Mitigation Steps

Disallow creators from voting for their own pieces.


## Assessed type

Context
`voteForManyWithSig` functions in `CultureIndex` are open to replay attacks if fails.
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L419-L444
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L367-L380


# Vulnerability details

## Impact
If **`_vote`** fails or reverts for any reason, the nonce in **`_verifyVoteSignature`** doesn't increment. 
This leads to the same vote tx being replayed by anyone.

## Proof of Concept
A user submits a vote via **`voteForManyWithSig`**, triggering **`_verifyVoteSignature`** for signature validation.

If **`_vote`** within **`_voteForMany`** fails, the transaction reverts but the `nonce` doesn't increment, leaving the vote susceptible to replay attacks using the same signature while `deadline` is not reached.

```solidity
function _verifyVoteSignature(
        address from,
        uint256[] calldata pieceIds,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal returns (bool success) {
        require(deadline >= block.timestamp, "Signature expired");

        bytes32 voteHash;

        voteHash = keccak256(abi.encode(VOTE_TYPEHASH, from, pieceIds, nonces[from]++, deadline));

        bytes32 digest = _hashTypedDataV4(voteHash);

        address recoveredAddress = ecrecover(digest, v, r, s);

        // Ensure to address is not 0
        if (from == address(0)) revert ADDRESS_ZERO();

        // Ensure signature is valid
        if (recoveredAddress == address(0) || recoveredAddress != from) revert INVALID_SIGNATURE();

        return true;
    }
```

```solidity
function voteForManyWithSig(
        address from,
        uint256[] calldata pieceIds,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external nonReentrant {
        bool success = _verifyVoteSignature(from, pieceIds, deadline, v, r, s);

        if (!success) revert INVALID_SIGNATURE();

        _voteForMany(pieceIds, from);
    }
```

PoC

1. Alice give her sig to Bob
2. Bob call `voteForManyWithSig` / `batchVoteForManyWithSig`;
3. For any reason tx is failed;
4. Bob changed his decision and asked for new sig for different pieceIds;
5. The attacker can replay tx from step 3.

## Tools Used
vscode

## Recommended Mitigation Steps
Failed txs should still increase the nonce.


## Assessed type

Other
The entire AuctionHouse contract can be disabled by a wrong creator bps sum.
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

# Vulnerability details

## Description
There is a potential for a DoS in the `AuctionHouse` contract related to the `buyToken` call from the `ERC20TokenEmitter`. 

This is due to the `buyToken` requiring the `bpsSum` to be equal to `10_000`, but there is no restriction when creating the art piece that stablishes that the sum of the creators bps has to be `10_000`.

## Impact
Since the DoS would not allow any new auction to start, it's impact would be high. 

This DoS can be caused accidentally and can brick the entire `AuctionHouse` contract.


## Proof Of Concept

A new piece gets created, but the sum of the bps on the creatorArray does not equal `10_000`.

The piece ends up being the most voted one, and an auction is created for it.

Once the auction ends, `_settleAuction` is called.

Expected Behavior:
```
The Auction gets settled, the buyToken function from the ERC20TokenEmitter is able to distribute the tokens without any issue, and a new auction is able to start.
```
Unexpected Behavior:
```
The Auction cannot be settled, as the entire transaction ends up reverting when the buyToken function compares the `bpsSum` to `10_000`
```
This is due to the following code:

```
        uint256 bpsSum = 0;

        //Mint tokens to buyers

        for (uint256 i = 0; i < addresses.length; i++) {
            if (totalTokensForBuyers > 0) {
                // transfer tokens to address
                _mint(addresses[i], uint256((totalTokensForBuyers * int(basisPointSplits[i])) / 10_000));
            }
            bpsSum += basisPointSplits[i];
        }

        require(bpsSum == 10_000, "bps must add up to 10_000");
```
The require needs the `bpsSum` to be `10_000`, but bricks the entire transaction if it is not.


This would cause a Denial of Service that would not enable the contract to start new auctions, besides of locking the current funds.

The permanent DoS is due to `settleCurrentAndCreateNewAuction()` needing to settle the current auction to start a new one, and the `unpause` needing the `auction.settled` variable to be true to be able to call _createAuction().

## Recommended Mitigation Steps

A possible fix would be to prevent a piece from being auctioned if the sum of the creators bps is not `10_000`



## Assessed type

DoS
Bidders can bid at previous auction reserve price by frontrunning the setter transactions
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
- The `AuctionHouse.settleCurrentAndCreateNewAuction` can frontrun the setter functions such as `setCreatorRateBps`, `setMinCreatorRateBps`, `setEntropyRateBps`, `setTimeBuffer`, `setMinBidIncrementPercentage` & `setReservePrice`.
- As soon as the current auction ends, an attacker can call `settleCurrentAndCreateNewAuction` to settle the current auction and start a new auction. And he his bid will follow the previous auction's state like `reservePricev, creatorRateBps, minCreatorRateBps, entropyRateBps, duration`.
- And now the owner will pause and update these important satrte for the new auction.
- But the attacker already placed the bid and now its in his favour to get the token at a cheap price if the new reservePrice is  too high.


```solidity
    function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused {
        _settleAuction();
        _createAuction();
    }
```

## Proof of Concept

- An attack waits for the block timestamp == auction.endTime.
- At exact auction.endTime, he calls `AuctionHouse.settleCurrentAndCreateNewAuction` that settles and creates a new auction.
- And now he will place the bid at previous reserve price and previous state of auction.
- The attacker gains value, if there's a significant increase in reserve price. And will nt attract any new bidders, so he will get the Tokens, making that auction round wasted.

## Tools Used

- Manual review

## Recommended Mitigation Steps

- Make the `settleCurrentAndCreateNewAuction` function to follow `onlyOwner` modifier.
- Or make anyOne to settle the auction, but creating a auction can be controlled by owner. So now owner can pause and change the important state like price and period, rates etc., And then unpause to create a new auction.

```solidity
-   function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused {
+   function settleCurrentAndCreateNewAuction() external override nonReentrant whenNotPaused onlyOwner {
        _settleAuction();
        _createAuction();
    }
```





## Assessed type

DoS
Delegation to address(0) causes permanent loss of voting power
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/base/VotesUpgradeable.sol#L206-L213
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/NontransferableERC20Votes.sol#L29
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/base/erc20/ERC20VotesUpgradeable.sol#L24


# Vulnerability details

## Impact
As stated in the comment on line 12 of NontransferableERC20Votes.sol, delegation of vote power can be done through the `delegate` function or by providing a signature to be used with `delegateBySig`. However, these functions do not prevent users from delegating to address(0), leading users to permanently lose their voting power, intentionally or accidentally.

## Proof of Concept

A test demonstrating that delegation to address(0) is possible and indeed results in the loss of voting power. You can paste this into `NontransferableERC20.t.sol`. Also see: Issue in Nouns Builder repo for the same bug https://github.com/code-423n4/2022-09-nouns-builder-findings/issues/203

```javascript
function testVotingAndDelegationToAddress0() public {
        address delegate = address(0);
        uint256 mintAmount = 1000 * 1e18;

        // Mint tokens to the owner
        vm.startPrank(address(erc20TokenEmitter));
        erc20Token.mint(address(this), mintAmount);
        vm.stopPrank();

        // Delegate voting power
        erc20Token.delegate(delegate);

        // @bug Voting power is lost.
        assertEq(erc20Token.getVotes(delegate), 0);

        // Ensure that no tokens were transferred in the process of delegation
        assertEq(erc20Token.balanceOf(delegate), 0, "Delegation should not transfer tokens");
}
```

## Tools Used

Manual Review

## Recommended Mitigation Steps

Don't allow delegation to address(0) by adding checks to both delegate and delegateBySig functions as the following: 

```
require(delegatee != address(0), "Votes: delegate to the zero address");
```







## Assessed type

Context
An artPiece with a creator address set to address(0) leads to a pseudo-permanent DoS if it ends up being auctioned.
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

# Vulnerability details

## Description
There is a potential for a DoS in the `AuctionHouse` contract related to the `buyToken` call from the `ERC20TokenEmitter`. 

This is due to the `buyToken` using the `_mint` function to all the addresses that are stored on the creators array. As the creators array for an art piece is not checked, an address(0) could be saved, which causes a revert when 'buyToken' tries to mint.

## Impact
Since the DoS would not allow any new auction to start, it's impact would be high. 

The only fix currently on the code itself is allowing the creators to receive the entire `creatorsShare` to avoid the `if (creatorsShare > ethPaidToCreators)` conditional. This is due to the `buyToken` call being behind this conditional. There may be settings where this is not even possible, as the ethPaidToCreators could be less than creatorsShare due to rounding issues.
## Proof of Concept

A new piece gets created, but the creatorArray has an address(0) with a very low amount of bps.(Could be by mistake or malicious)

The piece ends up being the most voted one, and an auction is created for it.

Once the auction ends, `_settleAuction` is called.

Expected Behavior:
```
The Auction gets settled, the buyToken function from the ERC20TokenEmitter is able to distribute the tokens without any issue, and a new auction is able to start.
```
Unexpected Behavior:
```
The Auction cannot be settled, as the entire transaction ends up reverting when the buyToken function calls _mint() with address(0) as the account, which triggers a revert.
```
This would cause a Denial of Service that would not enable the contract to start new auctions, besides of locking the current funds.

This is due to `settleCurrentAndCreateNewAuction()` needing to settle the current auction to start a new one, and the `unpause` needing the `auction.settled` variable to be true to be able to call _createAuction().

## Recommended Mitigation Steps

One way to mitigate this issue would be to check the creatorArray addresses, and prevent any address(0) from being pushed into the newPiece array. 






## Assessed type

DoS