xanoutas/solidity-security-findings huggingface dataset:
Anyone can complete the Rng relay auction
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
There's no access restriction on `rngComplete` which allows anyone to execute this function and complete the auction. This can lead to unexpected behavior or potential DOS attack where a user completes the auction. 


## Tools Used
Manual Analysis

## Recommended Mitigation Steps
It's recommended to check that only the relayer can execute this function.

```solidity
/// @notice Called by the relayer to complete the Rng relay auction.
...
function rngComplete(
    uint256 _randomNumber,
    uint256 _rngCompletedAt,
    address _rewardRecipient,
    uint32 _sequenceId,
    AuctionResult calldata _rngAuctionResult
  ) external returns (bytes32) {
+    require(msg.sender == rngAuctionRelayer, "RngRelayAuction/rngComplete: Only the relayer can call this function");
    if (_sequenceHasCompleted(_sequenceId)) revert SequenceAlreadyCompleted();
```


## Assessed type

Access Control
Integer underflow/overflow is possible in some of the timestamp calculations if sequenceOffset or auctionDuration are set maliciously
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L381-L387


# Vulnerability details

## Impact
An attacker could exploit this to make _auctionElapsedTime return a low value when it should be high, thereby manipulating the reward calculation.

## Proof of Concept
The vulnerability comes from the subtraction (currentTime - sequenceOffset) which could underflow if sequenceOffset is maliciously set to a very large value greater than currentTime.

For example:

currentTime = 100
sequenceOffset = 200
Then currentTime - sequenceOffset will underflow to a very large number close to 2^64.

This can then cause the modulo % sequencePeriod to return an incorrect elapsed time.
## Tools Used
Manual

## Recommended Mitigation Steps
add an explicit check for underflow before doing the subtraction


## Assessed type

Other
RngRelayAuction can be bricked or used to specify arbitrary winning random numbers
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L131-L173
https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L241-L243


# Vulnerability details

## Impact
The `RngRelayAuction` contract deployed on each chain has a `rngComplete` method that is supposed to be called by the relayer in order to close/complete a prize draw.

However this method doesn't have any access control and can therefore be called by anyone with arbitrary random numbers or sequence ids. And arbitrary random number can be supplied to ensure that the caller always wins a prize. And the maximum `_sequenceId` value can be supplied to perpetually brick the relay auction contract. 

## Proof of Concept
This vulnerability exists due to lack of access control:

```
  function rngComplete(
    uint256 _randomNumber,
    uint256 _rngCompletedAt,
    address _rewardRecipient,
    uint32 _sequenceId,
    AuctionResult calldata _rngAuctionResult
  ) external returns (bytes32) {
    if (_sequenceHasCompleted(_sequenceId)) revert SequenceAlreadyCompleted();
    uint64 _auctionElapsedSeconds = uint64(block.timestamp < _rngCompletedAt ? 0 : block.timestamp - _rngCompletedAt);
    if (_auctionElapsedSeconds > (_auctionDurationSeconds-1)) revert AuctionExpired();
    // Calculate the reward fraction and set the draw auction results
    UD2x18 rewardFraction = _fractionalReward(_auctionElapsedSeconds);
    _auctionResults.rewardFraction = rewardFraction;
    _auctionResults.recipient = _rewardRecipient;
    _lastSequenceId = _sequenceId;

    AuctionResult[] memory auctionResults = new AuctionResult[](2);
    auctionResults[0] = _rngAuctionResult;
    auctionResults[1] = AuctionResult({
      rewardFraction: rewardFraction,
      recipient: _rewardRecipient
    });
```

Supplying an arbitrary `_randomNumber` value is clearly an issue since the number is now not random and doesn't originate from an on-chain random number generator. As stated above, a malicious user can ensure they win every round by supplying a random number that always places them in the winning zone.

In terms of bricking the contract, this can be achieved due to the call to `_sequenceHasCompleted` on the first line of the above method:

```
  function _sequenceHasCompleted(uint32 _sequenceId) internal view returns (bool) {
    return _lastSequenceId >= _sequenceId;
  }
```

This method checks that the supplied `_sequenceId` is greater than the last sequence id. Due to the lack of access control a malicious user can call `rngComplete` with `_sequenceId = type(uint32).max` to permanently brick the contract going forwards.

## Tools Used
Manual review

## Recommended Mitigation Steps
The `rngComplete` method should only be able to be called by the auction relayer on the respective chain. Below is a suggested diff:

```
diff --git a/src/RngRelayAuction.sol b/src/
_computeRewardFraction logic looks susceptible to manipulation around the edges. 
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L400-L405


# Vulnerability details

## Impact
if __auctionElapsedTime is 0, it returns the full reward fraction

## Proof of Concept
The RewardLib.fractionalReward calculates the reward fraction linearly based on the elapsed time. When __auctionElapsedTime is 0, fractionalReward will return 1 (full rewards).
This can be manipulated by calling _computeRewardFraction with a low __auctionElapsedTime right after the new sequence starts. 


## Tools Used
Manual
## Recommended Mitigation Steps
add validation in _computeRewardFraction to ensure __auctionElapsedTime is within expected range


## Assessed type

Other
A malicious deployer can set auctionTargetTime close to auctionDuration to make it hard or impossible for anyone to win a reward.
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L152-L153
https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L145-L146
https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L398-L406


# Vulnerability details

## Impact
A malicious deployer could set auctionTargetTime very close to auctionDuration, making it hard/impossible for anyone to win a reward leading to losses

## Proof of Concept
In the constructor, auctionTargetTime is not validated to be less than auctionDuration: https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L152-L153
https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L145-L146

The reward amount depends on the elapsed time and the target time:https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngAuction.sol#L398-L406 



If auctionTargetTime is set very close to auctionDuration, then the window to call startRngRequest and get a decent reward fraction will be very small.
For example, if auctionDuration is 3600 seconds (1 hour) and auctionTargetTime is set to 3590 seconds (59 minutes 50 seconds), there is only a 10 second window at the end of the auction to call startRngRequest and get a high reward.
A malicious deployer could set auctionTargetTime = auctionDuration - 1 to make the window 1 second.



## Tools Used
Manual

## Recommended Mitigation Steps
Validate auctionTargetTime  and add a minimum window between target and duration 


## Assessed type

Other
Only a relayer should call rngComplete function in RngRelayAuction.sol contract.
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

An arbitrary user can set a wining number and set a _rewardRecipient to take the rewards

## Proof of Concept

The contract RngRelayAuction is setting a rngAuctionRelayer in the constructor.

```
file: pt-v5-draw-auction/src/RngRelayAuction.sol
 /// @notice The relayer that RNG results must originate from.
    /// @dev Note that this may be a Remote Owner if relayed over an ERC-5164 bridge.
    address public immutable rngAuctionRelayer;
```
https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L77C1-L79C46

```
file: pt-v5-draw-auction/src/RngRelayAuction.sol
constructor(
    PrizePool prizePool_,
    address _rngAuctionRelayer,
    uint64 auctionDurationSeconds_,
    uint64 auctionTargetTime_
  ) {
    ...
    rngAuctionRelayer = _rngAuctionRelayer;
    ...
  }
```
https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L102C3-L121C4

However this relayer is never used, Now look  the rngComplete:

```
file: pt-v5-draw-auction/src/RngRelayAuction.sol

  function rngComplete(
    uint256 _randomNumber,
    uint256 _rngCompletedAt,
    address _rewardRecipient,
    uint32 _sequenceId,
    AuctionResult calldata _rngAuctionResult
  ) external returns (bytes32) {
    if (_sequenceHasCompleted(_sequenceId)) revert SequenceAlreadyCompleted();
    uint64 _auctionElapsedSeconds = uint64(block.timestamp < _rngCompletedAt ? 0 : block.timestamp - _rngCompletedAt);
    if (_auctionElapsedSeconds > (_auctionDurationSeconds-1)) revert AuctionExpired();
    ...
    uint32 drawId = prizePool.closeDraw(_randomNumber); //setting wining number
    ...
    for (uint8 i = 0; i < _rewards.length; i++) {
      uint104 _reward = uint104(_rewards[i]);
      if (_reward > 0) {
        prizePool.withdrawReserve(auctionResults[i].recipient, _reward); //sending the rewards
        emit AuctionRewardDistributed(_sequenceId, auctionResults[i].recipient, i, _reward);
      }
    }


    return bytes32(uint(drawId));
  }



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

We can see that this function is never checking for the caller allowing a user pass a ramdom number and pass a winners. 

We can analize the calls that this function make to the prizePool contract:

- Closing the draw and setting a winningRandomNumber_  in the prizePool:

```
// this function is in the prizePool contract 
 function closeDraw(uint256 winningRandomNumber_) external onlyDrawManager returns (uint16) {
```

- Sending the rewards passing a winner in the prizePool

```  
function withdrawReserve(address _to, uint104 _amount) external onlyDrawManager {
```
An user can pass those va
rounding error can lead to DISABLE the system 
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
In RNGAuction.sol, _openSequenceId() if the difference between currentTime and sequenceOffset is less than the sequencePeriod,   it will round to zero leading to all functions depending on its value to return false (like _canStartNextSequence()), or even revert in case of startRNGRequest().
this will disable the RNG part,not starting new auctions and hence the prizing part will be FROZEN.

#Prove of concept:

  function _openSequenceId() internal view returns (uint32) {
   
    uint64 currentTime = _currentTime();
    if (currentTime < sequenceOffset) {
      return 0;
    }
    return uint32((currentTime - sequenceOffset) / sequencePeriod); // this could round to zero
  }

## Tools Used
    Manual review

## Recommended Mitigation Steps
 first check if uint32((currentTime - sequenceOffset) > sequencePeriod)

*Note that this part of the code has been referred to in the publically known issues for checking not to divide by zero issue, my finding is not about that so it is totally different issue. 



## Assessed type

Math
rngComplete() Lack of access control
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
`rngComplete()` Lack of access control , anyone can supply any random number

## Proof of Concept
`RngRelayAuction.rngComplete()` Used to provide random numbers and distribute rewards
In particular, providing random numbers is critical
However, there is currently a lack of access control, so anyone can call it and specify random numbers at will

```solidity
  function rngComplete(
    uint256 _randomNumber,
    uint256 _rngCompletedAt,
    address _rewardRecipient,
    uint32 _sequenceId,
    AuctionResult calldata _rngAuctionResult
  ) external returns (bytes32) {
    if (_sequenceHasCompleted(_sequenceId)) revert SequenceAlreadyCompleted();
    uint64 _auctionElapsedSeconds = uint64(block.timestamp < _rngCompletedAt ? 0 : block.timestamp - _rngCompletedAt);
    if (_auctionElapsedSeconds > (_auctionDurationSeconds-1)) revert AuctionExpired();
    // Calculate the reward fraction and set the draw auction results
    UD2x18 rewardFraction = _fractionalReward(_auctionElapsedSeconds);
    _auctionResults.rewardFraction = rewardFraction;
    _auctionResults.recipient = _rewardRecipient;
    _lastSequenceId = _sequenceId;

    AuctionResult[] memory auctionResults = new AuctionResult[](2);
    auctionResults[0] = _rngAuctionResult;
    auctionResults[1] = AuctionResult({
      rewardFraction: rewardFraction,
      recipient: _rewardRecipient
    });

@>  uint32 drawId = prizePool.closeDraw(_randomNumber);

    uint256 futureReserve = prizePool.reserve() + prizePool.reserveForOpenDraw();
    uint256[] memory _rewards = RewardLib.rewards(auctionResults, futureReserve);

    emit RngSequenceCompleted(
      _sequenceId,
      drawId,
      _rewardRecipient,
      _auctionElapsedSeconds,
      rewardFraction
    );

    for (uint8 i = 0; i < _rewards.length; i++) {
      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

## Recommended Mitigation Steps

Only `rngAuctionRelayer` can call it

```solidity
      function rngComplete(
        uint256 _randomNumber,
        uint256 _rngCompletedAt,
        address _rewardRecipient,
        uint32 _sequenceId,
        AuctionResult calldata _rngAuctionResult
      ) external returns (bytes32) {

+    require(msg.sender == rngAuctionRelayer,"invald sender");  

```


## Assessed type

Access Control
`rngComplete` function should only be called by `rngAuctionRelayer`
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact

The `rngComplete` function is supposed to be called by the relayer to complete the Rng relay auction and send auction rewards to the recipient, but because the function doesn't have any access control it can be called by anyone, an attacker can call the function before the relayer and give a different `_rewardRecipient` and thus he can collect all the rewards and the true auction reward recipient will not get any.

## Proof of Concept

The issue occurs in the `rngComplete` function below :

```solidity
function rngComplete(
    uint256 _randomNumber,
    uint256 _rngCompletedAt,
    address _rewardRecipient, // @audit can set any address
    uint32 _sequenceId,
    AuctionResult calldata _rngAuctionResult
) external returns (bytes32) {
    // @audit should only be callable by rngAuctionRelayer
    if (_sequenceHasCompleted(_sequenceId)) revert SequenceAlreadyCompleted();
    uint64 _auctionElapsedSeconds = uint64(
        block.timestamp < _rngCompletedAt ? 0 : block.timestamp - _rngCompletedAt
    );
    if (_auctionElapsedSeconds > (_auctionDurationSeconds - 1)) revert AuctionExpired();
    // Calculate the reward fraction and set the draw auction results
    UD2x18 rewardFraction = _fractionalReward(_auctionElapsedSeconds);
    _auctionResults.rewardFraction = rewardFraction;
    _auctionResults.recipient = _rewardRecipient;
    _lastSequenceId = _sequenceId;

    AuctionResult[] memory auctionResults = new AuctionResult[](2);
    auctionResults[0] = _rngAuctionResult;
    auctionResults[1] = AuctionResult({
        rewardFraction: rewardFraction,
        recipient: _rewardRecipient
    });

    uint32 drawId = prizePool.closeDraw(_randomNumber);

    uint256 futureReserve = prizePool.reserve() + prizePool.reserveForOpenDraw();
    uint256[] memory _rewards = RewardLib.rewards(auctionResults, futureReserve);

    emit RngSequenceCompleted(
        _sequenceId,
        drawId,
        _rewardRecipient,
        _auctionElapsedSeconds,
        rewardFraction
    );

    for (uint8 i = 0; i < _rewards.length; i++) {
        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));
}
```

As we can see the function does not have any access control (modifier or check on the msg.sender), so any user can call it and you can also notice that the `_rewardRecipient` (the address that receives the rewards) is given as argument to the function and there is no check to verify that it is the correct auction reward receiver. 

Hence an attacker can call the function before the relayer does, he can thus complete the auction and give another
Anybody can call the RngRelayAuction::rngComplete() function and send data at will to manipulate the result of the draw
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Impact
- Users can manipulate the result of the draw by calling the [`RngRelayAuction::rngComplete()`](https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L125-L176) function and sending data at their convenience 

## Proof of Concept
- The [`RngRelayAuction::rngComplete()`](https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L125-L176) is on charge of closing the draw in the PrizePool as well as withdrawing the reserve and send it to the rewardRecipient.
- **All the data that should be used to execute the `rngComplete()` function should be computed by the relayer**, this function forwards the computed data from the relayer onto the PrizePool.

- **The problem is that this function has no access control**, there is any check to validate if the caller is the relayer, which means that **anybody can call this function and set the parameters at their convenience**, which will lead to close draws using values that could benefit the caller and all the rest of users will be impacted because the randomNumber is now manipulated at the convenience of whoever calls first the `rngComplete()`.

## Tools Used
Manual Audit

## Recommended Mitigation Steps
- Add an Access Control Mechanism to ensure that only the authorized relayer can call the [`RngRelayAuction::rngComplete()`](https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L125-L176)
- Use the [`rngAuctionRelayer` variable which as stated by the comments in the code](https://github.com/GenerationSoftware/pt-v5-draw-auction/blob/f1c6d14a1772d6609de1870f8713fb79977d51c1/src/RngRelayAuction.sol#L77-L79), this variable stores the address of the relayer that RNG results must originate from.

```solidity
function rngComplete(
  ...
) external returns (bytes32) {
+ require(msg.sender == rngAuctionRelayer, "Caller is not the authorized relayer");

  ...
  ...
  ...
}
```


## Assessed type

Invalid Validation
canStartNextSequence() function does not check if the auction has already been completed
Audit: 2023-08-pooltogether-findings
Severity: HIGH

# Lines of code

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


# Vulnerability details

## Description

The canStartNextSequence() function in the RngAuction contract has a bug that could result in an incorrect reward being awarded. The function currently checks if the current auction has expired, but it does not check if the auction has already been completed. This means that it is possible for the startRngRequest() function to be called even if the auction has already been completed, which would result in an incorrect reward being awarded.

## Impact

The bug could result in an incorrect reward being awarded to the winner of the auction. If the startRngRequest() function is called after the auction has already been completed, the reward will be awarded to the wrong person.

## Proof of Concept


// Deploy the RngAuction contract
contract RngAuction is Ownable {
  ...
}

// Set the `_nextRng` variable to a valid RNG service.
RngAuction contract = new RngAuction(rngService);

// Wait for the auction to complete.
uint64 currentSequenceId = contract.openSequenceId();
uint64 lastAuctionSequenceId = contract.lastAuction.sequenceId;
while (currentSequenceId == lastAuctionSequenceId) {
  // Wait for the auction to complete.
  currentSequenceId = contract.openSequenceId();
}

// Call the `startRngRequest()` function.
contract.startRngRequest(msg.sender);

// Check the value of the `_lastAuction.sequenceId` variable.
if (contract.lastAuction.sequenceId == currentSequenceId) {
  // The bug is present.
}



// The output of the POC test
> The bug is present.


As we can see, the output of the POC test indicates that the bug is present. This is because the startRngRequest() function was able to be called even after the auction had already been completed. This would result in the reward being awarded to the wrong person.

## Tools Used

REMIX IDE

## Recommended Mitigation Steps

he suggested fix for the bug in the canStartNextSequence() function in the RngAuction contract is to add an additional check to the function to see if the auction has already been completed. The following code can be added to the function:

if (_lastAuction.sequenceId == _openSequenceId) {
  // The auction has already been completed
  return false;
}
This check will ensure that the startRngRequest() function is only called if the auction has not already been completed. This will prevent incorrect rewards from being awarded.

Here is the complete code for the canStartNextSequence() function with the fix:

function canStartNextSequence() internal view returns (bool) {
  if (_lastAuction.sequenceId == _openSequenceId) {
    // The auction has already been completed
    return false;
  }

  // The auction has not yet been completed
  return true;
}


## Assessed type

Other