xanoutas/solidity-security-findings huggingface dataset:
Sending less amount of Eth instead of sending full amount and remaining Eth will be locked in the contract
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#L191


# Vulnerability details

## Impact
In the [buyToken](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L152C5-L230C6) function of the [ERC20TokenEmitter](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L17C1-L17C30) contract, when users buy tokens they send eth to treasury and creator, for creators only portion of creator reward transfered directly and remaining eth will be locked in the contract untouched.

## Proof of Concept
As can be observed by looking the [buyToken](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L152C5-L230C6) function of the [ERC20TokenEmitter](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L17C1-L17C30) contract creator receive reward according to the entropyBps.

```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;
        //Tokens to emit to creators
        int totalTokensForCreators = ((msgValueRemaining - toPayTreasury) - creatorDirectPayment) > 0 //@audit remaining in the contract
            ? getTokenQuoteForEther((msgValueRemaining - toPayTreasury) - creatorDirectPayment)
            : int(0);

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

        //Transfer
Hijacking voting using NFT flash loans
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-L324


# Vulnerability details

## Impact
The voting mechanism can be gamed by attackers by using flash loans of VerbToken NFT.

Currently the voting weight of a voter is determined by his Erc20 and VerbToken NFT balances at the time of art piece creation.
```solidity
    function _vote(uint256 pieceId, address voter) internal {
        ...

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

    function _getPastVotes(address account, uint256 blockNumber) internal view returns (uint256) {
        return
            _calculateVoteWeight(
                erc20VotingToken.getPastVotes(account, blockNumber),
                erc721VotingToken.getPastVotes(account, blockNumber)
            );
    }
```

The `erc721VotingToken` is a token which can be freely moved in the market. Hence these tokens can be captured easily by buying them or by flash loans.

Consider this scenario:
1. Attacker gets hold of 1000 VerbTokens instantly (by flash loan or buying).
2. Attacker lists a new art piece in CultureIndex contract. 
3. Attacker uses his instantaneous voting power to vote on the newly created art piece.
4. The art piece becomes the most highly voted piece and can now go for auction.
5. The attacker returns the 1000 VerbTokens (repays flash loan or sells them).

Using this attack the attacker was able to create a new art piece and make it the top voted piece instantly, i.e. essentially screwing up the community voting mechanism completely. In the next auction this maliciously top voted art piece will get selected for auction. 

External third party services can be created to provide this instant voting feature as a service, leaving no room for innocent users to win voting mechanism ever.

## Tools Used
Manual review

## Recommended Mitigation Steps
Consider adding a delay of minimum 1 block between the art piece creation and its voting. 


## Assessed type

Context
Attacker could remove votes after reaching quorum to drop a piece that no longer has enough votes. 
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#L519-L534
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L523


# Vulnerability details

## Impact
Pieces could be removed without having ongoing community support. This could undermine the legitimacy of the curation process.

 It allows pieces to be dropped without having enough support. Specifically:

An attacker could manipulate votes to barely reach quorum for a piece they want dropped.
Then after it reaches the top spot to be dropped, the attacker could remove some of their votes so the total drops below quorum.
When dropTopVotedPiece is called, the piece will still get dropped even though it no longer has enough votes to reach quorum.
This undermines the integrity of the quorum voting system. Pieces could get dropped without having ongoing community support.

The severity is high because it allows the core voting mechanism and quorum rules to be bypassed. This could enable censorship, manipulation, or other unintended outcomes.

## Proof of Concept
There is no check that the total vote weight exceeds the quorum before dropping a piece in dropTopVotedPiece. An attacker could remove votes after reaching quorum to drop a piece that no longer has enough votes. 

There is a potential vulnerability in the dropTopVotedPiece() function where a piece could be dropped without having enough votes to meet quorum at the time it is dropped.

Here is how it works:
1. The piece reaches the quorum vote threshold needed to be eligible for dropping. This saves a quorumVotes value in the piece struct when it is created.
2. Voters remove some of their votes so that the current totalVoteWeights for the piece falls below the original quorumVotes threshold.
3. The dropTopVotedPiece() function only checks that the current totalVoteWeights meets the quorum, not the original quorumVotes value from when the piece was created:

       require(totalVoteWeights[piece.pieceId] >= piece.quorumVotes, "Does not meet quorum votes to be 
       dropped.");

So a piece could be dropped without actually having enough votes based on the rules initialized when it was created.



## Tools Used
Manual
## Recommended Mitigation Steps
The dropTopVotedPiece() function should also check that the current total vote weight meets the original quorum votes value from when the piece was created:

       function dropTopVotedPiece() public nonReentrant returns (ArtPiece memory) {

         // Existing code

         require(totalVoteWeights[piece.pieceId] >= piece.quorumVotes, "Does not meet original quorum 
       votes.");  
         require(totalVoteWeights[piece.pieceId] >= pieces[piece.pieceId].quorumVotes, "Does not meet current 
       quorum votes.");

         // Rest of function

This ensures the piece has both the original level
Manager contract can get initialized
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

In the upgradable contracts you are using both `constructor`and `initialize` function for initializing the contract

```
    constructor(address _manager) payable initializer {
        manager = IRevolutionBuilder(_manager);
    }

```

```   
 function initialize(
        address _initialOwner,
        IRevolutionBuilder.ERC20TokenParams calldata _erc20TokenParams
    ) external initializer {
        require(msg.sender == address(manager), "Only manager can initialize");

        __NontransferableERC20Votes_init(_initialOwner, _erc20TokenParams.name, _erc20TokenParams.symbol);

```
In contracts deployed in the Manager contract, in their `constructor` you first set the manager's contract address and in the `initialize` function you require the caller to be the manager however the problem is since these contracts meant to be upgradable the `initialize` function will always fail in this case because when you set the manager address in the `constructor` the address will be stored in implementation contract's storage not in the proxy contract, so when you try to `initialize` the contract from manager contract the tx will fail because in the proxy contract's storage the manager address is still `address(0)`, So any` initialize` function that require the caller to be manager will faill because the manager is not set in the proxy contract.


This problem also exist for Manager contract itself because you use both `constructor` and `initialize` function for initializing the contract, any value set in the `constructor` won't exist in the proxy contract and most of the contract's functionality won't work since important values are not set.

## Impact
those contracts that is meant to be deployed by manager won't be deployed and fail, and those upgradable contracts which use both `constructor` and `initialize` function will not work as expected

## Proof of Concept
All contracts affected are listed below

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


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

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

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

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

## Tools Used
Manual Review 

## Recommended Mitigation Steps
Since contracts are deployed and ini
Rounding error in VRGDAC.yToX
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#L67


# Vulnerability details

## Impact
Due to the rounding error in the function, it will calculate the token amount incorrectly.

## Proof of Concept
The function yToX calculate the token amount as follows.
``````solidity
function yToX(int256 timeSinceStart, int256 sold, int256 amount) public view virtual returns (int256) {
        int256 soldDifference = wadMul(perTimeUnit, timeSinceStart) - sold;
        unchecked {
            return
                wadMul(
                    perTimeUnit,
                    wadDiv(
                        wadLn(
                            wadDiv(
                                wadMul(
                                    targetPrice,
                                    wadMul(
                                        perTimeUnit,
67                                     wadExp(wadMul(soldDifference, wadDiv(decayConstant, perTimeUnit)))
                                    )
                                ),
                                wadMul(
                                    targetPrice,
                                    wadMul(
                                        perTimeUnit,
                                        wadPow(1e18 - priceDecayPercent, wadDiv(soldDifference, perTimeUnit))
                                    )
                                ) - wadMul(amount, decayConstant)
                            )
                        ),
                        decayConstant
                    )
                );
        }
    }
``````
However at L67, in wadExp, they first divide and multiply and this can lead to rounding error and calculate token amount incorrectly.

## Tools Used
Manual review
## Recommended Mitigation Steps
```diff
function yToX(int256 timeSinceStart, int256 sold, int256 amount) public view virtual returns (int256) {
        int256 soldDifference = wadMul(perTimeUnit, timeSinceStart) - sold;
        unchecked {
            return
                wadMul(
                    perTimeUnit,
                    wadDiv(
                        wadLn(
                            wadDiv(
                                wadMul(
                                    targetPrice,
                                    wadMul(
                                        perTimeUnit,
-                                       wadExp(wadMul(soldDifference, wadDiv(decayConstant, perTimeUnit)))
+                                      wadExp(wadDiv(wadMul(soldDifference, decayConstant), perTimeUnit))
                                    )
                                ),
                                wadMul(
                                    targetPrice,
                                    wadMul(
                                        perTimeUnit,
                                        wadPow(1e18 - priceDecayPercent, wadDiv(soldDiff
ETH used to calculate ERC20 tokens for creatorsAddress is permanently locked in ERC20TokenEmitter.sol
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#L179


# Vulnerability details

## Impact
In buyToken() function, after calculating the rewards for the ProtocolRewards contract, treasury and creatorDirectPayment, the remaining ether used to calculate the ERC20 tokens to be minted is permanently locked in the ERC20TokenEmitter.sol contract. The greater the creatorRateBps and smaller the entropyRateBps, the higher is the amount of ETH permanently locked in the contract.

Greater the creatorRateBps = higher is the cut for creatorsAddress compared to treasury
Smaller the entropyRateBps = lower is the ETH cut and higher is the ETH (which is locked) used to calculate ERC20 cut for creatorsAddress

## Proof of Concept
Here is the whole process:

1. Currently we know that when we call buyToken(), the ETH is split into these parts:
 - 2.5% of msg.value to the ProtocolRewards contract
 - 10000 - creatorRateBps of the remaining amount to treasury
 - entropyRateBps of the remaining amount to creatorsAddress in ETH directly
 - Remaining ETH is used to calculate ERC20 tokens to mint to creatorsAddress. (This is the ETH that is permanently locked)

2. Let's walk through an example. Let's say we want to buy 10 ETH worth of tokens through function buyToken().

creatorRateBps = 5000
entropyRateBps = 5000
Initial value = 10 ETH = 10 * (10 ^ 18)

**Protocol Rewards contract**

Protocol Rewards split 
= 2.5 % of 10 ETH
= 2.5 * (10 ^ 17) ETH

**Remaining ETH = Initial value - Protocol Rewards split = 9.75 * (10 ^ 18) ETH**

**Treasury**

Treasury split
 = (Remaining ETH * (10000 - 5000)) / 10000
 = 4.875 * (10 ^ 18) ETH

**Remaining ETH = Remaining ETH - Treasury split = 4.875 * (10 ^ 18) ETH**

**Creator Direct Payment**

Creator direct payment in ETH
 = (Remaining ETH * 5000) / 10000
 = 2.4375 * (10 ^ 18) ETH

**Remaining ETH = Remaining ETH - Creator direct payment = 2.4375 * (10 ^ 18) ETH**

This final amount of remaining ETH is used to calculate ERC20 tokens to mint to the creatorsAddress but after the calculation is done, the ETH is never withdrawn or able to be withdrawn, leaving it permanently locked in the ERC20TokenEmitter.sol contract.

In this case, the greater the creatorRateBps and smaller the entropyRateBps, the higher is the amount of ETH permanently locked in the contract.

**Note: All calculations done above are based on [1](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L165), [2](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L173), [3](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/ERC20TokenEmitter.sol#L177), [4](https://github.com/code-423n4/2023-12-r
The VerbsToken held by the Auction contract should be excluded in calculating the quorum votes
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#L228
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/AuctionHouse.sol#L313


# Vulnerability details

## Impact
The calculation for quorum votes will be incorrect in this case.

## Proof of Concept
Assuming the following scenario:

1. An ongoing auction where bidders are actively bidding for an NFT with 2 days remaining.
2. Simultaneously, an art piece is created. Let's consider ERC20 totalSupply as 1000 tokens and ERC721 totalSupply as 10.

Assume `erc721VotingTokenWeight` is set to 20. The voting weight is calculated as `erc20Balance + (erc721Balance * erc721VotingTokenWeight * 1e18)`, resulting in a value 'X' (e.g., `1000 + (10 * 20 * 1e18)`).

Now, this 'X' is used to calculate quorum votes with the formula `(quorumVotesBPS * newPiece.totalVotesSupply) / 10_000`. However, this approach is flawed. The ongoing auction holds a VerbToken each time, and these tokens are owned by the auction contract. Since the auction contract cannot execute the `vote` function in the CultureIndex contract, it disrupts the voting process. In the worst-case scenario, quorum votes might exceed the votes held by the voters. The actual voting weight should be calculated as  `erc20Balance + ((erc721Balance - 1) * erc721VotingTokenWeight * 1e18)`,

To address this issue, if an auction is in progress, the VerbTokens held by the Auction contract should be excluded when calculating quorum votes. This adjustment ensures a more accurate representation of voting power, preventing potential issues where quorum votes surpass the actual votes held by voters.
## Tools Used
Manual review
## Recommended Mitigation Steps
When the auction is ongoing, exclude the VerbTokens held by the Auction contract from the calculation of the  voting weight (`_calculateVoteWeight( erc20VotingToken.totalSupply(), erc721VotingToken.totalSupply() -1 );`).


## Assessed type

Timing
Immutable implementation addresses list will cause error when deploying RevolutionBuilder contract
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/src/builder/RevolutionBuilder.sol#L59-L111
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/main/packages/revolution/test/RevolutionBuilder.t.sol#L79-L96


# Vulnerability details

## Impact


## Proof of Concept
These several contracts ( Erc721Token,Descriptor, AuctionHouse,DAOExecutor,CultureIndex,ERC20TokenEmitter, MaxHeap)  's deployment constructor need `` `RevolutionBuilder` contract as immutable manager

``` solidity
/// @notice The contract upgrade manager
    IRevolutionBuilder private immutable manager;

constructor(address _manager) payable initializer {
        manager = IRevolutionBuilder(_manager);
   }
`

but in ``RevolutionBuilder` contract, its contructor need these several contract's implementation address.
the deployment of implementation address process have to  execute constructor method .

```solidity

 /// @notice The token implementation address
    address public immutable erc721TokenImpl;

    /// @notice The descriptor implementation address
    address public immutable descriptorImpl;

    /// @notice The auction house implementation address
    address public immutable auctionImpl;

    /// @notice The executor implementation address
    address public immutable executorImpl;

    /// @notice The dao implementation address
    address public immutable daoImpl;

    /// @notice The erc20TokenEmitter implementation address
    address public immutable erc20TokenEmitterImpl;

    /// @notice The cultureIndex implementation address
    address public immutable cultureIndexImpl;

    /// @notice The erc20Token implementation address
    address public immutable erc20TokenImpl;

    /// @notice The maxHeap implementation address
    address public immutable maxHeapImpl;


  constructor(
        address _erc721TokenImpl,
        address _descriptorImpl,
        address _auctionImpl,
        address _executorImpl,
        address _daoImpl,
        address _cultureIndexImpl,
        address _erc20TokenImpl,
        address _erc20TokenEmitterImpl,
        address _maxHeapImpl
    ) payable initializer {
        erc721TokenImpl = _erc721TokenImpl;
        descriptorImpl = _descriptorImpl;
        auctionImpl = _auctionImpl;
        executorImpl = _executorImpl;
        daoImpl = _daoImpl;
        cultureIndexImpl = _cultureIndexImpl;
        erc20TokenImpl = _erc20TokenImpl;
        erc20TokenEmitterImpl = _erc20TokenEmitterImpl;
        maxHeapImpl = _maxHeapImpl;
    }
```
and both them are set into  immutable state variable, so it results a dead lock .

Let us check the `RevolutionBuilder` test File

```solidity
 managerImpl0 = address(
            new RevolutionBuilder(
                address(0),
                address(0),
                address(0),
                address(0),
                address(0),
                address(0),
                address(0),
                address(0),
                addr