xanoutas/solidity-security-findings huggingface dataset:
Incorrect Parameter in `collect_protocol_7540_F_A_9_F` Method of `lib.rs` Causes Token Transfer Failure when Collecting Protocol Fees
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/lib.rs#L1149-L1150
https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/wasm_erc20.rs#L158


# Vulnerability details

## Impact

The `collect_protocol_7540_F_A_9_F` method in the `Pools` struct of the `lib.rs` contract of the Seawater package is responsible for collecting protocol fees. It uses the `transfer_to_addr` function to transfer tokens to the recipient. However, the first parameter passed is the recipient address, and the second is the token address. In the `transfer_to_addr` function of the `wasm_erc20.rs` contract, the first parameter is expected to be the token address and the second the recipient address. Due to the incorrect parameter, the tokens will not be transferred correctly to the recipient, causing a failure in the fee collection process.

## Proof of Concept

The `collect_protocol_7540_F_A_9_F` method in the `Pools` struct of the `lib.rs` contract within the Seawater package is responsible for collecting protocol fees and transferring tokens to the recipient. It uses the `transfer_to_addr` function, where the first parameter is the recipient address and the second is the token address.
[pkg/seawater/src/lib.rs:collect_protocol_7540_F_A_9_F-L1149-L1150](https://github.com/code-423n4/2024-08-superposition/blob/4528c9d2dbe1550d2660dac903a8246076044905/pkg/seawater/src/lib.rs#L1149C1-L1150C78)

```rust
                #[allow(non_snake_case)]
                pub fn collect_protocol_7540_F_A_9_F(
                    &mut self,
                    pool: Address,
                    amount_0: u128,
                    amount_1: u128,
                    recipient: Address,
                ) -> Result<(u128, u128), Revert> {
                    assert_eq_or!(
                        msg::sender(),
                        self.seawater_admin.get(),
                        Error::SeawaterAdminOnly
                    );

                    let (token_0, token_1) = self
                        .pools
                        .setter(pool)
                        .collect_protocol(amount_0, amount_1)?;

1149 @audit=>       erc20::transfer_to_addr(recipient, pool, U256::from(token_0))?;
1150 @audit=>       erc20::transfer_to_addr(recipient, FUSDC_ADDR, U256::from(token_1))?;

                    #[cfg(feature = "log-events")]
                    evm::log(events::CollectProtocolFees {
                        pool,
                        to: recipient,
                        amount0: token_0,
                        amount1: token_1,
                    });

                    // transfer tokens
                    Ok((token_0, token_1))
                }

```

The issue arises because the `transfer_to_addr` function in the `wasm_erc20.rs` contract expects the first parameter to be the token address and the second to be the recipient address. Pas
Seller can steal back the nft from buyer
Audit: 2024-08-superposition-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-superposition/blob/main/pkg/sol/OwnershipNFTs.sol#L109-L116


# Vulnerability details

User can sell/transfer nft to a buyer. 
However it is possible for the user to steal back the nft from the buyer because the approvals are not deleted/removed after transfer.

## Proof of Concept
The token ID can be approved by anyone as long as the conditions inside `_requireAuthorised()` are fulfilled.

```solidity
File: OwnershipNFTs.sol

   function approve(address _approved, uint256 _tokenId) external payable {
        _requireAuthorised(msg.sender, _tokenId);
        getApproved[_tokenId] = _approved;
    }

   function _requireAuthorised(address _from, uint256 _tokenId) internal view {
        // revert if the sender is not authorised or the owner
        bool isAllowed =
            msg.sender == _from ||
            isApprovedForAll[_from][msg.sender] ||
            msg.sender == getApproved[_tokenId];

        require(isAllowed, "not allowed");
        require(ownerOf(_tokenId) == _from, "_from is not the owner!");
    }
```

### Scenario
- Alice owns a nft with token Id `1`.
- Alice calls the `approve()` setting `getApproved` to her own address.
- Alice sells the nft to Bob. The nft is successfully transferred & now the owner of the nft is Bob.
- Alice again calls transfer/safeTransferFrom function which internally calls the `_requireAuthorised()`.
- Because the `getApproved` for that tokenId `1` is still set to Alice's address, the checks pass & the nft is transferred back to Alice.

## Impact
Buyer will lose both the nft & funds paid.

## Tools Used
Manual Review

## Recommended Mitigation Steps
Reset/remove all the approvals after a nft is transferred.


## Assessed type

Error
Upgraded Q -> 3 from #332 [1726856796021]
Audit: 2024-08-phi-findings
Severity: HIGH

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

 [L-17] Consider introducing a rate limit as to how often mintFee and soulBounded values can be changed.
Artist has the power to increase fees by frontrunning certain users. This could be used to favour some users over the others by charging them less and the others more.

Similarly, the soulBounded value can be changed by artist to favour certain users to transfer their NFTs in the same block. Following this, the artist can reset it back to true.

Solution: Consider having some buffer period as to how often these values can be updated.

File: PhiFactory.sol
219:     function updateArtSettings(
220:         uint256 artId_,
221:         string memory url_,
222:         address receiver_,
223:         uint256 maxSupply_,
224:         uint256 mintFee_,
225:         uint256 startTime_,
226:         uint256 endTime_,
227:         bool soulBounded_,
228:         IPhiNFT1155Ownable.RoyaltyConfiguration memory configuration
229:     )
Upgraded Q -> 3 from #48 [1726245912184]
Audit: 2024-08-phi-findings
Severity: HIGH

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

 [03] Allowing art’s properties like startTime, endTime, and soulBounded to be updated through PhiFactory.updateArtSettings function can cause other users’ art claiming and art transfer transactions to be DOS’ed
Description
The PhiFactory.updateArtSettings function can be called by the corresponding art’s artist to update such art’s properties, such as its startTime, endTime, and soulBounded but such function call can unexpectedly DOS other users’ art claiming and art transfer transactions.

When the PhiFactory.updateArtSettings transaction, which updates the corresponding art’s startTime or endTime, is intentionally or unintentionally executed before the transaction for claiming such art, such art claiming transaction can revert unexpectedly because the PhiFactory._validateAndUpdateClaimState function would revert when such art’s new startTime is later than block.timestamp or such art’s new endTime is earlier than block.timestamp even though such art could be claimed if its startTime and endTime could remain at their original values.

Similarly, when the PhiFactory.updateArtSettings transaction, which updates the corresponding art’s soulBounded from false to true, is intentionally or unintentionally executed before the transaction for transferring such art, such art transfer transaction can revert unexpectedly since the PhiNFT1155.safeTransferFrom and PhiNFT1155.safeBatchTransferFrom functions would revert when such art’s new soulBounded is true even though such art could be transferred if its soulBounded could stay false.

https://github.com/code-423n4/2024-08-phi/blob/3a817c9dedca53ea27ff3e7988f8389086935b8b/src/PhiFactory.sol#L215-L257

    function updateArtSettings(
        uint256 artId_,
        string memory url_,
        address receiver_,
        uint256 maxSupply_,
        uint256 mintFee_,
        uint256 startTime_,
        uint256 endTime_,
        bool soulBounded_,
        IPhiNFT1155Ownable.RoyaltyConfiguration memory configuration
    )
        external
        onlyArtCreator(artId_)
    {
        if (receiver_ == address(0)) {
            revert InvalidAddressZero();
        }

        if (endTime_ < startTime_) {
            revert InvalidTimeRange();
        }
        if (endTime_ < block.timestamp) {
            revert EndTimeInPast();
        }

        PhiArt storage art = arts[artId_];

        if (art.numberMinted > maxSupply_) {
            revert ExceedMaxSupply();
        }

        art.receiver = receiver_;
        art.maxSupply = maxSupply_;
        art.mintFee = mintFee_;
@>      art.startTime = startTime_;
@>      art.endTime = endTime_;
@>      art.soulBounded = soulBounded_;
        art.uri = url_;

        uint256 tokenId = IPhiNFT1155Ownable(art.artAddress).getTokenIdFromFactoryArtId(artId_);
        IPhiNFT1155Ownable(art.artAddress).updateRoyalties(tokenId, configuration);
Cred contract has Unprotected Function _addCredIdPerAddress
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

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

Cred contract has Unprotected Function _addCredIdPerAddress.

The _addCredIdPerAddress function is marked as public, which means it can be called by any external address. This lack of access control could allow malicious actors to manipulate the _credIdsPerAddress mapping, potentially leading to data corruption, unexpected behaviour, or exploitation of the contract’s logic.

Allowing external access to the _addCredIdPerAddress function can result in unauthorised additions of credIds to a curator’s address. This could lead to:

Inaccurate tracking of credIds associated with an address.

Potential manipulation of the internal state, affecting the logic of other functions relying on this mapping.

Possible exploitation of the contract’s functionality, leading to financial losses or incorrect distribution of rewards.

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

Consider the following scenario where an external user calls the _addCredIdPerAddress function directly:
```sol
contract MaliciousContract {
    Cred credContract;

    constructor(address credContractAddress) {
        credContract = Cred(credContractAddress);
    }

    function exploit() external {
        uint256 fakeCredId = 9999; // Arbitrary fake credId
        address victim = msg.sender; // Victim's address
        credContract._addCredIdPerAddress(fakeCredId, victim); // Publicly accessible function
    }
}
```

In the above code, the MaliciousContract calls the _addCredIdPerAddress function on the Cred contract, adding an arbitrary credId to the victim’s address. This could cause unintended behaviour in the Cred contract, especially when querying or updating the victim’s credId list.

## Tools Used
Manual review & Remix IDE.

## Recommended Mitigation Steps

To mitigate this issue, the visibility of the _addCredIdPerAddress function should be changed to internal or private to restrict access to only the contract itself or its derived contracts. Additionally, review the access control for this function to ensure it is used securely within the contract’s logic.
```sol
// Original Function
function _addCredIdPerAddress(uint256 credId_, address sender_) public {
    _credIdsPerAddress[sender_].push(credId_);
    _credIdsPerAddressCredIdIndex[sender_][credId_] = _credIdsPerAddressArrLength[sender_];
    _credIdsPerAddressArrLength[sender_]++;
}

// Mitigated Function
function _addCredIdPerAddress(uint256 credId_, address sender_) internal {
    _credIdsPerAddress[sender_].push(credId_);
    _credIdsPerAddressCredIdIndex[sender_][credId_] = _credIdsPerAddressArrLength[sender_];
    _credIdsPerAddressArrLength[sender_]++;
}
```
By changing the visibility to interna
Anyone outside of Phi ecosystem can benefit from rewards
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`CuratorRewardsDistributor::distribute` is used to distribute the awards to the Cred involved Curators, based on their shares number.
A missing check allows anyone outside of the Phi ecosystem to receive reward for triggering the `CuratorRewardsDistributor::distribute` function. The only prerequisite for this to happen is for the attacker to find existing `CredId`.
This vulnerability could lead to unauthorized distribution of rewards, impacting the integrity and fairness of the reward system.

## Proof of Concept
Add the following lines of code in
`test\CuratorRewardsDistributor.t.sol`

```code
function testDistribute() public {
        uint256 credId = 1;
        uint256 depositAmount = 1 ether;

        // Deposit some ETH to the curatorRewardsDistributor
        curatorRewardsDistributor.deposit{ value: depositAmount }(credId, depositAmount);

        // Signal creds for different users
        vm.startPrank(user1);
        vm.deal(user1, bondingCurve.getBuyPriceAfterFee(credId, 1, 1));
        cred.buyShareCred{ value: bondingCurve.getBuyPriceAfterFee(credId, 1, 1) }(credId, 1, 0);
        vm.stopPrank();

        vm.startPrank(user2);
        vm.deal(user2, bondingCurve.getBuyPriceAfterFee(credId, 2, 2));
        cred.buyShareCred{ value: bondingCurve.getBuyPriceAfterFee(credId, 2, 2) }(credId, 2, 0);
        vm.stopPrank();

        assertEq(cred.getShareNumber(credId, user1), 1, "Signal count should be 1");
        assertEq(cred.getShareNumber(credId, user2), 2, "Signal count should be 2");
        assertEq(cred.getCurrentSupply(credId), 4, "Signal count should be 3");
        assertEq(cred.getCuratorAddressLength(credId), 3, "Signal count should be 3");

        // Record initial balances
        uint256 initialUser1Balance = phiRewards.balanceOf(user1);
        uint256 initialUser2Balance = phiRewards.balanceOf(user2);
        uint256 ownerBalance = owner.balance;

        // Distribute rewards
     @>  - vm.prank(owner);
     @>  + address attacker = makeAddr("attacker");
     @>  + vm.prank(attacker);
        curatorRewardsDistributor.distribute(credId);

        // Check final balances
        uint256 finalUser1Balance = phiRewards.balanceOf(user1);
        uint256 finalUser2Balance = phiRewards.balanceOf(user2);
        uint256 finalOwnerBalance = owner.balance;

        // Assert the distribution
        assertEq(
            finalUser1Balance - initialUser1Balance,
            (depositAmount - depositAmount / 100) / 4,
            "User1 should receive 1/4 of the rewards"
        );
        assertEq(
            finalUser2Balance - initialUser2Balance,
            ((depositAmount - depositAmount / 100) * 2) / 4,
            "User2 should receive 2/4 of the rewards"
        );
       @> - assertEq(
       @> -     finalOwnerBalance - ownerBalance,
Critical State-Changing Functions Are Marked as Public Instead of Internal or Private
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The functions `Cred::_addCredIdPerAddress()` and `Cred::_removeCredIdPerAddress()` are only called via the function
`Cred::_updateCuratorShareBalance()`, which is internal. However, both `_addCredIdPerAddress` and
`_removeCredIdPerAddress` are marked as public, which allows anyone to update the state and add/remove a new credId to the address's list. These functions take arbitrary
`uint256 credId_` and `address sender_`, enabling anyone to change values for any user.

Here is the implementation of `Cred::_updateCuratorShareBalance()`:

```javascript
function _updateCuratorShareBalance(uint256 credId_, address sender_, uint256 amount_, bool isBuy) internal {
    (, uint256 currentNum) = shareBalance[credId_].tryGet(sender_);

    if (isBuy) {
        if (currentNum == 0 && !_credIdExistsPerAddress[sender_][credId_]) {
            // @audit _addCredIdPerAddress() can only be called through here, therefore shouldn't be public
            @> _addCredIdPerAddress(credId_, sender_);
            _credIdExistsPerAddress[sender_][credId_] = true;
        }
        shareBalance[credId_].set(sender_, currentNum + amount_);
    } else {
        if ((currentNum - amount_) == 0) {
            // @audit _removeCredIdPerAddress() can only be called through here, therefore shouldn't be public
            @> _removeCredIdPerAddress(credId_, sender_);
            _credIdExistsPerAddress[sender_][credId_] = false;
        }
        shareBalance[credId_].set(sender_, currentNum - amount_);
    }
}
```

Affected lines of code:

- [`_updateCuratorShareBalance()` Cred.sol#L666-L682](https://github.com/code-423n4/2024-08-phi/blob/main/src/Cred.sol#L666-L682)
- [`_addCredIdPerAddress()` Cred.sol#L685-L692](https://github.com/code-423n4/2024-08-phi/blob/main/src/Cred.sol#L685-L692)
- [`_removeCredIdPerAddress()` Cred.sol#L695-L729](https://github.com/code-423n4/2024-08-phi/blob/main/src/Cred.sol#L695-L729)

## Tools Used

Manual Review

## Recommended Mitigation Steps

- Change the functions' visibility to either `private` or `internal` based on the use case, so that nobody can
  externally call these functions with arbitrary data.

```diff
- function _addCredIdPerAddress(uint256 credId_, address sender_) public
+ function _addCredIdPerAddress(uint256 credId_, address sender_) private

- function _removeCredIdPerAddress(uint256 credId_, address sender_) public
+ function _removeCredIdPerAddress(uint256 credId_, address sender_) private
```









## Assessed type

Other
`CuratorRewardsDistributor::distribute` can be DoS-ed, leading to loss of user funds
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/main/src/reward/CuratorRewardsDistributor.sol#L77-L130


# Vulnerability details

## Impact
If a large amount of users buy shares of the same credential, `CuratorRewardsDistributor::distribute` will hit the block gas limit and rewards will not be transferred to the `PhiRewards` contract, meaning balances will not be updated for each stakeholders and users will lose their rewards.

## Proof of Concept
Let's have a look at the [`CuratorRewardsDistributor::distribute`](https://github.com/code-423n4/2024-08-phi/blob/main/src/reward/CuratorRewardsDistributor.sol#L77-L130) function below

```javascript
    function distribute(uint256 credId) external {
        if (!credContract.isExist(credId)) revert InvalidCredId();
        uint256 totalBalance = balanceOf[credId];
        if (totalBalance == 0) {
            revert NoBalanceToDistribute();
        }

        address[] memory distributeAddresses = credContract.getCuratorAddresses(credId, 0, 0);
        uint256 totalNum;

        for (uint256 i = 0; i < distributeAddresses.length; i++) {
            totalNum += credContract.getShareNumber(credId, distributeAddresses[i]);
        }

        if (totalNum == 0) {
            revert NoSharesToDistribute();
        }

        uint256[] memory amounts = new uint256[](distributeAddresses.length);
        bytes4[] memory reasons = new bytes4[](distributeAddresses.length);

        uint256 royaltyfee = (totalBalance * withdrawRoyalty) / RATIO_BASE;
        uint256 distributeAmount = totalBalance - royaltyfee;

        // actualDistributeAmount is used to avoid rounding errors
        // amount[0] = 333 333 333 333 333 333
        // amount[1] = 333 333 333 333 333 333
        // amount[2] = 333 333 333 333 333 333
        uint256 actualDistributeAmount = 0;
        for (uint256 i = 0; i < distributeAddresses.length; i++) {
            address user = distributeAddresses[i];

            uint256 userAmounts = credContract.getShareNumber(credId, user);
            uint256 userRewards = (distributeAmount * userAmounts) / totalNum;

            if (userRewards > 0) {
                amounts[i] = userRewards;
                actualDistributeAmount += userRewards;
            }
        }

        balanceOf[credId] -= totalBalance;

        _msgSender().safeTransferETH(royaltyfee + distributeAmount - actualDistributeAmount);

        //slither-disable-next-line arbitrary-send-eth
        phiRewardsContract.depositBatch{ value: actualDistributeAmount }(
            distributeAddresses, amounts, reasons, "deposit from curator rewards distributor"
        );

        emit RewardsDistributed(
            credId, _msgSender(), royaltyfee + distributeAmount - actualDistributeAmount, distributeAmount, totalBalance
        );
    }
```

we see that the `amounts` are computing by looping on the `distributeAddresses` array, which is in theory only bound by the `Cred::MAX_SUPPLY` constant set at `999`. this means th