xanoutas/solidity-security-findings huggingface dataset:
Incorrect Calculation of Mint Price When Mint Count Equals Max Tokens Per Generation
Audit: 2024-07-traitforge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L227-L232
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L280-L309


# Vulnerability details

## Impact
The `_mintInternal` function does not correctly handle the case when the number of minted tokens in the current generation equals `maxTokensPerGen`. This can lead to incorrect mint price calculations when the generation is incremented. 
As a result, users might encounter errors such as 'Insufficient ETH send for minting.' due to mismatches between the expected and actual mint price.

## Proof of Concept
In the provided test code, the issue manifests as follows:

    describe('test', () => {
      it('test calculating mint price', async () => {
        const maxTokensPerGen = await nft.maxTokensPerGen();
        const startPrice = await nft.startPrice();
        for(let i = 0; i < maxTokensPerGen; i++) {
          await nft.connect(owner).mintToken(merkleInfo.whitelist[0].proof, {
            value: ethers.parseEther('1'),
          });
        }

        console.log(await nft.calculateMintPrice());
        await expect(
          nft.connect(user1).mintToken(merkleInfo.whitelist[1].proof, {
            value: startPrice
          })
        ).to.be.revertedWith('Insufficient ETH send for minting.');
      });
    });

This test reveals that after minting the maximum number of tokens, the next mint attempt fails due to incorrect price calculation.

## Tools Used
Manual code review

## Recommended Mitigation Steps
This is the mitigated code.

    function _mintInternal(address to, uint256 mintPrice) internal {    // @audit-ok
      _tokenIds++;
      uint256 newItemId = _tokenIds;
      _mint(to, newItemId);
      uint256 entropyValue = entropyGenerator.getNextEntropy();

      tokenCreationTimestamps[newItemId] = block.timestamp;
      tokenEntropy[newItemId] = entropyValue;
      tokenGenerations[newItemId] = currentGeneration;
      generationMintCounts[currentGeneration]++;
      initialOwners[newItemId] = to;

      if (generationMintCounts[currentGeneration] >= maxTokensPerGen) {
        _incrementGeneration();
      }

      if (!airdropContract.airdropStarted()) {
        airdropContract.addUserAmount(to, entropyValue);
      }

      emit Minted(
        msg.sender,
        newItemId,
        currentGeneration,
        entropyValue,
        mintPrice
      );

      _distributeFunds(mintPrice);
    }


## Assessed type

Invalid Validation
Missing `pause/unpause` functionalities in contracts inheriting pausable
Audit: 2024-07-traitforge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/DevFund/DevFund.sol#L9
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntityForging/EntityForging.sol#L10
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntityTrading/EntityTrading.sol#L11
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/EntropyGenerator/EntropyGenerator.sol#L9
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/NukeFund/NukeFund.sol#L11
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L14


# Vulnerability details

## Impact
The following contracts in scope: `DevFund`, `EntityForging`, `EntityTrading`, `EntropyGenerator`, `NukeFund`, `TraitForgeNft` are supposed to be pausable (as they all inherit from `Pausable`), but they don't implement the external pause/unpause functionalities which means it will never be possible to pause them.

The OpenZeppelin `Pausable` contract contains internal functions (`_pause` and `_unpause`) to manage the paused state of the contract. Any contract inheriting Pausable **MUST** implement external functions that call these internal functions to enable pausing and unpausing.

## Tools used
Manual Review

## Recommended Mitigation Steps
Add public/external pause and unpause functions in the mentioned contracts to enable their pausable functionality. This can be done as shown in the example below:
```solidity
function pause() external onlyOwner {
  _pause();
}

function unpause() external onlyOwner {
  _unpause();
}
```


## Assessed type

Context
The contracts can't be paused or unpaused 
Audit: 2024-07-traitforge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/#L1


# Vulnerability details


##  Description  +  Impact    


    


`` Pausable `` library that is used by the `` DevFund `` / ``EntityForging `` / `` EntityTrading `` / `` ... ``  have 0 `` external `` / `` public `` functions so the developer has to implement himself the entry points to `` pause `` and `` unpause `` the contract.


There is not way to block the access to the functions that use  `` whenNotPaused `` modifier.







##  PoC 






There is no need for a PoC because the issue is straightforward.



Check the `` Pausable `` contract used to verify that there are 0 `` external / public `` functions used to modify the ``_paused`` state variable to ``true`` and if you grep all the `` external `` / `` public `` from scope, there is not a single function that will execute `` Pausable :: _pause() `` or `` Pausable :: _unpause() ``








## Assessed type

Other
Incorrect Mint Price Calculation Leading to Potential Overpayment
Audit: 2024-07-traitforge-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L190
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L282


# Vulnerability details

## Impact

The vulnerability lies in the minting function of the contract, where the mint price is calculated based on the current generation. However, if the minting of the current generation reaches its maximum limit `(generationMintCounts[currentGeneration] >= maxTokensPerGen)`, the function proceeds to mint a new generation token without recalculating the mint price for the new generation. This discrepancy can cause users to overpay for their tokens, as they are charged the mint price for the previous generation while receiving a token from the new generation. 

## Proof of Concept

In this scenario, if the condition `generationMintCounts[currentGeneration] >= maxTokensPerGen` is true, the `_incrementGeneration()` function is called, which mints new generation token. However, the mintPrice is calculated for previous generation token, leading to a mismatch in the expected and actual mint prices.

```solidity
function mintToken( bytes32[] calldata proof)
    public
    payable
    whenNotPaused
    nonReentrant
    onlyWhitelisted(proof, keccak256(abi.encodePacked(msg.sender)))
  {
    //@audit price calculated before mint
    uint256 mintPrice = calculateMintPrice();
    require(msg.value >= mintPrice, 'Insufficient ETH send for minting.');
    _mintInternal(msg.sender, mintPrice);
```
```
  function _mintInternal(address to, uint256 mintPrice) internal {
    if (generationMintCounts[currentGeneration] >= maxTokensPerGen) {
      _incrementGeneration();
    }
```

https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L190
https://github.com/code-423n4/2024-07-traitforge/blob/279b2887e3d38bc219a05d332cbcb0655b2dc644/contracts/TraitForgeNft/TraitForgeNft.sol#L281

## Tools Used
Manual
## Recommended Mitigation Steps
Add a condition to the mint price calculation to check if the current generation limit is reached.


## Assessed type

Other
Malicious user can DoS another user's buying new tokens by transferring them dust from many coins due to usage of ownedCurvesTokenSubjects mapping array
Audit: 2024-01-curves-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-curves/blob/main/contracts/Curves.sol#L101
https://github.com/code-423n4/2024-01-curves/blob/main/contracts/Curves.sol#L328-L336


# Vulnerability details

## Impact
`_addOwnedCurvesTokenSubject` (and the underlying `mapping(address => address[]) private ownedCurvesTokenSubjects;`) can potentially DoS buys if an out of gas error is reached. This is because no addresses from the address[] are ever removed, even if the token balance is 0.

## Proof of Concept
_addOwnedCurvesTokenSubject is used in _buyCurvesToken, used in every buy transaction.
https://github.com/code-423n4/2024-01-curves/blob/main/contracts/Curves.sol#L263C14-L263C29

_transfer will add new tokens to this array
https://github.com/code-423n4/2024-01-curves/blob/main/contracts/Curves.sol#L318

If a malicious user wanted to spend the time, they could buy enough tokens to DoS another user's account by simply transferring dust to them, causing this array to grow too large.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Use OZ EnumerableSet and remove tokens from this set once the balance reaches 0 (eg: on sell)


## Assessed type

DoS
setCurves() function allows everyone to set curves address to FeeSplitter contract
Audit: 2024-01-curves-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-curves/blob/516aedb7b9a8d341d0d2666c23780d2bd8a9a600/contracts/FeeSplitter.sol#L35-L37


# Vulnerability details

## Summary
Attacker can drain whole ethers in ``FeeSplitter`` contract because ``setCurves()`` function in this contract doesnt have any access control so attacker can call this function and change ``Curves`` contract address to fake ``Curves`` contract that he created and drain whole fees.

## Vulnerability Details
``setCurves()`` function in ``FeeSplitter`` contract is used to set ``Curves`` contract address to able to interact with it and also ``balanceOf()`` function in ``FeeSplitter`` contract calls ``curvesTokenBalance()`` in ``Curves`` contract. attacker can create fake ``Cruves`` contract and manipulate ``curvesTokenBalance()`` function to return a massive amount for himself and then call ``setCurves()`` function and change the address to fake ``Curves`` contract that he created. then call ``claimFees()`` function to drain ethers, because ``claimFees()`` function calls ``updateFeeCredit()`` and ``getClaimableFees()`` functions, and both of these functions have calculations that increase return value. we can see these two functions have some calculations and multiply them to balance which is the attacker manipulated with fake ``Curves`` contract.

```solidity
function updateFeeCredit(address token, address account) internal {
        TokenData storage data = tokensData[token];
        uint256 balance = balanceOf(token, account);
        if (balance > 0) {
            uint256 owed = (data.cumulativeFeePerToken - data.userFeeOffset[account]) * balance;
            data.unclaimedFees[account] += owed / PRECISION;
            data.userFeeOffset[account] = data.cumulativeFeePerToken;
        }
    }
```
and

```solidity
    function getClaimableFees(address token, address account) public view returns (uint256) {
        TokenData storage data = tokensData[token];
        uint256 balance = balanceOf(token, account);
        uint256 owed = (data.cumulativeFeePerToken - data.userFeeOffset[account]) * balance;
        return (owed / PRECISION) + data.unclaimedFees[account];
    }
```


## Impact
attacker can drain ``FeeSplitter`` contract ethers by passing fake ``Curves`` contract address in ``setCurves()`` function.

## Proof of Concept
```solidity
function setCurves(Curves curves_) public {
        curves = curves_;
    }
```
```solidity
function balanceOf(address token, address account) public view returns (uint256) {
        return curves.curvesTokenBalance(token, account) * PRECISION;
    }
```

```solidity
function updateFeeCredit(address token, address account) internal {
        TokenData storage data = tokensData[token];
        uint256 balance = balanceOf(token, account);
        if (balance > 0) {
            uint256 owed = (data.cumulativeFeePerToken - data.userFeeOffset[account]) * balance;
            data.unclaimedFees[account] += owed / PRECISION;
            data.us
MEV; no slippage check on sell
Audit: 2024-01-curves-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-curves/blob/516aedb7b9a8d341d0d2666c23780d2bd8a9a600/contracts/Curves.sol#L282-L293


# Vulnerability details



## Impact
Standard MEV by anticipated a trade, especially on sell. Buy is implicitly protected by being limited by `msg.value`.

## Proof of Concept
When buying/selling tokens, there is no slippage protection in place. When buying the user must pay a sufficient amount and may thus use this as an implicit slippage check, which may very well be adequate. But selling offers no such means of protection. An attacker, being able to anticipate the victim's sell transaction, may sandwich it between a sell and a buy, and profit in the standard MEV fashion.

(I don't know how feasible frontrunning, which would be the standard way to anticipate the trade, is on the Form Network, e.g. if the sequencer is private. But some kind of frontrunning must always be considered a risk, since the transactions must be broadcast somewhere. In this particular case it is also conceivable that this could be achieved simply by streaming attack transactions around an approximate expected transaction time (e.g. the end of the presale) which only execute on the desired conditions.)

## Recommended Mitigation Steps
Add a slippage check on `sellCurvesToken()`.


## Assessed type

MEV
` FeeSplitter :: batchClaiming `   can cause Griefing/DOS attack
Audit: 2024-01-curves-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-curves/blob/516aedb7b9a8d341d0d2666c23780d2bd8a9a600/contracts/FeeSplitter.sol#L103


# Vulnerability details

## Impact
Griefing/DOS attack the is no limit for the `tokenList` , if the is to many token
the loop transaction can fail and revert
 
## Tools Used
manual 

## Recommended Mitigation Steps
have a maximum number `tokenList` for a loop not to go over


## Assessed type

DoS
The ownedCurvesTokenSubjects array length can be increased maliciously to stop conversion of internal Curve tokens to external tokens
Audit: 2024-01-curves-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-curves/blob/516aedb7b9a8d341d0d2666c23780d2bd8a9a600/contracts/Curves.sol#L328-L336
https://github.com/code-423n4/2024-01-curves/blob/516aedb7b9a8d341d0d2666c23780d2bd8a9a600/contracts/Curves.sol#L313-L325


# Vulnerability details

## Description
The `ownedCurvesTokenSubjects` is looped through to know if a token exists in the loop before it is added in the `_addOwnedCurvesTokenSubject` function.

    function _addOwnedCurvesTokenSubject(address owner_, address curvesTokenSubject) internal {
        address[] storage subjects = ownedCurvesTokenSubjects[owner_];
        for (uint256 i = 0; i < subjects.length; i++) {
            if (subjects[i] == curvesTokenSubject) {
                return;
            }
        }
        subjects.push(curvesTokenSubject);
    }

This function is called with `owner_` as the address of the Curves contract anytime a user wants to convert his internal Curve tokens to external Curve tokens. He calls the `withdraw` function which calls `_transfer` before calling `_addOwnedCurvesTokenSubject`.

The `ownedCurvesTokenSubjects[owner_]` array of the Curves contract can be increased maliciously by withdrawing many different Curve tokens. It can also grow over time. When it becomes too large, a lot of gas will be consumed to loop over it which can exceed the max gas a transaction can use on the Form network.

## Impact
Users will spend a lot of gas to convert their internal Curve tokens to external Curve tokens or won't be able to convert at all.


## Tools Used
Manual Analysis

## Recommended Mitigation Steps
Use Openzeppelin's EnumerableSet instead to implement the ownedCurvesTokenSubjects array. It can also be used for the `userTokens`  array in FeeSplitter.


## Assessed type

Loop
Selling 1 token at a time leads to less holders fee being collected
Audit: 2024-01-curves-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-curves/blob/main/contracts/FeeSplitter.sol#L90


# Vulnerability details

## Impact

**Disclaimer: I only started participating in this contest around 5 hours ago. It's coming to an end in 25 minutes, so I haven't been able to fully root cause this yet. I hope the judges understand :pray:**

I noticed that when `FeeSplitter#addFees()` is called through `Curves#_transferFees()`, it actually uses the token's total supply to determine how much holders fee to add:

```solidity
function addFees(address token) public payable onlyManager {
    uint256 totalSupply_ = totalSupply(token); // HERE
    if (totalSupply_ == 0) revert NoTokenHolders();
    TokenData storage data = tokensData[token];
    data.cumulativeFeePerToken += (msg.value * PRECISION) / totalSupply_;
}
```

Now, imagine two scenarios that should lead to the same amount of fees being collected. First, the assumptions:

- The total supply of the token is 121. 
- Alice owns 100 of these tokens.

Now, these two scenarios should both lead to the same amount of fees collected:

1. Alice sells all 100 tokens at once by calling `Curves#sellCurvesToken(subject, 100)`.
2. Alice sells 100 tokens, but 1 at a time through a smart contract in a loop.

From my testing, in the first case, the total holders fee accrued was 1.018 ETH (or well, native tokens).

In the second case, the total holders fee accrued was 0.37 ETH.

In both cases, Alice receives the same amount of ETH back for selling her tokens, which is 34.46 ETH.

Note that this may also apply to `_buyCurvesToken()`. I haven't been able to spend time understanding how `Curves#getPrice()` actually works, but it seems to be using the `totalSupply`. Since the supply is ever increasing for each token bought, I think it should calculate a lower fee for each subsequent token being bought. So that should be fixed as well.

Also a note, as you'll see in my POC, I left a re-entrancy test in there. I did that to showcase that re-entrancy does exist, although I cannot see a way to abuse this, as the final holders fee ends up being basically the same. 

***However, as I said, since there's no more time left in the contest for me, I do not doubt that there could be some potential re-entrancy attack vector that could be abused to steal tokens. Please go through every external function flow carefully to check for this prior to deployment.***

## Proof of Concept

The proof of concept only shows the `sellCurvesToken()` pathway, but it also very likely applies to `_buyCurvesToken()` as explained above.

I used the following contract to normal sell vs loop sell. Place it in `contracts/Test/MaliciousContract.sol`:

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;

interface ICurves {
    function buyCurvesToken(address curvesTokenSubject, uint256 amount) external payable;

    function sellCurvesToken(address curvesTokenSubject, uint256 amount) external;
}

contract MaliciousContract {
    uin
Loss of funds when using `batchClaiming` function
Audit: 2024-01-curves-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-curves/blob/516aedb7b9a8d341d0d2666c23780d2bd8a9a600/contracts/FeeSplitter.sol#L73-L78
https://github.com/code-423n4/2024-01-curves/blob/516aedb7b9a8d341d0d2666c23780d2bd8a9a600/contracts/FeeSplitter.sol#L103-L117


# Vulnerability details

## Impact
There is loss of funds when using `batchClaiming` and  function when any user sells all his CurveToken amount is 0

## Bug
`getClaimableFees` function returns 0 amount without accounting for the accumulated holderFees when any user's CurveToken amount is 0 and due to this `batchClaiming` function will result in holderFee loss.

## Proof of Concept
Here is the foundry [test file](https://gist.github.com/0x0bserve/ed75141187bea998aacffb3de5e3175a)

Here is the test with main logic of the exploit:
```javascript
    function testWrongClaimableFee() public {
        // Owner sets ExternalFeePercent
        vm.startPrank(owner);
        curves.setMaxFeePercent(5e16);
        curves.setExternalFeePercent(1e16, 1e16, 1e16);
        feeSplitter.setCurves(curves);
        vm.stopPrank();

        // Creator buys first CurveToken
        vm.prank(creator);
        curves.buyCurvesToken(creator, 1);

        // User 1 buys CurveToken
        vm.prank(user);
        curves.buyCurvesToken{value: 1 ether}(creator, 1);

        // Some transactions of CurveToken by some user 2
        vm.prank(user_2);
        curves.buyCurvesToken{value: 1 ether}(creator, 10);

        // If user sells his CurveToken, then his claimable holderFees becomes 0
        vm.startPrank(user);
        curves.sellCurvesToken(creator, 1);
        uint claimableHolderFees = feeSplitter.getClaimableFees(creator, user);
        assertEq(claimableHolderFees, 0);
    }
```

## Tools Used
Foundry


## Assessed type

Context
No slippage control on any sell (eg: Curves.sellCurvesToken) makes sells vulnerable to sandwich attacks
Audit: 2024-01-curves-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-01-curves/blob/main/contracts/Curves.sol#L282-L293


# Vulnerability details

## Impact
Users have no slippage control or protection on sells. Therefore, they cannot prevent a malicious user who owns the same token from selling a massive amount of tokens (tx1) before their sell tx executes (tx2), causing them to receive much less ETH than expected. The same malicious user can then buy their tokens back at a reduced cost (tx3) after the first two transactions execute within the same block.

## Proof of Concept
1. User A decides to sell some tokens and calls `sellCurvesToken` expecting a certain amount of ETH in return.
2. Malicious user M is monitoring the mempool for large sell transactions and submits a `sellCurvesToken` tx of their own with higher gas than user A.
3. M also submits a tx to buy back the token they sold in #2 at a reduced cost with lower gas than user A.
4. User A receives less ETH than expected after accounting for fees.

## Tools Used
Manual review

## Recommended Mitigation Steps
Along with the typical "run a private mempool" which may not be very practical, some more interesting options are:
- Add a minimum expected amount input param to the sell functions, reverting if the price is less than this expected amount
- Rework the buy and sell functions adding full slippage controls across the protocol
- Add a timelock to recent buys - something as simple as requiring 1 block between buy and sell transactions can prevent this scenario


## Assessed type

Other