The PriorityPool contract contains a pauseForUpdate function designed to pause critical operations during key updates (such as recalculating distribution or updating the Merkle tree). However, several critical functions, including deposit, withdraw, and unqueueTokens, do not have the whenNotPaused modifier applied, allowing them to continue execution even when the contract is paused. This renders the pausing mechanism ineffective and can lead to potential inconsistencies in the contract's state during updates.
The functions affected by this issue are located in the PriorityPool contract:
deposit(uint256 _amount, bool _shouldQueue, bytes[] calldata _data)
withdraw(uint256 _amountToWithdraw, uint256 _amount, uint256 _sharesAmount, bytes32[] calldata _merkleProof, bool _shouldUnqueue, bool _shouldQueueWithdrawal)
These functions do not have the whenNotPaused modifier, which is typically used in conjunction with the OpenZeppelin Pausable contract to prevent execution during a paused state.
The pauseForUpdate function is called by the distribution oracle to temporarily halt operations during important updates to the contract’s state:
This calls OpenZeppelin’s _pause() function, which sets the contract to a paused state. However, without applying the whenNotPaused modifier to critical functions, they will continue to execute regardless of whether the contract is paused.
The pauseForUpdate function is intended to ensure that no new deposits, withdrawals, or unqueuing actions occur during sensitive updates (e.g., when recalculating the Merkle tree). Without enforcing the paused state, operations can continue, causing the contract to enter an inconsistent state.
If users are allowed to deposit or withdraw tokens while the distribution data is being updated, it could lead to incorrect tracking of user balances or queued tokens. This is especially critical in a contract that handles staking and liquidity, where token balances and queued amounts need to remain accurate.
This oversight can introduce vulnerabilities where state inconsistencies are exploited, particularly during the time-sensitive update process. The purpose of pausing is to ensure no external interactions can disrupt the update flow. Without the pause being respected, reentrancy or race conditions could arise.
The distribution oracle calls pauseForUpdate() to pause the contract and recalculate the distribution.
While the contract is in the paused state, a user calls the deposit function to queue tokens.
Since the deposit function does not have the whenNotPaused modifier, the operation proceeds, disrupting the distribution update.
This can cause the contract to incorrectly track the user’s balance, resulting in an inconsistent state.
The absence of the whenNotPaused modifier on critical functions like deposit, withdraw, and unqueueTokens can cause the contract’s state to become inconsistent during important updates, such as Merkle tree recalculations. If operations are performed during updates, this can lead to incorrect balances, queued tokens, and unclaimed token distributions.
Users can exploit this flaw by making transactions during a paused state, which may cause data mismatches or trigger vulnerabilities related to token accounting. For instance, deposits or withdrawals occurring during sensitive state transitions can open the door to potential attacks, such as double spending or improper token minting.
The primary purpose of pausing the contract is to ensure a safe and correct distribution of liquid staking tokens or rewards. When users are allowed to interact with the contract during the paused state, it risks disrupting the accurate distribution of tokens.
Manual Review
Ensure that all critical functions, particularly deposit, withdraw, and unqueueTokens, are protected by the whenNotPaused modifier to prevent them from being executed during a paused state.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.