// SPDX-License-Identifier: MIT

pragma solidity ^0.8.20;


struct TokenPair{
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        // uint24 fee;
        
        // uint256 amountOut;
    }


/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address from,
        address to,
        uint256 amount
    ) external returns (bool);
}



/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }




    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}

/**
 * @title SafeMath
 * @dev Math operations with safety checks that throw on error
 */
library SafeMath {

  /**
  * @dev Multiplies two numbers, throws on overflow.
  */
  function mul(uint256 a, uint256 b) internal pure returns (uint256) {
    if (a == 0) {
      return 0;
    }
    uint256 c = a * b;
    assert(c / a == b);
    return c;
  }

  /**
  * @dev Integer division of two numbers, truncating the quotient.
  */
  function div(uint256 a, uint256 b) internal pure returns (uint256) {
    // assert(b > 0); // Solidity automatically throws when dividing by 0
    uint256 c = a / b;
    // assert(a == b * c + a % b); // There is no case in which this doesn't hold
    return c;
  }

  /**
  * @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
  */
  function sub(uint256 a, uint256 b) internal pure returns (uint256) {
    assert(b <= a);
    return a - b;
  }

  /**
  * @dev Adds two numbers, throws on overflow.
  */
  function add(uint256 a, uint256 b) internal pure returns (uint256) {
    uint256 c = a + b;
    assert(c >= a);
    return c;
  }
}






abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}


    abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}




interface IT2SoulSharesSwapData{
     function addItemToSharesTokenList(uint256 sharesSubject, TokenPair calldata item) external returns(bool );
     function getSharesTokenListByShares(uint256 sharesSubject) external view returns (TokenPair[] memory);
    //  function removeSharesTokenByIdPair(uint256 sharesSubject, address pairTokenIn,address pairTokenOut) external  returns (bool);
     function getTokenPairBalance(uint256 sharesSubject,address tokenIn,address tokenOut) external view  returns(uint256 tokenBalance);
     /**
     * 
     * @param sharesSubject subject
     * @param tokenIn  token1 addr
     * @param tokenOut token2 addr 
     * @param amount   amout unit wei
     * @param plusflag true:plus,false:sub
     */
     function setTokenPairBlance(uint256 sharesSubject,address tokenIn,address tokenOut,uint256 amount,bool plusflag) external returns(bool );
     function setUniRoterVer(address token,uint8 ver) external returns (bool);
     function getUniRoterVer(address token) external view returns (uint8);
      

}


interface IWETH is IERC20 {
    function deposit() external payable;

    function withdraw(uint amount) external;
}



interface IUniswapV2Router01 {
    function factory() external pure returns (address);
    function WETH() external pure returns (address);

    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
    function removeLiquidity(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETH(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountToken, uint amountETH);
    function removeLiquidityWithPermit(
        address tokenA,
        address tokenB,
        uint liquidity,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountA, uint amountB);
    function removeLiquidityETHWithPermit(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountToken, uint amountETH);
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);
    function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
        external
        returns (uint[] memory amounts);
    function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
        external
        payable
        returns (uint[] memory amounts);

    function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
    function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
    function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
    function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
    function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
}

interface IUniswapV2Router02 is IUniswapV2Router01 {
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline
    ) external returns (uint amountETH);
    function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline,
        bool approveMax, uint8 v, bytes32 r, bytes32 s
    ) external returns (uint amountETH);

    function swapExactTokensForTokensSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
    function swapExactETHForTokensSupportingFeeOnTransferTokens(
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external payable;
    function swapExactTokensForETHSupportingFeeOnTransferTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external;
}
/// @title Callback for IUniswapV3PoolActions#swap
/// @notice Any contract that calls IUniswapV3PoolActions#swap must implement this interface
interface IUniswapV3SwapCallback {
    /// @notice Called to `msg.sender` after executing a swap via IUniswapV3Pool#swap.
    /// @dev In the implementation you must pay the pool tokens owed for the swap.
    /// The caller of this method must be checked to be a UniswapV3Pool deployed by the canonical UniswapV3Factory.
    /// amount0Delta and amount1Delta can both be 0 if no tokens were swapped.
    /// @param amount0Delta The amount of token0 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token0 to the pool.
    /// @param amount1Delta The amount of token1 that was sent (negative) or must be received (positive) by the pool by
    /// the end of the swap. If positive, the callback must send that amount of token1 to the pool.
    /// @param data Any data passed through by the caller via the IUniswapV3PoolActions#swap call
    function uniswapV3SwapCallback(
        int256 amount0Delta,
        int256 amount1Delta,
        bytes calldata data
    ) external;
}

/// @title Router token swapping functionality
/// @notice Functions for swapping tokens via Uniswap V3
interface ISwapRouter is IUniswapV3SwapCallback {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactInputSingleParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(ExactInputSingleParams calldata params) external payable returns (uint256 amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountIn;
        uint256 amountOutMinimum;
    }

    /// @notice Swaps `amountIn` of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactInputParams` in calldata
    /// @return amountOut The amount of the received token
    function exactInput(ExactInputParams calldata params) external payable returns (uint256 amountOut);

    struct ExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another token
    /// @param params The parameters necessary for the swap, encoded as `ExactOutputSingleParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutputSingle(ExactOutputSingleParams calldata params) external payable returns (uint256 amountIn);

    struct ExactOutputParams {
        bytes path;
        address recipient;
        uint256 deadline;
        uint256 amountOut;
        uint256 amountInMaximum;
    }

    /// @notice Swaps as little as possible of one token for `amountOut` of another along the specified path (reversed)
    /// @param params The parameters necessary for the multi-hop swap, encoded as `ExactOutputParams` in calldata
    /// @return amountIn The amount of the input token
    function exactOutput(ExactOutputParams calldata params) external payable returns (uint256 amountIn);
}



library TransferHelper {
    /// @notice Transfers tokens from the targeted address to the given destination
    /// @notice Errors with 'STF' if transfer fails
    /// @param token The contract address of the token to be transferred
    /// @param from The originating address from which the tokens will be transferred
    /// @param to The destination address of the transfer
    /// @param value The amount to be transferred
    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) =
            token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
    }

    /// @notice Transfers tokens from msg.sender to a recipient
    /// @dev Errors with ST if transfer fails
    /// @param token The contract address of the token which will be transferred
    /// @param to The recipient of the transfer
    /// @param value The value of the transfer
    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
    }

    /// @notice Approves the stipulated contract to spend the given allowance in the given token
    /// @dev Errors with 'SA' if transfer fails
    /// @param token The contract address of the token to be approved
    /// @param to The target of the approval
    /// @param value The amount of the given token the target will be allowed to spend
    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
        require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
    }

    /// @notice Transfers ETH to the recipient address
    /// @dev Fails with `STE`
    /// @param to The destination of the transfer
    /// @param value The value to be transferred
    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'STE');
    }
}



interface IVerifySig{
   

    /**
     * 
     * @param claimer  sender
     * @param questWallet  quest's wallet
     * @param questId  questid
     * @param _signature  sigstr
     * @return signer_ wallet
     * @return resMsg_ 
     */   
    function isT2MsgValid(address claimer,address questWallet, uint256 questId,bytes memory _signature) external view returns (address signer_, string memory resMsg_); 
    function isT2MsgValidTimestamp(address claimer,address questWallet, uint256 questId, uint unitValue,uint timestamp, bytes memory _signature) external view  returns (address signer_, string memory resMsg_);
}

contract Utilities is Ownable, Pausable {

    mapping(address => bool) private admins;
    bool private reentrancyLock = false;
    event OpraterAdmin(address indexed from);

    function addAdmin(address _address) public virtual onlyOwner {
        // require(_address.code.length>0," must be a contract's address");
        admins[_address] = true;
    }

    function addAdmins(address[] calldata _addresses) public virtual onlyOwner {
        uint256 len = _addresses.length;
        for (uint256 i = 0; i < len; i++) {
            // require(_addresses[i].code.length>0," must be a contract's address");
            admins[_addresses[i]] = true;
        }
    }

    // function addAdminsInternal(address[] calldata _addresses) internal {
    //     uint256 len = _addresses.length;
    //     for(uint256 i=0;i<len;i++)
    //     {
    //         // require(_addresses[i].code.length>0," must be a contract's address");
    //         admins[_addresses[i]]=true;
    //     }
    // }

    function removeAdmin(address _address) external onlyOwner {
        admins[_address] = false;
    }

    function removeAdmin(address[] calldata _addresses) external onlyOwner {
        uint256 len = _addresses.length;
        for (uint256 i = 0; i < len; i++) {
            admins[_addresses[i]] = false;
        }
    }

    // function removeAdminInternal(
    //     address[] memory _addresses
    // ) internal onlyOwner {
    //     uint256 len = _addresses.length;
    //     for (uint256 i = 0; i < len; i++) {
    //         admins[_addresses[i]] = false;
    //     }
    // }

    function setPause(bool _shouldPause) external onlyAdminOrOwner {
        if (_shouldPause) {
            _pause();
        } else {
            _unpause();
        }
    }

    function isAdmin(address _address) public view returns (bool) {
        return admins[_address];
    }

    modifier onlyAdmin() {
        require(admins[msg.sender], "Not admin");
        _;
    }

   

/* Prevent a contract function from being reentrant-called. */
    modifier ReentrancyLock {
        require(!reentrancyLock, "Reentrancy detected");
        reentrancyLock = true;
        _;
        reentrancyLock = false;
    }


    modifier onlyAdminOrOwner() {
        
        require(admins[msg.sender] || isOwner(), "Not admin or owner");      
        _;
        emit OpraterAdmin(msg.sender);
    }
    // modifier nonZeroAddress(address _address)    {
    //     require(address(0) != _address, "0 address");
    //     _;
    // }
    // modifier nonZeroLength(uint[] memory _array) {
    //     require(_array.length > 0, "Empty array");
    //     _;
    // }
    // modifier lengthsAreEqual(uint[] memory _array1, uint[] memory _array2) {
    //     require(_array1.length == _array2.length, "Unequal lengths");
    //     _;
    // }
    // modifier onlyEOA() {
    //     /* solhint-disable avoid-tx-origin */
    //     require(msg.sender == tx.origin, "No contracts");
    //     _;
    // }

    modifier sharesCaller() {
        /* solhint-disable avoid-tx-origin */
        require(msg.sender == tx.origin||isAdmin(msg.sender), " caller address error ");
        _;
    }

    function isOwner() internal view returns (bool) {
        return owner() == msg.sender;
    }

//     function isContract(address addr) internal view returns (bool) {
//         uint256 size;
//         assembly { size := extcodesize(addr) }
//         return size > 0;
//   }
}


contract T2SoulSharesFinance is Utilities {
    // Import SafeMath library for uint256
    using SafeMath for uint256;   
    address public  swapRouteV3Addr;
    address public  swapRouteV2Addr;

    address public protocolFeeDestination=0x2dFd4225De01DD83Da644285D07Fe2A43A5cEa62;

    //k-6:official-4,10000000000000000:10000000000000000,k+o=2%
    uint256 private  protocolFeePercent = 0.01 ether;
    uint256 private  subjectFeePercent = 0.01 ether;
    
    //perUnitValue lessthan 1: 10% feed
    // uint256 public lessOneBseFeePercent = 0.1 ether;
    //perUnitValue lessthan 1: 2% feed send to protocol&subjectfeepercent
    uint256 private  lessOneProtocolFeePercent = 0.02 ether;
    //perUnitValue lessthan 1: 8% feed  send to sharespool,lessOneBseFeePercent eq sum(lessOneProtocolFeePercent,lessOneSharesPoolFeePercent)
    uint256 private  lessOneSharesPoolFeePercent = 0.08 ether;

    //init shares price 10000000000000000
    uint256 public basePrice =0.01 ether;
        

    //Verify contract's addressqiang
    address private verifySigAddr;
    //signature's address
    address private t2t2SignatureAddr;    
  
    address public WETHAddr;   
    address public  t2swapDataAddr;  
  
    // SharesSubject => (Holder => Balance)
    mapping(uint256 => mapping(address => uint256)) public sharesBalance;

    // SharesSubject => Supply
    mapping(uint256 => uint256) public sharesSupply;
    mapping(address => uint256) private addr2RoomIdMap;
    mapping(uint256 => address) private roomId2AddrMap;

    mapping(uint256 => uint256) public masterFeeBalance;
    //subject =>amount unitValue lessOne balance reward pool
    mapping(uint256=>uint256) public sharesPool;
 
    mapping(uint256=>uint256) public roomEthBalance;   
    //30 sec
    uint24 public deadLineSec=300;


    event Trade(address trader, uint256 subject,bool isBuy,uint256 shareAmount,uint256 ethAmount,uint256 protocolEthAmount,uint256 subjectEthAmount,uint256 supply,uint256 poolAmount,uint256 poolTotalAmount);        
    //tradeType 0:key，1.token
    event SharesETHChange(uint256 subject, uint tradeType,  bool isBuy,uint256 changeAmount);
    event Deposit(address indexed from, uint256 amount);
    event Withdraw(address indexed from, uint256 amount);  
   
  
    event ExcutSwapSingle(uint256 sharesSubject,address sender, bool isBuy, address tokenIn,uint256 tokenInAmout, address tokenOut,uint256 tokenOutAmout,uint24 swapfeed,address receiver);

    // event ExcutSwapMulti(uint256 sharesSubject, address sender,address[] tokenIns,uint24[]  fees, address tokenOut,uint256 tokenOutAmout,address receiver);


    constructor()
    {}


    function setUniSwapAddr(address uniV2RouterAddr_,address uniV3RouterAddr_) external onlyAdminOrOwner ReentrancyLock returns (bool)
    {
        require(uniV2RouterAddr_!= address(0) && uniV3RouterAddr_!= address(0)," set uniVRouter error");
        swapRouteV2Addr = uniV2RouterAddr_;      
        swapRouteV3Addr = uniV3RouterAddr_;
        return true;

    }


   function setContracts(address verifySigAddr_, address t2t2SigAddr_,address  t2swapDataAddr_,address weth_) external onlyAdminOrOwner ReentrancyLock  returns (bool) {
        require(verifySigAddr_ != address(0) && t2t2SigAddr_ != address(0) && t2swapDataAddr_!=address(0) && weth_ != address(0) ,"set contracts error" ); 
        verifySigAddr = verifySigAddr_;
        t2t2SignatureAddr = t2t2SigAddr_;   
         WETHAddr = weth_;   
        t2swapDataAddr = t2swapDataAddr_;              
        return true;
    }

    function setBasePrice(uint256 _basePrice) public onlyAdminOrOwner {
        basePrice = _basePrice;
    }

     function setDeadLine(uint24 _deadLine) public onlyAdminOrOwner {
        
        deadLineSec = _deadLine;
    }


    function setFeesInfor(uint256 _protocolFee,uint256 _subjectFee,uint256 _lessOneProtocolFee,uint256 _lessOneSharesPoolFee) public onlyAdminOrOwner {
        protocolFeePercent = _protocolFee;
        subjectFeePercent = _subjectFee;
        lessOneProtocolFeePercent = _lessOneProtocolFee;
        lessOneSharesPoolFeePercent = _lessOneSharesPoolFee;
    }

    function getFeesInfor() public view returns (uint256 _protocolFee,uint256 _subjectFee,uint256 _lessOneProtocolFee,uint256 _lessOneSharesPoolFee)
    {
        
        _protocolFee= protocolFeePercent;
        _subjectFee=subjectFeePercent;
        _lessOneProtocolFee= lessOneProtocolFeePercent ;
        _lessOneSharesPoolFee=lessOneSharesPoolFeePercent ;
    }

    function setFeeDestination(
        address _feeDestination
    ) public onlyAdminOrOwner {
        require(address(0)!=_feeDestination," addr error");
        protocolFeeDestination = _feeDestination;
    }

     
    function getSharesTokenListByShares(uint256 sharesSubject) public view returns (TokenPair[] memory) {
        return  IT2SoulSharesSwapData(t2swapDataAddr).getSharesTokenListByShares(sharesSubject);
    }

    function getTokenPairBalance(uint256 sharesSubject,address tokenIn,address tokenOut) public view  returns(uint256 tokenBalance){
      
        return  IT2SoulSharesSwapData(t2swapDataAddr).getTokenPairBalance(sharesSubject,tokenIn,tokenOut);

    }  


    function getUniRoterVer(address token) public view returns (uint8){
        return  IT2SoulSharesSwapData(t2swapDataAddr).getUniRoterVer(token);
     }



    function getSwaV2pAmount(uint amountIn,address  tokenIn,address  tokenOut) public view  returns(uint[] memory amountOut)
    {       
        address[] memory paths = new address[](2);
        paths[0] = tokenIn;
        paths[1] = tokenOut;              
        amountOut = IUniswapV2Router02(swapRouteV2Addr).getAmountsOut(amountIn,paths);
        return amountOut;
    }
   

    function getPrice(uint256 supply,uint256 amount) public view returns (uint256) {
        uint256 sum1 = supply == 0? 0: ((supply - 1) * (supply) * (2 * (supply - 1) + 1)) / 6;
        uint256 sum2 = supply == 0 && amount == 1? 0: ((supply - 1 + amount) *(supply + amount) * (2 * (supply - 1 + amount) + 1)) / 6;
        uint256 summation = sum2 - sum1;
        uint256 basePrice_ = basePrice.mul(amount);
        return basePrice_>0?basePrice_.add((summation * 1 ether) / 16000):((summation * 1 ether) / 16000);
        // return  basePrice.add((summation * 1 ether) / 16000);
    }

    function getBuyPrice(uint256 sharesSubject,uint256 amount
    ) public view returns (uint256) {
        return getPrice(sharesSupply[sharesSubject], amount);
    }

    function getSellPrice(uint256 sharesSubject,uint256 amount) public view returns (uint256) {
        return getPrice(sharesSupply[sharesSubject] - amount, amount);
    }

    function getBuyPriceAfterFee(uint256 sharesSubject,uint256 amount) public view returns (uint256) {
        uint256 price = getBuyPrice(sharesSubject, amount);
        uint256 protocolFee = (price * protocolFeePercent) / 1 ether;
        uint256 subjectFee = (price * subjectFeePercent) / 1 ether;
        return price + protocolFee + subjectFee;
    }

    function getSellPriceAfterFee(
        uint256 sharesSubject,
        uint256 amount
    ) public view returns (uint256) {
        uint256 price = getSellPrice(sharesSubject, amount);
        uint256 protocolFee = (price * protocolFeePercent) / 1 ether;
        uint256 subjectFee = (price * subjectFeePercent) / 1 ether;
        return price - protocolFee - subjectFee;
    }

    function getSharesTotalAmount(uint256 sharesSubject) public view returns(uint256 )
    {
        uint256 supply = sharesSupply[sharesSubject];
        if(supply==0)
        {
          return 0;
        }
        else{
            uint256 sumFundShareAmount =0;
            for (uint32 i = 0; i <supply; i++) {
                sumFundShareAmount = sumFundShareAmount+getPrice(i,1);
            }
            return sumFundShareAmount;
        }
    }


    

  
    function buyShares(uint256 sharesSubject, uint256 amount,uint unitValue,uint timestamp, bytes memory _signature) public payable sharesCaller {

        require(unitValue==0 || unitValue==1," buyShares unitValue error ");
        uint256 supply = sharesSupply[sharesSubject];
        // require(
        //     supply > 0 || sharesSubject == msg.sender,
        //     "Only the shares' subject can buy the first share"
        // );
        require(supply > 0, "Unable to buy sharesSubject don't running");
        buyInterShares(sharesSubject, amount, supply,unitValue,timestamp,_signature);
    }

    function getETHProfit(uint256 sharesSubject, uint256 sellRatio) public view returns(bool existToken_, TokenPair[] memory sellPairs_){
        existToken_= false;
     
        uint256 sharesTokenBalance =0;
        uint256 swapAmoutInTemp =0;       
        uint256 i =0;      
        TokenPair[] memory tokenPairs = IT2SoulSharesSwapData(t2swapDataAddr).getSharesTokenListByShares(sharesSubject);
        if(tokenPairs.length==0)
        {
            return(false,sellPairs_); 
        } 
        sellPairs_=  new TokenPair[](tokenPairs.length);
        for (uint128 index = 0; index < tokenPairs.length; index++) {
          sharesTokenBalance=  IT2SoulSharesSwapData(t2swapDataAddr).getTokenPairBalance(sharesSubject,tokenPairs[index].tokenIn,tokenPairs[index].tokenOut);
          if(sharesTokenBalance>0)
          {
              existToken_ = true;        
              swapAmoutInTemp = (sharesTokenBalance.mul(sellRatio))/1 ether;
              TokenPair memory item = TokenPair(tokenPairs[index].tokenOut, tokenPairs[index].tokenIn,swapAmoutInTemp);
              sellPairs_[i]=item;
              i = i+1;
            
          }
        }
        return(existToken_,sellPairs_);

    }


    function emitExcutSwapSingle(uint256 sharesSubject, bool isBuy, address tokenIn,uint256 tokenInAmout, address tokenOut,uint256 tokenOutAmout,uint24 swapfeed) internal {
        emit ExcutSwapSingle(sharesSubject,msg.sender,isBuy,tokenIn,tokenInAmout,tokenOut,tokenOutAmout,swapfeed,address(this));             
    }

    function getSellRatio(uint256 sharesSubject, uint256 price) public view returns (uint256 ratio)
    {
        ratio = 1;
        uint256 sharesTotalAmout =   getSharesTotalAmount(sharesSubject);
        if(sharesTotalAmout==0)
        {
            return ratio;
        }else 
        {
           //using div 1 ether
            ratio = price.mul(1 ether).div(sharesTotalAmout);
            return ratio;     
        }
        
    } 

   
    function sellAssetToken(uint256 sharesSubject,uint256 price,uint24 swapFee) internal returns(uint256 price_ ) {

        bool existToken_ = false;  
     
        uint256 sellRatio = getSellRatio(sharesSubject,price);
        TokenPair[] memory sellPairs; 
       
        
        (existToken_,sellPairs)=getETHProfit(sharesSubject,sellRatio);
 
        if(existToken_==false)
        {
            if(getSharesTotalAmount(sharesSubject)== roomEthBalance[sharesSubject])
            {
              price_ = price; 
            }else {
              price_ = (sellRatio.mul(roomEthBalance[sharesSubject]))/1 ether; 
            }

            require(roomEthBalance[sharesSubject]>=price_," price balance error");
            roomEthBalance[sharesSubject] = roomEthBalance[sharesSubject].sub(price_);        
            emit SharesETHChange(sharesSubject,0,false,price_);

        }else if(existToken_==true)
        {          
           price_=sellFundToken(sharesSubject, sellPairs, sellRatio,swapFee);
        }
        
        return price_;

    }

    function sellFundToken(uint256 sharesSubject, TokenPair[] memory sellPairs, uint256 sellRatio,uint24 swapFee) internal returns (uint256 price_)
    {
        uint256 swapToken2WETH =0; 
        uint256 swapAmoutInTemp =0;       
        uint8 ver =2;
        for (uint128 index = 0; index < sellPairs.length; index++)
        { 
            ver =getUniRoterVer(sellPairs[index].tokenIn);             
            swapAmoutInTemp =swapSingle(sharesSubject,sellPairs[index].tokenIn,sellPairs[index].tokenOut,sellPairs[index].amountIn,swapFee,address(this),ver,0); 
            swapToken2WETH = swapToken2WETH.add(swapAmoutInTemp);          
            emitExcutSwapSingle(sharesSubject,false,sellPairs[index].tokenIn,sellPairs[index].amountIn, sellPairs[index].tokenOut,swapAmoutInTemp,swapFee);
        }          
        IWETH(WETHAddr).withdraw(swapToken2WETH);               
     
        if(roomEthBalance[sharesSubject]==0)
        {   
                                    
            price_=swapToken2WETH;
        }else if(roomEthBalance[sharesSubject]>0 )
        {  
            swapAmoutInTemp= roomEthBalance[sharesSubject].mul(sellRatio)/1 ether;
            require(roomEthBalance[sharesSubject]>=swapAmoutInTemp," roomEthBalance  error");                                          
            roomEthBalance[sharesSubject]=roomEthBalance[sharesSubject].sub(swapAmoutInTemp); 
            emit SharesETHChange(sharesSubject,0,false,swapAmoutInTemp);
            price_ = swapToken2WETH.add(swapAmoutInTemp);
        }

        return price_;
    }
   
    function sellShares(uint256 sharesSubject, uint256 amount,uint24 swapFee,uint unitValue,uint timestamp, bytes memory _signature) public payable sharesCaller ReentrancyLock
    {
        require(unitValue==0 || unitValue==1," sellshares unitValue error ");
        require(verifySinger(sharesSubject,unitValue,timestamp,_signature), " sellShares singer error");  
        sellInternShares(sharesSubject,amount,swapFee,unitValue);
    }
    
    function sellInternShares(uint256 sharesSubject, uint256 amount,uint24 swapFee,uint unitValue) internal  {
        
        uint256 supply = sharesSupply[sharesSubject];
        require(supply > amount, "Cannot sell the last share");
        require(sharesBalance[sharesSubject][msg.sender] >= amount,"Insufficient shares amount ");
       
     
        uint256 price = getPrice(supply- amount, amount);       
        supply= supply- amount;
        price=  sellAssetToken(sharesSubject,price,swapFee);
     
        uint256 protocolFee=0;
        uint256 subjectFee=0;
        uint256 lessOneSharesFundPoolFee=0;
        if(unitValue==1)
        {
            protocolFee = (price * protocolFeePercent) / 1 ether;
            subjectFee = (price * subjectFeePercent) / 1 ether;
        }else if(unitValue==0)
        {    
           protocolFee=subjectFee =((price * lessOneProtocolFeePercent) / 1 ether).div(2);
           lessOneSharesFundPoolFee = (price * lessOneSharesPoolFeePercent) / 1 ether;
             
        }
   
        sharesBalance[sharesSubject][msg.sender] =sharesBalance[sharesSubject][msg.sender] - amount;
     
        if(unitValue==1)
        {
            (bool success1, ) = msg.sender.call{value:  (price - protocolFee - subjectFee)}("");
            (bool success2, ) = protocolFeeDestination.call{value: protocolFee}("");

            // (bool success3, ) = sharesSubject.call{value: subjectFee}("");
            //   require(success1 && success2 && success3, "Unable to send funds");
            masterFeeBalance[sharesSubject] = masterFeeBalance[sharesSubject].add(subjectFee);
            require(success1 && success2, "Unable to sell send funds");
        }else if(unitValue<1)
        {  
            (bool success1, ) = msg.sender.call{value:  (price - protocolFee - subjectFee-lessOneSharesFundPoolFee)}("");
            (bool success2, ) = protocolFeeDestination.call{value: protocolFee}("");
            masterFeeBalance[sharesSubject] = masterFeeBalance[sharesSubject].add(subjectFee);
            sharesPool[sharesSubject] =sharesPool[sharesSubject].add(lessOneSharesFundPoolFee);
            require(success1 && success2, "Unable to sell pool send funds");
        }
        uint256 pools = sharesPool[sharesSubject];    
       
        sharesSupply[sharesSubject] =supply;
        emitTrader(msg.sender,sharesSubject,false,amount,price,protocolFee,subjectFee,supply,lessOneSharesFundPoolFee, pools);


    }

    function buyInterShares(uint256 sharesSubject,uint256 amount,uint256 supply,uint unitValue, uint timestamp, bytes memory _signature) internal {
        // uint256 supply = sharesSupply[sharesSubject];           
        require(verifySinger(sharesSubject,unitValue,timestamp,_signature), " buyInterShares singer error");   
        uint256 price = getPrice(supply, amount);
        uint256 protocolFee = (price * protocolFeePercent) / 1 ether;
        uint256 subjectFee = (price * subjectFeePercent) / 1 ether;    
        require(msg.value >= price + protocolFee + subjectFee,"Insufficient payment");
        sharesBalance[sharesSubject][msg.sender] =sharesBalance[sharesSubject][msg.sender] + amount;
        sharesSupply[sharesSubject] = supply + amount;        
        uint256 balanceValue = msg.value.sub(protocolFee);
        balanceValue = balanceValue.sub(subjectFee);
        roomEthBalance[sharesSubject]=roomEthBalance[sharesSubject].add(balanceValue);
        emit SharesETHChange(sharesSubject,0,true,balanceValue);
        uint256 poolAmount =0;      
        masterFeeBalance[sharesSubject] = masterFeeBalance[sharesSubject].add(subjectFee);   
        (bool success1, ) = protocolFeeDestination.call{value: protocolFee}("");   
        if (unitValue==0)
        {
           if(sharesPool[sharesSubject]>0) 
           {
                poolAmount = sharesPool[sharesSubject];
                sharesPool[sharesSubject]=0;        
                (bool success2, ) = msg.sender.call{value: poolAmount}(""); 
                require(success1 && success2, " Unable buy to send pool funds");
           }else
           {
             require(success1, " protocolFee 1 error ");
           }
        }else 
        {
            require(success1, " protocolFee 2 error");
        }
        // (bool success2, ) = sharesSubject.call{value: subjectFee}("");
        // require(success1 && success2, "Unable to send funds");
        // sharesTotalAmout ==sellRatio
        uint256 sharesTotalsupply  = sharesSupply[sharesSubject];     
        balanceValue   =sharesPool[sharesSubject];
        
        emitTrader(msg.sender,sharesSubject,true, amount,price,protocolFee,subjectFee,sharesTotalsupply,poolAmount, balanceValue);
        
        
        
    }
   
     function emitTrader(address trader, uint256 subject,bool isBuy,uint256 shareAmount,uint256 ethAmount,uint256 protocolEthAmount,uint256 subjectEthAmount,uint256 supply,uint256 poolAmount,uint256 poolTotalAmount) public  {
         emit Trade(trader,subject,isBuy, shareAmount,ethAmount,protocolEthAmount,subjectEthAmount,supply,poolAmount,poolTotalAmount);
     }




    function bindRoomAddr(uint256 sharesSubject, uint timestamp, bytes memory _signature) payable external sharesCaller whenNotPaused ReentrancyLock returns (bool) {       
     
       
        address addr_ =msg.sender;
        uint256 supply = sharesSupply[sharesSubject];
        if (supply == 0) {
            buyInterShares(sharesSubject, 1, supply,1,timestamp,_signature);
        }

        if (roomId2AddrMap[sharesSubject] != address(0)) {
            delete addr2RoomIdMap[roomId2AddrMap[sharesSubject]];
            delete roomId2AddrMap[sharesSubject];
        }
        roomId2AddrMap[sharesSubject] = addr_;
        addr2RoomIdMap[addr_] = sharesSubject;
        return true;
    }

    function getBindRoomAddr( uint256 roomId ) public view  returns (address addr) {
        return roomId2AddrMap[roomId];
    }

    fallback() external payable {
        emit Deposit(msg.sender, msg.value);
    }

    receive() external payable {
        emit Deposit(msg.sender, msg.value);
    }

    function verifySinger( uint256 roomId, uint unitValue, uint timestamp, bytes memory _signature ) internal view returns (bool) {
        IVerifySig verifySig = IVerifySig(verifySigAddr);
        string memory sigStr = "";
        address sig_;        
        (sig_, sigStr) = verifySig.isT2MsgValidTimestamp(msg.sender, t2t2SignatureAddr,roomId,unitValue,timestamp, _signature );          
        if (sig_ == t2t2SignatureAddr) {
            return true;
        } else {
            return false;
        }
    }

    function withdraw(uint256 sharesSubject) external ReentrancyLock {
        address to = roomId2AddrMap[sharesSubject];
        uint256 amount_ = masterFeeBalance[sharesSubject];
        require(msg.sender == to," room's address or sender's address is error");
        require(amount_ > 0, " shaers key amount error");
        masterFeeBalance[sharesSubject] = 0;
        uint256 amount = address(this).balance;
        require(amount >= amount_, " withdraw amount error! ");
        // payable(to).transfer(amount_);
        (bool success, ) = payable(to).call{value: amount_}("");
        require(success , " withdraw eth error"); 
        emit Withdraw(to, amount_);
    }

    function sellToken(uint256 sharesSubject,address tokenIn,address tokenOut,uint256 amountIn,uint24 swapFee,uint256 amountOutMin) public  sharesCaller whenNotPaused ReentrancyLock
    {         
        require(tokenOut == WETHAddr, "tokenOut is not weth address");
        uint256 tokenBalance =  IT2SoulSharesSwapData(t2swapDataAddr).getTokenPairBalance(sharesSubject,tokenIn,tokenOut);  
        require(tokenBalance>=amountIn && amountIn>0 ," amountIn error ");       
        require(msg.sender ==  roomId2AddrMap[sharesSubject]," room's address or sender's address is error");         
        require(tokenIn!=address(0) && tokenOut!=address(0) && swapFee>0," pairs error");         
        address receiver = address(this);
        uint8 ver =getUniRoterVer(tokenIn); 
        // 
        // TransferHelper.safeApprove(tokenIn, swapRouteV3Addr, amountIn);         
        uint256 tokenAmontOut = swapSingle(sharesSubject, tokenIn,tokenOut,amountIn,swapFee,receiver,ver,amountOutMin);      
        IWETH(WETHAddr).withdraw(tokenAmontOut);         
        roomEthBalance[sharesSubject]=roomEthBalance[sharesSubject].add(tokenAmontOut);  
        emit SharesETHChange(sharesSubject,1,false,tokenAmontOut);    
        emit ExcutSwapSingle(sharesSubject,msg.sender,false,tokenIn,amountIn,tokenOut,tokenAmontOut,swapFee,receiver); 

    }


    function excuteBuySingle(uint256 sharesSubject,address tokenIn,address tokenOut,uint256 amountIn,uint24 swapFee,uint8 version,uint256 amountOutMin)  public  sharesCaller whenNotPaused ReentrancyLock payable  {
       
        require(address(tokenIn)==address(WETHAddr) ," tokenIn address error"); 
        require(tokenIn!=address(0) && tokenOut!=address(0)," token address error");
        require(amountIn>0 ," amountIn error");       
        require(roomEthBalance[sharesSubject]>=amountIn, " room's eth amount error");
        require(msg.sender ==  roomId2AddrMap[sharesSubject]," room's address or sender's address is error");         
        require(tokenIn!=address(0) && tokenOut!=address(0) && swapFee>0," pairs error");    

        roomEthBalance[sharesSubject]=roomEthBalance[sharesSubject].sub(amountIn);  
        emit SharesETHChange(sharesSubject,1,true,amountIn);          
        IWETH(WETHAddr).deposit{value: amountIn}();
     
        address receiver =address(this);                  
        uint256 amountOut =swapSingle(sharesSubject,tokenIn,tokenOut,amountIn,swapFee,receiver,version,amountOutMin);     
        TokenPair memory item = TokenPair(tokenIn,tokenOut,amountIn);  
        IT2SoulSharesSwapData(t2swapDataAddr).addItemToSharesTokenList(sharesSubject,item);                   
        emit ExcutSwapSingle(sharesSubject,msg.sender,true,tokenIn,amountIn,tokenOut,amountOut,swapFee,receiver);       
        
    }


 
     function swapSingle(uint256 sharesSubject, address tokenIn,address tokenOut,uint256 amountIn,uint24 swapFee,address receiver,uint8 ver,uint256 amountOutMin)  internal returns(uint256 amountOut)   {

        if(tokenIn!=address(0) && tokenOut!=address(0) && amountIn>0 )
        {
            bool wethIn = false;
            if(address(WETHAddr)==address(tokenIn))
            {
                IT2SoulSharesSwapData(t2swapDataAddr).setUniRoterVer(tokenOut,ver);
                wethIn = true;
            }else {
                 IT2SoulSharesSwapData(t2swapDataAddr).setTokenPairBlance(sharesSubject,tokenIn,tokenOut,amountIn,wethIn);        
                 uint[] memory amountOuts = getSwaV2pAmount(amountIn,tokenIn,tokenOut);          
                if(amountOuts[1]>0)
                {
                    ver =2;
                }
            }


            if(ver==3)
            {    
                TransferHelper.safeApprove(tokenIn, swapRouteV3Addr, amountIn); 
                ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams(
                    { tokenIn: tokenIn,tokenOut: tokenOut,fee: swapFee,recipient: receiver,deadline: block.timestamp+deadLineSec,amountIn: amountIn,amountOutMinimum: amountOutMin,
             
                    sqrtPriceLimitX96: 0
                });
             amountOut = ISwapRouter(swapRouteV3Addr).exactInputSingle(params);
            }else 
            {
                TransferHelper.safeApprove(tokenIn, swapRouteV2Addr, amountIn);    
                address[] memory paths = new address[](2);
                paths[0] = tokenIn;
                paths[1] = tokenOut;
                uint[] memory amounts = IUniswapV2Router02(swapRouteV2Addr).swapExactTokensForTokens(amountIn,amountOutMin,paths,receiver, block.timestamp+deadLineSec);
                amountOut= amounts[1];

             }
                //wethIn: true.buytoken ,false.sell
             if(wethIn==true)
             {
                // amountOut rec tokens num
                IT2SoulSharesSwapData(t2swapDataAddr).setTokenPairBlance(sharesSubject,tokenIn,tokenOut,amountOut,wethIn); 
             }
                return amountOut;
        }else 
        {
            return  0;
        }

     }

}