Minting and Burning Liquidity Pool Tokens
Last updated
Last updated
Code4rena High Bug Bounty Payout
Impact:
The Pool contract permits users to burn liquidity provider (LP) tokens without concurrently withdrawing the corresponding tokens. This ability creates an opportunity for malicious actors to manipulate the pool's rate adversely, rendering it impossible for users to receive any LP tokens when depositing tokens into the pool. Such manipulation primarily hinges on the calculation of liquidity tokens at Utils:calcLiquidityUnits
, where a minimal total supply (e.g., 1) leads to all computed units rounding down to zero.
Proof of Concept:
Pool's Burn Logic:
Utils' LP Token Calculation:
Below is an example script where a user attempts to deposit 1M tokens into a pool with a total supply of just 1:
This script demonstrates that a pool with a total supply of 1 yields zero LP tokens for a deposit of 1M tokens, indicating a significant flaw in the system.
How the Vulnerability Works:
Given that any user can initiate a Pool via the PoolFactory, a malicious actor can create a pool, burn their LP tokens, and set the total supply to 1. Consequently, this actor becomes the sole owner of the pool's LP tokens henceforth.
Recommended Mitigation Steps:
Consider eliminating the burn functionality or limit its access to only privileged users to prevent arbitrary burning of LP tokens.
Implement safeguards within the smart contract to prevent the total supply from dropping to levels that would cause the rounding issue, like establishing a minimum total supply limit.
Review and revise the liquidity calculation logic to ensure that it handles edge cases gracefully, without leaving room for exploitation. Ensure thorough testing and auditing of the revised logic to confirm its robustness and security.
Impact:
A malicious actor can monitor the SetPricePerShare
event, exploiting it if the price data becomes stale due to a lack of updates, thus manipulating the mint()
function to use an outdated pricePerShare
that differs from the current market price. This user can then wait for the price to be updated, subsequently using the burn()
function with the newly updated pricePerShare
. This process allows the actor to make a risk-free profit at the expense of the contract's holdings.
Proof of Concept:
Recommended Mitigation Steps:
Introduce a Threshold Variable: Implement a threshold variable in the WrappedIbbtcEth
contract that defines the maximum allowable time since the last pricePerShare
update.
Develop Two Transfer Variants: Create two variants of transferFrom
and transfer
functions. Both should check if the current time minus the time of the last price update is less than the defined threshold:
If the condition is met, both function variants execute the transfer.
If the condition is not met, the first variant should revert, while the second one triggers a price update (even though it might be more gas-intensive).
Limit Exposure: With the threshold in place, if a scheduled price update fails (due to network issues or other reasons), the associated risk is confined to the market volatility during the threshold time, effectively capping the exposure.
Price Updating Mechanism: The pricePerShare
variable in WrappedIbbtcEth
is updated by the externally invoked updatePricePerShare
function. However, the variable is utilized in mint
, burn
, and transfer
functions without any check for its freshness, making it susceptible to being outdated or stalled. This stale data issue can occur if the updatePricePerShare
function fails to run due to various reasons, including bugs, system-level dependencies, or network outages. (See: and )