sherlock-audit / 2023-04-gmx-judging

2 stars 1 forks source link

chaduke - PositionUtils.validatePosition() uses ``isIncrease`` instead of ``false`` when calling isPositionLiquidatable(), making it not work properly for the case of ``isIncrease = true``. #180

Open sherlock-admin opened 1 year ago

sherlock-admin commented 1 year ago

chaduke

medium

PositionUtils.validatePosition() uses isIncrease instead of false when calling isPositionLiquidatable(), making it not work properly for the case of isIncrease = true.

Summary

PositionUtils.validatePosition() uses isIncrease instead of false when calling isPositionLiquidatable(), making it not work properly for the case of isIncrease = true. The main problem is that when calling isPositionLiquidatable(), we should always consider decreasing the position since we are proposing a liquidation trade (which is a decrease in position). Therefore, it should not use isIncrease for the input parameter for isPositionLiquidatable(). We should always use false instead.

Vulnerability Detail

PositionUtils.validatePosition() is called to validate whether a position is valid in both collateral size and position size, and in addition, to check if the position is liquidable:

https://github.com/sherlock-audit/2023-04-gmx/blob/main/gmx-synthetics/contracts/position/PositionUtils.sol#L261-L296

It calls function isPositionLiquidatable() to check if a position is liquidable. However, it passes the isIncrease to function isPositionLiquidatable() as an argument. Actually, the false value should always be used for calling function isPositionLiquidatable() since a liquidation is always a decrease position operation. A position is liquidable or not has nothing to do with exiting trade operations and only depend on the parameters of the position per se.

Current implementation has a problem for an increase order: Given a Increase order, for example, increase a position by $200, when PositionUtils.validatePosition() is called, which is after the position has been increased, we should not consider another $200 increase in isPositionLiquidatable() again as part of the price impact calculation. This is double-accouting for price impact calculation, one during the position increasing process, and another in the position validation process. On the other hand, if we use false here, then we are considering a decrease order (since a liquidation is a decrease order) and evaluate the hypothetical price impact if the position will be liquidated.

https://github.com/sherlock-audit/2023-04-gmx/blob/main/gmx-synthetics/contracts/position/PositionUtils.sol#L304-L412

Our POC code confirms my finding: intially, we don't have any positions, after executing a LimitIncrease order, the priceImpactUsd is evaluaed as follows (notice initialDiffUsd = 0):

PositionPricingUtils.getPriceImpactUsd started... openInterestParams.longOpenInterest: 0 openInterestParams.shortOpenInterest: 0 initialDiffUsd: 0 nextDiffUsd: 1123456700000000000000000000000 positiveImpactFactor: 50000000000000000000000 negativeImpactFactor: 100000000000000000000000 positiveImpactUsd: 0 negativeImpactUsd: 63107747838744499100000 deltaDiffUsd: 63107747838744499100000 priceImpactUsd: -63107747838744499100000 PositionPricingUtils.getPriceImpactUsd() completed. Initial priceImpactUsd: -63107747838744499100000 Capped priceImpactUsd: -63107747838744499100000

Then, during validation, when PositionUtils.validatePosition() is called, the double accouting occurs, notice the nextDiffUsd is doubled, as if the limitOrder was executed for another time!

PositionPricingUtils.getPriceImpactUsd started... openInterestParams.longOpenInterest: 1123456700000000000000000000000 openInterestParams.shortOpenInterest: 0 initialDiffUsd: 1123456700000000000000000000000 nextDiffUsd: 2246913400000000000000000000000 impactFactor: 100000000000000000000000 impactExponentFactor: 2000000000000000000000000000000 deltaDiffUsd: 189323243516233497450000 priceImpactUsd: -189323243516233497450000 priceImpactUsd: -189323243516233497450000 adjusted 2: priceImpactUsd: 0

The POC code is as follows, pay attention to the testLimit() and the execution of createLimitIncreaseOrder(). Please comment out the checks for signature, timestamp and block number for oracle price in the source code to run the testing smoothly without revert.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import "../contracts/role/RoleStore.sol";
import "../contracts/router/ExchangeRouter.sol";
import "../contracts/data/DataStore.sol";
import "../contracts/referral/ReferralStorage.sol";

import "../contracts/token/IWNT.sol";
import "../contracts/token/WNT.sol";
import "../contracts/token/SOLToken.sol";
import "../contracts/token/USDC.sol";
import "../contracts/token/tokenA.sol";
import "../contracts/token/tokenB.sol";
import "../contracts/token/tokenC.sol";

import "../contracts/market/MarketFactory.sol";
import "../contracts/deposit/DepositUtils.sol";
import "../contracts/oracle/OracleUtils.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "../contracts/withdrawal/WithdrawalUtils.sol";
import "../contracts/order/Order.sol";
import "../contracts/order/BaseOrderUtils.sol";
import "../contracts/price/Price.sol";
import "../contracts/utils/Debug.sol";
import "../contracts/position/Position.sol";
import "../contracts/exchange/LiquidationHandler.sol";
import "../contracts/utils/Calc.sol";
import "@openzeppelin/contracts/utils/math/SignedMath.sol";
import "@openzeppelin/contracts/utils/math/SafeCast.sol";

contract CounterTest is Test, Debug{
     using SignedMath for int256;
    using SafeCast for uint256;

    WNT _wnt; 
    USDC _usdc;
    SOLToken _sol;
    tokenA _tokenA;
    tokenB _tokenB;
    tokenC _tokenC;

    RoleStore _roleStore;
    Router _router;
    DataStore _dataStore;
    EventEmitter _eventEmitter;
    DepositVault _depositVault;
    OracleStore _oracleStore; 
    Oracle _oracle;
    DepositHandler _depositHandler;
    WithdrawalVault _withdrawalVault;
    WithdrawalHandler _withdrawalHandler;
    OrderHandler _orderHandler;
    SwapHandler _swapHandler;
    LiquidationHandler _liquidationHandler;
    ReferralStorage _referralStorage;
    OrderVault _orderVault;
    ExchangeRouter _erouter;
    MarketFactory _marketFactory;
    Market.Props _marketProps1;
    Market.Props _marketPropsAB;
    Market.Props _marketPropsBC;
    Market.Props _marketPropsCwnt;

    address depositor1;
    address depositor2;
    address depositor3;
    address uiFeeReceiver = address(333);

    function testGetFundingAmountPerSizeDelta() public{
        uint result = MarketUtils.getFundingAmountPerSizeDelta(2e15, 1e15+1, true);
        console2.log("result: %d", result);
        uint256 correctResult = 2e15 * 1e15 * 1e30 + 1e15; // this is a real round up
        correctResult = correctResult/(1e15+1);
        console2.log("correctResult: %d", correctResult);
        assertTrue(result  == 1e15 * 1e30);
    }

    function setUp() public {
        _wnt = new WNT();
        _usdc = new USDC();
        _sol = new SOLToken();
        _tokenA = new tokenA();
        _tokenB = new tokenB();
         _tokenC = new tokenC();

         _roleStore = new RoleStore();
         _router = new Router(_roleStore);
         _dataStore = new DataStore(_roleStore);

         _eventEmitter= new EventEmitter(_roleStore);
        _depositVault = new DepositVault(_roleStore, _dataStore);
        _oracleStore = new OracleStore(_roleStore, _eventEmitter);
        _oracle = new Oracle(_roleStore, _oracleStore);
        console2.logString("_oracle:"); console2.logAddress(address(_oracle));

         _depositHandler = new DepositHandler(_roleStore, _dataStore, _eventEmitter, _depositVault, _oracle);
         console2.logString("_depositHandler:"); console2.logAddress(address(_depositHandler));

       _withdrawalVault = new WithdrawalVault(_roleStore, _dataStore);
        _withdrawalHandler = new WithdrawalHandler(_roleStore, _dataStore, _eventEmitter, _withdrawalVault, _oracle);

        _swapHandler = new SwapHandler(_roleStore);
        _orderVault = new OrderVault(_roleStore, _dataStore);
        _referralStorage = new ReferralStorage();

        _orderHandler = new OrderHandler(_roleStore, _dataStore, _eventEmitter, _orderVault, _oracle, _swapHandler, _referralStorage);  
        _erouter = new ExchangeRouter(_router, _roleStore, _dataStore, _eventEmitter, _depositHandler, _withdrawalHandler, _orderHandler);
         console2.logString("_erouter:"); console2.logAddress(address(_erouter));
         _liquidationHandler = new LiquidationHandler(_roleStore, _dataStore, _eventEmitter, _orderVault, _oracle, _swapHandler, _referralStorage);

        _referralStorage.setHandler(address(_orderHandler), true);  

        /* set myself as the controller so that I can set the address of WNT (wrapped native token contracdt) */
        _roleStore.grantRole(address(this), Role.CONTROLLER);
        _roleStore.grantRole(address(this), Role.MARKET_KEEPER);

        _dataStore.setUint(Keys.MAX_SWAP_PATH_LENGTH, 5); // at most 5 markets in the path

        _dataStore.setAddress(Keys.WNT, address(_wnt));

        /* set the token transfer gas limit for wnt as 3200 */
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_wnt)), 32000);  
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_usdc)), 32000);  

        /* create a market (SQL, WNT, ETH, USDC) */
        _marketFactory = new MarketFactory(_roleStore, _dataStore, _eventEmitter);
        console2.logString("_marketFactory:"); console2.logAddress(address(_marketFactory));
        _roleStore.grantRole(address(_marketFactory), Role.CONTROLLER); // to save a market's props
        _roleStore.grantRole(address(_erouter), Role.CONTROLLER); 
        _roleStore.grantRole(address(_depositHandler), Role.CONTROLLER); 
        _roleStore.grantRole(address(_withdrawalHandler), Role.CONTROLLER); 
         _roleStore.grantRole(address(_swapHandler), Role.CONTROLLER);
        _roleStore.grantRole(address(_orderHandler), Role.CONTROLLER);   
        _roleStore.grantRole(address(_liquidationHandler), Role.CONTROLLER);     
        _roleStore.grantRole(address(_oracleStore), Role.CONTROLLER); // so it can call EventEmitter
        _roleStore.grantRole(address(_oracle), Role.CONTROLLER); // so it can call EventEmitter
        _roleStore.grantRole(address(this), Role.ORDER_KEEPER);
        _roleStore.grantRole(address(this), Role.LIQUIDATION_KEEPER);

        _marketProps1 = _marketFactory.createMarket(address(_sol), address(_wnt), address(_usdc), keccak256(abi.encode("sol-wnt-usdc"))); 
        _marketPropsAB = _marketFactory.createMarket(address(0), address(_tokenA), address(_tokenB), keccak256(abi.encode("swap-tokenA-tokenB"))); 
        _marketPropsBC = _marketFactory.createMarket(address(0), address(_tokenB), address(_tokenC), keccak256(abi.encode("swap-tokenB-tokenC"))); 
        _marketPropsCwnt = _marketFactory.createMarket(address(0), address(_tokenC), address(_wnt), keccak256(abi.encode("swap-tokenC-wnt"))); 

        _dataStore.setUint(Keys.minCollateralFactorForOpenInterestMultiplierKey(_marketProps1.marketToken, true), 1e25);
        _dataStore.setUint(Keys.minCollateralFactorForOpenInterestMultiplierKey(_marketProps1.marketToken, false), 1e25);

        // see fees for the market
        _dataStore.setUint(Keys.swapFeeFactorKey(_marketProps1.marketToken), 0.05e30); // 5%
        _dataStore.setUint(Keys.SWAP_FEE_RECEIVER_FACTOR, 0.5e30);
        _dataStore.setUint(Keys.positionFeeFactorKey(_marketProps1.marketToken), 0.00001234e30); // 2%
        _dataStore.setUint(Keys.POSITION_FEE_RECEIVER_FACTOR, 0.15e30);
         _dataStore.setUint(Keys.MAX_UI_FEE_FACTOR, 0.01e30);
        _dataStore.setUint(Keys.uiFeeFactorKey(uiFeeReceiver), 0.01e30); // only when this is set, one can receive ui fee, so stealing is not easy
        _dataStore.setInt(Keys.poolAmountAdjustmentKey(_marketProps1.marketToken, _marketProps1.longToken), 1);
        _dataStore.setInt(Keys.poolAmountAdjustmentKey(_marketProps1.marketToken, _marketProps1.shortToken), 1);
        _dataStore.setUint(Keys.swapImpactExponentFactorKey(_marketProps1.marketToken), 10e28);
        _dataStore.setUint(Keys.swapImpactFactorKey(_marketProps1.marketToken, true), 0.99e30);
        _dataStore.setUint(Keys.swapImpactFactorKey(_marketProps1.marketToken, false), 0.99e30);

        // set gas limit to transfer a token
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_sol)), 32000);  
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_wnt)), 32000);  
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_usdc)), 32000);  
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_tokenA)), 32000);  
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_tokenB)), 32000);  
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_tokenC)), 32000); 
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_marketProps1.marketToken)), 32000);  
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_marketPropsAB.marketToken)), 32000);
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_marketPropsBC.marketToken)), 32000);
        _dataStore.setUint(Keys.tokenTransferGasLimit(address(_marketPropsCwnt.marketToken)), 32000);

        /* Configure the system parameters/limits here */
        _dataStore.setUint(Keys.MAX_CALLBACK_GAS_LIMIT, 10000);
        _dataStore.setUint(Keys.EXECUTION_GAS_FEE_BASE_AMOUNT, 100);
        _dataStore.setUint(Keys.MAX_ORACLE_PRICE_AGE, 2 hours);
        _dataStore.setUint(Keys.MIN_ORACLE_BLOCK_CONFIRMATIONS, 3);
        _dataStore.setUint(Keys.MIN_COLLATERAL_USD, 1e30);  // just require $1 as min collateral usd
        _dataStore.setUint(Keys.reserveFactorKey(_marketProps1.marketToken, true), 5e29); // 50%
        _dataStore.setUint(Keys.reserveFactorKey(_marketProps1.marketToken, false), 5e29);
        _dataStore.setUint(Keys.fundingExponentFactorKey(_marketProps1.marketToken), 1.1e30); // 2 in 30 decimals like a square, cube, etc
         _dataStore.setUint(Keys.fundingFactorKey(_marketProps1.marketToken), 0.0000001e30);
         _dataStore.setUint(Keys.borrowingFactorKey(_marketProps1.marketToken, true), 0.87e30);
         _dataStore.setUint(Keys.borrowingFactorKey(_marketProps1.marketToken, false), 0.96e30);
         _dataStore.setUint(Keys.borrowingExponentFactorKey(_marketProps1.marketToken, true), 2.1e30);
         _dataStore.setUint(Keys.borrowingExponentFactorKey(_marketProps1.marketToken, false), 2.3e30);
         _dataStore.setUint(Keys.positionImpactExponentFactorKey(_marketProps1.marketToken), 2e30);
         _dataStore.setUint(Keys.positionImpactFactorKey(_marketProps1.marketToken, true), 5e22); 
         _dataStore.setUint(Keys.positionImpactFactorKey(_marketProps1.marketToken, false), 1e23);

        // set the limit of market tokens

        _dataStore.setUint(Keys.maxPoolAmountKey(_marketProps1.marketToken, _marketProps1.longToken), 1000e18);
        _dataStore.setUint(Keys.maxPoolAmountKey(_marketProps1.marketToken, _marketProps1.shortToken), 1000e18);
        _dataStore.setUint(Keys.maxPoolAmountKey(_marketPropsAB.marketToken, _marketPropsAB.longToken), 1000e18);
        _dataStore.setUint(Keys.maxPoolAmountKey(_marketPropsAB.marketToken, _marketPropsAB.shortToken), 1000e18);
        _dataStore.setUint(Keys.maxPoolAmountKey(_marketPropsBC.marketToken, _marketPropsBC.longToken), 1000e18);
        _dataStore.setUint(Keys.maxPoolAmountKey(_marketPropsBC.marketToken, _marketPropsBC.shortToken), 1000e18);
        _dataStore.setUint(Keys.maxPoolAmountKey(_marketPropsCwnt.marketToken, _marketPropsCwnt.longToken), 1000e18);
        _dataStore.setUint(Keys.maxPoolAmountKey(_marketPropsCwnt.marketToken, _marketPropsCwnt.shortToken), 1000e18);

        // set max open interest for each market
        _dataStore.setUint(Keys.maxOpenInterestKey(_marketProps1.marketToken, true), 1e39); // 1B $ 
        _dataStore.setUint(Keys.maxOpenInterestKey(_marketProps1.marketToken, false), 1e39); // 1B $

        _dataStore.setUint(Keys.maxPnlFactorKey(Keys.MAX_PNL_FACTOR_FOR_WITHDRAWALS, _marketProps1.marketToken, true), 10**29); // maxPnlFactor = 10% for long
         _dataStore.setUint(Keys.maxPnlFactorKey(Keys.MAX_PNL_FACTOR_FOR_WITHDRAWALS, _marketProps1.marketToken, false), 10**29); // maxPnlFactor = 10% for short
        // _dataStore.setBool(Keys.cancelDepositFeatureDisabledKey(address(_depositHandler)), true);
        _dataStore.setBool(Keys.cancelOrderFeatureDisabledKey(address(_orderHandler), uint256(Order.OrderType.MarketIncrease)), true);

         addFourSigners();
         address(_wnt).call{value: 10000e18}("");
         depositor1 = address(0x801);
         depositor2 = address(0x802);
         depositor3 = address(0x803);

         // make sure each depositor has some tokens.
         _wnt.transfer(depositor1, 1000e18);
         _wnt.transfer(depositor2, 1000e18);
         _wnt.transfer(depositor3, 1000e18);  
         _usdc.transfer(depositor1, 1000e18);
         _usdc.transfer(depositor2, 1000e18);
         _usdc.transfer(depositor3, 1000e18);
         _tokenA.transfer(depositor1, 1000e18);
         _tokenB.transfer(depositor1, 1000e18);
         _tokenC.transfer(depositor1, 1000e18);   

         printAllTokens();                 
    }

    error Unauthorized(string);
   // error Error(string);

function testLimit() public{
          OracleUtils.SetPricesParams memory priceParams = createSetPricesParams();

          vm.roll(block.number+2); // block 3

          bytes32 key = createDepositNoSwap(_marketProps1, depositor1, 90e18, true); // create a deposit at block 3 which is within range (2, 6)          
          _depositHandler.executeDeposit(key, priceParams);  
          uint mintedMarketTokens = IERC20(_marketProps1.marketToken).balanceOf(depositor1);
          key = createDepositNoSwap(_marketProps1, depositor1, 100e18, false); // create a deposit at block 3 which is within range (2, 6)          
          _depositHandler.executeDeposit(key, priceParams);  
          mintedMarketTokens = IERC20(_marketProps1.marketToken).balanceOf(depositor1);
          console2.log("Experiment 1  is completed.");     

          // console2.log("PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP");

          key = createMarketSwapOrder(depositor1,  address(_wnt), 1e15); // create a deposit at block 3 which is within range (2, 6)          
          _orderHandler.executeOrder(key, priceParams);  
          console2.log("Experiment 2  is completed.");    

        console2.log("\n\n depositor 1 createMarketIncreaseOrder");
         key = createMarketIncreaseOrder(depositor1, _marketProps1.marketToken, _marketProps1.longToken, 20e18, 1001e30, 106000000000000, true); // 
         console2.log("\nExecuting the order...");
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor1, _marketProps1.marketToken, _marketProps1.longToken, true);
         console2.log("Experiment 3  is completed.");     

        console2.log("\n\n depositor 2 createMarketIncreaseOrder");
         key = createMarketIncreaseOrder(depositor2, _marketProps1.marketToken, _marketProps1.longToken, 110e18, 13e30, 101000000000000, false); // 110 usdc as collateral
         console2.log("\nExecuting the order...");
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor2, _marketProps1.marketToken, _marketProps1.longToken, false);
       console2.log("Experiment 4  is completed.");     

        console2.log("PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP");
        vm.warp(2 days);
        setIndexTokenPrice(priceParams, 98, 100); // send 20e18 USDC, increase $13.123 in a long position with trigger price 101
        key = createLimitIncreaseOrder(depositor3, _marketProps1.marketToken, _marketProps1.shortToken, 23e18, 1.1234567e30, 101000000000000, true); // collateral token, usdsize, price
        console2.log("a LimitIncrease order created by depositor3 with key: ");
         console2.logBytes32(key);
         Position.printPosition(_dataStore, depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true);
         console2.log("\n\nExecuting the order, exiting moment...\n\n");
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true);
         console2.log("Experiment  5 is completed.\n");     

        // depositor3 creates a LimitDecrease order
        /*
        setIndexTokenPrice(priceParams, 120, 125);
        key = createLimitDecreaseOrder(depositor3, _marketProps1.marketToken, _marketProps1.shortToken, 7e18, 58e30, 120000000000000, 120000000000000, true); // retrieve $50? collateral token, usdsize, acceptible price
        console2.log("a LimitIncrease order created by depositor3 with key: ");
         console2.logBytes32(key);
         Position.printPosition(_dataStore, depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true);
         console2.log("\n\nExecuting the order, exiting moment...\n\n");
          _orderHandler.executeOrder(key, priceParams);          
        console2.log("Experiment 7 for is completed.");     
        */
}

function testMarketDecrease() public{

          OracleUtils.SetPricesParams memory priceParams = createSetPricesParams();

          vm.roll(block.number+2); // block 3

          bytes32 key = createDepositNoSwap(_marketProps1, depositor1, 90e18, true); // create a deposit at block 3 which is within range (2, 6)          
          _depositHandler.executeDeposit(key, priceParams);  
          uint mintedMarketTokens = IERC20(_marketProps1.marketToken).balanceOf(depositor1);
          key = createDepositNoSwap(_marketProps1, depositor1, 100e18, false); // create a deposit at block 3 which is within range (2, 6)          
          _depositHandler.executeDeposit(key, priceParams);  
          mintedMarketTokens = IERC20(_marketProps1.marketToken).balanceOf(depositor1);
         console2.log("Experiment 1  is completed.");     

          console2.log("\n\n depositor 2 deposit into marketProps1");
          key = createDepositNoSwap(_marketProps1, depositor2, 100e18, true);
          _depositHandler.executeDeposit(key, priceParams);
         mintedMarketTokens = IERC20(_marketProps1.marketToken).balanceOf(depositor2);
          printPoolsAmounts();
         console2.log("Experiment 2  is completed.");     

        console2.log("\n\n depositor 1 createMarketIncreaseOrder");
         key = createMarketIncreaseOrder(depositor1, _marketProps1.marketToken, _marketProps1.longToken, 20e18, 1e25, 106000000000000, true); // 
         console2.log("\nExecuting the order...");
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor1, _marketProps1.marketToken, _marketProps1.longToken, true);
         console2.log("Experiment 3  is completed.");     

        console2.log("\n\n depositor 2 createMarketIncreaseOrder");
         key = createMarketIncreaseOrder(depositor2, _marketProps1.marketToken, _marketProps1.longToken, 110e18, 1e25, 101000000000000, false); // 110 usdc as collateral
         console2.log("\nExecuting the order...");
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor2, _marketProps1.marketToken, _marketProps1.longToken, false);
       console2.log("Experiment 4  is completed.");     

        console2.log("********************************************");

         // deposit 2 will execute a marketDecreaseOrder now
         key =  createMarketDecreaseOrder(depositor2, _marketProps1.marketToken, _marketProps1.longToken, 70000000000000, 5e23, false) ; // decrease by 5%
         console2.log("a market desced order created with key: ");
         console2.logBytes32(key);
         console2.log("\nExecuting the order...");         
         setIndexTokenPrice(priceParams, 60, 65); // we have a profit for a short position
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor2, _marketProps1.marketToken, _marketProps1.longToken, false);
         console2.log("Experiment 5  is completed.");  

          printAllTokens();
}   

function  testLiquidation() public{
          // blockrange (2, 6)
          OracleUtils.SetPricesParams memory priceParams = createSetPricesParams();

          vm.roll(block.number+2); // block 3

          bytes32 key = createDepositNoSwap(_marketProps1, depositor1, 90e18, true); // create a deposit at block 3 which is within range (2, 6)          
          _depositHandler.executeDeposit(key, priceParams);  
          uint mintedMarketTokens = IERC20(_marketProps1.marketToken).balanceOf(depositor1);
          key = createDepositNoSwap(_marketProps1, depositor1, 100e18, false); // create a deposit at block 3 which is within range (2, 6)          
          _depositHandler.executeDeposit(key, priceParams);  
          mintedMarketTokens = IERC20(_marketProps1.marketToken).balanceOf(depositor1);
         console2.log("Experiment 1  is completed.");     

          console2.log("\n\n depositor 2 deposit into marketProps1");
          key = createDepositNoSwap(_marketProps1, depositor2, 100e18, true);
          _depositHandler.executeDeposit(key, priceParams);
         mintedMarketTokens = IERC20(_marketProps1.marketToken).balanceOf(depositor2);
          printPoolsAmounts();
         console2.log("Experiment 2  is completed.");     

        console2.log("\n\n depositor 1 createMarketIncreaseOrder");
         key = createMarketIncreaseOrder(depositor1, _marketProps1.marketToken, _marketProps1.longToken, 10e18, 1e25, 106000000000000, true);
         console2.log("\nExecuting the order...");
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor1, _marketProps1.marketToken, _marketProps1.longToken, true);
         console2.log("Experiment 3  is completed.");     

        console2.log("\n\n depositor 2 createMarketIncreaseOrder");
         key = createMarketIncreaseOrder(depositor2, _marketProps1.marketToken, _marketProps1.shortToken, 100e18, 1e25, 101000000000000, false);
         console2.log("\nExecuting the order...");
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor2, _marketProps1.marketToken, _marketProps1.shortToken, false);
       console2.log("Experiment 4  is completed.");     

         // deposit 2 will execute a marketDecreaseOrder now
         key =  createMarketDecreaseOrder(depositor2, _marketProps1.marketToken, _marketProps1.shortToken, 106000000000000, 5e23, false) ; // decrease by 5%
         console2.log("a market desced order created with key: ");
         console2.logBytes32(key);
         console2.log("\nExecuting the order...");         
         setIndexTokenPrice(priceParams, 84, 90);
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor2, _marketProps1.marketToken, _marketProps1.shortToken, false);
         console2.log("Experiment 5  is completed.");     

        // depositor3 will execute a LimitIncrease Order now
        key = createMarketIncreaseOrder(depositor3, _marketProps1.marketToken, _marketProps1.shortToken, 20e18, 200e30, 101000000000000, true); // collateral token, usdsize, price
        console2.log("a LimitIncrease order created by depositor3 with key: ");
         console2.logBytes32(key);
         Position.printPosition(_dataStore, depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true);
         console2.log("\n\nExecuting the order, exiting moment...\n\n");
         _orderHandler.executeOrder(key, priceParams);
         Position.printPosition(_dataStore, depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true);
         console2.log("Experiment  6 is completed.\n");     

        // depositor3 creates a LimitDecrease order
        setIndexTokenPrice(priceParams, 120, 125);
        key = createLimitDecreaseOrder(depositor3, _marketProps1.marketToken, _marketProps1.shortToken, 7e18, 58e30, 120000000000000, 120000000000000, true); // retrieve $50? collateral token, usdsize, acceptible price
        console2.log("a LimitIncrease order created by depositor3 with key: ");
         console2.logBytes32(key);
         Position.printPosition(_dataStore, depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true);
         console2.log("\n\nExecuting the order, exiting moment...\n\n");
          _orderHandler.executeOrder(key, priceParams);          
        console2.log("Experiment 7 for is completed.");     

        // depositor3 creates a stopLossDecrease order
        setIndexTokenPrice(priceParams, 97, 99);
        key = createStopLossDecrease(depositor3, _marketProps1.marketToken, _marketProps1.shortToken, 7e18, 58e30, 95000000000000, 92000000000000, true); // retrieve $50? collateral token, usdsize, acceptible price
        console2.log("a StopLossDecrease order created by depositor3 with key: ");
         console2.logBytes32(key);
         // Position.printPosition(_dataStore, depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true);

        console2.log("\n\nExecuting the order, exiting moment...\n\n");
        _orderHandler.executeOrder(key, priceParams);
        console2.log("Experiment 8 is completed.");     

         console2.log("\n\n*************************************************\n\n");

        // depositor3 creates a Liquidation order
        setIndexTokenPrice(priceParams, 75, 75);
        console2.log("Liquidate a position...");
        Position.printPosition(_dataStore, depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true);
        _liquidationHandler.executeLiquidation(depositor3, _marketProps1.marketToken, _marketProps1.shortToken, true, priceParams);
        console2.log("Experiment 9 is completed.");     

         // printPoolsAmounts();
         printAllTokens();

}

function printAllTokens() startedCompleted("printAllTokens") public
{
      console2.log("\nTokens used in this test:");
        console2.log("_wnt: "); console2.logAddress(address(_wnt));
        console2.log("_usdc: "); console2.logAddress(address(_usdc));
        console2.log("_sol: "); console2.logAddress(address(_sol));
        console2.log("_tokenA: "); console2.logAddress(address(_tokenA));
        console2.log("_tokenB: "); console2.logAddress(address(_tokenB));
        console2.log("_tokenC: "); console2.logAddress(address(_tokenC));
        console2.logString("test contract address:"); console2.logAddress(address(this));

        console2.log("_marketProps1 market token: "); console2.logAddress(address(_marketProps1.marketToken));
        console2.log("_marketPropsAB market token: "); console2.logAddress(address(_marketPropsAB.marketToken));
        console2.log("_marketPropsBC market token: "); console2.logAddress(address(_marketPropsBC.marketToken));
        console2.log("_marketProps1Cwnt market token: "); console2.logAddress(address(_marketPropsCwnt.marketToken));
        console2.log("\n");

}

function printMarketTokenAmount() public 
{   console2.log("Market token address: ");
    console2.logAddress(address(_marketProps1.marketToken));
    console2.log("depositor1 market token amount: %d", IERC20(_marketProps1.marketToken).balanceOf(depositor1));
    console2.log("depositor2 market token amount: %d", IERC20(_marketProps1.marketToken).balanceOf(depositor2));
    console2.log("depositor3 market token amount: %d", IERC20(_marketProps1.marketToken).balanceOf(depositor3));
}

function printLongShortTokens(address account) public
{
    console2.log("balance for "); console2.logAddress(account);
    console2.log("_wnt balance:", _wnt.balanceOf(account));
    console2.log("usdc balance:", _usdc.balanceOf(account));
}

function addFourSigners() private {
    _oracleStore.addSigner(address(901));
    _oracleStore.addSigner(address(902));    
    _oracleStore.addSigner(address(903));   
    _oracleStore.addSigner(address(904));   
}

function setIndexTokenPrice(OracleUtils.SetPricesParams memory priceParams, uint256 minP, uint256 maxP) public
{
    uint256 mask1 = ~uint256(type(uint96).max);   // (32*3 of 1's)
    console2.logBytes32(bytes32(mask1));

    uint256 minPrice = minP;
    minPrice = minPrice << 32 | minP;
    minPrice = minPrice << 32 | minP;

    uint256 maxPrice = maxP;
    maxPrice = maxPrice << 32 | maxP;
    maxPrice = maxPrice << 32 | maxP;

    priceParams.compactedMinPrices[0] = (priceParams.compactedMinPrices[0] & mask1) | minPrice;
    priceParams.compactedMaxPrices[0] = (priceParams.compactedMaxPrices[0] & mask1) | maxPrice;
}

function createSetPricesParams() public returns (OracleUtils.SetPricesParams memory) {
          uint256 signerInfo = 3;    // signer 904
          signerInfo = signerInfo << 16 | 2; // signer 903
          signerInfo = signerInfo << 16 | 1; // signer 902
          signerInfo = signerInfo << 16 | 3; // number of singers
          // will read out as 902, 903, 904 from the lowest first

          // the number of tokens, 6
          address[] memory tokens = new address[](6);
          tokens[0] = address(_sol);
          tokens[1] = address(_wnt);
          tokens[2] = address(_usdc);
          tokens[3] = address(_tokenA);
          tokens[4] = address(_tokenB);
          tokens[5] = address(_tokenC);

         // must be equal to the number of tokens 6, 64 for each one, so 64*6. 64*4 for one element, so need two elements 
          uint256[] memory compactedMinOracleBlockNumbers = new uint256[](2);
          compactedMinOracleBlockNumbers[0] = block.number+1;
          compactedMinOracleBlockNumbers[0] = compactedMinOracleBlockNumbers[0] << 64 | block.number+1;
          compactedMinOracleBlockNumbers[0] = compactedMinOracleBlockNumbers[0] << 64 | block.number+1;
          compactedMinOracleBlockNumbers[0] = compactedMinOracleBlockNumbers[0] << 64 | block.number+1;

          compactedMinOracleBlockNumbers[1] = block.number+1;
          compactedMinOracleBlockNumbers[1] = compactedMinOracleBlockNumbers[0] << 64 | block.number+1;

        // must be equal to the number of tokens 6, 64 for each one, so 64*6. 64*4 for one element, so need two elements 

          uint256[] memory compactedMaxOracleBlockNumbers = new uint256[](2);
          compactedMaxOracleBlockNumbers[0] = block.number+5; 
          compactedMaxOracleBlockNumbers[0] = compactedMaxOracleBlockNumbers[0] << 64 | block.number+5;
          compactedMaxOracleBlockNumbers[0] = compactedMaxOracleBlockNumbers[0] << 64 | block.number+5;  
          compactedMaxOracleBlockNumbers[0] = compactedMaxOracleBlockNumbers[0] << 64 | block.number+5;  

          compactedMaxOracleBlockNumbers[1] = block.number+5; 
          compactedMaxOracleBlockNumbers[1] = compactedMaxOracleBlockNumbers[0] << 64 | block.number+5;

         // must be equal to the number of tokens 6, 64 for each one, so 64*6. 64*4 for one element, so need two elements 
          uint256[] memory compactedOracleTimestamps = new uint256[](2);
          compactedOracleTimestamps[0]  =  9;
          compactedOracleTimestamps[0] = compactedOracleTimestamps[0] << 64 | 8;
          compactedOracleTimestamps[0] = compactedOracleTimestamps[0] << 64 | 7;
          compactedOracleTimestamps[0] = compactedOracleTimestamps[0] << 64 | 7;

          compactedOracleTimestamps[1]  =  9;
          compactedOracleTimestamps[1] = compactedOracleTimestamps[0] << 64 | 8;

           // must be equal to the number of tokens, 8 for each, so 8*6= 48, only need one element
           uint256[] memory compactedDecimals = new uint256[](1);
           compactedDecimals[0] =  12;
           compactedDecimals[0] =  compactedDecimals[0] << 8 | 12;
           compactedDecimals[0] =  compactedDecimals[0] << 8 | 12;
           compactedDecimals[0] =  compactedDecimals[0] << 8 | 12;
           compactedDecimals[0] =  compactedDecimals[0] << 8 | 12;
           compactedDecimals[0] =  compactedDecimals[0] << 8 | 12;

           // three signers, 6 tokens, so we have 3*6 = 18 entries, each entry takes 32 bits, so each 8 entries takes one element, we need 3 elements
           // price table:
           //      SOL:        100 101 102
           //      wnt:        200 201 203
           //      USDC        1   1    1
           //      tokenA      100 101  102
           //      tokenB      200 202  204
           //      tokenC      400 404  408

           uint256[] memory compactedMinPrices = new uint256[](3);
           compactedMinPrices[2] = 408;        
           compactedMinPrices[2] = compactedMinPrices[2] << 32 | 404;

           compactedMinPrices[1] = 400;
           compactedMinPrices[1] = compactedMinPrices[1] << 32 | 204;
           compactedMinPrices[1] = compactedMinPrices[1] << 32 | 202;
           compactedMinPrices[1] = compactedMinPrices[1] << 32 | 200;
           compactedMinPrices[1] = compactedMinPrices[1] << 32 | 102;
           compactedMinPrices[1] = compactedMinPrices[1] << 32 | 101;
           compactedMinPrices[1] = compactedMinPrices[1] << 32 | 100;
           compactedMinPrices[1] = compactedMinPrices[1] << 32 | 1;

           compactedMinPrices[0] = 1;
           compactedMinPrices[0] = compactedMinPrices[0] << 32 | 1;
           compactedMinPrices[0] = compactedMinPrices[0] << 32 | 203;
           compactedMinPrices[0] = compactedMinPrices[0] << 32 | 201;
           compactedMinPrices[0] = compactedMinPrices[0] << 32 | 200;
           compactedMinPrices[0] = compactedMinPrices[0] << 32 | 102;
           compactedMinPrices[0] = compactedMinPrices[0] << 32 | 101;
           compactedMinPrices[0] = compactedMinPrices[0] << 32 | 100;

           // three signers, 6 tokens, so we have 3*6 = 18 entries, each entry takes 8 bits, so we just need one element

           uint256[] memory compactedMinPricesIndexes = new uint256[](1);
           compactedMinPricesIndexes[0] = 1;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 2;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 0;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 1;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 2;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 0;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 1;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 2;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 0;    
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 1;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 2;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 0;    
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 1;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 2;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 0;    
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 1;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 2;
           compactedMinPricesIndexes[0] = compactedMinPricesIndexes[0] << 8 | 0;    

           // three signers, 6 tokens, so we have 3*6 = 18 entries, each entry takes 32 bits, so each 8 entries takes one element, we need 3 elements
           // price table:
           //      SOL:        105 106 107
           //      wnt:        205 206 208
           //      USDC        1   1    1
           //      tokenA      105 106  107
           //      tokenB      205 207  209
           //      tokenC      405 409  413
           uint256[] memory compactedMaxPrices = new uint256[](3);
           compactedMaxPrices[2] = 413;
           compactedMaxPrices[2] = compactedMaxPrices[2] << 32 | 409;

           compactedMaxPrices[1] = 405;
           compactedMaxPrices[1] = compactedMaxPrices[1] << 32 | 209;
           compactedMaxPrices[1] = compactedMaxPrices[1] << 32 | 207;
           compactedMaxPrices[1] = compactedMaxPrices[1] << 32 | 205;
           compactedMaxPrices[1] = compactedMaxPrices[1] << 32 | 107;
           compactedMaxPrices[1] = compactedMaxPrices[1] << 32 | 106;
           compactedMaxPrices[1] = compactedMaxPrices[1] << 32 | 105;
           compactedMaxPrices[1] = compactedMaxPrices[1] << 32 | 1;

           compactedMaxPrices[0] = 1;
           compactedMaxPrices[0] = compactedMaxPrices[0] << 32 | 1;
           compactedMaxPrices[0] = compactedMaxPrices[0] << 32 | 208;
           compactedMaxPrices[0] = compactedMaxPrices[0] << 32 | 206; 
           compactedMaxPrices[0] = compactedMaxPrices[0] << 32 | 205; 
           compactedMaxPrices[0] = compactedMaxPrices[0] << 32 | 107;
           compactedMaxPrices[0] = compactedMaxPrices[0] << 32 | 106;
           compactedMaxPrices[0] = compactedMaxPrices[0] << 32 | 105;

            // three signers, 6 tokens, so we have 3*6 = 18 entries, each entry takes 8 bits, so we just need one element

            uint256[] memory compactedMaxPricesIndexes = new uint256[](1);
            compactedMaxPricesIndexes[0] = 1; 
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 2;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 0;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 1;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 2;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 0;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 1;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 2;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 0;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 1;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 2;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 0;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 1;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 2;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 0;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 1;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 2;
            compactedMaxPricesIndexes[0] = compactedMaxPricesIndexes[0] << 8 | 0;

           // 3 signers and 6 tokens, so we need 3*6 signatures
            bytes[] memory signatures = new bytes[](18);
            for(uint i; i<18; i++){
                signatures[i] = abi.encode("SIGNATURE");
            }
            address[] memory priceFeedTokens;

          OracleUtils.SetPricesParams memory priceParams = OracleUtils.SetPricesParams(
               signerInfo,
               tokens,
               compactedMinOracleBlockNumbers,
               compactedMaxOracleBlockNumbers,
               compactedOracleTimestamps,
               compactedDecimals,
               compactedMinPrices, 
               compactedMinPricesIndexes,
               compactedMaxPrices, 
               compactedMaxPricesIndexes, 
               signatures, 
               priceFeedTokens
          );
          return priceParams;
}

/* 
*  The current index token price (85, 90), a trader sets a trigger price to 100 and then acceptabiel price to 95.
*  He like to long the index token. 
*  1. Pick the primary price 90 since we long, so choose the max
*  2. Make sure 90 < 100, and pick (90, 100) as the custom price since we long
*  3. Choose price 95 since 95 is within the range, and it is the highest acceptible price. Choosing 90 
*      will be in favor of the trader
* 
*/

function createMarketSwapOrder(address account,  address inputToken, uint256 inAmount) public returns(bytes32)
{   
    address[] memory swapPath = new address[](1);
    swapPath[0] =  _marketProps1.marketToken;
    // swapPath[0] = _marketPropsAB.marketToken;
    // swapPath[1] = _marketPropsBC.marketToken;
    // swapPath[2] = _marketPropsCwnt.marketToken;

    vm.prank(account);    
    _wnt.transfer(address(_orderVault), 3200);  // execution fee

    BaseOrderUtils.CreateOrderParams memory params;
    params.addresses.receiver = account;            // the account is the receiver
    params.addresses.callbackContract = address(0);
    params.addresses.uiFeeReceiver = account;   // set myself as the ui receiver
    // params.addresses.market = marketToken;
    params.addresses.initialCollateralToken = inputToken; // initial token
    params.addresses.swapPath = swapPath;

    // params.numbers.sizeDeltaUsd = sizeDeltaUsd;
    params.numbers.initialCollateralDeltaAmount = inAmount ; // this is actually useless, will be overidden by real transfer amount
     vm.prank(account);    
    IERC20(inputToken).transfer(address(_orderVault), inAmount);  // this is the real amount

    // params.numbers.triggerPrice = triggerPrice;
    // params.numbers.acceptablePrice = acceptablePrice; // I can buy with this price or lower effective spread control 
    params.numbers.executionFee = 3200;
    params.numbers.callbackGasLimit = 3200;
   // params.numbers.initialCollateralDeltaAmount = inAmount;
    params.numbers.minOutputAmount = 100; // use the control the final collateral amount, not for the position size delta, which is indirectly controlled by acceptable price

    params.orderType = Order.OrderType.MarketSwap;
    params.decreasePositionSwapType = Order.DecreasePositionSwapType.NoSwap;
    // params.isLong = isLong;
    params.shouldUnwrapNativeToken = false;
    params.referralCode = keccak256(abi.encode("MY REFERRAL"));

    vm.prank(account);
    bytes32 key = _erouter.createOrder(params);
    return key;
}

function createLiquidationOrder(address account, address marketToken, address collateralToken, uint256 collateralAmount, uint sizeDeltaUsd, uint triggerPrice, uint256 acceptablePrice, bool isLong) public returns(bytes32)
{
    address[] memory swapPath;

    //address[] memory swapPath = new address[](3);
    //swapPath[0] = _marketPropsAB.marketToken;
    //swapPath[1] = _marketPropsBC.marketToken;
    //swapPath[2] = _marketPropsCwnt.marketToken;

    vm.prank(account);    
    _wnt.transfer(address(_orderVault), 3200);  // execution fee

    BaseOrderUtils.CreateOrderParams memory params;
    params.addresses.receiver = account;
    params.addresses.callbackContract = address(0);
    params.addresses.uiFeeReceiver = uiFeeReceiver;
    params.addresses.market = marketToken; // final market
    params.addresses.initialCollateralToken = collateralToken; // initial token
    params.addresses.swapPath = swapPath;

    params.numbers.sizeDeltaUsd = sizeDeltaUsd;
    // params.numbers.initialCollateralDeltaAmount = ; // this is actually useless, will be overidden by real transfer amount
     vm.prank(account);    
    IERC20(collateralToken).transfer(address(_orderVault), collateralAmount);  // this is the real amount

    params.numbers.triggerPrice = triggerPrice;
    params.numbers.acceptablePrice = acceptablePrice; // I can buy with this price or lower effective spread control 
    params.numbers.executionFee = 3200;
    params.numbers.callbackGasLimit = 3200;
    params.numbers.minOutputAmount = 100; // use the control the final collateral amount, not for the position size delta, which is indirectly controlled by acceptable price

    params.orderType = Order.OrderType.Liquidation;
    params.decreasePositionSwapType = Order.DecreasePositionSwapType.NoSwap;
    params.isLong = isLong;
    params.shouldUnwrapNativeToken = false;
    params.referralCode = keccak256(abi.encode("MY REFERRAL"));

    vm.prank(account);
    bytes32 key = _erouter.createOrder(params);
    return key;
}    

function createStopLossDecrease(address account, address marketToken, address collateralToken, uint256 collateralAmount, uint sizeDeltaUsd, uint triggerPrice, uint256 acceptablePrice, bool isLong) public returns(bytes32)
{
    address[] memory swapPath;

    //address[] memory swapPath = new address[](3);
    //swapPath[0] = _marketPropsAB.marketToken;
    //swapPath[1] = _marketPropsBC.marketToken;
    //swapPath[2] = _marketPropsCwnt.marketToken;

    vm.prank(account);    
    _wnt.transfer(address(_orderVault), 3200);  // execution fee

    BaseOrderUtils.CreateOrderParams memory params;
    params.addresses.receiver = account;
    params.addresses.callbackContract = address(0);
    params.addresses.uiFeeReceiver = uiFeeReceiver;
    params.addresses.market = marketToken; // final market
    params.addresses.initialCollateralToken = collateralToken; // initial token
    params.addresses.swapPath = swapPath;

    params.numbers.sizeDeltaUsd = sizeDeltaUsd;
    // params.numbers.initialCollateralDeltaAmount = ; // this is actually useless, will be overidden by real transfer amount
     vm.prank(account);    
    IERC20(collateralToken).transfer(address(_orderVault), collateralAmount);  // this is the real amount

    params.numbers.triggerPrice = triggerPrice;
    params.numbers.acceptablePrice = acceptablePrice; // I can buy with this price or lower effective spread control 
    params.numbers.executionFee = 3200;
    params.numbers.callbackGasLimit = 3200;
    params.numbers.minOutputAmount = 100; // use the control the final collateral amount, not for the position size delta, which is indirectly controlled by acceptable price

    params.orderType = Order.OrderType.StopLossDecrease;
    params.decreasePositionSwapType = Order.DecreasePositionSwapType.NoSwap;
    params.isLong = isLong;
    params.shouldUnwrapNativeToken = false;
    params.referralCode = keccak256(abi.encode("MY REFERRAL"));

    vm.prank(account);
    bytes32 key = _erouter.createOrder(params);
    return key;
}    

function createLimitDecreaseOrder(address account, address marketToken, address collateralToken, uint256 collateralAmount, uint sizeDeltaUsd, uint triggerPrice, uint256 acceptablePrice, bool isLong) public returns(bytes32)
{
    address[] memory swapPath;

    //address[] memory swapPath = new address[](3);
    //swapPath[0] = _marketPropsAB.marketToken;
    //swapPath[1] = _marketPropsBC.marketToken;
    //swapPath[2] = _marketPropsCwnt.marketToken;

    vm.prank(account);    
    _wnt.transfer(address(_orderVault), 3200);  // execution fee

    BaseOrderUtils.CreateOrderParams memory params;
    params.addresses.receiver = account;
    params.addresses.callbackContract = address(0);
    params.addresses.uiFeeReceiver = uiFeeReceiver;
    params.addresses.market = marketToken; // final market
    params.addresses.initialCollateralToken = collateralToken; // initial token
    params.addresses.swapPath = swapPath;

    params.numbers.sizeDeltaUsd = sizeDeltaUsd;
    // params.numbers.initialCollateralDeltaAmount = ; // this is actually useless, will be overidden by real transfer amount
     vm.prank(account);    
    IERC20(collateralToken).transfer(address(_orderVault), collateralAmount);  // this is the real amount

    params.numbers.triggerPrice = triggerPrice;
    params.numbers.acceptablePrice = acceptablePrice; // I can buy with this price or lower effective spread control 
    params.numbers.executionFee = 3200;
    params.numbers.callbackGasLimit = 3200;
    params.numbers.minOutputAmount = 100; // use the control the final collateral amount, not for the position size delta, which is indirectly controlled by acceptable price

    params.orderType = Order.OrderType.LimitDecrease;
    params.decreasePositionSwapType = Order.DecreasePositionSwapType.NoSwap;
    params.isLong = isLong;
    params.shouldUnwrapNativeToken = false;
    params.referralCode = keccak256(abi.encode("MY REFERRAL"));

    vm.prank(account);
    bytes32 key = _erouter.createOrder(params);
    return key;
}    

function createLimitIncreaseOrder(address account, address marketToken, address collateralToken, uint256 collateralAmount, uint sizeDeltaUsd, uint triggerPrice, bool isLong) public returns(bytes32)
{
    address[] memory swapPath;

    //address[] memory swapPath = new address[](3);
    //swapPath[0] = _marketPropsAB.marketToken;
    //swapPath[1] = _marketPropsBC.marketToken;
    //swapPath[2] = _marketPropsCwnt.marketToken;

    vm.prank(account);    
    _wnt.transfer(address(_orderVault), 3200);  // execution fee

    BaseOrderUtils.CreateOrderParams memory params;
    params.addresses.receiver = account;
    params.addresses.callbackContract = address(0);
    params.addresses.uiFeeReceiver = uiFeeReceiver;
    params.addresses.market = marketToken; // final market
    params.addresses.initialCollateralToken = collateralToken; // initial token
    params.addresses.swapPath = swapPath;

    params.numbers.sizeDeltaUsd = sizeDeltaUsd;
    // params.numbers.initialCollateralDeltaAmount = ; // this is actually useless, will be overidden by real transfer amount
     vm.prank(account);    
    IERC20(collateralToken).transfer(address(_orderVault), collateralAmount);  // this is the real amount

    params.numbers.triggerPrice = triggerPrice;   // used for limit order
    params.numbers.acceptablePrice = 121000000000000; // I can buy with this price or lower effective spread control 
    params.numbers.executionFee = 3200;
    params.numbers.callbackGasLimit = 3200;
    params.numbers.minOutputAmount = 100; // use the control the final collateral amount, not for the position size delta, which is indirectly controlled by acceptable price

    params.orderType = Order.OrderType.LimitIncrease;
    params.decreasePositionSwapType = Order.DecreasePositionSwapType.NoSwap;
    params.isLong = isLong;
    params.shouldUnwrapNativeToken = false;
    params.referralCode = keccak256(abi.encode("MY REFERRAL"));

    vm.prank(account);
    bytes32 key = _erouter.createOrder(params);
    return key;
}    

function createMarketDecreaseOrder(address account, address marketToken, address collateralToken,  uint256 acceptablePrice, uint256 sizeInUsd, bool isLong) public returns(bytes32)
{
    address[] memory swapPath;

    //address[] memory swapPath = new address[](3);
    //swapPath[0] = _marketPropsAB.marketToken;
    //swapPath[1] = _marketPropsBC.marketToken;
    //swapPath[2] = _marketPropsCwnt.marketToken;

    vm.prank(account);    
    _wnt.transfer(address(_orderVault), 3200);  // execution fee

    BaseOrderUtils.CreateOrderParams memory params;
    params.addresses.receiver = account;
    params.addresses.callbackContract = address(0);
    params.addresses.uiFeeReceiver = uiFeeReceiver;
    params.addresses.market = marketToken; // final market
    params.addresses.initialCollateralToken = collateralToken; // initial token
    params.addresses.swapPath = swapPath;

    params.numbers.sizeDeltaUsd = sizeInUsd; // how much dollar to decrease, will convert into amt of tokens to decrease in long/short based on the execution price
    params.numbers.initialCollateralDeltaAmount = 13e18; // this is actually useless, will be overidden by real transfer amount
    // vm.prank(account);    
    // IERC20(collateralToken).transfer(address(_orderVault), collateralAmount);  // this is the real amount

    params.numbers.triggerPrice = 0;
    params.numbers.acceptablePrice = acceptablePrice; // I can buy with this price or lower effective spread control 
    params.numbers.executionFee = 3200;
    params.numbers.callbackGasLimit = 3200;
    params.numbers.minOutputAmount = 10e18; // use the control the final collateral amount, not for the position size delta, which is indirectly controlled by acceptable price

    params.orderType = Order.OrderType.MarketDecrease;
    params.decreasePositionSwapType = Order.DecreasePositionSwapType.NoSwap;
    params.isLong = isLong;
    params.shouldUnwrapNativeToken = false;
    params.referralCode = keccak256(abi.encode("MY REFERRAL"));

    vm.prank(account);
    bytes32 key = _erouter.createOrder(params);
    return key;
}    

function createMarketIncreaseOrder(address account, address marketToken, address collateralToken, uint256 collateralAmount, uint sizeDeltaUsd, uint acceptablePrice, bool isLong) public returns(bytes32)
{
    address[] memory swapPath;

    //address[] memory swapPath = new address[](3);
    //swapPath[0] = _marketPropsAB.marketToken;
    //swapPath[1] = _marketPropsBC.marketToken;
    //swapPath[2] = _marketPropsCwnt.marketToken;

    vm.prank(account);    
    _wnt.transfer(address(_orderVault), 3200);  // execution fee

    BaseOrderUtils.CreateOrderParams memory params;
    params.addresses.receiver = account;
    params.addresses.callbackContract = address(0);
    params.addresses.uiFeeReceiver = uiFeeReceiver;
    params.addresses.market = marketToken; // final market
    params.addresses.initialCollateralToken = collateralToken; // initial token
    params.addresses.swapPath = swapPath;

    params.numbers.sizeDeltaUsd = sizeDeltaUsd;
    // params.numbers.initialCollateralDeltaAmount = ; // this is actually useless, will be overidden by real transfer amount
     vm.prank(account);    
    IERC20(collateralToken).transfer(address(_orderVault), collateralAmount);  // this is the real amount

    params.numbers.triggerPrice = 0;
    params.numbers.acceptablePrice = acceptablePrice; // I can buy with this price or lower effective spread control 
    params.numbers.executionFee = 3200;
    params.numbers.callbackGasLimit = 3200;
    params.numbers.minOutputAmount = 100; // use the control the final collateral amount, not for the position size delta, which is indirectly controlled by acceptable price

    params.orderType = Order.OrderType.MarketIncrease;
    params.decreasePositionSwapType = Order.DecreasePositionSwapType.NoSwap;
    params.isLong = isLong;
    params.shouldUnwrapNativeToken = false;
    params.referralCode = keccak256(abi.encode("MY REFERRAL"));

    vm.prank(account);
    bytes32 key = _erouter.createOrder(params);
    return key;
}    

function createWithdraw(address withdrawor, uint marketTokenAmount) public returns (bytes32)
{
    address[] memory longTokenSwapPath;
    address[] memory shortTokenSwapPath;

    console.log("createWithdraw with withdrawor: ");
    console.logAddress(withdrawor);
     vm.prank(withdrawor);    
    _wnt.transfer(address(_withdrawalVault), 3200);  // execution fee

    vm.prank(withdrawor);
    ERC20(_marketProps1.marketToken).transfer(address(_withdrawalVault), marketTokenAmount);

    WithdrawalUtils.CreateWithdrawalParams memory params = WithdrawalUtils.CreateWithdrawalParams(
        withdrawor, // receiver
        address(0), // call back function
        uiFeeReceiver, // uiFeeReceiver
        _marketProps1.marketToken, // which market token to withdraw
        longTokenSwapPath,
        shortTokenSwapPath,
        123, // minLongTokenAmount
        134, // minShortTokenAmount
        false, // shouldUnwrapNativeToken
        3200, // execution fee
        3200 // callback gas limit
    );

    vm.prank(withdrawor);
    bytes32 key =   _erouter.createWithdrawal(params);
    return key;
}

function createDepositNoSwap(Market.Props memory marketProps, address depositor, uint amount, bool isLong) public returns (bytes32){
    address[] memory longTokenSwapPath;
    address[] memory shortTokenSwapPath;

    console.log("createDeposit with depositor: ");
    console.logAddress(depositor);

     vm.prank(depositor);
    _wnt.transfer(address(_depositVault), 3200);  // execution fee
    if(isLong){
            console2.log("000000000000000000");
           vm.prank(depositor);
           IERC20(marketProps.longToken).transfer(address(_depositVault), amount);   
           console2.log("bbbbbbbbbbbbbbbbbbbbbb");
    }
    else   {
        console2.log("111111111111111111111111");
        console2.log("deposit balance: %d, %d", IERC20(marketProps.shortToken).balanceOf(depositor), amount);
           vm.prank(depositor);
           IERC20(marketProps.shortToken).transfer(address(_depositVault), amount);
           console2.log("qqqqqqqqqqqqqqqqqq");
    }

    DepositUtils.CreateDepositParams memory params = DepositUtils.CreateDepositParams(
        depositor,
        address(0),
        uiFeeReceiver,
        marketProps.marketToken,
        marketProps.longToken,
        marketProps.shortToken,
        longTokenSwapPath,
        shortTokenSwapPath,
        100000, // minMarketTokens
        true,
        3200,  // execution fee
        3200     // call back gas limit
    );

    console2.log("aaaaaaaaaaaaaaaaaaaaaaaaa");
    vm.prank(depositor);
    bytes32 key1 = _erouter.createDeposit(params);

    return key1;
}

/*
function testCancelDeposit() public 
{
    address[] memory longTokenSwapPath;
    address[] memory shortTokenSwapPath;

    address(_wnt).call{value: 100e8}("");
    _wnt.transfer(address(_depositVault), 1e6);
    DepositUtils.CreateDepositParams memory params = DepositUtils.CreateDepositParams(
        msg.sender,
        address(0),
        address(111),
        _marketProps1.marketToken,
        _marketProps1.longToken,
        _marketProps1.shortToken,
        longTokenSwapPath,
        shortTokenSwapPath,
        100000, // minMarketTokens
        true,
        3200,  // execution fee
        3200     // call back gas limit
    );

    bytes32 key1 = _erouter.createDeposit(params);

    console.log("WNT balance of address(222) before cancelllation: %s", _wnt.balanceOf(address(222)));
    console.log("WNT balance of address(this) before cancelllation: %s", _wnt.balanceOf(address(this))); 

    _roleStore.grantRole(address(222), Role.CONTROLLER); // to save a market's props
    vm.prank(address(222));
     _depositHandler.cancelDeposit(key1);
    console.log("WNT balance of address(222) after cancelllation: %s", _wnt.balanceOf(address(222)));
    console.log("WNT balance of address(this) after cancelllation: %s", _wnt.balanceOf(address(this))); 
}
*/

function testERC165() public{
    bool yes = _wnt.supportsInterface(type(IWNT).interfaceId);
    console2.log("wnt suppports deposit?");
    console2.logBool(yes);
    vm.expectRevert();
    yes = IERC165(address(_sol)).supportsInterface(type(IWNT).interfaceId);
    console2.logBool(yes);

    if(ERC165Checker.supportsERC165(address(_wnt))){
        console2.log("_wnt supports ERC165");
    }
    if(ERC165Checker.supportsERC165(address(_sol))){
        console2.log("_sol supports ERC165");
    }
}

    function justError() external {
        // revert Unauthorized("abcdefg"); // 973d02cb
        // revert("abcdefg");  // 0x08c379a, Error selector
        // require(false, "abcdefg"); // 0x08ce79a, Error selector
        assert(3 == 4); // Panic: 0x4e487b71
    }

     function testErrorMessage() public{

        try this.justError(){}        
        catch (bytes memory reasonBytes) {
            (string memory msg, bool ok ) = ErrorUtils.getRevertMessage(reasonBytes);
            console2.log("Error Message: "); console2.logString(msg);
            console2.log("error?"); console2.logBool(ok);
        }    
    }

    function printAddresses() public{
        console2.log("_orderVault:"); console2.logAddress(address(_orderVault));
        console2.log("marketToken:"); console2.logAddress(address(_marketProps1.marketToken));
    }   

    function printPoolsAmounts() public{
        console2.log("\n The summary of pool amounts: ");

        uint256 amount = MarketUtils.getPoolAmount(_dataStore, _marketProps1, _marketProps1.longToken);
        console2.log("Market: _marketProps1, token: long/nwt, amount: %d", amount);
        amount = MarketUtils.getPoolAmount(_dataStore, _marketProps1, _marketProps1.shortToken);
        console2.log("Market: _marketProps1, token: short/USDC, amount: %d", amount);

        amount = MarketUtils.getPoolAmount(_dataStore, _marketPropsAB, _marketPropsAB.longToken);
        console2.log("Market: _marketPropsAB, token: long/A, amount: %d", amount);
        amount = MarketUtils.getPoolAmount(_dataStore, _marketPropsAB, _marketPropsAB.shortToken);
        console2.log("Market: _marketPropsAB, token: short/B, amount: %d", amount);

        amount = MarketUtils.getPoolAmount(_dataStore, _marketPropsBC, _marketPropsBC.longToken);
        console2.log("Market: _marketPropsBC, token: long/B, amount:%d", amount);
        amount = MarketUtils.getPoolAmount(_dataStore, _marketPropsBC, _marketPropsBC.shortToken);
        console2.log("Market: _marketPropsBC, token: short/C, amount: %d", amount);

        amount = MarketUtils.getPoolAmount(_dataStore, _marketPropsCwnt, _marketPropsCwnt.longToken);
        console2.log("Market: _marketPropsCwnt, token: long/C, amount: %d", amount);
        amount = MarketUtils.getPoolAmount(_dataStore, _marketPropsCwnt, _marketPropsCwnt.shortToken);
        console2.log("Market: _marketPropsCwnt, token: short/wnt, amount: %d", amount);

        console2.log("\n");
    }

}

Impact

PositionUtils.validatePosition() uses isIncrease instead of false when calling isPositionLiquidatable(), making it not work properly for the case of isIncrease = true. A liquidation should always be considered as a decrease order in terms of evaluating price impact.

Code Snippet

Tool used

VSCode

Manual Review

Recommendation

Pass false always to isPositionLiquidatable():

 function validatePosition(
        DataStore dataStore,
        IReferralStorage referralStorage,
        Position.Props memory position,
        Market.Props memory market,
        MarketUtils.MarketPrices memory prices,
        bool isIncrease,
        bool shouldValidateMinPositionSize,
        bool shouldValidateMinCollateralUsd
    ) public view {
        if (position.sizeInUsd() == 0 || position.sizeInTokens() == 0) {
            revert Errors.InvalidPositionSizeValues(position.sizeInUsd(), position.sizeInTokens());
        }

        MarketUtils.validateEnabledMarket(dataStore, market.marketToken);
        MarketUtils.validateMarketCollateralToken(market, position.collateralToken());

        if (shouldValidateMinPositionSize) {
            uint256 minPositionSizeUsd = dataStore.getUint(Keys.MIN_POSITION_SIZE_USD);
            if (position.sizeInUsd() < minPositionSizeUsd) {
                revert Errors.MinPositionSize(position.sizeInUsd(), minPositionSizeUsd);
            }
        }

        if (isPositionLiquidatable(
            dataStore,
            referralStorage,
            position,
            market,
            prices,
-            isIncrease,
+          false,
            shouldValidateMinCollateralUsd
        )) {
            revert Errors.LiquidatablePosition();
        }
    }
xvi10 commented 1 year ago

fixed in https://github.com/gmx-io/gmx-synthetics/pull/155/commits/ac2b1dc0fce1fc73d65859c95eb0ce72d20ff30d

the code was updated to check for whether a position is liquidatable after state variables were updated