xanoutas/solidity-security-findings huggingface dataset:
LiquidationPairFactory does not prevent duplicate LiquidationPair addresses.
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPairFactory.sol#L65


# Vulnerability details

## Description 

The createPair() function in the LiquidationPairFactory contract does not prevent two or more LiquidationPair contracts from having the same address. This could lead to a number of problems, including:

Two or more LiquidationPair contracts with the same address could compete with each other for users.
A malicious user could create a duplicate LiquidationPair contract with the same address as an existing contract in order to impersonate that contract.



 The deployedPairs mapping in the LiquidationPairFactory contract only prevents the same instance of a LiquidationPair contract from being deployed multiple times through the same factory. It does not prevent the deployment of multiple LiquidationPair contracts with the same address.

In the scenario , where two users call the createPair() function with the same parameters, the contract will indeed deploy two LiquidationPair contracts with the same address. This could potentially lead to unexpected behavior and issues.


## Impact
- Two or more LiquidationPair contracts with the same address could compete with each other for users. This could lead to confusion and frustration for users, and could also make it difficult for users to determine which LiquidationPair contract is legitimate.

- A malicious user could create a duplicate LiquidationPair contract with the same address as an existing contract in order to impersonate that contract. This could allow the malicious user to trick users into sending funds to the wrong contract, or to perform other malicious actions.

## Proof of Concept

import "remix/contracts/mocks/Account.sol";
import "./LiquidationPairFactory.sol";

contract LiquidationPairFactoryPOC {
  LiquidationPairFactory factory;
  Account sender;

  constructor() {
    sender = new Account();
    factory = new LiquidationPairFactory();
  }

  function testDuplicateAddresses() public {
    // Deploy two LiquidationPair contracts with the same parameters.
    LiquidationPair pair1 = factory.createPair(sender, "0x0", "0x0", 1, 1, 1, 1, 1, 1, 1);
    LiquidationPair pair2 = factory.createPair(sender, "0x0", "0x0", 1, 1, 1, 1, 1, 1, 1);

    // Check that the two LiquidationPair contracts have the same address.
    assert(pair1.address == pair2.address);
  }
}

contract Test {
  function main() public {
    LiquidationPairFactoryPOC poc = new LiquidationPairFactoryPOC();
    poc.testDuplicateAddresses();
  }
}

The POC test will run and print the following output:

AssertionError: pair1.address == pair2.address
    at LiquidationPairFactoryPOC.testDuplicateAddresses (LiquidationPairFactoryPOC.sol:19)

This output confirms that the bug is reproducible.


## Tools Used

Remix IDE

## Recommended Mitigation Steps

- The createPair() function could be updated to check for duplicate LiquidationP
Reentrancy vulnerability in swapExactAmountOut function
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L211


# Vulnerability details

## Description 

The swapExactAmountOut function in the LiquidationPair could potentially result in a reentrancy vulnerability. This is because the function calls the liquidate function of the source contract, which is an external contract. The liquidate function could potentially invoke arbitrary code, which could then call back into the swapExactAmountOut function. This would allow the attacker to execute the swapExactAmountOut function multiple times, effectively draining the contract's funds.

## Impact
If this vulnerability is exploited, the attacker could drain the contract's funds. This could have a significant impact on the users of the contract, as they could lose their funds.


## Proof of Concept
The following test can be used to reproduce the vulnerability:

contract Swap {
function swapExactAmountOut(
address _account,
uint256 _amountOut,
uint256 _amountInMax
) external returns (uint256) {
_checkUpdateAuction();
uint swapAmountIn = _computeExactAmountIn(_amountOut);
if (swapAmountIn > _amountInMax) {
revert SwapExceedsMax(_amountInMax, swapAmountIn);
}
_amountInForPeriod += uint96(swapAmountIn);
_amountOutForPeriod += uint96(_amountOut);
_lastAuctionTime += uint48(uint256(convert(convert(int256(_amountOut)).div(_emissionRate))));

    // Call the liquidate function, which could potentially call back into the swapExactAmountOut function.
    source.liquidate(_account, tokenIn, swapAmountIn, tokenOut, _amountOut);
    return swapAmountIn;
}
}

 ## Recommendation:

To prevent this vulnerability, the swapExactAmountOut function should be updated to check that the liquidate function does not call back into the swapExactAmountOut function. This can be done by using the require statement, as shown below:

function swapExactAmountOut(
    address _account,
    uint256 _amountOut,
    uint256 _amountInMax
  ) external returns (uint256) {
    _checkUpdateAuction();
    uint swapAmountIn = _computeExactAmountIn(_amountOut);
    if (swapAmountIn > _amountInMax) {
      revert SwapExceedsMax(_amountInMax, swapAmountIn);
    }
    _amountInForPeriod += uint96(swapAmountIn);
    _amountOutForPeriod += uint96(_amountOut);
    _lastAuctionTime += uint48(uint256(convert(convert(int256(_amountOut)).div(_emissionRate))));

    // Check that the liquidate function does not call back into the swapExactAmountOut function.
    require(
        source.liquidate(_account, tokenIn, swapAmountIn, tokenOut, _amountOut)
            != address(this)
    );

    return swapAmountIn;
  }

Alternatively, the swapExactAmountOut function could be updated to use the reentrancyGuard modifier. This modifier prevents the function from being called recursively, which would prevent the reentrancy vulnerability.







## Assessed type

Reentrancy
Potential Gas Limit Issue in RNG Relay Auction Contract
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L167
https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/libraries/RewardLib.sol#L66
https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/libraries/RewardLib.sol#L59


# Vulnerability details

## Impact
The impact of this finding is related to a potential gas limit issue in the RngRelayAuction.sol contract. The concern arises from the use of the rngComplete function, which relies on the RewardLib.rewards function to process an array of auctionResults.
 If the size of the auctionResults array becomes excessively large, it may lead to high gas consumption and could potentially cause the function to fail due to reaching gas limits.

## Proof of Concept

    function rewards(
    AuctionResult[] memory _auctionResults, //here
    uint256 _reserve
    ) internal pure returns (uint256[] memory) {
    uint256 remainingReserve = _reserve;
    uint256 _auctionResultsLength = _auctionResults.length;
    uint256[] memory _rewards = new uint256[](_auctionResultsLength);
    for (uint256 i; i < _auctionResultsLength; i++) {
      _rewards[i] = reward(_auctionResults[i], remainingReserve); //here
      remainingReserve = remainingReserve - _rewards[i];
    }
    return _rewards;
    }


    function rngComplete(
    uint256 _randomNumber,
    uint256 _rngCompletedAt,
    address _rewardRecipient,
    uint32 _sequenceId,
    AuctionResult calldata _rngAuctionResult
    ) external returns (bytes32) { 
    ...
    uint256[] memory _rewards = RewardLib.rewards(auctionResults, futureReserve); //here
    ..
    for (uint8 i = 0; i < _rewards.length; i++) { //here
      uint104 _reward = uint104(_rewards[i]);
      if (_reward > 0) {
        prizePool.withdrawReserve(auctionResults[i].recipient, _reward);
        emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward);
      }
    }

    return bytes32(uint(drawId));
    }
  

## Tools Used
manual review

## Recommended Mitigation Steps
Limit Array Size: Ensure that the auctionResults array passed to the rngComplete function is reasonably sized to prevent excessive gas consumption. Implement checks to limit the size of the array or break down large operations into smaller batches.




## Assessed type

Loop
The liquidation pair's periodLength and periodOffset may not necessarily match the prize pool draw length and offset
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L109
https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L110


# Vulnerability details

## Impact

As said in the specification, periodLength and periodOffset have to be synchronized with the prize pool to make sure that there is an auction every draw. But createPair() function in LiquidationPairFactory contract is external and allows for any parameters to be set by the user and there is no check that they match each other. That means any arbitary data can be set for the pair and it may not be the same as for the prize pool.

## Proof of Concept

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L109-110
https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationPair.sol#L65-88

## Tools Used

Manual review.

## Recommended Mitigation Steps

Make sure that the periodLength and periodOffset parameters in liquidation pair match the same parameters in the prize pool.


## Assessed type

Timing
rngComplete() may DoS if the rewards length is too big
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L167


# Vulnerability details

## Impact

In RngRelayAuction.sol, for loop is used to distribute the rewards if they are > 0. It loops through the _rewards.length and then withdraws the rewards for every recipient from the prize pool. However, this may become the problem if the rewards length is too big and, therefore, too expensive to execute. This may cause the function revert and execution of rngComplete() will become impossible for the caller.


## Proof of Concept

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L167-173

## Tools Used

Manual review.

## Recommended Mitigation Steps

It's recommended to set array size limit or change the logic so the function will not revert if the rewards array becomes too big.


## Assessed type

DoS
Unchecked return data can lead to memory corruption and DoS in RngAuctionRelayerDirect.sol
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuctionRelayerDirect.sol#L39


# Vulnerability details

## Impact

In RngAuctionRelayerDirect.sol, the external call is made to a _rngAuctionRelayListener using call() but the returnData remains unchecked. This can lead to a huge payload being sent from the relay listener and potentially corrupt memory and lead to a DoS attack as the allocation in memory will become very expensive.

## Proof of Concept

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuctionRelayerDirect.sol#L34-45

## Tools Used

Manual review.

## Recommended Mitigation Steps

The proper way to make call() is using Yul and assembly with checking the size of the returnData.


## Assessed type

DoS
# Missing deadline checks allow pending transactions to be maliciously executed
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/LiquidationRouter.sol#L63-L80


# Vulnerability details

## Impact
The `LiquidationRouter` contract does not allow users to submit a deadline using `swapExactAmountOut()` function. This missing feature enables pending transactions to be maliciously executed at a later point.

## Detailed description 
AMMs provide their users with an option to limit the execution of their pending actions, such as swaps or adding and removing liquidity. The most common solution is to include a deadline timestamp as a parameter (for example see [UniswapV2](https://github.com/Uniswap/v2-periphery/blob/0335e8f7e1bd1e8d8329fd300aea2ef2f36dd19f/contracts/UniswapV2Router02.sol#L70C35-L70C35) and [Uniswap V3](https://github.com/Uniswap/v3-periphery/blob/6cce88e63e176af1ddb6cc56e029110289622317/contracts/SwapRouter.sol#L119). If such an option is not present, users can unknowingly perform bad trades:

Alice wants to swap 100 tokens for 1 ETH and later sell the 1 ETH for 1000 DAI.
The transaction is submitted to the mempool, however, Alice chose a transaction fee that is too low for miners to be interested in including her transaction in a block. The transaction stays pending in the mempool for extended periods, which could be hours, days, weeks, or even longer.
When the average gas fee dropped far enough for Alice's transaction to become interesting again for miners to include it, her swap will be executed. In the meantime, the price of ETH could have drastically changed. She will still get 1 ETH but the DAI value of that output might be significantly lower. She has unknowingly performed a bad trade due to the pending transaction she forgot about.

The affected function in `LiquidationRouter.sol` is `swapExactAmountOut()`.

## Proof of Concept
Omitted in this case, since the exploit is solely based on the fact that there is no limit on how long a transaction including a swap is allowed to be pending, which can be clearly seen when looking at the mentioned function.

## Tools Used
Manual review.

## Recommended Mitigation Steps

Introduce a `ensure` modifier to check if the time is expired.

```
modifier ensure(uint256 deadline) {
        require(deadline >= block.timestamp, 'LiquidationRouter: EXPIRED');
        _;
    }
```
And use it in  `swapExactAmountOut()` function.

```
  /// @notice Swaps the given amount of output tokens for at most input tokens
  /// @param _liquidationPair The pair to swap against
  /// @param _receiver The account to receive the output tokens
  /// @param _amountOut The exactly amount of output tokens expected
  /// @param _amountInMax The maximum of input tokens to spend
  /// @return The actual number of input tokens used
  function swapExactAmountOut(
    LiquidationPair _liquidationPair,
    address _receiver,
    uint256 _amountOut,
    uint256 _amountInMax,
++  uint256 _deadline
++  ) external ensure(_deadline) on
create methods are suspicious of the reorg attack
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault-boost/blob/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBoosterFactory.sol#L29


# Vulnerability details

## Impact

The createVaultBooster() function deploys a new VaultBooster contract using the ```create```, where the address derivation depends only on the VaultBoosterFactory nonce.

Re-orgs can happen in all EVM chains and as confirmed the contracts will be deployed on most EVM compatible L2s including Arbitrum, etc. It is also planned to be deployed on ZKSync in future. In ethereum, where this is deployed, Re-orgs has already been happened. For more info, [check here](https://decrypt.co/101390/ethereum-beacon-chain-blockchain-reorg)

This issue will increase as some of the chains like Arbitrum and Polygon are suspicious of the reorg attacks.

Polygon re-org reference: [click here](https://protos.com/polygon-hit-by-157-block-reorg-despite-hard-fork-to-reduce-reorgs/) This one happened this year in February, 2023.

Polygon blocks forked: [check here](https://polygonscan.com/blocks_forked)

The issue would happen when users rely on the address derivation in advance or try to deploy the position clone with the same address on different EVM chains, any funds sent to the ```new``` contract could potentially be withdrawn by anyone else. All in all, it could lead to the theft of user funds.


```Solidity
File: src/VaultBoosterFactory.sol

    function createVaultBooster(PrizePool _prizePool, address _vault, address _owner) external returns (VaultBooster) {
>>        VaultBooster booster = new VaultBooster(_prizePool, _vault, _owner);

        emit CreatedVaultBooster(booster, _prizePool, _vault, _owner);

        return booster;
    }
```

Optimistic rollups (Optimism/Arbitrum) are also suspect to reorgs since if someone finds a fraud the blocks will be reverted, even though the user receives a confirmation.

**Attack Scenario**
Imagine that Alice deploys a new VaultBooster, and then sends funds to it. Bob sees that the network block reorg happens and calls createVaultBooster. Thus, it creates VaultBooster with an address to which Alice sends funds. Then Alices’ transactions are executed and Alice transfers funds to Bob’s controlled VaultBooster.

This is a Medium severity issue that has been referenced from below Code4rena reports,
https://code4rena.com/reports/2023-01-rabbithole/#m-01-questfactory-is-suspicious-of-the-reorg-attack
https://code4rena.com/reports/2023-04-frankencoin#m-14-re-org-attack-in-factory

## Proof of Concept
https://github.com/GenerationSoftware/pt-v5-vault-boost/blob/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBoosterFactory.sol#L29

## Tools Used
Manual Review

## Recommended Mitigation Steps
Deploy such contracts via `create2` with `salt` that includes `msg.sender`


## Assessed type

Other
The user can front-run the liquidation pair in VaultBooster
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-vault-boost/blob/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L193
https://github.com/GenerationSoftware/pt-v5-vault-boost/blob/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L211


# Vulnerability details

## Impact

In VaultBooster.sol, the user can set the liquidationPair address inside of a setBoost() function. In the case if the liquidationPair was created before and the user just points out to the existing liquidation pair, there is a possibility of front-run.

## Proof of Concept

The liquidatior will call swapExactAmountOut() in LiquidationRouter to start the process of liquidation and eventually liquidate() inside of a VaultBooster will be called. The problem is that the user can front-run the liquidator and call withdraw() before that. In this situation, the amountAvailable for the liquidator becomes 0 and his transaction is reverted resulting in losing money on gas costs (_amountOut > amountAvailable check will fail):

https://github.com/GenerationSoftware/pt-v5-vault-boost/blob/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L188-196
https://github.com/GenerationSoftware/pt-v5-vault-boost/blob/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L211-236

The problem is possible because the VaultBooster relies on balanceOf(address(this)) to compute the available amount that can be easily manipulated:

https://github.com/GenerationSoftware/pt-v5-vault-boost/blob/9d640051ab61a0fdbcc9500814b7f8242db9aec2/src/VaultBooster.sol#L190

## Tools Used

Manual review.

## Recommended Mitigation Steps

Change the logic so that the user can front-run the liquidator by withdrawing the funds, possibly some additional fees should be introduced.


## Assessed type

MEV
The ContinuousGDA implementation is incorrect leading to liquidation auctions running at the wrong price
Audit: 2023-08-pooltogether-findings
Severity: MEDIUM

# Lines of code

https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/libraries/ContinuousGDA.sol#L39-L41
https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/libraries/ContinuousGDA.sol#L65
https://github.com/GenerationSoftware/pt-v5-cgda-liquidator/blob/7f95bcacd4a566c2becb98d55c1886cadbaa8897/src/libraries/ContinuousGDA.sol#L86


# Vulnerability details

## Impact
The `LiquidationPair` contract facilitates Periodic Continuous Gradual Dutch Auctions for yield. This uses the underlying `ContinuousGDA.sol` library in order to correctly price the auctions.

However this library incorrectly implements the formula, using the emission rate in a few places where it should use the decay constant. Since the decay constant is usually less than the emission rate (as can also be seen from the test suite), this means that the `purchasePrice` calculation is lower than it should be, meaning that liquidations are over-incentivised.

## Proof of Concept
This is difficult to demonstrate given the issue is basically just that the formula in https://www.paradigm.xyz/2022/04/gda has been wrongly implemented. However I'll point out a few issues in the code:

```
  function purchasePrice(
    SD59x18 _amount,
    SD59x18 _emissionRate,
    SD59x18 _k,
    SD59x18 _decayConstant,
    SD59x18 _timeSinceLastAuctionStart
  ) internal pure returns (SD59x18) {
    if (_amount.unwrap() == 0) {
      return SD59x18.wrap(0);
    }
    SD59x18 topE = _decayConstant.mul(_amount).div(_emissionRate);
    topE = topE.exp().sub(ONE);
    SD59x18 bottomE = _decayConstant.mul(_timeSinceLastAuctionStart);
    bottomE = bottomE.exp();
    SD59x18 result;
    if (_emissionRate.unwrap() > 1e18) {
      result = _k.div(_emissionRate).mul(topE).div(bottomE);
    } else {
      result = _k.mul(topE.div(_emissionRate.mul(bottomE)));
    }
    return result;
  }
```

In the `result` calculation you can see that `_k` is divided by `_emissionRate`. However, according to the proper formula, `_k` should be divided by `_decayConstant`.

Another issue occurs in `purchaseAmount` where `_k` is added to `lnParam` instead of ONE and `price` is multiplied by `_emissionRate` instead of `_decayConstant`:


```
  function purchaseAmount(
    SD59x18 _price,
    SD59x18 _emissionRate,
    SD59x18 _k,
    SD59x18 _decayConstant,
    SD59x18 _timeSinceLastAuctionStart
  ) internal pure returns (SD59x18) {
    if (_price.unwrap() == 0) {
      return SD59x18.wrap(0);
    }
    SD59x18 exp = _decayConstant.mul(_timeSinceLastAuctionStart).exp();
    SD59x18 lnParam = _k.add(_price.mul(_emissionRate).mul(exp)).div(_k);
    SD59x18 numerator = _emissionRate.mul(lnParam.ln());
    SD59x18 amount = numerator.div(_decayConstant);
    return amount;
  }
```

I would suggest double checking the formula and the derivation of the complementary formulas to calculate amount/k. The correct implementation is show