xanoutas/solidity-security-findings huggingface dataset:
User can delegate the vote of a token multiple times
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L71-L89


# Vulnerability details

## Impact
In `VoteEscrow.delegate()` a token holder can delegate his voting power multiple times and to multiple token holders at the same time (including itself). This allows an attacker to get unlimited voting power, execute a governace attack and be able to change critical system parameters(for example change of `minter` in `GolomToken`).

## Proof of Concept
Call `VoteEscrow.delegate()` an arbitrary amount of times. No check is in place to prevent pushing many times `tokenId` into `delegatedTokenIds` ([VoteEscrowDelegation.sol#L80](https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L80)). Functions [`getVotes`](https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L168-L175) and [`getPriorVotes`](https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L185-L193) count the voting power of a delegatee (`tokenId`) adding up the voting power of all the tokens in the `delegatedTokenIds` array corresponding to the delegatee and given checkpoint, and inculding duplicates, like this:

```solidity
    function getVotes(uint256 tokenId) external view returns (uint256) {
        uint256[] memory delegated = _getCurrentDelegated(tokenId);
        uint256 votes = 0;
        for (uint256 index = 0; index < delegated.length; index++) {
            votes = votes + this.balanceOfNFT(delegated[index]);
        }
        return votes;
    }
```
So duplicate delegators are included in the voting power calculation and a very large voting power can be obtained by an attacker, who can create a proposal in the governance contract [`GovernorAlpha`](https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GovernerBravo.sol#L160) and make it pass calling [`castVote`](https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/governance/GovernerBravo.sol#L346), then `execute`  the proposal. The proposal could for example, after the timelock delay set, change the `minter` of `GolomToken`.

## Recommended Mitigation Steps

Add a check in the `delegate` function to prevent a delegator `tokenId` from delegating to more than one token at a time:

```solidity
    require(delegates[tokenId] == 0);
```

And allowing a new delegation only when the current delegation is removed, by adding to the function `removeDelegation`:

```solidity
    delegates[tokenId] = 0;
```
`removeDelegation` in VoteEscrowDelegation can be bypassed when transferring tokens
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L101-L104


# Vulnerability details

## Impact
In `_writeCheckpoint`, when the `oldCheckpoint` is accessed, it is stored in memory so any changes to that variable is not permanently stored. This means that if a user transfers a token to someone else, if a delegation is made in the same block then the delegations are not removed. This allows the new owner to essentially keep all the delegations (without the delegators consent) and use them for malicious purposes.

I believe this issue is medium severity (as it requires frontrunning) however can cause a severe breach in trust between delegators and the delegated.

## Proof of Concept
1. Bob transfers a token to alice (this can be mediated via a marketplace where alice bought the token off bob)
2. Before the transfer is made, alice frontruns the tx and delegates a random token to bob's token
3. As a token has been delegated in the same block that bob's token is being transferred in, the if statement:
```solidity
if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) {
```
   passes
4. The `delegatedIds` in storage is unchanged thereby allowing alice to keep all of bob's delegations

## Tools Used
VS Code
## Recommended Mitigation Steps
In `_withdrawCheckpoint` change
```solidity
        Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];
```
to
```solidity
        Checkpoint storage oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];
```
Previous delegation checkpoint is modified when creating a new one
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L79-L80
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L213-L214


# Vulnerability details

## Impact
Delegations are tracked using checkpoints stored in the contract's storage.

When `delegate()` and `removeDelegation()` are used to add a new checkpoint, the previous checkpoint is also modified because the modification is done on `Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1]` which is the storage of the previous checkpoint, **not** a temporary Checkpoint in memory.

By modifying the previous checkpoint, a user can add a delegation after a proposal has been submited and have the related delegation votes taken into account.

I have rated this finding as Medium because it allows voter fraud. The impact can be High on the governance contract, but it is out-of-scope.

Note: The whole delegation functionnality is currently unusable because the first checkpoint can never be inserted due to an overflow when getting `oldCheckpoint` in `_writeCheckpoint()`. A Low-impact finding is related to this bug. This finding assumes that this issue is fixed.

## Proof of Concept
* Have a veNFT token with at least one delegated token (so there is at least one related checkpoint)
* A voting proposal is submited
* Other veNFT tokens are delegated to the first one, using `delegate()`, but after the proposal has been submited
* The `getPriorVotes()` function, called with the proposal timestamp will also return the delegated votes, although they have been delegated after.

## Recommended Mitigation Steps
Use a temporary array stored in memory or modify `delegatedTokenIds` after the new checkpoint has been inserted.
A user can delegate his token's votes to several tokens
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L71-L89


# Vulnerability details

## Impact
In the `delegate` function in VoteEscrowDelegation.sol, there is no verification that a token has been already delegated to an other token.

A malicious user can create several `veNFT` tokens by splitting and locking his GOL tokens. By delegating each token to the other ones, he can increase his voting power and make a (malicious) proposal to be accepted.

I have rated this finding as Medium because the governance contract is out-of-scope. But voter fraud can be used to gain ownership, update contract settings, transfer available funds, ... This can be High if the impact on the governance contract can be used.

Note: The whole delegation functionnality is currently unusable because the first checkpoint can never be inserted due to an overflow when getting `oldCheckpoint` in `_writeCheckpoint()`. A Low-impact finding is related to this bug. This finding assumes that this issue is fixed.

## Proof of Concept
* A malicious user has 1000 GOL tokens
* He lock all of them, using 10 `create_lock()` calls, with 100 GOL tokens each time.
* He now owns 10 veNFT tokens
* He delegate each veNFT token to the 10 veNFT tokens, (100 `delegate()` calls necessary)
* He now have 10 times more voting power than he should normally have

Update the number of veNFT tokens to create to have as much voting power as necessary to make a proposal be accepted.

## Recommended Mitigation Steps
A check must be added to prevent a token to be delegated several times, to different tokens.
Wrong implementation of function `_transferFrom()` in `VoteEscrowDelegation.sol`
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L242


# Vulnerability details

## Impact
User can't transfer/transferFrom NFT 

 
## Proof of concept 
Function `transfer()` and `transferFrom()` will execute internal function `_transferFrom()` in contract `VoteEscrowDelegation.sol`. 
Function `_transferFrom()` use [command](https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L242) 
```solidity=
this.removeDelegation(_tokenId);
```
to remove the delegation. But use `this.` will let the function execute a external call to itself. That will make the [requirement](https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L211) 
```solidity=
require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');
```
failed, cause the current `msg.sender` now is its address (not the origin sender and not the owner of nft). 

## Tools Used
Manual review 
  
## Recommended Mitigation Steps
delete `this.` to make a internal call. 
```solidity=
// this.removeDelegation(_tokenId);
removeDelegation(_tokenId);
```
To much protocol fee is being withheld in _settleBalances() which makes the taker of a bid receive less than expected ETH
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L381
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L390
https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L397


# Vulnerability details

## Impact
When an order for multiple ERC115 NFTs is filled via either `fillBid()` or `fillCriteriaBid()` with an `amount` >1 the calculation of the `protocolfee` is first calculated to be 5% of the price of each NFT multiplied by the amount of NFTs to be filled specified in the call.
Then however when the total amount to be sent to the seller of the NFT is calculated the `protocolfee` is again multiplied by the `amount` as calculation to be deducted from what is to be sent to the seller.
This means the seller of the NFT isn't getting the correct amount and the difference is stuck in the contract.

## Proof of Concept
`protocolfee` is calculated on line [381](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L381) to already take into account the ammount of ERC1155 tokens being exchanged.
Then at line [390](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L390) and [397](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L397) the `protocolfee` is being deducted from `order.totalAmt` (which is the amount of ETH per NFT) and then multiplied again by the amount of tokens being exchanged. 
This means the taker is getting less than expected and this ETH is then stuck in the contract as only the correct amount is being sent (on line [384](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L384)) to the distributor.

## Tools Used
Manual detection

## Recommended Mitigation Steps
Perform the correct calculation at line [381](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L381) but be sure to then multiply the `protocolfee` by the amount of NFTs transfered to pay the correct amount of ETEH to the distributor in line [384](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/core/GolomTrader.sol#L384).
VoteEscrowDelegation: delegate reverts
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L71-L109


# Vulnerability details

## Impact

MED - Function could be impacted

The delegate function does not work properly, as the result, vote functionality is impacted

## Proof of Concept

- [Proof Of concept: the delegate reverts](https://gist.github.com/zzzitron/6f950a268d179218cadef74d7acdeeb4#file-2022-07-pocvoteescrowdelegation-specs-ts-diff-L142-L165)

1) The delegate reverts (The above proof of concept)
In the `VoteEscrowDelegation`, the `delegate` function calls `_writeCheckpoint` even when `numCheckpoints` is zero. When `numCheckpoints` is zero, `_writeCheckpoint` tries to access a array element of minus one (line 101), which results to revert with underflow panic. Since the first attempt to delegate fails, nobody can delegate any votes to anybody.

The `getVotes` will only count the delegated votes (not the balance of its own), nobody has any vote.

2) (Possibly) one can delegate the smae tokenId multiple times 
I was testing the `delegate` function for the possibility of multiple delegation, but since the delegation function itself does not work, multiple delegation was not possible to be tested. The vote was counted based on the `checkpoints` and it does not check the `delegates`. So, one can `delegate` multiple times and the same `tokenId` can be pushed to the `delegatedTokenIds` and it will be counted as a valid vote for multiple times.

```solidity
// VoteEscrowDelegation: delegate and _writeCheckpoint

 71     function delegate(uint256 tokenId, uint256 toTokenId) external {
 72         require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');
 73         require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power');
 74
 75         delegates[tokenId] = toTokenId;
 76         uint256 nCheckpoints = numCheckpoints[toTokenId];
 77
 78         if (nCheckpoints > 0) {
 79             Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1];
 80             checkpoint.delegatedTokenIds.push(tokenId);
 81             _writeCheckpoint(toTokenId, nCheckpoints, checkpoint.delegatedTokenIds);
 82         } else {
 83             uint256[] memory array = new uint256[](1);
 84             array[0] = tokenId;
 85             _writeCheckpoint(toTokenId, nCheckpoints, array);
 86         }
 87
 88         emit DelegateChanged(tokenId, toTokenId, msg.sender);
 89     }
 90
 91     /**
 92      * @notice Writes the checkpoint to store current NFTs in the specific block
 93      */
 94     function _writeCheckpoint(
 95         uint256 toTokenId,
 96         uint256 nCheckpoints,
 97         uint256[] memory _delegatedTokenIds
 98     ) internal {
 99         require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');
100
101         Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1
Arithmetic underflow prevents any votes from being delegated
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L101


# Vulnerability details

## Impact
In `_writeCheckpoint` in VoteEscrowDelegate, the contract tries to access the previous checkpoint at index `nCheckpoints - 1`. However, initially, when a user is delegated to this value is `0` thereby creating an arithmetic underflow and reverts the transaction. This prevents anyone from being delegated and because GovernerBravo.sol only counts delegated votes (and does not include the token vote itself), it would be impossible for anyone to create or vote on proposals. 

## Proof of Concept
1. A user with tokenId `1` delegates votes to token `2` by calling `delegate()`
2. As `nCheckpoints == 0`, a new dynamic array is created and passed to `_writeCheckpoints`
3. The contract performs the calculation `nCheckpoints - 1` which reverts with an arithmetic underflow

## Tools Used
VS Code
## Recommended Mitigation Steps
Change `_writeCheckpoint` from
```solidity
    function _writeCheckpoint(
        uint256 toTokenId,
        uint256 nCheckpoints,
        uint256[] memory _delegatedTokenIds
    ) internal {
        require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');


        Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];


        if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) {
            oldCheckpoint.delegatedTokenIds = _delegatedTokenIds;
        } else {
            checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds);
            numCheckpoints[toTokenId] = nCheckpoints + 1;
        }
    }
```
to
```solidity
    function _writeCheckpoint(
        uint256 toTokenId,
        uint256 nCheckpoints,
        uint256[] memory _delegatedTokenIds
    ) internal {
        require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');


        if (nCheckpoints > 0) {
            Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];
            if (oldCheckpoint.fromBlock == block.number) {
                    oldCheckpoint.delegatedTokenIds = _delegatedTokenIds;
            } else {
                checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds);
                numCheckpoints[toTokenId] = nCheckpoints + 1;
              }
            
        } else {
            checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds);
            numCheckpoints[toTokenId] = nCheckpoints + 1;
        }
    }
```
GolomTrader: Bad accounting for fillCriteriaBid and fillBid
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L375-L403


# Vulnerability details

## Impact

HIGH - assets can be lost directly.

When `fillCriterialBid` and `fillBid` are called for multiple NFTs, the taker of the bid will get less ether then they should get, and the ether will be lost in the GolomTrader.

## Proof of Concept

The `_settleBalances` is counting the protocolfee multiple times. In the line 381, the `protocolfee` is counted for the `amount` of NFTs. However upon subtracting the fees from the `totalAmt`, the `protocolfee` is again multiplied (line 390, 397). As the result the taker of the deal will get paid less ether then they should be. The ether the taker should get will be lost in the `GolomTrader` contract as there is no method to rescue the fund.

For example, the `totalAmt` is 10 ether and `amount` is 10, the `protocolfee` is 0.5 ether. For simplicity let's say other fees are zero. The taker of the bid, then, should get 99.5 (= 100 - 0.5). However, the taker will only get 95 ((10 - 0.5) * 10). The difference will be lost in the `GolomTrader` contract.

```solidity
// https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L375-L403
// GolomTrader::_settleBalances
// used in fillCriteriaBid and fillBid
// the protocolfee already counts for amount
// but again multiplied in the payEther (line 390, 397)

381         uint256 protocolfee = ((o.totalAmt * 50) / 10000) * amount;
382         WETH.transferFrom(o.signer, address(this), o.totalAmt * amount);
383         WETH.withdraw(o.totalAmt * amount);
384         payEther(protocolfee, address(distributor));
385         payEther(o.exchange.paymentAmt * amount, o.exchange.paymentAddress);
386         payEther(o.prePayment.paymentAmt * amount, o.prePayment.paymentAddress);
387         if (o.refererrAmt > 0 && referrer != address(0)) {
388             payEther(o.refererrAmt * amount, referrer);
389             payEther(
390                 (o.totalAmt - protocolfee - o.exchange.paymentAmt - o.prePayment.paymentAmt - o.refererrAmt) *
391                     amount -
392                     p.paymentAmt,
393                 msg.sender
394             );
395         } else {
396             payEther(
397                 (o.totalAmt - protocolfee - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount - p.paymentAmt,
398                 msg.sender
399             );
400         }
```

## Tools Used

None

## Recommended Mitigation Steps

Subtract protocolfee only once.

```solidity
// GolomTrader::_settleBalances
// mitigation suggestion

387         if (o.refererrAmt > 0 && referrer != address(0)) {
388             payEther(o.refererrAmt * amount, referrer);
389             payEther(
390                 (o.totalAmt - o.exchange.paymentAmt - o.prePayment.paymentAmt - o.refererrAmt) *
391                     amount - protocolfee -
39
`VoteEscrowDelegation.removeDelegation` doesn't check `nCheckpoints` against 0
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L213
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L242


# Vulnerability details

## Impact

if `nCheckpoints` is 0, `VoteEscrowDelegation.removeDelegation` will revert since `checkpoints[tokenId][nCheckpoints - 1]` trigger underflow in solidity 0.8.11. `VoteEscrowDelegation._transferFrom` would call `removeDelegation`. Thus, `_transferFrom` also suffers from this issue. 

## Proof of Concept


if `nCheckpoints` is 0, `VoteEscrowDelegation.removeDelegation` will revert since `checkpoints[tokenId][nCheckpoints - 1]` trigger underflow in solidity 0.8.11.
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L213
```
    function removeDelegation(uint256 tokenId) external {
        require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');
        uint256 nCheckpoints = numCheckpoints[tokenId];
        Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1];
        removeElement(checkpoint.delegatedTokenIds, tokenId);
        _writeCheckpoint(tokenId, nCheckpoints, checkpoint.delegatedTokenIds);
    }
```

`VoteEscrowDelegation._transferFrom` would call `removeDelegation`. Thus, `_transferFrom` also suffers from this issue. 
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L242
```
    function _transferFrom(
        address _from,
        address _to,
        uint256 _tokenId,
        address _sender
    ) internal override {
        require(attachments[_tokenId] == 0 && !voted[_tokenId], 'attached');

        // remove the delegation
        this.removeDelegation(_tokenId);

        …
    }
```

## Tools Used

None

## Recommended Mitigation Steps

Fix `removeDelegation`

```
    function removeDelegation(uint256 tokenId) external {
        require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');
        uint256 nCheckpoints = numCheckpoints[tokenId];
        if (nCheckpoints > 0) {
            Checkpoint storage checkpoint = checkpoints[tokenId][nCheckpoints - 1];
            removeElement(checkpoint.delegatedTokenIds, tokenId);
            _writeCheckpoint(tokenId, nCheckpoints, checkpoint.delegatedTokenIds);
        }
    }
```