hats-finance / Possum-Labs--Portals--0xed8965d49b8aeca763447d56e6da7f4e0506b2d3

GNU General Public License v2.0
0 stars 2 forks source link

Add dust amount check in `stake()` function. #24

Open hats-bug-reporter[bot] opened 11 months ago

hats-bug-reporter[bot] commented 11 months ago

Github username: @pavankv241 Twitter username: @PavanKumarKv2 Submission hash (on-chain): 0xbf1e2542281915652a11d7455bc260ebe3bcc9913a55de4d23d0633f399a7e86 Severity: low

Description:

Description

Implement a dust amount check mechanism to prevent users to stake small amounts and earn equivalent pool energy and more PSM tokens than usual. As the PSM pool grows, dust users can call the sellPortalEnergy() function to redeem pool energy for PSM tokens based on reserves.

  1. sellPortalEnergy() function

    
    function sellPortalEnergy(uint256 _amountInput, uint256 _minReceived, uint256 _deadline) external nonReentrant existingAccount {
        /// @dev Require that the input amount is greater than zero
        if (_amountInput == 0) {revert InvalidInput();}
    
        /// @dev Require that the deadline has not expired
        if (_deadline < block.timestamp) {revert DeadlineExpired();}
    
        /// @dev Update the stake data of the user
        _updateAccount(msg.sender,0);
    
        /// @dev Require that the user has enough portalEnergy to sell
        if(accounts[msg.sender].portalEnergy < _amountInput) {revert InsufficientBalance();}
    
        /// @dev Calculate the PSM token reserve (output)
        uint256 reserve0 = IERC20(PSM_ADDRESS).balanceOf(address(this)) - fundingRewardPool;
    
        /// @dev Calculate the reserve of portalEnergy (input)
        uint256 reserve1 = constantProduct / reserve0;
    
        /// @dev Calculate the amount of output token received based on the amount of portalEnergy sold
        uint256 amountReceived = (_amountInput * reserve0) / (_amountInput + reserve1);
    
        /// @dev Require that the amount of output token received is greater than or equal to the minimum expected output
        if(amountReceived < _minReceived) {revert InvalidOutput();}
    
        /// @dev Reduce the portalEnergy balance of the user by the amount of portalEnergy sold
        accounts[msg.sender].portalEnergy -= _amountInput;
    
        /// @dev Send the output token to the user
        IERC20(PSM_ADDRESS).safeTransfer(msg.sender, amountReceived);
    
        /// @dev Emit the portalEnergySellExecuted event with the user's address and the amount of portalEnergy sold
        emit PortalEnergySellExecuted(msg.sender, _amountInput);
    }
In this function, the user can specify the exact _amountInput amount which will be equal to pool energy, but the actual amount received, `amountReceived`, will be determined based on the relationship between reserves and the constant product , this will differ as pool grows.

## Impact 
Users can stake very small amount after some time pool grows they can redeem for more PSM tokens.

## Recommendation
Add minimum deposit in `stake()` function .
```solidity
uint256 minStakeAmount ; // Set a minimum stake amount, adjust as needed

if (_amount < minStakeAmount) {
    revert InsufficientStakeAmount();
}
PossumLabsCrypto commented 11 months ago

Thanks for the submission!

I´m not sure I understand the negative impact here.

Receiving more PSM for the same amount of Portal Energy if there is more PSM inside the pool is the normal function of a constant product DEX. Solidity still rounds down at the precision limit, so if anything, users will "lose money" if they only sell very small amounts.

The pool itself cannot grow nor shrink as the constant product is fixed at the moment of Portal activation. (see activatePortal())

Can you give a numeric example of the issue?

pavankv241 commented 11 months ago

Hi ,@PossumLabsCrypto

Here some example

1.two users contribute PSM tokens (100 1e18) each means (200 1e18). Here fundingBalance variable will be 200*1e18.

  1. btokens minted for each contribution (1000 1e18) each means (2000 1e18)

  2. Now if user calls activatePortal() function requiredPortalEnergyLiquidity = (200 1e18) (550) = 110000 1e18 constantProduct = (2001e18) (110000 1e18) = 22000000 * 1e36

  3. new Dust user stakes HLP tokens (1e18 / 2 = 1e17). maxStakeDebt and portal Energy will be 24657534246575342.

  4. Again same Dust user calls sellPortalEnergy() function with exact maxStakeDebt and portalEnergy assigned in above step. Note :- in this function we are not considering fundingRewardPool and _updateAccount(msg.sender,0); Because update will increase the dust user portal energy which means amountReceived will be more than explained here. reserve0 = 200 1e18. reserve1 = 110000 1e18; ( It includes division of constantProduct also). amountReceived = 44831870398833. (PSM transfer to DustUser).

We can run below code in Remix

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.15;

contract DustEX {

      uint256  _amountInput = 246575342465753424; // new user's  max value and portal energy when stake 1e18
      uint256  _amountinput2 =24657534246575342;  // new user's  max value and portal energy when stake 1e17.

      uint256  reserve0 = 200 * 1e18;
      uint256  reserve1 = 110000 * 1e18; // It includes constantProduct also.
      uint256 maxLockDuration = 7776000 ;
      uint256 constant private SECONDS_PER_YEAR = 31536000; 

function amountR_When_Stake_1e18( uint256 _amountInputZ ) public view returns(uint256 amountReceived) {

     amountReceived = (_amountInputZ * reserve0) / (_amountInputZ + reserve1);

}

// new user maxStake and portal Energy . 

function maxStake(uint _amount) public view returns(uint maxStakeDebt){

     maxStakeDebt = (_amount * maxLockDuration) / SECONDS_PER_YEAR;

}

}
// Amount received = 448317799536688 when 1e18.
// Amount recevied = 44831870398833  when 1e17.

We will try to provide POC in foundry (error occurred ) and please correct if this wrong calculation.

PossumLabsCrypto commented 11 months ago

Thank you for the POC.

This is exactly how it should work. 🙂

Scenarios A) staked 1e18 & sold PE -> output 448317799536688 PSM B) staked 1e17 & sold PE -> output 44831870398833 PSM

The general condition that more input brings more output is true: inputA > inputB && outputA > outputB

And also the price impact of selling is visible: inputA == inputB 10 && outputA < outputB 10

I suggest to not lose more time on this. 🤝