ETH Price: $3,157.61 (-4.06%)

Contract

0xc759C6233e9C1095328D29CffF319780b28CecD8
 
Transaction Hash
Method
Block
From
To
Process Rewards206179572024-08-27 5:53:5979 days ago1724738039IN
0xc759C623...0b28CecD8
0 ETH0.000060131.30726923
Unstake206179482024-08-27 5:51:5979 days ago1724737919IN
0xc759C623...0b28CecD8
0 ETH0.000290491.05220443
Stake204528382024-08-04 4:36:35102 days ago1722746195IN
0xc759C623...0b28CecD8
0 ETH0.000215521
Unstake197573442024-04-29 0:19:11199 days ago1714349951IN
0xc759C623...0b28CecD8
0 ETH0.001439154.9845584
Unstake196701452024-04-16 19:32:23211 days ago1713295943IN
0xc759C623...0b28CecD8
0 ETH0.001163329.59110728
Unstake194496262024-03-16 19:48:35242 days ago1710618515IN
0xc759C623...0b28CecD8
0 ETH0.008542231.68214669
Unstake193970182024-03-09 10:43:35250 days ago1709981015IN
0xc759C623...0b28CecD8
0 ETH0.0124153543.29919347
Unstake192944162024-02-24 2:19:11264 days ago1708741151IN
0xc759C623...0b28CecD8
0 ETH0.005734220
Unstake191526982024-02-04 4:52:23284 days ago1707022343IN
0xc759C623...0b28CecD8
0 ETH0.0035640712.43043635
Unstake187882542023-12-15 1:30:35335 days ago1702603835IN
0xc759C623...0b28CecD8
0 ETH0.0030461733.62598233
Unstake187882082023-12-15 1:21:23335 days ago1702603283IN
0xc759C623...0b28CecD8
0 ETH0.0025565128.22447853
Unstake187881962023-12-15 1:18:59335 days ago1702603139IN
0xc759C623...0b28CecD8
0 ETH0.0105613533.80043495
Unstake187662372023-12-11 23:28:23338 days ago1702337303IN
0xc759C623...0b28CecD8
0 ETH0.0118861541.45878837
Unstake186804322023-11-29 23:05:23350 days ago1701299123IN
0xc759C623...0b28CecD8
0 ETH0.0115326141.76728607
Unstake186804112023-11-29 23:01:11350 days ago1701298871IN
0xc759C623...0b28CecD8
0 ETH0.0011530440.13093498
Unstake186628822023-11-27 12:08:35353 days ago1701086915IN
0xc759C623...0b28CecD8
0 ETH0.0100722937.89370087
Unstake186554002023-11-26 10:57:47354 days ago1700996267IN
0xc759C623...0b28CecD8
0 ETH0.00525619.1046774
Unstake185281752023-11-08 15:37:23372 days ago1699457843IN
0xc759C623...0b28CecD8
0 ETH0.0121476151.52751825
Unstake184749212023-11-01 4:37:59379 days ago1698813479IN
0xc759C623...0b28CecD8
0 ETH0.0013895315.34074317
Unstake184749132023-11-01 4:36:23379 days ago1698813383IN
0xc759C623...0b28CecD8
0 ETH0.0046229814.79592878
Unstake183031932023-10-08 3:50:59403 days ago1696737059IN
0xc759C623...0b28CecD8
0 ETH0.000490865.40479736
Unstake177099702023-07-17 2:20:35486 days ago1689560435IN
0xc759C623...0b28CecD8
0 ETH0.005115216.37128867
Unstake169548732023-04-01 14:25:35593 days ago1680359135IN
0xc759C623...0b28CecD8
0 ETH0.0024590527.0758078
Unstake166682062023-02-20 7:14:23633 days ago1676877263IN
0xc759C623...0b28CecD8
0 ETH0.005413419
Unstake165009072023-01-27 21:43:23656 days ago1674855803IN
0xc759C623...0b28CecD8
0 ETH0.0054023918.8419374
View all transactions

Latest 25 internal transactions (View All)

Advanced mode:
Parent Transaction Hash Block From To
206179482024-08-27 5:51:5979 days ago1724737919
0xc759C623...0b28CecD8
0 ETH
206179482024-08-27 5:51:5979 days ago1724737919
0xc759C623...0b28CecD8
0 ETH
206179482024-08-27 5:51:5979 days ago1724737919
0xc759C623...0b28CecD8
0 ETH
204528382024-08-04 4:36:35102 days ago1722746195
0xc759C623...0b28CecD8
0 ETH
204528382024-08-04 4:36:35102 days ago1722746195
0xc759C623...0b28CecD8
0 ETH
204528382024-08-04 4:36:35102 days ago1722746195
0xc759C623...0b28CecD8
0 ETH
197573442024-04-29 0:19:11199 days ago1714349951
0xc759C623...0b28CecD8
0 ETH
197573442024-04-29 0:19:11199 days ago1714349951
0xc759C623...0b28CecD8
0 ETH
197573442024-04-29 0:19:11199 days ago1714349951
0xc759C623...0b28CecD8
0 ETH
196701452024-04-16 19:32:23211 days ago1713295943
0xc759C623...0b28CecD8
0 ETH
196701452024-04-16 19:32:23211 days ago1713295943
0xc759C623...0b28CecD8
0 ETH
194496262024-03-16 19:48:35242 days ago1710618515
0xc759C623...0b28CecD8
0 ETH
194496262024-03-16 19:48:35242 days ago1710618515
0xc759C623...0b28CecD8
0 ETH
194496262024-03-16 19:48:35242 days ago1710618515
0xc759C623...0b28CecD8
0 ETH
193970182024-03-09 10:43:35250 days ago1709981015
0xc759C623...0b28CecD8
0 ETH
193970182024-03-09 10:43:35250 days ago1709981015
0xc759C623...0b28CecD8
0 ETH
193970182024-03-09 10:43:35250 days ago1709981015
0xc759C623...0b28CecD8
0 ETH
192944162024-02-24 2:19:11264 days ago1708741151
0xc759C623...0b28CecD8
0 ETH
192944162024-02-24 2:19:11264 days ago1708741151
0xc759C623...0b28CecD8
0 ETH
192944162024-02-24 2:19:11264 days ago1708741151
0xc759C623...0b28CecD8
0 ETH
191526982024-02-04 4:52:23284 days ago1707022343
0xc759C623...0b28CecD8
0 ETH
191526982024-02-04 4:52:23284 days ago1707022343
0xc759C623...0b28CecD8
0 ETH
191526982024-02-04 4:52:23284 days ago1707022343
0xc759C623...0b28CecD8
0 ETH
187882542023-12-15 1:30:35335 days ago1702603835
0xc759C623...0b28CecD8
0 ETH
187882082023-12-15 1:21:23335 days ago1702603283
0xc759C623...0b28CecD8
0 ETH
View All Internal Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
FlashPoolV2

Compiler Version
v0.8.1+commit.df193b15

Optimization Enabled:
Yes with 200 runs

Other Settings:
default evmVersion, MIT license

Contract Source Code (Solidity)

/**
 *Submitted for verification at Etherscan.io on 2021-09-27
*/

// SPDX-License-Identifier: MIT
pragma solidity 0.8.1;

/**
 * @title Linked to ILV Marker Interface
 *
 * @notice Marks smart contracts which are linked to IlluviumERC20 token instance upon construction,
 *      all these smart contracts share a common ilv() address getter
 *
 * @notice Implementing smart contracts MUST verify that they get linked to real IlluviumERC20 instance
 *      and that ilv() getter returns this very same instance address
 *
 * @author Basil Gorin
 */
interface ILinkedToILV {
  /**
   * @notice Getter for a verified IlluviumERC20 instance address
   *
   * @return IlluviumERC20 token instance address smart contract is linked to
   */
  function ilv() external view returns (address);
}

/**
 * @title Illuvium Pool
 *
 * @notice An abstraction representing a pool, see IlluviumPoolBase for details
 *
 * @author Pedro Bergamini, reviewed by Basil Gorin
 */
interface IPool is ILinkedToILV {
  /**
   * @dev Deposit is a key data structure used in staking,
   *      it represents a unit of stake with its amount, weight and term (time interval)
   */
  struct Deposit {
    // @dev token amount staked
    uint256 tokenAmount;
    // @dev stake weight
    uint256 weight;
    // @dev locking period - from
    uint64 lockedFrom;
    // @dev locking period - until
    uint64 lockedUntil;
    // @dev indicates if the stake was created as a yield reward
    bool isYield;
  }

  // for the rest of the functions see Soldoc in IlluviumPoolBase

  function silv() external view returns (address);

  function poolToken() external view returns (address);

  function isFlashPool() external view returns (bool);

  function weight() external view returns (uint32);

  function lastYieldDistribution() external view returns (uint64);

  function yieldRewardsPerWeight() external view returns (uint256);

  function usersLockingWeight() external view returns (uint256);

  function pendingYieldRewards(address _user) external view returns (uint256);

  function balanceOf(address _user) external view returns (uint256);

  function getDeposit(address _user, uint256 _depositId) external view returns (Deposit memory);

  function getDepositsLength(address _user) external view returns (uint256);

  function stake(
    uint256 _amount,
    uint64 _lockedUntil,
    bool useSILV
  ) external;

  function unstake(
    uint256 _depositId,
    uint256 _amount,
    bool useSILV
  ) external;

  function sync() external;

  function processRewards(bool useSILV) external;

  function setWeight(uint32 _weight) external;
}

interface ICorePool is IPool {
  function vaultRewardsPerToken() external view returns (uint256);

  function poolTokenReserve() external view returns (uint256);

  function stakeAsPool(address _staker, uint256 _amount) external;

  function receiveVaultRewards(uint256 _amount) external;
}

/**
 * @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 make 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;
  }
}

/**
 * @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 {
  address private _owner;

  event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

  /**
   * @dev Initializes the contract setting the deployer as the initial owner.
   */
  constructor() {
    address msgSender = msg.sender;
    _owner = msgSender;
    emit OwnershipTransferred(address(0), 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() == msg.sender, "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 {
    emit OwnershipTransferred(_owner, address(0));
    _owner = 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");
    emit OwnershipTransferred(_owner, newOwner);
    _owner = newOwner;
  }
}

/**
 * @title Address Utils
 *
 * @dev Utility library of inline functions on addresses
 *
 * @author Basil Gorin
 */
library AddressUtils {

  /**
   * @notice Checks if the target address is a contract
   * @dev This function will return false if invoked during the constructor of a contract,
   *      as the code is not actually created until after the constructor finishes.
   * @param addr address to check
   * @return whether the target address is a contract
   */
  function isContract(address addr) internal view returns (bool) {
    // a variable to load `extcodesize` to
    uint256 size = 0;

    // XXX Currently there is no better way to check if there is a contract in an address
    // than to check the size of the code at that address.
    // See https://ethereum.stackexchange.com/a/14016/36603 for more details about how this works.
    // TODO: Check this again before the Serenity release, because all addresses will be contracts.
    // solium-disable-next-line security/no-inline-assembly
    assembly {
    // retrieve the size of the code at address `addr`
      size := extcodesize(addr)
    }

    // positive size indicates a smart contract address
    return size > 0;
  }

}

/**
 * @title ERC20 token receiver interface
 *
 * @dev Interface for any contract that wants to support safe transfers
 *      from ERC20 token smart contracts.
 * @dev Inspired by ERC721 and ERC223 token standards
 *
 * @dev See https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
 * @dev See https://github.com/ethereum/EIPs/issues/223
 *
 * @author Basil Gorin
 */
interface ERC20Receiver {
  /**
   * @notice Handle the receipt of a ERC20 token(s)
   * @dev The ERC20 smart contract calls this function on the recipient
   *      after a successful transfer (`safeTransferFrom`).
   *      This function MAY throw to revert and reject the transfer.
   *      Return of other than the magic value MUST result in the transaction being reverted.
   * @notice The contract address is always the message sender.
   *      A wallet/broker/auction application MUST implement the wallet interface
   *      if it will accept safe transfers.
   * @param _operator The address which called `safeTransferFrom` function
   * @param _from The address which previously owned the token
   * @param _value amount of tokens which is being transferred
   * @param _data additional data with no specified format
   * @return `bytes4(keccak256("onERC20Received(address,address,uint256,bytes)"))` unless throwing
   */
  function onERC20Received(address _operator, address _from, uint256 _value, bytes calldata _data) external returns(bytes4);
}

/**
 * @title Access Control List
 *
 * @notice Access control smart contract provides an API to check
 *      if specific operation is permitted globally and/or
 *      if particular user has a permission to execute it.
 *
 * @notice It deals with two main entities: features and roles.
 *
 * @notice Features are designed to be used to enable/disable specific
 *      functions (public functions) of the smart contract for everyone.
 * @notice User roles are designed to restrict access to specific
 *      functions (restricted functions) of the smart contract to some users.
 *
 * @notice Terms "role", "permissions" and "set of permissions" have equal meaning
 *      in the documentation text and may be used interchangeably.
 * @notice Terms "permission", "single permission" implies only one permission bit set.
 *
 * @dev This smart contract is designed to be inherited by other
 *      smart contracts which require access control management capabilities.
 *
 * @author Basil Gorin
 */
contract AccessControl {
  /**
   * @notice Access manager is responsible for assigning the roles to users,
   *      enabling/disabling global features of the smart contract
   * @notice Access manager can add, remove and update user roles,
   *      remove and update global features
   *
   * @dev Role ROLE_ACCESS_MANAGER allows modifying user roles and global features
   * @dev Role ROLE_ACCESS_MANAGER has single bit at position 255 enabled
   */
  uint256 public constant ROLE_ACCESS_MANAGER = 0x8000000000000000000000000000000000000000000000000000000000000000;

  /**
   * @dev Bitmask representing all the possible permissions (super admin role)
   * @dev Has all the bits are enabled (2^256 - 1 value)
   */
  uint256 private constant FULL_PRIVILEGES_MASK = type(uint256).max; // before 0.8.0: uint256(-1) overflows to 0xFFFF...

  /**
   * @notice Privileged addresses with defined roles/permissions
   * @notice In the context of ERC20/ERC721 tokens these can be permissions to
   *      allow minting or burning tokens, transferring on behalf and so on
   *
   * @dev Maps user address to the permissions bitmask (role), where each bit
   *      represents a permission
   * @dev Bitmask 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF
   *      represents all possible permissions
   * @dev Zero address mapping represents global features of the smart contract
   */
  mapping(address => uint256) public userRoles;

  /**
   * @dev Fired in updateRole() and updateFeatures()
   *
   * @param _by operator which called the function
   * @param _to address which was granted/revoked permissions
   * @param _requested permissions requested
   * @param _actual permissions effectively set
   */
  event RoleUpdated(address indexed _by, address indexed _to, uint256 _requested, uint256 _actual);

  /**
   * @notice Creates an access control instance,
   *      setting contract creator to have full privileges
   */
  constructor() {
    // contract creator has full privileges
    userRoles[msg.sender] = FULL_PRIVILEGES_MASK;
  }

  /**
   * @notice Retrieves globally set of features enabled
   *
   * @dev Auxiliary getter function to maintain compatibility with previous
   *      versions of the Access Control List smart contract, where
   *      features was a separate uint256 public field
   *
   * @return 256-bit bitmask of the features enabled
   */
  function features() public view returns(uint256) {
    // according to new design features are stored in zero address
    // mapping of `userRoles` structure
    return userRoles[address(0)];
  }

  /**
   * @notice Updates set of the globally enabled features (`features`),
   *      taking into account sender's permissions
   *
   * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
   * @dev Function is left for backward compatibility with older versions
   *
   * @param _mask bitmask representing a set of features to enable/disable
   */
  function updateFeatures(uint256 _mask) public {
    // delegate call to `updateRole`
    updateRole(address(0), _mask);
  }

  /**
   * @notice Updates set of permissions (role) for a given user,
   *      taking into account sender's permissions.
   *
   * @dev Setting role to zero is equivalent to removing an all permissions
   * @dev Setting role to `FULL_PRIVILEGES_MASK` is equivalent to
   *      copying senders' permissions (role) to the user
   * @dev Requires transaction sender to have `ROLE_ACCESS_MANAGER` permission
   *
   * @param operator address of a user to alter permissions for or zero
   *      to alter global features of the smart contract
   * @param role bitmask representing a set of permissions to
   *      enable/disable for a user specified
   */
  function updateRole(address operator, uint256 role) public {
    // caller must have a permission to update user roles
    require(isSenderInRole(ROLE_ACCESS_MANAGER), "insufficient privileges (ROLE_ACCESS_MANAGER required)");

    // evaluate the role and reassign it
    userRoles[operator] = evaluateBy(msg.sender, userRoles[operator], role);

    // fire an event
    emit RoleUpdated(msg.sender, operator, role, userRoles[operator]);
  }

  /**
   * @notice Determines the permission bitmask an operator can set on the
   *      target permission set
   * @notice Used to calculate the permission bitmask to be set when requested
   *     in `updateRole` and `updateFeatures` functions
   *
   * @dev Calculated based on:
   *      1) operator's own permission set read from userRoles[operator]
   *      2) target permission set - what is already set on the target
   *      3) desired permission set - what do we want set target to
   *
   * @dev Corner cases:
   *      1) Operator is super admin and its permission set is `FULL_PRIVILEGES_MASK`:
   *        `desired` bitset is returned regardless of the `target` permission set value
   *        (what operator sets is what they get)
   *      2) Operator with no permissions (zero bitset):
   *        `target` bitset is returned regardless of the `desired` value
   *        (operator has no authority and cannot modify anything)
   *
   * @dev Example:
   *      Consider an operator with the permissions bitmask     00001111
   *      is about to modify the target permission set          01010101
   *      Operator wants to set that permission set to          00110011
   *      Based on their role, an operator has the permissions
   *      to update only lowest 4 bits on the target, meaning that
   *      high 4 bits of the target set in this example is left
   *      unchanged and low 4 bits get changed as desired:      01010011
   *
   * @param operator address of the contract operator which is about to set the permissions
   * @param target input set of permissions to operator is going to modify
   * @param desired desired set of permissions operator would like to set
   * @return resulting set of permissions given operator will set
   */
  function evaluateBy(address operator, uint256 target, uint256 desired) public view returns(uint256) {
    // read operator's permissions
    uint256 p = userRoles[operator];

    // taking into account operator's permissions,
    // 1) enable the permissions desired on the `target`
    target |= p & desired;
    // 2) disable the permissions desired on the `target`
    target &= FULL_PRIVILEGES_MASK ^ (p & (FULL_PRIVILEGES_MASK ^ desired));

    // return calculated result
    return target;
  }

  /**
   * @notice Checks if requested set of features is enabled globally on the contract
   *
   * @param required set of features to check against
   * @return true if all the features requested are enabled, false otherwise
   */
  function isFeatureEnabled(uint256 required) public view returns(bool) {
    // delegate call to `__hasRole`, passing `features` property
    return __hasRole(features(), required);
  }

  /**
   * @notice Checks if transaction sender `msg.sender` has all the permissions required
   *
   * @param required set of permissions (role) to check against
   * @return true if all the permissions requested are enabled, false otherwise
   */
  function isSenderInRole(uint256 required) public view returns(bool) {
    // delegate call to `isOperatorInRole`, passing transaction sender
    return isOperatorInRole(msg.sender, required);
  }

  /**
   * @notice Checks if operator has all the permissions (role) required
   *
   * @param operator address of the user to check role for
   * @param required set of permissions (role) to check
   * @return true if all the permissions requested are enabled, false otherwise
   */
  function isOperatorInRole(address operator, uint256 required) public view returns(bool) {
    // delegate call to `__hasRole`, passing operator's permissions (role)
    return __hasRole(userRoles[operator], required);
  }

  /**
   * @dev Checks if role `actual` contains all the permissions required `required`
   *
   * @param actual existent role
   * @param required required role
   * @return true if actual has required role (all permissions), false otherwise
   */
  function __hasRole(uint256 actual, uint256 required) internal pure returns(bool) {
    // check the bitmask for the role required and return the result
    return actual & required == required;
  }
}

/**
 * @title Illuvium (ILV) ERC20 token
 *
 * @notice Illuvium is a core ERC20 token powering the game.
 *      It serves as an in-game currency, is tradable on exchanges,
 *      it powers up the governance protocol (Illuvium DAO) and participates in Yield Farming.
 *
 * @dev Token Summary:
 *      - Symbol: ILV
 *      - Name: Illuvium
 *      - Decimals: 18
 *      - Initial token supply: 7,000,000 ILV
 *      - Maximum final token supply: 10,000,000 ILV
 *          - Up to 3,000,000 ILV may get minted in 3 years period via yield farming
 *      - Mintable: total supply may increase
 *      - Burnable: total supply may decrease
 *
 * @dev Token balances and total supply are effectively 192 bits long, meaning that maximum
 *      possible total supply smart contract is able to track is 2^192 (close to 10^40 tokens)
 *
 * @dev Smart contract doesn't use safe math. All arithmetic operations are overflow/underflow safe.
 *      Additionally, Solidity 0.8.1 enforces overflow/underflow safety.
 *
 * @dev ERC20: reviewed according to https://eips.ethereum.org/EIPS/eip-20
 *
 * @dev ERC20: contract has passed OpenZeppelin ERC20 tests,
 *      see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/token/ERC20/ERC20.behavior.js
 *      see https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/test/token/ERC20/ERC20.test.js
 *      see adopted copies of these tests in the `test` folder
 *
 * @dev ERC223/ERC777: not supported;
 *      send tokens via `safeTransferFrom` and implement `ERC20Receiver.onERC20Received` on the receiver instead
 *
 * @dev Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) - resolved
 *      Related events and functions are marked with "ISBN:978-1-7281-3027-9" tag:
 *        - event Transferred(address indexed _by, address indexed _from, address indexed _to, uint256 _value)
 *        - event Approved(address indexed _owner, address indexed _spender, uint256 _oldValue, uint256 _value)
 *        - function increaseAllowance(address _spender, uint256 _value) public returns (bool)
 *        - function decreaseAllowance(address _spender, uint256 _value) public returns (bool)
 *      See: https://ieeexplore.ieee.org/document/8802438
 *      See: https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
 *
 * @author Basil Gorin
 */
contract IlluviumERC20 is AccessControl {
  /**
   * @dev Smart contract unique identifier, a random number
   * @dev Should be regenerated each time smart contact source code is changed
   *      and changes smart contract itself is to be redeployed
   * @dev Generated using https://www.random.org/bytes/
   */
  uint256 public constant TOKEN_UID = 0x83ecb176af7c4f35a45ff0018282e3a05a1018065da866182df12285866f5a2c;

  /**
   * @notice Name of the token: Illuvium
   *
   * @notice ERC20 name of the token (long name)
   *
   * @dev ERC20 `function name() public view returns (string)`
   *
   * @dev Field is declared public: getter name() is created when compiled,
   *      it returns the name of the token.
   */
  string public constant name = "Illuvium";

  /**
   * @notice Symbol of the token: ILV
   *
   * @notice ERC20 symbol of that token (short name)
   *
   * @dev ERC20 `function symbol() public view returns (string)`
   *
   * @dev Field is declared public: getter symbol() is created when compiled,
   *      it returns the symbol of the token
   */
  string public constant symbol = "ILV";

  /**
   * @notice Decimals of the token: 18
   *
   * @dev ERC20 `function decimals() public view returns (uint8)`
   *
   * @dev Field is declared public: getter decimals() is created when compiled,
   *      it returns the number of decimals used to get its user representation.
   *      For example, if `decimals` equals `6`, a balance of `1,500,000` tokens should
   *      be displayed to a user as `1,5` (`1,500,000 / 10 ** 6`).
   *
   * @dev NOTE: This information is only used for _display_ purposes: it in
   *      no way affects any of the arithmetic of the contract, including balanceOf() and transfer().
   */
  uint8 public constant decimals = 18;

  /**
   * @notice Total supply of the token: initially 7,000,000,
   *      with the potential to grow up to 10,000,000 during yield farming period (3 years)
   *
   * @dev ERC20 `function totalSupply() public view returns (uint256)`
   *
   * @dev Field is declared public: getter totalSupply() is created when compiled,
   *      it returns the amount of tokens in existence.
   */
  uint256 public totalSupply; // is set to 7 million * 10^18 in the constructor

  /**
   * @dev A record of all the token balances
   * @dev This mapping keeps record of all token owners:
   *      owner => balance
   */
  mapping(address => uint256) public tokenBalances;

  /**
   * @notice A record of each account's voting delegate
   *
   * @dev Auxiliary data structure used to sum up an account's voting power
   *
   * @dev This mapping keeps record of all voting power delegations:
   *      voting delegator (token owner) => voting delegate
   */
  mapping(address => address) public votingDelegates;

  /**
   * @notice A voting power record binds voting power of a delegate to a particular
   *      block when the voting power delegation change happened
   */
  struct VotingPowerRecord {
    /*
     * @dev block.number when delegation has changed; starting from
     *      that block voting power value is in effect
     */
    uint64 blockNumber;

    /*
     * @dev cumulative voting power a delegate has obtained starting
     *      from the block stored in blockNumber
     */
    uint192 votingPower;
  }

  /**
   * @notice A record of each account's voting power
   *
   * @dev Primarily data structure to store voting power for each account.
   *      Voting power sums up from the account's token balance and delegated
   *      balances.
   *
   * @dev Stores current value and entire history of its changes.
   *      The changes are stored as an array of checkpoints.
   *      Checkpoint is an auxiliary data structure containing voting
   *      power (number of votes) and block number when the checkpoint is saved
   *
   * @dev Maps voting delegate => voting power record
   */
  mapping(address => VotingPowerRecord[]) public votingPowerHistory;

  /**
   * @dev A record of nonces for signing/validating signatures in `delegateWithSig`
   *      for every delegate, increases after successful validation
   *
   * @dev Maps delegate address => delegate nonce
   */
  mapping(address => uint256) public nonces;

  /**
   * @notice A record of all the allowances to spend tokens on behalf
   * @dev Maps token owner address to an address approved to spend
   *      some tokens on behalf, maps approved address to that amount
   * @dev owner => spender => value
   */
  mapping(address => mapping(address => uint256)) public transferAllowances;

  /**
   * @notice Enables ERC20 transfers of the tokens
   *      (transfer by the token owner himself)
   * @dev Feature FEATURE_TRANSFERS must be enabled in order for
   *      `transfer()` function to succeed
   */
  uint32 public constant FEATURE_TRANSFERS = 0x0000_0001;

  /**
   * @notice Enables ERC20 transfers on behalf
   *      (transfer by someone else on behalf of token owner)
   * @dev Feature FEATURE_TRANSFERS_ON_BEHALF must be enabled in order for
   *      `transferFrom()` function to succeed
   * @dev Token owner must call `approve()` first to authorize
   *      the transfer on behalf
   */
  uint32 public constant FEATURE_TRANSFERS_ON_BEHALF = 0x0000_0002;

  /**
   * @dev Defines if the default behavior of `transfer` and `transferFrom`
   *      checks if the receiver smart contract supports ERC20 tokens
   * @dev When feature FEATURE_UNSAFE_TRANSFERS is enabled the transfers do not
   *      check if the receiver smart contract supports ERC20 tokens,
   *      i.e. `transfer` and `transferFrom` behave like `unsafeTransferFrom`
   * @dev When feature FEATURE_UNSAFE_TRANSFERS is disabled (default) the transfers
   *      check if the receiver smart contract supports ERC20 tokens,
   *      i.e. `transfer` and `transferFrom` behave like `safeTransferFrom`
   */
  uint32 public constant FEATURE_UNSAFE_TRANSFERS = 0x0000_0004;

  /**
   * @notice Enables token owners to burn their own tokens,
   *      including locked tokens which are burnt first
   * @dev Feature FEATURE_OWN_BURNS must be enabled in order for
   *      `burn()` function to succeed when called by token owner
   */
  uint32 public constant FEATURE_OWN_BURNS = 0x0000_0008;

  /**
   * @notice Enables approved operators to burn tokens on behalf of their owners,
   *      including locked tokens which are burnt first
   * @dev Feature FEATURE_OWN_BURNS must be enabled in order for
   *      `burn()` function to succeed when called by approved operator
   */
  uint32 public constant FEATURE_BURNS_ON_BEHALF = 0x0000_0010;

  /**
   * @notice Enables delegators to elect delegates
   * @dev Feature FEATURE_DELEGATIONS must be enabled in order for
   *      `delegate()` function to succeed
   */
  uint32 public constant FEATURE_DELEGATIONS = 0x0000_0020;

  /**
   * @notice Enables delegators to elect delegates on behalf
   *      (via an EIP712 signature)
   * @dev Feature FEATURE_DELEGATIONS must be enabled in order for
   *      `delegateWithSig()` function to succeed
   */
  uint32 public constant FEATURE_DELEGATIONS_ON_BEHALF = 0x0000_0040;

  /**
   * @notice Token creator is responsible for creating (minting)
   *      tokens to an arbitrary address
   * @dev Role ROLE_TOKEN_CREATOR allows minting tokens
   *      (calling `mint` function)
   */
  uint32 public constant ROLE_TOKEN_CREATOR = 0x0001_0000;

  /**
   * @notice Token destroyer is responsible for destroying (burning)
   *      tokens owned by an arbitrary address
   * @dev Role ROLE_TOKEN_DESTROYER allows burning tokens
   *      (calling `burn` function)
   */
  uint32 public constant ROLE_TOKEN_DESTROYER = 0x0002_0000;

  /**
   * @notice ERC20 receivers are allowed to receive tokens without ERC20 safety checks,
   *      which may be useful to simplify tokens transfers into "legacy" smart contracts
   * @dev When `FEATURE_UNSAFE_TRANSFERS` is not enabled addresses having
   *      `ROLE_ERC20_RECEIVER` permission are allowed to receive tokens
   *      via `transfer` and `transferFrom` functions in the same way they
   *      would via `unsafeTransferFrom` function
   * @dev When `FEATURE_UNSAFE_TRANSFERS` is enabled `ROLE_ERC20_RECEIVER` permission
   *      doesn't affect the transfer behaviour since
   *      `transfer` and `transferFrom` behave like `unsafeTransferFrom` for any receiver
   * @dev ROLE_ERC20_RECEIVER is a shortening for ROLE_UNSAFE_ERC20_RECEIVER
   */
  uint32 public constant ROLE_ERC20_RECEIVER = 0x0004_0000;

  /**
   * @notice ERC20 senders are allowed to send tokens without ERC20 safety checks,
   *      which may be useful to simplify tokens transfers into "legacy" smart contracts
   * @dev When `FEATURE_UNSAFE_TRANSFERS` is not enabled senders having
   *      `ROLE_ERC20_SENDER` permission are allowed to send tokens
   *      via `transfer` and `transferFrom` functions in the same way they
   *      would via `unsafeTransferFrom` function
   * @dev When `FEATURE_UNSAFE_TRANSFERS` is enabled `ROLE_ERC20_SENDER` permission
   *      doesn't affect the transfer behaviour since
   *      `transfer` and `transferFrom` behave like `unsafeTransferFrom` for any receiver
   * @dev ROLE_ERC20_SENDER is a shortening for ROLE_UNSAFE_ERC20_SENDER
   */
  uint32 public constant ROLE_ERC20_SENDER = 0x0008_0000;

  /**
   * @dev Magic value to be returned by ERC20Receiver upon successful reception of token(s)
   * @dev Equal to `bytes4(keccak256("onERC20Received(address,address,uint256,bytes)"))`,
   *      which can be also obtained as `ERC20Receiver(address(0)).onERC20Received.selector`
   */
  bytes4 private constant ERC20_RECEIVED = 0x4fc35859;

  /**
   * @notice EIP-712 contract's domain typeHash, see https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash
   */
  bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,uint256 chainId,address verifyingContract)");

  /**
   * @notice EIP-712 delegation struct typeHash, see https://eips.ethereum.org/EIPS/eip-712#rationale-for-typehash
   */
  bytes32 public constant DELEGATION_TYPEHASH = keccak256("Delegation(address delegate,uint256 nonce,uint256 expiry)");

  /**
   * @dev Fired in transfer(), transferFrom() and some other (non-ERC20) functions
   *
   * @dev ERC20 `event Transfer(address indexed _from, address indexed _to, uint256 _value)`
   *
   * @param _from an address tokens were consumed from
   * @param _to an address tokens were sent to
   * @param _value number of tokens transferred
   */
  event Transfer(address indexed _from, address indexed _to, uint256 _value);

  /**
   * @dev Fired in approve() and approveAtomic() functions
   *
   * @dev ERC20 `event Approval(address indexed _owner, address indexed _spender, uint256 _value)`
   *
   * @param _owner an address which granted a permission to transfer
   *      tokens on its behalf
   * @param _spender an address which received a permission to transfer
   *      tokens on behalf of the owner `_owner`
   * @param _value amount of tokens granted to transfer on behalf
   */
  event Approval(address indexed _owner, address indexed _spender, uint256 _value);

  /**
   * @dev Fired in mint() function
   *
   * @param _by an address which minted some tokens (transaction sender)
   * @param _to an address the tokens were minted to
   * @param _value an amount of tokens minted
   */
  event Minted(address indexed _by, address indexed _to, uint256 _value);

  /**
   * @dev Fired in burn() function
   *
   * @param _by an address which burned some tokens (transaction sender)
   * @param _from an address the tokens were burnt from
   * @param _value an amount of tokens burnt
   */
  event Burnt(address indexed _by, address indexed _from, uint256 _value);

  /**
   * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9)
   *
   * @dev Similar to ERC20 Transfer event, but also logs an address which executed transfer
   *
   * @dev Fired in transfer(), transferFrom() and some other (non-ERC20) functions
   *
   * @param _by an address which performed the transfer
   * @param _from an address tokens were consumed from
   * @param _to an address tokens were sent to
   * @param _value number of tokens transferred
   */
  event Transferred(address indexed _by, address indexed _from, address indexed _to, uint256 _value);

  /**
   * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9)
   *
   * @dev Similar to ERC20 Approve event, but also logs old approval value
   *
   * @dev Fired in approve() and approveAtomic() functions
   *
   * @param _owner an address which granted a permission to transfer
   *      tokens on its behalf
   * @param _spender an address which received a permission to transfer
   *      tokens on behalf of the owner `_owner`
   * @param _oldValue previously granted amount of tokens to transfer on behalf
   * @param _value new granted amount of tokens to transfer on behalf
   */
  event Approved(address indexed _owner, address indexed _spender, uint256 _oldValue, uint256 _value);

  /**
   * @dev Notifies that a key-value pair in `votingDelegates` mapping has changed,
   *      i.e. a delegator address has changed its delegate address
   *
   * @param _of delegator address, a token owner
   * @param _from old delegate, an address which delegate right is revoked
   * @param _to new delegate, an address which received the voting power
   */
  event DelegateChanged(address indexed _of, address indexed _from, address indexed _to);

  /**
   * @dev Notifies that a key-value pair in `votingPowerHistory` mapping has changed,
   *      i.e. a delegate's voting power has changed.
   *
   * @param _of delegate whose voting power has changed
   * @param _fromVal previous number of votes delegate had
   * @param _toVal new number of votes delegate has
   */
  event VotingPowerChanged(address indexed _of, uint256 _fromVal, uint256 _toVal);

  /**
   * @dev Deploys the token smart contract,
   *      assigns initial token supply to the address specified
   *
   * @param _initialHolder owner of the initial token supply
   */
  constructor(address _initialHolder) {
    // verify initial holder address non-zero (is set)
    require(_initialHolder != address(0), "_initialHolder not set (zero address)");

    // mint initial supply
    mint(_initialHolder, 7_000_000e18);
  }

  // ===== Start: ERC20/ERC223/ERC777 functions =====

  /**
   * @notice Gets the balance of a particular address
   *
   * @dev ERC20 `function balanceOf(address _owner) public view returns (uint256 balance)`
   *
   * @param _owner the address to query the the balance for
   * @return balance an amount of tokens owned by the address specified
   */
  function balanceOf(address _owner) public view returns (uint256 balance) {
    // read the balance and return
    return tokenBalances[_owner];
  }

  /**
   * @notice Transfers some tokens to an external address or a smart contract
   *
   * @dev ERC20 `function transfer(address _to, uint256 _value) public returns (bool success)`
   *
   * @dev Called by token owner (an address which has a
   *      positive token balance tracked by this smart contract)
   * @dev Throws on any error like
   *      * insufficient token balance or
   *      * incorrect `_to` address:
   *          * zero address or
   *          * self address or
   *          * smart contract which doesn't support ERC20
   *
   * @param _to an address to transfer tokens to,
   *      must be either an external address or a smart contract,
   *      compliant with the ERC20 standard
   * @param _value amount of tokens to be transferred, must
   *      be greater than zero
   * @return success true on success, throws otherwise
   */
  function transfer(address _to, uint256 _value) public returns (bool success) {
    // just delegate call to `transferFrom`,
    // `FEATURE_TRANSFERS` is verified inside it
    return transferFrom(msg.sender, _to, _value);
  }

  /**
   * @notice Transfers some tokens on behalf of address `_from' (token owner)
   *      to some other address `_to`
   *
   * @dev ERC20 `function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)`
   *
   * @dev Called by token owner on his own or approved address,
   *      an address approved earlier by token owner to
   *      transfer some amount of tokens on its behalf
   * @dev Throws on any error like
   *      * insufficient token balance or
   *      * incorrect `_to` address:
   *          * zero address or
   *          * same as `_from` address (self transfer)
   *          * smart contract which doesn't support ERC20
   *
   * @param _from token owner which approved caller (transaction sender)
   *      to transfer `_value` of tokens on its behalf
   * @param _to an address to transfer tokens to,
   *      must be either an external address or a smart contract,
   *      compliant with the ERC20 standard
   * @param _value amount of tokens to be transferred, must
   *      be greater than zero
   * @return success true on success, throws otherwise
   */
  function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
    // depending on `FEATURE_UNSAFE_TRANSFERS` we execute either safe (default)
    // or unsafe transfer
    // if `FEATURE_UNSAFE_TRANSFERS` is enabled
    // or receiver has `ROLE_ERC20_RECEIVER` permission
    // or sender has `ROLE_ERC20_SENDER` permission
    if(isFeatureEnabled(FEATURE_UNSAFE_TRANSFERS)
    || isOperatorInRole(_to, ROLE_ERC20_RECEIVER)
      || isSenderInRole(ROLE_ERC20_SENDER)) {
      // we execute unsafe transfer - delegate call to `unsafeTransferFrom`,
      // `FEATURE_TRANSFERS` is verified inside it
      unsafeTransferFrom(_from, _to, _value);
    }
    // otherwise - if `FEATURE_UNSAFE_TRANSFERS` is disabled
    // and receiver doesn't have `ROLE_ERC20_RECEIVER` permission
    else {
      // we execute safe transfer - delegate call to `safeTransferFrom`, passing empty `_data`,
      // `FEATURE_TRANSFERS` is verified inside it
      safeTransferFrom(_from, _to, _value, "");
    }

    // both `unsafeTransferFrom` and `safeTransferFrom` throw on any error, so
    // if we're here - it means operation successful,
    // just return true
    return true;
  }

  /**
   * @notice Transfers some tokens on behalf of address `_from' (token owner)
   *      to some other address `_to`
   *
   * @dev Inspired by ERC721 safeTransferFrom, this function allows to
   *      send arbitrary data to the receiver on successful token transfer
   * @dev Called by token owner on his own or approved address,
   *      an address approved earlier by token owner to
   *      transfer some amount of tokens on its behalf
   * @dev Throws on any error like
   *      * insufficient token balance or
   *      * incorrect `_to` address:
   *          * zero address or
   *          * same as `_from` address (self transfer)
   *          * smart contract which doesn't support ERC20Receiver interface
   * @dev Returns silently on success, throws otherwise
   *
   * @param _from token owner which approved caller (transaction sender)
   *      to transfer `_value` of tokens on its behalf
   * @param _to an address to transfer tokens to,
   *      must be either an external address or a smart contract,
   *      compliant with the ERC20 standard
   * @param _value amount of tokens to be transferred, must
   *      be greater than zero
   * @param _data [optional] additional data with no specified format,
   *      sent in onERC20Received call to `_to` in case if its a smart contract
   */
  function safeTransferFrom(address _from, address _to, uint256 _value, bytes memory _data) public {
    // first delegate call to `unsafeTransferFrom`
    // to perform the unsafe token(s) transfer
    unsafeTransferFrom(_from, _to, _value);

    // after the successful transfer - check if receiver supports
    // ERC20Receiver and execute a callback handler `onERC20Received`,
    // reverting whole transaction on any error:
    // check if receiver `_to` supports ERC20Receiver interface
    if(AddressUtils.isContract(_to)) {
      // if `_to` is a contract - execute onERC20Received
      bytes4 response = ERC20Receiver(_to).onERC20Received(msg.sender, _from, _value, _data);

      // expected response is ERC20_RECEIVED
      require(response == ERC20_RECEIVED, "invalid onERC20Received response");
    }
  }

  /**
   * @notice Transfers some tokens on behalf of address `_from' (token owner)
   *      to some other address `_to`
   *
   * @dev In contrast to `safeTransferFrom` doesn't check recipient
   *      smart contract to support ERC20 tokens (ERC20Receiver)
   * @dev Designed to be used by developers when the receiver is known
   *      to support ERC20 tokens but doesn't implement ERC20Receiver interface
   * @dev Called by token owner on his own or approved address,
   *      an address approved earlier by token owner to
   *      transfer some amount of tokens on its behalf
   * @dev Throws on any error like
   *      * insufficient token balance or
   *      * incorrect `_to` address:
   *          * zero address or
   *          * same as `_from` address (self transfer)
   * @dev Returns silently on success, throws otherwise
   *
   * @param _from token owner which approved caller (transaction sender)
   *      to transfer `_value` of tokens on its behalf
   * @param _to an address to transfer tokens to,
   *      must be either an external address or a smart contract,
   *      compliant with the ERC20 standard
   * @param _value amount of tokens to be transferred, must
   *      be greater than zero
   */
  function unsafeTransferFrom(address _from, address _to, uint256 _value) public {
    // if `_from` is equal to sender, require transfers feature to be enabled
    // otherwise require transfers on behalf feature to be enabled
    require(_from == msg.sender && isFeatureEnabled(FEATURE_TRANSFERS)
      || _from != msg.sender && isFeatureEnabled(FEATURE_TRANSFERS_ON_BEHALF),
      _from == msg.sender? "transfers are disabled": "transfers on behalf are disabled");

    // non-zero source address check - Zeppelin
    // obviously, zero source address is a client mistake
    // it's not part of ERC20 standard but it's reasonable to fail fast
    // since for zero value transfer transaction succeeds otherwise
    require(_from != address(0), "ERC20: transfer from the zero address"); // Zeppelin msg

    // non-zero recipient address check
    require(_to != address(0), "ERC20: transfer to the zero address"); // Zeppelin msg

    // sender and recipient cannot be the same
    require(_from != _to, "sender and recipient are the same (_from = _to)");

    // sending tokens to the token smart contract itself is a client mistake
    require(_to != address(this), "invalid recipient (transfer to the token smart contract itself)");

    // according to ERC-20 Token Standard, https://eips.ethereum.org/EIPS/eip-20
    // "Transfers of 0 values MUST be treated as normal transfers and fire the Transfer event."
    if(_value == 0) {
      // emit an ERC20 transfer event
      emit Transfer(_from, _to, _value);

      // don't forget to return - we're done
      return;
    }

    // no need to make arithmetic overflow check on the _value - by design of mint()

    // in case of transfer on behalf
    if(_from != msg.sender) {
      // read allowance value - the amount of tokens allowed to transfer - into the stack
      uint256 _allowance = transferAllowances[_from][msg.sender];

      // verify sender has an allowance to transfer amount of tokens requested
      require(_allowance >= _value, "ERC20: transfer amount exceeds allowance"); // Zeppelin msg

      // update allowance value on the stack
      _allowance -= _value;

      // update the allowance value in storage
      transferAllowances[_from][msg.sender] = _allowance;

      // emit an improved atomic approve event
      emit Approved(_from, msg.sender, _allowance + _value, _allowance);

      // emit an ERC20 approval event to reflect the decrease
      emit Approval(_from, msg.sender, _allowance);
    }

    // verify sender has enough tokens to transfer on behalf
    require(tokenBalances[_from] >= _value, "ERC20: transfer amount exceeds balance"); // Zeppelin msg

    // perform the transfer:
    // decrease token owner (sender) balance
    tokenBalances[_from] -= _value;

    // increase `_to` address (receiver) balance
    tokenBalances[_to] += _value;

    // move voting power associated with the tokens transferred
    __moveVotingPower(votingDelegates[_from], votingDelegates[_to], _value);

    // emit an improved transfer event
    emit Transferred(msg.sender, _from, _to, _value);

    // emit an ERC20 transfer event
    emit Transfer(_from, _to, _value);
  }

  /**
   * @notice Approves address called `_spender` to transfer some amount
   *      of tokens on behalf of the owner
   *
   * @dev ERC20 `function approve(address _spender, uint256 _value) public returns (bool success)`
   *
   * @dev Caller must not necessarily own any tokens to grant the permission
   *
   * @param _spender an address approved by the caller (token owner)
   *      to spend some tokens on its behalf
   * @param _value an amount of tokens spender `_spender` is allowed to
   *      transfer on behalf of the token owner
   * @return success true on success, throws otherwise
   */
  function approve(address _spender, uint256 _value) public returns (bool success) {
    // non-zero spender address check - Zeppelin
    // obviously, zero spender address is a client mistake
    // it's not part of ERC20 standard but it's reasonable to fail fast
    require(_spender != address(0), "ERC20: approve to the zero address"); // Zeppelin msg

    // read old approval value to emmit an improved event (ISBN:978-1-7281-3027-9)
    uint256 _oldValue = transferAllowances[msg.sender][_spender];

    // perform an operation: write value requested into the storage
    transferAllowances[msg.sender][_spender] = _value;

    // emit an improved atomic approve event (ISBN:978-1-7281-3027-9)
    emit Approved(msg.sender, _spender, _oldValue, _value);

    // emit an ERC20 approval event
    emit Approval(msg.sender, _spender, _value);

    // operation successful, return true
    return true;
  }

  /**
   * @notice Returns the amount which _spender is still allowed to withdraw from _owner.
   *
   * @dev ERC20 `function allowance(address _owner, address _spender) public view returns (uint256 remaining)`
   *
   * @dev A function to check an amount of tokens owner approved
   *      to transfer on its behalf by some other address called "spender"
   *
   * @param _owner an address which approves transferring some tokens on its behalf
   * @param _spender an address approved to transfer some tokens on behalf
   * @return remaining an amount of tokens approved address `_spender` can transfer on behalf
   *      of token owner `_owner`
   */
  function allowance(address _owner, address _spender) public view returns (uint256 remaining) {
    // read the value from storage and return
    return transferAllowances[_owner][_spender];
  }

  // ===== End: ERC20/ERC223/ERC777 functions =====

  // ===== Start: Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) =====

  /**
   * @notice Increases the allowance granted to `spender` by the transaction sender
   *
   * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9)
   *
   * @dev Throws if value to increase by is zero or too big and causes arithmetic overflow
   *
   * @param _spender an address approved by the caller (token owner)
   *      to spend some tokens on its behalf
   * @param _value an amount of tokens to increase by
   * @return success true on success, throws otherwise
   */
  function increaseAllowance(address _spender, uint256 _value) public virtual returns (bool) {
    // read current allowance value
    uint256 currentVal = transferAllowances[msg.sender][_spender];

    // non-zero _value and arithmetic overflow check on the allowance
    require(currentVal + _value > currentVal, "zero value approval increase or arithmetic overflow");

    // delegate call to `approve` with the new value
    return approve(_spender, currentVal + _value);
  }

  /**
   * @notice Decreases the allowance granted to `spender` by the caller.
   *
   * @dev Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9)
   *
   * @dev Throws if value to decrease by is zero or is bigger than currently allowed value
   *
   * @param _spender an address approved by the caller (token owner)
   *      to spend some tokens on its behalf
   * @param _value an amount of tokens to decrease by
   * @return success true on success, throws otherwise
   */
  function decreaseAllowance(address _spender, uint256 _value) public virtual returns (bool) {
    // read current allowance value
    uint256 currentVal = transferAllowances[msg.sender][_spender];

    // non-zero _value check on the allowance
    require(_value > 0, "zero value approval decrease");

    // verify allowance decrease doesn't underflow
    require(currentVal >= _value, "ERC20: decreased allowance below zero");

    // delegate call to `approve` with the new value
    return approve(_spender, currentVal - _value);
  }

  // ===== End: Resolution for the Multiple Withdrawal Attack on ERC20 Tokens (ISBN:978-1-7281-3027-9) =====

  // ===== Start: Minting/burning extension =====

  /**
   * @dev Mints (creates) some tokens to address specified
   * @dev The value specified is treated as is without taking
   *      into account what `decimals` value is
   * @dev Behaves effectively as `mintTo` function, allowing
   *      to specify an address to mint tokens to
   * @dev Requires sender to have `ROLE_TOKEN_CREATOR` permission
   *
   * @dev Throws on overflow, if totalSupply + _value doesn't fit into uint256
   *
   * @param _to an address to mint tokens to
   * @param _value an amount of tokens to mint (create)
   */
  function mint(address _to, uint256 _value) public {
    // check if caller has sufficient permissions to mint tokens
    require(isSenderInRole(ROLE_TOKEN_CREATOR), "insufficient privileges (ROLE_TOKEN_CREATOR required)");

    // non-zero recipient address check
    require(_to != address(0), "ERC20: mint to the zero address"); // Zeppelin msg

    // non-zero _value and arithmetic overflow check on the total supply
    // this check automatically secures arithmetic overflow on the individual balance
    require(totalSupply + _value > totalSupply, "zero value mint or arithmetic overflow");

    // uint192 overflow check (required by voting delegation)
    require(totalSupply + _value <= type(uint192).max, "total supply overflow (uint192)");

    // perform mint:
    // increase total amount of tokens value
    totalSupply += _value;

    // increase `_to` address balance
    tokenBalances[_to] += _value;

    // create voting power associated with the tokens minted
    __moveVotingPower(address(0), votingDelegates[_to], _value);

    // fire a minted event
    emit Minted(msg.sender, _to, _value);

    // emit an improved transfer event
    emit Transferred(msg.sender, address(0), _to, _value);

    // fire ERC20 compliant transfer event
    emit Transfer(address(0), _to, _value);
  }

  /**
   * @dev Burns (destroys) some tokens from the address specified
   * @dev The value specified is treated as is without taking
   *      into account what `decimals` value is
   * @dev Behaves effectively as `burnFrom` function, allowing
   *      to specify an address to burn tokens from
   * @dev Requires sender to have `ROLE_TOKEN_DESTROYER` permission
   *
   * @param _from an address to burn some tokens from
   * @param _value an amount of tokens to burn (destroy)
   */
  function burn(address _from, uint256 _value) public {
    // check if caller has sufficient permissions to burn tokens
    // and if not - check for possibility to burn own tokens or to burn on behalf
    if(!isSenderInRole(ROLE_TOKEN_DESTROYER)) {
      // if `_from` is equal to sender, require own burns feature to be enabled
      // otherwise require burns on behalf feature to be enabled
      require(_from == msg.sender && isFeatureEnabled(FEATURE_OWN_BURNS)
        || _from != msg.sender && isFeatureEnabled(FEATURE_BURNS_ON_BEHALF),
        _from == msg.sender? "burns are disabled": "burns on behalf are disabled");

      // in case of burn on behalf
      if(_from != msg.sender) {
        // read allowance value - the amount of tokens allowed to be burnt - into the stack
        uint256 _allowance = transferAllowances[_from][msg.sender];

        // verify sender has an allowance to burn amount of tokens requested
        require(_allowance >= _value, "ERC20: burn amount exceeds allowance"); // Zeppelin msg

        // update allowance value on the stack
        _allowance -= _value;

        // update the allowance value in storage
        transferAllowances[_from][msg.sender] = _allowance;

        // emit an improved atomic approve event
        emit Approved(msg.sender, _from, _allowance + _value, _allowance);

        // emit an ERC20 approval event to reflect the decrease
        emit Approval(_from, msg.sender, _allowance);
      }
    }

    // at this point we know that either sender is ROLE_TOKEN_DESTROYER or
    // we burn own tokens or on behalf (in latest case we already checked and updated allowances)
    // we have left to execute balance checks and burning logic itself

    // non-zero burn value check
    require(_value != 0, "zero value burn");

    // non-zero source address check - Zeppelin
    require(_from != address(0), "ERC20: burn from the zero address"); // Zeppelin msg

    // verify `_from` address has enough tokens to destroy
    // (basically this is a arithmetic overflow check)
    require(tokenBalances[_from] >= _value, "ERC20: burn amount exceeds balance"); // Zeppelin msg

    // perform burn:
    // decrease `_from` address balance
    tokenBalances[_from] -= _value;

    // decrease total amount of tokens value
    totalSupply -= _value;

    // destroy voting power associated with the tokens burnt
    __moveVotingPower(votingDelegates[_from], address(0), _value);

    // fire a burnt event
    emit Burnt(msg.sender, _from, _value);

    // emit an improved transfer event
    emit Transferred(msg.sender, _from, address(0), _value);

    // fire ERC20 compliant transfer event
    emit Transfer(_from, address(0), _value);
  }

  // ===== End: Minting/burning extension =====

  // ===== Start: DAO Support (Compound-like voting delegation) =====

  /**
   * @notice Gets current voting power of the account `_of`
   * @param _of the address of account to get voting power of
   * @return current cumulative voting power of the account,
   *      sum of token balances of all its voting delegators
   */
  function getVotingPower(address _of) public view returns (uint256) {
    // get a link to an array of voting power history records for an address specified
    VotingPowerRecord[] storage history = votingPowerHistory[_of];

    // lookup the history and return latest element
    return history.length == 0? 0: history[history.length - 1].votingPower;
  }

  /**
   * @notice Gets past voting power of the account `_of` at some block `_blockNum`
   * @dev Throws if `_blockNum` is not in the past (not the finalized block)
   * @param _of the address of account to get voting power of
   * @param _blockNum block number to get the voting power at
   * @return past cumulative voting power of the account,
   *      sum of token balances of all its voting delegators at block number `_blockNum`
   */
  function getVotingPowerAt(address _of, uint256 _blockNum) public view returns (uint256) {
    // make sure block number is not in the past (not the finalized block)
    require(_blockNum < block.number, "not yet determined"); // Compound msg

    // get a link to an array of voting power history records for an address specified
    VotingPowerRecord[] storage history = votingPowerHistory[_of];

    // if voting power history for the account provided is empty
    if(history.length == 0) {
      // than voting power is zero - return the result
      return 0;
    }

    // check latest voting power history record block number:
    // if history was not updated after the block of interest
    if(history[history.length - 1].blockNumber <= _blockNum) {
      // we're done - return last voting power record
      return getVotingPower(_of);
    }

    // check first voting power history record block number:
    // if history was never updated before the block of interest
    if(history[0].blockNumber > _blockNum) {
      // we're done - voting power at the block num of interest was zero
      return 0;
    }

    // `votingPowerHistory[_of]` is an array ordered by `blockNumber`, ascending;
    // apply binary search on `votingPowerHistory[_of]` to find such an entry number `i`, that
    // `votingPowerHistory[_of][i].blockNumber <= _blockNum`, but in the same time
    // `votingPowerHistory[_of][i + 1].blockNumber > _blockNum`
    // return the result - voting power found at index `i`
    return history[__binaryLookup(_of, _blockNum)].votingPower;
  }

  /**
   * @dev Reads an entire voting power history array for the delegate specified
   *
   * @param _of delegate to query voting power history for
   * @return voting power history array for the delegate of interest
   */
  function getVotingPowerHistory(address _of) public view returns(VotingPowerRecord[] memory) {
    // return an entire array as memory
    return votingPowerHistory[_of];
  }

  /**
   * @dev Returns length of the voting power history array for the delegate specified;
   *      useful since reading an entire array just to get its length is expensive (gas cost)
   *
   * @param _of delegate to query voting power history length for
   * @return voting power history array length for the delegate of interest
   */
  function getVotingPowerHistoryLength(address _of) public view returns(uint256) {
    // read array length and return
    return votingPowerHistory[_of].length;
  }

  /**
   * @notice Delegates voting power of the delegator `msg.sender` to the delegate `_to`
   *
   * @dev Accepts zero value address to delegate voting power to, effectively
   *      removing the delegate in that case
   *
   * @param _to address to delegate voting power to
   */
  function delegate(address _to) public {
    // verify delegations are enabled
    require(isFeatureEnabled(FEATURE_DELEGATIONS), "delegations are disabled");
    // delegate call to `__delegate`
    __delegate(msg.sender, _to);
  }

  /**
   * @notice Delegates voting power of the delegator (represented by its signature) to the delegate `_to`
   *
   * @dev Accepts zero value address to delegate voting power to, effectively
   *      removing the delegate in that case
   *
   * @dev Compliant with EIP-712: Ethereum typed structured data hashing and signing,
   *      see https://eips.ethereum.org/EIPS/eip-712
   *
   * @param _to address to delegate voting power to
   * @param _nonce nonce used to construct the signature, and used to validate it;
   *      nonce is increased by one after successful signature validation and vote delegation
   * @param _exp signature expiration time
   * @param v the recovery byte of the signature
   * @param r half of the ECDSA signature pair
   * @param s half of the ECDSA signature pair
   */
  function delegateWithSig(address _to, uint256 _nonce, uint256 _exp, uint8 v, bytes32 r, bytes32 s) public {
    // verify delegations on behalf are enabled
    require(isFeatureEnabled(FEATURE_DELEGATIONS_ON_BEHALF), "delegations on behalf are disabled");

    // build the EIP-712 contract domain separator
    bytes32 domainSeparator = keccak256(abi.encode(DOMAIN_TYPEHASH, keccak256(bytes(name)), block.chainid, address(this)));

    // build the EIP-712 hashStruct of the delegation message
    bytes32 hashStruct = keccak256(abi.encode(DELEGATION_TYPEHASH, _to, _nonce, _exp));

    // calculate the EIP-712 digest "\x19\x01" ‖ domainSeparator ‖ hashStruct(message)
    bytes32 digest = keccak256(abi.encodePacked("\x19\x01", domainSeparator, hashStruct));

    // recover the address who signed the message with v, r, s
    address signer = ecrecover(digest, v, r, s);

    // perform message integrity and security validations
    require(signer != address(0), "invalid signature"); // Compound msg
    require(_nonce == nonces[signer], "invalid nonce"); // Compound msg
    require(block.timestamp < _exp, "signature expired"); // Compound msg

    // update the nonce for that particular signer to avoid replay attack
    nonces[signer]++;

    // delegate call to `__delegate` - execute the logic required
    __delegate(signer, _to);
  }

  /**
   * @dev Auxiliary function to delegate delegator's `_from` voting power to the delegate `_to`
   * @dev Writes to `votingDelegates` and `votingPowerHistory` mappings
   *
   * @param _from delegator who delegates his voting power
   * @param _to delegate who receives the voting power
   */
  function __delegate(address _from, address _to) private {
    // read current delegate to be replaced by a new one
    address _fromDelegate = votingDelegates[_from];

    // read current voting power (it is equal to token balance)
    uint256 _value = tokenBalances[_from];

    // reassign voting delegate to `_to`
    votingDelegates[_from] = _to;

    // update voting power for `_fromDelegate` and `_to`
    __moveVotingPower(_fromDelegate, _to, _value);

    // emit an event
    emit DelegateChanged(_from, _fromDelegate, _to);
  }

  /**
   * @dev Auxiliary function to move voting power `_value`
   *      from delegate `_from` to the delegate `_to`
   *
   * @dev Doesn't have any effect if `_from == _to`, or if `_value == 0`
   *
   * @param _from delegate to move voting power from
   * @param _to delegate to move voting power to
   * @param _value voting power to move from `_from` to `_to`
   */
  function __moveVotingPower(address _from, address _to, uint256 _value) private {
    // if there is no move (`_from == _to`) or there is nothing to move (`_value == 0`)
    if(_from == _to || _value == 0) {
      // return silently with no action
      return;
    }

    // if source address is not zero - decrease its voting power
    if(_from != address(0)) {
      // read current source address voting power
      uint256 _fromVal = getVotingPower(_from);

      // calculate decreased voting power
      // underflow is not possible by design:
      // voting power is limited by token balance which is checked by the callee
      uint256 _toVal = _fromVal - _value;

      // update source voting power from `_fromVal` to `_toVal`
      __updateVotingPower(_from, _fromVal, _toVal);
    }

    // if destination address is not zero - increase its voting power
    if(_to != address(0)) {
      // read current destination address voting power
      uint256 _fromVal = getVotingPower(_to);

      // calculate increased voting power
      // overflow is not possible by design:
      // max token supply limits the cumulative voting power
      uint256 _toVal = _fromVal + _value;

      // update destination voting power from `_fromVal` to `_toVal`
      __updateVotingPower(_to, _fromVal, _toVal);
    }
  }

  /**
   * @dev Auxiliary function to update voting power of the delegate `_of`
   *      from value `_fromVal` to value `_toVal`
   *
   * @param _of delegate to update its voting power
   * @param _fromVal old voting power of the delegate
   * @param _toVal new voting power of the delegate
   */
  function __updateVotingPower(address _of, uint256 _fromVal, uint256 _toVal) private {
    // get a link to an array of voting power history records for an address specified
    VotingPowerRecord[] storage history = votingPowerHistory[_of];

    // if there is an existing voting power value stored for current block
    if(history.length != 0 && history[history.length - 1].blockNumber == block.number) {
      // update voting power which is already stored in the current block
      history[history.length - 1].votingPower = uint192(_toVal);
    }
    // otherwise - if there is no value stored for current block
    else {
      // add new element into array representing the value for current block
      history.push(VotingPowerRecord(uint64(block.number), uint192(_toVal)));
    }

    // emit an event
    emit VotingPowerChanged(_of, _fromVal, _toVal);
  }

  /**
   * @dev Auxiliary function to lookup an element in a sorted (asc) array of elements
   *
   * @dev This function finds the closest element in an array to the value
   *      of interest (not exceeding that value) and returns its index within an array
   *
   * @dev An array to search in is `votingPowerHistory[_to][i].blockNumber`,
   *      it is sorted in ascending order (blockNumber increases)
   *
   * @param _to an address of the delegate to get an array for
   * @param n value of interest to look for
   * @return an index of the closest element in an array to the value
   *      of interest (not exceeding that value)
   */
  function __binaryLookup(address _to, uint256 n) private view returns(uint256) {
    // get a link to an array of voting power history records for an address specified
    VotingPowerRecord[] storage history = votingPowerHistory[_to];

    // left bound of the search interval, originally start of the array
    uint256 i = 0;

    // right bound of the search interval, originally end of the array
    uint256 j = history.length - 1;

    // the iteration process narrows down the bounds by
    // splitting the interval in a half oce per each iteration
    while(j > i) {
      // get an index in the middle of the interval [i, j]
      uint256 k = j - (j - i) / 2;

      // read an element to compare it with the value of interest
      VotingPowerRecord memory cp = history[k];

      // if we've got a strict equal - we're lucky and done
      if(cp.blockNumber == n) {
        // just return the result - index `k`
        return k;
      }
      // if the value of interest is bigger - move left bound to the middle
      else if (cp.blockNumber < n) {
        // move left bound `i` to the middle position `k`
        i = k;
      }
      // otherwise, when the value of interest is smaller - move right bound to the middle
      else {
        // move right bound `j` to the middle position `k - 1`:
        // element at position `k` is bigger and cannot be the result
        j = k - 1;
      }
    }

    // reaching that point means no exact match found
    // since we're interested in the element which is not bigger than the
    // element of interest, we return the lower bound `i`
    return i;
  }
}

// ===== End: DAO Support (Compound-like voting delegation) =====
/**
 * @title Illuvium Aware
 *
 * @notice Helper smart contract to be inherited by other smart contracts requiring to
 *      be linked to verified IlluviumERC20 instance and performing some basic tasks on it
 *
 * @author Basil Gorin
 */
abstract contract IlluviumAware is ILinkedToILV {
  /// @dev Link to ILV ERC20 Token IlluviumERC20 instance
  address public immutable override ilv;

  /**
   * @dev Creates IlluviumAware instance, requiring to supply deployed IlluviumERC20 instance address
   *
   * @param _ilv deployed IlluviumERC20 instance address
   */
  constructor(address _ilv) {
    // verify ILV address is set and is correct
    require(_ilv != address(0), "ILV address not set");
    require(IlluviumERC20(_ilv).TOKEN_UID() == 0x83ecb176af7c4f35a45ff0018282e3a05a1018065da866182df12285866f5a2c, "unexpected TOKEN_UID");

    // write ILV address
    ilv = _ilv;
  }

  /**
   * @dev Executes IlluviumERC20.safeTransferFrom(address(this), _to, _value, "")
   *      on the bound IlluviumERC20 instance
   *
   * @dev Reentrancy safe due to the IlluviumERC20 design
   */
  function transferIlv(address _to, uint256 _value) internal {
    // just delegate call to the target
    transferIlvFrom(address(this), _to, _value);
  }

  /**
   * @dev Executes IlluviumERC20.transferFrom(_from, _to, _value)
   *      on the bound IlluviumERC20 instance
   *
   * @dev Reentrancy safe due to the IlluviumERC20 design
   */
  function transferIlvFrom(address _from, address _to, uint256 _value) internal {
    // just delegate call to the target
    IlluviumERC20(ilv).transferFrom(_from, _to, _value);
  }

  /**
   * @dev Executes IlluviumERC20.mint(_to, _values)
   *      on the bound IlluviumERC20 instance
   *
   * @dev Reentrancy safe due to the IlluviumERC20 design
   */
  function mintIlv(address _to, uint256 _value) internal {
    // just delegate call to the target
    IlluviumERC20(ilv).mint(_to, _value);
  }

}

/**
 * @title Illuvium Pool Base
 *
 * @notice An abstract contract containing common logic for any pool,
 *      be it a flash pool (temporary pool like SNX) or a core pool (permanent pool like ILV/ETH or ILV pool)
 *
 * @dev Deployment and initialization.
 *      Any pool deployed must be bound to the deployed pool factory (IlluviumPoolFactory)
 *      Additionally, 3 token instance addresses must be defined on deployment:
 *          - ILV token address
 *          - sILV token address, used to mint sILV rewards
 *          - pool token address, it can be ILV token address, ILV/ETH pair address, and others
 *
 * @dev Pool weight defines the fraction of the yield current pool receives among the other pools,
 *      pool factory is responsible for the weight synchronization between the pools.
 * @dev The weight is logically 10% for ILV pool and 90% for ILV/ETH pool.
 *      Since Solidity doesn't support fractions the weight is defined by the division of
 *      pool weight by total pools weight (sum of all registered pools within the factory)
 * @dev For ILV Pool we use 100 as weight and for ILV/ETH pool - 900.
 *
 * @author Pedro Bergamini, reviewed by Basil Gorin
 */
abstract contract IlluviumPoolBase is IPool, IlluviumAware, ReentrancyGuard {
  /// @dev Data structure representing token holder using a pool
  struct User {
    // @dev Total staked amount
    uint256 tokenAmount;
    // @dev Total weight
    uint256 totalWeight;
    // @dev Auxiliary variable for yield calculation
    uint256 subYieldRewards;
    // @dev Auxiliary variable for vault rewards calculation
    uint256 subVaultRewards;
    // @dev An array of holder's deposits
    Deposit[] deposits;
  }

  /// @dev Token holder storage, maps token holder address to their data record
  mapping(address => User) public users;

  /// @dev Link to sILV ERC20 Token EscrowedIlluviumERC20 instance
  address public immutable override silv;

  /// @dev Link to the pool factory IlluviumPoolFactory instance
  IlluviumPoolFactory public immutable factory;

  /// @dev Link to the pool token instance, for example ILV or ILV/ETH pair
  address public immutable override poolToken;

  /// @dev Pool weight, 100 for ILV pool or 900 for ILV/ETH
  uint32 public override weight;

  /// @dev Block number of the last yield distribution event
  uint64 public override lastYieldDistribution;

  /// @dev Used to calculate yield rewards
  /// @dev This value is different from "reward per token" used in locked pool
  /// @dev Note: stakes are different in duration and "weight" reflects that
  uint256 public override yieldRewardsPerWeight;

  /// @dev Used to calculate yield rewards, keeps track of the tokens weight locked in staking
  uint256 public override usersLockingWeight;

  /**
   * @dev Stake weight is proportional to deposit amount and time locked, precisely
   *      "deposit amount wei multiplied by (fraction of the year locked plus one)"
   * @dev To avoid significant precision loss due to multiplication by "fraction of the year" [0, 1],
   *      weight is stored multiplied by 1e6 constant, as an integer
   * @dev Corner case 1: if time locked is zero, weight is deposit amount multiplied by 1e6
   * @dev Corner case 2: if time locked is one year, fraction of the year locked is one, and
   *      weight is a deposit amount multiplied by 2 * 1e6
   */
  uint256 internal constant WEIGHT_MULTIPLIER = 1e6;

  /**
   * @dev When we know beforehand that staking is done for a year, and fraction of the year locked is one,
   *      we use simplified calculation and use the following constant instead previos one
   */
  uint256 internal constant YEAR_STAKE_WEIGHT_MULTIPLIER = 2 * WEIGHT_MULTIPLIER;

  /**
   * @dev Rewards per weight are stored multiplied by 1e12, as integers.
   */
  uint256 internal constant REWARD_PER_WEIGHT_MULTIPLIER = 1e12;

  /**
   * @dev Fired in _stake() and stake()
   *
   * @param _by an address which performed an operation, usually token holder
   * @param _from token holder address, the tokens will be returned to that address
   * @param amount amount of tokens staked
   */
  event Staked(address indexed _by, address indexed _from, uint256 amount);

  /**
   * @dev Fired in _updateStakeLock() and updateStakeLock()
   *
   * @param _by an address which performed an operation
   * @param depositId updated deposit ID
   * @param lockedFrom deposit locked from value
   * @param lockedUntil updated deposit locked until value
   */
  event StakeLockUpdated(address indexed _by, uint256 depositId, uint64 lockedFrom, uint64 lockedUntil);

  /**
   * @dev Fired in _unstake() and unstake()
   *
   * @param _by an address which performed an operation, usually token holder
   * @param _to an address which received the unstaked tokens, usually token holder
   * @param amount amount of tokens unstaked
   */
  event Unstaked(address indexed _by, address indexed _to, uint256 amount);

  /**
   * @dev Fired in _sync(), sync() and dependent functions (stake, unstake, etc.)
   *
   * @param _by an address which performed an operation
   * @param yieldRewardsPerWeight updated yield rewards per weight value
   * @param lastYieldDistribution usually, current block number
   */
  event Synchronized(address indexed _by, uint256 yieldRewardsPerWeight, uint64 lastYieldDistribution);

  /**
   * @dev Fired in _processRewards(), processRewards() and dependent functions (stake, unstake, etc.)
   *
   * @param _by an address which performed an operation
   * @param _to an address which claimed the yield reward
   * @param sIlv flag indicating if reward was paid (minted) in sILV
   * @param amount amount of yield paid
   */
  event YieldClaimed(address indexed _by, address indexed _to, bool sIlv, uint256 amount);

  /**
   * @dev Fired in setWeight()
   *
   * @param _by an address which performed an operation, always a factory
   * @param _fromVal old pool weight value
   * @param _toVal new pool weight value
   */
  event PoolWeightUpdated(address indexed _by, uint32 _fromVal, uint32 _toVal);

  /**
   * @dev Overridden in sub-contracts to construct the pool
   *
   * @param _ilv ILV ERC20 Token IlluviumERC20 address
   * @param _silv sILV ERC20 Token EscrowedIlluviumERC20 address
   * @param _factory Pool factory IlluviumPoolFactory instance/address
   * @param _poolToken token the pool operates on, for example ILV or ILV/ETH pair
   * @param _initBlock initial block used to calculate the rewards
   *      note: _initBlock can be set to the future effectively meaning _sync() calls will do nothing
   * @param _weight number representing a weight of the pool, actual weight fraction
   *      is calculated as that number divided by the total pools weight and doesn't exceed one
   */
  constructor(
    address _ilv,
    address _silv,
    IlluviumPoolFactory _factory,
    address _poolToken,
    uint64 _initBlock,
    uint32 _weight
  ) IlluviumAware(_ilv) {
    // verify the inputs are set
    require(_silv != address(0), "sILV address not set");
    require(address(_factory) != address(0), "ILV Pool fct address not set");
    require(_poolToken != address(0), "pool token address not set");
    require(_initBlock > 0, "init block not set");
    require(_weight > 0, "pool weight not set");

    // verify sILV instance supplied
    require(
      EscrowedIlluviumERC20(_silv).TOKEN_UID() ==
      0xac3051b8d4f50966afb632468a4f61483ae6a953b74e387a01ef94316d6b7d62,
      "unexpected sILV TOKEN_UID"
    );
    // verify IlluviumPoolFactory instance supplied
    require(
      _factory.FACTORY_UID() == 0xc5cfd88c6e4d7e5c8a03c255f03af23c0918d8e82cac196f57466af3fd4a5ec7,
      "unexpected FACTORY_UID"
    );

    // save the inputs into internal state variables
    silv = _silv;
    factory = _factory;
    poolToken = _poolToken;
    weight = _weight;

    // init the dependent internal state variables
    lastYieldDistribution = _initBlock;
  }

  /**
   * @notice Calculates current yield rewards value available for address specified
   *
   * @param _staker an address to calculate yield rewards value for
   * @return calculated yield reward value for the given address
   */
  function pendingYieldRewards(address _staker) external view override returns (uint256) {
    // `newYieldRewardsPerWeight` will store stored or recalculated value for `yieldRewardsPerWeight`
    uint256 newYieldRewardsPerWeight;

    // if smart contract state was not updated recently, `yieldRewardsPerWeight` value
    // is outdated and we need to recalculate it in order to calculate pending rewards correctly
    if (blockNumber() > lastYieldDistribution && usersLockingWeight != 0) {
      uint256 endBlock = factory.endBlock();
      uint256 multiplier =
      blockNumber() > endBlock ? endBlock - lastYieldDistribution : blockNumber() - lastYieldDistribution;
      uint256 ilvRewards = (multiplier * weight * factory.ilvPerBlock()) / factory.totalWeight();

      // recalculated value for `yieldRewardsPerWeight`
      newYieldRewardsPerWeight = rewardToWeight(ilvRewards, usersLockingWeight) + yieldRewardsPerWeight;
    } else {
      // if smart contract state is up to date, we don't recalculate
      newYieldRewardsPerWeight = yieldRewardsPerWeight;
    }

    // based on the rewards per weight value, calculate pending rewards;
    User memory user = users[_staker];
    uint256 pending = weightToReward(user.totalWeight, newYieldRewardsPerWeight) - user.subYieldRewards;

    return pending;
  }

  /**
   * @notice Returns total staked token balance for the given address
   *
   * @param _user an address to query balance for
   * @return total staked token balance
   */
  function balanceOf(address _user) external view override returns (uint256) {
    // read specified user token amount and return
    return users[_user].tokenAmount;
  }

  /**
   * @notice Returns information on the given deposit for the given address
   *
   * @dev See getDepositsLength
   *
   * @param _user an address to query deposit for
   * @param _depositId zero-indexed deposit ID for the address specified
   * @return deposit info as Deposit structure
   */
  function getDeposit(address _user, uint256 _depositId) external view override returns (Deposit memory) {
    // read deposit at specified index and return
    return users[_user].deposits[_depositId];
  }

  /**
   * @notice Returns number of deposits for the given address. Allows iteration over deposits.
   *
   * @dev See getDeposit
   *
   * @param _user an address to query deposit length for
   * @return number of deposits for the given address
   */
  function getDepositsLength(address _user) external view override returns (uint256) {
    // read deposits array length and return
    return users[_user].deposits.length;
  }

  /**
   * @notice Stakes specified amount of tokens for the specified amount of time,
   *      and pays pending yield rewards if any
   *
   * @dev Requires amount to stake to be greater than zero
   *
   * @param _amount amount of tokens to stake
   * @param _lockUntil stake period as unix timestamp; zero means no locking
   * @param _useSILV a flag indicating if previous reward to be paid as sILV
   */
  function stake(
    uint256 _amount,
    uint64 _lockUntil,
    bool _useSILV
  ) external override {
    // delegate call to an internal function
    _stake(msg.sender, _amount, _lockUntil, _useSILV, false);
  }

  /**
   * @notice Unstakes specified amount of tokens, and pays pending yield rewards if any
   *
   * @dev Requires amount to unstake to be greater than zero
   *
   * @param _depositId deposit ID to unstake from, zero-indexed
   * @param _amount amount of tokens to unstake
   * @param _useSILV a flag indicating if reward to be paid as sILV
   */
  function unstake(
    uint256 _depositId,
    uint256 _amount,
    bool _useSILV
  ) external override {
    // delegate call to an internal function
    _unstake(msg.sender, _depositId, _amount, _useSILV);
  }

  /**
   * @notice Extends locking period for a given deposit
   *
   * @dev Requires new lockedUntil value to be:
   *      higher than the current one, and
   *      in the future, but
   *      no more than 1 year in the future
   *
   * @param depositId updated deposit ID
   * @param lockedUntil updated deposit locked until value
   * @param useSILV used for _processRewards check if it should use ILV or sILV
   */
  function updateStakeLock(
    uint256 depositId,
    uint64 lockedUntil,
    bool useSILV
  ) external {
    // sync and call processRewards
    _sync();
    _processRewards(msg.sender, useSILV, false);
    // delegate call to an internal function
    _updateStakeLock(msg.sender, depositId, lockedUntil);
  }

  /**
   * @notice Service function to synchronize pool state with current time
   *
   * @dev Can be executed by anyone at any time, but has an effect only when
   *      at least one block passes between synchronizations
   * @dev Executed internally when staking, unstaking, processing rewards in order
   *      for calculations to be correct and to reflect state progress of the contract
   * @dev When timing conditions are not met (executed too frequently, or after factory
   *      end block), function doesn't throw and exits silently
   */
  function sync() external override {
    // delegate call to an internal function
    _sync();
  }

  /**
   * @notice Service function to calculate and pay pending yield rewards to the sender
   *
   * @dev Can be executed by anyone at any time, but has an effect only when
   *      executed by deposit holder and when at least one block passes from the
   *      previous reward processing
   * @dev Executed internally when staking and unstaking, executes sync() under the hood
   *      before making further calculations and payouts
   * @dev When timing conditions are not met (executed too frequently, or after factory
   *      end block), function doesn't throw and exits silently
   *
   * @param _useSILV flag indicating whether to mint sILV token as a reward or not;
   *      when set to true - sILV reward is minted immediately and sent to sender,
   *      when set to false - new ILV reward deposit gets created if pool is an ILV pool
   *      (poolToken is ILV token), or new pool deposit gets created together with sILV minted
   *      when pool is not an ILV pool (poolToken is not an ILV token)
   */
  function processRewards(bool _useSILV) external virtual override {
    // delegate call to an internal function
    _processRewards(msg.sender, _useSILV, true);
  }

  /**
   * @dev Executed by the factory to modify pool weight; the factory is expected
   *      to keep track of the total pools weight when updating
   *
   * @dev Set weight to zero to disable the pool
   *
   * @param _weight new weight to set for the pool
   */
  function setWeight(uint32 _weight) external override {
    // verify function is executed by the factory
    require(msg.sender == address(factory), "access denied");

    // emit an event logging old and new weight values
    emit PoolWeightUpdated(msg.sender, weight, _weight);

    // set the new weight value
    weight = _weight;
  }

  /**
   * @dev Similar to public pendingYieldRewards, but performs calculations based on
   *      current smart contract state only, not taking into account any additional
   *      time/blocks which might have passed
   *
   * @param _staker an address to calculate yield rewards value for
   * @return pending calculated yield reward value for the given address
   */
  function _pendingYieldRewards(address _staker) internal view returns (uint256 pending) {
    // read user data structure into memory
    User memory user = users[_staker];

    // and perform the calculation using the values read
    return weightToReward(user.totalWeight, yieldRewardsPerWeight) - user.subYieldRewards;
  }

  /**
   * @dev Used internally, mostly by children implementations, see stake()
   *
   * @param _staker an address which stakes tokens and which will receive them back
   * @param _amount amount of tokens to stake
   * @param _lockUntil stake period as unix timestamp; zero means no locking
   * @param _useSILV a flag indicating if previous reward to be paid as sILV
   * @param _isYield a flag indicating if that stake is created to store yield reward
   *      from the previously unstaked stake
   */
  function _stake(
    address _staker,
    uint256 _amount,
    uint64 _lockUntil,
    bool _useSILV,
    bool _isYield
  ) internal virtual {
    // validate the inputs
    require(_amount > 0, "zero amount");
    require(
      _lockUntil == 0 || (_lockUntil > now256() && _lockUntil - now256() <= 365 days),
      "invalid lock interval"
    );

    // update smart contract state
    _sync();

    // get a link to user data struct, we will write to it later
    User storage user = users[_staker];
    // process current pending rewards if any
    if (user.tokenAmount > 0) {
      _processRewards(_staker, _useSILV, false);
    }

    // in most of the cases added amount `addedAmount` is simply `_amount`
    // however for deflationary tokens this can be different

    // read the current balance
    uint256 previousBalance = IERC20(poolToken).balanceOf(address(this));
    // transfer `_amount`; note: some tokens may get burnt here
    transferPoolTokenFrom(address(msg.sender), address(this), _amount);
    // read new balance, usually this is just the difference `previousBalance - _amount`
    uint256 newBalance = IERC20(poolToken).balanceOf(address(this));
    // calculate real amount taking into account deflation
    uint256 addedAmount = newBalance - previousBalance;

    // set the `lockFrom` and `lockUntil` taking into account that
    // zero value for `_lockUntil` means "no locking" and leads to zero values
    // for both `lockFrom` and `lockUntil`
    uint64 lockFrom = _lockUntil > 0 ? uint64(now256()) : 0;
    uint64 lockUntil = _lockUntil;

    // stake weight formula rewards for locking
    uint256 stakeWeight =
    (((lockUntil - lockFrom) * WEIGHT_MULTIPLIER) / 365 days + WEIGHT_MULTIPLIER) * addedAmount;

    // makes sure stakeWeight is valid
    assert(stakeWeight > 0);

    // create and save the deposit (append it to deposits array)
    Deposit memory deposit =
    Deposit({
    tokenAmount: addedAmount,
    weight: stakeWeight,
    lockedFrom: lockFrom,
    lockedUntil: lockUntil,
    isYield: _isYield
    });
    // deposit ID is an index of the deposit in `deposits` array
    user.deposits.push(deposit);

    // update user record
    user.tokenAmount += addedAmount;
    user.totalWeight += stakeWeight;
    user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);

    // update global variable
    usersLockingWeight += stakeWeight;

    // emit an event
    emit Staked(msg.sender, _staker, _amount);
  }

  /**
   * @dev Used internally, mostly by children implementations, see unstake()
   *
   * @param _staker an address which unstakes tokens (which previously staked them)
   * @param _depositId deposit ID to unstake from, zero-indexed
   * @param _amount amount of tokens to unstake
   * @param _useSILV a flag indicating if reward to be paid as sILV
   */
  function _unstake(
    address _staker,
    uint256 _depositId,
    uint256 _amount,
    bool _useSILV
  ) internal virtual {
    // verify an amount is set
    require(_amount > 0, "zero amount");

    // get a link to user data struct, we will write to it later
    User storage user = users[_staker];
    // get a link to the corresponding deposit, we may write to it later
    Deposit storage stakeDeposit = user.deposits[_depositId];
    // deposit structure may get deleted, so we save isYield flag to be able to use it
    bool isYield = stakeDeposit.isYield;

    // verify available balance
    // if staker address ot deposit doesn't exist this check will fail as well
    require(stakeDeposit.tokenAmount >= _amount, "amount exceeds stake");

    // update smart contract state
    _sync();
    // and process current pending rewards if any
    _processRewards(_staker, _useSILV, false);

    // recalculate deposit weight
    uint256 previousWeight = stakeDeposit.weight;
    uint256 newWeight =
    (((stakeDeposit.lockedUntil - stakeDeposit.lockedFrom) * WEIGHT_MULTIPLIER) /
    365 days +
    WEIGHT_MULTIPLIER) * (stakeDeposit.tokenAmount - _amount);

    // update the deposit, or delete it if its depleted
    if (stakeDeposit.tokenAmount - _amount == 0) {
      delete user.deposits[_depositId];
    } else {
      stakeDeposit.tokenAmount -= _amount;
      stakeDeposit.weight = newWeight;
    }

    // update user record
    user.tokenAmount -= _amount;
    user.totalWeight = user.totalWeight - previousWeight + newWeight;
    user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);

    // update global variable
    usersLockingWeight = usersLockingWeight - previousWeight + newWeight;

    // if the deposit was created by the pool itself as a yield reward
    if (isYield) {
      // mint the yield via the factory
      factory.mintYieldTo(msg.sender, _amount);
    } else {
      // otherwise just return tokens back to holder
      transferPoolToken(msg.sender, _amount);
    }

    // emit an event
    emit Unstaked(msg.sender, _staker, _amount);
  }

  /**
   * @dev Used internally, mostly by children implementations, see sync()
   *
   * @dev Updates smart contract state (`yieldRewardsPerWeight`, `lastYieldDistribution`),
   *      updates factory state via `updateILVPerBlock`
   */
  function _sync() internal virtual {
    // update ILV per block value in factory if required
    if (factory.shouldUpdateRatio()) {
      factory.updateILVPerBlock();
    }

    // check bound conditions and if these are not met -
    // exit silently, without emitting an event
    uint256 endBlock = factory.endBlock();
    if (lastYieldDistribution >= endBlock) {
      return;
    }
    if (blockNumber() <= lastYieldDistribution) {
      return;
    }
    // if locking weight is zero - update only `lastYieldDistribution` and exit
    if (usersLockingWeight == 0) {
      lastYieldDistribution = uint64(blockNumber());
      return;
    }

    // to calculate the reward we need to know how many blocks passed, and reward per block
    uint256 currentBlock = blockNumber() > endBlock ? endBlock : blockNumber();
    uint256 blocksPassed = currentBlock - lastYieldDistribution;
    uint256 ilvPerBlock = factory.ilvPerBlock();

    // calculate the reward
    uint256 ilvReward = (blocksPassed * ilvPerBlock * weight) / factory.totalWeight();

    // update rewards per weight and `lastYieldDistribution`
    yieldRewardsPerWeight += rewardToWeight(ilvReward, usersLockingWeight);
    lastYieldDistribution = uint64(currentBlock);

    // emit an event
    emit Synchronized(msg.sender, yieldRewardsPerWeight, lastYieldDistribution);
  }

  /**
   * @dev Used internally, mostly by children implementations, see processRewards()
   *
   * @param _staker an address which receives the reward (which has staked some tokens earlier)
   * @param _useSILV flag indicating whether to mint sILV token as a reward or not, see processRewards()
   * @param _withUpdate flag allowing to disable synchronization (see sync()) if set to false
   * @return pendingYield the rewards calculated and optionally re-staked
   */
  function _processRewards(
    address _staker,
    bool _useSILV,
    bool _withUpdate
  ) internal virtual returns (uint256 pendingYield) {
    // update smart contract state if required
    if (_withUpdate) {
      _sync();
    }

    // calculate pending yield rewards, this value will be returned
    pendingYield = _pendingYieldRewards(_staker);

    // if pending yield is zero - just return silently
    if (pendingYield == 0) return 0;

    // get link to a user data structure, we will write into it later
    User storage user = users[_staker];

    // if sILV is requested
    if (_useSILV) {
      // - mint sILV
      mintSIlv(_staker, pendingYield);
    } else if (poolToken == ilv) {
      // calculate pending yield weight,
      // 2e6 is the bonus weight when staking for 1 year
      uint256 depositWeight = pendingYield * YEAR_STAKE_WEIGHT_MULTIPLIER;

      // if the pool is ILV Pool - create new ILV deposit
      // and save it - push it into deposits array
      Deposit memory newDeposit =
      Deposit({
      tokenAmount: pendingYield,
      lockedFrom: uint64(now256()),
      lockedUntil: uint64(now256() + 365 days), // staking yield for 1 year
      weight: depositWeight,
      isYield: true
      });
      user.deposits.push(newDeposit);

      // update user record
      user.tokenAmount += pendingYield;
      user.totalWeight += depositWeight;

      // update global variable
      usersLockingWeight += depositWeight;
    } else {
      // for other pools - stake as pool
      address ilvPool = factory.getPoolAddress(ilv);
      ICorePool(ilvPool).stakeAsPool(_staker, pendingYield);
    }

    // update users's record for `subYieldRewards` if requested
    if (_withUpdate) {
      user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);
    }

    // emit an event
    emit YieldClaimed(msg.sender, _staker, _useSILV, pendingYield);
  }

  /**
   * @dev See updateStakeLock()
   *
   * @param _staker an address to update stake lock
   * @param _depositId updated deposit ID
   * @param _lockedUntil updated deposit locked until value
   */
  function _updateStakeLock(
    address _staker,
    uint256 _depositId,
    uint64 _lockedUntil
  ) internal {
    // validate the input time
    require(_lockedUntil > now256(), "lock should be in the future");

    // get a link to user data struct, we will write to it later
    User storage user = users[_staker];
    // get a link to the corresponding deposit, we may write to it later
    Deposit storage stakeDeposit = user.deposits[_depositId];

    // validate the input against deposit structure
    require(_lockedUntil > stakeDeposit.lockedUntil, "invalid new lock");

    // verify locked from and locked until values
    if (stakeDeposit.lockedFrom == 0) {
      require(_lockedUntil - now256() <= 365 days, "max lock period is 365 days");
      stakeDeposit.lockedFrom = uint64(now256());
    } else {
      require(_lockedUntil - stakeDeposit.lockedFrom <= 365 days, "max lock period is 365 days");
    }

    // update locked until value, calculate new weight
    stakeDeposit.lockedUntil = _lockedUntil;
    uint256 newWeight =
    (((stakeDeposit.lockedUntil - stakeDeposit.lockedFrom) * WEIGHT_MULTIPLIER) /
    365 days +
    WEIGHT_MULTIPLIER) * stakeDeposit.tokenAmount;

    // save previous weight
    uint256 previousWeight = stakeDeposit.weight;
    // update weight
    stakeDeposit.weight = newWeight;

    // update user total weight and global locking weight
    user.totalWeight = user.totalWeight - previousWeight + newWeight;
    usersLockingWeight = usersLockingWeight - previousWeight + newWeight;

    // emit an event
    emit StakeLockUpdated(_staker, _depositId, stakeDeposit.lockedFrom, _lockedUntil);
  }

  /**
   * @dev Converts stake weight (not to be mixed with the pool weight) to
   *      ILV reward value, applying the 10^12 division on weight
   *
   * @param _weight stake weight
   * @param rewardPerWeight ILV reward per weight
   * @return reward value normalized to 10^12
   */
  function weightToReward(uint256 _weight, uint256 rewardPerWeight) public pure returns (uint256) {
    // apply the formula and return
    return (_weight * rewardPerWeight) / REWARD_PER_WEIGHT_MULTIPLIER;
  }

  /**
   * @dev Converts reward ILV value to stake weight (not to be mixed with the pool weight),
   *      applying the 10^12 multiplication on the reward
   *      - OR -
   * @dev Converts reward ILV value to reward/weight if stake weight is supplied as second
   *      function parameter instead of reward/weight
   *
   * @param reward yield reward
   * @param rewardPerWeight reward/weight (or stake weight)
   * @return stake weight (or reward/weight)
   */
  function rewardToWeight(uint256 reward, uint256 rewardPerWeight) public pure returns (uint256) {
    // apply the reverse formula and return
    return (reward * REWARD_PER_WEIGHT_MULTIPLIER) / rewardPerWeight;
  }

  /**
   * @dev Testing time-dependent functionality is difficult and the best way of
   *      doing it is to override block number in helper test smart contracts
   *
   * @return `block.number` in mainnet, custom values in testnets (if overridden)
   */
  function blockNumber() public view virtual returns (uint256) {
    // return current block number
    return block.number;
  }

  /**
   * @dev Testing time-dependent functionality is difficult and the best way of
   *      doing it is to override time in helper test smart contracts
   *
   * @return `block.timestamp` in mainnet, custom values in testnets (if overridden)
   */
  function now256() public view virtual returns (uint256) {
    // return current block timestamp
    return block.timestamp;
  }

  /**
   * @dev Executes EscrowedIlluviumERC20.mint(_to, _values)
   *      on the bound EscrowedIlluviumERC20 instance
   *
   * @dev Reentrancy safe due to the EscrowedIlluviumERC20 design
   */
  function mintSIlv(address _to, uint256 _value) private {
    // just delegate call to the target
    EscrowedIlluviumERC20(silv).mint(_to, _value);
  }

  /**
   * @dev Executes SafeERC20.safeTransfer on a pool token
   *
   * @dev Reentrancy safety enforced via `ReentrancyGuard.nonReentrant`
   */
  function transferPoolToken(address _to, uint256 _value) internal nonReentrant {
    // just delegate call to the target
    SafeERC20.safeTransfer(IERC20(poolToken), _to, _value);
  }

  /**
   * @dev Executes SafeERC20.safeTransferFrom on a pool token
   *
   * @dev Reentrancy safety enforced via `ReentrancyGuard.nonReentrant`
   */
  function transferPoolTokenFrom(
    address _from,
    address _to,
    uint256 _value
  ) internal nonReentrant {
    // just delegate call to the target
    SafeERC20.safeTransferFrom(IERC20(poolToken), _from, _to, _value);
  }
}

/**
 * @title Illuvium Core Pool
 *
 * @notice Core pools represent permanent pools like ILV or ILV/ETH Pair pool,
 *      core pools allow staking for arbitrary periods of time up to 1 year
 *
 * @dev See IlluviumPoolBase for more details
 *
 * @author Pedro Bergamini, reviewed by Basil Gorin
 */
contract IlluviumCorePool is IlluviumPoolBase {
  /// @dev Flag indicating pool type, false means "core pool"
  bool public constant override isFlashPool = false;

  /// @dev Link to deployed IlluviumVault instance
  address public vault;

  /// @dev Used to calculate vault rewards
  /// @dev This value is different from "reward per token" used in locked pool
  /// @dev Note: stakes are different in duration and "weight" reflects that
  uint256 public vaultRewardsPerWeight;

  /// @dev Pool tokens value available in the pool;
  ///      pool token examples are ILV (ILV core pool) or ILV/ETH pair (LP core pool)
  /// @dev For LP core pool this value doesnt' count for ILV tokens received as Vault rewards
  ///      while for ILV core pool it does count for such tokens as well
  uint256 public poolTokenReserve;

  /**
   * @dev Fired in receiveVaultRewards()
   *
   * @param _by an address that sent the rewards, always a vault
   * @param amount amount of tokens received
   */
  event VaultRewardsReceived(address indexed _by, uint256 amount);

  /**
   * @dev Fired in _processVaultRewards() and dependent functions, like processRewards()
   *
   * @param _by an address which executed the function
   * @param _to an address which received a reward
   * @param amount amount of reward received
   */
  event VaultRewardsClaimed(address indexed _by, address indexed _to, uint256 amount);

  /**
   * @dev Fired in setVault()
   *
   * @param _by an address which executed the function, always a factory owner
   */
  event VaultUpdated(address indexed _by, address _fromVal, address _toVal);

  /**
   * @dev Creates/deploys an instance of the core pool
   *
   * @param _ilv ILV ERC20 Token IlluviumERC20 address
   * @param _silv sILV ERC20 Token EscrowedIlluviumERC20 address
   * @param _factory Pool factory IlluviumPoolFactory instance/address
   * @param _poolToken token the pool operates on, for example ILV or ILV/ETH pair
   * @param _initBlock initial block used to calculate the rewards
   * @param _weight number representing a weight of the pool, actual weight fraction
   *      is calculated as that number divided by the total pools weight and doesn't exceed one
   */
  constructor(
    address _ilv,
    address _silv,
    IlluviumPoolFactory _factory,
    address _poolToken,
    uint64 _initBlock,
    uint32 _weight
  ) IlluviumPoolBase(_ilv, _silv, _factory, _poolToken, _initBlock, _weight) {}

  /**
   * @notice Calculates current vault rewards value available for address specified
   *
   * @dev Performs calculations based on current smart contract state only,
   *      not taking into account any additional time/blocks which might have passed
   *
   * @param _staker an address to calculate vault rewards value for
   * @return pending calculated vault reward value for the given address
   */
  function pendingVaultRewards(address _staker) public view returns (uint256 pending) {
    User memory user = users[_staker];

    return weightToReward(user.totalWeight, vaultRewardsPerWeight) - user.subVaultRewards;
  }

  /**
   * @dev Executed only by the factory owner to Set the vault
   *
   * @param _vault an address of deployed IlluviumVault instance
   */
  function setVault(address _vault) external {
    // verify function is executed by the factory owner
    require(factory.owner() == msg.sender, "access denied");

    // verify input is set
    require(_vault != address(0), "zero input");

    // emit an event
    emit VaultUpdated(msg.sender, vault, _vault);

    // update vault address
    vault = _vault;
  }

  /**
   * @dev Executed by the vault to transfer vault rewards ILV from the vault
   *      into the pool
   *
   * @dev This function is executed only for ILV core pools
   *
   * @param _rewardsAmount amount of ILV rewards to transfer into the pool
   */
  function receiveVaultRewards(uint256 _rewardsAmount) external {
    require(msg.sender == vault, "access denied");
    // return silently if there is no reward to receive
    if (_rewardsAmount == 0) {
      return;
    }
    require(usersLockingWeight > 0, "zero locking weight");

    transferIlvFrom(msg.sender, address(this), _rewardsAmount);

    vaultRewardsPerWeight += rewardToWeight(_rewardsAmount, usersLockingWeight);

    // update `poolTokenReserve` only if this is a ILV Core Pool
    if (poolToken == ilv) {
      poolTokenReserve += _rewardsAmount;
    }

    emit VaultRewardsReceived(msg.sender, _rewardsAmount);
  }

  /**
   * @notice Service function to calculate and pay pending vault and yield rewards to the sender
   *
   * @dev Internally executes similar function `_processRewards` from the parent smart contract
   *      to calculate and pay yield rewards; adds vault rewards processing
   *
   * @dev Can be executed by anyone at any time, but has an effect only when
   *      executed by deposit holder and when at least one block passes from the
   *      previous reward processing
   * @dev Executed internally when "staking as a pool" (`stakeAsPool`)
   * @dev When timing conditions are not met (executed too frequently, or after factory
   *      end block), function doesn't throw and exits silently
   *
   * @dev _useSILV flag has a context of yield rewards only
   *
   * @param _useSILV flag indicating whether to mint sILV token as a reward or not;
   *      when set to true - sILV reward is minted immediately and sent to sender,
   *      when set to false - new ILV reward deposit gets created if pool is an ILV pool
   *      (poolToken is ILV token), or new pool deposit gets created together with sILV minted
   *      when pool is not an ILV pool (poolToken is not an ILV token)
   */
  function processRewards(bool _useSILV) external override {
    _processRewards(msg.sender, _useSILV, true);
  }

  /**
   * @dev Executed internally by the pool itself (from the parent `IlluviumPoolBase` smart contract)
   *      as part of yield rewards processing logic (`IlluviumPoolBase._processRewards` function)
   * @dev Executed when _useSILV is false and pool is not an ILV pool - see `IlluviumPoolBase._processRewards`
   *
   * @param _staker an address which stakes (the yield reward)
   * @param _amount amount to be staked (yield reward amount)
   */
  function stakeAsPool(address _staker, uint256 _amount) external {
    require(factory.poolExists(msg.sender), "access denied");
    _sync();
    User storage user = users[_staker];
    if (user.tokenAmount > 0) {
      _processRewards(_staker, true, false);
    }
    uint256 depositWeight = _amount * YEAR_STAKE_WEIGHT_MULTIPLIER;
    Deposit memory newDeposit =
    Deposit({
    tokenAmount: _amount,
    lockedFrom: uint64(now256()),
    lockedUntil: uint64(now256() + 365 days),
    weight: depositWeight,
    isYield: true
    });
    user.tokenAmount += _amount;
    user.totalWeight += depositWeight;
    user.deposits.push(newDeposit);

    usersLockingWeight += depositWeight;

    user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);
    user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);

    // update `poolTokenReserve` only if this is a LP Core Pool (stakeAsPool can be executed only for LP pool)
    poolTokenReserve += _amount;
  }

  /**
   * @inheritdoc IlluviumPoolBase
   *
   * @dev Additionally to the parent smart contract, updates vault rewards of the holder,
   *      and updates (increases) pool token reserve (pool tokens value available in the pool)
   */
  function _stake(
    address _staker,
    uint256 _amount,
    uint64 _lockedUntil,
    bool _useSILV,
    bool _isYield
  ) internal override {
    super._stake(_staker, _amount, _lockedUntil, _useSILV, _isYield);
    User storage user = users[_staker];
    user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);

    poolTokenReserve += _amount;
  }

  /**
   * @inheritdoc IlluviumPoolBase
   *
   * @dev Additionally to the parent smart contract, updates vault rewards of the holder,
   *      and updates (decreases) pool token reserve (pool tokens value available in the pool)
   */
  function _unstake(
    address _staker,
    uint256 _depositId,
    uint256 _amount,
    bool _useSILV
  ) internal override {
    User storage user = users[_staker];
    Deposit memory stakeDeposit = user.deposits[_depositId];
    require(stakeDeposit.lockedFrom == 0 || now256() > stakeDeposit.lockedUntil, "deposit not yet unlocked");
    poolTokenReserve -= _amount;
    super._unstake(_staker, _depositId, _amount, _useSILV);
    user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);
  }

  /**
   * @inheritdoc IlluviumPoolBase
   *
   * @dev Additionally to the parent smart contract, processes vault rewards of the holder,
   *      and for ILV pool updates (increases) pool token reserve (pool tokens value available in the pool)
   */
  function _processRewards(
    address _staker,
    bool _useSILV,
    bool _withUpdate
  ) internal override returns (uint256 pendingYield) {
    _processVaultRewards(_staker);
    pendingYield = super._processRewards(_staker, _useSILV, _withUpdate);

    // update `poolTokenReserve` only if this is a ILV Core Pool
    if (poolToken == ilv && !_useSILV) {
      poolTokenReserve += pendingYield;
    }
  }

  /**
   * @dev Used internally to process vault rewards for the staker
   *
   * @param _staker address of the user (staker) to process rewards for
   */
  function _processVaultRewards(address _staker) private {
    User storage user = users[_staker];
    uint256 pendingVaultClaim = pendingVaultRewards(_staker);
    if (pendingVaultClaim == 0) return;
    // read ILV token balance of the pool via standard ERC20 interface
    uint256 ilvBalance = IERC20(ilv).balanceOf(address(this));
    require(ilvBalance >= pendingVaultClaim, "contract ILV balance too low");

    // update `poolTokenReserve` only if this is a ILV Core Pool
    if (poolToken == ilv) {
      // protects against rounding errors
      poolTokenReserve -= pendingVaultClaim > poolTokenReserve ? poolTokenReserve : pendingVaultClaim;
    }

    user.subVaultRewards = weightToReward(user.totalWeight, vaultRewardsPerWeight);

    // transfer fails if pool ILV balance is not enough - which is a desired behavior
    transferIlv(_staker, pendingVaultClaim);

    emit VaultRewardsClaimed(msg.sender, _staker, pendingVaultClaim);
  }
}

/**
 * @title Illuvium Pool Factory
 *
 * @notice ILV Pool Factory manages Illuvium Yield farming pools, provides a single
 *      public interface to access the pools, provides an interface for the pools
 *      to mint yield rewards, access pool-related info, update weights, etc.
 *
 * @notice The factory is authorized (via its owner) to register new pools, change weights
 *      of the existing pools, removing the pools (by changing their weights to zero)
 *
 * @dev The factory requires ROLE_TOKEN_CREATOR permission on the ILV token to mint yield
 *      (see `mintYieldTo` function)
 *
 * @author Pedro Bergamini, reviewed by Basil Gorin
 */
contract IlluviumPoolFactory is Ownable, IlluviumAware {
  /**
   * @dev Smart contract unique identifier, a random number
   * @dev Should be regenerated each time smart contact source code is changed
   *      and changes smart contract itself is to be redeployed
   * @dev Generated using https://www.random.org/bytes/
   */
  uint256 public constant FACTORY_UID = 0xc5cfd88c6e4d7e5c8a03c255f03af23c0918d8e82cac196f57466af3fd4a5ec7;

  /// @dev Auxiliary data structure used only in getPoolData() view function
  struct PoolData {
    // @dev pool token address (like ILV)
    address poolToken;
    // @dev pool address (like deployed core pool instance)
    address poolAddress;
    // @dev pool weight (200 for ILV pools, 800 for ILV/ETH pools - set during deployment)
    uint32 weight;
    // @dev flash pool flag
    bool isFlashPool;
  }

  /**
   * @dev ILV/block determines yield farming reward base
   *      used by the yield pools controlled by the factory
   */
  uint192 public ilvPerBlock;

  /**
   * @dev The yield is distributed proportionally to pool weights;
   *      total weight is here to help in determining the proportion
   */
  uint32 public totalWeight;

  /**
   * @dev ILV/block decreases by 3% every blocks/update (set to 91252 blocks during deployment);
   *      an update is triggered by executing `updateILVPerBlock` public function
   */
  uint32 public immutable blocksPerUpdate;

  /**
   * @dev End block is the last block when ILV/block can be decreased;
   *      it is implied that yield farming stops after that block
   */
  uint32 public endBlock;

  /**
   * @dev Each time the ILV/block ratio gets updated, the block number
   *      when the operation has occurred gets recorded into `lastRatioUpdate`
   * @dev This block number is then used to check if blocks/update `blocksPerUpdate`
   *      has passed when decreasing yield reward by 3%
   */
  uint32 public lastRatioUpdate;

  /// @dev sILV token address is used to create ILV core pool(s)
  address public immutable silv;

  /// @dev Maps pool token address (like ILV) -> pool address (like core pool instance)
  mapping(address => address) public pools;

  /// @dev Keeps track of registered pool addresses, maps pool address -> exists flag
  mapping(address => bool) public poolExists;

  /**
   * @dev Fired in createPool() and registerPool()
   *
   * @param _by an address which executed an action
   * @param poolToken pool token address (like ILV)
   * @param poolAddress deployed pool instance address
   * @param weight pool weight
   * @param isFlashPool flag indicating if pool is a flash pool
   */
  event PoolRegistered(
    address indexed _by,
    address indexed poolToken,
    address indexed poolAddress,
    uint64 weight,
    bool isFlashPool
  );

  /**
   * @dev Fired in changePoolWeight()
   *
   * @param _by an address which executed an action
   * @param poolAddress deployed pool instance address
   * @param weight new pool weight
   */
  event WeightUpdated(address indexed _by, address indexed poolAddress, uint32 weight);

  /**
   * @dev Fired in updateILVPerBlock()
   *
   * @param _by an address which executed an action
   * @param newIlvPerBlock new ILV/block value
   */
  event IlvRatioUpdated(address indexed _by, uint256 newIlvPerBlock);

  /**
   * @dev Creates/deploys a factory instance
   *
   * @param _ilv ILV ERC20 token address
   * @param _silv sILV ERC20 token address
   * @param _ilvPerBlock initial ILV/block value for rewards
   * @param _blocksPerUpdate how frequently the rewards gets updated (decreased by 3%), blocks
   * @param _initBlock block number to measure _blocksPerUpdate from
   * @param _endBlock block number when farming stops and rewards cannot be updated anymore
   */
  constructor(
    address _ilv,
    address _silv,
    uint192 _ilvPerBlock,
    uint32 _blocksPerUpdate,
    uint32 _initBlock,
    uint32 _endBlock
  ) IlluviumAware(_ilv) {
    // verify the inputs are set
    require(_silv != address(0), "sILV address not set");
    require(_ilvPerBlock > 0, "ILV/block not set");
    require(_blocksPerUpdate > 0, "blocks/update not set");
    require(_initBlock > 0, "init block not set");
    require(_endBlock > _initBlock, "invalid end block: must be greater than init block");

    // verify sILV instance supplied
    require(
      EscrowedIlluviumERC20(_silv).TOKEN_UID() ==
      0xac3051b8d4f50966afb632468a4f61483ae6a953b74e387a01ef94316d6b7d62,
      "unexpected sILV TOKEN_UID"
    );

    // save the inputs into internal state variables
    silv = _silv;
    ilvPerBlock = _ilvPerBlock;
    blocksPerUpdate = _blocksPerUpdate;
    lastRatioUpdate = _initBlock;
    endBlock = _endBlock;
  }

  /**
   * @notice Given a pool token retrieves corresponding pool address
   *
   * @dev A shortcut for `pools` mapping
   *
   * @param poolToken pool token address (like ILV) to query pool address for
   * @return pool address for the token specified
   */
  function getPoolAddress(address poolToken) external view returns (address) {
    // read the mapping and return
    return pools[poolToken];
  }

  /**
   * @notice Reads pool information for the pool defined by its pool token address,
   *      designed to simplify integration with the front ends
   *
   * @param _poolToken pool token address to query pool information for
   * @return pool information packed in a PoolData struct
   */
  function getPoolData(address _poolToken) public view returns (PoolData memory) {
    // get the pool address from the mapping
    address poolAddr = pools[_poolToken];

    // throw if there is no pool registered for the token specified
    require(poolAddr != address(0), "pool not found");

    // read pool information from the pool smart contract
    // via the pool interface (IPool)
    address poolToken = IPool(poolAddr).poolToken();
    bool isFlashPool = IPool(poolAddr).isFlashPool();
    uint32 weight = IPool(poolAddr).weight();

    // create the in-memory structure and return it
    return PoolData({ poolToken: poolToken, poolAddress: poolAddr, weight: weight, isFlashPool: isFlashPool });
  }

  /**
   * @dev Verifies if `blocksPerUpdate` has passed since last ILV/block
   *      ratio update and if ILV/block reward can be decreased by 3%
   *
   * @return true if enough time has passed and `updateILVPerBlock` can be executed
   */
  function shouldUpdateRatio() public view returns (bool) {
    // if yield farming period has ended
    if (blockNumber() > endBlock) {
      // ILV/block reward cannot be updated anymore
      return false;
    }

    // check if blocks/update (91252 blocks) have passed since last update
    return blockNumber() >= lastRatioUpdate + blocksPerUpdate;
  }

  /**
   * @dev Creates a core pool (IlluviumCorePool) and registers it within the factory
   *
   * @dev Can be executed by the pool factory owner only
   *
   * @param poolToken pool token address (like ILV, or ILV/ETH pair)
   * @param initBlock init block to be used for the pool created
   * @param weight weight of the pool to be created
   */
  function createPool(
    address poolToken,
    uint64 initBlock,
    uint32 weight
  ) external virtual onlyOwner {
    // create/deploy new core pool instance
    IPool pool = new IlluviumCorePool(ilv, silv, this, poolToken, initBlock, weight);

    // register it within a factory
    registerPool(address(pool));
  }

  /**
   * @dev Registers an already deployed pool instance within the factory
   *
   * @dev Can be executed by the pool factory owner only
   *
   * @param poolAddr address of the already deployed pool instance
   */
  function registerPool(address poolAddr) public onlyOwner {
    // read pool information from the pool smart contract
    // via the pool interface (IPool)
    address poolToken = IPool(poolAddr).poolToken();
    bool isFlashPool = IPool(poolAddr).isFlashPool();
    uint32 weight = IPool(poolAddr).weight();

    // ensure that the pool is not already registered within the factory
    require(pools[poolToken] == address(0), "this pool is already registered");

    // create pool structure, register it within the factory
    pools[poolToken] = poolAddr;
    poolExists[poolAddr] = true;
    // update total pool weight of the factory
    totalWeight += weight;

    // emit an event
    emit PoolRegistered(msg.sender, poolToken, poolAddr, weight, isFlashPool);
  }

  /**
   * @notice Decreases ILV/block reward by 3%, can be executed
   *      no more than once per `blocksPerUpdate` blocks
   */
  function updateILVPerBlock() external {
    // checks if ratio can be updated i.e. if blocks/update (91252 blocks) have passed
    require(shouldUpdateRatio(), "too frequent");

    // decreases ILV/block reward by 3%
    ilvPerBlock = (ilvPerBlock * 97) / 100;

    // set current block as the last ratio update block
    lastRatioUpdate = uint32(blockNumber());

    // emit an event
    emit IlvRatioUpdated(msg.sender, ilvPerBlock);
  }

  /**
   * @dev Mints ILV tokens; executed by ILV Pool only
   *
   * @dev Requires factory to have ROLE_TOKEN_CREATOR permission
   *      on the ILV ERC20 token instance
   *
   * @param _to an address to mint tokens to
   * @param _amount amount of ILV tokens to mint
   */
  function mintYieldTo(address _to, uint256 _amount) external {
    // verify that sender is a pool registered withing the factory
    require(poolExists[msg.sender], "access denied");

    // mint ILV tokens as required
    mintIlv(_to, _amount);
  }

  /**
   * @dev Changes the weight of the pool;
   *      executed by the pool itself or by the factory owner
   *
   * @param poolAddr address of the pool to change weight for
   * @param weight new weight value to set to
   */
  function changePoolWeight(address poolAddr, uint32 weight) external {
    // verify function is executed either by factory owner or by the pool itself
    require(msg.sender == owner() || poolExists[msg.sender]);

    // recalculate total weight
    totalWeight = totalWeight + weight - IPool(poolAddr).weight();

    // set the new pool weight
    IPool(poolAddr).setWeight(weight);

    // emit an event
    emit WeightUpdated(msg.sender, poolAddr, weight);
  }

  /**
   * @dev Testing time-dependent functionality is difficult and the best way of
   *      doing it is to override block number in helper test smart contracts
   *
   * @return `block.number` in mainnet, custom values in testnets (if overridden)
   */
  function blockNumber() public view virtual returns (uint256) {
    // return current block number
    return block.number;
  }
}

/**
 * @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);
}

/**
 * @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;
    // solhint-disable-next-line no-inline-assembly
    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");

    // solhint-disable-next-line avoid-low-level-calls, avoid-call-value
    (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");

    // solhint-disable-next-line avoid-low-level-calls
    (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");

    // solhint-disable-next-line avoid-low-level-calls
    (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.3._
   */
  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.3._
   */
  function functionDelegateCall(
    address target,
    bytes memory data,
    string memory errorMessage
  ) internal returns (bytes memory) {
    require(isContract(target), "Address: delegate call to non-contract");

    // solhint-disable-next-line avoid-low-level-calls
    (bool success, bytes memory returndata) = target.delegatecall(data);
    return _verifyCallResult(success, returndata, errorMessage);
  }

  function _verifyCallResult(
    bool success,
    bytes memory returndata,
    string memory errorMessage
  ) private 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

        // solhint-disable-next-line no-inline-assembly
        assembly {
          let returndata_size := mload(returndata)
          revert(add(32, returndata), returndata_size)
        }
      } else {
        revert(errorMessage);
      }
    }
  }
}

/**
 * @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'
    // solhint-disable-next-line max-line-length
    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 {
    uint256 newAllowance = token.allowance(address(this), spender) - 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
      // solhint-disable-next-line max-line-length
      require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }
  }
}

/**
 * @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 guidelines: functions revert instead
 * of 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}.
 */

// Copied from Open Zeppelin

contract ERC20 is IERC20 {
  mapping(address => uint256) private _balances;

  mapping(address => mapping(address => uint256)) private _allowances;

  uint256 private _totalSupply;

  string private _name;
  string private _symbol;
  uint8 private _decimals;

  /**
   * @notice Token creator is responsible for creating (minting)
   *      tokens to an arbitrary address
   * @dev Role ROLE_TOKEN_CREATOR allows minting tokens
   *      (calling `mint` function)
   */
  uint32 public constant ROLE_TOKEN_CREATOR = 0x0001_0000;

  /**
   * @dev Sets the values for {name} and {symbol}, initializes {decimals} with
   * a default value of 18.
   *
   * To select a different value for {decimals}, use {_setupDecimals}.
   *
   * All three of these values are immutable: they can only be set once during
   * construction.
   */
  constructor(string memory name_, string memory symbol_) {
    _name = name_;
    _symbol = symbol_;
    _decimals = 18;
  }

  /**
   * @dev Returns the name of the token.
   */
  function name() public view virtual returns (string memory) {
    return _name;
  }

  /**
   * @dev Returns the symbol of the token, usually a shorter version of the
   * name.
   */
  function symbol() public view virtual 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 {_setupDecimals} is
   * called.
   *
   * 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 returns (uint8) {
    return _decimals;
  }

  /**
   * @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(msg.sender, 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(msg.sender, 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);
    _approve(sender, msg.sender, _allowances[sender][msg.sender] - 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(msg.sender, spender, _allowances[msg.sender][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) {
    _approve(msg.sender, spender, _allowances[msg.sender][spender] - subtractedValue);
    return true;
  }

  /**
   * @dev Moves tokens `amount` from `sender` to `recipient`.
   *
   * This is 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);

    _balances[sender] = _balances[sender] - amount;
    _balances[recipient] = _balances[recipient] + amount;
    emit Transfer(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:
   *
   * - `to` 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 = _totalSupply + amount;
    _balances[account] = _balances[account] + amount;
    emit Transfer(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);

    _balances[account] = _balances[account] - amount;
    _totalSupply = _totalSupply - amount;
    emit Transfer(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 Sets {decimals} to a value other than the default one of 18.
   *
   * WARNING: This function should only be called from the constructor. Most
   * applications that interact with token contracts will not expect
   * {decimals} to ever change, and may work incorrectly if it does.
   */
  function _setupDecimals(uint8 decimals_) internal virtual {
    _decimals = decimals_;
  }

  /**
   * @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 to 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 {}
}

contract EscrowedIlluviumERC20 is ERC20("Escrowed Illuvium", "sILV"), AccessControl {
  /**
   * @dev Smart contract unique identifier, a random number
   * @dev Should be regenerated each time smart contact source code is changed
   *      and changes smart contract itself is to be redeployed
   * @dev Generated using https://www.random.org/bytes/
   */
  uint256 public constant TOKEN_UID = 0xac3051b8d4f50966afb632468a4f61483ae6a953b74e387a01ef94316d6b7d62;

  /**
   * @notice Must be called by ROLE_TOKEN_CREATOR addresses.
   *
   * @param recipient address to receive the tokens.
   * @param amount number of tokens to be minted.
   */
  function mint(address recipient, uint256 amount) external {
    require(isSenderInRole(ROLE_TOKEN_CREATOR), "insufficient privileges (ROLE_TOKEN_CREATOR required)");
    _mint(recipient, amount);
  }

  /**
   * @param amount number of tokens to be burned.
   */
  function burn(uint256 amount) external {
    _burn(msg.sender, amount);
  }
}

/**
 * @title Flash Pool Base
 *
 * @notice An abstract contract containing logic for a new Flash Pool version.
 *         It fixes the REWARD_PER_WEIGHT_MULTIPLIER constant to allow bigger supply
 *         tokens flash pools.
 *
 * @dev Deployment and initialization.
 *      Any pool deployed must be bound to the deployed pool factory (IlluviumPoolFactory)
 *      Additionally, 3 token instance addresses must be defined on deployment:
 *          - ILV token address
 *          - sILV token address, used to mint sILV rewards
 *          - pool token address, it can be ILV token address, ILV/ETH pair address, and others
 *
 * @dev Pool weight defines the fraction of the yield current pool receives among the other pools,
 *      pool factory is responsible for the weight synchronization between the pools.
 * @dev The weight is logically 10% for ILV pool and 90% for ILV/ETH pool.
 *      Since Solidity doesn't support fractions the weight is defined by the division of
 *      pool weight by total pools weight (sum of all registered pools within the factory)
 * @dev For ILV Pool we use 200 as weight and for ILV/ETH pool 800.
 *
 * @author Pedro Bergamini, reviewed by Basil Gorin
 */
abstract contract FlashPoolBase is IPool, IlluviumAware, ReentrancyGuard {
  /// @dev Data structure representing token holder using a pool
  struct User {
    // @dev Total staked amount
    uint256 tokenAmount;
    // @dev Total weight
    uint256 totalWeight;
    // @dev Auxiliary variable for yield calculation
    uint256 subYieldRewards;
    // @dev Auxiliary variable for vault rewards calculation
    uint256 subVaultRewards;
    // @dev An array of holder's deposits
    Deposit[] deposits;
  }

  /// @dev Token holder storage, maps token holder address to their data record
  mapping(address => User) public users;

  /// @dev Link to sILV ERC20 Token EscrowedIlluviumERC20 instance
  address public immutable override silv;

  /// @dev Link to the pool factory IlluviumPoolFactory instance
  IlluviumPoolFactory public immutable factory;

  /// @dev Link to the internal token instance, for example SNX or XYZ
  address public immutable internalToken;

  /// @dev Pool weight, 100 for ILV pool or 900 for ILV/ETH
  uint32 public override weight;

  /// @dev Block number of the last yield distribution event
  uint64 public override lastYieldDistribution;

  /// @dev Used to calculate yield rewards
  /// @dev This value is different from "reward per token" used in locked pool
  /// @dev Note: stakes are different in duration and "weight" reflects that
  uint256 public override yieldRewardsPerWeight;

  /// @dev Used to calculate yield rewards, keeps track of the tokens weight locked in staking
  uint256 public override usersLockingWeight;

  /**
   * @dev Stake weight is proportional to deposit amount and time locked, precisely
   *      "deposit amount wei multiplied by (fraction of the year locked plus one)"
   * @dev To avoid significant precision loss due to multiplication by "fraction of the year" [0, 1],
   *      weight is stored multiplied by 1e6 constant, as an integer
   * @dev Corner case 1: if time locked is zero, weight is deposit amount multiplied by 1e6
   * @dev Corner case 2: if time locked is one year, fraction of the year locked is one, and
   *      weight is a deposit amount multiplied by 2 * 1e6
   */
  uint256 internal constant WEIGHT_MULTIPLIER = 1e6;

  /**
   * @dev When we know beforehand that staking is done for a year, and fraction of the year locked is one,
   *      we use simplified calculation and use the following constant instead previos one
   */
  uint256 internal constant YEAR_STAKE_WEIGHT_MULTIPLIER = 2 * WEIGHT_MULTIPLIER;

  /**
   * @dev Rewards per weight are stored multiplied by 1e12, as integers.
   */
  uint256 internal constant REWARD_PER_WEIGHT_MULTIPLIER = 1e18;

  /**
   * @dev Fired in _stake() and stake()
   *
   * @param _by an address which performed an operation, usually token holder
   * @param _from token holder address, the tokens will be returned to that address
   * @param amount amount of tokens staked
   */
  event Staked(address indexed _by, address indexed _from, uint256 amount);

  /**
   * @dev Fired in _updateStakeLock() and updateStakeLock()
   *
   * @param _by an address which performed an operation
   * @param depositId updated deposit ID
   * @param lockedFrom deposit locked from value
   * @param lockedUntil updated deposit locked until value
   */
  event StakeLockUpdated(address indexed _by, uint256 depositId, uint64 lockedFrom, uint64 lockedUntil);

  /**
   * @dev Fired in _unstake() and unstake()
   *
   * @param _by an address which performed an operation, usually token holder
   * @param _to an address which received the unstaked tokens, usually token holder
   * @param amount amount of tokens unstaked
   */
  event Unstaked(address indexed _by, address indexed _to, uint256 amount);

  /**
   * @dev Fired in _sync(), sync() and dependent functions (stake, unstake, etc.)
   *
   * @param _by an address which performed an operation
   * @param yieldRewardsPerWeight updated yield rewards per weight value
   * @param lastYieldDistribution usually, current block number
   */
  event Synchronized(address indexed _by, uint256 yieldRewardsPerWeight, uint64 lastYieldDistribution);

  /**
   * @dev Fired in _processRewards(), processRewards() and dependent functions (stake, unstake, etc.)
   *
   * @param _by an address which performed an operation
   * @param _to an address which claimed the yield reward
   * @param sIlv flag indicating if reward was paid (minted) in sILV
   * @param amount amount of yield paid
   */
  event YieldClaimed(address indexed _by, address indexed _to, bool sIlv, uint256 amount);

  /**
   * @dev Fired in setWeight()
   *
   * @param _by an address which performed an operation, always a factory
   * @param _fromVal old pool weight value
   * @param _toVal new pool weight value
   */
  event PoolWeightUpdated(address indexed _by, uint32 _fromVal, uint32 _toVal);

  /**
   * @dev Overridden in sub-contracts to construct the pool
   *
   * @param _ilv ILV ERC20 Token IlluviumERC20 address
   * @param _silv sILV ERC20 Token EscrowedIlluviumERC20 address
   * @param _factory Pool factory IlluviumPoolFactory instance/address
   * @param _internalToken token the pool operates on
   * @param _initBlock initial block used to calculate the rewards
   *      note: _initBlock can be set to the future effectively meaning _sync() calls will do nothing
   * @param _weight number representing a weight of the pool, actual weight fraction
   *      is calculated as that number divided by the total pools weight and doesn't exceed one
   */
  constructor(
    address _ilv,
    address _silv,
    IlluviumPoolFactory _factory,
    address _internalToken,
    uint64 _initBlock,
    uint32 _weight
  ) IlluviumAware(_ilv) {
    // verify the inputs are set
    require(_silv != address(0), "sILV address not set");
    require(address(_factory) != address(0), "ILV Pool fct address not set");
    require(_internalToken != address(0), "token address not set");
    require(_initBlock > 0, "init block not set");
    require(_weight > 0, "pool weight not set");

    // verify sILV instance supplied
    require(
      EscrowedIlluviumERC20(_silv).TOKEN_UID() ==
      0xac3051b8d4f50966afb632468a4f61483ae6a953b74e387a01ef94316d6b7d62,
      "unexpected sILV TOKEN_UID"
    );
    // verify IlluviumPoolFactory instance supplied
    require(
      _factory.FACTORY_UID() == 0xc5cfd88c6e4d7e5c8a03c255f03af23c0918d8e82cac196f57466af3fd4a5ec7,
      "unexpected FACTORY_UID"
    );

    // save the inputs into internal state variables
    silv = _silv;
    factory = _factory;
    internalToken = _internalToken;
    weight = _weight;

    // init the dependent internal state variables
    lastYieldDistribution = _initBlock;
  }

  /**
   * @dev Faked link to the pool token instance
   */
  function poolToken() external view override returns(address) {
    return address(this);
  }

  /**
   * @notice Calculates current yield rewards value available for address specified
   *
   * @param _staker an address to calculate yield rewards value for
   * @return calculated yield reward value for the given address
   */
  function pendingYieldRewards(address _staker) external view override returns (uint256) {
    // `newYieldRewardsPerWeight` will store stored or recalculated value for `yieldRewardsPerWeight`
    uint256 newYieldRewardsPerWeight;

    // if smart contract state was not updated recently, `yieldRewardsPerWeight` value
    // is outdated and we need to recalculate it in order to calculate pending rewards correctly
    if (blockNumber() > lastYieldDistribution && usersLockingWeight != 0) {
      uint256 endBlock = factory.endBlock();
      uint256 multiplier =
      blockNumber() > endBlock ? endBlock - lastYieldDistribution : blockNumber() - lastYieldDistribution;
      uint256 ilvRewards = (multiplier * weight * factory.ilvPerBlock()) / factory.totalWeight();

      // recalculated value for `yieldRewardsPerWeight`
      newYieldRewardsPerWeight = rewardToWeight(ilvRewards, usersLockingWeight) + yieldRewardsPerWeight;
    } else {
      // if smart contract state is up to date, we don't recalculate
      newYieldRewardsPerWeight = yieldRewardsPerWeight;
    }

    // based on the rewards per weight value, calculate pending rewards;
    User memory user = users[_staker];
    uint256 pending = weightToReward(user.totalWeight, newYieldRewardsPerWeight) - user.subYieldRewards;

    return pending;
  }

  /**
   * @notice Returns total staked token balance for the given address
   *
   * @param _user an address to query balance for
   * @return total staked token balance
   */
  function balanceOf(address _user) external view override returns (uint256) {
    // read specified user token amount and return
    return users[_user].tokenAmount;
  }

  /**
   * @notice Returns information on the given deposit for the given address
   *
   * @dev See getDepositsLength
   *
   * @param _user an address to query deposit for
   * @param _depositId zero-indexed deposit ID for the address specified
   * @return deposit info as Deposit structure
   */
  function getDeposit(address _user, uint256 _depositId) external view override returns (Deposit memory) {
    // read deposit at specified index and return
    return users[_user].deposits[_depositId];
  }

  /**
   * @notice Returns number of deposits for the given address. Allows iteration over deposits.
   *
   * @dev See getDeposit
   *
   * @param _user an address to query deposit length for
   * @return number of deposits for the given address
   */
  function getDepositsLength(address _user) external view override returns (uint256) {
    // read deposits array length and return
    return users[_user].deposits.length;
  }

  /**
   * @notice Stakes specified amount of tokens for the specified amount of time,
   *      and pays pending yield rewards if any
   *
   * @dev Requires amount to stake to be greater than zero
   *
   * @param _amount amount of tokens to stake
   * @param _lockUntil stake period as unix timestamp; zero means no locking
   * @param _useSILV a flag indicating if previous reward to be paid as sILV
   */
  function stake(
    uint256 _amount,
    uint64 _lockUntil,
    bool _useSILV
  ) external override {
    // delegate call to an internal function
    _stake(msg.sender, _amount, _lockUntil, _useSILV, false);
  }

  /**
   * @notice Unstakes specified amount of tokens, and pays pending yield rewards if any
   *
   * @dev Requires amount to unstake to be greater than zero
   *
   * @param _depositId deposit ID to unstake from, zero-indexed
   * @param _amount amount of tokens to unstake
   * @param _useSILV a flag indicating if reward to be paid as sILV
   */
  function unstake(
    uint256 _depositId,
    uint256 _amount,
    bool _useSILV
  ) external override {
    // delegate call to an internal function
    _unstake(msg.sender, _depositId, _amount, _useSILV);
  }

  /**
   * @notice Extends locking period for a given deposit
   *
   * @dev Requires new lockedUntil value to be:
   *      higher than the current one, and
   *      in the future, but
   *      no more than 1 year in the future
   *
   * @param depositId updated deposit ID
   * @param lockedUntil updated deposit locked until value
   * @param useSILV used for _processRewards check if it should use ILV or sILV
   */
  function updateStakeLock(
    uint256 depositId,
    uint64 lockedUntil,
    bool useSILV
  ) external {
    // sync and call processRewards
    _sync();
    _processRewards(msg.sender, useSILV, false);
    // delegate call to an internal function
    _updateStakeLock(msg.sender, depositId, lockedUntil);
  }

  /**
   * @notice Service function to synchronize pool state with current time
   *
   * @dev Can be executed by anyone at any time, but has an effect only when
   *      at least one block passes between synchronizations
   * @dev Executed internally when staking, unstaking, processing rewards in order
   *      for calculations to be correct and to reflect state progress of the contract
   * @dev When timing conditions are not met (executed too frequently, or after factory
   *      end block), function doesn't throw and exits silently
   */
  function sync() external override {
    // delegate call to an internal function
    _sync();
  }

  /**
   * @notice Service function to calculate and pay pending yield rewards to the sender
   *
   * @dev Can be executed by anyone at any time, but has an effect only when
   *      executed by deposit holder and when at least one block passes from the
   *      previous reward processing
   * @dev Executed internally when staking and unstaking, executes sync() under the hood
   *      before making further calculations and payouts
   * @dev When timing conditions are not met (executed too frequently, or after factory
   *      end block), function doesn't throw and exits silently
   *
   * @param _useSILV flag indicating whether to mint sILV token as a reward or not;
   *      when set to true - sILV reward is minted immediately and sent to sender,
   *      when set to false - new pool deposit gets created together with sILV minted
   */
  function processRewards(bool _useSILV) external virtual override {
    // delegate call to an internal function
    _processRewards(msg.sender, _useSILV, true);
  }

  /**
   * @dev Executed by the factory to modify pool weight; the factory is expected
   *      to keep track of the total pools weight when updating
   *
   * @dev Set weight to zero to disable the pool
   *
   * @param _weight new weight to set for the pool
   */
  function setWeight(uint32 _weight) external override {
    // verify function is executed by the factory
    require(msg.sender == address(factory), "access denied");

    // emit an event logging old and new weight values
    emit PoolWeightUpdated(msg.sender, weight, _weight);

    // set the new weight value
    weight = _weight;
  }

  /**
   * @dev Similar to public pendingYieldRewards, but performs calculations based on
   *      current smart contract state only, not taking into account any additional
   *      time/blocks which might have passed
   *
   * @param _staker an address to calculate yield rewards value for
   * @return pending calculated yield reward value for the given address
   */
  function _pendingYieldRewards(address _staker) internal view returns (uint256 pending) {
    // read user data structure into memory
    User memory user = users[_staker];

    // and perform the calculation using the values read
    return weightToReward(user.totalWeight, yieldRewardsPerWeight) - user.subYieldRewards;
  }

  /**
   * @dev Used internally, mostly by children implementations, see stake()
   *
   * @param _staker an address which stakes tokens and which will receive them back
   * @param _amount amount of tokens to stake
   * @param _lockUntil stake period as unix timestamp; zero means no locking
   * @param _useSILV a flag indicating if previous reward to be paid as sILV
   * @param _isYield a flag indicating if that stake is created to store yield reward
   *      from the previously unstaked stake
   */
  function _stake(
    address _staker,
    uint256 _amount,
    uint64 _lockUntil,
    bool _useSILV,
    bool _isYield
  ) internal virtual {
    // validate the inputs
    require(_amount > 0, "zero amount");
    require(
      _lockUntil == 0 || (_lockUntil > now256() && _lockUntil - now256() <= 365 days),
      "invalid lock interval"
    );

    // update smart contract state
    _sync();

    // get a link to user data struct, we will write to it later
    User storage user = users[_staker];
    // process current pending rewards if any
    if (user.tokenAmount > 0) {
      _processRewards(_staker, _useSILV, false);
    }

    // in most of the cases added amount `addedAmount` is simply `_amount`
    // however for deflationary tokens this can be different

    // read the current balance
    uint256 previousBalance = IERC20(internalToken).balanceOf(address(this));
    // transfer `_amount`; note: some tokens may get burnt here
    transferPoolTokenFrom(address(msg.sender), address(this), _amount);
    // read new balance, usually this is just the difference `previousBalance - _amount`
    uint256 newBalance = IERC20(internalToken).balanceOf(address(this));
    // calculate real amount taking into account deflation
    uint256 addedAmount = newBalance - previousBalance;

    // set the `lockFrom` and `lockUntil` taking into account that
    // zero value for `_lockUntil` means "no locking" and leads to zero values
    // for both `lockFrom` and `lockUntil`
    uint64 lockFrom = _lockUntil > 0 ? uint64(now256()) : 0;
    uint64 lockUntil = _lockUntil;

    // stake weight formula rewards for locking
    uint256 stakeWeight =
    (((lockUntil - lockFrom) * WEIGHT_MULTIPLIER) / 365 days + WEIGHT_MULTIPLIER) * addedAmount;

    // makes sure stakeWeight is valid
    assert(stakeWeight > 0);

    // create and save the deposit (append it to deposits array)
    Deposit memory deposit =
    Deposit({
    tokenAmount: addedAmount,
    weight: stakeWeight,
    lockedFrom: lockFrom,
    lockedUntil: lockUntil,
    isYield: _isYield
    });
    // deposit ID is an index of the deposit in `deposits` array
    user.deposits.push(deposit);

    // update user record
    user.tokenAmount += addedAmount;
    user.totalWeight += stakeWeight;
    user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);

    // update global variable
    usersLockingWeight += stakeWeight;

    // emit an event
    emit Staked(msg.sender, _staker, _amount);
  }

  /**
   * @dev Used internally, mostly by children implementations, see unstake()
   *
   * @param _staker an address which unstakes tokens (which previously staked them)
   * @param _depositId deposit ID to unstake from, zero-indexed
   * @param _amount amount of tokens to unstake
   * @param _useSILV a flag indicating if reward to be paid as sILV
   */
  function _unstake(
    address _staker,
    uint256 _depositId,
    uint256 _amount,
    bool _useSILV
  ) internal virtual {
    // verify an amount is set
    require(_amount > 0, "zero amount");

    // get a link to user data struct, we will write to it later
    User storage user = users[_staker];
    // get a link to the corresponding deposit, we may write to it later
    Deposit storage stakeDeposit = user.deposits[_depositId];
    // deposit structure may get deleted, so we save isYield flag to be able to use it
    bool isYield = stakeDeposit.isYield;

    // verify available balance
    // if staker address ot deposit doesn't exist this check will fail as well
    require(stakeDeposit.tokenAmount >= _amount, "amount exceeds stake");

    // update smart contract state
    _sync();
    // and process current pending rewards if any
    _processRewards(_staker, _useSILV, false);

    // recalculate deposit weight
    uint256 previousWeight = stakeDeposit.weight;
    uint256 newWeight =
    (((stakeDeposit.lockedUntil - stakeDeposit.lockedFrom) * WEIGHT_MULTIPLIER) /
    365 days +
    WEIGHT_MULTIPLIER) * (stakeDeposit.tokenAmount - _amount);

    // update the deposit, or delete it if its depleted
    if (stakeDeposit.tokenAmount - _amount == 0) {
      delete user.deposits[_depositId];
    } else {
      stakeDeposit.tokenAmount -= _amount;
      stakeDeposit.weight = newWeight;
    }

    // update user record
    user.tokenAmount -= _amount;
    user.totalWeight = user.totalWeight - previousWeight + newWeight;
    user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);

    // update global variable
    usersLockingWeight = usersLockingWeight - previousWeight + newWeight;

    // if the deposit was created by the pool itself as a yield reward
    if (isYield) {
      // mint the yield via the factory
      factory.mintYieldTo(msg.sender, _amount);
    } else {
      // otherwise just return tokens back to holder
      transferPoolToken(msg.sender, _amount);
    }

    // emit an event
    emit Unstaked(msg.sender, _staker, _amount);
  }

  /**
   * @dev Used internally, mostly by children implementations, see sync()
   *
   * @dev Updates smart contract state (`yieldRewardsPerWeight`, `lastYieldDistribution`),
   *      updates factory state via `updateILVPerBlock`
   */
  function _sync() internal virtual {
    // update ILV per block value in factory if required
    if (factory.shouldUpdateRatio()) {
      factory.updateILVPerBlock();
    }

    // check bound conditions and if these are not met -
    // exit silently, without emitting an event
    uint256 endBlock = factory.endBlock();
    if (lastYieldDistribution >= endBlock) {
      return;
    }
    if (blockNumber() <= lastYieldDistribution) {
      return;
    }
    // if locking weight is zero - update only `lastYieldDistribution` and exit
    if (usersLockingWeight == 0) {
      lastYieldDistribution = uint64(blockNumber());
      return;
    }

    // to calculate the reward we need to know how many blocks passed, and reward per block
    uint256 currentBlock = blockNumber() > endBlock ? endBlock : blockNumber();
    uint256 blocksPassed = currentBlock - lastYieldDistribution;
    uint256 ilvPerBlock = factory.ilvPerBlock();

    // calculate the reward
    uint256 ilvReward = (blocksPassed * ilvPerBlock * weight) / factory.totalWeight();

    // update rewards per weight and `lastYieldDistribution`
    yieldRewardsPerWeight += rewardToWeight(ilvReward, usersLockingWeight);
    lastYieldDistribution = uint64(currentBlock);

    // emit an event
    emit Synchronized(msg.sender, yieldRewardsPerWeight, lastYieldDistribution);
  }

  /**
   * @dev Used internally, mostly by children implementations, see processRewards()
   *
   * @param _staker an address which receives the reward (which has staked some tokens earlier)
   * @param _useSILV flag indicating whether to mint sILV token as a reward or not, see processRewards()
   * @param _withUpdate flag allowing to disable synchronization (see sync()) if set to false
   * @return pendingYield the rewards calculated and optionally re-staked
   */
  function _processRewards(
    address _staker,
    bool _useSILV,
    bool _withUpdate
  ) internal virtual returns (uint256 pendingYield) {
    // update smart contract state if required
    if (_withUpdate) {
      _sync();
    }

    // calculate pending yield rewards, this value will be returned
    pendingYield = _pendingYieldRewards(_staker);

    // if pending yield is zero - just return silently
    if (pendingYield == 0) return 0;

    // get link to a user data structure, we will write into it later
    User storage user = users[_staker];

    // if sILV is requested
    if (_useSILV) {
      // - mint sILV
      mintSIlv(_staker, pendingYield);
    } else {
      // for other pools - stake as pool
      address ilvPool = factory.getPoolAddress(ilv);
      ICorePool(ilvPool).stakeAsPool(_staker, pendingYield);
    }

    // update users's record for `subYieldRewards` if requested
    if (_withUpdate) {
      user.subYieldRewards = weightToReward(user.totalWeight, yieldRewardsPerWeight);
    }

    // emit an event
    emit YieldClaimed(msg.sender, _staker, _useSILV, pendingYield);
  }

  /**
   * @dev See updateStakeLock()
   *
   * @param _staker an address to update stake lock
   * @param _depositId updated deposit ID
   * @param _lockedUntil updated deposit locked until value
   */
  function _updateStakeLock(
    address _staker,
    uint256 _depositId,
    uint64 _lockedUntil
  ) internal {
    // validate the input time
    require(_lockedUntil > now256(), "lock should be in the future");

    // get a link to user data struct, we will write to it later
    User storage user = users[_staker];
    // get a link to the corresponding deposit, we may write to it later
    Deposit storage stakeDeposit = user.deposits[_depositId];

    // validate the input against deposit structure
    require(_lockedUntil > stakeDeposit.lockedUntil, "invalid new lock");

    // verify locked from and locked until values
    if (stakeDeposit.lockedFrom == 0) {
      require(_lockedUntil - now256() <= 365 days, "max lock period is 365 days");
      stakeDeposit.lockedFrom = uint64(now256());
    } else {
      require(_lockedUntil - stakeDeposit.lockedFrom <= 365 days, "max lock period is 365 days");
    }

    // update locked until value, calculate new weight
    stakeDeposit.lockedUntil = _lockedUntil;
    uint256 newWeight =
    (((stakeDeposit.lockedUntil - stakeDeposit.lockedFrom) * WEIGHT_MULTIPLIER) /
    365 days +
    WEIGHT_MULTIPLIER) * stakeDeposit.tokenAmount;

    // save previous weight
    uint256 previousWeight = stakeDeposit.weight;
    // update weight
    stakeDeposit.weight = newWeight;

    // update user total weight and global locking weight
    user.totalWeight = user.totalWeight - previousWeight + newWeight;
    usersLockingWeight = usersLockingWeight - previousWeight + newWeight;

    // emit an event
    emit StakeLockUpdated(_staker, _depositId, stakeDeposit.lockedFrom, _lockedUntil);
  }

  /**
   * @dev Converts stake weight (not to be mixed with the pool weight) to
   *      ILV reward value, applying the 10^12 division on weight
   *
   * @param _weight stake weight
   * @param rewardPerWeight ILV reward per weight
   * @return reward value normalized to 10^12
   */
  function weightToReward(uint256 _weight, uint256 rewardPerWeight) public pure returns (uint256) {
    // apply the formula and return
    return (_weight * rewardPerWeight) / REWARD_PER_WEIGHT_MULTIPLIER;
  }

  /**
   * @dev Converts reward ILV value to stake weight (not to be mixed with the pool weight),
   *      applying the 10^12 multiplication on the reward
   *      - OR -
   * @dev Converts reward ILV value to reward/weight if stake weight is supplied as second
   *      function parameter instead of reward/weight
   *
   * @param reward yield reward
   * @param rewardPerWeight reward/weight (or stake weight)
   * @return stake weight (or reward/weight)
   */
  function rewardToWeight(uint256 reward, uint256 rewardPerWeight) public pure returns (uint256) {
    // apply the reverse formula and return
    return (reward * REWARD_PER_WEIGHT_MULTIPLIER) / rewardPerWeight;
  }

  /**
   * @dev Testing time-dependent functionality is difficult and the best way of
   *      doing it is to override block number in helper test smart contracts
   *
   * @return `block.number` in mainnet, custom values in testnets (if overridden)
   */
  function blockNumber() public view virtual returns (uint256) {
    // return current block number
    return block.number;
  }

  /**
   * @dev Testing time-dependent functionality is difficult and the best way of
   *      doing it is to override time in helper test smart contracts
   *
   * @return `block.timestamp` in mainnet, custom values in testnets (if overridden)
   */
  function now256() public view virtual returns (uint256) {
    // return current block timestamp
    return block.timestamp;
  }

  /**
   * @dev Executes EscrowedIlluviumERC20.mint(_to, _values)
   *      on the bound EscrowedIlluviumERC20 instance
   *
   * @dev Reentrancy safe due to the EscrowedIlluviumERC20 design
   */
  function mintSIlv(address _to, uint256 _value) private {
    // just delegate call to the target
    EscrowedIlluviumERC20(silv).mint(_to, _value);
  }

  /**
   * @dev Executes SafeERC20.safeTransfer on a pool token
   *
   * @dev Reentrancy safety enforced via `ReentrancyGuard.nonReentrant`
   */
  function transferPoolToken(address _to, uint256 _value) internal nonReentrant {
    // just delegate call to the target
    SafeERC20.safeTransfer(IERC20(internalToken), _to, _value);
  }

  /**
   * @dev Executes SafeERC20.safeTransferFrom on a pool token
   *
   * @dev Reentrancy safety enforced via `ReentrancyGuard.nonReentrant`
   */
  function transferPoolTokenFrom(
    address _from,
    address _to,
    uint256 _value
  ) internal nonReentrant {
    // just delegate call to the target
    SafeERC20.safeTransferFrom(IERC20(internalToken), _from, _to, _value);
  }
}

/**
 * @title Flash Pool V2
 *
 * @notice Flash pools represent temporary pools like SNX pool.
 *
 * @notice Flash pools doesn't lock tokens, staked tokens can be unstaked  at any time
 *
 * @dev See FlashPoolBase for more details
 *
 * @author Pedro Bergamini, reviewed by Basil Gorin
 */
contract FlashPoolV2 is FlashPoolBase {
  /// @dev Pool expiration time, the pool considered to be disabled once end block is reached
  /// @dev Expired pools don't process any rewards, users are expected to withdraw staked tokens
  ///      from the flash pools once they expire
  uint64 public endBlock;

  /// @dev Flag indicating pool type, true means "flash pool"
  bool public constant override isFlashPool = true;

  /**
   * @dev Creates/deploys an instance of the flash pool
   *
   * @param _ilv ILV ERC20 Token IlluviumERC20 address
   * @param _silv sILV ERC20 Token EscrowedIlluviumERC20 address
   * @param _factory Pool factory IlluviumPoolFactory instance/address
   * @param _internalToken token the pool operates on, for example ILV or ILV/ETH pair
   * @param _initBlock initial block used to calculate the rewards
   * @param _weight number representing a weight of the pool, actual weight fraction
   *      is calculated as that number divided by the total pools weight and doesn't exceed one
   * @param _endBlock pool expiration time (as block number)
   */
  constructor(
    address _ilv,
    address _silv,
    IlluviumPoolFactory _factory,
    address _internalToken,
    uint64 _initBlock,
    uint32 _weight,
    uint64 _endBlock
  ) FlashPoolBase(_ilv, _silv, _factory, _internalToken, _initBlock, _weight) {
    // check the inputs which are not checked by the pool base
    require(_endBlock > _initBlock, "end block must be higher than init block");

    // assign the end block
    endBlock = _endBlock;
  }

  /**
   * @notice The function to check pool state. Flash pool is considered "disabled"
   *      once time reaches its "end block"
   *
   * @return true if pool is disabled (time has reached end block), false otherwise
   */
  function isPoolDisabled() public view returns (bool) {
    // verify the pool expiration condition and return the result
    return blockNumber() >= endBlock;
  }

  /**
   * @inheritdoc FlashPoolBase
   *
   * @dev Overrides the _stake() in base by setting the locked until value to 1 year in the future;
   *      locked until value has only locked weight effect and doesn't do any real token locking
   *
   * @param _lockedUntil not used, overridden with now + 1 year just to have correct calculation
   *      of the locking weights
   */
  function _stake(
    address _staker,
    uint256 _amount,
    uint64 _lockedUntil,
    bool useSILV,
    bool isYield
  ) internal override {
    // override the `_lockedUntil` and execute parent
    // we set "locked period" to 365 days only to have correct calculation of locking weights,
    // the tokens are not really locked since _unstake in the core pool doesn't check the "locked period"
    super._stake(_staker, _amount, uint64(now256() + 365 days), useSILV, isYield);
  }

  /**
   * @inheritdoc FlashPoolBase
   *
   * @dev In addition to regular sync() routine of the base, set the pool weight
   *      to zero, effectively disabling the pool in the factory
   * @dev If the pool is disabled regular sync() routine is ignored
   */
  function _sync() internal override {
    // if pool is disabled/expired
    if (isPoolDisabled()) {
      // if weight is not yet set
      if (weight != 0) {
        // set the pool weight (sets both factory and local values)
        factory.changePoolWeight(address(this), 0);
      }
      // and exit
      return;
    }

    // for enabled pools perform regular sync() routine
    super._sync();
  }
}

Contract Security Audit

Contract ABI

[{"inputs":[{"internalType":"address","name":"_ilv","type":"address"},{"internalType":"address","name":"_silv","type":"address"},{"internalType":"contract IlluviumPoolFactory","name":"_factory","type":"address"},{"internalType":"address","name":"_internalToken","type":"address"},{"internalType":"uint64","name":"_initBlock","type":"uint64"},{"internalType":"uint32","name":"_weight","type":"uint32"},{"internalType":"uint64","name":"_endBlock","type":"uint64"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":false,"internalType":"uint32","name":"_fromVal","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"_toVal","type":"uint32"}],"name":"PoolWeightUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositId","type":"uint256"},{"indexed":false,"internalType":"uint64","name":"lockedFrom","type":"uint64"},{"indexed":false,"internalType":"uint64","name":"lockedUntil","type":"uint64"}],"name":"StakeLockUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Staked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":false,"internalType":"uint256","name":"yieldRewardsPerWeight","type":"uint256"},{"indexed":false,"internalType":"uint64","name":"lastYieldDistribution","type":"uint64"}],"name":"Synchronized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Unstaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_by","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"bool","name":"sIlv","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"YieldClaimed","type":"event"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"blockNumber","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endBlock","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"contract IlluviumPoolFactory","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"},{"internalType":"uint256","name":"_depositId","type":"uint256"}],"name":"getDeposit","outputs":[{"components":[{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint256","name":"weight","type":"uint256"},{"internalType":"uint64","name":"lockedFrom","type":"uint64"},{"internalType":"uint64","name":"lockedUntil","type":"uint64"},{"internalType":"bool","name":"isYield","type":"bool"}],"internalType":"struct IPool.Deposit","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"getDepositsLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ilv","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"internalToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isFlashPool","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isPoolDisabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastYieldDistribution","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"now256","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_staker","type":"address"}],"name":"pendingYieldRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"poolToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"_useSILV","type":"bool"}],"name":"processRewards","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"reward","type":"uint256"},{"internalType":"uint256","name":"rewardPerWeight","type":"uint256"}],"name":"rewardToWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint32","name":"_weight","type":"uint32"}],"name":"setWeight","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"silv","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"uint64","name":"_lockUntil","type":"uint64"},{"internalType":"bool","name":"_useSILV","type":"bool"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sync","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"bool","name":"_useSILV","type":"bool"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"depositId","type":"uint256"},{"internalType":"uint64","name":"lockedUntil","type":"uint64"},{"internalType":"bool","name":"useSILV","type":"bool"}],"name":"updateStakeLock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"users","outputs":[{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint256","name":"totalWeight","type":"uint256"},{"internalType":"uint256","name":"subYieldRewards","type":"uint256"},{"internalType":"uint256","name":"subVaultRewards","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usersLockingWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"weight","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_weight","type":"uint256"},{"internalType":"uint256","name":"rewardPerWeight","type":"uint256"}],"name":"weightToReward","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"yieldRewardsPerWeight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]

6101006040523480156200001257600080fd5b5060405162002f2438038062002f24833981016040819052620000359162000446565b868686868686856001600160a01b0381166200006e5760405162461bcd60e51b81526004016200006590620005d8565b60405180910390fd5b806001600160a01b0316638a114e136040518163ffffffff1660e01b815260040160206040518083038186803b158015620000a857600080fd5b505afa158015620000bd573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620000e39190620004ee565b7f83ecb176af7c4f35a45ff0018282e3a05a1018065da866182df12285866f5a2c14620001245760405162461bcd60e51b81526004016200006590620005a1565b60601b6001600160601b03191660805260016000556001600160a01b038516620001625760405162461bcd60e51b815260040162000065906200068e565b6001600160a01b0384166200018b5760405162461bcd60e51b815260040162000065906200053e565b6001600160a01b038316620001b45760405162461bcd60e51b815260040162000065906200060f565b6000826001600160401b031611620001e05760405162461bcd60e51b8152600401620000659062000575565b60008163ffffffff1611620002095760405162461bcd60e51b81526004016200006590620006c5565b846001600160a01b0316638a114e136040518163ffffffff1660e01b815260040160206040518083038186803b1580156200024357600080fd5b505afa15801562000258573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906200027e9190620004ee565b7fac3051b8d4f50966afb632468a4f61483ae6a953b74e387a01ef94316d6b7d6214620002bf5760405162461bcd60e51b81526004016200006590620006fc565b836001600160a01b031663f70b7fce6040518163ffffffff1660e01b815260040160206040518083038186803b158015620002f957600080fd5b505afa1580156200030e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620003349190620004ee565b7fc5cfd88c6e4d7e5c8a03c255f03af23c0918d8e82cac196f57466af3fd4a5ec714620003755760405162461bcd60e51b8152600401620000659062000507565b606094851b6001600160601b031990811660a05293851b841660c0529190931b90911660e0526002805463ffffffff191663ffffffff90921691909117600160201b600160601b0319166401000000006001600160401b0393841602179055848116908316119050620003fc5760405162461bcd60e51b8152600401620000659062000646565b600580546001600160401b0319166001600160401b0392909216919091179055506200074c945050505050565b80516001600160401b03811681146200044157600080fd5b919050565b600080600080600080600060e0888a03121562000461578283fd5b87516200046e8162000733565b6020890151909750620004818162000733565b6040890151909650620004948162000733565b6060890151909550620004a78162000733565b9350620004b76080890162000429565b925060a088015163ffffffff81168114620004d0578283fd5b9150620004e060c0890162000429565b905092959891949750929550565b60006020828403121562000500578081fd5b5051919050565b60208082526016908201527f756e657870656374656420464143544f52595f55494400000000000000000000604082015260600190565b6020808252601c908201527f494c5620506f6f6c206663742061646472657373206e6f742073657400000000604082015260600190565b6020808252601290820152711a5b9a5d08189b1bd8dac81b9bdd081cd95d60721b604082015260600190565b60208082526014908201527f756e657870656374656420544f4b454e5f554944000000000000000000000000604082015260600190565b60208082526013908201527f494c562061646472657373206e6f742073657400000000000000000000000000604082015260600190565b60208082526015908201527f746f6b656e2061646472657373206e6f74207365740000000000000000000000604082015260600190565b60208082526028908201527f656e6420626c6f636b206d75737420626520686967686572207468616e20696e604082015267697420626c6f636b60c01b606082015260800190565b60208082526014908201527f73494c562061646472657373206e6f7420736574000000000000000000000000604082015260600190565b60208082526013908201527f706f6f6c20776569676874206e6f742073657400000000000000000000000000604082015260600190565b60208082526019908201527f756e65787065637465642073494c5620544f4b454e5f55494400000000000000604082015260600190565b6001600160a01b03811681146200074957600080fd5b50565b60805160601c60a05160601c60c05160601c60e05160601c61271462000810600039600081816109bd015281816119d601528181611a8201528181611d4b0152611da7015260008181610402015281816104f60152818161058d015281816108a201528181610a3001528181610ab601528181610b9d0152818161124001528181611310015281816113a60152818161141c0152818161157e015261161c01526000818161079001526118900152600081816103840152610bca01526127146000f3fe608060405234801561001057600080fd5b50600436106101a95760003560e01c806357e871e7116100f9578063a87430ba11610097578063cbdf382c11610071578063cbdf382c14610348578063e8d3cad514610350578063fa213bd614610363578063fff6cae91461036b576101a9565b8063a87430ba14610315578063beb0ed6c14610338578063c45a015514610340576101a9565b80638e169d47116100d35780638e169d47146102d2578063a0974b41146102e5578063a156dc28146102ed578063a1aab33f14610300576101a9565b806357e871e7146102a457806370a08231146102ac57806379b91d69146102bf576101a9565b80632726b506116101665780634087aeb7116101405780634087aeb7146102635780634ce0f9a61461027657806351c547f81461027e57806352044ec914610291576101a9565b80632726b5061461023357806329eb5f2c14610253578063406a30291461025b576101a9565b8063083c6323146101ae578063084ff3c9146101cc57806315e4b1ae146101e15780631984db99146101f65780631da10d91146102165780631fffab221461022b575b600080fd5b6101b6610373565b6040516101c391906125ba565b60405180910390f35b6101d4610382565b6040516101c391906121f0565b6101f46101ef36600461215b565b6103a6565b005b610209610204366004612028565b6103cb565b6040516101c39190612538565b61021e610789565b6040516101c39190612260565b6101d461078e565b610246610241366004612060565b6107b2565b6040516101c391906124ed565b6101b6610863565b61021e610879565b6101f461027136600461219c565b610897565b610209610948565b6101f461028c36600461208b565b61094c565b6101f461029f36600461215b565b61095c565b61020961096a565b6102096102ba366004612028565b61096e565b6101f46102cd366004612123565b610989565b6102096102e0366004612102565b610995565b6101d46109bb565b6102096102fb366004612102565b6109df565b6103086109f4565b6040516101c39190612592565b610328610323366004612028565b610a00565b6040516101c39493929190612541565b610209610a28565b6101d4610a2e565b6101d4610a52565b61020961035e366004612028565b610a56565b610209610a74565b6101f4610a7a565b6005546001600160401b031681565b7f000000000000000000000000000000000000000000000000000000000000000081565b6103ae610a84565b6103ba33826000610b2e565b506103c6338484610d18565b505050565b6002546000908190600160201b90046001600160401b03166103eb61096a565b1180156103f9575060045415155b156106755760007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663083c63236040518163ffffffff1660e01b815260040160206040518083038186803b15801561045957600080fd5b505afa15801561046d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049191906121b8565b63ffffffff1690506000816104a461096a565b116104d357600254600160201b90046001600160401b03166104c461096a565b6104ce9190612625565b6104f0565b6002546104f090600160201b90046001600160401b031683612625565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166396c82e576040518163ffffffff1660e01b815260040160206040518083038186803b15801561054d57600080fd5b505afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058591906121b8565b63ffffffff167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316634f5cc8026040518163ffffffff1660e01b815260040160206040518083038186803b1580156105e457600080fd5b505afa1580156105f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061c91906120c3565b6002546001600160c01b03919091169061063c9063ffffffff1685612606565b6106469190612606565b61065091906125e6565b9050600354610661826004546109df565b61066b91906125ce565b935050505061067a565b506003545b6001600160a01b0383166000908152600160208181526040808420815160a08101835281548152938101548484015260028101548483015260038101546060850152600481018054835181860281018601909452808452919360808601939290879084015b828210156107575760008481526020908190206040805160a0810182526003860290920180548352600180820154848601526002909101546001600160401b0380821693850193909352600160401b81049092166060840152600160801b90910460ff161515608083015290835290920191016106df565b5050505081525050905060008160400151610776836020015185610995565b6107809190612625565b95945050505050565b600181565b7f000000000000000000000000000000000000000000000000000000000000000081565b6107ba611ffa565b6001600160a01b03831660009081526001602052604090206004018054839081106107f557634e487b7160e01b600052603260045260246000fd5b60009182526020918290206040805160a081018252600393909302909101805483526001810154938301939093526002909201546001600160401b0380821693830193909352600160401b81049092166060820152600160801b90910460ff16151560808201529392505050565b600254600160201b90046001600160401b031681565b6005546000906001600160401b031661089061096a565b1015905090565b336001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016146108e85760405162461bcd60e51b81526004016108df906123e0565b60405180910390fd5b60025460405133917f06555fe9dc8cbe328585a0c60ae1b7aafe71c28a706c2769d6cb4ee6e3e44e46916109249163ffffffff169085906125a3565b60405180910390a26002805463ffffffff191663ffffffff92909216919091179055565b4290565b61095833826001610b2e565b5050565b6103c6338484846000610fd0565b4390565b6001600160a01b031660009081526001602052604090205490565b6103c633848484610ff9565b6000670de0b6b3a76400006109aa8385612606565b6109b491906125e6565b9392505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000816109aa670de0b6b3a764000085612606565b60025463ffffffff1681565b6001602081905260009182526040909120805491810154600282015460039092015490919084565b60045481565b7f000000000000000000000000000000000000000000000000000000000000000081565b3090565b6001600160a01b031660009081526001602052604090206004015490565b60035481565b610a82610a84565b565b610a8c610879565b15610b265760025463ffffffff1615610b2157604051638172407560e01b81526001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690638172407590610aee903090600090600401612228565b600060405180830381600087803b158015610b0857600080fd5b505af1158015610b1c573d6000803e3d6000fd5b505050505b610a82565b610a8261130e565b60008115610b3e57610b3e610a84565b610b478461176e565b905080610b56575060006109b4565b6001600160a01b03841660009081526001602052604090208315610b8357610b7e8583611879565b610ca7565b60405163091465f760e11b81526000906001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001690631228cbee90610bf2907f0000000000000000000000000000000000000000000000000000000000000000906004016121f0565b60206040518083038186803b158015610c0a57600080fd5b505afa158015610c1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c429190612044565b6040516344cc892d60e01b81529091506001600160a01b038216906344cc892d90610c739089908790600401612247565b600060405180830381600087803b158015610c8d57600080fd5b505af1158015610ca1573d6000803e3d6000fd5b50505050505b8215610cc357610cbd8160010154600354610995565b60028201555b846001600160a01b0316336001600160a01b03167f5033fdcf01566fb38fe1493114b856ff2a5d1c7875a6fafdacd1d320a012806a8685604051610d0892919061226b565b60405180910390a3509392505050565b610d20610948565b816001600160401b031611610d475760405162461bcd60e51b81526004016108df90612350565b6001600160a01b038316600090815260016020526040812060048101805491929185908110610d8657634e487b7160e01b600052603260045260246000fd5b906000526020600020906003020190508060020160089054906101000a90046001600160401b03166001600160401b0316836001600160401b031611610dde5760405162461bcd60e51b81526004016108df906123b6565b60028101546001600160401b0316610e5e576301e13380610dfd610948565b610e10906001600160401b038616612625565b1115610e2e5760405162461bcd60e51b81526004016108df906122ae565b610e36610948565b60028201805467ffffffffffffffff19166001600160401b0392909216919091179055610ea3565b60028101546301e1338090610e7c906001600160401b03168561263c565b6001600160401b03161115610ea35760405162461bcd60e51b81526004016108df906122ae565b60028101805467ffffffffffffffff60401b1916600160401b6001600160401b038681168202929092179283905583546000939092620f4240926301e13380928492610ef49282821692041661263c565b6001600160401b0316610f079190612606565b610f1191906125e6565b610f1b91906125ce565b610f259190612606565b600180840180549083905590850154919250908290610f45908390612625565b610f4f91906125ce565b60018501556004548290610f64908390612625565b610f6e91906125ce565b60045560028301546040516001600160a01b038916917f85daa0d8a4afa74e5bd57c0f5d2cddf52920ec882a02b8d3f646c972b4cfb6b491610fbf918a916001600160401b03909116908a90612573565b60405180910390a250505050505050565b610ff28585610fdd610948565b610feb906301e133806125ce565b85856118fd565b5050505050565b600082116110195760405162461bcd60e51b81526004016108df906122e5565b6001600160a01b03841660009081526001602052604081206004810180549192918690811061105857634e487b7160e01b600052603260045260246000fd5b6000918252602090912060039091020160028101548154919250600160801b900460ff169085111561109c5760405162461bcd60e51b81526004016108df906124bf565b6110a4610a84565b6110b087856000610b2e565b50600182015482546000906110c6908890612625565b6002850154620f4240906301e133809082906110f5906001600160401b0380821691600160401b90041661263c565b6001600160401b03166111089190612606565b61111291906125e6565b61111c91906125ce565b6111269190612606565b8454909150611136908890612625565b6111975784600401888154811061115d57634e487b7160e01b600052603260045260246000fd5b6000918252602082206003909102018181556001810191909155600201805470ffffffffffffffffffffffffffffffffff191690556111b8565b868460000160008282546111ab9190612625565b9091555050600184018190555b868560000160008282546111cc9190612625565b9091555050600185015481906111e3908490612625565b6111ed91906125ce565b600186018190556003546112019190610995565b60028601556004548190611216908490612625565b61122091906125ce565b60045582156112ae5760405163e14bdb7160e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063e14bdb71906112779033908b90600401612247565b600060405180830381600087803b15801561129157600080fd5b505af11580156112a5573d6000803e3d6000fd5b505050506112b8565b6112b83388611d1e565b886001600160a01b0316336001600160a01b03167fd8654fcc8cf5b36d30b3f5e4688fc78118e6d68de60b9994e09902268b57c3e3896040516112fb9190612538565b60405180910390a3505050505050505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316639f1dc9bd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561136757600080fd5b505afa15801561137b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061139f91906120a7565b15611418577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0c71b436040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156113ff57600080fd5b505af1158015611413573d6000803e3d6000fd5b505050505b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663083c63236040518163ffffffff1660e01b815260040160206040518083038186803b15801561147357600080fd5b505afa158015611487573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114ab91906121b8565b60025463ffffffff919091169150600160201b90046001600160401b031681116114d55750610a82565b600254600160201b90046001600160401b03166114f061096a565b116114fb5750610a82565b6004546115365761150a61096a565b600260046101000a8154816001600160401b0302191690836001600160401b0316021790555050610a82565b60008161154161096a565b116115535761154e61096a565b611555565b815b60025490915060009061157890600160201b90046001600160401b031683612625565b905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316634f5cc8026040518163ffffffff1660e01b815260040160206040518083038186803b1580156115d557600080fd5b505afa1580156115e9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061160d91906120c3565b6001600160c01b0316905060007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166396c82e576040518163ffffffff1660e01b815260040160206040518083038186803b15801561167357600080fd5b505afa158015611687573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116ab91906121b8565b60025463ffffffff91821691166116c28486612606565b6116cc9190612606565b6116d691906125e6565b90506116e4816004546109df565b600360008282546116f591906125ce565b9091555050600280546bffffffffffffffff000000001916600160201b6001600160401b038781168202929092179283905560035460405133947f5ffbf9ce09d035b92503aad17a31b3d37ca5cd887b63701ddc2200be77d9ccc79461175f94909104169061255c565b60405180910390a25050505050565b6001600160a01b0381166000908152600160208181526040808420815160a081018352815481529381015484840152600281015484830152600381015460608501526004810180548351818602810186019094528084528695949293608086019390929190879084015b828210156118505760008481526020908190206040805160a0810182526003860290920180548352600180820154848601526002909101546001600160401b0380821693850193909352600160401b81049092166060840152600160801b90910460ff161515608083015290835290920191016117d8565b50505050815250509050806040015161186f8260200151600354610995565b6109b49190612625565b6040516340c10f1960e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906340c10f19906118c79085908590600401612247565b600060405180830381600087803b1580156118e157600080fd5b505af11580156118f5573d6000803e3d6000fd5b505050505050565b6000841161191d5760405162461bcd60e51b81526004016108df906122e5565b6001600160401b038316158061196b5750611936610948565b836001600160401b031611801561196b57506301e13380611955610948565b611968906001600160401b038616612625565b11155b6119875760405162461bcd60e51b81526004016108df90612387565b61198f610a84565b6001600160a01b03851660009081526001602052604090208054156119bc576119ba86846000610b2e565b505b6040516370a0823160e01b81526000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190611a0b9030906004016121f0565b60206040518083038186803b158015611a2357600080fd5b505afa158015611a37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a5b91906120ea565b9050611a68333088611d7a565b6040516370a0823160e01b81526000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906370a0823190611ab79030906004016121f0565b60206040518083038186803b158015611acf57600080fd5b505afa158015611ae3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b0791906120ea565b90506000611b158383612625565b9050600080886001600160401b031611611b30576000611b38565b611b38610948565b905087600083620f42406301e1338081611b52878761263c565b6001600160401b0316611b659190612606565b611b6f91906125e6565b611b7991906125ce565b611b839190612606565b905060008111611ba357634e487b7160e01b600052600160045260246000fd5b6040805160a08101825285815260208082018481526001600160401b03878116948401948552868116606085019081528d15156080860190815260048e01805460018082018355600092835296822088516003909202019081559451958501959095559551600290930180549151965167ffffffffffffffff199092169383169390931767ffffffffffffffff60401b1916600160401b96909216959095021760ff60801b1916600160801b94151594909402939093179092558854909186918a9190611c719084906125ce565b9250508190555081886001016000828254611c8c91906125ce565b92505081905550611ca38860010154600354610995565b88600201819055508160046000828254611cbd91906125ce565b925050819055508c6001600160a01b0316336001600160a01b03167f5dac0c1b1112564a045ba943c9d50270893e8e826c49be8e7073adc713ab7bd78e604051611d079190612538565b60405180910390a350505050505050505050505050565b60026000541415611d415760405162461bcd60e51b81526004016108df90612488565b6002600055611d717f00000000000000000000000000000000000000000000000000000000000000008383611dd8565b50506001600055565b60026000541415611d9d5760405162461bcd60e51b81526004016108df90612488565b6002600055611dce7f0000000000000000000000000000000000000000000000000000000000000000848484611e2e565b5050600160005550565b6103c68363a9059cbb60e01b8484604051602401611df7929190612247565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611e55565b611e4f846323b872dd60e01b858585604051602401611df793929190612204565b50505050565b6000611eaa826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316611ee49092919063ffffffff16565b8051909150156103c65780806020019051810190611ec891906120a7565b6103c65760405162461bcd60e51b81526004016108df9061243e565b6060611ef38484600085611efb565b949350505050565b606082471015611f1d5760405162461bcd60e51b81526004016108df9061230a565b611f2685611fbb565b611f425760405162461bcd60e51b81526004016108df90612407565b600080866001600160a01b03168587604051611f5e91906121d4565b60006040518083038185875af1925050503d8060008114611f9b576040519150601f19603f3d011682016040523d82523d6000602084013e611fa0565b606091505b5091509150611fb0828286611fc1565b979650505050505050565b3b151590565b60608315611fd05750816109b4565b825115611fe05782518084602001fd5b8160405162461bcd60e51b81526004016108df919061227b565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b600060208284031215612039578081fd5b81356109b4816126a6565b600060208284031215612055578081fd5b81516109b4816126a6565b60008060408385031215612072578081fd5b823561207d816126a6565b946020939093013593505050565b60006020828403121561209c578081fd5b81356109b4816126be565b6000602082840312156120b8578081fd5b81516109b4816126be565b6000602082840312156120d4578081fd5b81516001600160c01b03811681146109b4578182fd5b6000602082840312156120fb578081fd5b5051919050565b60008060408385031215612114578182fd5b50508035926020909101359150565b600080600060608486031215612137578081fd5b83359250602084013591506040840135612150816126be565b809150509250925092565b60008060006060848603121561216f578283fd5b8335925060208401356001600160401b038116811461218c578283fd5b91506040840135612150816126be565b6000602082840312156121ad578081fd5b81356109b4816126cc565b6000602082840312156121c9578081fd5b81516109b4816126cc565b600082516121e6818460208701612664565b9190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b0392909216825263ffffffff16602082015260400190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b9115158252602082015260400190565b600060208252825180602084015261229a816040850160208701612664565b601f01601f19169190910160400192915050565b6020808252601b908201527f6d6178206c6f636b20706572696f642069732033363520646179730000000000604082015260600190565b6020808252600b908201526a1e995c9bc8185b5bdd5b9d60aa1b604082015260600190565b60208082526026908201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6040820152651c8818d85b1b60d21b606082015260800190565b6020808252601c908201527f6c6f636b2073686f756c6420626520696e207468652066757475726500000000604082015260600190565b6020808252601590820152741a5b9d985b1a59081b1bd8dac81a5b9d195c9d985b605a1b604082015260600190565b60208082526010908201526f696e76616c6964206e6577206c6f636b60801b604082015260600190565b6020808252600d908201526c1858d8d95cdcc819195b9a5959609a1b604082015260600190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b602080825260149082015273616d6f756e742065786365656473207374616b6560601b604082015260600190565b600060a082019050825182526020830151602083015260408301516001600160401b038082166040850152806060860151166060850152505060808301511515608083015292915050565b90815260200190565b93845260208401929092526040830152606082015260800190565b9182526001600160401b0316602082015260400190565b9283526001600160401b03918216602084015216604082015260600190565b63ffffffff91909116815260200190565b63ffffffff92831681529116602082015260400190565b6001600160401b0391909116815260200190565b600082198211156125e1576125e1612690565b500190565b60008261260157634e487b7160e01b81526012600452602481fd5b500490565b600081600019048311821515161561262057612620612690565b500290565b60008282101561263757612637612690565b500390565b60006001600160401b038381169083168181101561265c5761265c612690565b039392505050565b60005b8381101561267f578181015183820152602001612667565b83811115611e4f5750506000910152565b634e487b7160e01b600052601160045260246000fd5b6001600160a01b03811681146126bb57600080fd5b50565b80151581146126bb57600080fd5b63ffffffff811681146126bb57600080fdfea2646970667358221220eea0765ce21f41ccb5e0c687f1b68cabe019c9c106e5bc574b07ceae3efc788864736f6c63430008010033000000000000000000000000767fe9edc9e0df98e07454847909b5e959d7ca0e000000000000000000000000398aea1c9ceb7de800284bb399a15e0efe5a9ec20000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c7000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca0000000000000000000000000000000000000000000000000000000000cb43a500000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000ce3fcc

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101a95760003560e01c806357e871e7116100f9578063a87430ba11610097578063cbdf382c11610071578063cbdf382c14610348578063e8d3cad514610350578063fa213bd614610363578063fff6cae91461036b576101a9565b8063a87430ba14610315578063beb0ed6c14610338578063c45a015514610340576101a9565b80638e169d47116100d35780638e169d47146102d2578063a0974b41146102e5578063a156dc28146102ed578063a1aab33f14610300576101a9565b806357e871e7146102a457806370a08231146102ac57806379b91d69146102bf576101a9565b80632726b506116101665780634087aeb7116101405780634087aeb7146102635780634ce0f9a61461027657806351c547f81461027e57806352044ec914610291576101a9565b80632726b5061461023357806329eb5f2c14610253578063406a30291461025b576101a9565b8063083c6323146101ae578063084ff3c9146101cc57806315e4b1ae146101e15780631984db99146101f65780631da10d91146102165780631fffab221461022b575b600080fd5b6101b6610373565b6040516101c391906125ba565b60405180910390f35b6101d4610382565b6040516101c391906121f0565b6101f46101ef36600461215b565b6103a6565b005b610209610204366004612028565b6103cb565b6040516101c39190612538565b61021e610789565b6040516101c39190612260565b6101d461078e565b610246610241366004612060565b6107b2565b6040516101c391906124ed565b6101b6610863565b61021e610879565b6101f461027136600461219c565b610897565b610209610948565b6101f461028c36600461208b565b61094c565b6101f461029f36600461215b565b61095c565b61020961096a565b6102096102ba366004612028565b61096e565b6101f46102cd366004612123565b610989565b6102096102e0366004612102565b610995565b6101d46109bb565b6102096102fb366004612102565b6109df565b6103086109f4565b6040516101c39190612592565b610328610323366004612028565b610a00565b6040516101c39493929190612541565b610209610a28565b6101d4610a2e565b6101d4610a52565b61020961035e366004612028565b610a56565b610209610a74565b6101f4610a7a565b6005546001600160401b031681565b7f000000000000000000000000767fe9edc9e0df98e07454847909b5e959d7ca0e81565b6103ae610a84565b6103ba33826000610b2e565b506103c6338484610d18565b505050565b6002546000908190600160201b90046001600160401b03166103eb61096a565b1180156103f9575060045415155b156106755760007f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c76001600160a01b031663083c63236040518163ffffffff1660e01b815260040160206040518083038186803b15801561045957600080fd5b505afa15801561046d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049191906121b8565b63ffffffff1690506000816104a461096a565b116104d357600254600160201b90046001600160401b03166104c461096a565b6104ce9190612625565b6104f0565b6002546104f090600160201b90046001600160401b031683612625565b905060007f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c76001600160a01b03166396c82e576040518163ffffffff1660e01b815260040160206040518083038186803b15801561054d57600080fd5b505afa158015610561573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061058591906121b8565b63ffffffff167f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c76001600160a01b0316634f5cc8026040518163ffffffff1660e01b815260040160206040518083038186803b1580156105e457600080fd5b505afa1580156105f8573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061061c91906120c3565b6002546001600160c01b03919091169061063c9063ffffffff1685612606565b6106469190612606565b61065091906125e6565b9050600354610661826004546109df565b61066b91906125ce565b935050505061067a565b506003545b6001600160a01b0383166000908152600160208181526040808420815160a08101835281548152938101548484015260028101548483015260038101546060850152600481018054835181860281018601909452808452919360808601939290879084015b828210156107575760008481526020908190206040805160a0810182526003860290920180548352600180820154848601526002909101546001600160401b0380821693850193909352600160401b81049092166060840152600160801b90910460ff161515608083015290835290920191016106df565b5050505081525050905060008160400151610776836020015185610995565b6107809190612625565b95945050505050565b600181565b7f000000000000000000000000398aea1c9ceb7de800284bb399a15e0efe5a9ec281565b6107ba611ffa565b6001600160a01b03831660009081526001602052604090206004018054839081106107f557634e487b7160e01b600052603260045260246000fd5b60009182526020918290206040805160a081018252600393909302909101805483526001810154938301939093526002909201546001600160401b0380821693830193909352600160401b81049092166060820152600160801b90910460ff16151560808201529392505050565b600254600160201b90046001600160401b031681565b6005546000906001600160401b031661089061096a565b1015905090565b336001600160a01b037f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c716146108e85760405162461bcd60e51b81526004016108df906123e0565b60405180910390fd5b60025460405133917f06555fe9dc8cbe328585a0c60ae1b7aafe71c28a706c2769d6cb4ee6e3e44e46916109249163ffffffff169085906125a3565b60405180910390a26002805463ffffffff191663ffffffff92909216919091179055565b4290565b61095833826001610b2e565b5050565b6103c6338484846000610fd0565b4390565b6001600160a01b031660009081526001602052604090205490565b6103c633848484610ff9565b6000670de0b6b3a76400006109aa8385612606565b6109b491906125e6565b9392505050565b7f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca81565b6000816109aa670de0b6b3a764000085612606565b60025463ffffffff1681565b6001602081905260009182526040909120805491810154600282015460039092015490919084565b60045481565b7f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c781565b3090565b6001600160a01b031660009081526001602052604090206004015490565b60035481565b610a82610a84565b565b610a8c610879565b15610b265760025463ffffffff1615610b2157604051638172407560e01b81526001600160a01b037f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c71690638172407590610aee903090600090600401612228565b600060405180830381600087803b158015610b0857600080fd5b505af1158015610b1c573d6000803e3d6000fd5b505050505b610a82565b610a8261130e565b60008115610b3e57610b3e610a84565b610b478461176e565b905080610b56575060006109b4565b6001600160a01b03841660009081526001602052604090208315610b8357610b7e8583611879565b610ca7565b60405163091465f760e11b81526000906001600160a01b037f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c71690631228cbee90610bf2907f000000000000000000000000767fe9edc9e0df98e07454847909b5e959d7ca0e906004016121f0565b60206040518083038186803b158015610c0a57600080fd5b505afa158015610c1e573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c429190612044565b6040516344cc892d60e01b81529091506001600160a01b038216906344cc892d90610c739089908790600401612247565b600060405180830381600087803b158015610c8d57600080fd5b505af1158015610ca1573d6000803e3d6000fd5b50505050505b8215610cc357610cbd8160010154600354610995565b60028201555b846001600160a01b0316336001600160a01b03167f5033fdcf01566fb38fe1493114b856ff2a5d1c7875a6fafdacd1d320a012806a8685604051610d0892919061226b565b60405180910390a3509392505050565b610d20610948565b816001600160401b031611610d475760405162461bcd60e51b81526004016108df90612350565b6001600160a01b038316600090815260016020526040812060048101805491929185908110610d8657634e487b7160e01b600052603260045260246000fd5b906000526020600020906003020190508060020160089054906101000a90046001600160401b03166001600160401b0316836001600160401b031611610dde5760405162461bcd60e51b81526004016108df906123b6565b60028101546001600160401b0316610e5e576301e13380610dfd610948565b610e10906001600160401b038616612625565b1115610e2e5760405162461bcd60e51b81526004016108df906122ae565b610e36610948565b60028201805467ffffffffffffffff19166001600160401b0392909216919091179055610ea3565b60028101546301e1338090610e7c906001600160401b03168561263c565b6001600160401b03161115610ea35760405162461bcd60e51b81526004016108df906122ae565b60028101805467ffffffffffffffff60401b1916600160401b6001600160401b038681168202929092179283905583546000939092620f4240926301e13380928492610ef49282821692041661263c565b6001600160401b0316610f079190612606565b610f1191906125e6565b610f1b91906125ce565b610f259190612606565b600180840180549083905590850154919250908290610f45908390612625565b610f4f91906125ce565b60018501556004548290610f64908390612625565b610f6e91906125ce565b60045560028301546040516001600160a01b038916917f85daa0d8a4afa74e5bd57c0f5d2cddf52920ec882a02b8d3f646c972b4cfb6b491610fbf918a916001600160401b03909116908a90612573565b60405180910390a250505050505050565b610ff28585610fdd610948565b610feb906301e133806125ce565b85856118fd565b5050505050565b600082116110195760405162461bcd60e51b81526004016108df906122e5565b6001600160a01b03841660009081526001602052604081206004810180549192918690811061105857634e487b7160e01b600052603260045260246000fd5b6000918252602090912060039091020160028101548154919250600160801b900460ff169085111561109c5760405162461bcd60e51b81526004016108df906124bf565b6110a4610a84565b6110b087856000610b2e565b50600182015482546000906110c6908890612625565b6002850154620f4240906301e133809082906110f5906001600160401b0380821691600160401b90041661263c565b6001600160401b03166111089190612606565b61111291906125e6565b61111c91906125ce565b6111269190612606565b8454909150611136908890612625565b6111975784600401888154811061115d57634e487b7160e01b600052603260045260246000fd5b6000918252602082206003909102018181556001810191909155600201805470ffffffffffffffffffffffffffffffffff191690556111b8565b868460000160008282546111ab9190612625565b9091555050600184018190555b868560000160008282546111cc9190612625565b9091555050600185015481906111e3908490612625565b6111ed91906125ce565b600186018190556003546112019190610995565b60028601556004548190611216908490612625565b61122091906125ce565b60045582156112ae5760405163e14bdb7160e01b81526001600160a01b037f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c7169063e14bdb71906112779033908b90600401612247565b600060405180830381600087803b15801561129157600080fd5b505af11580156112a5573d6000803e3d6000fd5b505050506112b8565b6112b83388611d1e565b886001600160a01b0316336001600160a01b03167fd8654fcc8cf5b36d30b3f5e4688fc78118e6d68de60b9994e09902268b57c3e3896040516112fb9190612538565b60405180910390a3505050505050505050565b7f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c76001600160a01b0316639f1dc9bd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561136757600080fd5b505afa15801561137b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061139f91906120a7565b15611418577f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c76001600160a01b031663d0c71b436040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156113ff57600080fd5b505af1158015611413573d6000803e3d6000fd5b505050505b60007f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c76001600160a01b031663083c63236040518163ffffffff1660e01b815260040160206040518083038186803b15801561147357600080fd5b505afa158015611487573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906114ab91906121b8565b60025463ffffffff919091169150600160201b90046001600160401b031681116114d55750610a82565b600254600160201b90046001600160401b03166114f061096a565b116114fb5750610a82565b6004546115365761150a61096a565b600260046101000a8154816001600160401b0302191690836001600160401b0316021790555050610a82565b60008161154161096a565b116115535761154e61096a565b611555565b815b60025490915060009061157890600160201b90046001600160401b031683612625565b905060007f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c76001600160a01b0316634f5cc8026040518163ffffffff1660e01b815260040160206040518083038186803b1580156115d557600080fd5b505afa1580156115e9573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061160d91906120c3565b6001600160c01b0316905060007f0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c76001600160a01b03166396c82e576040518163ffffffff1660e01b815260040160206040518083038186803b15801561167357600080fd5b505afa158015611687573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906116ab91906121b8565b60025463ffffffff91821691166116c28486612606565b6116cc9190612606565b6116d691906125e6565b90506116e4816004546109df565b600360008282546116f591906125ce565b9091555050600280546bffffffffffffffff000000001916600160201b6001600160401b038781168202929092179283905560035460405133947f5ffbf9ce09d035b92503aad17a31b3d37ca5cd887b63701ddc2200be77d9ccc79461175f94909104169061255c565b60405180910390a25050505050565b6001600160a01b0381166000908152600160208181526040808420815160a081018352815481529381015484840152600281015484830152600381015460608501526004810180548351818602810186019094528084528695949293608086019390929190879084015b828210156118505760008481526020908190206040805160a0810182526003860290920180548352600180820154848601526002909101546001600160401b0380821693850193909352600160401b81049092166060840152600160801b90910460ff161515608083015290835290920191016117d8565b50505050815250509050806040015161186f8260200151600354610995565b6109b49190612625565b6040516340c10f1960e01b81526001600160a01b037f000000000000000000000000398aea1c9ceb7de800284bb399a15e0efe5a9ec216906340c10f19906118c79085908590600401612247565b600060405180830381600087803b1580156118e157600080fd5b505af11580156118f5573d6000803e3d6000fd5b505050505050565b6000841161191d5760405162461bcd60e51b81526004016108df906122e5565b6001600160401b038316158061196b5750611936610948565b836001600160401b031611801561196b57506301e13380611955610948565b611968906001600160401b038616612625565b11155b6119875760405162461bcd60e51b81526004016108df90612387565b61198f610a84565b6001600160a01b03851660009081526001602052604090208054156119bc576119ba86846000610b2e565b505b6040516370a0823160e01b81526000906001600160a01b037f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca16906370a0823190611a0b9030906004016121f0565b60206040518083038186803b158015611a2357600080fd5b505afa158015611a37573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a5b91906120ea565b9050611a68333088611d7a565b6040516370a0823160e01b81526000906001600160a01b037f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca16906370a0823190611ab79030906004016121f0565b60206040518083038186803b158015611acf57600080fd5b505afa158015611ae3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611b0791906120ea565b90506000611b158383612625565b9050600080886001600160401b031611611b30576000611b38565b611b38610948565b905087600083620f42406301e1338081611b52878761263c565b6001600160401b0316611b659190612606565b611b6f91906125e6565b611b7991906125ce565b611b839190612606565b905060008111611ba357634e487b7160e01b600052600160045260246000fd5b6040805160a08101825285815260208082018481526001600160401b03878116948401948552868116606085019081528d15156080860190815260048e01805460018082018355600092835296822088516003909202019081559451958501959095559551600290930180549151965167ffffffffffffffff199092169383169390931767ffffffffffffffff60401b1916600160401b96909216959095021760ff60801b1916600160801b94151594909402939093179092558854909186918a9190611c719084906125ce565b9250508190555081886001016000828254611c8c91906125ce565b92505081905550611ca38860010154600354610995565b88600201819055508160046000828254611cbd91906125ce565b925050819055508c6001600160a01b0316336001600160a01b03167f5dac0c1b1112564a045ba943c9d50270893e8e826c49be8e7073adc713ab7bd78e604051611d079190612538565b60405180910390a350505050505050505050505050565b60026000541415611d415760405162461bcd60e51b81526004016108df90612488565b6002600055611d717f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca8383611dd8565b50506001600055565b60026000541415611d9d5760405162461bcd60e51b81526004016108df90612488565b6002600055611dce7f000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca848484611e2e565b5050600160005550565b6103c68363a9059cbb60e01b8484604051602401611df7929190612247565b60408051601f198184030181529190526020810180516001600160e01b03166001600160e01b031990931692909217909152611e55565b611e4f846323b872dd60e01b858585604051602401611df793929190612204565b50505050565b6000611eaa826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316611ee49092919063ffffffff16565b8051909150156103c65780806020019051810190611ec891906120a7565b6103c65760405162461bcd60e51b81526004016108df9061243e565b6060611ef38484600085611efb565b949350505050565b606082471015611f1d5760405162461bcd60e51b81526004016108df9061230a565b611f2685611fbb565b611f425760405162461bcd60e51b81526004016108df90612407565b600080866001600160a01b03168587604051611f5e91906121d4565b60006040518083038185875af1925050503d8060008114611f9b576040519150601f19603f3d011682016040523d82523d6000602084013e611fa0565b606091505b5091509150611fb0828286611fc1565b979650505050505050565b3b151590565b60608315611fd05750816109b4565b825115611fe05782518084602001fd5b8160405162461bcd60e51b81526004016108df919061227b565b6040805160a08101825260008082526020820181905291810182905260608101829052608081019190915290565b600060208284031215612039578081fd5b81356109b4816126a6565b600060208284031215612055578081fd5b81516109b4816126a6565b60008060408385031215612072578081fd5b823561207d816126a6565b946020939093013593505050565b60006020828403121561209c578081fd5b81356109b4816126be565b6000602082840312156120b8578081fd5b81516109b4816126be565b6000602082840312156120d4578081fd5b81516001600160c01b03811681146109b4578182fd5b6000602082840312156120fb578081fd5b5051919050565b60008060408385031215612114578182fd5b50508035926020909101359150565b600080600060608486031215612137578081fd5b83359250602084013591506040840135612150816126be565b809150509250925092565b60008060006060848603121561216f578283fd5b8335925060208401356001600160401b038116811461218c578283fd5b91506040840135612150816126be565b6000602082840312156121ad578081fd5b81356109b4816126cc565b6000602082840312156121c9578081fd5b81516109b4816126cc565b600082516121e6818460208701612664565b9190910192915050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b0392909216825263ffffffff16602082015260400190565b6001600160a01b03929092168252602082015260400190565b901515815260200190565b9115158252602082015260400190565b600060208252825180602084015261229a816040850160208701612664565b601f01601f19169190910160400192915050565b6020808252601b908201527f6d6178206c6f636b20706572696f642069732033363520646179730000000000604082015260600190565b6020808252600b908201526a1e995c9bc8185b5bdd5b9d60aa1b604082015260600190565b60208082526026908201527f416464726573733a20696e73756666696369656e742062616c616e636520666f6040820152651c8818d85b1b60d21b606082015260800190565b6020808252601c908201527f6c6f636b2073686f756c6420626520696e207468652066757475726500000000604082015260600190565b6020808252601590820152741a5b9d985b1a59081b1bd8dac81a5b9d195c9d985b605a1b604082015260600190565b60208082526010908201526f696e76616c6964206e6577206c6f636b60801b604082015260600190565b6020808252600d908201526c1858d8d95cdcc819195b9a5959609a1b604082015260600190565b6020808252601d908201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e7472616374000000604082015260600190565b6020808252602a908201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6040820152691bdd081cdd58d8d9595960b21b606082015260800190565b6020808252601f908201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c00604082015260600190565b602080825260149082015273616d6f756e742065786365656473207374616b6560601b604082015260600190565b600060a082019050825182526020830151602083015260408301516001600160401b038082166040850152806060860151166060850152505060808301511515608083015292915050565b90815260200190565b93845260208401929092526040830152606082015260800190565b9182526001600160401b0316602082015260400190565b9283526001600160401b03918216602084015216604082015260600190565b63ffffffff91909116815260200190565b63ffffffff92831681529116602082015260400190565b6001600160401b0391909116815260200190565b600082198211156125e1576125e1612690565b500190565b60008261260157634e487b7160e01b81526012600452602481fd5b500490565b600081600019048311821515161561262057612620612690565b500290565b60008282101561263757612637612690565b500390565b60006001600160401b038381169083168181101561265c5761265c612690565b039392505050565b60005b8381101561267f578181015183820152602001612667565b83811115611e4f5750506000910152565b634e487b7160e01b600052601160045260246000fd5b6001600160a01b03811681146126bb57600080fd5b50565b80151581146126bb57600080fd5b63ffffffff811681146126bb57600080fdfea2646970667358221220eea0765ce21f41ccb5e0c687f1b68cabe019c9c106e5bc574b07ceae3efc788864736f6c63430008010033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

000000000000000000000000767fe9edc9e0df98e07454847909b5e959d7ca0e000000000000000000000000398aea1c9ceb7de800284bb399a15e0efe5a9ec20000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c7000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca0000000000000000000000000000000000000000000000000000000000cb43a500000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000ce3fcc

-----Decoded View---------------
Arg [0] : _ilv (address): 0x767FE9EDC9E0dF98E07454847909b5E959D7ca0E
Arg [1] : _silv (address): 0x398AeA1c9ceb7dE800284bb399A15e0Efe5A9EC2
Arg [2] : _factory (address): 0x2996222cb2bF3675e5f5f88A5F211736197F03C7
Arg [3] : _internalToken (address): 0x514910771AF9Ca656af840dff83E8264EcF986CA
Arg [4] : _initBlock (uint64): 13321125
Arg [5] : _weight (uint32): 20
Arg [6] : _endBlock (uint64): 13516748

-----Encoded View---------------
7 Constructor Arguments found :
Arg [0] : 000000000000000000000000767fe9edc9e0df98e07454847909b5e959d7ca0e
Arg [1] : 000000000000000000000000398aea1c9ceb7de800284bb399a15e0efe5a9ec2
Arg [2] : 0000000000000000000000002996222cb2bf3675e5f5f88a5f211736197f03c7
Arg [3] : 000000000000000000000000514910771af9ca656af840dff83e8264ecf986ca
Arg [4] : 0000000000000000000000000000000000000000000000000000000000cb43a5
Arg [5] : 0000000000000000000000000000000000000000000000000000000000000014
Arg [6] : 0000000000000000000000000000000000000000000000000000000000ce3fcc


Deployed Bytecode Sourcemap

181512:3568:0:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;181798:22;;;:::i;:::-;;;;;;;:::i;:::-;;;;;;;;70700:37;;;:::i;:::-;;;;;;;:::i;163925:319::-;;;;;;:::i;:::-;;:::i;:::-;;159592:1339;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;181890:48::-;;;:::i;:::-;;;;;;;:::i;153030:38::-;;;:::i;161606:207::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;:::i;153468:44::-;;;:::i;183329:165::-;;;:::i;166235:347::-;;;;;;:::i;:::-;;:::i;179961:130::-;;;:::i;165787:167::-;;;;;;:::i;:::-;;:::i;162683:219::-;;;;;;:::i;:::-;;:::i;179568:129::-;;;:::i;161120:171::-;;;;;;:::i;:::-;;:::i;163268:217::-;;;;;;:::i;:::-;;:::i;178388:211::-;;;;;;:::i;:::-;;:::i;153264:38::-;;;:::i;179082:217::-;;;;;;:::i;:::-;;:::i;153370:29::-;;;:::i;:::-;;;;;;;:::i;152918:37::-;;;;;;:::i;:::-;;:::i;:::-;;;;;;;;;;:::i;153869:42::-;;;:::i;153141:44::-;;;:::i;159252:94::-;;;:::i;162080:177::-;;;;;;:::i;:::-;;:::i;153721:45::-;;;:::i;164811:100::-;;;:::i;181798:22::-;;;-1:-1:-1;;;;;181798:22:0;;:::o;70700:37::-;;;:::o;163925:319::-;164076:7;:5;:7::i;:::-;164090:43;164106:10;164118:7;164127:5;164090:15;:43::i;:::-;;164186:52;164203:10;164215:9;164226:11;164186:16;:52::i;:::-;163925:319;;;:::o;159592:1339::-;160036:21;;159670:7;;;;-1:-1:-1;;;160036:21:0;;-1:-1:-1;;;;;160036:21:0;160020:13;:11;:13::i;:::-;:37;:64;;;;-1:-1:-1;160061:18:0;;:23;;160020:64;160016:665;;;160095:16;160114:7;-1:-1:-1;;;;;160114:16:0;;:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;160095:37;;;;160141:18;160185:8;160169:13;:11;:13::i;:::-;:24;:99;;160247:21;;-1:-1:-1;;;160247:21:0;;-1:-1:-1;;;;;160247:21:0;160231:13;:11;:13::i;:::-;:37;;;;:::i;:::-;160169:99;;;160207:21;;160196:32;;-1:-1:-1;;;160207:21:0;;-1:-1:-1;;;;;160207:21:0;160196:8;:32;:::i;:::-;160141:127;;160277:18;160346:7;-1:-1:-1;;;;;160346:19:0;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;160298:69;;160321:7;-1:-1:-1;;;;;160321:19:0;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;160312:6;;-1:-1:-1;;;;;160299:43:0;;;;;:19;;160312:6;;160299:10;:19;:::i;:::-;:43;;;;:::i;:::-;160298:69;;;;:::i;:::-;160277:90;;160511:21;;160462:46;160477:10;160489:18;;160462:14;:46::i;:::-;:70;;;;:::i;:::-;160435:97;;160016:665;;;;;;-1:-1:-1;160652:21:0;;160016:665;-1:-1:-1;;;;;160782:14:0;;160763:16;160782:14;;;:5;:14;;;;;;;;160763:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;160782:14;;160763:33;;;;;;:16;;:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;160763:33:0;;;;;;;;;;-1:-1:-1;;;160763:33:0;;;;;;;;;-1:-1:-1;;;160763:33:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;160803:15;160882:4;:20;;;160821:58;160836:4;:16;;;160854:24;160821:14;:58::i;:::-;:81;;;;:::i;:::-;160803:99;159592:1339;-1:-1:-1;;;;;159592:1339:0:o;181890:48::-;181934:4;181890:48;:::o;153030:38::-;;;:::o;161606:207::-;161693:14;;:::i;:::-;-1:-1:-1;;;;;161774:12:0;;;;;;:5;:12;;;;;:21;;:33;;161796:10;;161774:33;;;;-1:-1:-1;;;161774:33:0;;;;;;;;;;;;;;;;;;161767:40;;;;;;;;161774:33;;;;;;;;161767:40;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;161767:40:0;;;;;;;;;;-1:-1:-1;;;161767:40:0;;;;;;;;;-1:-1:-1;;;161767:40:0;;;;;;;;;;;;161606:207;-1:-1:-1;;;161606:207:0:o;153468:44::-;;;-1:-1:-1;;;153468:44:0;;-1:-1:-1;;;;;153468:44:0;;:::o;183329:165::-;183480:8;;183376:4;;-1:-1:-1;;;;;183480:8:0;183463:13;:11;:13::i;:::-;:25;;183456:32;;183329:165;:::o;166235:347::-;166354:10;-1:-1:-1;;;;;166376:7:0;166354:30;;166346:56;;;;-1:-1:-1;;;166346:56:0;;;;;;;:::i;:::-;;;;;;;;;166502:6;;166472:46;;166490:10;;166472:46;;;;166502:6;;;166510:7;;166472:46;:::i;:::-;;;;;;;;166560:6;:16;;-1:-1:-1;;166560:16:0;;;;;;;;;;;;166235:347::o;179961:130::-;180070:15;179961:130;:::o;165787:167::-;165905:43;165921:10;165933:8;165943:4;165905:15;:43::i;:::-;;165787:167;:::o;162683:219::-;162840:56;162847:10;162859:7;162868:10;162880:8;162890:5;162840:6;:56::i;179568:129::-;179679:12;179568:129;:::o;161120:171::-;-1:-1:-1;;;;;161261:12:0;161186:7;161261:12;;;:5;:12;;;;;:24;;161120:171::o;163268:217::-;163428:51;163437:10;163449;163461:7;163470:8;163428;:51::i;178388:211::-;178475:7;155022:4;178536:25;178546:15;178536:7;:25;:::i;:::-;178535:58;;;;:::i;:::-;178528:65;178388:211;-1:-1:-1;;;178388:211:0:o;153264:38::-;;;:::o;179082:217::-;179168:7;179278:15;179237:37;155022:4;179237:6;:37;:::i;153370:29::-;;;;;;:::o;152918:37::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;153869:42::-;;;;:::o;153141:44::-;;;:::o;159252:94::-;159335:4;159252:94;:::o;162080:177::-;-1:-1:-1;;;;;162223:12:0;162154:7;162223:12;;;:5;:12;;;;;:21;;:28;;162080:177::o;153721:45::-;;;;:::o;164811:100::-;164898:7;:5;:7::i;:::-;164811:100::o;184659:418::-;184741:16;:14;:16::i;:::-;184737:256;;;184807:6;;;;:11;184803:149;;184900:42;;-1:-1:-1;;;184900:42:0;;-1:-1:-1;;;;;184900:7:0;:24;;;;:42;;184933:4;;184940:1;;184900:42;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;184803:149;184979:7;;184737:256;185058:13;:11;:13::i;175037:1147::-;175158:20;175239:11;175235:41;;;175261:7;:5;:7::i;:::-;175368:29;175389:7;175368:20;:29::i;:::-;175353:44;-1:-1:-1;175466:17:0;175462:31;;-1:-1:-1;175492:1:0;175485:8;;175462:31;-1:-1:-1;;;;;175593:14:0;;175573:17;175593:14;;;:5;:14;;;;;175645:256;;;;175690:31;175699:7;175708:12;175690:8;:31::i;:::-;175645:256;;;175804:27;;-1:-1:-1;;;175804:27:0;;175786:15;;-1:-1:-1;;;;;175804:7:0;:22;;;;:27;;175827:3;;175804:27;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;175840:53;;-1:-1:-1;;;175840:53:0;;175786:45;;-1:-1:-1;;;;;;175840:30:0;;;;;:53;;175871:7;;175880:12;;175840:53;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;175645:256;;175978:11;175974:112;;;176023:55;176038:4;:16;;;176056:21;;176023:14;:55::i;:::-;176000:20;;;:78;175974:112;176146:7;-1:-1:-1;;;;;176121:57:0;176134:10;-1:-1:-1;;;;;176121:57:0;;176155:8;176165:12;176121:57;;;;;;;:::i;:::-;;;;;;;;175037:1147;;;;;;:::o;176400:1688::-;176575:8;:6;:8::i;:::-;176560:12;-1:-1:-1;;;;;176560:23:0;;176552:64;;;;-1:-1:-1;;;176552:64:0;;;;;;;:::i;:::-;-1:-1:-1;;;;;176711:14:0;;176691:17;176711:14;;;:5;:14;;;;;176837:13;;;:25;;176711:14;;176691:17;176851:10;;176837:25;;;;-1:-1:-1;;;176837:25:0;;;;;;;;;;;;;;;;;;;176806:56;;176947:12;:24;;;;;;;;;;-1:-1:-1;;;;;176947:24:0;-1:-1:-1;;;;;176932:39:0;:12;-1:-1:-1;;;;;176932:39:0;;176924:68;;;;-1:-1:-1;;;176924:68:0;;;;;;;:::i;:::-;177056:23;;;;-1:-1:-1;;;;;177056:23:0;177052:290;;177130:8;177118;:6;:8::i;:::-;177103:23;;-1:-1:-1;;;;;177103:23:0;;;:::i;:::-;:35;;177095:75;;;;-1:-1:-1;;;177095:75:0;;;;;;;:::i;:::-;177212:8;:6;:8::i;:::-;177179:23;;;:42;;-1:-1:-1;;177179:42:0;-1:-1:-1;;;;;177179:42:0;;;;;;;;;;177052:290;;;177267:23;;;;177294:8;;177252:38;;-1:-1:-1;;;;;177267:23:0;177252:12;:38;:::i;:::-;-1:-1:-1;;;;;177252:50:0;;;177244:90;;;;-1:-1:-1;;;177244:90:0;;;;;;;:::i;:::-;177406:24;;;:39;;-1:-1:-1;;;;177406:39:0;-1:-1:-1;;;;;;;;177406:39:0;;;;;;;;;;;;;177597:24;;-1:-1:-1;;177597:24:0;;154568:3;;177560:8;;154568:3;;177480:50;;177507:23;;;;177480:24;;:50;:::i;:::-;-1:-1:-1;;;;;177479:72:0;;;;;:::i;:::-;177478:90;;;;:::i;:::-;:115;;;;:::i;:::-;177477:144;;;;:::i;:::-;177684:19;;;;;;177732:31;;;;177850:16;;;;177452:169;;-1:-1:-1;177684:19:0;177452:169;;177850:33;;177684:19;;177850:33;:::i;:::-;:45;;;;:::i;:::-;177831:16;;;:64;177923:18;;177961:9;;177923:35;;177944:14;;177923:35;:::i;:::-;:47;;;;:::i;:::-;177902:18;:68;178044:23;;;;178006:76;;-1:-1:-1;;;;;178006:76:0;;;;;;;178032:10;;-1:-1:-1;;;;;178044:23:0;;;;178069:12;;178006:76;:::i;:::-;;;;;;;;176400:1688;;;;;;;:::o;183889:495::-;184301:77;184314:7;184323;184339:8;:6;:8::i;:::-;:19;;184350:8;184339:19;:::i;:::-;184361:7;184370;184301:12;:77::i;:::-;183889:495;;;;;:::o;170760:2166::-;170946:1;170936:7;:11;170928:35;;;;-1:-1:-1;;;170928:35:0;;;;;;;:::i;:::-;-1:-1:-1;;;;;171058:14:0;;171038:17;171058:14;;;:5;:14;;;;;171184:13;;;:25;;171058:14;;171038:17;171198:10;;171184:25;;;;-1:-1:-1;;;171184:25:0;;;;;;;;;;;;;;;;;;;;;;171319:20;;;;171469:24;;171184:25;;-1:-1:-1;;;;171319:20:0;;;;;-1:-1:-1;;171469:35:0;171461:68;;;;-1:-1:-1;;;171461:68:0;;;;;;;:::i;:::-;171574:7;:5;:7::i;:::-;171639:41;171655:7;171664:8;171674:5;171639:15;:41::i;:::-;-1:-1:-1;171749:19:0;;;;171921:24;;171724:22;;171921:34;;171948:7;;171921:34;:::i;:::-;171830:23;;;;154568:3;;171883:8;;154568:3;;171803:50;;-1:-1:-1;;;;;171830:23:0;;;;-1:-1:-1;;;171803:24:0;;;:50;:::i;:::-;-1:-1:-1;;;;;171802:72:0;;;;;:::i;:::-;171801:90;;;;:::i;:::-;:115;;;;:::i;:::-;171800:156;;;;:::i;:::-;172026:24;;171775:181;;-1:-1:-1;172026:34:0;;172053:7;;172026:34;:::i;:::-;172022:192;;172083:4;:13;;172097:10;172083:25;;;;;;-1:-1:-1;;;172083:25:0;;;;;;;;;;;;;;;;;;;;;172076:32;;;;;;;;;;;;;;-1:-1:-1;;172076:32:0;;;172022:192;;;172159:7;172131:12;:24;;;:35;;;;;;;:::i;:::-;;;;-1:-1:-1;;172175:19:0;;;:31;;;172022:192;172269:7;172249:4;:16;;;:27;;;;;;;:::i;:::-;;;;-1:-1:-1;;172302:16:0;;;;172338:9;;172302:33;;172321:14;;172302:33;:::i;:::-;:45;;;;:::i;:::-;172283:16;;;:64;;;172410:21;;172377:55;;172283:64;172377:14;:55::i;:::-;172354:20;;;:78;172493:18;;172531:9;;172493:35;;172514:14;;172493:35;:::i;:::-;:47;;;;:::i;:::-;172472:18;:68;172621:226;;;;172684:40;;-1:-1:-1;;;172684:40:0;;-1:-1:-1;;;;;172684:7:0;:19;;;;:40;;172704:10;;172716:7;;172684:40;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;172621:226;;;172801:38;172819:10;172831:7;172801:17;:38::i;:::-;172903:7;-1:-1:-1;;;;;172882:38:0;172891:10;-1:-1:-1;;;;;172882:38:0;;172912:7;172882:38;;;;;;:::i;:::-;;;;;;;;170760:2166;;;;;;;;;:::o;173176:1377::-;173279:7;-1:-1:-1;;;;;173279:25:0;;:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;173275:77;;;173317:7;-1:-1:-1;;;;;173317:25:0;;:27;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;173275:77;173467:16;173486:7;-1:-1:-1;;;;;173486:16:0;;:18;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;173515:21;;173467:37;;;;;;-1:-1:-1;;;;173515:21:0;;-1:-1:-1;;;;;173515:21:0;-1:-1:-1;;173511:62:0;;173559:7;;;173511:62;173600:21;;-1:-1:-1;;;173600:21:0;;-1:-1:-1;;;;;173600:21:0;173583:13;:11;:13::i;:::-;:38;173579:67;;173632:7;;;173579:67;173737:18;;173733:106;;173802:13;:11;:13::i;:::-;173771:21;;:45;;;;;-1:-1:-1;;;;;173771:45:0;;;;;-1:-1:-1;;;;;173771:45:0;;;;;;173825:7;;;173733:106;173940:20;173979:8;173963:13;:11;:13::i;:::-;:24;:51;;174001:13;:11;:13::i;:::-;173963:51;;;173990:8;173963:51;174059:21;;173940:74;;-1:-1:-1;174021:20:0;;174044:36;;-1:-1:-1;;;174059:21:0;;-1:-1:-1;;;;;174059:21:0;173940:74;174044:36;:::i;:::-;174021:59;;174087:19;174109:7;-1:-1:-1;;;;;174109:19:0;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;174087:43:0;;;174168:17;174228:7;-1:-1:-1;;;;;174228:19:0;;:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;174218:6;;174188:61;;;;;174218:6;174189:26;174204:11;174189:12;:26;:::i;:::-;:35;;;;:::i;:::-;174188:61;;;;:::i;:::-;174168:81;;174345:45;174360:9;174371:18;;174345:14;:45::i;:::-;174320:21;;:70;;;;;;;:::i;:::-;;;;-1:-1:-1;;174397:21:0;:44;;-1:-1:-1;;174397:44:0;-1:-1:-1;;;;;;;;174397:44:0;;;;;;;;;;;;;174502:21;;174477:70;;174490:10;;174477:70;;;;174525:21;;;;;174477:70;:::i;:::-;;;;;;;;173176:1377;;;;;:::o;166968:330::-;-1:-1:-1;;;;;167126:14:0;;167038:15;167126:14;;;:5;:14;;;;;;;;167107:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;167038:15;;167107:33;167126:14;;167107:33;;;;;;;;167038:15;;167107:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;167107:33:0;;;;;;;;;;-1:-1:-1;;;167107:33:0;;;;;;;;;-1:-1:-1;;;167107:33:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;167272:4;:20;;;167214:55;167229:4;:16;;;167247:21;;167214:14;:55::i;:::-;:78;;;;:::i;180300:154::-;180403:45;;-1:-1:-1;;;180403:45:0;;-1:-1:-1;;;;;180425:4:0;180403:32;;;;:45;;180436:3;;180441:6;;180403:45;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;180300:154;;:::o;167821:2567::-;168020:1;168010:7;:11;168002:35;;;;-1:-1:-1;;;168002:35:0;;;;;;;:::i;:::-;-1:-1:-1;;;;;168060:15:0;;;;:79;;;168093:8;:6;:8::i;:::-;168080:10;-1:-1:-1;;;;;168080:21:0;;:58;;;;;168130:8;168118;:6;:8::i;:::-;168105:21;;-1:-1:-1;;;;;168105:21:0;;;:::i;:::-;:33;;168080:58;168044:134;;;;-1:-1:-1;;;168044:134:0;;;;;;;:::i;:::-;168223:7;:5;:7::i;:::-;-1:-1:-1;;;;;168325:14:0;;168305:17;168325:14;;;:5;:14;;;;;168397:16;;:20;168393:84;;168428:41;168444:7;168453:8;168463:5;168428:15;:41::i;:::-;;168393:84;168684:46;;-1:-1:-1;;;168684:46:0;;168658:23;;-1:-1:-1;;;;;168691:13:0;168684:31;;;;:46;;168724:4;;168684:46;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;168658:72;;168802:66;168832:10;168853:4;168860:7;168802:21;:66::i;:::-;168986:46;;-1:-1:-1;;;168986:46:0;;168965:18;;-1:-1:-1;;;;;168993:13:0;168986:31;;;;:46;;169026:4;;168986:46;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;168965:67;-1:-1:-1;169099:19:0;169121:28;169134:15;168965:67;169121:28;:::i;:::-;169099:50;;169350:15;169381:1;169368:10;-1:-1:-1;;;;;169368:14:0;;:37;;169404:1;169368:37;;;169392:8;:6;:8::i;:::-;169350:55;-1:-1:-1;169431:10:0;169412:16;169606:11;154568:3;169574:8;154568:3;169529:20;169350:55;169431:10;169529:20;:::i;:::-;-1:-1:-1;;;;;169528:42:0;;;;;:::i;:::-;169527:55;;;;:::i;:::-;:75;;;;:::i;:::-;169526:91;;;;:::i;:::-;169499:118;;169687:1;169673:11;:15;169666:23;;-1:-1:-1;;;169666:23:0;;;;;;;;;169794:153;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;169794:153:0;;;;;;;;;;;;;;;;;;;;;;;;;;;170020:13;;;:27;;;;;;;;-1:-1:-1;170020:27:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;170020:27:0;;;;;;;;;;-1:-1:-1;;;;170020:27:0;-1:-1:-1;;;170020:27:0;;;;;;;;;-1:-1:-1;;;;170020:27:0;-1:-1:-1;;;170020:27:0;;;;;;;;;;;;;;170083:31;;169794:153;;;;170020:13;;-1:-1:-1;170083:31:0;;169794:153;;170083:31;:::i;:::-;;;;;;;;170141:11;170121:4;:16;;;:31;;;;;;;:::i;:::-;;;;;;;;170182:55;170197:4;:16;;;170215:21;;170182:14;:55::i;:::-;170159:4;:20;;:78;;;;170299:11;170277:18;;:33;;;;;;;:::i;:::-;;;;;;;;170365:7;-1:-1:-1;;;;;170346:36:0;170353:10;-1:-1:-1;;;;;170346:36:0;;170374:7;170346:36;;;;;;:::i;:::-;;;;;;;;167821:2567;;;;;;;;;;;;;:::o;180612:190::-;4567:1;5130:7;;:19;;5122:63;;;;-1:-1:-1;;;5122:63:0;;;;;;;:::i;:::-;4567:1;5255:7;:18;180738:58:::1;180768:13;180784:3:::0;180789:6;180738:22:::1;:58::i;:::-;-1:-1:-1::0;;4525:1:0;5418:7;:22;180612:190::o;180964:240::-;4567:1;5130:7;;:19;;5122:63;;;;-1:-1:-1;;;5122:63:0;;;;;;;:::i;:::-;4567:1;5255:7;:18;181129:69:::1;181163:13;181179:5:::0;181186:3;181191:6;181129:26:::1;:69::i;:::-;-1:-1:-1::0;;4525:1:0;5418:7;:22;-1:-1:-1;180964:240:0:o;136572:191::-;136671:86;136691:5;136721:23;;;136746:2;136750:5;136698:58;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;136698:58:0;;;;;;;;;;;;;;-1:-1:-1;;;;;136698:58:0;-1:-1:-1;;;;;;136698:58:0;;;;;;;;;;136671:19;:86::i;136769:224::-;136891:96;136911:5;136941:27;;;136970:4;136976:2;136980:5;136918:68;;;;;;;;;;:::i;136891:96::-;136769:224;;;;:::o;138835:730::-;139243:23;139269:69;139297:4;139269:69;;;;;;;;;;;;;;;;;139277:5;-1:-1:-1;;;;;139269:27:0;;;:69;;;;;:::i;:::-;139349:17;;139243:95;;-1:-1:-1;139349:21:0;139345:215;;139486:10;139475:30;;;;;;;;;;;;:::i;:::-;139467:85;;;;-1:-1:-1;;;139467:85:0;;;;;;;:::i;131865:209::-;131988:12;132016:52;132038:6;132046:4;132052:1;132055:12;132016:21;:52::i;:::-;132009:59;131865:209;-1:-1:-1;;;;131865:209:0:o;132907:533::-;133059:12;133113:5;133088:21;:30;;133080:81;;;;-1:-1:-1;;;133080:81:0;;;;;;;:::i;:::-;133176:18;133187:6;133176:10;:18::i;:::-;133168:60;;;;-1:-1:-1;;;133168:60:0;;;;;;;:::i;:::-;133294:12;133308:23;133335:6;-1:-1:-1;;;;;133335:11:0;133355:5;133363:4;133335:33;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;133293:75;;;;133382:52;133400:7;133409:10;133421:12;133382:17;:52::i;:::-;133375:59;132907:533;-1:-1:-1;;;;;;;132907:533:0:o;129073:404::-;129423:20;129463:8;;;129073:404::o;135384:659::-;135520:12;135545:7;135541:497;;;-1:-1:-1;135570:10:0;135563:17;;135541:497;135668:17;;:21;135664:367;;135897:10;135891:17;135948:15;135935:10;135931:2;135927:19;135920:44;135855:120;136008:12;136001:20;;-1:-1:-1;;;136001:20:0;;;;;;;;:::i;-1:-1:-1:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;14:259:1:-;;126:2;114:9;105:7;101:23;97:32;94:2;;;147:6;139;132:22;94:2;191:9;178:23;210:33;237:5;210:33;:::i;278:263::-;;401:2;389:9;380:7;376:23;372:32;369:2;;;422:6;414;407:22;369:2;459:9;453:16;478:33;505:5;478:33;:::i;546:327::-;;;675:2;663:9;654:7;650:23;646:32;643:2;;;696:6;688;681:22;643:2;740:9;727:23;759:33;786:5;759:33;:::i;:::-;811:5;863:2;848:18;;;;835:32;;-1:-1:-1;;;633:240:1:o;878:253::-;;987:2;975:9;966:7;962:23;958:32;955:2;;;1008:6;1000;993:22;955:2;1052:9;1039:23;1071:30;1095:5;1071:30;:::i;1136:257::-;;1256:2;1244:9;1235:7;1231:23;1227:32;1224:2;;;1277:6;1269;1262:22;1224:2;1314:9;1308:16;1333:30;1357:5;1333:30;:::i;1398:310::-;;1521:2;1509:9;1500:7;1496:23;1492:32;1489:2;;;1542:6;1534;1527:22;1489:2;1573:16;;-1:-1:-1;;;;;1618:31:1;;1608:42;;1598:2;;1669:6;1661;1654:22;1713:194;;1836:2;1824:9;1815:7;1811:23;1807:32;1804:2;;;1857:6;1849;1842:22;1804:2;-1:-1:-1;1885:16:1;;1794:113;-1:-1:-1;1794:113:1:o;1912:258::-;;;2041:2;2029:9;2020:7;2016:23;2012:32;2009:2;;;2062:6;2054;2047:22;2009:2;-1:-1:-1;;2090:23:1;;;2160:2;2145:18;;;2132:32;;-1:-1:-1;1999:171:1:o;2175:389::-;;;;2318:2;2306:9;2297:7;2293:23;2289:32;2286:2;;;2339:6;2331;2324:22;2286:2;2380:9;2367:23;2357:33;;2437:2;2426:9;2422:18;2409:32;2399:42;;2491:2;2480:9;2476:18;2463:32;2504:30;2528:5;2504:30;:::i;:::-;2553:5;2543:15;;;2276:288;;;;;:::o;2569:509::-;;;;2711:2;2699:9;2690:7;2686:23;2682:32;2679:2;;;2732:6;2724;2717:22;2679:2;2773:9;2760:23;2750:33;;2833:2;2822:9;2818:18;2805:32;-1:-1:-1;;;;;2870:5:1;2866:30;2859:5;2856:41;2846:2;;2916:6;2908;2901:22;2846:2;2944:5;-1:-1:-1;3001:2:1;2986:18;;2973:32;3014;2973;3014;:::i;3083:257::-;;3194:2;3182:9;3173:7;3169:23;3165:32;3162:2;;;3215:6;3207;3200:22;3162:2;3259:9;3246:23;3278:32;3304:5;3278:32;:::i;3345:261::-;;3467:2;3455:9;3446:7;3442:23;3438:32;3435:2;;;3488:6;3480;3473:22;3435:2;3525:9;3519:16;3544:32;3570:5;3544:32;:::i;3611:274::-;;3778:6;3772:13;3794:53;3840:6;3835:3;3828:4;3820:6;3816:17;3794:53;:::i;:::-;3863:16;;;;;3748:137;-1:-1:-1;;3748:137:1:o;3890:203::-;-1:-1:-1;;;;;4054:32:1;;;;4036:51;;4024:2;4009:18;;3991:102::o;4098:375::-;-1:-1:-1;;;;;4356:15:1;;;4338:34;;4408:15;;;;4403:2;4388:18;;4381:43;4455:2;4440:18;;4433:34;;;;4288:2;4273:18;;4255:218::o;4478:298::-;-1:-1:-1;;;;;4677:32:1;;;;4659:51;;4758:10;4746:23;4741:2;4726:18;;4719:51;4647:2;4632:18;;4614:162::o;4781:274::-;-1:-1:-1;;;;;4973:32:1;;;;4955:51;;5037:2;5022:18;;5015:34;4943:2;4928:18;;4910:145::o;5060:187::-;5225:14;;5218:22;5200:41;;5188:2;5173:18;;5155:92::o;5252:258::-;5445:14;;5438:22;5420:41;;5492:2;5477:18;;5470:34;5408:2;5393:18;;5375:135::o;5751:383::-;;5900:2;5889:9;5882:21;5932:6;5926:13;5975:6;5970:2;5959:9;5955:18;5948:34;5991:66;6050:6;6045:2;6034:9;6030:18;6025:2;6017:6;6013:15;5991:66;:::i;:::-;6118:2;6097:15;-1:-1:-1;;6093:29:1;6078:45;;;;6125:2;6074:54;;5872:262;-1:-1:-1;;5872:262:1:o;6139:351::-;6341:2;6323:21;;;6380:2;6360:18;;;6353:30;6419:29;6414:2;6399:18;;6392:57;6481:2;6466:18;;6313:177::o;6495:335::-;6697:2;6679:21;;;6736:2;6716:18;;;6709:30;-1:-1:-1;;;6770:2:1;6755:18;;6748:41;6821:2;6806:18;;6669:161::o;6835:402::-;7037:2;7019:21;;;7076:2;7056:18;;;7049:30;7115:34;7110:2;7095:18;;7088:62;-1:-1:-1;;;7181:2:1;7166:18;;7159:36;7227:3;7212:19;;7009:228::o;7242:352::-;7444:2;7426:21;;;7483:2;7463:18;;;7456:30;7522;7517:2;7502:18;;7495:58;7585:2;7570:18;;7416:178::o;7599:345::-;7801:2;7783:21;;;7840:2;7820:18;;;7813:30;-1:-1:-1;;;7874:2:1;7859:18;;7852:51;7935:2;7920:18;;7773:171::o;7949:340::-;8151:2;8133:21;;;8190:2;8170:18;;;8163:30;-1:-1:-1;;;8224:2:1;8209:18;;8202:46;8280:2;8265:18;;8123:166::o;8294:337::-;8496:2;8478:21;;;8535:2;8515:18;;;8508:30;-1:-1:-1;;;8569:2:1;8554:18;;8547:43;8622:2;8607:18;;8468:163::o;8636:353::-;8838:2;8820:21;;;8877:2;8857:18;;;8850:30;8916:31;8911:2;8896:18;;8889:59;8980:2;8965:18;;8810:179::o;8994:406::-;9196:2;9178:21;;;9235:2;9215:18;;;9208:30;9274:34;9269:2;9254:18;;9247:62;-1:-1:-1;;;9340:2:1;9325:18;;9318:40;9390:3;9375:19;;9168:232::o;9405:355::-;9607:2;9589:21;;;9646:2;9626:18;;;9619:30;9685:33;9680:2;9665:18;;9658:61;9751:2;9736:18;;9579:181::o;9765:344::-;9967:2;9949:21;;;10006:2;9986:18;;;9979:30;-1:-1:-1;;;10040:2:1;10025:18;;10018:50;10100:2;10085:18;;9939:170::o;10114:595::-;;10294:3;10283:9;10279:19;10271:27;;10331:6;10325:13;10314:9;10307:32;10395:4;10387:6;10383:17;10377:24;10370:4;10359:9;10355:20;10348:54;10449:4;10441:6;10437:17;10431:24;-1:-1:-1;;;;;10548:2:1;10534:12;10530:21;10523:4;10512:9;10508:20;10501:51;10620:2;10612:4;10604:6;10600:17;10594:24;10590:33;10583:4;10572:9;10568:20;10561:63;;;10694:4;10686:6;10682:17;10676:24;10669:32;10662:40;10655:4;10644:9;10640:20;10633:70;10261:448;;;;:::o;10714:177::-;10860:25;;;10848:2;10833:18;;10815:76::o;10896:391::-;11127:25;;;11183:2;11168:18;;11161:34;;;;11226:2;11211:18;;11204:34;11269:2;11254:18;;11247:34;11114:3;11099:19;;11081:206::o;11292:271::-;11464:25;;;-1:-1:-1;;;;;11525:31:1;11520:2;11505:18;;11498:59;11452:2;11437:18;;11419:144::o;11568:370::-;11766:25;;;-1:-1:-1;;;;;11864:15:1;;;11859:2;11844:18;;11837:43;11916:15;11911:2;11896:18;;11889:43;11754:2;11739:18;;11721:217::o;11943:192::-;12117:10;12105:23;;;;12087:42;;12075:2;12060:18;;12042:93::o;12140:291::-;12320:10;12357:15;;;12339:34;;12409:15;;12404:2;12389:18;;12382:43;12298:2;12283:18;;12265:166::o;12436:200::-;-1:-1:-1;;;;;12598:31:1;;;;12580:50;;12568:2;12553:18;;12535:101::o;12641:128::-;;12712:1;12708:6;12705:1;12702:13;12699:2;;;12718:18;;:::i;:::-;-1:-1:-1;12754:9:1;;12689:80::o;12774:217::-;;12840:1;12830:2;;-1:-1:-1;;;12865:31:1;;12919:4;12916:1;12909:15;12947:4;12872:1;12937:15;12830:2;-1:-1:-1;12976:9:1;;12820:171::o;12996:168::-;;13102:1;13098;13094:6;13090:14;13087:1;13084:21;13079:1;13072:9;13065:17;13061:45;13058:2;;;13109:18;;:::i;:::-;-1:-1:-1;13149:9:1;;13048:116::o;13169:125::-;;13237:1;13234;13231:8;13228:2;;;13242:18;;:::i;:::-;-1:-1:-1;13279:9:1;;13218:76::o;13299:229::-;;-1:-1:-1;;;;;13435:10:1;;;;13405;;13457:12;;;13454:2;;;13472:18;;:::i;:::-;13509:13;;13347:181;-1:-1:-1;;;13347:181:1:o;13533:258::-;13605:1;13615:113;13629:6;13626:1;13623:13;13615:113;;;13705:11;;;13699:18;13686:11;;;13679:39;13651:2;13644:10;13615:113;;;13746:6;13743:1;13740:13;13737:2;;;-1:-1:-1;;13781:1:1;13763:16;;13756:27;13586:205::o;13796:127::-;13857:10;13852:3;13848:20;13845:1;13838:31;13888:4;13885:1;13878:15;13912:4;13909:1;13902:15;13928:133;-1:-1:-1;;;;;14005:31:1;;13995:42;;13985:2;;14051:1;14048;14041:12;13985:2;13975:86;:::o;14066:120::-;14154:5;14147:13;14140:21;14133:5;14130:32;14120:2;;14176:1;14173;14166:12;14191:123;14278:10;14271:5;14267:22;14260:5;14257:33;14247:2;;14304:1;14301;14294:12

Swarm Source

ipfs://eea0765ce21f41ccb5e0c687f1b68cabe019c9c106e5bc574b07ceae3efc7888

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ Download: CSV Export  ]

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.