xanoutas/solidity-security-findings huggingface dataset:
A portion of ETH from buying governance token is lost
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/08ff070da420e95d7c7ddf9d068cbf54433101c4/packages/revolution/src/ERC20TokenEmitter.sol#L164-L177


# Vulnerability details

## Impact

Due to a flaw in the `ERC20TokenEmitter` contract, a portion of ETH used to buy governance tokens becomes irretrievably stuck in the contract. This represents a financial loss to the protocol and its participants.

## Proof of Concept

When purchasing tokens from the ERC20TokenEmitter contract, the incoming ETH is allocated to protocol rewards, treasury, and creators

        // 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;

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/08ff070da420e95d7c7ddf9d068cbf54433101c4/packages/revolution/src/ERC20TokenEmitter.sol#L164-L177

However, when `entropyRateBps` is greater than zero, not all of the remaining ETH is transferred to creators. Only a fraction is sent according to the following formula, leaving the remainder trapped in the contract.

        uint256 creatorDirectPayment = ((msgValueRemaining - toPayTreasury) * entropyRateBps) / 10_000;

POC:

- Navigate to : `cd packages/revolution`
- Create a test file `test/EthStuck.t.sol` 
- Execute `forge test -vvvvv --match-path test/EthStuck.t.sol --match-test testEthStuckInContract`

```javascript
// SPDX-License-Identifier: MIT
pragma solidity 0.8.22;

import { Test } from "forge-std/Test.sol";
import {console} from "forge-std/console.sol";
import { AuctionHouseTest } from "./auction/AuctionHouse.t.sol";
import { IERC20TokenEmitter } from "../../src/interfaces/IERC20TokenEmitter.sol";

contract POCTest is AuctionHouseTest {
    function testEthStuckInContract() public {
        erc20TokenEmitter.setCreatorRateBps(1000);
        erc20TokenEmitter.setEntropyRateBps(3000);

        uint256 buyAmount = 10 ether;
        vm.deal(address(21), buyAmount);

        address[] memory recipients = new address[](1);
        recipients[0] = address(1);

        uint256[] memory bps = new uint256[](1);
        bps[0] = 10_000;
        vm.stopPrank();

        vm.prank(address(21));
        erc20TokenEmitter.buyToken{ value: buyAmount }(
            recipients,
            bps,
            IERC20TokenEmitter.ProtocolRewardAddresses({
                builder: address(0),
                purchaseReferral: address(0),
                deployer: address(0)
In `ERC20TokenEmitter:: buyToken` part of the received ETH is not send to the treasury and will be stuck in the contract forever
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

## Impact
When `buyingTokens`, part of the received ETH is not send to the treasury but remains in the `ERC20TokenEmitter` contract. Since this contract has no way to withdraw ETH from it, the ETH is lost forever


## Proof of Concept
When calling `ERC20TokenEmitter::buyToken` to buy new tokens, the `msg.value` of the transaction is used as the basis to determin how much tokens will be minted.

First, a total of 2,5% of fees are deducted for `builder_rewards`, `purchas_referral`, `deployer_reward` and `revolution_reward` and send to `protocolRewards`:

```
protocolRewards.depositRewards{ value: totalReward }(
   builderReferral,
   settings.builderReferralReward,
   purchaseReferral,
   settings.purchaseReferralReward,
   deployer,
   settings.deployerReward,
   revolutionRewardRecipient,
   settings.revolutionReward
);
``` 

The rest of the eth is divided into `toPayTreasury`, `creatorDirectPayment` and `partToBuyTokensForCreator` .

 `toPayTreasury`, is send to the treasury:
```
 //Deposit funds to treasury
 (bool success, ) = treasury.call{ value: toPayTreasury }(new bytes(0));
```

`creatorDirectPayment` is send to the creator:

```
//Transfer ETH to creators
(success, ) = creatorsAddress.call{ value: creatorDirectPayment }(new bytes(0));
```

The problem arises from the fact that `partToBuyTokensForCreator` is used to calculate the amount of tokens to mint to the creator but is never send to the treasury and therefore remains in the `ERC20TokenEmitter` contract. 

Since there is no function that can withdraw access ETH from the contract, the ETH is stuck and lost for ever.

Here is a POC to show this. It can be copied into `ERC20TokenEmitter.t.sol` and run there:

```
    function test_TokenEmitterBalance(uint256 creatorRateBps, uint256 entropyRateBps) public {
        // Assume valid rates
        vm.assume(creatorRateBps > 0 && creatorRateBps <= 10000 && entropyRateBps > 0 && entropyRateBps <= 10000);

        vm.startPrank(erc20TokenEmitter.owner());
        //set creatorRate and entropyRate
        erc20TokenEmitter.setCreatorRateBps(creatorRateBps);
        erc20TokenEmitter.setEntropyRateBps(entropyRateBps);
        vm.stopPrank();

        //expect tokenEmitter balance to start out at 0
        assertEq(address(erc20TokenEmitter).balance, 0, "Balance should start at 0");

        address[] memory recipients = new address[](1);
        recipients[0] = address(1);

        uint256[] memory bps = new uint256[](1);
        bps[0] = 10_000;

        erc20TokenEmitter.buyToken{ value: 1 ether }(
            recipients,
            bps,
            IERC20TokenEmitter.ProtocolRewardAddresses({
                builder: address(0),
                purchaseReferral: address(0),
                deployer: address(0)
            })
        );
The ````initialOwner```` of many contracts are wrongly set
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The ````initialOwner```` of many contracts is set to ````dao````, but the dao contract (````VerbsDAOLogicV1````) can not call these contracts directly, which cause all privileged functions marked as ````onlyOwner```` can never be called.

## Proof of Concept
As shown by the following code snippet, The ````initialOwner```` of many contracts is set to ````dao```` during deployment.
```solidity
File: src\builder\RevolutionBuilder.sol
139:     function deploy(
..
148:     ) external returns (DAOAddresses memory) {
149:         require(_initialOwner != address(0), "Initial owner cannot be 0x0");
...
179:         // Initialize each instance with the provided settings
180:         IMaxHeap(daoAddressesByToken[erc721Token].maxHeap).initialize({
181:             initialOwner: daoAddressesByToken[erc721Token].dao,
...
183:         });
184: 
185:         IVerbsToken(erc721Token).initialize({
...
188:             initialOwner: daoAddressesByToken[erc721Token].dao,
...
191:         });
192: 
193:         IDescriptor(daoAddressesByToken[erc721Token].descriptor).initialize({
194:             initialOwner: daoAddressesByToken[erc721Token].dao,
...
196:         });
197: 
198:         ICultureIndex(daoAddressesByToken[erc721Token].cultureIndex).initialize({
...
201:             initialOwner: daoAddressesByToken[erc721Token].dao,
...
205:         });
206: 
207:         IAuctionHouse(daoAddressesByToken[erc721Token].auction).initialize({
...
210:             initialOwner: daoAddressesByToken[erc721Token].dao,
...
213:         });
...
219: 
220:         IERC20TokenEmitter(daoAddressesByToken[erc721Token].erc20TokenEmitter).initialize({
...
222:             initialOwner: daoAddressesByToken[erc721Token].dao,
...
226:         });
...
253:     }
```
But, we look at the ````dao```` contract, these management calls from DAO should via ````DAOExecutor/timelock```` contract. The ````msg.sender```` is ````DAOExecutor```` rather than ````dao```` while calling ````onlyOwner```` functions of the above contracts. Hence, all those calls would revert.
```solidity
File: src\governance\VerbsDAOLogicV1.sol
380:     function execute(uint256 proposalId) external {
...
387:         for (uint256 i = 0; i < proposal.targets.length; i++) {
388:             timelock.executeTransaction( // @audit management via DAOExecutor
389:                 proposal.targets[i],
390:                 proposal.values[i],
391:                 proposal.signatures[i],
392:                 proposal.calldatas[i],
393:                 proposal.eta
394:             );
395:         }
...
397:     }

```

## Tools Used
Manually review

## Recommended Mitigation Steps
Set  ````DAOExecutor```` as owner.


## Assessed type

Access Control
VRGDAC#yToX returns a negative number when a small amount is passed in.
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#L259


# Vulnerability details

## Impact
A malicious user pays a small amount of eth, obtains a large number of tokens.

## Proof of Concept

The protocol calculates the number of tokens that can be obtained using the `VRGDAC#yToX` function.

ERC20TokenEmitter#buyToken -> getTokenQuoteForEther -> vrgdac.yToX -> _mint

```solidity
    function getTokenQuoteForEther(uint256 etherAmount) public view returns (int gainedX) {
        require(etherAmount > 0, "Ether amount must be greater than 0");
        // Note: By using toDaysWadUnsafe(block.timestamp - startTime) we are establishing that 1 "unit of time" is 1 day.
        // solhint-disable-next-line not-rely-on-time
        return
            vrgdac.yToX({
                timeSinceStart: toDaysWadUnsafe(block.timestamp - startTime),
                sold: emittedTokenWad,
                amount: int(etherAmount)
            });
    }
```

The amount passed in the `yToX` function is calculated based on the amount of eth paid by the caller.

The problem is that the `VRGDAC#yToX` function returns a negative number if the amount passed in is small, negative numbers are converted to a large uint256 value ,so `ERC20TokenEmitter#buyToken` mint a large number of tokens while the caller pays very little eth.

Here is the test code:
Initialize the VRGDAC using the parameters in the test code.

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

Setting amount to 1e6 will result in an exception return value:

```solidity
    function testVRGDAC() public {
        VRGDAC vrgdac = new VRGDAC(1 ether, 1e18 / 10, 1_000 * 1e18);
        int256 x = vrgdac.yToX({
            timeSinceStart: toDaysWadUnsafe(86400 * 2),
            sold: 1 ether,
            amount: 1e6
        });

        console.log(uint256(x));
        console.log(uint256(x) / 1e18);
    }
```

Run the `forge test -vvvv`, console to output:

```
Logs:
  115792089237316195423570985008687907853269984665640564039457584007913125150936
  115792089237316195423570985008687907853269984665640564039457

Traces:
  [558631] CounterTest::testVRGDAC() 
    ├─ [517512] → new VRGDAC@0x2e234DAe75C793f67A35089C9d99245E1C58470b
    │   └─ ← 2578 bytes of code
    ├─ [4366] VRGDAC::yToX(2000000000000000000, 1000000000000000000, 1000000) [staticcall]
    │   └─ ← -4489000
    ├─ [0] console::f5b1bba9(ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbb80d8) [staticcall]
    │   └─ ← ()
    ├─ [0] console::f5b1bba9(0000000000000012725dd1d243aba0e75fe645cc4873f9e65afe688c928e1f21) [staticcall]
    │   └─ ← ()
    └─ ← ()
```

As you can see from the above output, `yToX` returns the negative number -4489000, and in the `ERC20TokenEmitter#buyToken` function converts the return value of `yToX` to uint256, thus mint a large number of tokens.

```solidity
    function buyT
buyToken() makes wrong calculations and some Ether will stuck 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#L152-L230


# Vulnerability details

## Impact
When buyToken(), the msg.value can be split into 4 ways. 
1). Go to protocolRewardsRecipients by _handleRewardsAndGetValueToSend()
2). Be used to mint ERC20 to buyer (and go to treasury)
3). Be used to mint ERC20 to creators (and go to treasury)
4). Directly go to creators
However, the function fails to do the third point, it only mint ERC20 token to creators and do not pay that amount to treasury, as a result, that amount will be stuck in the contract forever.

## Proof of Concept
Assuming the buyer pays msg.value, the first part will go to protocolRewardsRecipients by _handleRewardsAndGetValueToSend().
```
uint256 msgValueRemaining = _handleRewardsAndGetValueToSend(
            msg.value,
            protocolRewardsRecipients.builder,
            protocolRewardsRecipients.purchaseReferral,
            protocolRewardsRecipients.deployer
        );
```

The second part of msg.value will go to treasury 
```
(bool success, ) = treasury.call{ value: toPayTreasury }(new bytes(0));
        require(success, "Transfer failed.");
```

The third pard go to the creators (amount paid directly to creators)
```
if (creatorDirectPayment > 0) {
            (success, ) = creatorsAddress.call{ value: creatorDirectPayment }(new bytes(0));
            require(success, "Transfer failed.");
        }
```

As we can see (msgValueRemaining - toPayTreasury - creatorDirectPayment) amount of Ether will stuck in the contract. This is because buyToken() only mint the ERC20 token for creators but forget to transfer that amount of Ether to treasury. Basically, when minting the token, treasury should receive all the Ether. In this case, we can see it in this way, the user pays the Ether to treasury for all ERC20 tokens, part of the tokens are for himself and the other are for creators.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Transfer the Ether used to mint ERC20 tokens for creators to treasury.


## Assessed type

Other
Flash loans can affect voting
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#L313
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L233


# Vulnerability details

## Impact

Voting utilizes `_getPastVotes()` function which calculates the voting power based on `pieces[pieceId].creationBlock`. This means, that we can only vote with the power we had when the Piece was created (`pieces[pieceId].creationBlock`). This, however, still allows attacker to take a flash loan immediately when the Piece is being created and increase his/her voting power - which affect the whole voting mechanism.


## Proof of Concept


When Piece is being created, it sets its `creationBlock` to the current `block.number`.

[File: CultureIndex.sol](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L209)
```
function createPiece(
[...]
newPiece.creationBlock = block.number;
```

The attacker observes the mem-pool, sees that new Piece is being created and immediately takes a flash loan to increase his/her voting power. Then, he/she immediately votes.
It's important that the flash loan and `vote()` is within the same block as `createPiece()`, so that `block.number` will be the same for all these TXs.

Let's see how `vote()` will work in that scenario:

[File: CultureIndex.sol](https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/CultureIndex.sol#L307)
```
function _vote(uint256 pieceId, address voter) internal {
[...]
uint256 weight = _getPastVotes(voter, pieces[pieceId].creationBlock);
```

Since `pieces[pieceId].creationBlock` points to the same `block.number` when the attacker took a flash loan (`createPiece()`, flash loan,  `vote()` are within the same block), the attacker's voting power is calculated based on the flash loan he/she took. This leads to the voting manipulation - because the flash loan is used to increase the attacker's voting power.


## Tools Used

Manual code review

## Recommended Mitigation Steps

Calculate the voting power based on the previous block.

```
uint256 weight = _getPastVotes(voter, pieces[pieceId].creationBlock);
```

should be changed to:

```
uint256 weight = _getPastVotes(voter, pieces[pieceId].creationBlock - 1);
```


## Assessed type

Governance
Verbs tokens allow owner to delegate to address(0), which can result in the loss of votes and block future transfer
Audit: 2023-12-revolutionprotocol-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/base/VotesUpgradeable.sol#L164-L167
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/base/VotesUpgradeable.sol#L172-L175
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/base/VotesUpgradeable.sol#L180-L199
https://github.com/code-423n4/2023-12-revolutionprotocol/blob/d42cc62b873a1b2b44f57310f9d4bbfdd875e8d6/packages/revolution/src/base/VotesUpgradeable.sol#L233-L253


# Vulnerability details

## Impact
VerbsToken is a ERC721 token designed so that the owner can have votes and delegate their votes. Under current implementation owner can delegate the votes to address(0), however, in this way they will lose their votes and in the future when they want to transfer the token it will revert. 

## Proof of Concept
VerbsToken inherits from ERC721CheckpointableUpgradeable, and ERC721CheckpointableUpgradeable inherits from VotesUpgradeable. Within VotesUpgradeable, user can delegate votes by delegate() and delegateBySig(), both of these functions allow delegate to address(0) and will then call _delegate()

```
    function delegate(address delegatee) public virtual {
        address account = _msgSender();
        _delegate(account, delegatee);
    }
```

```
function delegateBySig(
        address delegatee,
        uint256 nonce,
        uint256 expiry,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) public virtual {
        if (block.timestamp > expiry) {
            revert VotesExpiredSignature(expiry);
        }
        address signer = ECDSA.recover(
            _hashTypedDataV4(keccak256(abi.encode(DELEGATION_TYPEHASH, delegatee, nonce, expiry))),
            v,
            r,
            s
        );
        _useCheckedNonce(signer, nonce);
        _delegate(signer, delegatee);
    }
```

Within _delegate(), it will just set the new delegates and move the votes:

```
function _delegate(address account, address delegatee) internal virtual {
        VotesStorage storage $ = _getVotesStorage();
        address oldDelegate = delegates(account);
        $._delegatee[account] = delegatee;

        emit DelegateChanged(account, oldDelegate, delegatee);
        _moveDelegateVotes(oldDelegate, delegatee, _getVotingUnits(account));
    }
```

Let's say Alice delegate all her votes to address(0), when she wants to delegate to other address it will revert, this is because in _delegate(), when it trying to get Alice's delegatee, if the delegatee is address(0) it will always return Alice's address:

```
function delegates(address account) public view virtual returns (address) {
        VotesStorage storage $ = _getVotesStorage();
        return $._delegatee[account] == address(0) ? account : $._delegatee[account];
    }
```

Since Alice's votes is zero now
Unallocated ETH Issue in ERC20TokenEmitter's buyToken Function
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


# Vulnerability details

## **Summary:**

The **`buyToken`** function in the ERC20TokenEmitter contract is designed to send out all ETH received in a transaction (confirmed by devs). However, a portion of the ETH is inadvertently left in the contract, which subsequently becomes locked as there is no mechanism to withdraw it.

## **Vulnerability Details:**

To understand this issue, let's examine the **`buyToken`** function, focusing on how ETH and tokens are distributed from a transaction.

### Step 1: Deduct protocol rewards

The function first allocates protocol rewards, which are 2.5% of the total transaction value.

- **_handleRewardsAndGetValueToSend** = 2.5% of Total (msg.value)

```solidity
function buyToken(
        ...
    ) public payable nonReentrant whenNotPaused returns (uint256 tokensSoldWad) {
        ...
        // Get value left after protocol rewards
        uint256 msgValueRemaining = _handleRewardsAndGetValueToSend(
            msg.value,
            protocolRewardsRecipients.builder,
            protocolRewardsRecipients.purchaseReferral,
            protocolRewardsRecipients.deployer
        );
        ...
    }
```

### Step 2: Calculate toPayTreasury pay

Next, the amount of ETH to pay the treasury is calculated based on **`creatorRateBps`**.

- msgValueRemaining = Total (msg.value) - _depositPurchaseRewards
- **toPayTreasury** = (msgValueRemaining * (10_000 - creatorRateBps)) / 10_000;

```jsx
function buyToken(
        ...
    ) public payable nonReentrant whenNotPaused returns (uint256 tokensSoldWad) {
        ...
        //Share of purchase amount to send to treasury
        uint256 toPayTreasury = (msgValueRemaining * (10_000 - creatorRateBps)) / 10_000;
        ...
    }
```

### Step 3: Calculate creator pay

The creator's share is then divided into ETH (creatorDirectPayment) and tokens. The ETH part is sent directly to the creator, while the token part is minted accordingly.

- **creatorDirectPayment** = ((msgValueRemaining - toPayTreasury) * entropyRateBps) / 10_000;
    - Amount of ETH creator address gets
- if ((msgValueRemaining - toPayTreasury) - creatorDirectPayment) > 0
    - getTokenQuoteForEther((msgValueRemaining - toPayTreasury) - creatorDirectPayment)
        - Amount of Tokens creator address gets

```solidity
function buyToken(
        ...
    ) public payable nonReentrant whenNotPaused returns (uint256 tokensSoldWad) {
        ...
        //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
        int256 totalTokensForCreators = ((msgValueRemaining - toPayTreasury) - creatorDirectPayment) > 0
            ? getTokenQuoteForEther((msgValueRemaining - toP