xanoutas/solidity-security-findings huggingface dataset:
Users could lose rewards after `removeRewardToken()` 
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexRewards.sol#L179-L197
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexRewards.sol#L386-L415


# Vulnerability details

## Impact

The admin could call `removeRewardToken()` to change the `rewardToken[]` array. If before the reward token is added back, some user claims the rewards, the user could lose the rewards for the removed token. 

And `claim()` function could be called by anyone, some users could lose rewards due to other users' operation. Malicious users could monitor admin's call to `removeRewardToken()`, and call `claim()` for other users right after. As a result, the malicious users effectively steal portion of the removed token from other users. Which could be claimed after added back.


## Proof of Concept

Consider the following:
- pxGlp `rewardTokens[]` has 2 token, WETH and esGMX. 
- The admin calls `removeRewardToken(pxGlp, 1)`, then `rewardTokens[]` only has 1 token, WETH.
- Before the reward token is added back, Alice calls `claim(pxGlp, Alice)`. But now the `rewardTokens[]` only has WETH. 
- Alice's `userRewards` will be reset to 0 afterwards. The esGMX rewards will no longer be available even added back.

In this case, no esGMX will be received although px contract will get esGMX in `harvest()`. Those rewards will be left for the other users.

Even worse, since anyone can call `claim()`, Alice could lose the rewards if someone else called `claim(pxGlp, Alice)` right after the admin removed the token. Malicious user could abuse this to claim for other users to make them lose rewards. The lost rewards will accumulate until the reward token is added back. Effectively the malicious user can steal other users rewards portion.


`rewardTokens[]` array could remove token.
```solidity
File: src/PirexRewards.sol
179:     function removeRewardToken(ERC20 producerToken, uint256 removalIndex)

185:         ERC20[] storage rewardTokens = producerTokens[producerToken]
186:             .rewardTokens;
187:         uint256 lastIndex = rewardTokens.length - 1;
188: 
189:         if (removalIndex != lastIndex) {
190:             // Set the element at removalIndex to the last element
191:             rewardTokens[removalIndex] = rewardTokens[lastIndex];
192:         }
193: 
194:         rewardTokens.pop();
195: 
196:         emit RemoveRewardToken(producerToken, removalIndex);
197:     }
```

The for loop won't do anything to the removed token, even the px contract received the reward. And user's `rewards` will be cleared.
```solidity
373:     function claim(ERC20 producerToken, address user) external {

387:             uint256 rLen = rewardTokens.length;

390:             p.globalState.rewards = globalRewards - userRewards;
391:             p.userStates[user].rewards = 0;

396:             for (uint256 i; i < rLen; ++i) {
397:                 ERC20 rewardToken =
PirexGmx#migrateReward() may cause users to lose Reward.
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexGmx.sol#L940-L949


# Vulnerability details

## Impact

PirexGmx#migrateReward() may cause users to lose Reward. before PirexRewards.sol set new PirexGmx

## Proof of Concept

The current migration process is: call # completemigration ()-> # migrateward ()

After this method, the producer of PirexRewards.sol contract is still the old PirexGmx. 

At this time, if AutoPxGmx#compound () is called by bot:

AutoPxGmx#compound() -> PirexRewards#.claim() -> old_PirexGmx#claimRewards()

Old_PirexGmx#claimRewards () will return zero rewards  and the reward of AutopXGMX will be lost.


old PirexGmx still can execute
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexGmx.sol#L824-L828

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexGmx.sol#L940-L949

## Tools Used

## Recommended Mitigation Steps

There are two ways to solve the problem.
1. set the producer of PirexRewards to the new PirexGmx in completeMigration ().
2. In #migrateReward (), set the old PirexGmx's "pirexRewards" to address(0), so that you can't use the old PirexGmx to get rewards

Simply use the second, such as:
```solidity
    function migrateReward() external whenPaused {
        if (msg.sender != migratedTo) revert NotMigratedTo();
        if (gmxRewardRouterV2.pendingReceivers(address(this)) != address(0))
            revert PendingMigration();

        // Transfer out any remaining base reward (ie. WETH) to the new contract
        gmxBaseReward.safeTransfer(
            migratedTo,
            gmxBaseReward.balanceOf(address(this))
        );
+       pirexRewards ==address(0);   //*** set pirexRewards=0,Avoid claimRewards () being called by mistake.***//
    }
```
```
AutoPxGlp/AutoPxGmx sets a new platform, does not work properly
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGlp.sol#L130-L136


# Vulnerability details

## Impact
#setPlatform() sets the new platform, the lack of gmxBaseReward.safeApprove(address(_platform)), causes #compound() to fail, thus causing deposit/withdraw also to fail.

## Proof of Concept

```solidity
    function setPlatform(address _platform) external onlyOwner {
        if (_platform == address(0)) revert ZeroAddress();

        platform = _platform; //****@audit only set plateform, don't execute gmxBaseReward.safeApprove(address(_platform), type(uint256).max) ****/

        emit PlatformUpdated(_platform);
    }
```

#compound () will fail to use the new platform.

```solidity
    function compound(
        uint256 minUsdg,
        uint256 minGlp,
        bool optOutIncentive
    )
...

        if (gmxBaseRewardAmountIn != 0) {
            // Deposit received rewards for pxGLP
            (, pxGlpAmountOut, ) = PirexGmx(platform).depositGlp( //****@audit not gmxBaseReward.approve(platform), depositGlp() will fail ****//
                address(gmxBaseReward),
                gmxBaseRewardAmountIn,
                minUsdg,
                minGlp,
                address(this)
            );
        }    
```

Therefore, all calls to #compound () will fail.
#withdraw() -> #compound()
#redeem() -> #compound()
#depositFsGlp() -> #beforeDeposit() -> #compound()
#depositGlp() -> #beforeDeposit() -> #compound()


note::AutoPxGmx#setPlatform() Have the same problem.

## Tools Used

## Recommended Mitigation Steps

```solidity

    function setPlatform(address _platform) external onlyOwner {
        if (_platform == address(0)) revert ZeroAddress();

+       gmxBaseReward.safeApprove(platform, 0);
+       gmxBaseReward.safeApprove(_platform, type(uint256).max);

        platform = _platform;

        emit PlatformUpdated(_platform);
    }
```
The transferFrom() method is used instead of safeTransferFrom()
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexGmx.sol#L436
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PirexERC4626.sol#L261


# Vulnerability details

# Vulnerability details

Details & Impact
The transferFrom() method is used instead of safeTransferFrom(), presumably to save gas. I however argue that this isn’t recommended because:

1)OpenZeppelin’s documentation discourages the use of transferFrom(), use safeTransferFrom() whenever possible
2)Given that any NFT can be used for the call option, there are a few NFTs (here’s an example) that have logic in the onERC721Received() function, which is only triggered in the safeTransferFrom() function and not in transferFrom()

Recommended Mitigation Steps
Call the safeTransferFrom() method instead of transferFrom()


File:2022-11-redactedcartel\src\PirexGmx.sol
 
  436,19:         stakedGlp.transferFrom(msg.sender, address(this), amount);

File:2022-11-redactedcartel\src\vaults\PirexERC4626.sol

  261,29:         bool status = ERC20.transferFrom(from, to, amount);
If condition will never execute to true as a result neither of the operations get executed.
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/main/src/vaults/AutoPxGlp.sol#L250-L261


# Vulnerability details

In AutoPxGlp.compound neither of the if statement will set to true defined on L251 and L266 as a result none of the operation will never get executed that are defined under these if statement.

https://github.com/code-423n4/2022-11-redactedcartel/blob/main/src/vaults/AutoPxGlp.sol#L250-L261
uint256 newAssets = totalAssets() - preClaimTotalAssets;
        if (newAssets != 0) {
            totalPxGlpFee = (newAssets * platformFee) / FEE_DENOMINATOR;
            pxGlpIncentive = optOutIncentive
                ? 0
                : (totalPxGlpFee * compoundIncentive) / FEE_DENOMINATOR;

            if (pxGlpIncentive != 0)
                asset.safeTransfer(msg.sender, pxGlpIncentive);

            asset.safeTransfer(owner, totalPxGlpFee - pxGlpIncentive);
        }

        As newAssets will always get equal to zero, since newAssets = totalAssets() - preClaimTotalAssets, where totalAssets() function on L142 will return asset.balanceOf(address(this)); and preClaimTotalAssets is already initialized to asset.balanceOf(address(this)); under same contract on L299 which will always point to same address balance as totalAssets which leads to newAssets = 0 always.further down it will failed to transfer valid amount of assets to the owner: asset.safeTransfer(owner, totalPxGlpFee - pxGlpIncentive);

https://github.com/code-423n4/2022-11-redactedcartel/blob/main/src/vaults/AutoPxGlp.sol#L266-L280
        // Track the amount of pxGMX received
        pxGmxAmountOut = pxGmx.balanceOf(address(this)) - preClaimPxGmxAmount;

        if (pxGmxAmountOut != 0) {
            // Calculate and distribute pxGMX fees if the amount of pxGMX increased
            totalPxGmxFee = (pxGmxAmountOut * platformFee) / FEE_DENOMINATOR;
            pxGmxIncentive = optOutIncentive
                ? 0
                : (totalPxGmxFee * compoundIncentive) / FEE_DENOMINATOR;

            if (pxGmxIncentive != 0)
                pxGmx.safeTransfer(msg.sender, pxGmxIncentive);

            pxGmx.safeTransfer(owner, totalPxGmxFee - pxGmxIncentive);

            // Update the pxGmx reward accrual
            _harvest(pxGmxAmountOut - totalPxGmxFee);
        }

Here, same thing happen as above if statement will never get execute to true making all the operation fail under this if statement because as if statement and operation's involved in this will execute only when pxGmxAmountOut != 0 but here pxGmxAmountOut will always equal to zero since pxGmxAmountOut = pxGmx.balanceOf(address(this)) - preClaimPxGmxAmount; where preClaimPxGmxAmount = pxGmx.balanceOf(address(this)); as initialized on L230 will make pxGmxAmountOut = 0 always which will affect operations defined under this statement to never get executed.further down it will failed to transfer valid amount of pxGMX to the owner: pxGmx.safeTransfer(owner, totalPxGmxFee - pxGmxIncentive);

Recommende
In AutoPxGmx.compound neither of the operation succeed
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/main/src/vaults/AutoPxGmx.sol#L288-L295


# Vulnerability details

POC: In AutoPxGmx.compound on L288 if statement get also equal to zero therefore neither totalFee nor incentive get calculated correctly and due to this asset.safeTransfer(owner, totalFee - incentive); on L299 will never get executed.

https://github.com/code-423n4/2022-11-redactedcartel/blob/main/src/vaults/AutoPxGmx.sol#L288-L295
if ((totalAssets() - assetsBeforeClaim) != 0) {
            totalFee =
                ((asset.balanceOf(address(this)) - assetsBeforeClaim) *
                    platformFee) /
                FEE_DENOMINATOR;
            incentive = optOutIncentive
                ? 0
                : (totalFee * compoundIncentive) / FEE_DENOMINATOR;

            if (incentive != 0) asset.safeTransfer(msg.sender, incentive);

            asset.safeTransfer(owner, totalFee - incentive);
        }

https://github.com/code-423n4/2022-11-redactedcartel/blob/main/src/vaults/AutoPxGmx.sol#L288
    if ((totalAssets() - assetsBeforeClaim) != 0)    
Under above if statement as totalAssets() from L164 will return asset.balanceOf(address(this)) and assetsBeforeClaim has a value initialized equal to asset.balanceOf(address(this)); on L260 which will make if statement always equal to zero, due to which totalFee and incentive will not set correctly and safetransfer will not get execute.

further if by any how if statment get passed, totalFee and incentive will again always be equal to zero making asset.safeTransfer(owner, totalFee - incentive); will always sends zero amount of assets.

 totalFee =
                ((asset.balanceOf(address(this)) - assetsBeforeClaim) *
                    platformFee) /
                FEE_DENOMINATOR;
            incentive = optOutIncentive
                ? 0
                : (totalFee * compoundIncentive) / FEE_DENOMINATOR;

In calculation for totalFee (asset.balanceOf(address(this)) - assetsBeforeClaim) is always equal to zero since, asset.balanceOf(address(this) = assetsBeforeClaim as assign on L260 under same function compound, which makes whole calculation equal to zero as a result totalFee = 0; 
which will further effect incentive as it also involve totalFee in its calculation making incentive always equal to zero.Further down asset.safeTransfer(owner, totalFee - incentive); will always sends zero amount of assets,since totalFee - incentive = 0 .

Recommendation: Make a check for totalAssets and assetsBeforeClaim so that they never get same and    assetsBeforeClaim always lesser than totalAssets otherwise it may overflow.
anyone can claim rewards for another user
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexRewards.sol#L373


# Vulnerability details

## Impact
Users can claim rewards for other users, as much the rewards wont go to the attacker it may create some for of confusion finding that someone claimed rewards on your behalf. you may have wanted to stake a little bit longer but now your plans have being cut short 

## Proof of Concept

```diff
diff --git a/PirexRewardsClaim.t.sol b/PirexRewards.t.sol
index 3f650ee..9ef071f 100644
--- a/PirexRewardsClaim.t.sol
+++ b/PirexRewards.t.sol
@@ -1347,6 +1347,41 @@ contract PirexRewardsTest is Helper {
     }
 
 
+     function testClaim2 (uint32 secondsElapsed,
+        uint8 multiplier,
+        bool useETH,
+        bool forwardRewards) external {
+        vm.assume(secondsElapsed > 10);
+        vm.assume(secondsElapsed < 365 days);
+        vm.assume(multiplier != 0);
+        vm.assume(multiplier < 10);
+
+        _depositGmxForTestAccounts(true, address(this), multiplier);
+        _depositGlpForTestAccounts(true, address(this), multiplier, useETH);
+
+        pirexRewards.addRewardToken(pxGmx, weth);
+        pirexRewards.addRewardToken(pxGlp, weth);
+        pirexRewards.harvest();
+
+        address alice = address(3);
+        address bob = address(4);
+
+         vm.startPrank(alice);
+
+         pirexRewards.setRewardRecipient(pxGmx, weth, address(this));
+         pirexRewards.setRewardRecipient(pxGlp, weth, address(this));
+
+         vm.stopPrank();
+
+         vm.startPrank(bob);
+         pirexRewards.claim(pxGmx, alice);
+         vm.stopPrank();
+
+         emit Claim(pxGmx, alice);
+
+         emit log_named_uint('alice bal', weth.balanceOf(address(this)));
+    }
+
     
     /**
         @notice Test tx success: claim


## Tools Used
foundry 
## Recommended Mitigation Steps
check the user claiming rewards is the msg.sender
Possible DOS aatack 
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexRewards.sol#L107-L126


# Vulnerability details

## Impact
with users adding their own recepients, they could potentially add the wrong tokens. as much as there is a mechanism of removing the mapping there is no incentive to. they will just add the correct token later on and continue with things as usual.
at some point along the 10 million entries the mapping will become expensive to read from 

## Proof of Concept
https://code4rena.com/reports/2022-09-vtvl#m-03-possible-dos-on-vestingrecipients-due-to-lack-of-disposal-mechanism

https://ethereum.stackexchange.com/a/77216

## Tools Used

## Recommended Mitigation Steps
introduce a require check that makes sure the tokens submitted are the ones the admin stores
Removal of Reward Token Leading To Loss of Accrued Rewards
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexRewards.sol#L179


# Vulnerability details

In the removeRewardToken() method in PirexRewards.sol, users could lose rewards they have not yet claimed once a reward token is removed since the removal method does not give users a chance to claim eligible rewards.

## Impact
Users will lose rewards that they have accrued while a reward token was still eligible. Once it is removed, all rewards accrued associated with that reward token will be lost. 

## Proof of Concept
Below shows how a user will not be able to claim rewards for a reward token that was removed, even though they accrued rewards while it was still in the reward token list

```solidity
function testLosingRewardsOnRewardTokenRemoval() external {
        //create new producer token that will soon have reward tokens added
        vm.prank(testAccounts[0]);
        SampleProducerToken token = new SampleProducerToken();
        assertEq(10 ether, token.balanceOf(testAccounts[0]));
        //add reward token to the producer token
        ERC20 rewardToken = weth;
        assertTrue(rewardToken.balanceOf(testAccounts[0]) == 0);
        pirexRewards.addRewardToken(token, rewardToken);
        //allow token to accrue rewards
        pirexRewards.globalAccrue(token);
        pirexRewards.userAccrue(token, testAccounts[0]);
        vm.warp(block.timestamp + 1 days);
        pirexRewards.globalAccrue(token);
        pirexRewards.userAccrue(token, testAccounts[0]);
        (,, uint256 globalRewards) = _getGlobalState(token);
        (,,uint256 userRewards) = pirexRewards.getUserState(token, testAccounts[0]);
        //there are now global and user rewards on this token
        assertTrue(globalRewards >= 0);
        assertTrue(userRewards >= 0);
        //remove reward token and claim
        pirexRewards.removeRewardToken(token, 0);
        pirexRewards.claim(token, testAccounts[0]);
        (,, globalRewards) = _getGlobalState(token);
        (,,userRewards) = pirexRewards.getUserState(token, testAccounts[0]);
        assertTrue(globalRewards == 0);
        assertTrue(userRewards == 0);
        //user has lost all rewards accrued during reward token eligibility
        assertTrue(rewardToken.balanceOf(testAccounts[0]) == 0);
    }
```

## Tools Used
Manual Audit, Foundry

## Recommended Mitigation Steps
In the removeRewardToken() function, claim rewards for all of the users before actually removing it so that no one will lost rewards gained before reward token removal. 

```solidity
claim(producerToken, all users); //this should happen before token is removed

uint256 lastIndex = rewardTokens.length - 1;
if (removalIndex != lastIndex) {
      // Set the element at removalIndex to the last element
      rewardTokens[removalIndex] = rewardTokens[lastIndex];
}
rewardTokens.pop();
```
Oracle manipulation with read only reentrancy in `get_virtual_price()` is not fully protected against
Audit: 2023-07-amphora-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/EthSafeStableCurveOracle.sol#L33-L44


# Vulnerability details

## Impact

Inaccurate price data integrated due to the manipulation of oracle data which would easily be the root cause of attackers being able to interact with protocol on ambiguous prices.

## Proof of Concept

Note that the solution implemented by many protocols is to add a call to the pool to execute a non reentrant function inside the oracle which is correctly implemented in the
[`_updateVirtualPrice()`](https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/EthSafeStableCurveOracle.sol#L33-L44) function of `EthSafeStableCurveOracle.sol`

```solidity
  function _updateVirtualPrice() internal {
    uint256 _virtualPrice = CRV_POOL.get_virtual_price();

    // We remove 0 liquidity to check the curve pool lock and avoid any manipulation
    uint256[2] memory _amounts;
    CRV_POOL.remove_liquidity(0, _amounts);

    virtualPrice = _virtualPrice;
  }
```

As seen the execution of `    CRV_POOL.remove_liquidity(0, _amounts);` properly mitigates this since the `remove_liquidity()` function prevents any reentrancy attacks due to it's non-reentrant lock.

Now after noting that the `_updateVirtualPrice()` function has implemented a protective measure against this vulnerability, there are some instances where `get_virtual_price()` is called which do not have such protections, leaving them susceptible to exploitation, an example can be seen in the internal [`_get()`](https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/TriCrypto2Oracle.sol#L51-L63) function of `TriCrypto2Oracle.sol`

```solidity
  function _get() internal view returns (uint256 _maxPrice) {
    //@audit
    uint256 _vp = TRI_CRYPTO.get_virtual_price();

    // Get the prices from chainlink and add 10 decimals
    uint256 _wbtcPrice = (WBTC_ORACLE_RELAY.peekValue());
    uint256 _ethPrice = (ETH_ORACLE_RELAY.peekValue());
    uint256 _usdtPrice = (USDT_ORACLE_RELAY.peekValue());

    uint256 _basePrices = (_wbtcPrice * _ethPrice * _usdtPrice);

    _maxPrice = (3 * _vp * FixedPointMathLib.cbrt(_basePrices)) / 1 ether;
    // removed discount since the % is so small that it doesn't make a difference
  }
```

As seen no non-reentrant function call are made within `_get()` which easily leaves it to vulnerable to the manipulation.

## Tool used

Manual Audit

## Recommended Mitigation Steps

Tested and trusted blue-chipped measures of protecting against oracle manipulation should be implemented in all instances of `get_virtual_price()`

### Additional Note

The [`_updateVirtualPrice()`](https://github.com/code-423n4/2023-07-amphora/blob/daae020331404647c661ab534d20093c875483e1/core/solidity/contracts/periphery/oracles/CbEt