creation_sourcecode: {{
    "language": "Solidity",
    "sources": {
      "/contracts/LQDXStaker.sol": {
        "content": "// SPDX-License-Identifier: MIT\r\npragma solidity >=0.8.0 <0.9.0;\r\n\r\nabstract contract Context {\r\n  function _msgSender() internal view virtual returns (address) {\r\n    return msg.sender;\r\n  }\r\n  function _msgData() internal view virtual returns (bytes calldata) {\r\n    return msg.data;\r\n  }\r\n}\r\n\r\nabstract contract Ownable is Context {\r\n  address private _owner;\r\n\r\n  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\r\n\r\n  constructor() {\r\n    _transferOwnership(_msgSender());\r\n  }\r\n\r\n  modifier onlyOwner() {\r\n    _checkOwner();\r\n    _;\r\n  }\r\n\r\n  function owner() public view virtual returns (address) {\r\n    return _owner;\r\n  }\r\n\r\n  function _checkOwner() internal view virtual {\r\n    require(owner() == _msgSender(), \"Ownable: caller is not the owner\");\r\n  }\r\n\r\n  function renounceOwnership() public virtual onlyOwner {\r\n    _transferOwnership(address(0));\r\n  }\r\n\r\n  function transferOwnership(address newOwner) public virtual onlyOwner {\r\n    require(newOwner != address(0), \"Ownable: new owner is the zero address\");\r\n    _transferOwnership(newOwner);\r\n  }\r\n\r\n  function _transferOwnership(address newOwner) internal virtual {\r\n    address oldOwner = _owner;\r\n    _owner = newOwner;\r\n    emit OwnershipTransferred(oldOwner, newOwner);\r\n  }\r\n}\r\n\r\ninterface IERC20 {\r\n  event Approval(address indexed owner, address indexed spender, uint value);\r\n  event Transfer(address indexed from, address indexed to, uint value);\r\n\r\n  function name() external view returns (string memory);\r\n  function symbol() external view returns (string memory);\r\n  function decimals() external view returns (uint8);\r\n  function totalSupply() external view returns (uint);\r\n  function balanceOf(address owner) external view returns (uint);\r\n  function allowance(address owner, address spender) external view returns (uint);\r\n\r\n  function approve(address spender, uint value) external returns (bool);\r\n  function transfer(address to, uint value) external returns (bool);\r\n  function transferFrom(address from, address to, uint value) external returns (bool);\r\n}\r\n\r\ninterface IERC20Permit {\r\n  function permit(\r\n    address owner,\r\n    address spender,\r\n    uint256 value,\r\n    uint256 deadline,\r\n    uint8 v,\r\n    bytes32 r,\r\n    bytes32 s\r\n  ) external;\r\n  function nonces(address owner) external view returns (uint256);\r\n  function DOMAIN_SEPARATOR() external view returns (bytes32);\r\n}\r\n\r\nlibrary Address {\r\n  function isContract(address account) internal view returns (bool) {\r\n    return account.code.length > 0;\r\n  }\r\n\r\n  function sendValue(address payable recipient, uint256 amount) internal {\r\n    require(address(this).balance >= amount, \"Address: insufficient balance\");\r\n\r\n    (bool success, ) = recipient.call{value: amount}(\"\");\r\n    require(success, \"Address: unable to send value, recipient may have reverted\");\r\n  }\r\n\r\n  function functionCall(address target, bytes memory data) internal returns (bytes memory) {\r\n      return functionCallWithValue(target, data, 0, \"Address: low-level call failed\");\r\n  }\r\n\r\n  function functionCall(\r\n      address target,\r\n      bytes memory data,\r\n      string memory errorMessage\r\n  ) internal returns (bytes memory) {\r\n      return functionCallWithValue(target, data, 0, errorMessage);\r\n  }\r\n\r\n  function functionCallWithValue(\r\n      address target,\r\n      bytes memory data,\r\n      uint256 value\r\n  ) internal returns (bytes memory) {\r\n    return functionCallWithValue(target, data, value, \"Address: low-level call with value failed\");\r\n  }\r\n\r\n  function functionCallWithValue(\r\n    address target,\r\n    bytes memory data,\r\n    uint256 value,\r\n    string memory errorMessage\r\n  ) internal returns (bytes memory) {\r\n    require(address(this).balance >= value, \"Address: insufficient balance for call\");\r\n    (bool success, bytes memory returndata) = target.call{value: value}(data);\r\n    return verifyCallResultFromTarget(target, success, returndata, errorMessage);\r\n  }\r\n\r\n  function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {\r\n    return functionStaticCall(target, data, \"Address: low-level static call failed\");\r\n  }\r\n\r\n  function functionStaticCall(\r\n    address target,\r\n    bytes memory data,\r\n    string memory errorMessage\r\n  ) internal view returns (bytes memory) {\r\n    (bool success, bytes memory returndata) = target.staticcall(data);\r\n    return verifyCallResultFromTarget(target, success, returndata, errorMessage);\r\n  }\r\n\r\n  function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {\r\n    return functionDelegateCall(target, data, \"Address: low-level delegate call failed\");\r\n  }\r\n\r\n  function functionDelegateCall(\r\n    address target,\r\n    bytes memory data,\r\n    string memory errorMessage\r\n  ) internal returns (bytes memory) {\r\n    (bool success, bytes memory returndata) = target.delegatecall(data);\r\n    return verifyCallResultFromTarget(target, success, returndata, errorMessage);\r\n  }\r\n\r\n  function verifyCallResultFromTarget(\r\n    address target,\r\n    bool success,\r\n    bytes memory returndata,\r\n    string memory errorMessage\r\n  ) internal view returns (bytes memory) {\r\n    if (success) {\r\n      if (returndata.length == 0) {\r\n        require(isContract(target), \"Address: call to non-contract\");\r\n      }\r\n      return returndata;\r\n    } else {\r\n      _revert(returndata, errorMessage);\r\n    }\r\n  }\r\n\r\n  function verifyCallResult(\r\n    bool success,\r\n    bytes memory returndata,\r\n    string memory errorMessage\r\n  ) internal pure returns (bytes memory) {\r\n    if (success) {\r\n      return returndata;\r\n    } else {\r\n      _revert(returndata, errorMessage);\r\n    }\r\n  }\r\n\r\n  function _revert(bytes memory returndata, string memory errorMessage) private pure {\r\n    if (returndata.length > 0) {\r\n      assembly {\r\n        let returndata_size := mload(returndata)\r\n        revert(add(32, returndata), returndata_size)\r\n      }\r\n    } else {\r\n      revert(errorMessage);\r\n    }\r\n  }\r\n}\r\n\r\nlibrary SafeERC20 {\r\n  using Address for address;\r\n\r\n  function safeTransfer(\r\n    IERC20 token,\r\n    address to,\r\n    uint256 value\r\n  ) internal {\r\n    _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));\r\n  }\r\n\r\n  function safeTransferFrom(\r\n    IERC20 token,\r\n    address from,\r\n    address to,\r\n    uint256 value\r\n  ) internal {\r\n    _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));\r\n  }\r\n\r\n  function safeApprove(\r\n    IERC20 token,\r\n    address spender,\r\n    uint256 value\r\n  ) internal {\r\n    require(\r\n      (value == 0) || (token.allowance(address(this), spender) == 0),\r\n      \"SafeERC20: approve from non-zero to non-zero allowance\"\r\n    );\r\n    _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));\r\n  }\r\n\r\n  function safeIncreaseAllowance(\r\n    IERC20 token,\r\n    address spender,\r\n    uint256 value\r\n  ) internal {\r\n    uint256 newAllowance = token.allowance(address(this), spender) + value;\r\n    _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\r\n  }\r\n\r\n  function safeDecreaseAllowance(\r\n    IERC20 token,\r\n    address spender,\r\n    uint256 value\r\n  ) internal {\r\n    unchecked {\r\n      uint256 oldAllowance = token.allowance(address(this), spender);\r\n      require(oldAllowance >= value, \"SafeERC20: decreased allowance below zero\");\r\n      uint256 newAllowance = oldAllowance - value;\r\n      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));\r\n    }\r\n  }\r\n\r\n  function safePermit(\r\n    IERC20Permit token,\r\n    address owner,\r\n    address spender,\r\n    uint256 value,\r\n    uint256 deadline,\r\n    uint8 v,\r\n    bytes32 r,\r\n    bytes32 s\r\n  ) internal {\r\n    uint256 nonceBefore = token.nonces(owner);\r\n    token.permit(owner, spender, value, deadline, v, r, s);\r\n    uint256 nonceAfter = token.nonces(owner);\r\n    require(nonceAfter == nonceBefore + 1, \"SafeERC20: permit did not succeed\");\r\n  }\r\n\r\n  function _callOptionalReturn(IERC20 token, bytes memory data) private {\r\n    bytes memory returndata = address(token).functionCall(data, \"SafeERC20: low-level call failed\");\r\n    if (returndata.length > 0) {\r\n      require(abi.decode(returndata, (bool)), \"SafeERC20: ERC20 operation did not succeed\");\r\n    }\r\n  }\r\n}\r\n\r\nabstract contract ReentrancyGuard {\r\n  uint256 private constant _NOT_ENTERED = 1;\r\n  uint256 private constant _ENTERED = 2;\r\n\r\n  uint256 private _status;\r\n\r\n  constructor () {\r\n    _status = _NOT_ENTERED;\r\n  }\r\n  \r\n  modifier nonReentrant() {\r\n    require(_status != _ENTERED, \"ReentrancyGuard: reentrant call\");\r\n    _status = _ENTERED;\r\n    _;\r\n    _status = _NOT_ENTERED;\r\n  }\r\n}\r\n\r\ncontract LQDXStaker is Ownable, ReentrancyGuard {\r\n  using SafeERC20 for IERC20;\r\n\r\n  mapping (address => address) public rewardToken;\r\n  address public treasury;\r\n\r\n  struct StakeInfo {\r\n    uint256 amount;\r\n    uint256 timestamp;\r\n  }\r\n\r\n  struct lpHoldInfo {\r\n    uint256 amount;\r\n    StakeInfo[] stakeLog;\r\n    uint256 logStartIndex;\r\n    uint256 debtReward;\r\n    uint256 timestamp;\r\n  }\r\n\r\n  struct allocInfo {\r\n    uint256 alloc;\r\n    uint256 timestamp;\r\n  }\r\n\r\n  mapping (address => uint256) public totalStake;\r\n  // account -> stakingtoken -> info\r\n  mapping (address => mapping(address => lpHoldInfo)) public userInfo;\r\n  // token -> alloc\r\n  mapping (address => allocInfo[]) public allocPoints;\r\n  mapping (address => uint256) public lockPeriod;\r\n  mapping (address => bool) public operators;\r\n  uint256 public totalAllocPoint = 365 * 24 * 3600 * 100 * 100000;\r\n  uint256 public coreDecimal = 1000_000;\r\n  uint256 public deposistFee = 0;\r\n  uint256 public withdrawFee = 10_000;\r\n  uint256 public rewardFee = 10_000;\r\n\r\n  event UpdateAlloc(address token, uint256 alloc, address operator);\r\n  event UpdateLockPeriod(address token, uint256 period, address operator);\r\n  event ClaimLiquidXReward(address account, address token, uint256 amount);\r\n  event LQDXStakeingFee(address token, uint256 fee, address treasury);\r\n\r\n  constructor(address _treasury, address[] memory _stakingTokens, address[] memory _rewardTokens, uint256[] memory _apys) {\r\n    treasury = _treasury;\r\n    require(_stakingTokens.length == _rewardTokens.length, \"Wrong Parameters\");\r\n    require(_stakingTokens.length == _apys.length, \"Wrong Parameters\");\r\n    uint256 len = _stakingTokens.length;\r\n    for (uint256 x = 0; x<len; x++) {\r\n      rewardToken[_stakingTokens[x]] = _rewardTokens[x];\r\n      allocInfo memory tmp = allocInfo(_apys[x], block.timestamp);\r\n      allocPoints[_stakingTokens[x]].push(tmp);\r\n    }\r\n  }\r\n\r\n  receive() external payable {\r\n  }\r\n\r\n  event Stake(address account, address token, uint256 amount, uint256 tvl, uint256 timestamp);\r\n  event Unstake(address account, address token, uint256 amount, uint256 tvl, uint256 timestamp);\r\n\r\n  function stake(address account, address token, uint256 amount) public payable {\r\n    require(amount > 0, \"LQDXStaker: Wrong amount\");\r\n    if (token != address(0)) {\r\n      uint256 balance = IERC20(token).balanceOf(address(this));\r\n      IERC20(token).safeTransferFrom(account, address(this), amount);\r\n      amount = IERC20(token).balanceOf(address(this)) - balance;\r\n    }\r\n    else {\r\n      amount = msg.value;\r\n    }\r\n\r\n    claim(account, token);\r\n    StakeInfo memory log = StakeInfo(amount, block.timestamp);\r\n    userInfo[account][token].stakeLog.push(log);\r\n    userInfo[account][token].amount += amount;\r\n    totalStake[token] += amount;\r\n    emit Stake(account, token, amount, userInfo[account][token].amount, block.timestamp);\r\n  }\r\n\r\n  function getAvailalbeUnstakeAmount(address account, address token) public view returns (uint256, uint256) {\r\n    uint256 period = lockPeriod[token];\r\n    uint256 currentTs = block.timestamp;\r\n    if (period == 0) return (userInfo[account][token].amount, currentTs);\r\n    uint256 len = userInfo[account][token].stakeLog.length;\r\n    uint256 totalAmount = 0;\r\n    for (uint256 x = userInfo[account][token].logStartIndex; x < len; x ++) {\r\n      if (userInfo[account][token].stakeLog[x].timestamp + period <= currentTs) {\r\n        totalAmount += userInfo[account][token].stakeLog[x].amount;\r\n      }\r\n      else {\r\n        if (totalAmount > 0) {\r\n          return (totalAmount, currentTs);\r\n        }\r\n        else {\r\n          return (0, userInfo[account][token].stakeLog[x].timestamp + period);\r\n        }\r\n      }\r\n    }\r\n    return (totalAmount, currentTs);\r\n  }\r\n\r\n  function _updateUserStakeLog(address account, address token, uint256 amount) private {\r\n    if (userInfo[account][token].amount > amount) {\r\n      userInfo[account][token].amount -= amount;\r\n    }\r\n    else {\r\n      userInfo[account][token].amount = 0;\r\n    }\r\n\r\n    uint256 len = userInfo[account][token].stakeLog.length;\r\n    for (uint256 x = userInfo[account][token].logStartIndex; x < len; x ++) {\r\n      if (userInfo[account][token].stakeLog[x].amount >= amount) {\r\n        userInfo[account][token].stakeLog[x].amount -= amount;\r\n        break;\r\n      }\r\n      else {\r\n        userInfo[account][token].stakeLog[x].amount = 0;\r\n        userInfo[account][token].logStartIndex ++;\r\n        amount -= userInfo[account][token].stakeLog[x].amount;\r\n      }\r\n    }\r\n    \r\n    emit Unstake(account, token, amount, userInfo[account][token].amount, block.timestamp);\r\n  }\r\n\r\n  function unstake(address account, address token, uint256 amount) public {\r\n    require(amount > 0, \"LQDXStaker: Wrong amount\");\r\n    claim(account, token);\r\n    (uint256 unstakableAmount, ) = getAvailalbeUnstakeAmount(account, token);\r\n    if (unstakableAmount > 0) {\r\n      if (unstakableAmount < amount) {\r\n        amount = unstakableAmount;\r\n      }\r\n\r\n      _updateUserStakeLog(account, token, amount);\r\n\r\n      if (totalStake[token] >= amount) {\r\n        totalStake[token] -= amount;\r\n      }\r\n      else {\r\n        totalStake[token] = 0;\r\n      }\r\n\r\n      if (withdrawFee > 0) {\r\n        amount = _cutFee(token, withdrawFee, amount);\r\n      }\r\n      if (amount > 0) {\r\n        if (token != address(0)) {\r\n          IERC20(token).safeTransfer(account, amount);\r\n        }\r\n        else {\r\n          (bool success, ) = payable(account).call{value: amount}(\"\");\r\n          require(success, \"LQDXStaker Failed unstake\");\r\n        }\r\n      }\r\n    }\r\n  }\r\n\r\n  function getCurrentAllocPoint(address pool) public view returns(uint256) {\r\n    uint256 len = allocPoints[pool].length;\r\n    if (len > 0) {\r\n      return allocPoints[pool][len-1].alloc;\r\n    }\r\n    else {\r\n      return 0;\r\n    }\r\n  }\r\n\r\n  function _getStartAlloc(address lpAddress, uint256 timestamp) internal view returns(uint256) {\r\n    uint256 index = 0;\r\n    uint256 len = allocPoints[lpAddress].length;\r\n    for (; index < len; index++) {\r\n      if (allocPoints[lpAddress][index].timestamp > timestamp) break;\r\n    }\r\n    return index;\r\n  }\r\n\r\n  function getReward(address account, address token) public view returns(uint256) {\r\n    lpHoldInfo memory userI = userInfo[account][token];\r\n    if (userI.amount > 0) {\r\n      uint256 index = _getStartAlloc(token, userI.timestamp);\r\n      uint256 len = allocPoints[token].length;\r\n      uint256 amount = userI.debtReward;\r\n      uint256 alloc = 0;\r\n      uint256 ts = 0;\r\n      uint256 startTs = userI.timestamp;\r\n      if (index < len && len > 0) {\r\n        for (; index < len; index ++) {\r\n          ts = allocPoints[token][index].timestamp;\r\n          if (index > 0) {\r\n            alloc = allocPoints[token][index-1].alloc;\r\n            if (alloc > 0) {\r\n              amount += userI.amount * alloc * (ts - startTs) / totalAllocPoint;\r\n            }\r\n          }\r\n          startTs = ts;\r\n        }\r\n        alloc = allocPoints[token][index-1].alloc;\r\n      }\r\n      else if (index > 0) {\r\n        alloc = allocPoints[token][index-1].alloc;\r\n        ts = userI.timestamp;\r\n      }\r\n      if (alloc > 0) {\r\n        amount += userI.amount * alloc * (block.timestamp - ts) / totalAllocPoint;\r\n      }\r\n      return amount;\r\n    }\r\n    else {\r\n      return 0;\r\n    }\r\n  }\r\n\r\n  function claim(address account, address token) public nonReentrant {\r\n    require(account == msg.sender || operators[msg.sender], \"LQDXStaker: FORBIDDEN\");\r\n    uint256 amount = getReward(account, token);\r\n    if (amount > 0) {\r\n      address pmRewardToken = rewardToken[token];\r\n      uint256 balance = 0;\r\n      if (pmRewardToken != address(0)) {\r\n        balance = IERC20(pmRewardToken).balanceOf(address(this));\r\n      }\r\n      else {\r\n        balance = address(this).balance;\r\n      }\r\n      if (token == pmRewardToken) {\r\n        uint256 tvl = totalStake[token];\r\n        if (balance >= tvl) {\r\n          balance -= tvl;\r\n        }\r\n        else {\r\n          balance = 0;\r\n        }\r\n      }\r\n      if (balance >= amount) {\r\n        if (rewardFee > 0) {\r\n          amount = _cutFee(pmRewardToken, rewardFee, amount);\r\n        }\r\n        if (pmRewardToken != address(0)) {\r\n          IERC20(pmRewardToken).safeTransfer(account, amount);\r\n        }\r\n        else {\r\n          (bool success, ) = payable(account).call{value: amount}(\"\");\r\n          require(success, \"LQDXStaker Failed claim\");\r\n        }\r\n        emit ClaimLiquidXReward(account, pmRewardToken, amount);\r\n        userInfo[account][token].debtReward = 0;\r\n      }\r\n      else {\r\n        if (balance > 0) {\r\n          if (rewardFee > 0) {\r\n            balance = _cutFee(pmRewardToken, rewardFee, balance);\r\n          }\r\n          if (pmRewardToken != address(0)) {\r\n            IERC20(pmRewardToken).safeTransfer(account, balance);\r\n          }\r\n          else {\r\n            (bool success, ) = payable(account).call{value: balance}(\"\");\r\n            require(success, \"LQDXStaker Failed claim\");\r\n          }\r\n          emit ClaimLiquidXReward(account, pmRewardToken, balance);\r\n        }\r\n        userInfo[account][token].debtReward += (amount - balance);\r\n      }\r\n    }\r\n    userInfo[account][token].timestamp = block.timestamp;\r\n  }\r\n\r\n  function forceSetUserInfo(address account, address token, uint256 amount, uint256 timetamp, uint256 debt, uint256[] calldata amounts, uint256[] calldata timestamps) public onlyOwner {\r\n    require(amounts.length == timestamps.length, \"LQDXStaker: wrong parameter\");\r\n    userInfo[account][token].amount = amount;\r\n    userInfo[account][token].debtReward = debt;\r\n    userInfo[account][token].timestamp = timetamp;\r\n    uint256 len = timestamps.length;\r\n    for (uint256 x = 0; x < len; x++) {\r\n      StakeInfo memory log = StakeInfo(amounts[x], timestamps[x]);\r\n      userInfo[account][token].stakeLog.push(log);\r\n    }\r\n  }\r\n\r\n  function setAllockPoint(address token, uint256 alloc) public onlyOwner {\r\n    allocInfo memory tmp = allocInfo(alloc, block.timestamp);\r\n    allocPoints[token].push(tmp);\r\n    emit UpdateAlloc(token, alloc, msg.sender);\r\n  }\r\n\r\n  function setUnlockPeriod(address token, uint256 period) public onlyOwner {\r\n    lockPeriod[token] = period;\r\n    emit UpdateLockPeriod(token, period, msg.sender);\r\n  }\r\n\r\n  function setDepositFee(uint256 fee) public onlyOwner {\r\n    deposistFee = fee;\r\n  }\r\n\r\n  function setWithdrawFee(uint256 fee) public onlyOwner {\r\n    withdrawFee = fee;\r\n  }\r\n\r\n  function setRewardFee(uint256 fee) public onlyOwner {\r\n    rewardFee = fee;\r\n  }\r\n\r\n  function setRewardToken(address _stakingToken, address _reward) public onlyOwner() {\r\n    rewardToken[_stakingToken] = _reward;\r\n  }\r\n\r\n  function withdrawToken(address token, address target, uint256 amount) public onlyOwner {\r\n    if (token == address(0)) {\r\n      (bool success, ) = payable(target).call{value: amount}(\"\");\r\n      require(success, \"LQDXStaker: Failed withdraw\");\r\n    }\r\n    else {\r\n      IERC20(token).safeTransfer(target, amount);\r\n    }\r\n  }\r\n\r\n  function setOperator(address _operator, bool mode) external onlyOwner {\r\n    operators[_operator] = mode;\r\n  }\r\n\r\n  function _cutFee(address _token, uint256 _fee, uint256 _amount) internal returns(uint256) {\r\n    if (_amount > 0) {\r\n      uint256 fee = _amount * _fee / coreDecimal;\r\n      if (fee > 0) {\r\n        if (_token == address(0)) {\r\n          (bool success, ) = payable(treasury).call{value: fee}(\"\");\r\n          require(success, \"LQDXStaker Failed cut fee\");\r\n        }\r\n        else {\r\n          IERC20(_token).safeTransfer(treasury, fee);\r\n        }\r\n        emit LQDXStakeingFee(_token, fee, treasury);\r\n      }\r\n      return _amount - fee;\r\n    }\r\n    return 0;\r\n  }\r\n}\r\n"
      }
    },
    "settings": {
      "remappings": [],
      "optimizer": {
        "enabled": true,
        "runs": 200
      },
      "evmVersion": "paris",
      "libraries": {},
      "outputSelection": {
        "*": {
          "*": [
            "evm.bytecode",
            "evm.deployedBytecode",
            "devdoc",
            "userdoc",
            "metadata",
            "abi"
          ]
        }
      }
    }
  }}