xanoutas/solidity-security-findings huggingface dataset:
`PirexGmx(platform).depositFsGlp` Function Can Be DOSed
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/main/src/vaults/AutoPxGlp.sol#L330
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/PirexGmx.sol#L422
https://github.com/gmx-io/gmx-contracts/blob/516c78624bc7f6e10096f49ce05984990ade2ab0/contracts/staking/StakedGlp.sol#L86


# Vulnerability details

## Proof of Concept

Users call the `AutoPxGlp.depositFsGlp` function of the auto-compounding vault to deposit their staked GLP (fsGLP) tokens in exchange for apxGPL tokens. Within this function:

- In Line 344 - The caller's staked GLP (fsGLP) tokens will be transferred from the caller into the vault.
- In Line 349 - The vault will call the `PirexGmx(platform).depositFsGlp` function to deposit the received staked GLP (fsGLP) tokens to the `PirexGmx` contract to mint pxGLP tokens.

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

```solidity
File: AutoPxGlp.sol
330:     function depositFsGlp(uint256 amount, address receiver)
331:         external
332:         nonReentrant
333:         returns (uint256)
334:     {
335:         if (amount == 0) revert ZeroAmount();
336:         if (receiver == address(0)) revert ZeroAddress();
337: 
338:         if (totalAssets() != 0) beforeDeposit(address(0), 0, 0);
339: 
340:         ERC20 stakedGlp = ERC20(address(PirexGmx(platform).stakedGlp()));
341: 
342:         // Transfer fsGLP from the caller to the vault
343:         // before approving PirexGmx to proceed with the deposit
344:         stakedGlp.safeTransferFrom(msg.sender, address(this), amount);
345: 
346:         // Approve as needed here since the stakedGlp address is mutable in PirexGmx
347:         stakedGlp.safeApprove(platform, amount);
348: 
349:         (, uint256 assets, ) = PirexGmx(platform).depositFsGlp(
350:             amount,
351:             address(this)
352:         );
```

Within the `PirexGmx.depositFsGlp` function, the `stakedGlp.transferFrom` function will be called at Line 436 to transfer the staked GLP (fsGLP) tokens from the vault (caller) to the `PirexGmx` contract.

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

```solidity
File: PirexGmx.sol
422:     function depositFsGlp(uint256 amount, address receiver)
423:         external
424:         whenNotPaused
425:         nonReentrant
426:         returns (
427:             uint256,
428:             uint256,
429:             uint256
430:         )
431:     {
432:         if (amount == 0) revert ZeroAmount();
433:         if (receiver == address(0)) revert ZeroAddress();
434: 
435:         // Transfer the caller's fsGLP (unstaked for the user, staked for this contract)
436:         stakedGlp.transferFrom(msg.sender, address(this), amount);
```

However, by inspecting the GMX's `stakedGlp` contract, it was observed that there is a cooldown duration for the transfer of staked GLP (fsGLP) toke
User's Accrued Rewards Will Be Lost
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Proof of Concept

If the user deposits too little GMX compared to other users (or total supply of pxGMX), the user will not be able to receive rewards after calling the `PirexRewards.claim` function. Subsequently, their accrued rewards will be cleared out (set to zero), and they will lose their rewards.

The amount of reward tokens that are claimable by a user is computed in Line 403 of the `PirexRewards.claim` function.

If the balance of pxGMX of a user is too small compared to other users (or total supply of pxGMX), the code below will always return zero due to rounding issues within solidity.

```solidity
uint256 amount = (rewardState * userRewards) / globalRewards;
```

Since the user's accrued rewards is cleared at Line 391 within the `PirexRewards.claim` function (`p.userStates[user].rewards = 0;`), the user's accrued rewards will be lost.

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

```solidity
File: PirexRewards.sol
368:     /**
369:         @notice Claim rewards
370:         @param  producerToken  ERC20    Producer token contract
371:         @param  user           address  User
372:     */
373:     function claim(ERC20 producerToken, address user) external {
374:         if (address(producerToken) == address(0)) revert ZeroAddress();
375:         if (user == address(0)) revert ZeroAddress();
376: 
377:         harvest();
378:         userAccrue(producerToken, user);
379: 
380:         ProducerToken storage p = producerTokens[producerToken];
381:         uint256 globalRewards = p.globalState.rewards;
382:         uint256 userRewards = p.userStates[user].rewards;
383: 
384:         // Claim should be skipped and not reverted on zero global/user reward
385:         if (globalRewards != 0 && userRewards != 0) {
386:             ERC20[] memory rewardTokens = p.rewardTokens;
387:             uint256 rLen = rewardTokens.length;
388: 
389:             // Update global and user reward states to reflect the claim
390:             p.globalState.rewards = globalRewards - userRewards;
391:             p.userStates[user].rewards = 0;
392: 
393:             emit Claim(producerToken, user);
394: 
395:             // Transfer the proportionate reward token amounts to the recipient
396:             for (uint256 i; i < rLen; ++i) {
397:                 ERC20 rewardToken = rewardTokens[i];
398:                 address rewardRecipient = p.rewardRecipients[user][rewardToken];
399:                 address recipient = rewardRecipient != address(0)
400:                     ? rewardRecipient
401:                     : user;
402:                 uint256 rewardState = p.rewardStates[rewardToken];
403:                 uint256 amount = (rewardState * userRewards) / globalRewards;
404: 
405:                 if (amoun
Vault Share Calculation Can Be Broken By First User/Attacker
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
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PirexERC4626.sol#L178
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PirexERC4626.sol#L156


# Vulnerability details

## Proof of Concept

> Note: This issue affects both the AutoPxGmx and AutoPxGlp vaults. Since the root cause is the same, the PoC of AutoPxGlp vault is omitted for brevity.

A well-known attack vector for almost all shares-based liquidity pool contracts, where an early user can manipulate the price per share and profit from late users' deposits because of the precision loss caused by the rather large value of price per share.

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

```solidity
File: PirexERC4626.sol
60:     function deposit(uint256 assets, address receiver)
61:         public
62:         virtual
63:         returns (uint256 shares)
64:     {
65:         if (totalAssets() != 0) beforeDeposit(receiver, assets, shares);
66: 
67:         // Check for rounding error since we round down in previewDeposit.
68:         require((shares = previewDeposit(assets)) != 0, "ZERO_SHARES");
69: 
70:         // Need to transfer before minting or ERC777s could reenter.
71:         asset.safeTransferFrom(msg.sender, address(this), assets);
72: 
73:         _mint(receiver, shares);
74: 
75:         emit Deposit(msg.sender, receiver, assets, shares);
76: 
77:         afterDeposit(receiver, assets, shares);
78:     }
```

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

```solidity
File: PirexERC4626.sol
178:     function previewDeposit(uint256 assets)
179:         public
180:         view
181:         virtual
182:         returns (uint256)
183:     {
184:         return convertToShares(assets);
185:     }
```

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

```solidity
File: PirexERC4626.sol
156:     function convertToShares(uint256 assets)
157:         public
158:         view
159:         virtual
160:         returns (uint256)
161:     {
162:         uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
163: 
164:         return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
165:     }
```

The vault share minting formula is taken from Line 164 in the above `convertToShares` function:

```solidity
supply == 0 ? assets : assets.mulDivDown(supply, totalAssets())
```

This can be simplified into the following in a layman's term:

```solidity
if (supply == 0)
Then shares = assets
Else shares = (assets * supply) / totalAssets
```

If an
Malicious Users Can Drain The Assets Of Auto Compound Vault
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
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGmx.sol#L199
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/AutoPxGmx.sol#L315


# Vulnerability details

## Proof of Concept

> Note: This issue affects both the AutoPxGmx and AutoPxGlp vaults. Since the root cause is the same, the PoC of AutoPxGlp vault is omitted for brevity.

The `PirexERC4626.convertToShares` function relies on the `mulDivDown` function in Line 164 when calculating the number of shares needed in exchange for a certain number of assets. Note that the computation is rounded down, therefore, if the result is less than 1 (e.g. 0.9), Solidity will round them down to zero. Thus, it is possible that this function will return zero.

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

```solidity
File: PirexERC4626.sol
156:     function convertToShares(uint256 assets)
157:         public
158:         view
159:         virtual
160:         returns (uint256)
161:     {
162:         uint256 supply = totalSupply; // Saves an extra SLOAD if totalSupply is non-zero.
163: 
164:         return supply == 0 ? assets : assets.mulDivDown(supply, totalAssets());
165:     }
```

The `AutoPxGmx.previewWithdraw` function relies on the `PirexERC4626.convertToShares` function in Line 206. Thus, this function will also "round down".

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

```solidity
File: AutoPxGmx.sol
199:     function previewWithdraw(uint256 assets)
200:         public
201:         view
202:         override
203:         returns (uint256)
204:     {
205:         // Calculate shares based on the specified assets' proportion of the pool
206:         uint256 shares = convertToShares(assets);
207: 
208:         // Save 1 SLOAD
209:         uint256 _totalSupply = totalSupply;
210: 
211:         // Factor in additional shares to fulfill withdrawal if user is not the last to withdraw
212:         return
213:             (_totalSupply == 0 || _totalSupply - shares == 0)
214:                 ? shares
215:                 : (shares * FEE_DENOMINATOR) /
216:                     (FEE_DENOMINATOR - withdrawalPenalty);
217:     }
```

The `AutoPxGmx.withdraw` function relies on the `AutoPxGmx.previewWithdraw` function. In certain conditions, the `AutoPxGmx.previewWithdraw` function in Line 323 will return zero if the withdrawal amount causes the division within the `PirexERC4626.convertToShares` function to round down to zero (usually due to a small amount of withdrawal amount).

If the `AutoPxGmx.previewWithdraw` function in Line 323 returns zero, no shares will be burned
Users Receive Less Rewards Due To Miscalculations
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Background

The amount of rewards accrued by global and user states is computed by the following steps:

1. Calculate seconds elapsed since the last update (`block.timestamp - lastUpdate`)
2. Calculate the new rewards by multiplying seconds elapsed by the last supply (`(block.timestamp - lastUpdate) * lastSupply`)
3. Append the new rewards to the existing rewards (`rewards = rewards + (block.timestamp - lastUpdate) * lastSupply`)

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

```solidity
/**
    @notice Update global accrual state
    @param  globalState    GlobalState  Global state of the producer token
    @param  producerToken  ERC20        Producer token contract
*/
function _globalAccrue(GlobalState storage globalState, ERC20 producerToken)
	internal
{
    uint256 totalSupply = producerToken.totalSupply();
    uint256 lastUpdate = globalState.lastUpdate;
    uint256 lastSupply = globalState.lastSupply;

    // Calculate rewards, the product of seconds elapsed and last supply
    // Only calculate and update states when needed
    if (block.timestamp != lastUpdate || totalSupply != lastSupply) {
        uint256 rewards = globalState.rewards +
            (block.timestamp - lastUpdate) *
            lastSupply;
            
            globalState.lastUpdate = block.timestamp.safeCastTo32();
            globalState.lastSupply = totalSupply.safeCastTo224();
            globalState.rewards = rewards;
   	..SNIP..
}
```

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

```solidity
/**
    @notice Update user rewards accrual state
    @param  producerToken  ERC20    Rewards-producing token
    @param  user           address  User address
*/
function userAccrue(ERC20 producerToken, address user) public {
    if (address(producerToken) == address(0)) revert ZeroAddress();
    if (user == address(0)) revert ZeroAddress();

    UserState storage u = producerTokens[producerToken].userStates[user];
    uint256 balance = producerToken.balanceOf(user);

    // Calculate the amount of rewards accrued by the user up to this call
    uint256 rewards = u.rewards +
    u.lastBalance *
    (block.timestamp - u.lastUpdate);
    
    u.lastUpdate = block.timestamp.safeCastTo32();
    u.lastBalance = balance.safeCastTo224();
    u.rewards = rewards;
    ..SNIP..
}
```

When a user claims the rewards, the number of reward tokens the user is entitled to is equal to the `rewardState` scaled by the r
The number of `rewards` that malicious users can share
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/main/src/PirexRewards.sol#L281-L297


# Vulnerability details

## Impact

In PirexRewards#userAccrue(), users can update their `rewards` value by passing in the address of the `producertoken`. However, the standard for contract update rewards is `producerToken.balanceOf(user)`, which is the number of `producertoken` currently owned by the user. 


An attacker can create multiple contracts for `depostieGmx` to obtain `pxgmx`, and then send `producertoken(pxgmx)` in multiple addresses to repeatedly update user assets. For example, the attacker has a total of 10 `pxgmx`, sent to 10 different malicious accounts. Repeat the operation. The reward is equivalent to a 10x increase. Each account can share the same resource.

```solidity
    function userAccrue(ERC20 producerToken, address user) public { 
        if (address(producerToken) == address(0)) revert ZeroAddress();
        if (user == address(0)) revert ZeroAddress();

        UserState storage u = producerTokens[producerToken].userStates[user];
        uint256 balance = producerToken.balanceOf(user);

        // Calculate the amount of rewards accrued by the user up to this call
        uint256 rewards = u.rewards +
            u.lastBalance *
            (block.timestamp - u.lastUpdate);

        u.lastUpdate = block.timestamp.safeCastTo32();
        u.lastBalance = balance.safeCastTo224();
        u.rewards = rewards;

        emit UserAccrue(producerToken, user, block.timestamp, balance, rewards);
    }
```

## Proof of Concept

Flash loan to borrow a large amount of producetoken
## Tools Used
vscode
## Recommended Mitigation Steps

1. Prohibit passing producertoken between users
Users can avoid to give fee although platform has deposit fee (`feeAmount`)
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

### Impact

The `PirexGmx.depositGmx()` function converts GMX tokens to pxGMX, while doing this, with a commission of up to 20% determined by the platform.
However, it is possible to reset this commission in certain quantities, even if it is small, and this causes no commission to be received even though there is a commission rate, and the platform loses money.

The `Pire Gmx.deposit Gmx()` function does the commission calculation with the following line of code;

```solidity

 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);
    }

```

With the following code block in the `PirexGmx.depositGmx()` function, the `feeAmount` amount is not minted, this block gives us ; Only if the feeAmount rate is 0% it confirms that there is no fee charged, but even if the rate is greater than 0% (for example: 5%), it can be seen in the example below that the fee will be 0

```solidity

 // Mint pxGMX for fee distribution contract
        if (feeAmount != 0) {
            pxGmx.mint(address(pirexFees), feeAmount);
        }

```

This can also cause a sybil attack in Layer 2 with low fees such as Arbitrum

### Proof of Concept

Assuming the commission rate is 5% (it can be up to 20%) with the scenario below, if 1-199 GMX tokens are traded, the platform commission rate (feeAmount) will be 0;

```solidity
        //  "asset = 1 - 199 " Scenario
        //  feeAmount = (assets * fees[f] ) / 1_000_000
        //  feeAmount = (4 GMX * 5_000 ) / 1_000_000
        //  feeAmount = 0 
        //  postFeeAmount = 4
```



### Tools Used
Manual Code Review

### Recommended Mitigation Steps

Add to `if (fees[f] > 0 )` block for feeAmount check;

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

        if (fees[f] > 0 ) {
            feeAmount = (assets * fees[f]) / FEE_DENOMINATOR;
            require(feeAmount > 0 , "Insufficient asset amount");
        }

        assert(feeAmount + postFeeAmount == assets);
    }
```
PxGmxReward.sol: Invalid ERC20 instance of non-ERC20 contract
Audit: 2022-11-redactedcartel-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PxGmxReward.sol#L50
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PxGmxReward.sol#L72
https://github.com/code-423n4/2022-11-redactedcartel/blob/03b71a8d395c02324cb9fdaf92401357da5b19d1/src/vaults/PxGmxReward.sol#L11


# Vulnerability details

## Impact

In the code samples I provided you can see that an ERC20 instance is created of the contract `PxGmxReward.sol` ( `ERC20(address(this))`) which is not an ERC20 contract. 

Within the functions `_globalAccrue` & `_userAccrue` these instances are calling ERC20 methods which will always revert as `PxGmxReward.sol` is not ERC20 or PxERC20 or PirexERC4626 as used in the other Vault contracts.

This means the logic in the `claim` function will not properly calculate rewards and the function should revert which, if not corrected, will result in a loss of function for claiming rewards.

## Proof of Concept

This can be easily reproduced in remix as the `PxGmxReward` contract is only inheriting the `Owned` contract and no ERC20 contract and isn't implementing any ERC20 functions.

```
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract mockToken is ERC20 {
    constructor() ERC20("test", "test"){}
}

contract test {
    ERC20 public testToken;
    

    constructor(address _testToken) {
        testToken = ERC20(_testToken);
    }


    //As seen in PxGmxReward.sol
    function getBalance() external view returns (uint256 supplyTotal) {
        return supplyTotal = ERC20(address(this)).totalSupply();
    }

    //Normal ERC20
    function getBalanceMock() external view returns (uint256 mockSupply) {
        return mockSupply = ERC20(testToken).totalSupply();
    }
}
```

## Tools Used

Manual Audit


## Recommended Mitigation Steps

I believe the instead of using `address(this)` in the above functions, it is meant to be calling the variable `pxGmx` which is the ERC20 token & this matches the natspec comment for the `claim` function.