xanoutas/solidity-security-findings huggingface dataset:
First `ERC4626` deposit can break share calculation 
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`ERC4626` vault share price can be maliciously inflated on the initial deposit, leading to the next depositor losing assets due to precision issues.
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PirexERC4626.sol#L156-L165

## Proof of Concept
The first depositor of an `ERC4626` vault can maliciously manipulate the share price by depositing the lowest possible amount (1 wei) of liquidity and then artificially inflating `ERC4626.totalAssets.`

This can inflate the base share price as high as 1:1e18 early on, which force all subsequence deposit to use this share price as a base and worst case, due to rounding down, if this malicious initial deposit front-run someone else depositing, this depositor will receive 0 shares and lost his deposited assets.

Given a vault with DAI as the underlying asset:

Alice (attacker) deposits initial liquidity of 1 wei DAI via deposit()
Alice receives 1e18 (1 wei) vault shares
Alice transfers 1 ether of DAI via transfer() to the vault to artificially inflate the asset balance without minting new shares. The asset balance is now 1 ether + 1 wei DAI -> vault share price is now very high (= 1000000000000000000001 wei ~ 1000 * 1e18)
Bob (victim) deposits 100 ether DAI
Bob receives 0 shares
Bob receives 0 shares due to a precision issue. His deposited funds are lost.

The shares are calculated as following
`return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());`
In case of a very high share price, due to `totalAssets()` > assets * supply, shares will be 0.

## Tools Used
Manual Review

## Recommended Mitigation Steps
This is a well-known issue, Uniswap and other protocols had similar issues when `supply == 0`.

For the first deposit, mint a fixed amount of shares, e.g. 10**decimals()
`
if (supply == 0) {
    return 10**decimals; 
} else {
    return assets.mulDivDown(supply, totalAssets());
}
`
In _computeAssetAmounts() the feeAmount always returns 0 amount. Also postFeeAmount value is always equal to ASSETS value.
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexGmx.sol#L217-L226
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexGmx.sol#L378-L412


# Vulnerability details

## Impact

###  depositFsGlp (), depositGmx () functions its possible the full amount minted to receiver. _computeAssetAmounts functions always returns feeAmount value is zero.  So pirexFees contract not receive any fee amount in all deposit functions . pirexFees  lose the feeAmount  for all  Fees.Deposit,Fees.Redemption,Fees.Reward calls. 
 
## Proof of Concept

 ###   same Problem in other deposit functions also .  _depositGlp(), _redeemPxGlp(),depositFsGlp(),claimUserReward() functions using _computeAssetAmounts () call

     (uint256 postFeeAmount, uint256 feeAmount) = _computeAssetAmounts(
            Fees.Deposit,
            amount
        );

##

Lets assume the _computeAssetAmounts(Fees.Deposit,amount ); function is called with Fees.Deposit,amount. Here Fees.Deposit returns 0 to caller and lets assume amount going to deposit is 10000. So we need need to calculate postFeeAmount and feeAmount values . So we call _computeAssetAmounts function 

The Possible inputs for Fees f is [0,1,2] . Enum having only 3 inputs . Fees.Deposit returns 0, Fees.Redemption returns 1, Fees.Reward retuens 2. 

     function _computeAssetAmounts(Fees f, uint256 assets)
        internal
        view
        returns (uint256 postFeeAmount, uint256 feeAmount)
    {
        feeAmount = (assets * fees[f]) / FEE_DENOMINATOR;
        postFeeAmount = assets - feeAmount;
     assert(feeAmount + postFeeAmount == assets);
    }

## _computeAssetAmounts()  the feeAmount always return 0 there is no reduction in postFeeAmount   for all inputs [Fees.Deposit,Fees.Redemption,Fees.Reward] . The Overall Flow affects because of wrong deposit functions codes.



Now the internal function _computeAssetAmounts called with (Fees.Deposit ,10000) . Here FEE_DENOMINATOR value is 1_000_000

feeAmount calculation :

     feeAmount = 10000*fees[Fees.Deposit]/ 1000000  // @Audit  here fees[Fees.Deposit] value is 0 

here feeAmount value returns value 0. 

postFeeAmount  calculation : 

    postFeeAmount = 10000 -0;

So postFeeAmount is now 10000.

The return values are  (uint256 postFeeAmount, uint256 feeAmount)  (10000,0) 

The Amount and the postFeeAmount are same . There is no reductions since the feeAmount is 0 . 

### In pxGmx.mint(receiver, postFeeAmount);

in this functions we mint the pxGmx tokens to receiver . receiver gets full amount minted without any feeAmount reductions.

     if (feeAmount != 0) {
            pxGmx.mint(address(pirexFees), feeAmount);
        }

here feeAmount is zero so can't mint any feeAmount to pirexFees contract.



Here we assume _computeAssetAmounts called with (Fees.Redemption,10000)  and  (Fees.Reward,10000)  Here FEE_DENOMINATOR value is 1_000_000

The   _compu
First user of autocompounding vaults (AutoPxGlp and AutoPxGmx) can cause loss of funds for later users
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
First user of the vaults can cause loss of funds for subsequent users by throwing off the share ratios. This is due to a rounding error which has to be accounted for in erc4626 vault contracts. Consider the following scenario:

1. First user deposits 1 wei of pxGlp. This mints him 1 share in return. The ratio is 1 share : 1 glp (all in wei)
2. He then sends the contract 1e18 pxGlp (1 ether of pxGlp) without minting anything. Now, the ratio in the vault is 1 share: 1e18+1 glp
3. Second user arrives and deposits 2e18 (2 ether) of pxGlp. Due to the rounding error, the second user will be minted only 1 wei of the share token. This will let him redeem half of the assets in the vault (~1.5e18 pxGlp) since both users have equal no of tokens, even though he contributed 67% of the vault.
4. First user can now redeem his 1 share and take back ~1.5e18 pxGlp, making a profit of 0.5e18 pxGlp

## Proof of Concept
```solidity
function testDustAttack() public {
        _setupRewardsAndTestAccounts(1);

        // Deposit dust value
        _depositToVault(testAccounts[0], 1 wei);

        // Send 1 eth to throw off ratio
        vm.startPrank(testAccounts[0]);
        pxGlp.transfer(address(autoPxGlp), 1 ether);
        vm.stopPrank();

        // Second user (victim) approaches
        _depositToVault(testAccounts[1], 2 ether);

        // Second user tries to redeem
        vm.startPrank(testAccounts[1]);
        uint256 assetval = autoPxGlp.redeem(
            autoPxGlp.balanceOf(testAccounts[1]),
            testAccounts[1],
            testAccounts[1]
        );
        uint256 lostAmt = 2 ether - assetval;
        console.log("redeemed by user1", assetval);
        console.log("lost by user1", lostAmt);
        console.log("Percentage loss:", (lostAmt * 100) / 2 ether, "%");
    }
```
## Tools Used
Foundry

## Recommended Mitigation Steps
This can be mitigated in 2 ways:
1. If the number of shares issued is below a critical limit (~ 1000 wei of share tokens), require a minimum amount of px tokens to be deposited.
Or,
2. Have the DAO deposit the first few tokens, and lock in that amount by losing access to those share tokens.
Adversary can claim pxGmx for AutoPxGlp vault and break pxGmx internal accounting
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

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

## Proof of Concept

        uint256 preClaimTotalAssets = asset.balanceOf(address(this));
        uint256 preClaimPxGmxAmount = pxGmx.balanceOf(address(this));
 
        PirexRewards(rewardsModule).claim(asset, address(this));
        PirexRewards(rewardsModule).claim(pxGmx, address(this));
        ...

        // 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);
        } else {
            // Required to keep the globalState up-to-date
            _globalAccrue();
        }

    function _harvest(uint256 rewardAmount) internal {
        // Update global reward accrual state and associate with the update of reward state
        _globalAccrue();

        if (rewardAmount != 0) {
            rewardState += rewardAmount;

            emit Harvest(rewardAmount);
        }
    }

AutoPxGlp internally accounts for pxGmx with the _harvest subcall in compound. The amount of pxGmx received by claiming from the PirexRewards is passed in updating the rewardState, which is used to calculate pxGmx distribution. This accounting is only accurate if claim is only ever called by compound. The issue is that PirexRewards#claim is external and can be called by anyone for any account. An adversary can use this to lock the rewards so they can never be distributed.

Example:
AutoPxGlp has a balance of 10 pxGmx and rewardState has been accurately updated so it also has a balance of 10. Now there is 1 pxGmx available to be claimed for the vault. An adversary calls PirexRewards#claim for the vault sending it 1 pxGmx. Later the there is 1 pxGmx available to claim and the vault calls compound with a current balance of 11 pxGmx. It claims, increasing the balance of the vault to 12 pxGmx. Harvest is called with the difference (12 - 11 = 1) increasing rewardState to 11 (10 + 1). The balances are now forever out of sync and all rewards collected by the adversary cannot be distributed.
 
## Tools Used

Manual Review

## Recommended Mitigation Steps

Change the harvest subcall in compound to:

    -   _harvest(pxGmxAmountOut - totalPxGmxFee);
    +   _harvest(pxGmx.balanceOf(add
fees are wrong and if a user deposits 1  gmx they will get back 0.1 pxgmx back,lossing their funds 
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/684627b7889e34ba7799e50074d138361f0f532b/src/PirexGmx.sol#L217
https://github.com/code-423n4/2022-11-redactedcartel/blob/684627b7889e34ba7799e50074d138361f0f532b/src/PirexGmx.sol#L396
https://github.com/code-423n4/2022-11-redactedcartel/blob/684627b7889e34ba7799e50074d138361f0f532b/src/PirexGmx.sol#L439
https://github.com/code-423n4/2022-11-redactedcartel/blob/684627b7889e34ba7799e50074d138361f0f532b/src/PirexGmx.sol#L519
https://github.com/code-423n4/2022-11-redactedcartel/blob/684627b7889e34ba7799e50074d138361f0f532b/src/PirexGmx.sol#L633
https://github.com/code-423n4/2022-11-redactedcartel/blob/684627b7889e34ba7799e50074d138361f0f532b/src/PirexGmx.sol#L833


# Vulnerability details

## Impact
because `postFeeAmount` is `assets-fees` which is the fee amount that is taken off from the deposited amount but this variable is the  one that is getting minted which is not the `assets-fees`

```js
computeAssetAmoutns->
 feeAmount = (assets * fees[f]) / FEE_DENOMINATOR;
        postFeeAmount = assets - feeAmount;
        assert(feeAmount + postFeeAmount == assets);
```
`postFeeAmount=fee`
`Feeamount= assets-fee`
which is not the right way
ex:
Alice deposits  1 ether worth of gmx 
`fees[f]=100,000`
```js
feeAmount=(1e18* 100,000) / 1,000,000= 1e17

postFeeAmount= 1e18-1e17= 1e1
asset(1e17+1e1==1e18);
```
not when `pxGmx` is minted this is where the calculation is wrong :
```js
  pxGmx.mint(receiver, postFeeAmount);

        // Mint pxGMX for fee distribution contract
        if (feeAmount != 0) {
            pxGmx.mint(address(pirexFees), feeAmount);
        }
```
it mints the user who deposits only 1e1 tokens which is not what they deposited 
The users will take a 99% loss.
Alice deposited 1 ether of GMX
and gets back 1e1 worth of `pxGmx`
and then the fee contract gets `1e17 of pxGmx` which is too many tokens and that can cause wrong rewards to be given out and Alice's funds will be lost.
## Proof of Concept
```js
deposit->
      (uint256 postFeeAmount, uint256 feeAmount) = _computeAssetAmounts(
            Fees.Deposit,
            amount
        );

        // Mint pxGLP for the receiver (excludes fees)
        pxGlp.mint(receiver, postFeeAmount);

        // Mint pxGLP for fee distribution contract
        if (feeAmount != 0) {
            pxGlp.mint(address(pirexFees), feeAmount);
        }
```
```js
    function _computeAssetAmounts(Fees f, uint256 assets)
        internal
        view
        returns (uint256 postFeeAmount, uint256 feeAmount)
    {

        feeAmount = (assets * fees[f]) / FEE_DENOMINATOR;
        postFeeAmount = assets - feeAmount;
        assert(feeAmount + postFeeAmount == assets);
```
## Recommended Mitigation Steps
change the variables to opposite values 
```js
pxGmx.mint(receiver,feeAmount)

   if (postFeeAmount != 0) {
            pxGmx.mint(address(pirexFees),postFeeAmount);
        }
```
Anybody can obtain pxGLP without staking any fsGLP for it
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
Malicious user can obtain pxGLP without depositing any fsGLP and then redeem it for other tokens.

## Proof of Concept
The problem occurs at line 436 in the `PirexGmx.sol` contract. We have the line `stakedGlp.transferFrom(msg.sender, address(this), amount);` which should transfer stakedGlp from the sender into the contract, as it does. However, a malicious user can call the function `depositFsGlp` with any amount that he doesn't have, then the `stakedGlp.transferFrom(msg.sender, address(this), amount);` line will return false, but because of the lack of check if the transfer is successful, the rest of the `depositFsGlp` will execute, minting the pxGLP tokens to the receiver. At the end, the attacked will have x amount of pxGLP tockens without depositing a single fsGLP token.


## Tools Used
IDE (Remix)

## Recommended Mitigation Steps

Make sure to check the return of the `stakedGlp.transferFrom(msg.sender, address(this), amount);` line. One approach to this is:
`require(stakedGlp.transferFrom(msg.sender, address(this), amount), "fsGLP Transfer Failed.");`. Another approach is to first check that msg.sender's fsGLP balance is bigger or equal to the ammount that he want to deposit.
Manipulate the price per share value and unfair share of future users' deposits'
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGlp.sol#L142-L144
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGmx.sol#L164-L166


# Vulnerability details

## Impact
Most of the share based vault implementation will face this issue. The vault is based on the `ERC4626` where the shares are calculated based on the deposit value. By depositing large amount as initial deposit, initial depositor can influence the future depositors value. Future depositors are forced for huge value of asset to deposit. It is not practically possible for all the users. This could directly affect on the attrition of users towards this system.

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGlp.sol#L142-L144
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGmx.sol#L164-L166

## Proof of Concept
The attacker can profit from future users' deposits. While the late users will lose part of their funds to the attacker.

A malicious early user can deposit() with 1 wei of asset token as the first depositor of the AutoRoller token, and get 1 wei of shares.

Then the attacker can send 10000e18 - 1 of asset tokens and inflate the price per share from 1.0000 to an extreme value of 1.0000e22 ( from (1 + 10000e18 - 1) / 1) .

As a result, the future user who deposits 19999e18 will only receive 1 wei (from 19999e18 * 1 / 10000e18) of shares token.

They will immediately lose 9999e18 or half of their deposits if they redeem() right after the deposit().

`ERC4626 implementation function mint(uint256 shares, address receiver) public virtual returns (uint256 assets) { assets = previewMint(shares); // No need to check for rounding error, previewMint rounds up. `

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PirexERC4626.sol#L60-L78
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PirexERC4626.sol#L80-L97
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PirexERC4626.sol#L156-L165
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PirexERC4626.sol#L178-L185

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider requiring a minimal amount of share tokens to be minted for the first minter, and send a portion of the initial mints as a reserve to the DAO/ burn so that the price per share can be more resistant to manipulation.



        // function deposit(uint256 assets, address receiver)
        public
        virtual
        returns (uint256 shares)
    {
        if (totalAssets() != 0) beforeDeposit(receiver, assets, shares); Check for rounding error
Upgraded Q -> M from #38 [1670234424045]
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

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

 L2. Pool selection for the compound is not restricted
Compound function of AutoPxGmx contract is public and available for everyone. An attacker can create a custom pool with a higher fee value (currently, on tests it uses 0.3%, but it's possible to create a pool with a max of 1% fee) on Uniswap and manually execute compound function to drain gmxBaseReward from higher fees. The issue is minor while Uniswap limits pool fees to 1%, but in the future, in case it will be changed - the issue could become more critical.

Path: ./external/vaults/AutoPxGmx.sol : compound()
Recommendation: Do not allow it to pass a custom fee value (use fee from state variable).
Upgraded Q -> M from #251 [1670231833702]
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

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

 AutoPxGlp.setPlatform and AutoPxGmx.setPlatform break the vaults functionalities.
Looking at AutoPxGlp.setPlatform: this admin setter allows the owner to change the pirexGmx address in AutoPxGlp. The issue is that it does not call gmxBaseReward.safeApprove(platform, type(uin256).max).

This means that users will not be able to call depositFsGlp() anymore, as it calls beforeDeposit(), which calls compound(), where a platform.depositGlp() call is made:

238:         if (gmxBaseRewardAmountIn != 0) {
239:             // Deposit received rewards for pxGLP
240:             (, pxGlpAmountOut, ) = PirexGmx(platform).depositGlp(
241:                 address(gmxBaseReward),
242:                 gmxBaseRewardAmountIn,
243:                 minUsdg,
244:                 minGlp,
245:                 address(this)
246:             );
247:         }
This external call will revert here as the platform's gmxBaseReward allowance is 0.

Users will not only lose the ability to deposit GLP into the contracts, they also will not be able to call withdraw or redeem as both these functions call compound(): meaning they will not be able to retrieve their pxGLP.

Note that while centralization issues are mentioned in the README:

we also believe that certain privileges need to be retained by our team in order to safeguard user funds and maintain the protocol's continued operations 
In this case, the issue is not that this function can be used in a way to grieve users, but that it by design simply breaks the entire autoPxGlp functionality: There is no way to call gmxBaseReward.safeApprove() on a new platform.

The exact same issue happens in autoPxGmx with setPlatform, causing the compound() call to revert here.

You should add:

gmxBaseReward.safeApprove(platform, type(uin256).max) in AutoPxGlp.setPlatform.
gmx.safeApprove(platform, type(uin256).max) in AutoPxGmx.setPlatform.
Upgraded Q -> M from #316 [1670230229509]
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

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

 compound in AutoPxGmx can be called by anyone and can be sandwiched if a poorly chosen amountOutMinimum is used. The idea is to call the function often by adding an incentive to the caller. There is a problematic hidden incentive - the amountOutMinimum value is controlled by the caller. This means that MEV bots have incentive to call this function and take a cut of the rewards by setting amountOutMinimum to a lower value than could be received by the vault.

The function argument amountOutMinimum is on line 244. It is used in the Uniswap swap on line 275. It can be set to any value except zero, enabling the majority of the value from the swap to be lost.

Recommendation
Disincentive sandwiching of this swap. There should be additional checks that the amountOutMinimum function argument is a reasonable value. One way to do this is using a TWAP or oracle to get the exchange rate and use gmxBaseRewardAmountIn to calculate what the minimum allowable amountOutMinimum is.
Upgraded Q -> M from #402 [1670185822442]
Audit: 2022-11-redactedcartel-findings
Severity: MEDIUM

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

 Staked Gmx RewardTracker may retain allowances
Summary: Both the configureGmxState() function and the setContract(Contracts c, address contractAddress) can be used to update the PirexGmx contract's stakedGmx storage variable with a new staked Gmx RewardTracker contract. However only the setContract(Contracts c, address contractAddress) reset and set the allowance for the PirexContract before updating the stakedGmx storage variable with the new contract.

Impact: an older Staked Gmx contract (eventually a vulnerable one) may retain unlimited allowance to spend Gmx from PirexGmx contract if inadvertently updated via the configureGmxState() function rather than the setContract(Contracts c, address contractAddress) one.

Recommended Mitigation Steps: if the intent of the configureGmxState() function is to initialize/configure the PirexGmx contract once and only once, and use the setContract(Contracts c, address contractAddress) for live updates, use a flag to prevent further calls to the configureGmxState() function after the first one.