xanoutas/solidity-security-findings huggingface dataset:
Auction settlement can be DOS's
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

When the deadline of the auction is reached, the auction is settled and a new auction is created by calling `AuctionHouse.sol:settleCurrentAndCreateNewAuction`.

The settlement of the auction performs many operations, including the transfer of the NFT to the highest bidder, the transfer of the auction proceeds to the creators, and the minting of governance tokens to each of the creators. This implies iterating over the list of creators twice. This list can be up to 100 elements long.

The first iteration is of special importance, as ether is sent to each of the creators and if the transaction fails, a transaction of WETH is sent to the creator. Although the amount of gas sent to transfer ether is [limited to 50,000 gas](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/AuctionHouse.sol#L429), it is still possible to consume all the gas of the block by adding a large number of creators that will consume all available gas in the transfer.

## Proof of Concept

Add the following code snippet to ` AuctionSettling.t.sol`:

```solidity
    (...)
    
import "forge-std/console2.sol";

contract GasBurner {
    uint256 n;
    receive() external payable {
        while(true) { n++;}
    }
}

contract AuctionHouseSettleTest is AuctionHouseTest {
    // Fallback function to allow contract to receive Ether
    receive() external payable {}

    function testAuditDOSSettleAuction() public {
        uint256 nCreators = 100;
        address gasBurner = address(new GasBurner());

        address[] memory creatorAddresses = new address[](nCreators);
        uint256[] memory creatorBps = new uint256[](nCreators);
        uint256 totalBps = 0;

        // Assume n creators with equal share
        for (uint256 i = 0; i < nCreators; i++) {
            creatorAddresses[i] = address(gasBurner);
            if (i == nCreators - 1) {
                creatorBps[i] = 10_000 - totalBps;
            } else {
                creatorBps[i] = (10_000) / (nCreators - 1);
            }

            totalBps += creatorBps[i];
        }

        uint256 verbId = createArtPieceMultiCreator(
            "Multi Creator Art",
            "An art piece with multiple creators",
            ICultureIndex.MediaType.IMAGE,
            "ipfs://multi-creator-art",
            "",
            "",
            creatorAddresses,
            creatorBps
        );

        auction.unpause();

        uint256 bidAmount = auction.reservePrice();
        vm.deal(address(21_000), bidAmount + 1 ether);
        vm.startPrank(address(21_000));
        auction.createBid{ value: bidAmount }(verbId, address(21_000));
        vm.stopPrank();

        vm.warp(block.timestamp + auction.duration() + 1); // Fast forward time to end the auction
Protocol targeting to deploy on `Base` and `Optimism` L2s but  does not include `chainID` to `domainSeparator`
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
A function that uses signature does not include `chainId` to `domainSeparator` and can be replayed on `Base` and `Optimism`. According to the readme protocol will be deployed to these chains in the feature.

## Proof of Concept
In function above chainID not included, although in contract, that out of scope, chainId included to domainSeparator.

Not included:

```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;
    }
```

Included:

```solidity
function castVoteBySig(uint256 proposalId, uint8 support, uint8 v, bytes32 r, bytes32 s) external {
        bytes32 domainSeparator = keccak256(
@>          abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), getChainIdInternal(), address(this))
        );     
        bytes32 structHash = keccak256(abi.encode(BALLOT_TYPEHASH, proposalId, support));
        bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash));
        address signatory = ecrecover(digest, v, r, s);
        require(signatory != address(0), "VerbsDAO::castVoteBySig: invalid signature");
        emit VoteCast(signatory, proposalId, support, castVoteInternal(signatory, proposalId, support), "");
    }
```

## Tools Used
vscode

## Recommended Mitigation Steps
Include `chainId` to `domainSeparator`.


## Assessed type

Other
`buyToken` can be front-runned to increase ERC20 token price which results in lower amount minted to creators
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

The `buyToken` function can be called by anyone, an attacker can call this function to front run the transaction of a honest user who's trying to buy ERC20 tokens and increase the VRGDA price, thus when the user transaction goes through he will be minted less amount of ERC20 tokens than what was intended for his provided ether amount.

## Proof of Concept

The issue occurs in the `buyToken` method shown below :

```solidity
function buyToken(
    address[] calldata addresses,
    uint[] calldata basisPointSplits,
    ProtocolRewardAddresses calldata protocolRewardsRecipients
) public payable nonReentrant whenNotPaused returns (uint256 tokensSoldWad) {
    //prevent treasury from paying itself
    require(msg.sender != treasury && msg.sender != creatorsAddress, "Funds recipient cannot buy tokens");

    require(msg.value > 0, "Must send ether");
    // ensure the same number of addresses and bps
    require(addresses.length == basisPointSplits.length, "Parallel arrays required");

    // Get value left after protocol rewards
    uint256 msgValueRemaining = _handleRewardsAndGetValueToSend(
        msg.value,
        protocolRewardsRecipients.builder,
        protocolRewardsRecipients.purchaseReferral,
        protocolRewardsRecipients.deployer
    );

    //Share of purchase amount to send to treasury
    uint256 toPayTreasury = (msgValueRemaining * (10_000 - creatorRateBps)) / 10_000;

    //Share of purchase amount to reserve for creators
    //Ether directly sent to creators
    uint256 creatorDirectPayment = ((msgValueRemaining - toPayTreasury) * entropyRateBps) / 10_000;

    //@audit using getTokenQuoteForEther to get ERC20 tokens amount
    //Tokens to emit to creators
    int totalTokensForCreators = ((msgValueRemaining - toPayTreasury) - creatorDirectPayment) > 0
        ? getTokenQuoteForEther((msgValueRemaining - toPayTreasury) - creatorDirectPayment)
        : int(0);

    // Tokens to emit to buyers
    int totalTokensForBuyers = toPayTreasury > 0 ? getTokenQuoteForEther(toPayTreasury) : int(0);

    //Transfer ETH to treasury and update emitted
    emittedTokenWad += totalTokensForBuyers;
    if (totalTokensForCreators > 0) emittedTokenWad += totalTokensForCreators;

    //Deposit funds to treasury
    (bool success, ) = treasury.call{ value: toPayTreasury }(new bytes(0));
    require(success, "Transfer failed.");

    //Transfer ETH to creators
    if (creatorDirectPayment > 0) {
        (success, ) = creatorsAddress.call{ value: creatorDirectPayment }(new bytes(0));
        require(success, "Transfer failed.");
    }

    //Mint tokens for creators
    if (totalTokensForCreators > 0 && creatorsAddress != address(0)) {
        _mint(creat
newPiece.quorumVotes can round down to zero when creating new piece in case totalSupply is very low; effect is CultureIndex.dropTopVotedPiece can drop a piece without meeting quorum vote
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L234
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#L284


# Vulnerability details

## Impact
newPiece.quorumVotes can round down to zero when creating new piece in case totalSupply is very low; effect is CultureIndex.dropTopVotedPiece can drop a piece without meeting quorum vote

## Proof of Concept
New piece quroum vote can be zero because there is a possibility of rounding down to zero in case totalsupply is very low or zero( In the early stages) as seen in this line 
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L234
qorum votes is calculated using this formula
newPiece.quorumVotes = (quorumVotesBPS * newPiece.totalVotesSupply) / 10_000;

and totalVotesSupply is calculated using this code
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L226
newPiece.totalVotesSupply = _calculateVoteWeight(
            erc20VotingToken.totalSupply(),
            erc721VotingToken.totalSupply()
        );

and  _calculateVoteWeight() function is calculated using this code
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L284
function _calculateVoteWeight(uint256 erc20Balance, uint256 erc721Balance) internal view returns (uint256) {
        return erc20Balance + (erc721Balance * erc721VotingTokenWeight * 1e18);
    }

So in the early stages when erc20VotingToken.totalSupply() and  erc721VotingToken.totalSupply() are very low or zero _calculateVoteWeight() can return 0 or a very low uint
Following , newPiece.quorumVotes = (quorumVotesBPS * newPiece.totalVotesSupply) / 10_000; could also return 0 due to some rounding down, of course setting quorumVotesBPS very high can orevent this only in case newPiece.totalVotesSupply is not 0, in case it's 0  newPiece.quorumVotes will be 0.

- verbToken ca, effectively drop a top voted piece that did not match quorum vote because it's basically 0 now
- verbToken will get minted just like that.

## Tools Used
Manual review
## Recommended Mitigation Steps
DAO can mint some tokens in the early stage to increase for the treasury to prevent rounding down.


## Assessed type

Error
AuctionHouse paused if settleCurrentAndCreateNewAuction called when top piece in CultureIndex not reached quorum
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

Malicious actor can pause AuctionHouse or it can happen or by accedent. Admin actions needed to unpause it.  This would be bad expirience for users, inconvenient for admin and can damage long term protocol viability because need of constantly monitoring pause state by admin.

## Proof of Concept

Add this to AuctionBasic.t.sol:
```
function testAuctionCreationDOS_quorum() public {
    // create votes and art piece so piece quorum > 0
    deal(address(erc20Token), address(1), 1e18, true);
    createDefaultArtPiece();

    // ... no votes -> piece not reached quorum
   
    // pause triggered
    vm.expectEmit(true, true, true, true);
    emit PausableUpgradeable.Paused(address(dao));
    auction.unpause();
}

```

If top voted piece not reached quorum, it cannot be dropped. But if previous auctin ended, anyone can settle it and start new. Minting new verbs will revert because of dropTopVotedPiece and auction house will be paused. Current auction can still be settled and winner gets their art, but to start new auction admin actions required. 

## Tools Used

Manual Review

## Recommended Mitigation Steps

Gracefully handle this case and revert instead of pausing when top piece not reached quorum.
Also separate auction settlement and creation so even when no piece reached quorum current winner can take thier prize whithout pausing.



## Assessed type

DoS
`AuctionHouse.sol` - auction parameters are not individually cached for each auction
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details


## Impact

The `AuctionHouse.sol` contract is designed for managing NFT auctions, includes also various features such as auction creation, bidding, settlement, and integration with external tokens. The contract allow several auction parameters such as `timeBuffer`, `reservePrice`, and `minBidIncrementPercentage` to can be changed globally through an active auction. These parameters are not individually cached for each auction, but rather exist as global settings for the contract. This design can lead to inconsistencies and potentially exploitable conditions if these parameters are changed during an ongoing auction.

```js
    /**
     * @notice Set the auction time buffer.
     * @dev Only callable by the owner.
     */
    function setTimeBuffer(uint256 _timeBuffer) external override onlyOwner {
        timeBuffer = _timeBuffer;

        emit AuctionTimeBufferUpdated(_timeBuffer);
    }
```

```js
    /**
     * @notice Set the auction reserve price.
     * @dev Only callable by the owner.
     */
    function setReservePrice(uint256 _reservePrice) external override onlyOwner {
        reservePrice = _reservePrice;

        emit AuctionReservePriceUpdated(_reservePrice);
    }
```

```js
    /**
     * @notice Set the auction minimum bid increment percentage.
     * @dev Only callable by the owner.
     */
    function setMinBidIncrementPercentage(uint8 _minBidIncrementPercentage) external override onlyOwner {
        minBidIncrementPercentage = _minBidIncrementPercentage;

        emit AuctionMinBidIncrementPercentageUpdated(_minBidIncrementPercentage);
    }
```

## Proof of Concept

1. **Global Parameters**: In the current implementation, the contract uses single, global state variables for `timeBuffer`, `reservePrice`, and `minBidIncrementPercentage`. These variables apply to the active auction and any future auctions until they are changed.

2. **Lack of Individual Auction Context**: Each auction does not have its own set of these parameters. Consequently, if an auction is ongoing and one of these parameters is changed, the new values immediately affect the current auction.

3. **Potential Exploitation**: If these parameters are changed while an auction is active. This can lead to scenarios where the rules of the auction are altered in real-time, potentially disadvantaging certain bidders or favoring others. For example, increasing the `minBidIncrementPercentage` could unexpectedly raise the minimum additional bid amount required, while decreasing the `timeBuffer` could shorten the auction duration unexpectedly.

Consider the following scenario:
1. New auction is created and currently `reservePrice` for the `AuctionHouse.sol` contract is `1 ether`.
2. Bob really want this NFT and create bid with `amount = 3e18` via `createBid()` function.
3. The `reservePrice` is changed to `5 ether
AuctionHouse::MIN_TOKEN_MINT_GAS_THRESHOLD is insufficient
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Malicious actor can pause AuctionHouse or it can happen or by accedent. Admin actions needed to unpause it.  This would be bad expirience for users, inconvenient for admin and can damage long term protocol viability because need of constantly monitoring pause state by admin.

## Proof of Concept
Add this to AuctionBasic.t.sol:
```
function testAuctionCreationDOS_gas() public {
   // create piece with lots of creators
    address[] memory creators = new address[](100);
    for (uint160 i; i < 100; i++) creators[i] = address(i+1);
    uint[] memory bps = new uint[](100);
    bps[0] = 10_000;
    createArtPieceMultiCreator(
        "test",
        "test",
        ICultureIndex.MediaType.IMAGE,
        "test",
        "test",
        "test",
        creators,
        bps
    );

    // unpausing auction triggers create new auction rutine same as when settleCurrentAndCreateNewAuction
    vm.expectEmit(true, true, true, true);
    emit PausableUpgradeable.Paused(address(dao));
    address(auction).call{gas: 1_000_000}(abi.encodeWithSelector(auction.unpause.selector));
}

```

When art piece have a lot of creatorss, 750_000 gas threshold is insufficient to write all creators to Verbs storage. Mint will revert because of OOG and pause triggered.

This happens because writing 1 creator to storage costs ~20K gas, 20K * 100 > 750_000.

## Tools Used

www.evm.codes
Manual Review

## Recommended Mitigation Steps

Raise threshold of lower max amount of creators. Another solution would be revert if return data if mint call is empty and it's (likely) because of OOG.








## Assessed type

DoS
Reserve Price can be change anytime which unfairly affects the settleAuction function result
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The _settleAuction function checks if the contract's balance is less than the reservePrice to determine the auction's outcome. If the reservePrice is changed right before settlement, it could unfairly alter the auction's result. The outcome will be the refund of eth or weth to the winner bidder and the burning of Verb nft. 

````
function _settleAuction() internal {
        IAuctionHouse.Auction memory _auction = auction;

        require(_auction.startTime != 0, "Auction hasn't begun");
        require(!_auction.settled, "Auction has already been settled");
        //slither-disable-next-line timestamp
        require(block.timestamp >= _auction.endTime, "Auction hasn't completed");

        auction.settled = true;

        uint256 creatorTokensEmitted = 0;
        // Check if contract balance is greater than reserve price
        if (address(this).balance < reservePrice) { // if contract eth bal is more than reserve price, you can't execute this or burn the verb ID
            // If contract balance is less than reserve price, refund to the last bidder
            if (_auction.bidder != address(0)) {
                _safeTransferETHWithFallback(_auction.bidder, _auction.amount);
            }

            // And then burn the Noun
            verbs.burn(_auction.verbId);
````

This is basically resetting the whole process from voting up to the distribution of bid amount and verb nft to proper accounts, just because of untimely change of reserve price. Bidder participants might feel the auction  rule is unpredictable and biased.

## Proof of Concept
Please run this test to prove that reserve price can be change anytime in which it can alter the result of auction.
Before you run this test, please follow this gist instruction https://gist.github.com/bluenights004/3a63b2d7916c951c9241d41c62a27b42

Test Detail:
````
function testSettlingAuctionReservePriceChange() public {
        createDefaultArtPiece();
        auction.unpause();

        uint256 bidAmount = auction.reservePrice(); // bidAmount is equal to reserve price of 1 ether
        address auctionOwner = auction.owner(); // auction owner address
         vm.deal(address(11), bidAmount); // setting up bidder's address and eth balance
        vm.startPrank(address(11));
        auction.createBid{ value: bidAmount }(0, address(11)); // Assuming first auction's verbId is 0
        vm.stopPrank();

        vm.warp(block.timestamp + auction.duration() + 1); // Fast forward time to end the auction

        vm.startPrank(auctionOwner);
        auction.setReservePrice(2 ether); // auction owner change the reserve price before settle auction into 2 ether
        auction.pause();
        vm.stopPrank();

        console.log("Eth balance of Last Bidder (before settle):", address(11).balance);
        console.log("Eth balance of Aucti
The pausing mechanism doesn't govern emergency cases
Audit: 2023-12-revolutionprotocol-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details



## Impact
The normal operations will continue even during an emergency


## Proof of Concept
The `AuctionHouse` contract is pausable. But this mechanism is used for `_createAuction` and `settleAuction`;

Accordingly, an auction is created during paused times, and the owner unpauses the contract to start the auction, and again the owner pauses the contract for `settleAuction` or anyone can call `settleCurrentAndCreateNewAuction` during unpaused times.

So if there should be any contingencies arise, if the owner pauses the contract, the users will continue `createBid` as there's no modifier 
```solidity
Contract: AuctionHouse.sol

171:     function createBid(uint256 verbId, address bidder) external payable override nonReentrant {
```
and they can settle the auction due to whenPaused modifier
```solidity
Contract: AuctionHouse.sol

161:     function settleAuction() external override whenPaused nonReentrant {
162:         _settleAuction();
163:     }
```

## Tools Used
Manual Review

## Recommended Mitigation Steps
We recommend using the pause functionality as a preparation of contingencies not as a trigger and carry `_createAuction` under a specific function including `_settleAuction`


## Assessed type

Access Control