xanoutas/solidity-security-findings huggingface dataset:
Upgraded Q -> 2 from #339 [1726263420420]
Audit: 2024-08-phi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #339 as 2 risk. The relevant finding follows:

 Low - 1
Missing whenNotPaused modifier causes the protocol to not halt and continue the execution of functions which should stop incase of emergency.

Recommended Mitigation Steps
Below mentioned functions with contract they are in are missing whenNotPaused modifier : PhiFactory :: updateArtSettings(), claim(), batchClaim() PhiNFT1155 :: updateRoyalties(), safeTransferFrom(), safeBatchTransferFrom, ``
Upgraded Q -> 2 from #338 [1726263351284]
Audit: 2024-08-phi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #338 as 2 risk. The relevant finding follows:

 [L-05] Upgradeable contracts should have a __gap variable
Overview
The upgradeable contracts within the system, specifically Cred.sol, PhiFactory.sol, and PhiNFT1155.sol, lack a __gap variable. This omission could potentially lead to storage layout incompatibilities in future upgrades.

Description
In upgradeable contracts, the __gap variable serves as a component for maintaining storage layout compatibility across different versions. It reserves storage slots that can be used to add new state variables in future upgrades without affecting the storage layout of existing variables. Without this gap, adding new state variables in upgraded versions could potentially overwrite or corrupt existing data.

Code Location
This issue affects the following upgradeable contracts:

Cred.sol - https://github.com/code-423n4/2024-08-phi/blob/main/src/Cred.sol#L19-L19
PhiFactory.sol - https://github.com/code-423n4/2024-08-phi/blob/main/src/PhiFactory.sol#L22-L22
PhiNFT1155.sol - https://github.com/code-423n4/2024-08-phi/blob/main/src/art/PhiNFT1155.sol#L21-L21
Impact
The absence of a __gap variable in these upgradeable contracts could lead to several potential issues:

Difficulty in adding new state variables: Future upgrades that require new state variables may be impossible without breaking storage compatibility.
Risk of data corruption: If upgrades are attempted without proper precautions, existing data could be overwritten or corrupted.
Limited flexibility: The lack of reserved storage slots reduces the contract’s ability to evolve over time.
Increased upgrade complexity: Each upgrade would require careful manual management of the storage layout to avoid conflicts.
Recommended Mitigations
Add a __gap variable to each upgradeable contract. This should be a uint256 array with a size that leaves ample room for future additions.
Ensure that the __gap variable is placed at the end of the storage layout in each contract.
Example implementation:

contract UpgradeableContract is Initializable {
    // Existing state variables...

    // Reserved storage space to allow for new state variables in future upgrades.
    // The size of this array should be calculated carefully based on the contract's needs.
    uint256[50] private __gap;
}
Upgraded Q -> 2 from #331 [1726262867235]
Audit: 2024-08-phi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #331 as 2 risk. The relevant finding follows:

 L-04: protocolFeeDestination is not synchronized between PhiNFT1155 and PhiFactory
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L43C20-L43C42

The protocolFeeDestination is set once in the initialize function of the PhiNFT1155. However, it is possible for this value to change on the PhiFactory via the setProtocolFeeDestination function. This does not align with the way the artCreateFee is synchronized between the two contracts, due to the fact it is fetched every time here https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L142.

Recommendation
Pass in the protocolFeeDestination as a function parameter, or otherwise this variable can be entirely removed from the PhiNFT1155 and the logic for sending funds to the protocolFeeDestination can be moved to the PhiFactory. The latter approach may be preferrable as it simplifies the contract design.

See the markdown file with the details of this report here.
Upgraded Q -> 2 from #331 [1726262860659]
Audit: 2024-08-phi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #331 as 2 risk. The relevant finding follows:

 L-03: PhiNFT1155 createArtFromFactory sends refund to PhiFactory not to the user
The createArtFromFactory method sends a refund back the msg.sender (i.e. the PhiFactory) rather than the account/user which is creating the art.

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L152

This means the user may end up losing funds to the PhiFactory. These funds are then only withdrawable to the protocolFeeDestination.

Recommendation
Consider moving the refund logic back to the PhiFactory. From the context of the PhiFactory the msg.sender is the correct address to send the refund to.
Upgraded Q -> 2 from #328 [1726261890506]
Audit: 2024-08-phi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #328 as 2 risk. The relevant finding follows:

 [L-01] PhiNFT1155 attempts to refund excess ether sent during art creation but mistakenly sends it to the PhiFactory contract rather than the actual caller, causing the function to revert
Link

If the user sends too large an artFee when creating art the PhiNFT1155 contract attempts to refund the excess:

        if ((msg.value - artFee) > 0) {
            _msgSender().safeTransferETH(msg.value - artFee);
        }
However in this case _msgSender() will be the PhiFactory contract, which does not have any method to receive this ether and therefore the function call will revert.

This is shown in the following test added to PhiFactory.t.sol here:

    function test_Toad_createArtRefundFail() public {
        bytes memory credData = abi.encode(1, owner, "SIGNATURE", 31_337, bytes32(0));
        bytes memory signCreateData = abi.encode(expiresIn, IMAGE_URL, 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);

        // Try createArt sending wrong NFT_ART_CREATE_FEE will revert because PhiNFT1155 tries to send the refund to the PhiFactory which does not have a receive function rather than the actual caller
        vm.expectRevert();
        phiFactory.createArt{ value: NFT_ART_CREATE_FEE + 1 }(signCreateData, abi.encodePacked(cr, cs), config);
Upgraded Q -> 2 from #23 [1726245557261]
Audit: 2024-08-phi-findings
Severity: MEDIUM

Judge has assessed an item in Issue #23 as 2 risk. The relevant finding follows:

 [L-3] Cooldown Exploit Allows Blocking of Share Sales
Relevant GitHub Links:
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L186

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L588

Vulnerability Details:
The buyShareCredFor function in the Cred contract allows users to buy shares on behalf of another account. During this process, the internal _handleTrade function is called, which resets the lastTradeTimestamp to the current block.timestamp after a buy and enforces a 10-minute cooldown period before the user can sell shares.

    function _handleTrade(
        uint256 credId_,
        uint256 amount_,
        bool isBuy,
        address curator_,
        uint256 priceLimit
    )
        internal
        whenNotPaused
    {
        ...
        if (isBuy) {
            if (priceLimit != 0 && price + protocolFee + creatorFee > priceLimit) revert PriceExceedsLimit();
            if (supply + amount_ > MAX_SUPPLY) {
                revert MaxSupplyReached();
            }

            if (msg.value < price + protocolFee + creatorFee) {
                revert InsufficientPayment();
            }
        } else {
            if (priceLimit != 0 && price - protocolFee - creatorFee < priceLimit) revert PriceBelowLimit();
            if (block.timestamp <= lastTradeTimestamp[credId_][curator_] + SHARE_LOCK_PERIOD) {
                revert ShareLockPeriodNotPassed(
                    block.timestamp, lastTradeTimestamp[credId_][curator_] + SHARE_LOCK_PERIOD
                );
            }
            (, uint256 nums) = shareBalance[credId_].tryGet(curator_);
            if (nums < amount_) {
                revert InsufficientShares();
            }
        }

        _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 user can exploit this functionality by buying a single share for another user, maliciously resetting their cooldown period. This effectively blocks the targeted user from selling their shares for the duration of the enforced cooldown, disrupting their trading ability.

Tools Used:
Manual analysis
Recommendation:
While this should not pose a significant issue given the short 10-minute cooldown period, the protocol should be aware of the potential for exploitation.
buyShareCredFor Function Vulnerable to Griefing and Front-Running Attacks
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact

This can be exploited to:
1. An attacker repeatedly buys small amounts of shares for a target user, constantly resetting their SHARE_LOCK_PERIOD and preventing them from selling.
2. Just before a large sell order is about to be executed, a front-runner buys a minimal amount of shares for the seller, forcing them to wait and potentially miss out on favorable market conditions.

The impact of these attacks can be significant as users may be unable to sell their shares at desired times, leading to potential financial losses.


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

Anyone can call for a curator `buyShareCredFor` resetting their trade timestamp
`lastTradeTimestamp[credId_][curator_] = block.timestamp;`

Resulting in actual curator failing to sell on desired time: 
`
if (block.timestamp <= lastTradeTimestamp[credId][curator] + SHARE_LOCK_PERIOD) {
    revert ShareLockPeriodNotPassed(
        block.timestamp, lastTradeTimestamp[credId][curator] + SHARE_LOCK_PERIOD
    );
}
` 

## Tools Used
Manual Review
## Recommended Mitigation Steps

Implement separate rate control for `buyShareCredFor`, where other users can only trade with larger cooldown period
Implement separate lock periods for buying and selling as selling cannot be done by attackers.



## Assessed type

DoS
PhiNFT1155 contract derives from non upgradeable base contracts, does not provision for upgrade  
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-L30
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/abstract/CreatorRoyaltiesControl.sol#L9-L15


# Vulnerability details

## Impact
`PhiNFT1155` contract derives from `CreatorRoyaltiesControl` which does not following the upgradeable name space pattern for storage layout. Due to this, the storage variables in `CreatorRoyaltiesControl` line up right before the storage variables of `PhiNFT1155` contract.

As there is no gap declared in `CreatorRoyaltiesControl`, there is lack of provision for future upgrades for `CreatorRoyaltiesControl` contract.

## Proof of Concept
`CreatorRoyaltiesControl` contract does not provide for future upgrades as there is no gap declared. 

```solidity
   abstract contract CreatorRoyaltiesControl is ICreatorRoyaltiesControl {
    mapping(uint256 _tokenId => RoyaltyConfiguration _configuration) public royalties;
    uint256 private constant ROYALTY_BPS_TO_PERCENT = 10_000;
    address private royaltyRecipient;
    bool private initilaized;

```
And since  `PhiNFT1155` contract inherits `CreatorRoyaltiesControl`, the storage layout define does not allow for addition of new state level variables in `CreatorRoyaltiesControl` contract.

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


## Tools Used
Manual Review

## Recommended Mitigation Steps
a) update `CreatorRoyaltiesControl` to use name space storage.
   This is ensure the slot assigned for the contract is unique and does not conflict with storage layout of derived contract.

b) add gap to provision addition of new state variables in future.

```
  uint256[50] private __gap;
```






## Assessed type

Upgradable
Improper ETH Handling in Claim Function Leading to Potential ETH Accumulation
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
This vulnerability could lead to:

Unintended accumulation of ETH in the contract
Potential loss of funds for users who overpay

## Proof of Concept
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L299

The `claim` function is payable and accepts ETH.
It calculates `mintFee` using `getArtMintFee(artId, quantity_)`.
It then calls `this.merkleClaim` or `this.signatureClaim` with `value: mintFee`.

If actual `msg.value` is greater than `mintFee`, the excess ETH will remain in the contract.
There seems to be no mechanism in this function to refund or handle excess ETH. 

## Tools Used
Manual Review

## Recommended Mitigation Steps
Add a check to ensure msg.value exactly matches mintFee
Alternatively implement a refund mechanism for excess ETH


## Assessed type

ETH-Transfer
Creating Art will revert if Eth sent is greater than create fee
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L210
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L551-L568
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L620-L649
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L651-L671
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/art/PhiNFT1155.sol#L138-L156


# Vulnerability details

## Impact
Creating Art will revert if the amount of Eth sent to `PhiFactory::createArt(...)` is greater than create fee. The revert occurs because the excess eth is sent back to the `msg.sender` which happens to the `PhiFactory`. `PhiFactory` contract does not accept eth directly as it does not support `receive()` or `fallback()` functions.

## Proof of Concept
When `PhiFactory::createArt(...)` function call is made, it calls `createERC1155Internal(...)` function which checks if the credentials exists or not. Based on whether the credentials exists or not, the call flows to either `new` or `existing` NFT function.

```
   function createERC1155Internal(uint256 newArtId, ERC1155Data memory createData_) internal returns (address) {
        ...suppressed code
        address artAddress;
        if (credNFTContracts[credChainId][credId] == address(0)) {
            artAddress = _createNewNFTContract(currentArt, newArtId, createData_, credId, credChainId, verificationType);
        } else {
            artAddress = _useExistingNFTContract(currentArt, newArtId, createData_, credId, credChainId);
        }
  
        return artAddress;
    }
```

Both `new` or `existing` routes the call to `PhiNFT1155::createArtFromFactory(...)` function which creates the new art work, transfers the fee applicable to the receiving address and return excess ether to the caller of this function. Also, returns the new  token id.

The problem is in the returning of Eth to the caller. As the caller is `PhiFactory` contract which does not necessary functions implemented to accept Ether, the call will revert resulting in Art work not being minted.

```
  function createArtFromFactory(uint256 artId_) external payable onlyPhiFactory whenNotPaused returns (uint256) {
        ... 
        if ((msg.value - artFee) > 0) {
 ==>           _msgSender().safeTransferETH(msg.value - artFee);
        }
        return createdTokenId;
    }
```
`_msgSender().safeTransferETH(...)` is the the reason for reverting. There are two problems here.

a) Excess Ether is being sent to `PhiFactory` contract which does not accept Ether, which is a technical problem

b) Excess Ether should be returned to the original caller of `PhiFactory::createArt(...)` function which is the bug in the logic.

### POC
In the `_createArt(...)`, pass excess Ether as below.

` phiFactory.createArt{ value: N
`SHARE_LOCK_PERIOD` affects existing shares
Audit: 2024-08-phi-findings
Severity: MEDIUM

# Lines of code

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


# Vulnerability details

## Impact
Users are forced to wait for the another cooldown period to elapse for `shares` that had passed their cooldown period already.
Plus given the fact that any user can perform `buy` trades for any other user, this can DoS the user on `sell` trades.

## Proof of Concept
During a `buy` trade, the users `shareBalance[credId_]` is increamented by `amount` in [`_updateCuratorShareBalance()`](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L674):
```solidity
        if (isBuy) {
            ---SNIP---
>>          shareBalance[credId_].set(sender_, currentNum + amount_);
        }
```
After this, `lastTradeTimestamp[credId_][curator_]` is updated to current timestamp:
```solidity
lastTradeTimestamp[credId_][curator_] = block.timestamp;
```

Then during [`sell` trade](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L625-L628), the system requires that a certain amount of time (`SHARE_LOCK_PERIOD`) must have passed since the last trade:
```solidity
>>          if (block.timestamp <= lastTradeTimestamp[credId_][curator_] + SHARE_LOCK_PERIOD) {
                revert ShareLockPeriodNotPassed(
                    block.timestamp, lastTradeTimestamp[credId_][curator_] + SHARE_LOCK_PERIOD
                );
            }
```
As seen the function will revert if this condition is not met.

However this design will also impact their `existing shares` which should not be affected.

**Scenario**
- Consider a case where a user already say `1000 shares` in the protocol. It has been `30 minutes` since he last purchased.
- He then makes another purchase worth `200 shares`.
- During this time, his `lastTradeTimestamp` for this `credId_` is set to `block.timestamp`
- When he then tries a sell trade worth `1000 shares`, this will be reverted when in reality, these `shares` were bought earlier.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider redesigning the unlock mechanism to only impact the `newly bought shares` instead of affecting all the users shares.









## Assessed type

Other
Poor implementation of the Openzeppelin `UUPSUpgradeable` library, which will cause storage collisions during the update.
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
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/PhiFactory.sol#L22
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L19


# Vulnerability details

## Impact
Storage collision in the first place. And this is highly likely to result in unpredictable behaviour, or even security vulnerabilities.

## Proof of Concept
One of the crucial points to bear in mind when implementing a proxy is the allocation of storage space before and after the update, in order to prevent storage conflicts.

https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps

The implementation of the `UUPSUpgradeable` library in the following contracts `PhiNFT1155.sol`, `PhiFactory.sol` and `Cred.sol` are wrong. In fact, no empty space has been reserved in case the contracts updated in the future need additional space to store new storage variables. So if, during the update, one or more variables were added to one or more contracts, this would overwrite the storage variables already existing in their slot, resulting in a conflict and thus a disruption of the logic put in place before the update.

So all smart contracts that inherit from `UUPSUpgradeable` are likely to cause a storage collision during the update.

## Tools Used
Manual review.

## Recommended Mitigation Steps
Add a storage _gap variable in all contract who inherited from UUPSUpgradeable.
NB: The _gap variable should be located after all other storage variables in the contract. Better to place it in last line of the contract before the closing braket.
for example, in `PhiNFT1155` contract do this:


```diff
contract PhiNFT1155 is
    Initializable,
    UUPSUpgradeable,
    ERC1155SupplyUpgradeable,
    ReentrancyGuardUpgradeable,
    PausableUpgradeable,
    Ownable2StepUpgradeable,
    IPhiNFT1155,
    Claimable,
    CreatorRoyaltiesControl
{
    /... all the contract code

+   uint256[50] private __gap;
}
```
Please see this doc for more details:
https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps





## Assessed type

Upgradable