Closed sherlock-admin2 closed 6 months ago
See comments here, where pools can be unblocked at any time by admin/user syncing reserves. Admin can first withdraw maintenance fee to set respective MT_FEE to zero before performing unblocking, in that case, user would simply be losing by paying maintenance fee to try and grief pool.
Varun_05
high
Base/Quote token reserve can become equal to zero when K=0
Summary
For some liquidity pools which have LP fee rate equal to zero or to be precise less than 0.1 percent it can cause the base/quote token reserve to be equal to zero which can have a great impact for example if a new liquidity providers calls the buy shares function to buy shares and sends the tokens then buy shares function would revert and it would not be possible to refill the reserves until all the shares supply becomes equal to zero
Vulnerability Detail
According to the docs https://docs.dodoex.io/en/product/fees there are liquidity pools with lp fee rate equal to zero. So lets start with the buyShares function which as follows Note the following poc applies when k = 0
Lets take I price = 10**18
So now 1500 shares would be minted and mint function as as follows
Now _BASERESERVE = 1500 _QUOTERESERVE = 1500
So till now _BASERESERVE = 1500 , _QUOTERESERVE = 1500, totalSupply = 1500, _BASETARGET = 1500 _QUOTETARGET = 1500
Now a user might be shareholder or can be anyone calls the sell quote function and transfer 1500 quote tokens to the contract
So now quoteBalance becomes 3000 quoteinput = 1500
Then querySellQuote function is called with the values as follows querySellQuote(tx.origin,1500)
to get the state getPMMState(); is called which as follows
pluggin in the values e18 = 10**18 state.i = e18; state.K = 0(for this particular case it is equal to 0 }; state.B = 1500; state.Q = 1500; state.B0 = _BASETARGET; // will be calculated in adjustedTarget state.Q0 = _QUOTETARGET; state.R = PMMPricing.RState(RState) = ONE PMMPricing.adjustedTarget(state); this doesn't do anything as intially rstate = one
Now the following function is called PMMPricing.sellQuoteToken(state, payQuoteAmount); here payQuoteAmount = 1500
As state.R == RState.ONE is true then if condition will be executed with payQuoteAmount = 1500
ROneSellQuoteToken(state, 1500); will be called ----------- 6th step
Now following function is called with V0 = 1500,V1 = 1500, delta = 1500,i = e18,k = 0
Now as k =0 following will be executed
So DecimalMath.mulFloor(i, delta) = DecimalMath.mulFloor(e18, 1500) = 1500 and V1 = 1500 so the returned value is equal to 1500
So _ROneSellQuoteToken(state, 1500) highlighted above as 6th step returns 1500 So (receiveBaseAmount, newRState) = PMMPricing.sellQuoteToken(state, payQuoteAmount); returns (1500,RState.ABOVE_ONE)
Therefore receiveBaseAmount = 1500 Now the following calculations will be done
As lp fee rate is taken as zero Let's take _MT_FEERATE = 5% i.e 5e16 So mtFee = DecimalMath.mulFloor(receiveBaseAmount, mtFeeRate); = 75 receiveBaseAmount = 1500-0-75 = 1425 newQuoteTarget = state.Q0 = 1500
Back to original sellquote function
receiveBaseAmount = 1425 mtFee = 75 newRState = above one newQuoteTarget = 1500 Then _transferBaseOut(to, receiveBaseAmount); is called i.e _transferBaseOut(to, 1425); Now initially only first shareHolder had transferred 1500 base tokens and now we have transferred 1425 so left amount of base tokens = 75 _MT_FEEBASE = _MT_FEEBASE + mtFee; = 0 + 75 therefore _MT_FEEBASE = 75 Finally we update the reserves by following _setReserve((_BASETOKEN.balanceOf(address(this)) - _MT_FEEBASE), quoteBalance); _BASETOKEN.balanceOf(address(this)) = 75 as we have transferred 1425 to the user _MT_FEEBASE = 75 quoteBalance = 3000 therefore _setReserve is called as follows _setReserve(0,3000) and set reserve function is as follows
Now _BASERESERVE = 0 _QUOTERESERVE = 1500
Similarly we can prove for quote Reserve = 0
Impact
Now if the initial Shareholder hasn't sold its shares and reduced the totalSupply to zero, then nobody would be able to buy shares until base reserve becomes greater than zero i.e when base tokens are sold. This is because when buyShares function will be called neither if condition will be satisfied nor else if
so shares will be equal to zero and when _mint(to,0) will be called it will revert because minimum shares minted are 1000. So this can cause DOS of buy shares function
Code Snippet
https://github.com/sherlock-audit/2023-12-dodo-gsp/blob/main/dodo-gassaving-pool/contracts/GasSavingPool/impl/GSPFunding.sol#L31
https://github.com/sherlock-audit/2023-12-dodo-gsp/blob/main/dodo-gassaving-pool/contracts/GasSavingPool/impl/GSPTrader.sol#L79
Tool used
Manual Review
Recommendation
Introduce lp mint fe and that too greater than 0.1% or you can handle the case when base/quote token reserve = 0 just like the way you do for totalSupply == 0 case
Duplicate of #49