ETH Price: $3,758.93 (+0.85%)

Transaction Decoder

Block:
14505707 at Apr-02-2022 08:29:23 AM +UTC
Transaction Fee:
0.007080723897008064 ETH $26.62
Gas Used:
176,217 Gas / 40.181843392 Gwei

Emitted Events:

358 LooksRareToken.Transfer( from=[Sender] 0xf018cd5eb7b4a9668aeb17e53120f703a84345d2, to=[Receiver] AggregatorFeeSharingWithUniswapV3, value=10040000000000000000001 )
359 LooksRareToken.Approval( owner=[Sender] 0xf018cd5eb7b4a9668aeb17e53120f703a84345d2, spender=[Receiver] AggregatorFeeSharingWithUniswapV3, value=115792089237316195423570985008687907853269984665640564009632158807284206830815 )
360 LooksRareToken.Transfer( from=[Receiver] AggregatorFeeSharingWithUniswapV3, to=FeeSharingSystem, value=10040000000000000000001 )
361 LooksRareToken.Approval( owner=[Receiver] AggregatorFeeSharingWithUniswapV3, spender=FeeSharingSystem, value=115792089237316195423570985008687907853269984665640436933708936305133849562099 )
362 LooksRareToken.Transfer( from=FeeSharingSystem, to=TokenDistributor, value=10040000000000000000001 )
363 LooksRareToken.Approval( owner=FeeSharingSystem, spender=TokenDistributor, value=115792089237316195423570985008687907853269984665639520868763252646742605774884 )
364 TokenDistributor.Deposit( user=FeeSharingSystem, amount=10040000000000000000001, harvestedAmount=0 )
365 FeeSharingSystem.Deposit( user=[Receiver] AggregatorFeeSharingWithUniswapV3, amount=10040000000000000000001, harvestedAmount=0 )
366 AggregatorFeeSharingWithUniswapV3.Deposit( user=[Sender] 0xf018cd5eb7b4a9668aeb17e53120f703a84345d2, amount=10040000000000000000001 )

Account State Difference:

  Address   Before After State Difference Code
0x3ab16Af1...8D00fd9ba
(LooksRare: Aggregator Fee Sharing Uniswap V3)
0x465A790B...ad7e0d3b1
(LooksRare: Token Distributor)
0xBcD7254A...c5dCC12ce
(LooksRare: Fee Sharing)
(Ezil.me : Ezil Pool 4)
266.013043072746393566 Eth266.013307398246393566 Eth0.0002643255
0xF018cd5E...3a84345d2
0.088785557767199563 Eth
Nonce: 157
0.081704833870191499 Eth
Nonce: 158
0.007080723897008064
0xf4d2888d...4c092421E

Execution Trace

AggregatorFeeSharingWithUniswapV3.deposit( amount=10040000000000000000001 )
  • LooksRareToken.transferFrom( sender=0xF018cd5EB7b4a9668aEb17E53120F703a84345d2, recipient=0x3ab16Af1315dc6C95F83Cbf522fecF98D00fd9ba, amount=10040000000000000000001 ) => ( True )
  • FeeSharingSystem.calculateSharesValueInLOOKS( user=0x3ab16Af1315dc6C95F83Cbf522fecF98D00fd9ba ) => ( 112708582237368051529132461 )
    • TokenDistributor.userInfo( 0xBcD7254A1D759EFA08eC7c3291B2E85c5dCC12ce ) => ( amount=303327122333669502960347691, rewardDebt=134506577682482989695515578 )
    • TokenDistributor.calculatePendingRewards( user=0xBcD7254A1D759EFA08eC7c3291B2E85c5dCC12ce ) => ( 0 )
    • FeeSharingSystem.deposit( amount=10040000000000000000001, claimRewardToken=False )
      • TokenDistributor.CALL( )
      • TokenDistributor.userInfo( 0xBcD7254A1D759EFA08eC7c3291B2E85c5dCC12ce ) => ( amount=303327122333669502960347691, rewardDebt=134506577682482989695515578 )
      • LooksRareToken.transferFrom( sender=0x3ab16Af1315dc6C95F83Cbf522fecF98D00fd9ba, recipient=0xBcD7254A1D759EFA08eC7c3291B2E85c5dCC12ce, amount=10040000000000000000001 ) => ( True )
      • LooksRareToken.allowance( owner=0xBcD7254A1D759EFA08eC7c3291B2E85c5dCC12ce, spender=0x465A790B428268196865a3AE2648481ad7e0d3b1 ) => ( 115792089237316195423570985008687907853269984665639520878803252646742605774885 )
      • TokenDistributor.deposit( amount=10040000000000000000001 )
        • LooksRareToken.transferFrom( sender=0xBcD7254A1D759EFA08eC7c3291B2E85c5dCC12ce, recipient=0x465A790B428268196865a3AE2648481ad7e0d3b1, amount=10040000000000000000001 ) => ( True )
          File 1 of 4: AggregatorFeeSharingWithUniswapV3
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
          import {Pausable} from "@openzeppelin/contracts/security/Pausable.sol";
          import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
          import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import {ISwapRouter} from "./uniswap-interfaces/ISwapRouter.sol";
          import {FeeSharingSystem} from "./FeeSharingSystem.sol";
          /**
           * @title AggregatorFeeSharingWithUniswapV3
           * @notice It sells WETH to LOOKS using Uniswap V3.
           * @dev Prime shares represent the number of shares in the FeeSharingSystem. When not specified, shares represent secondary shares in this contract.
           */
          contract AggregatorFeeSharingWithUniswapV3 is Ownable, Pausable, ReentrancyGuard {
              using SafeERC20 for IERC20;
              // Maximum buffer between 2 harvests (in blocks)
              uint256 public constant MAXIMUM_HARVEST_BUFFER_BLOCKS = 6500;
              // FeeSharingSystem (handles the distribution of WETH for LOOKS stakers)
              FeeSharingSystem public immutable feeSharingSystem;
              // Router of Uniswap v3
              ISwapRouter public immutable uniswapRouter;
              // Minimum deposit in LOOKS (it is derived from the FeeSharingSystem)
              uint256 public immutable MINIMUM_DEPOSIT_LOOKS;
              // LooksRare Token (LOOKS)
              IERC20 public immutable looksRareToken;
              // Reward token (WETH)
              IERC20 public immutable rewardToken;
              // Whether harvest and WETH selling is triggered automatically at user action
              bool public canHarvest;
              // Trading fee on Uniswap v3 (e.g., 3000 ---> 0.3%)
              uint24 public tradingFeeUniswapV3;
              // Buffer between two harvests (in blocks)
              uint256 public harvestBufferBlocks;
              // Last user action block
              uint256 public lastHarvestBlock;
              // Maximum price of LOOKS (in WETH) multiplied 1e18 (e.g., 0.0004 ETH --> 4e14)
              uint256 public maxPriceLOOKSInWETH;
              // Threshold amount (in rewardToken)
              uint256 public thresholdAmount;
              // Total number of shares outstanding
              uint256 public totalShares;
              // Keeps track of number of user shares
              mapping(address => uint256) public userInfo;
              event ConversionToLOOKS(uint256 amountSold, uint256 amountReceived);
              event Deposit(address indexed user, uint256 amount);
              event FailedConversion();
              event HarvestStart();
              event HarvestStop();
              event NewHarvestBufferBlocks(uint256 harvestBufferBlocks);
              event NewMaximumPriceLOOKSInWETH(uint256 maxPriceLOOKSInWETH);
              event NewThresholdAmount(uint256 thresholdAmount);
              event NewTradingFeeUniswapV3(uint24 tradingFeeUniswapV3);
              event Withdraw(address indexed user, uint256 amount);
              /**
               * @notice Constructor
               * @param _feeSharingSystem address of the fee sharing system contract
               * @param _uniswapRouter address of the Uniswap v3 router
               */
              constructor(address _feeSharingSystem, address _uniswapRouter) {
                  address looksRareTokenAddress = address(FeeSharingSystem(_feeSharingSystem).looksRareToken());
                  address rewardTokenAddress = address(FeeSharingSystem(_feeSharingSystem).rewardToken());
                  looksRareToken = IERC20(looksRareTokenAddress);
                  rewardToken = IERC20(rewardTokenAddress);
                  feeSharingSystem = FeeSharingSystem(_feeSharingSystem);
                  uniswapRouter = ISwapRouter(_uniswapRouter);
                  IERC20(looksRareTokenAddress).approve(_feeSharingSystem, type(uint256).max);
                  IERC20(rewardTokenAddress).approve(_uniswapRouter, type(uint256).max);
                  tradingFeeUniswapV3 = 3000;
                  MINIMUM_DEPOSIT_LOOKS = FeeSharingSystem(_feeSharingSystem).PRECISION_FACTOR();
              }
              /**
               * @notice Deposit LOOKS tokens
               * @param amount amount to deposit (in LOOKS)
               * @dev There is a limit of 1 LOOKS per deposit to prevent potential manipulation of the shares
               */
              function deposit(uint256 amount) external nonReentrant whenNotPaused {
                  require(amount >= MINIMUM_DEPOSIT_LOOKS, "Deposit: Amount must be >= 1 LOOKS");
                  if (block.number > (lastHarvestBlock + harvestBufferBlocks) && canHarvest && totalShares != 0) {
                      _harvestAndSellAndCompound();
                  }
                  // Transfer LOOKS tokens to this address
                  looksRareToken.safeTransferFrom(msg.sender, address(this), amount);
                  // Fetch the total number of LOOKS staked by this contract
                  uint256 totalAmountStaked = feeSharingSystem.calculateSharesValueInLOOKS(address(this));
                  uint256 currentShares = totalShares == 0 ? amount : (amount * totalShares) / totalAmountStaked;
                  require(currentShares != 0, "Deposit: Fail");
                  // Adjust number of shares for user/total
                  userInfo[msg.sender] += currentShares;
                  totalShares += currentShares;
                  // Deposit to FeeSharingSystem contract
                  feeSharingSystem.deposit(amount, false);
                  emit Deposit(msg.sender, amount);
              }
              /**
               * @notice Redeem shares for LOOKS tokens
               * @param shares number of shares to redeem
               */
              function withdraw(uint256 shares) external nonReentrant {
                  require(
                      (shares > 0) && (shares <= userInfo[msg.sender]),
                      "Withdraw: Shares equal to 0 or larger than user shares"
                  );
                  _withdraw(shares);
              }
              /**
               * @notice Withdraw all shares of sender
               */
              function withdrawAll() external nonReentrant {
                  require(userInfo[msg.sender] > 0, "Withdraw: Shares equal to 0");
                  _withdraw(userInfo[msg.sender]);
              }
              /**
               * @notice Harvest pending WETH, sell them to LOOKS, and deposit LOOKS (if possible)
               * @dev Only callable by owner.
               */
              function harvestAndSellAndCompound() external nonReentrant onlyOwner {
                  require(totalShares != 0, "Harvest: No share");
                  require(block.number != lastHarvestBlock, "Harvest: Already done");
                  _harvestAndSellAndCompound();
              }
              /**
               * @notice Adjust allowance if necessary
               * @dev Only callable by owner.
               */
              function checkAndAdjustLOOKSTokenAllowanceIfRequired() external onlyOwner {
                  looksRareToken.approve(address(feeSharingSystem), type(uint256).max);
              }
              /**
               * @notice Adjust allowance if necessary
               * @dev Only callable by owner.
               */
              function checkAndAdjustRewardTokenAllowanceIfRequired() external onlyOwner {
                  rewardToken.approve(address(uniswapRouter), type(uint256).max);
              }
              /**
               * @notice Update harvest buffer block
               * @param _newHarvestBufferBlocks buffer in blocks between two harvest operations
               * @dev Only callable by owner.
               */
              function updateHarvestBufferBlocks(uint256 _newHarvestBufferBlocks) external onlyOwner {
                  require(
                      _newHarvestBufferBlocks <= MAXIMUM_HARVEST_BUFFER_BLOCKS,
                      "Owner: Must be below MAXIMUM_HARVEST_BUFFER_BLOCKS"
                  );
                  harvestBufferBlocks = _newHarvestBufferBlocks;
                  emit NewHarvestBufferBlocks(_newHarvestBufferBlocks);
              }
              /**
               * @notice Start automatic harvest/selling transactions
               * @dev Only callable by owner
               */
              function startHarvest() external onlyOwner {
                  canHarvest = true;
                  emit HarvestStart();
              }
              /**
               * @notice Stop automatic harvest transactions
               * @dev Only callable by owner
               */
              function stopHarvest() external onlyOwner {
                  canHarvest = false;
                  emit HarvestStop();
              }
              /**
               * @notice Update maximum price of LOOKS in WETH
               * @param _newMaxPriceLOOKSInWETH new maximum price of LOOKS in WETH times 1e18
               * @dev Only callable by owner
               */
              function updateMaxPriceOfLOOKSInWETH(uint256 _newMaxPriceLOOKSInWETH) external onlyOwner {
                  maxPriceLOOKSInWETH = _newMaxPriceLOOKSInWETH;
                  emit NewMaximumPriceLOOKSInWETH(_newMaxPriceLOOKSInWETH);
              }
              /**
               * @notice Adjust trading fee for Uniswap v3
               * @param _newTradingFeeUniswapV3 new tradingFeeUniswapV3
               * @dev Only callable by owner. Can only be 10,000 (1%), 3000 (0.3%), or 500 (0.05%).
               */
              function updateTradingFeeUniswapV3(uint24 _newTradingFeeUniswapV3) external onlyOwner {
                  require(
                      _newTradingFeeUniswapV3 == 10000 || _newTradingFeeUniswapV3 == 3000 || _newTradingFeeUniswapV3 == 500,
                      "Owner: Fee invalid"
                  );
                  tradingFeeUniswapV3 = _newTradingFeeUniswapV3;
                  emit NewTradingFeeUniswapV3(_newTradingFeeUniswapV3);
              }
              /**
               * @notice Adjust threshold amount for periodic Uniswap v3 WETH --> LOOKS conversion
               * @param _newThresholdAmount new threshold amount (in WETH)
               * @dev Only callable by owner
               */
              function updateThresholdAmount(uint256 _newThresholdAmount) external onlyOwner {
                  thresholdAmount = _newThresholdAmount;
                  emit NewThresholdAmount(_newThresholdAmount);
              }
              /**
               * @notice Pause
               * @dev Only callable by owner
               */
              function pause() external onlyOwner whenNotPaused {
                  _pause();
              }
              /**
               * @notice Unpause
               * @dev Only callable by owner
               */
              function unpause() external onlyOwner whenPaused {
                  _unpause();
              }
              /**
               * @notice Calculate price of one share (in LOOKS token)
               * Share price is expressed times 1e18
               */
              function calculateSharePriceInLOOKS() external view returns (uint256) {
                  uint256 totalAmountStakedWithAggregator = feeSharingSystem.calculateSharesValueInLOOKS(address(this));
                  return
                      totalShares == 0
                          ? MINIMUM_DEPOSIT_LOOKS
                          : (totalAmountStakedWithAggregator * MINIMUM_DEPOSIT_LOOKS) / (totalShares);
              }
              /**
               * @notice Calculate price of one share (in prime share)
               * Share price is expressed times 1e18
               */
              function calculateSharePriceInPrimeShare() external view returns (uint256) {
                  (uint256 totalNumberPrimeShares, , ) = feeSharingSystem.userInfo(address(this));
                  return
                      totalShares == 0 ? MINIMUM_DEPOSIT_LOOKS : (totalNumberPrimeShares * MINIMUM_DEPOSIT_LOOKS) / totalShares;
              }
              /**
               * @notice Calculate shares value of a user (in LOOKS)
               * @param user address of the user
               */
              function calculateSharesValueInLOOKS(address user) external view returns (uint256) {
                  uint256 totalAmountStakedWithAggregator = feeSharingSystem.calculateSharesValueInLOOKS(address(this));
                  return totalShares == 0 ? 0 : (totalAmountStakedWithAggregator * userInfo[user]) / totalShares;
              }
              /**
               * @notice Harvest pending WETH, sell them to LOOKS, and deposit LOOKS (if possible)
               */
              function _harvestAndSellAndCompound() internal {
                  // Try/catch to prevent revertions if nothing to harvest
                  try feeSharingSystem.harvest() {} catch {}
                  uint256 amountToSell = rewardToken.balanceOf(address(this));
                  if (amountToSell >= thresholdAmount) {
                      bool isExecuted = _sellRewardTokenToLOOKS(amountToSell);
                      if (isExecuted) {
                          uint256 adjustedAmount = looksRareToken.balanceOf(address(this));
                          if (adjustedAmount >= MINIMUM_DEPOSIT_LOOKS) {
                              feeSharingSystem.deposit(adjustedAmount, false);
                          }
                      }
                  }
                  // Adjust last harvest block
                  lastHarvestBlock = block.number;
              }
              /**
               * @notice Sell WETH to LOOKS
               * @param _amount amount of rewardToken to convert (WETH)
               * @return whether the transaction went through
               */
              function _sellRewardTokenToLOOKS(uint256 _amount) internal returns (bool) {
                  uint256 amountOutMinimum = maxPriceLOOKSInWETH != 0 ? (_amount * 1e18) / maxPriceLOOKSInWETH : 0;
                  // Set the order parameters
                  ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams(
                      address(rewardToken), // tokenIn
                      address(looksRareToken), // tokenOut
                      tradingFeeUniswapV3, // fee
                      address(this), // recipient
                      block.timestamp, // deadline
                      _amount, // amountIn
                      amountOutMinimum, // amountOutMinimum
                      0 // sqrtPriceLimitX96
                  );
                  // Swap on Uniswap V3
                  try uniswapRouter.exactInputSingle(params) returns (uint256 amountOut) {
                      emit ConversionToLOOKS(_amount, amountOut);
                      return true;
                  } catch {
                      emit FailedConversion();
                      return false;
                  }
              }
              /**
               * @notice Withdraw shares
               * @param _shares number of shares to redeem
               * @dev The difference between the two snapshots of LOOKS balances is used to know how many tokens to transfer to user.
               */
              function _withdraw(uint256 _shares) internal {
                  if (block.number > (lastHarvestBlock + harvestBufferBlocks) && canHarvest) {
                      _harvestAndSellAndCompound();
                  }
                  // Take snapshot of current LOOKS balance
                  uint256 previousBalanceLOOKS = looksRareToken.balanceOf(address(this));
                  // Fetch total number of prime shares
                  (uint256 totalNumberPrimeShares, , ) = feeSharingSystem.userInfo(address(this));
                  // Calculate number of prime shares to redeem based on existing shares (from this contract)
                  uint256 currentNumberPrimeShares = (totalNumberPrimeShares * _shares) / totalShares;
                  // Adjust number of shares for user/total
                  userInfo[msg.sender] -= _shares;
                  totalShares -= _shares;
                  // Withdraw amount equivalent in prime shares
                  feeSharingSystem.withdraw(currentNumberPrimeShares, false);
                  // Calculate the difference between the current balance of LOOKS with the previous snapshot
                  uint256 amountToTransfer = looksRareToken.balanceOf(address(this)) - previousBalanceLOOKS;
                  // Transfer the LOOKS amount back to user
                  looksRareToken.safeTransfer(msg.sender, amountToTransfer);
                  emit Withdraw(msg.sender, amountToTransfer);
              }
          }// SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          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 Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
                  _;
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing 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);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (security/Pausable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which allows children to implement an emergency stop
           * mechanism that can be triggered by an authorized account.
           *
           * This module is used through inheritance. It will make available the
           * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
           * the functions of your contract. Note that they will not be pausable by
           * simply including this module, only once the modifiers are put in place.
           */
          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 Returns true if the contract is paused, and false otherwise.
               */
              function paused() public view virtual returns (bool) {
                  return _paused;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is not paused.
               *
               * Requirements:
               *
               * - The contract must not be paused.
               */
              modifier whenNotPaused() {
                  require(!paused(), "Pausable: paused");
                  _;
              }
              /**
               * @dev Modifier to make a function callable only when the contract is paused.
               *
               * Requirements:
               *
               * - The contract must be paused.
               */
              modifier whenPaused() {
                  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());
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuard {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
              uint256 private _status;
              constructor() {
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and making it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  // On the first call to nonReentrant, _notEntered will be true
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
                  _;
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          import "../../../utils/Address.sol";
          /**
           * @title SafeERC20
           * @dev Wrappers around ERC20 operations that throw on failure (when the token
           * contract returns false). Tokens that return no value (and instead revert or
           * throw on failure) are also supported, non-reverting calls are assumed to be
           * successful.
           * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
           * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
           */
          library SafeERC20 {
              using Address for address;
              function safeTransfer(
                  IERC20 token,
                  address to,
                  uint256 value
              ) internal {
                  _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
              }
              function safeTransferFrom(
                  IERC20 token,
                  address from,
                  address to,
                  uint256 value
              ) internal {
                  _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
              }
              /**
               * @dev Deprecated. This function has issues similar to the ones found in
               * {IERC20-approve}, and its usage is discouraged.
               *
               * Whenever possible, use {safeIncreaseAllowance} and
               * {safeDecreaseAllowance} instead.
               */
              function safeApprove(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  // safeApprove should only be called when setting an initial allowance,
                  // or when resetting it to zero. To increase and decrease it, use
                  // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                  require(
                      (value == 0) || (token.allowance(address(this), spender) == 0),
                      "SafeERC20: approve from non-zero to non-zero allowance"
                  );
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
              }
              function safeIncreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  uint256 newAllowance = token.allowance(address(this), spender) + value;
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
              }
              function safeDecreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  unchecked {
                      uint256 oldAllowance = token.allowance(address(this), spender);
                      require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                      uint256 newAllowance = oldAllowance - value;
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
              }
              /**
               * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
               * on the return value: the return value is optional (but if data is returned, it must not be false).
               * @param token The token targeted by the call.
               * @param data The call data (encoded using abi.encode or one of its variants).
               */
              function _callOptionalReturn(IERC20 token, bytes memory data) private {
                  // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                  // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                  // the target address contains contract code and also asserts for success in the low-level call.
                  bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                  if (returndata.length > 0) {
                      // Return data is optional
                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                  }
              }
          }
          // SPDX-License-Identifier: GPL-2.0-or-later
          pragma solidity >=0.7.5;
          pragma abicoder v2;
          import "./IUniswapV3SwapCallback.sol";
          /// @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);
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
          import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
          import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import {TokenDistributor} from "./TokenDistributor.sol";
          /**
           * @title FeeSharingSystem
           * @notice It handles the distribution of fees using
           * WETH along with the auto-compounding of LOOKS.
           */
          contract FeeSharingSystem is ReentrancyGuard, Ownable {
              using SafeERC20 for IERC20;
              struct UserInfo {
                  uint256 shares; // shares of token staked
                  uint256 userRewardPerTokenPaid; // user reward per token paid
                  uint256 rewards; // pending rewards
              }
              // Precision factor for calculating rewards and exchange rate
              uint256 public constant PRECISION_FACTOR = 10**18;
              IERC20 public immutable looksRareToken;
              IERC20 public immutable rewardToken;
              TokenDistributor public immutable tokenDistributor;
              // Reward rate (block)
              uint256 public currentRewardPerBlock;
              // Last reward adjustment block number
              uint256 public lastRewardAdjustment;
              // Last update block for rewards
              uint256 public lastUpdateBlock;
              // Current end block for the current reward period
              uint256 public periodEndBlock;
              // Reward per token stored
              uint256 public rewardPerTokenStored;
              // Total existing shares
              uint256 public totalShares;
              mapping(address => UserInfo) public userInfo;
              event Deposit(address indexed user, uint256 amount, uint256 harvestedAmount);
              event Harvest(address indexed user, uint256 harvestedAmount);
              event NewRewardPeriod(uint256 numberBlocks, uint256 rewardPerBlock, uint256 reward);
              event Withdraw(address indexed user, uint256 amount, uint256 harvestedAmount);
              /**
               * @notice Constructor
               * @param _looksRareToken address of the token staked (LOOKS)
               * @param _rewardToken address of the reward token
               * @param _tokenDistributor address of the token distributor contract
               */
              constructor(
                  address _looksRareToken,
                  address _rewardToken,
                  address _tokenDistributor
              ) {
                  rewardToken = IERC20(_rewardToken);
                  looksRareToken = IERC20(_looksRareToken);
                  tokenDistributor = TokenDistributor(_tokenDistributor);
              }
              /**
               * @notice Deposit staked tokens (and collect reward tokens if requested)
               * @param amount amount to deposit (in LOOKS)
               * @param claimRewardToken whether to claim reward tokens
               * @dev There is a limit of 1 LOOKS per deposit to prevent potential manipulation of current shares
               */
              function deposit(uint256 amount, bool claimRewardToken) external nonReentrant {
                  require(amount >= PRECISION_FACTOR, "Deposit: Amount must be >= 1 LOOKS");
                  // Auto compounds for everyone
                  tokenDistributor.harvestAndCompound();
                  // Update reward for user
                  _updateReward(msg.sender);
                  // Retrieve total amount staked by this contract
                  (uint256 totalAmountStaked, ) = tokenDistributor.userInfo(address(this));
                  // Transfer LOOKS tokens to this address
                  looksRareToken.safeTransferFrom(msg.sender, address(this), amount);
                  uint256 currentShares;
                  // Calculate the number of shares to issue for the user
                  if (totalShares != 0) {
                      currentShares = (amount * totalShares) / totalAmountStaked;
                      // This is a sanity check to prevent deposit for 0 shares
                      require(currentShares != 0, "Deposit: Fail");
                  } else {
                      currentShares = amount;
                  }
                  // Adjust internal shares
                  userInfo[msg.sender].shares += currentShares;
                  totalShares += currentShares;
                  uint256 pendingRewards;
                  if (claimRewardToken) {
                      // Fetch pending rewards
                      pendingRewards = userInfo[msg.sender].rewards;
                      if (pendingRewards > 0) {
                          userInfo[msg.sender].rewards = 0;
                          rewardToken.safeTransfer(msg.sender, pendingRewards);
                      }
                  }
                  // Verify LOOKS token allowance and adjust if necessary
                  _checkAndAdjustLOOKSTokenAllowanceIfRequired(amount, address(tokenDistributor));
                  // Deposit user amount in the token distributor contract
                  tokenDistributor.deposit(amount);
                  emit Deposit(msg.sender, amount, pendingRewards);
              }
              /**
               * @notice Harvest reward tokens that are pending
               */
              function harvest() external nonReentrant {
                  // Auto compounds for everyone
                  tokenDistributor.harvestAndCompound();
                  // Update reward for user
                  _updateReward(msg.sender);
                  // Retrieve pending rewards
                  uint256 pendingRewards = userInfo[msg.sender].rewards;
                  // If pending rewards are null, revert
                  require(pendingRewards > 0, "Harvest: Pending rewards must be > 0");
                  // Adjust user rewards and transfer
                  userInfo[msg.sender].rewards = 0;
                  // Transfer reward token to sender
                  rewardToken.safeTransfer(msg.sender, pendingRewards);
                  emit Harvest(msg.sender, pendingRewards);
              }
              /**
               * @notice Withdraw staked tokens (and collect reward tokens if requested)
               * @param shares shares to withdraw
               * @param claimRewardToken whether to claim reward tokens
               */
              function withdraw(uint256 shares, bool claimRewardToken) external nonReentrant {
                  require(
                      (shares > 0) && (shares <= userInfo[msg.sender].shares),
                      "Withdraw: Shares equal to 0 or larger than user shares"
                  );
                  _withdraw(shares, claimRewardToken);
              }
              /**
               * @notice Withdraw all staked tokens (and collect reward tokens if requested)
               * @param claimRewardToken whether to claim reward tokens
               */
              function withdrawAll(bool claimRewardToken) external nonReentrant {
                  _withdraw(userInfo[msg.sender].shares, claimRewardToken);
              }
              /**
               * @notice Update the reward per block (in rewardToken)
               * @dev Only callable by owner. Owner is meant to be another smart contract.
               */
              function updateRewards(uint256 reward, uint256 rewardDurationInBlocks) external onlyOwner {
                  // Adjust the current reward per block
                  if (block.number >= periodEndBlock) {
                      currentRewardPerBlock = reward / rewardDurationInBlocks;
                  } else {
                      currentRewardPerBlock =
                          (reward + ((periodEndBlock - block.number) * currentRewardPerBlock)) /
                          rewardDurationInBlocks;
                  }
                  lastUpdateBlock = block.number;
                  periodEndBlock = block.number + rewardDurationInBlocks;
                  emit NewRewardPeriod(rewardDurationInBlocks, currentRewardPerBlock, reward);
              }
              /**
               * @notice Calculate pending rewards (WETH) for a user
               * @param user address of the user
               */
              function calculatePendingRewards(address user) external view returns (uint256) {
                  return _calculatePendingRewards(user);
              }
              /**
               * @notice Calculate value of LOOKS for a user given a number of shares owned
               * @param user address of the user
               */
              function calculateSharesValueInLOOKS(address user) external view returns (uint256) {
                  // Retrieve amount staked
                  (uint256 totalAmountStaked, ) = tokenDistributor.userInfo(address(this));
                  // Adjust for pending rewards
                  totalAmountStaked += tokenDistributor.calculatePendingRewards(address(this));
                  // Return user pro-rata of total shares
                  return userInfo[user].shares == 0 ? 0 : (totalAmountStaked * userInfo[user].shares) / totalShares;
              }
              /**
               * @notice Calculate price of one share (in LOOKS token)
               * Share price is expressed times 1e18
               */
              function calculateSharePriceInLOOKS() external view returns (uint256) {
                  (uint256 totalAmountStaked, ) = tokenDistributor.userInfo(address(this));
                  // Adjust for pending rewards
                  totalAmountStaked += tokenDistributor.calculatePendingRewards(address(this));
                  return totalShares == 0 ? PRECISION_FACTOR : (totalAmountStaked * PRECISION_FACTOR) / (totalShares);
              }
              /**
               * @notice Return last block where trading rewards were distributed
               */
              function lastRewardBlock() external view returns (uint256) {
                  return _lastRewardBlock();
              }
              /**
               * @notice Calculate pending rewards for a user
               * @param user address of the user
               */
              function _calculatePendingRewards(address user) internal view returns (uint256) {
                  return
                      ((userInfo[user].shares * (_rewardPerToken() - (userInfo[user].userRewardPerTokenPaid))) /
                          PRECISION_FACTOR) + userInfo[user].rewards;
              }
              /**
               * @notice Check current allowance and adjust if necessary
               * @param _amount amount to transfer
               * @param _to token to transfer
               */
              function _checkAndAdjustLOOKSTokenAllowanceIfRequired(uint256 _amount, address _to) internal {
                  if (looksRareToken.allowance(address(this), _to) < _amount) {
                      looksRareToken.approve(_to, type(uint256).max);
                  }
              }
              /**
               * @notice Return last block where rewards must be distributed
               */
              function _lastRewardBlock() internal view returns (uint256) {
                  return block.number < periodEndBlock ? block.number : periodEndBlock;
              }
              /**
               * @notice Return reward per token
               */
              function _rewardPerToken() internal view returns (uint256) {
                  if (totalShares == 0) {
                      return rewardPerTokenStored;
                  }
                  return
                      rewardPerTokenStored +
                      ((_lastRewardBlock() - lastUpdateBlock) * (currentRewardPerBlock * PRECISION_FACTOR)) /
                      totalShares;
              }
              /**
               * @notice Update reward for a user account
               * @param _user address of the user
               */
              function _updateReward(address _user) internal {
                  if (block.number != lastUpdateBlock) {
                      rewardPerTokenStored = _rewardPerToken();
                      lastUpdateBlock = _lastRewardBlock();
                  }
                  userInfo[_user].rewards = _calculatePendingRewards(_user);
                  userInfo[_user].userRewardPerTokenPaid = rewardPerTokenStored;
              }
              /**
               * @notice Withdraw staked tokens (and collect reward tokens if requested)
               * @param shares shares to withdraw
               * @param claimRewardToken whether to claim reward tokens
               */
              function _withdraw(uint256 shares, bool claimRewardToken) internal {
                  // Auto compounds for everyone
                  tokenDistributor.harvestAndCompound();
                  // Update reward for user
                  _updateReward(msg.sender);
                  // Retrieve total amount staked and calculated current amount (in LOOKS)
                  (uint256 totalAmountStaked, ) = tokenDistributor.userInfo(address(this));
                  uint256 currentAmount = (totalAmountStaked * shares) / totalShares;
                  userInfo[msg.sender].shares -= shares;
                  totalShares -= shares;
                  // Withdraw amount equivalent in shares
                  tokenDistributor.withdraw(currentAmount);
                  uint256 pendingRewards;
                  if (claimRewardToken) {
                      // Fetch pending rewards
                      pendingRewards = userInfo[msg.sender].rewards;
                      if (pendingRewards > 0) {
                          userInfo[msg.sender].rewards = 0;
                          rewardToken.safeTransfer(msg.sender, pendingRewards);
                      }
                  }
                  // Transfer LOOKS tokens to sender
                  looksRareToken.safeTransfer(msg.sender, currentAmount);
                  emit Withdraw(msg.sender, currentAmount, pendingRewards);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @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;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 standard as defined in the EIP.
           */
          interface IERC20 {
              /**
               * @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 `recipient`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
                  address recipient,
                  uint256 amount
              ) external returns (bool);
              /**
               * @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);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Address.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize, which returns 0 for contracts in
                  // construction, since the code is only stored at the end of the
                  // constructor execution.
                  uint256 size;
                  assembly {
                      size := extcodesize(account)
                  }
                  return size > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCall(target, data, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  require(isContract(target), "Address: call to non-contract");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  require(isContract(target), "Address: static call to non-contract");
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(isContract(target), "Address: delegate call to non-contract");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      // Look for revert reason and bubble it up if present
                      if (returndata.length > 0) {
                          // The easiest way to bubble the revert reason is using memory via assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: GPL-2.0-or-later
          pragma solidity >=0.5.0;
          /// @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;
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
          import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import {ILooksRareToken} from "../interfaces/ILooksRareToken.sol";
          /**
           * @title TokenDistributor
           * @notice It handles the distribution of LOOKS token.
           * It auto-adjusts block rewards over a set number of periods.
           */
          contract TokenDistributor is ReentrancyGuard {
              using SafeERC20 for IERC20;
              using SafeERC20 for ILooksRareToken;
              struct StakingPeriod {
                  uint256 rewardPerBlockForStaking;
                  uint256 rewardPerBlockForOthers;
                  uint256 periodLengthInBlock;
              }
              struct UserInfo {
                  uint256 amount; // Amount of staked tokens provided by user
                  uint256 rewardDebt; // Reward debt
              }
              // Precision factor for calculating rewards
              uint256 public constant PRECISION_FACTOR = 10**12;
              ILooksRareToken public immutable looksRareToken;
              address public immutable tokenSplitter;
              // Number of reward periods
              uint256 public immutable NUMBER_PERIODS;
              // Block number when rewards start
              uint256 public immutable START_BLOCK;
              // Accumulated tokens per share
              uint256 public accTokenPerShare;
              // Current phase for rewards
              uint256 public currentPhase;
              // Block number when rewards end
              uint256 public endBlock;
              // Block number of the last update
              uint256 public lastRewardBlock;
              // Tokens distributed per block for other purposes (team + treasury + trading rewards)
              uint256 public rewardPerBlockForOthers;
              // Tokens distributed per block for staking
              uint256 public rewardPerBlockForStaking;
              // Total amount staked
              uint256 public totalAmountStaked;
              mapping(uint256 => StakingPeriod) public stakingPeriod;
              mapping(address => UserInfo) public userInfo;
              event Compound(address indexed user, uint256 harvestedAmount);
              event Deposit(address indexed user, uint256 amount, uint256 harvestedAmount);
              event NewRewardsPerBlock(
                  uint256 indexed currentPhase,
                  uint256 startBlock,
                  uint256 rewardPerBlockForStaking,
                  uint256 rewardPerBlockForOthers
              );
              event Withdraw(address indexed user, uint256 amount, uint256 harvestedAmount);
              /**
               * @notice Constructor
               * @param _looksRareToken LOOKS token address
               * @param _tokenSplitter token splitter contract address (for team and trading rewards)
               * @param _startBlock start block for reward program
               * @param _rewardsPerBlockForStaking array of rewards per block for staking
               * @param _rewardsPerBlockForOthers array of rewards per block for other purposes (team + treasury + trading rewards)
               * @param _periodLengthesInBlocks array of period lengthes
               * @param _numberPeriods number of periods with different rewards/lengthes (e.g., if 3 changes --> 4 periods)
               */
              constructor(
                  address _looksRareToken,
                  address _tokenSplitter,
                  uint256 _startBlock,
                  uint256[] memory _rewardsPerBlockForStaking,
                  uint256[] memory _rewardsPerBlockForOthers,
                  uint256[] memory _periodLengthesInBlocks,
                  uint256 _numberPeriods
              ) {
                  require(
                      (_periodLengthesInBlocks.length == _numberPeriods) &&
                          (_rewardsPerBlockForStaking.length == _numberPeriods) &&
                          (_rewardsPerBlockForStaking.length == _numberPeriods),
                      "Distributor: Lengthes must match numberPeriods"
                  );
                  // 1. Operational checks for supply
                  uint256 nonCirculatingSupply = ILooksRareToken(_looksRareToken).SUPPLY_CAP() -
                      ILooksRareToken(_looksRareToken).totalSupply();
                  uint256 amountTokensToBeMinted;
                  for (uint256 i = 0; i < _numberPeriods; i++) {
                      amountTokensToBeMinted +=
                          (_rewardsPerBlockForStaking[i] * _periodLengthesInBlocks[i]) +
                          (_rewardsPerBlockForOthers[i] * _periodLengthesInBlocks[i]);
                      stakingPeriod[i] = StakingPeriod({
                          rewardPerBlockForStaking: _rewardsPerBlockForStaking[i],
                          rewardPerBlockForOthers: _rewardsPerBlockForOthers[i],
                          periodLengthInBlock: _periodLengthesInBlocks[i]
                      });
                  }
                  require(amountTokensToBeMinted == nonCirculatingSupply, "Distributor: Wrong reward parameters");
                  // 2. Store values
                  looksRareToken = ILooksRareToken(_looksRareToken);
                  tokenSplitter = _tokenSplitter;
                  rewardPerBlockForStaking = _rewardsPerBlockForStaking[0];
                  rewardPerBlockForOthers = _rewardsPerBlockForOthers[0];
                  START_BLOCK = _startBlock;
                  endBlock = _startBlock + _periodLengthesInBlocks[0];
                  NUMBER_PERIODS = _numberPeriods;
                  // Set the lastRewardBlock as the startBlock
                  lastRewardBlock = _startBlock;
              }
              /**
               * @notice Deposit staked tokens and compounds pending rewards
               * @param amount amount to deposit (in LOOKS)
               */
              function deposit(uint256 amount) external nonReentrant {
                  require(amount > 0, "Deposit: Amount must be > 0");
                  // Update pool information
                  _updatePool();
                  // Transfer LOOKS tokens to this contract
                  looksRareToken.safeTransferFrom(msg.sender, address(this), amount);
                  uint256 pendingRewards;
                  // If not new deposit, calculate pending rewards (for auto-compounding)
                  if (userInfo[msg.sender].amount > 0) {
                      pendingRewards =
                          ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                          userInfo[msg.sender].rewardDebt;
                  }
                  // Adjust user information
                  userInfo[msg.sender].amount += (amount + pendingRewards);
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  // Increase totalAmountStaked
                  totalAmountStaked += (amount + pendingRewards);
                  emit Deposit(msg.sender, amount, pendingRewards);
              }
              /**
               * @notice Compound based on pending rewards
               */
              function harvestAndCompound() external nonReentrant {
                  // Update pool information
                  _updatePool();
                  // Calculate pending rewards
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  // Return if no pending rewards
                  if (pendingRewards == 0) {
                      // It doesn't throw revertion (to help with the fee-sharing auto-compounding contract)
                      return;
                  }
                  // Adjust user amount for pending rewards
                  userInfo[msg.sender].amount += pendingRewards;
                  // Adjust totalAmountStaked
                  totalAmountStaked += pendingRewards;
                  // Recalculate reward debt based on new user amount
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  emit Compound(msg.sender, pendingRewards);
              }
              /**
               * @notice Update pool rewards
               */
              function updatePool() external nonReentrant {
                  _updatePool();
              }
              /**
               * @notice Withdraw staked tokens and compound pending rewards
               * @param amount amount to withdraw
               */
              function withdraw(uint256 amount) external nonReentrant {
                  require(
                      (userInfo[msg.sender].amount >= amount) && (amount > 0),
                      "Withdraw: Amount must be > 0 or lower than user balance"
                  );
                  // Update pool
                  _updatePool();
                  // Calculate pending rewards
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  // Adjust user information
                  userInfo[msg.sender].amount = userInfo[msg.sender].amount + pendingRewards - amount;
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  // Adjust total amount staked
                  totalAmountStaked = totalAmountStaked + pendingRewards - amount;
                  // Transfer LOOKS tokens to the sender
                  looksRareToken.safeTransfer(msg.sender, amount);
                  emit Withdraw(msg.sender, amount, pendingRewards);
              }
              /**
               * @notice Withdraw all staked tokens and collect tokens
               */
              function withdrawAll() external nonReentrant {
                  require(userInfo[msg.sender].amount > 0, "Withdraw: Amount must be > 0");
                  // Update pool
                  _updatePool();
                  // Calculate pending rewards and amount to transfer (to the sender)
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  uint256 amountToTransfer = userInfo[msg.sender].amount + pendingRewards;
                  // Adjust total amount staked
                  totalAmountStaked = totalAmountStaked - userInfo[msg.sender].amount;
                  // Adjust user information
                  userInfo[msg.sender].amount = 0;
                  userInfo[msg.sender].rewardDebt = 0;
                  // Transfer LOOKS tokens to the sender
                  looksRareToken.safeTransfer(msg.sender, amountToTransfer);
                  emit Withdraw(msg.sender, amountToTransfer, pendingRewards);
              }
              /**
               * @notice Calculate pending rewards for a user
               * @param user address of the user
               * @return Pending rewards
               */
              function calculatePendingRewards(address user) external view returns (uint256) {
                  if ((block.number > lastRewardBlock) && (totalAmountStaked != 0)) {
                      uint256 multiplier = _getMultiplier(lastRewardBlock, block.number);
                      uint256 tokenRewardForStaking = multiplier * rewardPerBlockForStaking;
                      uint256 adjustedEndBlock = endBlock;
                      uint256 adjustedCurrentPhase = currentPhase;
                      // Check whether to adjust multipliers and reward per block
                      while ((block.number > adjustedEndBlock) && (adjustedCurrentPhase < (NUMBER_PERIODS - 1))) {
                          // Update current phase
                          adjustedCurrentPhase++;
                          // Update rewards per block
                          uint256 adjustedRewardPerBlockForStaking = stakingPeriod[adjustedCurrentPhase].rewardPerBlockForStaking;
                          // Calculate adjusted block number
                          uint256 previousEndBlock = adjustedEndBlock;
                          // Update end block
                          adjustedEndBlock = previousEndBlock + stakingPeriod[adjustedCurrentPhase].periodLengthInBlock;
                          // Calculate new multiplier
                          uint256 newMultiplier = (block.number <= adjustedEndBlock)
                              ? (block.number - previousEndBlock)
                              : stakingPeriod[adjustedCurrentPhase].periodLengthInBlock;
                          // Adjust token rewards for staking
                          tokenRewardForStaking += (newMultiplier * adjustedRewardPerBlockForStaking);
                      }
                      uint256 adjustedTokenPerShare = accTokenPerShare +
                          (tokenRewardForStaking * PRECISION_FACTOR) /
                          totalAmountStaked;
                      return (userInfo[user].amount * adjustedTokenPerShare) / PRECISION_FACTOR - userInfo[user].rewardDebt;
                  } else {
                      return (userInfo[user].amount * accTokenPerShare) / PRECISION_FACTOR - userInfo[user].rewardDebt;
                  }
              }
              /**
               * @notice Update reward variables of the pool
               */
              function _updatePool() internal {
                  if (block.number <= lastRewardBlock) {
                      return;
                  }
                  if (totalAmountStaked == 0) {
                      lastRewardBlock = block.number;
                      return;
                  }
                  // Calculate multiplier
                  uint256 multiplier = _getMultiplier(lastRewardBlock, block.number);
                  // Calculate rewards for staking and others
                  uint256 tokenRewardForStaking = multiplier * rewardPerBlockForStaking;
                  uint256 tokenRewardForOthers = multiplier * rewardPerBlockForOthers;
                  // Check whether to adjust multipliers and reward per block
                  while ((block.number > endBlock) && (currentPhase < (NUMBER_PERIODS - 1))) {
                      // Update rewards per block
                      _updateRewardsPerBlock(endBlock);
                      uint256 previousEndBlock = endBlock;
                      // Adjust the end block
                      endBlock += stakingPeriod[currentPhase].periodLengthInBlock;
                      // Adjust multiplier to cover the missing periods with other lower inflation schedule
                      uint256 newMultiplier = _getMultiplier(previousEndBlock, block.number);
                      // Adjust token rewards
                      tokenRewardForStaking += (newMultiplier * rewardPerBlockForStaking);
                      tokenRewardForOthers += (newMultiplier * rewardPerBlockForOthers);
                  }
                  // Mint tokens only if token rewards for staking are not null
                  if (tokenRewardForStaking > 0) {
                      // It allows protection against potential issues to prevent funds from being locked
                      bool mintStatus = looksRareToken.mint(address(this), tokenRewardForStaking);
                      if (mintStatus) {
                          accTokenPerShare = accTokenPerShare + ((tokenRewardForStaking * PRECISION_FACTOR) / totalAmountStaked);
                      }
                      looksRareToken.mint(tokenSplitter, tokenRewardForOthers);
                  }
                  // Update last reward block only if it wasn't updated after or at the end block
                  if (lastRewardBlock <= endBlock) {
                      lastRewardBlock = block.number;
                  }
              }
              /**
               * @notice Update rewards per block
               * @dev Rewards are halved by 2 (for staking + others)
               */
              function _updateRewardsPerBlock(uint256 _newStartBlock) internal {
                  // Update current phase
                  currentPhase++;
                  // Update rewards per block
                  rewardPerBlockForStaking = stakingPeriod[currentPhase].rewardPerBlockForStaking;
                  rewardPerBlockForOthers = stakingPeriod[currentPhase].rewardPerBlockForOthers;
                  emit NewRewardsPerBlock(currentPhase, _newStartBlock, rewardPerBlockForStaking, rewardPerBlockForOthers);
              }
              /**
               * @notice Return reward multiplier over the given "from" to "to" block.
               * @param from block to start calculating reward
               * @param to block to finish calculating reward
               * @return the multiplier for the period
               */
              function _getMultiplier(uint256 from, uint256 to) internal view returns (uint256) {
                  if (to <= endBlock) {
                      return to - from;
                  } else if (from >= endBlock) {
                      return 0;
                  } else {
                      return endBlock - from;
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          interface ILooksRareToken is IERC20 {
              function SUPPLY_CAP() external view returns (uint256);
              function mint(address account, uint256 amount) external returns (bool);
          }
          

          File 2 of 4: LooksRareToken
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
          import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
          import {ILooksRareToken} from "../interfaces/ILooksRareToken.sol";
          /**
           * @title LooksRareToken (LOOKS)
           * @notice
          LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRAR'''''''''''''''''''''''''''''''''''OOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKS:.                                        .;OOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOO,.                                            .,KSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRAREL'                ..',;:LOOKS::;,'..                'RARELOOKSRARELOOKSR
          LOOKSRARELOOKSRAR.              .,:LOOKSRARELOOKSRARELO:,.              .RELOOKSRARELOOKSR
          LOOKSRARELOOKS:.             .;RARELOOKSRARELOOKSRARELOOKSl;.             .:OOKSRARELOOKSR
          LOOKSRARELOO;.            .'OKSRARELOOKSRARELOOKSRARELOOKSRARE'.            .;KSRARELOOKSR
          LOOKSRAREL,.            .,LOOKSRARELOOK:;;:"""":;;;lELOOKSRARELO,.            .,RARELOOKSR
          LOOKSRAR.             .;okLOOKSRAREx:.              .;OOKSRARELOOK;.             .RELOOKSR
          LOOKS:.             .:dOOOLOOKSRARE'      .''''..     .OKSRARELOOKSR:.             .LOOKSR
          LOx;.             .cKSRARELOOKSRAR'     'LOOKSRAR'     .KSRARELOOKSRARc..            .OKSR
          L;.             .cxOKSRARELOOKSRAR.    .LOOKS.RARE'     ;kRARELOOKSRARExc.             .;R
          LO'             .;oOKSRARELOOKSRAl.    .LOOKS.RARE.     :kRARELOOKSRAREo;.             'SR
          LOOK;.            .,KSRARELOOKSRAx,     .;LOOKSR;.     .oSRARELOOKSRAo,.            .;OKSR
          LOOKSk:.            .'RARELOOKSRARd;.      ....       'oOOOOOOOOOOxc'.            .:LOOKSR
          LOOKSRARc.             .:dLOOKSRAREko;.            .,lxOOOOOOOOOd:.             .ARELOOKSR
          LOOKSRARELo'             .;oOKSRARELOOxoc;,....,;:ldkOOOOOOOOkd;.             'SRARELOOKSR
          LOOKSRARELOOd,.            .,lSRARELOOKSRARELOOKSRARELOOKSRkl,.            .,OKSRARELOOKSR
          LOOKSRARELOOKSx;.            ..;oxELOOKSRARELOOKSRARELOkxl:..            .:LOOKSRARELOOKSR
          LOOKSRARELOOKSRARc.              .':cOKSRARELOOKSRALOc;'.              .ARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELl'                 ...'',,,,''...                 'SRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOo,.                                          .,OKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSx;.                                      .;xOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLO:.                                  .:SRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLOOKl.                              .lOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLOOKSRo'.                        .'oLOOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLOOKSRARd;.                    .;xRELOOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLOOKSRARELO:.                .:kRARELOOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKl.            .cOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRo'        'oLOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARE,.  .,dRELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR
          LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR
           */
          contract LooksRareToken is ERC20, Ownable, ILooksRareToken {
              uint256 private immutable _SUPPLY_CAP;
              /**
               * @notice Constructor
               * @param _premintReceiver address that receives the premint
               * @param _premintAmount amount to premint
               * @param _cap supply cap (to prevent abusive mint)
               */
              constructor(
                  address _premintReceiver,
                  uint256 _premintAmount,
                  uint256 _cap
              ) ERC20("LooksRare Token", "LOOKS") {
                  require(_cap > _premintAmount, "LOOKS: Premint amount is greater than cap");
                  // Transfer the sum of the premint to address
                  _mint(_premintReceiver, _premintAmount);
                  _SUPPLY_CAP = _cap;
              }
              /**
               * @notice Mint LOOKS tokens
               * @param account address to receive tokens
               * @param amount amount to mint
               * @return status true if mint is successful, false if not
               */
              function mint(address account, uint256 amount) external override onlyOwner returns (bool status) {
                  if (totalSupply() + amount <= _SUPPLY_CAP) {
                      _mint(account, amount);
                      return true;
                  }
                  return false;
              }
              /**
               * @notice View supply cap
               */
              function SUPPLY_CAP() external view override returns (uint256) {
                  return _SUPPLY_CAP;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          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 Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
                  _;
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing 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);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/ERC20.sol)
          pragma solidity ^0.8.0;
          import "./IERC20.sol";
          import "./extensions/IERC20Metadata.sol";
          import "../../utils/Context.sol";
          /**
           * @dev Implementation of the {IERC20} interface.
           *
           * This implementation is agnostic to the way tokens are created. This means
           * that a supply mechanism has to be added in a derived contract using {_mint}.
           * For a generic mechanism see {ERC20PresetMinterPauser}.
           *
           * TIP: For a detailed writeup see our guide
           * https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
           * to implement supply mechanisms].
           *
           * We have followed general OpenZeppelin Contracts guidelines: functions revert
           * instead returning `false` on failure. This behavior is nonetheless
           * conventional and does not conflict with the expectations of ERC20
           * applications.
           *
           * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
           * This allows applications to reconstruct the allowance for all accounts just
           * by listening to said events. Other implementations of the EIP may not emit
           * these events, as it isn't required by the specification.
           *
           * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
           * functions have been added to mitigate the well-known issues around setting
           * allowances. See {IERC20-approve}.
           */
          contract ERC20 is Context, IERC20, IERC20Metadata {
              mapping(address => uint256) private _balances;
              mapping(address => mapping(address => uint256)) private _allowances;
              uint256 private _totalSupply;
              string private _name;
              string private _symbol;
              /**
               * @dev Sets the values for {name} and {symbol}.
               *
               * The default value of {decimals} is 18. To select a different value for
               * {decimals} you should overload it.
               *
               * All two of these values are immutable: they can only be set once during
               * construction.
               */
              constructor(string memory name_, string memory symbol_) {
                  _name = name_;
                  _symbol = symbol_;
              }
              /**
               * @dev Returns the name of the token.
               */
              function name() public view virtual override returns (string memory) {
                  return _name;
              }
              /**
               * @dev Returns the symbol of the token, usually a shorter version of the
               * name.
               */
              function symbol() public view virtual override returns (string memory) {
                  return _symbol;
              }
              /**
               * @dev Returns the number of decimals used to get its user representation.
               * For example, if `decimals` equals `2`, a balance of `505` tokens should
               * be displayed to a user as `5.05` (`505 / 10 ** 2`).
               *
               * Tokens usually opt for a value of 18, imitating the relationship between
               * Ether and Wei. This is the value {ERC20} uses, unless this function is
               * overridden;
               *
               * NOTE: This information is only used for _display_ purposes: it in
               * no way affects any of the arithmetic of the contract, including
               * {IERC20-balanceOf} and {IERC20-transfer}.
               */
              function decimals() public view virtual override returns (uint8) {
                  return 18;
              }
              /**
               * @dev See {IERC20-totalSupply}.
               */
              function totalSupply() public view virtual override returns (uint256) {
                  return _totalSupply;
              }
              /**
               * @dev See {IERC20-balanceOf}.
               */
              function balanceOf(address account) public view virtual override returns (uint256) {
                  return _balances[account];
              }
              /**
               * @dev See {IERC20-transfer}.
               *
               * Requirements:
               *
               * - `recipient` cannot be the zero address.
               * - the caller must have a balance of at least `amount`.
               */
              function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
                  _transfer(_msgSender(), recipient, amount);
                  return true;
              }
              /**
               * @dev See {IERC20-allowance}.
               */
              function allowance(address owner, address spender) public view virtual override returns (uint256) {
                  return _allowances[owner][spender];
              }
              /**
               * @dev See {IERC20-approve}.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               */
              function approve(address spender, uint256 amount) public virtual override returns (bool) {
                  _approve(_msgSender(), spender, amount);
                  return true;
              }
              /**
               * @dev See {IERC20-transferFrom}.
               *
               * Emits an {Approval} event indicating the updated allowance. This is not
               * required by the EIP. See the note at the beginning of {ERC20}.
               *
               * Requirements:
               *
               * - `sender` and `recipient` cannot be the zero address.
               * - `sender` must have a balance of at least `amount`.
               * - the caller must have allowance for ``sender``'s tokens of at least
               * `amount`.
               */
              function transferFrom(
                  address sender,
                  address recipient,
                  uint256 amount
              ) public virtual override returns (bool) {
                  _transfer(sender, recipient, amount);
                  uint256 currentAllowance = _allowances[sender][_msgSender()];
                  require(currentAllowance >= amount, "ERC20: transfer amount exceeds allowance");
                  unchecked {
                      _approve(sender, _msgSender(), currentAllowance - amount);
                  }
                  return true;
              }
              /**
               * @dev Atomically increases the allowance granted to `spender` by the caller.
               *
               * This is an alternative to {approve} that can be used as a mitigation for
               * problems described in {IERC20-approve}.
               *
               * Emits an {Approval} event indicating the updated allowance.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               */
              function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
                  _approve(_msgSender(), spender, _allowances[_msgSender()][spender] + addedValue);
                  return true;
              }
              /**
               * @dev Atomically decreases the allowance granted to `spender` by the caller.
               *
               * This is an alternative to {approve} that can be used as a mitigation for
               * problems described in {IERC20-approve}.
               *
               * Emits an {Approval} event indicating the updated allowance.
               *
               * Requirements:
               *
               * - `spender` cannot be the zero address.
               * - `spender` must have allowance for the caller of at least
               * `subtractedValue`.
               */
              function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
                  uint256 currentAllowance = _allowances[_msgSender()][spender];
                  require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
                  unchecked {
                      _approve(_msgSender(), spender, currentAllowance - subtractedValue);
                  }
                  return true;
              }
              /**
               * @dev Moves `amount` of tokens from `sender` to `recipient`.
               *
               * This internal function is equivalent to {transfer}, and can be used to
               * e.g. implement automatic token fees, slashing mechanisms, etc.
               *
               * Emits a {Transfer} event.
               *
               * Requirements:
               *
               * - `sender` cannot be the zero address.
               * - `recipient` cannot be the zero address.
               * - `sender` must have a balance of at least `amount`.
               */
              function _transfer(
                  address sender,
                  address recipient,
                  uint256 amount
              ) internal virtual {
                  require(sender != address(0), "ERC20: transfer from the zero address");
                  require(recipient != address(0), "ERC20: transfer to the zero address");
                  _beforeTokenTransfer(sender, recipient, amount);
                  uint256 senderBalance = _balances[sender];
                  require(senderBalance >= amount, "ERC20: transfer amount exceeds balance");
                  unchecked {
                      _balances[sender] = senderBalance - amount;
                  }
                  _balances[recipient] += amount;
                  emit Transfer(sender, recipient, amount);
                  _afterTokenTransfer(sender, recipient, amount);
              }
              /** @dev Creates `amount` tokens and assigns them to `account`, increasing
               * the total supply.
               *
               * Emits a {Transfer} event with `from` set to the zero address.
               *
               * Requirements:
               *
               * - `account` cannot be the zero address.
               */
              function _mint(address account, uint256 amount) internal virtual {
                  require(account != address(0), "ERC20: mint to the zero address");
                  _beforeTokenTransfer(address(0), account, amount);
                  _totalSupply += amount;
                  _balances[account] += amount;
                  emit Transfer(address(0), account, amount);
                  _afterTokenTransfer(address(0), account, amount);
              }
              /**
               * @dev Destroys `amount` tokens from `account`, reducing the
               * total supply.
               *
               * Emits a {Transfer} event with `to` set to the zero address.
               *
               * Requirements:
               *
               * - `account` cannot be the zero address.
               * - `account` must have at least `amount` tokens.
               */
              function _burn(address account, uint256 amount) internal virtual {
                  require(account != address(0), "ERC20: burn from the zero address");
                  _beforeTokenTransfer(account, address(0), amount);
                  uint256 accountBalance = _balances[account];
                  require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
                  unchecked {
                      _balances[account] = accountBalance - amount;
                  }
                  _totalSupply -= amount;
                  emit Transfer(account, address(0), amount);
                  _afterTokenTransfer(account, address(0), amount);
              }
              /**
               * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
               *
               * This internal function is equivalent to `approve`, and can be used to
               * e.g. set automatic allowances for certain subsystems, etc.
               *
               * Emits an {Approval} event.
               *
               * Requirements:
               *
               * - `owner` cannot be the zero address.
               * - `spender` cannot be the zero address.
               */
              function _approve(
                  address owner,
                  address spender,
                  uint256 amount
              ) internal virtual {
                  require(owner != address(0), "ERC20: approve from the zero address");
                  require(spender != address(0), "ERC20: approve to the zero address");
                  _allowances[owner][spender] = amount;
                  emit Approval(owner, spender, amount);
              }
              /**
               * @dev Hook that is called before any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * will be transferred to `to`.
               * - when `from` is zero, `amount` tokens will be minted for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _beforeTokenTransfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {}
              /**
               * @dev Hook that is called after any transfer of tokens. This includes
               * minting and burning.
               *
               * Calling conditions:
               *
               * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
               * has been transferred to `to`.
               * - when `from` is zero, `amount` tokens have been minted for `to`.
               * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
               * - `from` and `to` are never both zero.
               *
               * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
               */
              function _afterTokenTransfer(
                  address from,
                  address to,
                  uint256 amount
              ) internal virtual {}
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          interface ILooksRareToken is IERC20 {
              function SUPPLY_CAP() external view returns (uint256);
              function mint(address account, uint256 amount) external returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @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;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 standard as defined in the EIP.
           */
          interface IERC20 {
              /**
               * @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 `recipient`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
                  address recipient,
                  uint256 amount
              ) external returns (bool);
              /**
               * @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);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          /**
           * @dev Interface for the optional metadata functions from the ERC20 standard.
           *
           * _Available since v4.1._
           */
          interface IERC20Metadata is IERC20 {
              /**
               * @dev Returns the name of the token.
               */
              function name() external view returns (string memory);
              /**
               * @dev Returns the symbol of the token.
               */
              function symbol() external view returns (string memory);
              /**
               * @dev Returns the decimals places of the token.
               */
              function decimals() external view returns (uint8);
          }
          

          File 3 of 4: FeeSharingSystem
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
          import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
          import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import {TokenDistributor} from "./TokenDistributor.sol";
          /**
           * @title FeeSharingSystem
           * @notice It handles the distribution of fees using
           * WETH along with the auto-compounding of LOOKS.
           */
          contract FeeSharingSystem is ReentrancyGuard, Ownable {
              using SafeERC20 for IERC20;
              struct UserInfo {
                  uint256 shares; // shares of token staked
                  uint256 userRewardPerTokenPaid; // user reward per token paid
                  uint256 rewards; // pending rewards
              }
              // Precision factor for calculating rewards and exchange rate
              uint256 public constant PRECISION_FACTOR = 10**18;
              IERC20 public immutable looksRareToken;
              IERC20 public immutable rewardToken;
              TokenDistributor public immutable tokenDistributor;
              // Reward rate (block)
              uint256 public currentRewardPerBlock;
              // Last reward adjustment block number
              uint256 public lastRewardAdjustment;
              // Last update block for rewards
              uint256 public lastUpdateBlock;
              // Current end block for the current reward period
              uint256 public periodEndBlock;
              // Reward per token stored
              uint256 public rewardPerTokenStored;
              // Total existing shares
              uint256 public totalShares;
              mapping(address => UserInfo) public userInfo;
              event Deposit(address indexed user, uint256 amount, uint256 harvestedAmount);
              event Harvest(address indexed user, uint256 harvestedAmount);
              event NewRewardPeriod(uint256 numberBlocks, uint256 rewardPerBlock, uint256 reward);
              event Withdraw(address indexed user, uint256 amount, uint256 harvestedAmount);
              /**
               * @notice Constructor
               * @param _looksRareToken address of the token staked (LOOKS)
               * @param _rewardToken address of the reward token
               * @param _tokenDistributor address of the token distributor contract
               */
              constructor(
                  address _looksRareToken,
                  address _rewardToken,
                  address _tokenDistributor
              ) {
                  rewardToken = IERC20(_rewardToken);
                  looksRareToken = IERC20(_looksRareToken);
                  tokenDistributor = TokenDistributor(_tokenDistributor);
              }
              /**
               * @notice Deposit staked tokens (and collect reward tokens if requested)
               * @param amount amount to deposit (in LOOKS)
               * @param claimRewardToken whether to claim reward tokens
               * @dev There is a limit of 1 LOOKS per deposit to prevent potential manipulation of current shares
               */
              function deposit(uint256 amount, bool claimRewardToken) external nonReentrant {
                  require(amount >= PRECISION_FACTOR, "Deposit: Amount must be >= 1 LOOKS");
                  // Auto compounds for everyone
                  tokenDistributor.harvestAndCompound();
                  // Update reward for user
                  _updateReward(msg.sender);
                  // Retrieve total amount staked by this contract
                  (uint256 totalAmountStaked, ) = tokenDistributor.userInfo(address(this));
                  // Transfer LOOKS tokens to this address
                  looksRareToken.safeTransferFrom(msg.sender, address(this), amount);
                  uint256 currentShares;
                  // Calculate the number of shares to issue for the user
                  if (totalShares != 0) {
                      currentShares = (amount * totalShares) / totalAmountStaked;
                      // This is a sanity check to prevent deposit for 0 shares
                      require(currentShares != 0, "Deposit: Fail");
                  } else {
                      currentShares = amount;
                  }
                  // Adjust internal shares
                  userInfo[msg.sender].shares += currentShares;
                  totalShares += currentShares;
                  uint256 pendingRewards;
                  if (claimRewardToken) {
                      // Fetch pending rewards
                      pendingRewards = userInfo[msg.sender].rewards;
                      if (pendingRewards > 0) {
                          userInfo[msg.sender].rewards = 0;
                          rewardToken.safeTransfer(msg.sender, pendingRewards);
                      }
                  }
                  // Verify LOOKS token allowance and adjust if necessary
                  _checkAndAdjustLOOKSTokenAllowanceIfRequired(amount, address(tokenDistributor));
                  // Deposit user amount in the token distributor contract
                  tokenDistributor.deposit(amount);
                  emit Deposit(msg.sender, amount, pendingRewards);
              }
              /**
               * @notice Harvest reward tokens that are pending
               */
              function harvest() external nonReentrant {
                  // Auto compounds for everyone
                  tokenDistributor.harvestAndCompound();
                  // Update reward for user
                  _updateReward(msg.sender);
                  // Retrieve pending rewards
                  uint256 pendingRewards = userInfo[msg.sender].rewards;
                  // If pending rewards are null, revert
                  require(pendingRewards > 0, "Harvest: Pending rewards must be > 0");
                  // Adjust user rewards and transfer
                  userInfo[msg.sender].rewards = 0;
                  // Transfer reward token to sender
                  rewardToken.safeTransfer(msg.sender, pendingRewards);
                  emit Harvest(msg.sender, pendingRewards);
              }
              /**
               * @notice Withdraw staked tokens (and collect reward tokens if requested)
               * @param shares shares to withdraw
               * @param claimRewardToken whether to claim reward tokens
               */
              function withdraw(uint256 shares, bool claimRewardToken) external nonReentrant {
                  require(
                      (shares > 0) && (shares <= userInfo[msg.sender].shares),
                      "Withdraw: Shares equal to 0 or larger than user shares"
                  );
                  _withdraw(shares, claimRewardToken);
              }
              /**
               * @notice Withdraw all staked tokens (and collect reward tokens if requested)
               * @param claimRewardToken whether to claim reward tokens
               */
              function withdrawAll(bool claimRewardToken) external nonReentrant {
                  _withdraw(userInfo[msg.sender].shares, claimRewardToken);
              }
              /**
               * @notice Update the reward per block (in rewardToken)
               * @dev Only callable by owner. Owner is meant to be another smart contract.
               */
              function updateRewards(uint256 reward, uint256 rewardDurationInBlocks) external onlyOwner {
                  // Adjust the current reward per block
                  if (block.number >= periodEndBlock) {
                      currentRewardPerBlock = reward / rewardDurationInBlocks;
                  } else {
                      currentRewardPerBlock =
                          (reward + ((periodEndBlock - block.number) * currentRewardPerBlock)) /
                          rewardDurationInBlocks;
                  }
                  lastUpdateBlock = block.number;
                  periodEndBlock = block.number + rewardDurationInBlocks;
                  emit NewRewardPeriod(rewardDurationInBlocks, currentRewardPerBlock, reward);
              }
              /**
               * @notice Calculate pending rewards (WETH) for a user
               * @param user address of the user
               */
              function calculatePendingRewards(address user) external view returns (uint256) {
                  return _calculatePendingRewards(user);
              }
              /**
               * @notice Calculate value of LOOKS for a user given a number of shares owned
               * @param user address of the user
               */
              function calculateSharesValueInLOOKS(address user) external view returns (uint256) {
                  // Retrieve amount staked
                  (uint256 totalAmountStaked, ) = tokenDistributor.userInfo(address(this));
                  // Adjust for pending rewards
                  totalAmountStaked += tokenDistributor.calculatePendingRewards(address(this));
                  // Return user pro-rata of total shares
                  return userInfo[user].shares == 0 ? 0 : (totalAmountStaked * userInfo[user].shares) / totalShares;
              }
              /**
               * @notice Calculate price of one share (in LOOKS token)
               * Share price is expressed times 1e18
               */
              function calculateSharePriceInLOOKS() external view returns (uint256) {
                  (uint256 totalAmountStaked, ) = tokenDistributor.userInfo(address(this));
                  // Adjust for pending rewards
                  totalAmountStaked += tokenDistributor.calculatePendingRewards(address(this));
                  return totalShares == 0 ? PRECISION_FACTOR : (totalAmountStaked * PRECISION_FACTOR) / (totalShares);
              }
              /**
               * @notice Return last block where trading rewards were distributed
               */
              function lastRewardBlock() external view returns (uint256) {
                  return _lastRewardBlock();
              }
              /**
               * @notice Calculate pending rewards for a user
               * @param user address of the user
               */
              function _calculatePendingRewards(address user) internal view returns (uint256) {
                  return
                      ((userInfo[user].shares * (_rewardPerToken() - (userInfo[user].userRewardPerTokenPaid))) /
                          PRECISION_FACTOR) + userInfo[user].rewards;
              }
              /**
               * @notice Check current allowance and adjust if necessary
               * @param _amount amount to transfer
               * @param _to token to transfer
               */
              function _checkAndAdjustLOOKSTokenAllowanceIfRequired(uint256 _amount, address _to) internal {
                  if (looksRareToken.allowance(address(this), _to) < _amount) {
                      looksRareToken.approve(_to, type(uint256).max);
                  }
              }
              /**
               * @notice Return last block where rewards must be distributed
               */
              function _lastRewardBlock() internal view returns (uint256) {
                  return block.number < periodEndBlock ? block.number : periodEndBlock;
              }
              /**
               * @notice Return reward per token
               */
              function _rewardPerToken() internal view returns (uint256) {
                  if (totalShares == 0) {
                      return rewardPerTokenStored;
                  }
                  return
                      rewardPerTokenStored +
                      ((_lastRewardBlock() - lastUpdateBlock) * (currentRewardPerBlock * PRECISION_FACTOR)) /
                      totalShares;
              }
              /**
               * @notice Update reward for a user account
               * @param _user address of the user
               */
              function _updateReward(address _user) internal {
                  if (block.number != lastUpdateBlock) {
                      rewardPerTokenStored = _rewardPerToken();
                      lastUpdateBlock = _lastRewardBlock();
                  }
                  userInfo[_user].rewards = _calculatePendingRewards(_user);
                  userInfo[_user].userRewardPerTokenPaid = rewardPerTokenStored;
              }
              /**
               * @notice Withdraw staked tokens (and collect reward tokens if requested)
               * @param shares shares to withdraw
               * @param claimRewardToken whether to claim reward tokens
               */
              function _withdraw(uint256 shares, bool claimRewardToken) internal {
                  // Auto compounds for everyone
                  tokenDistributor.harvestAndCompound();
                  // Update reward for user
                  _updateReward(msg.sender);
                  // Retrieve total amount staked and calculated current amount (in LOOKS)
                  (uint256 totalAmountStaked, ) = tokenDistributor.userInfo(address(this));
                  uint256 currentAmount = (totalAmountStaked * shares) / totalShares;
                  userInfo[msg.sender].shares -= shares;
                  totalShares -= shares;
                  // Withdraw amount equivalent in shares
                  tokenDistributor.withdraw(currentAmount);
                  uint256 pendingRewards;
                  if (claimRewardToken) {
                      // Fetch pending rewards
                      pendingRewards = userInfo[msg.sender].rewards;
                      if (pendingRewards > 0) {
                          userInfo[msg.sender].rewards = 0;
                          rewardToken.safeTransfer(msg.sender, pendingRewards);
                      }
                  }
                  // Transfer LOOKS tokens to sender
                  looksRareToken.safeTransfer(msg.sender, currentAmount);
                  emit Withdraw(msg.sender, currentAmount, pendingRewards);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
          pragma solidity ^0.8.0;
          import "../utils/Context.sol";
          /**
           * @dev Contract module which provides a basic access control mechanism, where
           * there is an account (an owner) that can be granted exclusive access to
           * specific functions.
           *
           * By default, the owner account will be the one that deploys the contract. This
           * can later be changed with {transferOwnership}.
           *
           * This module is used through inheritance. It will make available the modifier
           * `onlyOwner`, which can be applied to your functions to restrict their use to
           * the owner.
           */
          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 Returns the address of the current owner.
               */
              function owner() public view virtual returns (address) {
                  return _owner;
              }
              /**
               * @dev Throws if called by any account other than the owner.
               */
              modifier onlyOwner() {
                  require(owner() == _msgSender(), "Ownable: caller is not the owner");
                  _;
              }
              /**
               * @dev Leaves the contract without owner. It will not be possible to call
               * `onlyOwner` functions anymore. Can only be called by the current owner.
               *
               * NOTE: Renouncing ownership will leave the contract without an owner,
               * thereby removing 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);
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuard {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
              uint256 private _status;
              constructor() {
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and making it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  // On the first call to nonReentrant, _notEntered will be true
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
                  _;
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          import "../../../utils/Address.sol";
          /**
           * @title SafeERC20
           * @dev Wrappers around ERC20 operations that throw on failure (when the token
           * contract returns false). Tokens that return no value (and instead revert or
           * throw on failure) are also supported, non-reverting calls are assumed to be
           * successful.
           * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
           * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
           */
          library SafeERC20 {
              using Address for address;
              function safeTransfer(
                  IERC20 token,
                  address to,
                  uint256 value
              ) internal {
                  _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
              }
              function safeTransferFrom(
                  IERC20 token,
                  address from,
                  address to,
                  uint256 value
              ) internal {
                  _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
              }
              /**
               * @dev Deprecated. This function has issues similar to the ones found in
               * {IERC20-approve}, and its usage is discouraged.
               *
               * Whenever possible, use {safeIncreaseAllowance} and
               * {safeDecreaseAllowance} instead.
               */
              function safeApprove(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  // safeApprove should only be called when setting an initial allowance,
                  // or when resetting it to zero. To increase and decrease it, use
                  // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                  require(
                      (value == 0) || (token.allowance(address(this), spender) == 0),
                      "SafeERC20: approve from non-zero to non-zero allowance"
                  );
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
              }
              function safeIncreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  uint256 newAllowance = token.allowance(address(this), spender) + value;
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
              }
              function safeDecreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  unchecked {
                      uint256 oldAllowance = token.allowance(address(this), spender);
                      require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                      uint256 newAllowance = oldAllowance - value;
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
              }
              /**
               * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
               * on the return value: the return value is optional (but if data is returned, it must not be false).
               * @param token The token targeted by the call.
               * @param data The call data (encoded using abi.encode or one of its variants).
               */
              function _callOptionalReturn(IERC20 token, bytes memory data) private {
                  // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                  // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                  // the target address contains contract code and also asserts for success in the low-level call.
                  bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                  if (returndata.length > 0) {
                      // Return data is optional
                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
          import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import {ILooksRareToken} from "../interfaces/ILooksRareToken.sol";
          /**
           * @title TokenDistributor
           * @notice It handles the distribution of LOOKS token.
           * It auto-adjusts block rewards over a set number of periods.
           */
          contract TokenDistributor is ReentrancyGuard {
              using SafeERC20 for IERC20;
              using SafeERC20 for ILooksRareToken;
              struct StakingPeriod {
                  uint256 rewardPerBlockForStaking;
                  uint256 rewardPerBlockForOthers;
                  uint256 periodLengthInBlock;
              }
              struct UserInfo {
                  uint256 amount; // Amount of staked tokens provided by user
                  uint256 rewardDebt; // Reward debt
              }
              // Precision factor for calculating rewards
              uint256 public constant PRECISION_FACTOR = 10**12;
              ILooksRareToken public immutable looksRareToken;
              address public immutable tokenSplitter;
              // Number of reward periods
              uint256 public immutable NUMBER_PERIODS;
              // Block number when rewards start
              uint256 public immutable START_BLOCK;
              // Accumulated tokens per share
              uint256 public accTokenPerShare;
              // Current phase for rewards
              uint256 public currentPhase;
              // Block number when rewards end
              uint256 public endBlock;
              // Block number of the last update
              uint256 public lastRewardBlock;
              // Tokens distributed per block for other purposes (team + treasury + trading rewards)
              uint256 public rewardPerBlockForOthers;
              // Tokens distributed per block for staking
              uint256 public rewardPerBlockForStaking;
              // Total amount staked
              uint256 public totalAmountStaked;
              mapping(uint256 => StakingPeriod) public stakingPeriod;
              mapping(address => UserInfo) public userInfo;
              event Compound(address indexed user, uint256 harvestedAmount);
              event Deposit(address indexed user, uint256 amount, uint256 harvestedAmount);
              event NewRewardsPerBlock(
                  uint256 indexed currentPhase,
                  uint256 startBlock,
                  uint256 rewardPerBlockForStaking,
                  uint256 rewardPerBlockForOthers
              );
              event Withdraw(address indexed user, uint256 amount, uint256 harvestedAmount);
              /**
               * @notice Constructor
               * @param _looksRareToken LOOKS token address
               * @param _tokenSplitter token splitter contract address (for team and trading rewards)
               * @param _startBlock start block for reward program
               * @param _rewardsPerBlockForStaking array of rewards per block for staking
               * @param _rewardsPerBlockForOthers array of rewards per block for other purposes (team + treasury + trading rewards)
               * @param _periodLengthesInBlocks array of period lengthes
               * @param _numberPeriods number of periods with different rewards/lengthes (e.g., if 3 changes --> 4 periods)
               */
              constructor(
                  address _looksRareToken,
                  address _tokenSplitter,
                  uint256 _startBlock,
                  uint256[] memory _rewardsPerBlockForStaking,
                  uint256[] memory _rewardsPerBlockForOthers,
                  uint256[] memory _periodLengthesInBlocks,
                  uint256 _numberPeriods
              ) {
                  require(
                      (_periodLengthesInBlocks.length == _numberPeriods) &&
                          (_rewardsPerBlockForStaking.length == _numberPeriods) &&
                          (_rewardsPerBlockForStaking.length == _numberPeriods),
                      "Distributor: Lengthes must match numberPeriods"
                  );
                  // 1. Operational checks for supply
                  uint256 nonCirculatingSupply = ILooksRareToken(_looksRareToken).SUPPLY_CAP() -
                      ILooksRareToken(_looksRareToken).totalSupply();
                  uint256 amountTokensToBeMinted;
                  for (uint256 i = 0; i < _numberPeriods; i++) {
                      amountTokensToBeMinted +=
                          (_rewardsPerBlockForStaking[i] * _periodLengthesInBlocks[i]) +
                          (_rewardsPerBlockForOthers[i] * _periodLengthesInBlocks[i]);
                      stakingPeriod[i] = StakingPeriod({
                          rewardPerBlockForStaking: _rewardsPerBlockForStaking[i],
                          rewardPerBlockForOthers: _rewardsPerBlockForOthers[i],
                          periodLengthInBlock: _periodLengthesInBlocks[i]
                      });
                  }
                  require(amountTokensToBeMinted == nonCirculatingSupply, "Distributor: Wrong reward parameters");
                  // 2. Store values
                  looksRareToken = ILooksRareToken(_looksRareToken);
                  tokenSplitter = _tokenSplitter;
                  rewardPerBlockForStaking = _rewardsPerBlockForStaking[0];
                  rewardPerBlockForOthers = _rewardsPerBlockForOthers[0];
                  START_BLOCK = _startBlock;
                  endBlock = _startBlock + _periodLengthesInBlocks[0];
                  NUMBER_PERIODS = _numberPeriods;
                  // Set the lastRewardBlock as the startBlock
                  lastRewardBlock = _startBlock;
              }
              /**
               * @notice Deposit staked tokens and compounds pending rewards
               * @param amount amount to deposit (in LOOKS)
               */
              function deposit(uint256 amount) external nonReentrant {
                  require(amount > 0, "Deposit: Amount must be > 0");
                  // Update pool information
                  _updatePool();
                  // Transfer LOOKS tokens to this contract
                  looksRareToken.safeTransferFrom(msg.sender, address(this), amount);
                  uint256 pendingRewards;
                  // If not new deposit, calculate pending rewards (for auto-compounding)
                  if (userInfo[msg.sender].amount > 0) {
                      pendingRewards =
                          ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                          userInfo[msg.sender].rewardDebt;
                  }
                  // Adjust user information
                  userInfo[msg.sender].amount += (amount + pendingRewards);
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  // Increase totalAmountStaked
                  totalAmountStaked += (amount + pendingRewards);
                  emit Deposit(msg.sender, amount, pendingRewards);
              }
              /**
               * @notice Compound based on pending rewards
               */
              function harvestAndCompound() external nonReentrant {
                  // Update pool information
                  _updatePool();
                  // Calculate pending rewards
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  // Return if no pending rewards
                  if (pendingRewards == 0) {
                      // It doesn't throw revertion (to help with the fee-sharing auto-compounding contract)
                      return;
                  }
                  // Adjust user amount for pending rewards
                  userInfo[msg.sender].amount += pendingRewards;
                  // Adjust totalAmountStaked
                  totalAmountStaked += pendingRewards;
                  // Recalculate reward debt based on new user amount
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  emit Compound(msg.sender, pendingRewards);
              }
              /**
               * @notice Update pool rewards
               */
              function updatePool() external nonReentrant {
                  _updatePool();
              }
              /**
               * @notice Withdraw staked tokens and compound pending rewards
               * @param amount amount to withdraw
               */
              function withdraw(uint256 amount) external nonReentrant {
                  require(
                      (userInfo[msg.sender].amount >= amount) && (amount > 0),
                      "Withdraw: Amount must be > 0 or lower than user balance"
                  );
                  // Update pool
                  _updatePool();
                  // Calculate pending rewards
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  // Adjust user information
                  userInfo[msg.sender].amount = userInfo[msg.sender].amount + pendingRewards - amount;
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  // Adjust total amount staked
                  totalAmountStaked = totalAmountStaked + pendingRewards - amount;
                  // Transfer LOOKS tokens to the sender
                  looksRareToken.safeTransfer(msg.sender, amount);
                  emit Withdraw(msg.sender, amount, pendingRewards);
              }
              /**
               * @notice Withdraw all staked tokens and collect tokens
               */
              function withdrawAll() external nonReentrant {
                  require(userInfo[msg.sender].amount > 0, "Withdraw: Amount must be > 0");
                  // Update pool
                  _updatePool();
                  // Calculate pending rewards and amount to transfer (to the sender)
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  uint256 amountToTransfer = userInfo[msg.sender].amount + pendingRewards;
                  // Adjust total amount staked
                  totalAmountStaked = totalAmountStaked - userInfo[msg.sender].amount;
                  // Adjust user information
                  userInfo[msg.sender].amount = 0;
                  userInfo[msg.sender].rewardDebt = 0;
                  // Transfer LOOKS tokens to the sender
                  looksRareToken.safeTransfer(msg.sender, amountToTransfer);
                  emit Withdraw(msg.sender, amountToTransfer, pendingRewards);
              }
              /**
               * @notice Calculate pending rewards for a user
               * @param user address of the user
               * @return Pending rewards
               */
              function calculatePendingRewards(address user) external view returns (uint256) {
                  if ((block.number > lastRewardBlock) && (totalAmountStaked != 0)) {
                      uint256 multiplier = _getMultiplier(lastRewardBlock, block.number);
                      uint256 tokenRewardForStaking = multiplier * rewardPerBlockForStaking;
                      uint256 adjustedEndBlock = endBlock;
                      uint256 adjustedCurrentPhase = currentPhase;
                      // Check whether to adjust multipliers and reward per block
                      while ((block.number > adjustedEndBlock) && (adjustedCurrentPhase < (NUMBER_PERIODS - 1))) {
                          // Update current phase
                          adjustedCurrentPhase++;
                          // Update rewards per block
                          uint256 adjustedRewardPerBlockForStaking = stakingPeriod[adjustedCurrentPhase].rewardPerBlockForStaking;
                          // Calculate adjusted block number
                          uint256 previousEndBlock = adjustedEndBlock;
                          // Update end block
                          adjustedEndBlock = previousEndBlock + stakingPeriod[adjustedCurrentPhase].periodLengthInBlock;
                          // Calculate new multiplier
                          uint256 newMultiplier = (block.number <= adjustedEndBlock)
                              ? (block.number - previousEndBlock)
                              : stakingPeriod[adjustedCurrentPhase].periodLengthInBlock;
                          // Adjust token rewards for staking
                          tokenRewardForStaking += (newMultiplier * adjustedRewardPerBlockForStaking);
                      }
                      uint256 adjustedTokenPerShare = accTokenPerShare +
                          (tokenRewardForStaking * PRECISION_FACTOR) /
                          totalAmountStaked;
                      return (userInfo[user].amount * adjustedTokenPerShare) / PRECISION_FACTOR - userInfo[user].rewardDebt;
                  } else {
                      return (userInfo[user].amount * accTokenPerShare) / PRECISION_FACTOR - userInfo[user].rewardDebt;
                  }
              }
              /**
               * @notice Update reward variables of the pool
               */
              function _updatePool() internal {
                  if (block.number <= lastRewardBlock) {
                      return;
                  }
                  if (totalAmountStaked == 0) {
                      lastRewardBlock = block.number;
                      return;
                  }
                  // Calculate multiplier
                  uint256 multiplier = _getMultiplier(lastRewardBlock, block.number);
                  // Calculate rewards for staking and others
                  uint256 tokenRewardForStaking = multiplier * rewardPerBlockForStaking;
                  uint256 tokenRewardForOthers = multiplier * rewardPerBlockForOthers;
                  // Check whether to adjust multipliers and reward per block
                  while ((block.number > endBlock) && (currentPhase < (NUMBER_PERIODS - 1))) {
                      // Update rewards per block
                      _updateRewardsPerBlock(endBlock);
                      uint256 previousEndBlock = endBlock;
                      // Adjust the end block
                      endBlock += stakingPeriod[currentPhase].periodLengthInBlock;
                      // Adjust multiplier to cover the missing periods with other lower inflation schedule
                      uint256 newMultiplier = _getMultiplier(previousEndBlock, block.number);
                      // Adjust token rewards
                      tokenRewardForStaking += (newMultiplier * rewardPerBlockForStaking);
                      tokenRewardForOthers += (newMultiplier * rewardPerBlockForOthers);
                  }
                  // Mint tokens only if token rewards for staking are not null
                  if (tokenRewardForStaking > 0) {
                      // It allows protection against potential issues to prevent funds from being locked
                      bool mintStatus = looksRareToken.mint(address(this), tokenRewardForStaking);
                      if (mintStatus) {
                          accTokenPerShare = accTokenPerShare + ((tokenRewardForStaking * PRECISION_FACTOR) / totalAmountStaked);
                      }
                      looksRareToken.mint(tokenSplitter, tokenRewardForOthers);
                  }
                  // Update last reward block only if it wasn't updated after or at the end block
                  if (lastRewardBlock <= endBlock) {
                      lastRewardBlock = block.number;
                  }
              }
              /**
               * @notice Update rewards per block
               * @dev Rewards are halved by 2 (for staking + others)
               */
              function _updateRewardsPerBlock(uint256 _newStartBlock) internal {
                  // Update current phase
                  currentPhase++;
                  // Update rewards per block
                  rewardPerBlockForStaking = stakingPeriod[currentPhase].rewardPerBlockForStaking;
                  rewardPerBlockForOthers = stakingPeriod[currentPhase].rewardPerBlockForOthers;
                  emit NewRewardsPerBlock(currentPhase, _newStartBlock, rewardPerBlockForStaking, rewardPerBlockForOthers);
              }
              /**
               * @notice Return reward multiplier over the given "from" to "to" block.
               * @param from block to start calculating reward
               * @param to block to finish calculating reward
               * @return the multiplier for the period
               */
              function _getMultiplier(uint256 from, uint256 to) internal view returns (uint256) {
                  if (to <= endBlock) {
                      return to - from;
                  } else if (from >= endBlock) {
                      return 0;
                  } else {
                      return endBlock - from;
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
          pragma solidity ^0.8.0;
          /**
           * @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;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 standard as defined in the EIP.
           */
          interface IERC20 {
              /**
               * @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 `recipient`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
                  address recipient,
                  uint256 amount
              ) external returns (bool);
              /**
               * @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);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Address.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize, which returns 0 for contracts in
                  // construction, since the code is only stored at the end of the
                  // constructor execution.
                  uint256 size;
                  assembly {
                      size := extcodesize(account)
                  }
                  return size > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCall(target, data, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  require(isContract(target), "Address: call to non-contract");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  require(isContract(target), "Address: static call to non-contract");
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(isContract(target), "Address: delegate call to non-contract");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      // Look for revert reason and bubble it up if present
                      if (returndata.length > 0) {
                          // The easiest way to bubble the revert reason is using memory via assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          interface ILooksRareToken is IERC20 {
              function SUPPLY_CAP() external view returns (uint256);
              function mint(address account, uint256 amount) external returns (bool);
          }
          

          File 4 of 4: TokenDistributor
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
          import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
          import {ILooksRareToken} from "../interfaces/ILooksRareToken.sol";
          /**
           * @title TokenDistributor
           * @notice It handles the distribution of LOOKS token.
           * It auto-adjusts block rewards over a set number of periods.
           */
          contract TokenDistributor is ReentrancyGuard {
              using SafeERC20 for IERC20;
              using SafeERC20 for ILooksRareToken;
              struct StakingPeriod {
                  uint256 rewardPerBlockForStaking;
                  uint256 rewardPerBlockForOthers;
                  uint256 periodLengthInBlock;
              }
              struct UserInfo {
                  uint256 amount; // Amount of staked tokens provided by user
                  uint256 rewardDebt; // Reward debt
              }
              // Precision factor for calculating rewards
              uint256 public constant PRECISION_FACTOR = 10**12;
              ILooksRareToken public immutable looksRareToken;
              address public immutable tokenSplitter;
              // Number of reward periods
              uint256 public immutable NUMBER_PERIODS;
              // Block number when rewards start
              uint256 public immutable START_BLOCK;
              // Accumulated tokens per share
              uint256 public accTokenPerShare;
              // Current phase for rewards
              uint256 public currentPhase;
              // Block number when rewards end
              uint256 public endBlock;
              // Block number of the last update
              uint256 public lastRewardBlock;
              // Tokens distributed per block for other purposes (team + treasury + trading rewards)
              uint256 public rewardPerBlockForOthers;
              // Tokens distributed per block for staking
              uint256 public rewardPerBlockForStaking;
              // Total amount staked
              uint256 public totalAmountStaked;
              mapping(uint256 => StakingPeriod) public stakingPeriod;
              mapping(address => UserInfo) public userInfo;
              event Compound(address indexed user, uint256 harvestedAmount);
              event Deposit(address indexed user, uint256 amount, uint256 harvestedAmount);
              event NewRewardsPerBlock(
                  uint256 indexed currentPhase,
                  uint256 startBlock,
                  uint256 rewardPerBlockForStaking,
                  uint256 rewardPerBlockForOthers
              );
              event Withdraw(address indexed user, uint256 amount, uint256 harvestedAmount);
              /**
               * @notice Constructor
               * @param _looksRareToken LOOKS token address
               * @param _tokenSplitter token splitter contract address (for team and trading rewards)
               * @param _startBlock start block for reward program
               * @param _rewardsPerBlockForStaking array of rewards per block for staking
               * @param _rewardsPerBlockForOthers array of rewards per block for other purposes (team + treasury + trading rewards)
               * @param _periodLengthesInBlocks array of period lengthes
               * @param _numberPeriods number of periods with different rewards/lengthes (e.g., if 3 changes --> 4 periods)
               */
              constructor(
                  address _looksRareToken,
                  address _tokenSplitter,
                  uint256 _startBlock,
                  uint256[] memory _rewardsPerBlockForStaking,
                  uint256[] memory _rewardsPerBlockForOthers,
                  uint256[] memory _periodLengthesInBlocks,
                  uint256 _numberPeriods
              ) {
                  require(
                      (_periodLengthesInBlocks.length == _numberPeriods) &&
                          (_rewardsPerBlockForStaking.length == _numberPeriods) &&
                          (_rewardsPerBlockForStaking.length == _numberPeriods),
                      "Distributor: Lengthes must match numberPeriods"
                  );
                  // 1. Operational checks for supply
                  uint256 nonCirculatingSupply = ILooksRareToken(_looksRareToken).SUPPLY_CAP() -
                      ILooksRareToken(_looksRareToken).totalSupply();
                  uint256 amountTokensToBeMinted;
                  for (uint256 i = 0; i < _numberPeriods; i++) {
                      amountTokensToBeMinted +=
                          (_rewardsPerBlockForStaking[i] * _periodLengthesInBlocks[i]) +
                          (_rewardsPerBlockForOthers[i] * _periodLengthesInBlocks[i]);
                      stakingPeriod[i] = StakingPeriod({
                          rewardPerBlockForStaking: _rewardsPerBlockForStaking[i],
                          rewardPerBlockForOthers: _rewardsPerBlockForOthers[i],
                          periodLengthInBlock: _periodLengthesInBlocks[i]
                      });
                  }
                  require(amountTokensToBeMinted == nonCirculatingSupply, "Distributor: Wrong reward parameters");
                  // 2. Store values
                  looksRareToken = ILooksRareToken(_looksRareToken);
                  tokenSplitter = _tokenSplitter;
                  rewardPerBlockForStaking = _rewardsPerBlockForStaking[0];
                  rewardPerBlockForOthers = _rewardsPerBlockForOthers[0];
                  START_BLOCK = _startBlock;
                  endBlock = _startBlock + _periodLengthesInBlocks[0];
                  NUMBER_PERIODS = _numberPeriods;
                  // Set the lastRewardBlock as the startBlock
                  lastRewardBlock = _startBlock;
              }
              /**
               * @notice Deposit staked tokens and compounds pending rewards
               * @param amount amount to deposit (in LOOKS)
               */
              function deposit(uint256 amount) external nonReentrant {
                  require(amount > 0, "Deposit: Amount must be > 0");
                  // Update pool information
                  _updatePool();
                  // Transfer LOOKS tokens to this contract
                  looksRareToken.safeTransferFrom(msg.sender, address(this), amount);
                  uint256 pendingRewards;
                  // If not new deposit, calculate pending rewards (for auto-compounding)
                  if (userInfo[msg.sender].amount > 0) {
                      pendingRewards =
                          ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                          userInfo[msg.sender].rewardDebt;
                  }
                  // Adjust user information
                  userInfo[msg.sender].amount += (amount + pendingRewards);
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  // Increase totalAmountStaked
                  totalAmountStaked += (amount + pendingRewards);
                  emit Deposit(msg.sender, amount, pendingRewards);
              }
              /**
               * @notice Compound based on pending rewards
               */
              function harvestAndCompound() external nonReentrant {
                  // Update pool information
                  _updatePool();
                  // Calculate pending rewards
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  // Return if no pending rewards
                  if (pendingRewards == 0) {
                      // It doesn't throw revertion (to help with the fee-sharing auto-compounding contract)
                      return;
                  }
                  // Adjust user amount for pending rewards
                  userInfo[msg.sender].amount += pendingRewards;
                  // Adjust totalAmountStaked
                  totalAmountStaked += pendingRewards;
                  // Recalculate reward debt based on new user amount
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  emit Compound(msg.sender, pendingRewards);
              }
              /**
               * @notice Update pool rewards
               */
              function updatePool() external nonReentrant {
                  _updatePool();
              }
              /**
               * @notice Withdraw staked tokens and compound pending rewards
               * @param amount amount to withdraw
               */
              function withdraw(uint256 amount) external nonReentrant {
                  require(
                      (userInfo[msg.sender].amount >= amount) && (amount > 0),
                      "Withdraw: Amount must be > 0 or lower than user balance"
                  );
                  // Update pool
                  _updatePool();
                  // Calculate pending rewards
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  // Adjust user information
                  userInfo[msg.sender].amount = userInfo[msg.sender].amount + pendingRewards - amount;
                  userInfo[msg.sender].rewardDebt = (userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR;
                  // Adjust total amount staked
                  totalAmountStaked = totalAmountStaked + pendingRewards - amount;
                  // Transfer LOOKS tokens to the sender
                  looksRareToken.safeTransfer(msg.sender, amount);
                  emit Withdraw(msg.sender, amount, pendingRewards);
              }
              /**
               * @notice Withdraw all staked tokens and collect tokens
               */
              function withdrawAll() external nonReentrant {
                  require(userInfo[msg.sender].amount > 0, "Withdraw: Amount must be > 0");
                  // Update pool
                  _updatePool();
                  // Calculate pending rewards and amount to transfer (to the sender)
                  uint256 pendingRewards = ((userInfo[msg.sender].amount * accTokenPerShare) / PRECISION_FACTOR) -
                      userInfo[msg.sender].rewardDebt;
                  uint256 amountToTransfer = userInfo[msg.sender].amount + pendingRewards;
                  // Adjust total amount staked
                  totalAmountStaked = totalAmountStaked - userInfo[msg.sender].amount;
                  // Adjust user information
                  userInfo[msg.sender].amount = 0;
                  userInfo[msg.sender].rewardDebt = 0;
                  // Transfer LOOKS tokens to the sender
                  looksRareToken.safeTransfer(msg.sender, amountToTransfer);
                  emit Withdraw(msg.sender, amountToTransfer, pendingRewards);
              }
              /**
               * @notice Calculate pending rewards for a user
               * @param user address of the user
               * @return Pending rewards
               */
              function calculatePendingRewards(address user) external view returns (uint256) {
                  if ((block.number > lastRewardBlock) && (totalAmountStaked != 0)) {
                      uint256 multiplier = _getMultiplier(lastRewardBlock, block.number);
                      uint256 tokenRewardForStaking = multiplier * rewardPerBlockForStaking;
                      uint256 adjustedEndBlock = endBlock;
                      uint256 adjustedCurrentPhase = currentPhase;
                      // Check whether to adjust multipliers and reward per block
                      while ((block.number > adjustedEndBlock) && (adjustedCurrentPhase < (NUMBER_PERIODS - 1))) {
                          // Update current phase
                          adjustedCurrentPhase++;
                          // Update rewards per block
                          uint256 adjustedRewardPerBlockForStaking = stakingPeriod[adjustedCurrentPhase].rewardPerBlockForStaking;
                          // Calculate adjusted block number
                          uint256 previousEndBlock = adjustedEndBlock;
                          // Update end block
                          adjustedEndBlock = previousEndBlock + stakingPeriod[adjustedCurrentPhase].periodLengthInBlock;
                          // Calculate new multiplier
                          uint256 newMultiplier = (block.number <= adjustedEndBlock)
                              ? (block.number - previousEndBlock)
                              : stakingPeriod[adjustedCurrentPhase].periodLengthInBlock;
                          // Adjust token rewards for staking
                          tokenRewardForStaking += (newMultiplier * adjustedRewardPerBlockForStaking);
                      }
                      uint256 adjustedTokenPerShare = accTokenPerShare +
                          (tokenRewardForStaking * PRECISION_FACTOR) /
                          totalAmountStaked;
                      return (userInfo[user].amount * adjustedTokenPerShare) / PRECISION_FACTOR - userInfo[user].rewardDebt;
                  } else {
                      return (userInfo[user].amount * accTokenPerShare) / PRECISION_FACTOR - userInfo[user].rewardDebt;
                  }
              }
              /**
               * @notice Update reward variables of the pool
               */
              function _updatePool() internal {
                  if (block.number <= lastRewardBlock) {
                      return;
                  }
                  if (totalAmountStaked == 0) {
                      lastRewardBlock = block.number;
                      return;
                  }
                  // Calculate multiplier
                  uint256 multiplier = _getMultiplier(lastRewardBlock, block.number);
                  // Calculate rewards for staking and others
                  uint256 tokenRewardForStaking = multiplier * rewardPerBlockForStaking;
                  uint256 tokenRewardForOthers = multiplier * rewardPerBlockForOthers;
                  // Check whether to adjust multipliers and reward per block
                  while ((block.number > endBlock) && (currentPhase < (NUMBER_PERIODS - 1))) {
                      // Update rewards per block
                      _updateRewardsPerBlock(endBlock);
                      uint256 previousEndBlock = endBlock;
                      // Adjust the end block
                      endBlock += stakingPeriod[currentPhase].periodLengthInBlock;
                      // Adjust multiplier to cover the missing periods with other lower inflation schedule
                      uint256 newMultiplier = _getMultiplier(previousEndBlock, block.number);
                      // Adjust token rewards
                      tokenRewardForStaking += (newMultiplier * rewardPerBlockForStaking);
                      tokenRewardForOthers += (newMultiplier * rewardPerBlockForOthers);
                  }
                  // Mint tokens only if token rewards for staking are not null
                  if (tokenRewardForStaking > 0) {
                      // It allows protection against potential issues to prevent funds from being locked
                      bool mintStatus = looksRareToken.mint(address(this), tokenRewardForStaking);
                      if (mintStatus) {
                          accTokenPerShare = accTokenPerShare + ((tokenRewardForStaking * PRECISION_FACTOR) / totalAmountStaked);
                      }
                      looksRareToken.mint(tokenSplitter, tokenRewardForOthers);
                  }
                  // Update last reward block only if it wasn't updated after or at the end block
                  if (lastRewardBlock <= endBlock) {
                      lastRewardBlock = block.number;
                  }
              }
              /**
               * @notice Update rewards per block
               * @dev Rewards are halved by 2 (for staking + others)
               */
              function _updateRewardsPerBlock(uint256 _newStartBlock) internal {
                  // Update current phase
                  currentPhase++;
                  // Update rewards per block
                  rewardPerBlockForStaking = stakingPeriod[currentPhase].rewardPerBlockForStaking;
                  rewardPerBlockForOthers = stakingPeriod[currentPhase].rewardPerBlockForOthers;
                  emit NewRewardsPerBlock(currentPhase, _newStartBlock, rewardPerBlockForStaking, rewardPerBlockForOthers);
              }
              /**
               * @notice Return reward multiplier over the given "from" to "to" block.
               * @param from block to start calculating reward
               * @param to block to finish calculating reward
               * @return the multiplier for the period
               */
              function _getMultiplier(uint256 from, uint256 to) internal view returns (uint256) {
                  if (to <= endBlock) {
                      return to - from;
                  } else if (from >= endBlock) {
                      return 0;
                  } else {
                      return endBlock - from;
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Contract module that helps prevent reentrant calls to a function.
           *
           * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
           * available, which can be applied to functions to make sure there are no nested
           * (reentrant) calls to them.
           *
           * Note that because there is a single `nonReentrant` guard, functions marked as
           * `nonReentrant` may not call one another. This can be worked around by making
           * those functions `private`, and then adding `external` `nonReentrant` entry
           * points to them.
           *
           * TIP: If you would like to learn more about reentrancy and alternative ways
           * to protect against it, check out our blog post
           * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
           */
          abstract contract ReentrancyGuard {
              // Booleans are more expensive than uint256 or any type that takes up a full
              // word because each write operation emits an extra SLOAD to first read the
              // slot's contents, replace the bits taken up by the boolean, and then write
              // back. This is the compiler's defense against contract upgrades and
              // pointer aliasing, and it cannot be disabled.
              // The values being non-zero value makes deployment a bit more expensive,
              // but in exchange the refund on every call to nonReentrant will be lower in
              // amount. Since refunds are capped to a percentage of the total
              // transaction's gas, it is best to keep them low in cases like this one, to
              // increase the likelihood of the full refund coming into effect.
              uint256 private constant _NOT_ENTERED = 1;
              uint256 private constant _ENTERED = 2;
              uint256 private _status;
              constructor() {
                  _status = _NOT_ENTERED;
              }
              /**
               * @dev Prevents a contract from calling itself, directly or indirectly.
               * Calling a `nonReentrant` function from another `nonReentrant`
               * function is not supported. It is possible to prevent this from happening
               * by making the `nonReentrant` function external, and making it call a
               * `private` function that does the actual work.
               */
              modifier nonReentrant() {
                  // On the first call to nonReentrant, _notEntered will be true
                  require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
                  // Any calls to nonReentrant after this point will fail
                  _status = _ENTERED;
                  _;
                  // By storing the original value once again, a refund is triggered (see
                  // https://eips.ethereum.org/EIPS/eip-2200)
                  _status = _NOT_ENTERED;
              }
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)
          pragma solidity ^0.8.0;
          import "../IERC20.sol";
          import "../../../utils/Address.sol";
          /**
           * @title SafeERC20
           * @dev Wrappers around ERC20 operations that throw on failure (when the token
           * contract returns false). Tokens that return no value (and instead revert or
           * throw on failure) are also supported, non-reverting calls are assumed to be
           * successful.
           * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
           * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
           */
          library SafeERC20 {
              using Address for address;
              function safeTransfer(
                  IERC20 token,
                  address to,
                  uint256 value
              ) internal {
                  _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
              }
              function safeTransferFrom(
                  IERC20 token,
                  address from,
                  address to,
                  uint256 value
              ) internal {
                  _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
              }
              /**
               * @dev Deprecated. This function has issues similar to the ones found in
               * {IERC20-approve}, and its usage is discouraged.
               *
               * Whenever possible, use {safeIncreaseAllowance} and
               * {safeDecreaseAllowance} instead.
               */
              function safeApprove(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  // safeApprove should only be called when setting an initial allowance,
                  // or when resetting it to zero. To increase and decrease it, use
                  // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                  require(
                      (value == 0) || (token.allowance(address(this), spender) == 0),
                      "SafeERC20: approve from non-zero to non-zero allowance"
                  );
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
              }
              function safeIncreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  uint256 newAllowance = token.allowance(address(this), spender) + value;
                  _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
              }
              function safeDecreaseAllowance(
                  IERC20 token,
                  address spender,
                  uint256 value
              ) internal {
                  unchecked {
                      uint256 oldAllowance = token.allowance(address(this), spender);
                      require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
                      uint256 newAllowance = oldAllowance - value;
                      _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
                  }
              }
              /**
               * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
               * on the return value: the return value is optional (but if data is returned, it must not be false).
               * @param token The token targeted by the call.
               * @param data The call data (encoded using abi.encode or one of its variants).
               */
              function _callOptionalReturn(IERC20 token, bytes memory data) private {
                  // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
                  // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
                  // the target address contains contract code and also asserts for success in the low-level call.
                  bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
                  if (returndata.length > 0) {
                      // Return data is optional
                      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
                  }
              }
          }
          // SPDX-License-Identifier: MIT
          pragma solidity ^0.8.0;
          import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
          interface ILooksRareToken is IERC20 {
              function SUPPLY_CAP() external view returns (uint256);
              function mint(address account, uint256 amount) external returns (bool);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Interface of the ERC20 standard as defined in the EIP.
           */
          interface IERC20 {
              /**
               * @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 `recipient`.
               *
               * Returns a boolean value indicating whether the operation succeeded.
               *
               * Emits a {Transfer} event.
               */
              function transfer(address recipient, 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 `sender` to `recipient` 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 sender,
                  address recipient,
                  uint256 amount
              ) external returns (bool);
              /**
               * @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);
          }
          // SPDX-License-Identifier: MIT
          // OpenZeppelin Contracts v4.4.1 (utils/Address.sol)
          pragma solidity ^0.8.0;
          /**
           * @dev Collection of functions related to the address type
           */
          library Address {
              /**
               * @dev Returns true if `account` is a contract.
               *
               * [IMPORTANT]
               * ====
               * It is unsafe to assume that an address for which this function returns
               * false is an externally-owned account (EOA) and not a contract.
               *
               * Among others, `isContract` will return false for the following
               * types of addresses:
               *
               *  - an externally-owned account
               *  - a contract in construction
               *  - an address where a contract will be created
               *  - an address where a contract lived, but was destroyed
               * ====
               */
              function isContract(address account) internal view returns (bool) {
                  // This method relies on extcodesize, which returns 0 for contracts in
                  // construction, since the code is only stored at the end of the
                  // constructor execution.
                  uint256 size;
                  assembly {
                      size := extcodesize(account)
                  }
                  return size > 0;
              }
              /**
               * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
               * `recipient`, forwarding all available gas and reverting on errors.
               *
               * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
               * of certain opcodes, possibly making contracts go over the 2300 gas limit
               * imposed by `transfer`, making them unable to receive funds via
               * `transfer`. {sendValue} removes this limitation.
               *
               * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
               *
               * IMPORTANT: because control is transferred to `recipient`, care must be
               * taken to not create reentrancy vulnerabilities. Consider using
               * {ReentrancyGuard} or the
               * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
               */
              function sendValue(address payable recipient, uint256 amount) internal {
                  require(address(this).balance >= amount, "Address: insufficient balance");
                  (bool success, ) = recipient.call{value: amount}("");
                  require(success, "Address: unable to send value, recipient may have reverted");
              }
              /**
               * @dev Performs a Solidity function call using a low level `call`. A
               * plain `call` is an unsafe replacement for a function call: use this
               * function instead.
               *
               * If `target` reverts with a revert reason, it is bubbled up by this
               * function (like regular Solidity function calls).
               *
               * Returns the raw returned data. To convert to the expected return value,
               * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
               *
               * Requirements:
               *
               * - `target` must be a contract.
               * - calling `target` with `data` must not revert.
               *
               * _Available since v3.1._
               */
              function functionCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionCall(target, data, "Address: low-level call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
               * `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, 0, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but also transferring `value` wei to `target`.
               *
               * Requirements:
               *
               * - the calling contract must have an ETH balance of at least `value`.
               * - the called Solidity function must be `payable`.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value
              ) internal returns (bytes memory) {
                  return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
              }
              /**
               * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
               * with `errorMessage` as a fallback revert reason when `target` reverts.
               *
               * _Available since v3.1._
               */
              function functionCallWithValue(
                  address target,
                  bytes memory data,
                  uint256 value,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(address(this).balance >= value, "Address: insufficient balance for call");
                  require(isContract(target), "Address: call to non-contract");
                  (bool success, bytes memory returndata) = target.call{value: value}(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
                  return functionStaticCall(target, data, "Address: low-level static call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a static call.
               *
               * _Available since v3.3._
               */
              function functionStaticCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal view returns (bytes memory) {
                  require(isContract(target), "Address: static call to non-contract");
                  (bool success, bytes memory returndata) = target.staticcall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
                  return functionDelegateCall(target, data, "Address: low-level delegate call failed");
              }
              /**
               * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
               * but performing a delegate call.
               *
               * _Available since v3.4._
               */
              function functionDelegateCall(
                  address target,
                  bytes memory data,
                  string memory errorMessage
              ) internal returns (bytes memory) {
                  require(isContract(target), "Address: delegate call to non-contract");
                  (bool success, bytes memory returndata) = target.delegatecall(data);
                  return verifyCallResult(success, returndata, errorMessage);
              }
              /**
               * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
               * revert reason using the provided one.
               *
               * _Available since v4.3._
               */
              function verifyCallResult(
                  bool success,
                  bytes memory returndata,
                  string memory errorMessage
              ) internal pure returns (bytes memory) {
                  if (success) {
                      return returndata;
                  } else {
                      // Look for revert reason and bubble it up if present
                      if (returndata.length > 0) {
                          // The easiest way to bubble the revert reason is using memory via assembly
                          assembly {
                              let returndata_size := mload(returndata)
                              revert(add(32, returndata), returndata_size)
                          }
                      } else {
                          revert(errorMessage);
                      }
                  }
              }
          }