ETH Price: $3,918.95 (+7.26%)

Contract

0x61cc5dea05B910AAad1798f013E1Bb14334B8503
 

Overview

ETH Balance

0.062557401934327094 ETH

Eth Value

$245.16 (@ $3,918.95/ETH)

Token Holdings

Multichain Info

No addresses found
Age:180D
Amount:Between 1-1M
Reset Filter

Transaction Hash
Method
Block
From
To

There are no matching entries

Update your filters to view other transactions

Latest 8 internal transactions

Advanced mode:
Parent Transaction Hash Block From To
110573642020-10-15 1:19:591519 days ago1602724799
0x61cc5dea...4334B8503
1.41306131 ETH
110573542020-10-15 1:17:571519 days ago1602724677
0x61cc5dea...4334B8503
0.01471938 ETH
110573542020-10-15 1:17:571519 days ago1602724677
0x61cc5dea...4334B8503
0.16559312 ETH
110573542020-10-15 1:17:571519 days ago1602724677
0x61cc5dea...4334B8503
2.18399235 ETH
110573542020-10-15 1:17:571519 days ago1602724677
0x61cc5dea...4334B8503
3.83992358 ETH
109990742020-10-06 0:50:311528 days ago1601945431
0x61cc5dea...4334B8503
2 ETH
109990742020-10-06 0:50:311528 days ago1601945431
0x61cc5dea...4334B8503
2 ETH
109990742020-10-06 0:50:311528 days ago1601945431  Contract Creation0 ETH
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
LotteryStub

Compiler Version
v0.7.1+commit.f4a555be

Optimization Enabled:
Yes with 9000 runs

Other Settings:
default evmVersion

Contract Source Code (Solidity)

/**
 *Submitted for verification at Etherscan.io on 2020-10-06
*/

// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity 0.7.1;
pragma experimental ABIEncoderV2;


// 
/*
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with GSN meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {

    function _msgSender() internal view returns (address payable) {
        return msg.sender;
    }

    function _msgData() internal view returns (bytes memory) {
        this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
        return msg.data;
    }
}

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see {ERC20Detailed}.
 */
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 Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     *
     * _Available since v2.4.0._
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}

/**
 * @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 {ERC20Mintable}.
 *
 * 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}.
 */
contract ERC20 is Context, IERC20 {
    using SafeMath for uint256;

    mapping (address => uint256) private _balances;

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

    uint256 private _totalSupply;

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `recipient` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(_msgSender(), recipient, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        _approve(_msgSender(), spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20};
     *
     * Requirements:
     * - `sender` and `recipient` cannot be the zero address.
     * - `sender` must have a balance of at least `amount`.
     * - the caller must have allowance for `sender`'s tokens of at least
     * `amount`.
     */
    function transferFrom(address sender, address recipient, uint256 amount) public virtual override returns (bool) {
        _transfer(sender, recipient, amount);
        _approve(sender, _msgSender(), _allowances[sender][_msgSender()].sub(amount, "ERC20: transfer amount exceeds allowance"));
        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 returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].add(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 returns (bool) {
        _approve(_msgSender(), spender, _allowances[_msgSender()][spender].sub(subtractedValue, "ERC20: decreased allowance below zero"));
        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");

        _balances[sender] = _balances[sender].sub(amount, "ERC20: transfer amount exceeds balance");
        _balances[recipient] = _balances[recipient].add(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 {
        require(account != address(0), "ERC20: mint to the zero address");

        _totalSupply = _totalSupply.add(amount);
        _balances[account] = _balances[account].add(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 {
        require(account != address(0), "ERC20: burn from the zero address");

        _balances[account] = _balances[account].sub(amount, "ERC20: burn amount exceeds balance");
        _totalSupply = _totalSupply.sub(amount);
        emit Transfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
     *
     * This is 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 Destroys `amount` tokens from `account`.`amount` is then deducted
     * from the caller's allowance.
     *
     * See {_burn} and {_approve}.
     */
    function _burnFrom(address account, uint256 amount) internal {
        _burn(account, amount);
        _approve(account, _msgSender(), _allowances[account][_msgSender()].sub(amount, "ERC20: burn amount exceeds allowance"));
    }
}

// 
// Uniswap V2 Router Interface.
// Used on the Main-Net, and Public Test-Nets.
interface IUniswapRouter {
    // Get Factory and WETH addresses.
    function factory()  external pure returns (address);
    function WETH()     external pure returns (address);

    // Create/add to a liquidity pair using ETH.
    function addLiquidityETH(
        address token,
        uint amountTokenDesired,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline )                 
                                        external 
                                        payable 
        returns (
            uint amountToken, 
            uint amountETH, 
            uint liquidity 
        );

    // Remove liquidity pair.
    function removeLiquidityETHSupportingFeeOnTransferTokens(
        address token,
        uint liquidity,
        uint amountTokenMin,
        uint amountETHMin,
        address to,
        uint deadline ) 
                                        external
        returns (
            uint amountETH
        );

    // Get trade output amount, given an input.
    function getAmountsOut(
        uint amountIn, 
        address[] memory path ) 
                                        external view 
        returns (
            uint[] memory amounts
        );

    // Get trade input amount, given an output.
    function getAmountsIn(
        uint amountOut, 
        address[] memory path )
                                        external view
        returns (
            uint[] memory amounts
        );
}

// Uniswap Factory interface.
// We use it only to obtain the Token Exchange Pair address.
interface IUniswapFactory {
    function getPair(
        address tokenA, 
        address tokenB )
                                        external view 
    returns ( address pair );
}

// Uniswap Pair interface (it's also an ERC20 token).
// Used to get reserves, and token price.
interface IUniswapPair is IERC20
{
    // Addresses of the first and second pool-kens.
    function token0() external view returns (address);
    function token1() external view returns (address);

    // Get the pair's token pool reserves.
    function getReserves() 
                                        external view 
    returns (
        uint112 reserve0, 
        uint112 reserve1,
        uint32 blockTimestampLast
    );
}

// 
/**
 *  The Core Settings contract, which defines the global constants,
 *  which are used in the pool and related contracts (such as 
 *  OWNER_ADDRESS), and also defines the percentage simulation
 *  code, to use the same percentage precision across all contracts.
 */
contract CoreUniLotterySettings {
    // Percentage calculations.
    // As Solidity doesn't have floats, we have to use integers for
    // percentage arithmetics.
    // We set 1 percent to be equal to 1,000,000 - thus, we
    // simulate 6 decimal points when computing percentages.
    uint32 public constant PERCENT = 10 ** 6;
    uint32 constant BASIS_POINT = PERCENT / 100;

    uint32 constant _100PERCENT = 100 * PERCENT;

    /** The UniLottery Owner's address.
     *
     *  In the current version, The Owner has rights to:
     *  - Take up to 10% profit from every lottery.
     *  - Pool liquidity into the pool and unpool it.
     *  - Start new Auto-Mode & Manual-Mode lotteries.
     *  - Set randomness provider gas price & other settings.
     */

    // Public Testnets: 0xb13CB9BECcB034392F4c9Db44E23C3Fb5fd5dc63 
    // MainNet:         0x1Ae51bec001a4fA4E3b06A5AF2e0df33A79c01e2

    address payable public constant OWNER_ADDRESS =
        address( uint160( 0x1Ae51bec001a4fA4E3b06A5AF2e0df33A79c01e2 ) );


    // Maximum lottery fee the owner can imburse on transfers.
    uint32 constant MAX_OWNER_LOTTERY_FEE = 1 * PERCENT;

    // Minimum amout of profit percentage that must be distributed
    // to lottery winners.
    uint32 constant MIN_WINNER_PROFIT_SHARE = 40 * PERCENT;

    // Min & max profits the owner can take from lottery net profit.
    uint32 constant MIN_OWNER_PROFITS = 3 * PERCENT;
    uint32 constant MAX_OWNER_PROFITS = 10 * PERCENT;

    // Min & max amount of lottery profits that the pool must get.
    uint32 constant MIN_POOL_PROFITS = 10 * PERCENT;
    uint32 constant MAX_POOL_PROFITS = 60 * PERCENT;

    // Maximum lifetime of a lottery - 1 month (4 weeks).
    uint32 constant MAX_LOTTERY_LIFETIME = 4 weeks;

    // Callback gas requirements for a lottery's ending callback,
    // and for the Pool's Scheduled Callback.
    // Must be determined empirically.
    uint32 constant LOTTERY_RAND_CALLBACK_GAS = 200000;
    uint32 constant AUTO_MODE_SCHEDULED_CALLBACK_GAS = 3800431;
}

// 
// implement OpenZeppelin's ERC20 token.
// Use the Uniswap Interfaces.
// Use Core Settings.
// Interface of the Main Pool Contract, with the functions that we'll
// be calling from our contract.
interface IUniLotteryPool {
    function lotteryFinish( uint totalReturn, uint profitAmount )
    external payable;
}

// The Randomness Provider interface.
interface IRandomnessProvider {
    function requestRandomSeedForLotteryFinish()    external;
}

/**
 *  Simple, gas-efficient lottery, which uses Uniswap as liquidity provider,
 *  and determines the lottery winners through a 3 different approaches
 *  (explained in detail on EndingAlgoType documentation).
 *
 *  This contract contains all code of the lottery contract, and 
 *  lotteries themselves are just storage-container stubs, which use
 *  DelegateCall mechanism to execute this actual lottery code on
 *  their behalf.
 *
 *  Lottery workflow consists of these consecutive stages:
 *
 *  1. Initialization stage: Main Pool deploys this lottery contract,
 *      and calls initialize() with initial Ether funds provided.
 *      Lottery mints initial token supply and provides the liquidity
 *      to Uniswap, with whole token supply and initial Ether funds.
 *      Then, lottery becomes Active - trading tokens becomes allowed.
 *
 *  2. Active Stage:    Token transfers occurs on this stage, and 
 *      finish probability is Zero. Our ETH funds in Uniswap increases
 *      in this stage.
 *      When certain criteria of holder count and fund gains are met,
 *      the Finishing stage begins.
 *  
 *  3. Finishing Stage:     It can be considered a second part of
 *      an Active stage, because all token transfers and Uniswap trading
 *      are still allowed and occur actively.
 *      However, on this stage, for every transfer, a pseudo-random
 *      number is rolled, and if that rolled number is below a specific
 *      threshold, lottery de-activates, and Ending stage begins, when
 *      token transfers are denied.
 *      The threshold is determined by Finish Probability, which
 *      increases on every transfer on this stage.
 *
 *      However, notice that if Finishing Criteria (holder count and
 *      fund gains) are no-longer met, Finishing Stage pauses, and
 *      we get back to Active Stage.
 *
 *  4. Ending-Mining Stage - Step One:
 *      On this stage, we Remove our contract's liquidity share
 *      from Uniswap, then transfer the profits to the Pool and
 *      the Owner addresses.
 *
 *      Then, we call the Randomness Provider, requesting the Random Seed,
 *      which later should be passed to us by calling our callback
 *      (finish_randomnessProviderCallback).
 *
 *      Miner, who completes this step, gets portion of Mining Rewards,
 *      which are a dedicated profit share to miners.
 *
 *  5. Ending-Mining Stage - Step Two:  On this stage, if  *
 *      However, if Randomness Provider hasn't given us a seed after
 *      specific amount of time, on this step, before starting the
 *      Winner Selection Algorithm, an Alternative Seed Generation
 *      is performed, where the pseudo-random seed is generated based
 *      on data in our and Storage contracts (transfer hash, etc.).
 *
 *      If we're using MinedWinnerSelection ending algorithm type, then
 *      on this step the miner performs the gas-intensive Winner Selection 
 *      Algorithm, which involves complex score calculations in a loop, and
 *      then sorting the selected winners array.
 *
 *      Miner who successfully completes this step, gets a portion of
 *      the Mining Rewards.
 *
 *  6. Completion Stage (Winner Prize Claiming stage):  On this stage,
 *      the Lottery Winners can finally claim their Lottery Prizes,
 *      by calling a prize claim function on our contract.
 *
 *      If we're using WinnerSelfValidation ending algorithm, winner
 *      computes and validates his final score on this function by
 *      himself, so the prize claim transaction can be gas-costly.
 *
 *      However, is RolledRandomness or MinedWinnerSelection algorithms
 *      are used, this function is cheap in terms of gas.
 *
 *      However, if some of winners fail to claim their prizes after
 *      a specific amount of time (specified in config), then those
 *      prizes can then be claimed by Lottery Main Pool too.
 */
contract Lottery is ERC20, CoreUniLotterySettings
{
    // ===================== Events ===================== //

    // After initialize() function finishes.
    event LotteryInitialized();

    // Emitted when lottery active stage ends (Mining Stage starts),
    // on Mining Stage Step 1, after transferring profits to their
    // respective owners (pool and OWNER_ADDRESS).
    event LotteryEnd(
        uint128 totalReturn,
        uint128 profitAmount
    );

    // Emitted when on final finish, we call Randomness Provider
    // to callback us with random value.
    event RandomnessProviderCalled();

    // Requirements for finishing stage start have been reached - 
    // finishing stage has started.
    event FinishingStageStarted();

    // We were currently on the finishing stage, but some requirement
    // is no longer met. We must stop the finishing stage.
    event FinishingStageStopped();

    // New Referral ID has been generated.
    event ReferralIDGenerated(
        address referrer,
        uint256 id
    );

    // New referral has been registered with a valid referral ID.
    event ReferralRegistered(
        address referree,
        address referrer,
        uint256 id
    );

    // Fallback funds received.
    event FallbackEtherReceiver(
        address sender,
        uint value
    );


    // ======================  Structs & Enums  ====================== //

    // Lottery Stages. 
    // Described in more detail above, on contract's main doc.
    enum STAGE
    {
        // Initial stage - before the initialize() function is called.
        INITIAL,

        // Active Stage: On this stage, all token trading occurs.
        ACTIVE,

        // Finishing stage:
        // This is when all finishing criteria are met, and for every
        // transfer, we're rolling a pseudo-random number to determine
        // if we should end the lottery (move to Ending stage).
        FINISHING,

        // Ending - Mining Stage:
        // This stage starts after we lottery is no longer active,
        // finishing stage ends. On this stage, Miners perform the
        // Ending Algorithm and other operations.
        ENDING_MINING,

        // Lottery is completed - this is set after the Mining Stage ends.
        // In this stage, Lottery Winners can claim their prizes.
        COMPLETION,

        // DISABLED stage. Used when we want a lottery contract to be
        // absolutely disabled - so no state-modifying functions could
        // be called.
        // This is used in DelegateCall scenarios, where state-contract
        // delegate-calls code contract, to save on deployment costs.
        DISABLED
    }


    // Ending algorithm types enum.
    enum EndingAlgoType
    {
        // 1. Mined Winner Selection Algorithm.
        //  This algorithm is executed by a Lottery Miner in a single
        //  transaction, on Mining Step 2.
        //
        //  On that single transaction, all ending scores for all
        //  holders are computed, and a sorted winner array is formed,
        //  which is written onto the LotteryStorage state.
        //  Thus, it's gas expensive, and suitable only for small
        //  holder numbers (up to 300).
        //
        // Pros:
        //  + Guaranteed deterministically specifiable winner prize
        //    distribution - for example, if we specify that there
        //    must be 2 winners, of which first gets 60% of prize funds,
        //    and second gets 40% of prize funds, then it's
        //    guarateed that prize funds will be distributed just
        //    like that.
        //
        //  + Low gas cost of prize claims - only ~ 40,000 gas for
        //    claiming a prize.
        //
        // Cons:
        //  - Not scaleable - as the Winner Selection Algorithm is
        //    executed in a single transaction, it's limited by 
        //    block gas limit - 12,500,000 on the MainNet.
        //    Thus, the lottery is limited to ~300 holders, and
        //    max. ~200 winners of those holders.
        //    So, it's suitable for only express-lotteries, where
        //    a lottery runs only until ~300 holders are reached.
        //
        //  - High mining costs - if lottery has 300 holders,
        //    mining transaction takes up whole block gas limit.
        //
        MinedWinnerSelection,

        // 2. Winner Self-Validation Algorithm.
        //
        //  This algorithm does no operations during the Mining Stage
        //  (except for setting up a Random Seed in Lottery Storage) -
        //  the winner selection (obtaining a winner rank) is done by
        //  the winners themselves, when calling the prize claim
        //  functions.
        //
        //  This algorithm relies on a fact that by the time that
        //  random seed is obtained, all data needed for winner selection
        //  is already there - the holder scores of the Active Stage
        //  (ether contributed, time factors, token balance), and
        //  the Random Data (random seed + nonce (holder's address)),
        //  so, there is no need to compute and sort the scores for the
        //  whole holder array.
        //
        //  It's done like this: the holder checks if he's a winner, using
        //  a view-function off-chain, and if so, he calls the 
        //  claimWinnerPrize() function, which obtains his winner rank
        //  on O(n) time, and does no writing to contract states,
        //  except for prize transfer-related operations.
        //
        //  When computing the winner's rank on LotteryStorage,
        //  O(n) time is needed, as we loop through the holders array,
        //  computing ending scores for each holder, using already-known
        //  data. 
        //  However that means that for every prize claim, all scores of
        //  all holders must be re-computed.
        //  Computing a score for a single holder takes roughly 1500 gas
        //  (400 for 3 slots SLOAD, and ~300 for arithmetic operations).
        //
        //  So, this algorithm makes prize claims more expensive for
        //  every lottery holder.
        //  If there's 1000 holders, prize claim takes up 1,500,000 gas,
        //  so, this algorithm is not suitable for small prizes,
        //  because gas fee would be higher than the prize amount won.
        //
        // Pros:
        //  + Guaranteed deterministically specifiable winner prize
        //    distribution (same as for algorithm 1).
        //
        //  + No mining costs for winner selection algorithm.
        //
        //  + More scalable than algorithm 1.
        //
        // Cons:
        //  - High gas costs of prize claiming, rising with the number
        //    of lottery holders - 1500 for every lottery holder.
        //    Thus, suitable for only large prize amounts.
        //
        WinnerSelfValidation,

        // 3. Rolled-Randomness algorithm.
        //
        //  This algorithm is the most cheapest in terms of gas, but
        //  the winner prize distribution is non-deterministic.
        //
        //  This algorithm doesn't employ miners (no mining costs),
        //  and doesn't require to compute scores for every holder
        //  prior to getting a winner's rank, thus is the most scalable.
        //
        //  It works like this: a holder checks his winner status by
        //  computing only his own randomized score (rolling a random
        //  number from the random seed, and multiplying it by holder's
        //  Active Stage score), and computing this randomized-score's
        //  ratio relative to maximum available randomized score.
        //  The higher the ratio, the higher the winner rank is.
        //
        //  However, many players can roll very high or low scores, and
        //  get the same prizes, so it's difficult to make a fair and
        //  efficient deterministic prize distribution mechanism, so
        //  we have to fallback to specific heuristic workarounds.
        //
        // Pros:
        //  + Scalable: O(1) complexity for computing a winner rank,
        //      so there can be an unlimited amount of lottery holders,
        //      and gas costs for winner selection and prize claim would
        //      still be constant & low.
        //
        //  + Gas-efficient: gas costs for all winner-related operations
        //      are constant and low, because only single holder's score
        //      is computed.
        //
        //  + Doesn't require mining - even more gas savings.
        //
        // Cons:
        //  + Hard to make a deterministic and fair prize distribution
        //      mechanism, because of un-known environment - as only
        //      single holder's score is compared to max-available
        //      random score, not taking into account other holder
        //      scores.
        //
        RolledRandomness
    }


    /**
     *  Gas-efficient, minimal config, which specifies only basic,
     *  most-important and most-used settings.
     */
    struct LotteryConfig
    {
        // ================ Misc Settings =============== //

        // --------- Slot --------- //

        // Initial lottery funds (initial market cap).
        // Specified by pool, and is used to check if initial funds 
        // transferred to fallback are correct - equal to this value.
        uint initialFunds;


        // --------- Slot --------- //

        // The minimum ETH value of lottery funds, that, once
        // reached on an exchange liquidity pool (Uniswap, or our
        // contract), must be guaranteed to not shrink below this value.
        // 
        // This is accomplished in _transfer() function, by denying 
        // all sells that would drop the ETH amount in liquidity pool
        // below this value.
        // 
        // But on initial lottery stage, before this minimum requirement
        // is reached for the first time, all sells are allowed.
        //
        // This value is expressed in ETH - total amount of ETH funds
        // that we own in Uniswap liquidity pair.
        //
        // So, if initial funds were 10 ETH, and this is set to 100 ETH,
        // after liquidity pool's ETH value reaches 100 ETH, all further
        // sells which could drop the liquidity amount below 100 ETH,
        // would be denied by require'ing in _transfer() function
        // (transactions would be reverted).
        //
        uint128 fundRequirement_denySells;

        // ETH value of our funds that we own in Uniswap Liquidity Pair,
        // that's needed to start the Finishing Stage.
        uint128 finishCriteria_minFunds;


        // --------- Slot --------- //

        // Maximum lifetime of a lottery - maximum amount of time 
        // allowed for lottery to stay active.
        // By default, it's two weeks.
        // If lottery is still active (hasn't returned funds) after this
        // time, lottery will stop on the next token transfer.
        uint32 maxLifetime;

        // Maximum prize claiming time - for how long the winners
        // may be able to claim their prizes after lottery ending.
        uint32 prizeClaimTime;

        // Token transfer burn rates for buyers, and a default rate for
        // sells and non-buy-sell transfers.
        uint32 burn_buyerRate;
        uint32 burn_defaultRate;

        // Maximum amount of tokens (in percentage of initial supply)
        // to be allowed to own by a single wallet.
        uint32 maxAmountForWallet_percentageOfSupply;

        // The required amount of time that must pass after
        // the request to Randomness Provider has been made, for
        // external actors to be able to initiate alternative
        // seed generation algorithm.
        uint32 REQUIRED_TIME_WAITING_FOR_RANDOM_SEED;
        
        
        // ================ Profit Shares =============== //

        // "Mined Uniswap Lottery" ending Ether funds, which were obtained
        // by removing token liquidity from Uniswap, are transfered to
        // these recipient categories:
        //
        //  1. The Main Pool:   Initial funds, plus Pool's profit share.
        //  2. The Owner:       Owner's profit share.
        //
        //  3. The Miners:      Miner rewards for executing the winner
        //      selection algorithm stages.
        //      The more holders there are, the more stages the 
        //      winner selection algorithm must undergo.
        //      Each Miner, who successfully completed an algorithm
        //      stage, will get ETH reward equal to:
        //      (minerProfitShare / totalAlgorithmStages).
        //
        //  4. The Lottery Winners:     All remaining funds are given to
        //      Lottery Winners, which were determined by executing
        //      the Winner Selection Algorithm at the end of the lottery
        //      (Miners executed it).
        //      The Winners can claim their prizes by calling a 
        //      dedicated function in our contract.
        //
        //  The profit shares of #1 and #2 have controlled value ranges 
        //  specified in CoreUniLotterySettings.
        //
        //  All these shares are expressed as percentages of the
        //  lottery profit amount (totalReturn - initialFunds).
        //  Percentages are expressed using the PERCENT constant, 
        //  defined in CoreUniLotterySettings.
        //
        //  Here we specify profit shares of Pool, Owner, and the Miners.
        //  Winner Prize Fund is all that's left (must be more than 50%
        //  of all profits).
        //

        uint32 poolProfitShare;
        uint32 ownerProfitShare;

        // --------- Slot --------- //

        uint32 minerProfitShare;
        
        
        // =========== Lottery Finish criteria =========== //

        // Lottery finish by design is a whole soft stage, that
        // starts when criteria for holders and fund gains are met.
        // During this stage, for every token transfer, a pseudo-random
        // number will be rolled for lottery finish, with increasing 
        // probability.
        //
        // There are 2 ways that this probability increase is 
        // implemented:
        // 1. Increasing on every new holder.
        // 2. Increasing on every transaction after finish stage
        //    was initiated.
        //
        // On every new holder, probability increases more than on
        // new transactions.
        //
        // However, if during this stage some criteria become 
        // no-longer-met, the finish stage is cancelled.
        // This cancel can be implemented by setting finish probability
        // to zero, or leaving it as it was, but pausing the finishing
        // stage.
        // This is controlled by finish_resetProbabilityOnStop flag -
        // if not set, probability stays the same, when the finishing
        // stage is discontinued. 

        // ETH value of our funds that we own in Uniswap Liquidity Pair,
        // that's needed to start the Finishing Stage.
        //
        // LOOK ABOVE - arranged for tight-packing.

        // Minimum number of token holders required to start the
        // finishing stage.
        uint32 finishCriteria_minNumberOfHolders;

        // Minimum amount of time that lottery must be active.
        uint32 finishCriteria_minTimeActive;

        // Initial finish probability, when finishing stage was
        // just initiated.
        uint32 finish_initialProbability;

        // Finishing probability increase steps, for every new 
        // transaction and every new holder.
        // If holder number decreases, probability decreases.
        uint32 finish_probabilityIncreaseStep_transaction;
        uint32 finish_probabilityIncreaseStep_holder;


        // =========== Winner selection config =========== //

        // Winner selection algorithm settings.
        //
        // Algorithm is based on score, which is calculated for 
        // every holder on lottery finish, and is comprised of
        // the following parts.
        // Each part is normalized to range ( 0 - scorePoints ), 
        // from smallest to largest value of each holder;
        //
        // After scores are computed, they are multiplied by 
        // holder count factor (holderCount / holderCountDivisor),
        // and finally, multiplied by safely-generated random values,
        // to get end winning scores.
        // The top scorers win prizes.
        //
        // By default setting, max score is 40 points, and it's
        // comprised of the following parts:
        //
        // 1. Ether contributed (when buying from Uniswap or contract). 
        //    Gets added when buying, and subtracted when selling.
        //      Default: 10 points.
        //
        // 2. Amount of lottery tokens holder has on finish.
        //      Default: 5 points.
        //
        // 3. Ether contributed, multiplied by the relative factor
        //      of time - that is/*, "block.timestamp" */minus "lotteryStartTime".
        //      This way, late buyers can get more points even if
        //      they get little tokens and don't spend much ether.
        //      Default: 5 points.
        //
        // 4. Refferrer bonus. For every player that joined with
        //      your referral ID, you get (that player's score) / 10 
        //      points! This goes up to specified max score.
        //      Also, every player who provides a valid referral ID,
        //      gets 2 points for free!
        //      Default max bonus: 20 points.
        //
        int16 maxPlayerScore_etherContributed;
        int16 maxPlayerScore_tokenHoldingAmount;
        int16 maxPlayerScore_timeFactor;
        int16 maxPlayerScore_refferalBonus;

        // --------- Slot --------- //

        // Score-To-Random ration data (as a rational ratio number).
        // For example if 1:5, then scorePart = 1, and randPart = 5.
        uint16 randRatio_scorePart;
        uint16 randRatio_randPart;

        // Time factor divisor - interval of time, in seconds, after
        // which time factor is increased by one.
        uint16 timeFactorDivisor;

        // Bonus score a player should get when registering a valid
        // referral code obtained from a referrer.
        int16 playerScore_referralRegisteringBonus;


        // Are we resetting finish probability when finishing stage
        // stops, if some criteria are no longer met?
        bool finish_resetProbabilityOnStop;


        // =========== Winner Prize Fund Settings =========== //

        // There are 2 available modes that we can use to distribute
        // winnings: a computable sequence (geometrical progression),
        // or an array of winner prize fund share percentages.

        // More gas efficient is to use a computable sequence, 
        // where each winner gets a share equal to (factor * fundsLeft).
        // Factor is in range [0.01 - 1.00] - simulated as [1% - 100%].
        //
        // For example:
        // Winner prize fund is 100 ethers, Factor is 1/4 (25%), and 
        // there are 5 winners total (winnerCount), and sequenced winner
        // count is 2 (sequencedWinnerCount).
        //
        // So, we pre-compute the upper shares, till we arrive to the
        // sequenced winner count, in a loop:
        // - Winner 1: 0.25 * 100 = 25 eth; 100 - 25 = 75 eth left.
        // - Winner 2: 0.25 * 75 ~= 19 eth; 75  - 19 = 56 eth left.
        //
        // Now, we compute the left-over winner shares, which are
        // winners that get their prizes from the funds left after the
        // sequence winners.
        //
        // So, we just divide the leftover funds (56 eth), by 3,
        // because winnerCount - sequencedWinnerCount = 3.
        // - Winner 3 = 56 / 3 = 18 eth;
        // - Winner 4 = 56 / 3 = 18 eth;
        // - Winner 5 = 56 / 3 = 18 eth;
        //

        // If this value is 0, then we'll assume that array-mode is
        // to be used.
        uint32 prizeSequenceFactor;

        // Maximum number of winners that the prize sequence can yield,
        // plus the leftover winners, which will get equal shares of
        // the remainder from the first-prize sequence.
        
        uint16 prizeSequence_winnerCount;

        // How many winners would get sequence-computed prizes.
        // The left-over winners
        // This is needed because prizes in sequence tend to zero, so
        // we need to limit the sequence to avoid very small prizes,
        // and to avoid the remainder.
        uint16 prizeSequence_sequencedWinnerCount;

        // Initial token supply (without decimals).
        uint48 initialTokenSupply;

        // Ending Algorithm type.
        // More about the 3 algorithm types above.
        uint8 endingAlgoType;


        // --------- Slot --------- //

        // Array mode: The winner profit share percentages array. 
        // For example, lottery profits can be distributed this way:
        //
        // Winner profit shares (8 winners):
        // [ 20%, 15%, 10%, 5%, 4%, 3%, 2%, 1% ] = 60% of profits.
        // Owner profits: 10%
        // Pool profits:  30%
        //
        // Pool profit share is not defined explicitly in the config, so
        // when we internally validate specified profit shares, we 
        // assume the pool share to be the left amount until 100% ,
        // but we also make sure that this amount is at least equal to
        // MIN_POOL_PROFITS, defined in CoreSettings.
        //
        uint32[] winnerProfitShares;

    }


    // ========================= Constants ========================= //


    // The Miner Profits - max/min values.
    // These aren't defined in Core Settings, because Miner Profits
    // are only specific to this lottery type.

    uint32 constant MIN_MINER_PROFITS = 1 * PERCENT;
    uint32 constant MAX_MINER_PROFITS = 10 * PERCENT;


    // Uniswap Router V2 contract instance.
    // Address is the same for MainNet, and all public testnets.
    IUniswapRouter constant uniswapRouter = IUniswapRouter(
        address( 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D ) );


    // Public-accessible ERC20 token specific constants.
    string constant public name = "UniLottery Token";
    string constant public symbol = "ULT";
    uint256 constant public decimals = 18;


    // =================== State Variables =================== //

    // ------- Initial Slots ------- //

    // The config which is passed to constructor.
    LotteryConfig internal cfg;

    // ------- Slot ------- //

    // The Lottery Storage contract, which stores all holder data,
    // such as scores, referral tree data, etc.
    LotteryStorage public lotStorage;

    // ------- Slot ------- //

    // Pool address. Set on constructor from msg.sender.
    address payable public poolAddress;

    // ------- Slot ------- //
    
    // Randomness Provider address.
    address public randomnessProvider;

    // ------- Slot ------- //

    // Exchange address. In Uniswap mode, it's the Uniswap liquidity 
    // pair's address, where trades execute.
    address public exchangeAddress;

    // Start date.
    uint32 public startDate;

    // Completion (Mining Phase End) date.
    uint32 public completionDate;
    
    // The date when Randomness Provider was called, requesting a
    // random seed for the lottery finish.
    // Also, when this variable becomes Non-Zero, it indicates that we're
    // on Ending Stage Part One: waiting for the random seed.
    uint32 finish_timeRandomSeedRequested;

    // ------- Slot ------- //

    // WETH address. Set by calling Router's getter, on constructor.
    address WETHaddress;

    // Is the WETH first or second token in our Uniswap Pair?
    bool uniswap_ethFirst;

    // If we are, or were before, on finishing stage, this is the
    // probability of lottery going to Ending Stage on this transaction.
    uint32 finishProbablity;
    
    // Re-Entrancy Lock (Mutex).
    // We protect for reentrancy in the Fund Transfer functions.
    bool reEntrancyMutexLocked;
    
    // On which stage we are currently.
    uint8 public lotteryStage;
    
    // Indicator for whether the lottery fund gains have passed a 
    // minimum fund gain requirement.
    // After that time point (when this bool is set), the token sells
    // which could drop the fund value below the requirement, would
    // be denied.
    bool fundGainRequirementReached;
    
    // The current step of the Mining Stage.
    uint16 miningStep;

    // If we're currently on Special Transfer Mode - that is, we allow
    // direct transfers between parties even in NON-ACTIVE state.
    bool specialTransferModeEnabled;


    // ------- Slot ------- //
    
    // Per-Transaction Pseudo-Random hash value (transferHashValue).
    // This value is computed on every token transfer, by keccak'ing
    // the last (current) transferHashValue, msg.sender, block.timestamp, and 
    // transaction count.
    //
    // This is used on Finishing Stage, as a pseudo-random number,
    // which is used to check if we should end the lottery (move to
    // Ending Stage).
    uint256 transferHashValue;

    // ------- Slot ------- //

    // On lottery end, get & store the lottery total ETH return
    // (including initial funds), and profit amount.
    uint128 public ending_totalReturn;
    uint128 public ending_profitAmount;

    // ------- Slot ------- //

    // The mapping that contains TRUE for addresses that already claimed
    // their lottery winner prizes.
    // Used only in COMPLETION, on claimWinnerPrize(), to check if
    // msg.sender has already claimed his prize.
    mapping( address => bool ) public prizeClaimersAddresses;


    // ============= Private/internal functions ============= //


    // Pool Only modifier.
    modifier poolOnly {
        require( msg.sender == poolAddress/*,
                 "Function can be called only by the pool!" */);
        _;
    }

    // Only randomness provider allowed modifier.
    modifier randomnessProviderOnly {
        require( msg.sender == randomnessProvider/*,
                 "Function can be called only by the UniLottery"
                 " Randomness Provider!" */);
        _;
    }

    // Execute function only on specific lottery stage.
    modifier onlyOnStage( STAGE _stage ) 
    {
        require( lotteryStage == uint8( _stage )/*,
                 "Function cannot be called on current stage!" */);
        _;
    }

    // Modifier for protecting the function from re-entrant calls,
    // by using a locked Re-Entrancy Lock (Mutex).
    modifier mutexLOCKED
    {
        require( ! reEntrancyMutexLocked/*, 
                    "Re-Entrant Calls are NOT ALLOWED!" */);

        reEntrancyMutexLocked = true;
        _;
        reEntrancyMutexLocked = false;
    }


    // Check if we're currently on a specific stage.
    function onStage( STAGE _stage )
                                                internal view
    returns( bool )
    {
        return ( lotteryStage == uint8( _stage ) );
    }


    /**
     *  Check if token transfer to specific wallet won't exceed 
     *  maximum token amount allowed to own by a single wallet.
     *
     *  @return true, if holder's balance with "amount" added,
     *      would exceed the max allowed single holder's balance
     *      (by default, that is 5% of total supply).
     */
    function transferExceedsMaxBalance( 
            address holder, uint amount )
                                                internal view
    returns( bool )
    {
        uint maxAllowedBalance = 
            ( totalSupply() * cfg.maxAmountForWallet_percentageOfSupply ) /
            ( _100PERCENT );

        return ( ( balanceOf( holder ) + amount ) > maxAllowedBalance );
    }


    /**
     *  Update holder data.
     *  This function is called by _transfer() function, just before
     *  transfering final amount of tokens directly from sender to
     *  receiver.
     *  At this point, all burns/mints have been done, and we're sure
     *  that this transfer is valid and must be successful.
     *
     *  In all modes, this function is used to update the holder array.
     *
     *  However, on external exchange modes (e.g. on Uniswap mode),
     *  it is also used to track buy/sell ether value, to update holder
     *  scores, when token buys/sells cannot be tracked directly.
     *
     *  If, however, we use Standalone mode, we are the exchange,
     *  so on _transfer() we already know the ether value, which is
     *  set to currentBuySellEtherValue variable.
     *
     *  @param amountSent - the token amount that is deducted from
     *      sender's balance. This includes burn, and owner fee.
     *
     *  @param amountReceived - the token amount that receiver 
     *      actually receives, after burns and fees.
     *
     *  @return holderCountChanged - indicates whether holder count
     *      changes during this transfer - new holder joins or leaves
     *      (true), or no change occurs (false).
     */
    function updateHolderData_preTransfer(
            address sender,
            address receiver,
            uint256 amountSent,
            uint256 amountReceived )
                                                internal
    returns( bool holderCountChanged )
    {
        // Update holder array, if new token holder joined, or if
        // a holder transfered his whole balance.
        holderCountChanged = false;

        // Sender transferred his whole balance - no longer a holder.
        if( balanceOf( sender ) == amountSent ) 
        {
            lotStorage.removeHolder( sender );
            holderCountChanged = true;
        }

        // Receiver didn't have any tokens before - add it to holders.
        if( balanceOf( receiver ) == 0 && amountReceived > 0 )
        {
            lotStorage.addHolder( receiver );
            holderCountChanged = true;
        }

        // Update holder score factors: if buy/sell occured, update
        // etherContributed and timeFactors scores,
        // and also propagate the scores through the referral chain
        // to the parent referrers (this is done in Storage contract).

        // This lottery operates only on external exchange (Uniswap)
        // mode, so we have to find out the buy/sell Ether value by 
        // calling the external exchange (Uniswap pair) contract.

        // Temporary variable to store current transfer's buy/sell
        // value in Ethers.
        int buySellValue;

        // Sender is an exchange - buy detected.
        if( sender == exchangeAddress && receiver != exchangeAddress ) 
        {
            // Use the Router's functionality.
            // Set the exchange path to WETH -> ULT
            // (ULT is Lottery Token, and it's address is our address).
            address[] memory path = new address[]( 2 );
            path[ 0 ] = WETHaddress;
            path[ 1 ] = address(this);

            uint[] memory ethAmountIn = uniswapRouter.getAmountsIn(
                amountSent,     // uint amountOut, 
                path            // address[] path
            );

            buySellValue = int( ethAmountIn[ 0 ] );
            
            // Compute time factor value for the current ether value.
            // buySellValue is POSITIVE.
            // When computing Time Factors, leave only 2 ether decimals.
            int timeFactorValue = ( buySellValue / (1 ether / 100) ) * 
                int( (block.timestamp - startDate) / cfg.timeFactorDivisor );

            if( timeFactorValue == 0 )
                timeFactorValue = 1;

            // Update and propagate the buyer (receiver) scores.
            lotStorage.updateAndPropagateScoreChanges(
                    receiver,
                    int80( buySellValue ),
                    int80( timeFactorValue ),
                    int80( amountReceived ) );
        }

        // Receiver is an exchange - sell detected.
        else if( sender != exchangeAddress && receiver == exchangeAddress )
        {
            // Use the Router's functionality.
            // Set the exchange path to ULT -> WETH
            // (ULT is Lottery Token, and it's address is our address).
            address[] memory path = new address[]( 2 );
            path[ 0 ] = address(this);
            path[ 1 ] = WETHaddress;

            uint[] memory ethAmountOut = uniswapRouter.getAmountsOut(
                amountReceived,     // uint amountIn
                path                // address[] path
            );

            // It's a sell (ULT -> WETH), so set value to NEGATIVE.
            buySellValue = int( -1 ) * int( ethAmountOut[ 1 ] );
            
            // Compute time factor value for the current ether value.
            // buySellValue is NEGATIVE.
            int timeFactorValue = ( buySellValue / (1 ether / 100) ) * 
                int( (block.timestamp - startDate) / cfg.timeFactorDivisor );

            if( timeFactorValue == 0 )
                timeFactorValue = -1;

            // Update and propagate the seller (sender) scores.
            lotStorage.updateAndPropagateScoreChanges(
                    sender,
                    int80( buySellValue ),
                    int80( timeFactorValue ),
                    -1 * int80( amountSent ) );
        }

        // Neither Sender nor Receiver are exchanges - default transfer.
        // Tokens just got transfered between wallets, without 
        // exchanging for ETH - so etherContributed_change = 0. 
        // On this case, update both sender's & receiver's scores.
        //
        else {
            buySellValue = 0;

            lotStorage.updateAndPropagateScoreChanges( sender, 0, 0, 
                                            -1 * int80( amountSent ) );

            lotStorage.updateAndPropagateScoreChanges( receiver, 0, 0, 
                                            int80( amountReceived ) );
        }

        // Check if lottery liquidity pool funds have already
        // reached a minimum required ETH value.
        uint ethFunds = getCurrentEthFunds();

        if( !fundGainRequirementReached &&
            ethFunds >= cfg.fundRequirement_denySells )
        {
            fundGainRequirementReached = true;
        }

        // Check whether this token transfer is allowed if it's a sell
        // (if buySellValue is negative):
        //
        // If we've already reached the minimum fund gain requirement,
        // and this sell would shrink lottery liquidity pool's ETH funds
        // below this requirement, then deny this sell, causing this 
        // transaction to fail.

        if( fundGainRequirementReached &&
            buySellValue < 0 &&
            ( uint( -1 * buySellValue ) >= ethFunds ||
              ethFunds - uint( -1 * buySellValue ) < 
                cfg.fundRequirement_denySells ) )
        {
            require( false/*, "This sell would drop the lottery ETH funds"
                            "below the minimum requirement threshold!" */);
        }
    }
    
    
    /**
     *  Check for finishing stage start conditions.
     *  - If some conditions are met, start finishing stage!
     *    Do it by setting "onFinishingStage" bool.
     *  - If we're currently on finishing stage, and some condition
     *    is no longer met, then stop the finishing stage.
     */
    function checkFinishingStageConditions()
                                                    internal
    {
        // Firstly, check if lottery hasn't exceeded it's maximum lifetime.
        // If so, don't check anymore, just set finishing stage, and
        // end the lottery on further call of checkForEnding().
        if( (block.timestamp - startDate) > cfg.maxLifetime ) 
        {
            lotteryStage = uint8( STAGE.FINISHING );
            return;
        }

        // Compute & check the finishing criteria.

        // Notice that we adjust the config-specified fund gain
        // percentage increase to uint-mode, by adding 100 percents,
        // because we don't deal with negative percentages, and here
        // we represent loss as a percentage below 100%, and gains
        // as percentage above 100%.
        // So, if in regular gains notation, it's said 10% gain,
        // in uint mode, it's said 110% relative increase.
        //
        // (Also, remember that losses are impossible in our lottery
        //  working scheme).

        if( lotStorage.getHolderCount() >= cfg.finishCriteria_minNumberOfHolders
            &&
            getCurrentEthFunds() >= cfg.finishCriteria_minFunds
            &&
            (block.timestamp - startDate) >= cfg.finishCriteria_minTimeActive )
        {
            if( onStage( STAGE.ACTIVE ) )
            {
                // All conditions are met - start the finishing stage.
                lotteryStage = uint8( STAGE.FINISHING );

                emit FinishingStageStarted();
            }
        }

        else if( onStage( STAGE.FINISHING ) )
        {
            // However, what if some condition was not met, but we're
            // already on the finishing stage?
            // If so, we must stop the finishing stage.
            // But what to do with the finishing probability?
            // Config specifies if it should be reset or maintain it's
            // value until the next time finishing stage is started.

            lotteryStage = uint8( STAGE.ACTIVE );

            if( cfg.finish_resetProbabilityOnStop )
                finishProbablity = cfg.finish_initialProbability;

            emit FinishingStageStopped();
        }
    }


    /**
     *  We're currently on finishing stage - so let's check if
     *  we should end the lottery block.timestamp!
     *
     *  This function is called from _transfer(), only if we're sure
     *  that we're currently on finishing stage (onFinishingStage
     *  variable is set).
     *
     *  Here, we compute the pseudo-random number from hash of
     *  current message's sender, block.timestamp, and other values,
     *  and modulo it to the current finish probability.
     *  If it's equal to 1, then we end the lottery!
     *
     *  Also, here we update the finish probability according to
     *  probability update criteria - holder count, and tx count.
     *
     *  @param holderCountChanged - indicates whether Holder Count
     *      has changed during this transfer (new holder joined, or
     *      a holder sold all his tokens).
     */
    function checkForEnding( bool holderCountChanged )
                                                            internal
    {
        // At first, check if lottery max lifetime is exceeded.
        // If so, start ending procedures right block.timestamp.
        if( (block.timestamp - startDate) > cfg.maxLifetime )
        {
            startEndingStage();
            return;
        }

        // Now, we know that lottery lifetime is still OK, and we're
        // currently on Finishing Stage (because this function is
        // called only when onFinishingStage is set).
        //
        // Now, check if we should End the lottery, by computing
        // a modulo on a pseudo-random number, which is a transfer
        // hash, computed for every transfer on _transfer() function.
        //
        // Get the modulo amount according to current finish 
        // probability.
        // We use precision of 0.01% - notice the "10000 *" before
        // 100 PERCENT.
        // Later, when modulo'ing, we'll check if value is below 10000.
        //
        uint prec = 10000;
        uint modAmount = (prec * _100PERCENT) / finishProbablity;

        if( ( transferHashValue % modAmount ) <= prec )
        {
            // Finish probability is met! Commence lottery end - 
            // start Ending Stage.
            startEndingStage();
            return;
        }

        // Finish probability wasn't met.
        // Update the finish probability, by increasing it!

        // Transaction count criteria.
        // As we know that this function is called on every new 
        // transfer (transaction), we don't check if transactionCount
        // increased or not - we just perform probability update.

        finishProbablity += cfg.finish_probabilityIncreaseStep_transaction;

        // Now, perform holder count criteria update.
        // Finish probability increases, no matter if holder count
        // increases or decreases.
        if( holderCountChanged )
            finishProbablity += cfg.finish_probabilityIncreaseStep_holder;
    }


    /**
     *  Start the Ending Stage, by De-Activating the lottery,
     *  to deny all further token transfers (excluding the one when
     *  removing liquidity from Uniswap), and transition into the 
     *  Mining Phase - set the lotteryStage to MINING.
     */
    function startEndingStage()
                                                internal
    {
        lotteryStage = uint8( STAGE.ENDING_MINING );
    }


    /**
     *  Execute the first step of the Mining Stage - request a 
     *  Random Seed from the Randomness Provider.
     *
     *  Here, we call the Randomness Provider, asking for a true random seed
     *  to be passed to us into our callback, named 
     *  "finish_randomnessProviderCallback()".
     *
     *  When that callback will be called, our storage's random seed will
     *  be set, and we'll be able to start the Ending Algorithm on
     *  further mining steps.
     *
     *  Notice that Randomness Provider must already be funded, to
     *  have enough Ether for Provable fee and the gas costs of our
     *  callback function, which are quite high, because of winner
     *  selection algorithm, which is computationally expensive.
     *
     *  The Randomness Provider is always funded by the Pool,
     *  right before the Pool deploys and starts a new lottery, so
     *  as every lottery calls the Randomness Provider only once,
     *  the one-call-fund method for every lottery is sufficient.
     *
     *  Also notice, that Randomness Provider might fail to call
     *  our callback due to some unknown reasons!
     *  Then, the lottery profits could stay locked in this 
     *  lottery contract forever ?!!
     *
     *  No! We've thought about that - we've implemented the
     *  Alternative Ending mechanism, where, if specific time passes 
     *  after we've made a request to Randomness Provider, and
     *  callback hasn't been called yet, we allow external actor to
     *  execute the Alternative ending, which basically does the
     *  same things as the default ending, just that the Random Seed
     *  will be computed locally in our contract, using the
     *  Pseudo-Random mechanism, which could compute a reasonably
     *  fair and safe value using data from holder array, and other
     *  values, described in more detail on corresponding function's
     *  description.
     */
    function mine_requestRandomSeed()
                                                internal
    {
        // We're sure that the Randomness Provider has enough funds.
        // Execute the random request, and get ready for Ending Algorithm.

        IRandomnessProvider( randomnessProvider )
            .requestRandomSeedForLotteryFinish();

        // Store the time when random seed has been requested, to
        // be able to alternatively handle the lottery finish, if
        // randomness provider doesn't call our callback for some
        // reason.
        finish_timeRandomSeedRequested = uint32( block.timestamp );

        // Emit appropriate events.
        emit RandomnessProviderCalled();
    }


    /** PAYABLE [ OUT ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
     *
     *  Transfer the Owner & Pool profit shares, when lottery ends.
     *  This function is the first one that's executed on the Mining
     *  Stage.
     *  This is the first step of Mining. So, the Miner who executes this
     *  function gets the mining reward.
     *
     *  This function's job is to Gather the Profits & Initial Funds,
     *  and Transfer them to Profiters - that is, to The Pool, and
     *  to The Owner.
     *
     *  The Miners' profit share and Winner Prize Fund stay in this
     *  contract.
     *
     *  On this function, we (in this order):
     *
     *  1. Remove all liquidity from Uniswap (if using Uniswap Mode),
     *      pulling it to our contract's wallet.
     *
     *  2. Transfer the Owner and the Pool ETH profit shares to
     *      Owner and Pool addresses.
     *
     *  * This function transfers Ether out of our contract:
     *      - We transfer the Profits to Pool and Owner addresses.
     */
    function mine_removeUniswapLiquidityAndTransferProfits()
                                                                internal
                                                                mutexLOCKED
    {
        // We've already approved our token allowance to Router.
        // Now, approve Uniswap liquidity token's Router allowance.
        ERC20( exchangeAddress ).approve( address(uniswapRouter), uint(-1) );

        // Enable the SPECIAL-TRANSFER mode, to allow Uniswap to transfer
        // the tokens from Pair to Router, and then from Router to us.
        specialTransferModeEnabled = true;

        // Remove liquidity!
        uint amountETH = uniswapRouter
            .removeLiquidityETHSupportingFeeOnTransferTokens(
                address(this),          // address token,
                ERC20( exchangeAddress ).balanceOf( address(this) ),
                0,                      // uint amountTokenMin,
                0,                      // uint amountETHMin,
                address(this),          // address to,
                (block.timestamp + 10000000)        // uint deadline
            );

        // Tokens are transfered. Disable the special transfer mode.
        specialTransferModeEnabled = false;

        // Check that we've got a correct amount of ETH.
        require( address(this).balance >= amountETH &&
                 address(this).balance >= cfg.initialFunds/*,
                 "Incorrect amount of ETH received from Uniswap!" */);


        // Compute the Profit Amount (current balance - initial funds).
        ending_totalReturn = uint128( address(this).balance );
        ending_profitAmount = ending_totalReturn - uint128( cfg.initialFunds );

        // Compute, and Transfer Owner's profit share and 
        // Pool's profit share to their respective addresses.

        uint poolShare = ( ending_profitAmount * cfg.poolProfitShare ) /
                         ( _100PERCENT );

        uint ownerShare = ( ending_profitAmount * cfg.ownerProfitShare ) /
                          ( _100PERCENT );

        // To pool, transfer it's profit share plus initial funds.
        IUniLotteryPool( poolAddress ).lotteryFinish
            { value: poolShare + cfg.initialFunds }
            ( ending_totalReturn, ending_profitAmount );

        // Transfer Owner's profit share.
        OWNER_ADDRESS.transfer( ownerShare );

        // Emit ending event.
        emit LotteryEnd( ending_totalReturn, ending_profitAmount );
    }


    /**
     *  Executes a single step of the Winner Selection Algorithm
     *  (the Ending Algorithm).
     *  The algorithm itself is being executed in the Storage contract.
     *
     *  On current design, whole algorithm is executed in a single step.
     *
     *  This function is executed only in the Mining stage, and
     *  accounts for most of the gas spent during mining.
     */
    function mine_executeEndingAlgorithmStep()
                                                            internal
    {
        // Launch the winner algorithm, to execute the next step.
        lotStorage.executeWinnerSelectionAlgorithm();
    }



    // =============== Public functions =============== //


    /**
     *  Constructor of this delegate code contract.
     *  Here, we set OUR STORAGE's lotteryStage to DISABLED, because
     *  we don't want anybody to call this contract directly.
     */
    constructor()
    {
        lotteryStage = uint8( STAGE.DISABLED );
    }


    /**
     *  Construct the lottery contract which is delegating it's
     *  call to us.
     *
     *  @param config - LotteryConfig structure to use in this lottery.
     *
     *      Future approach: ABI-encoded Lottery Config 
     *      (different implementations might use different config 
     *      structures, which are ABI-decoded inside the implementation).
     *
     *      Also, this "config" includes the ABI-encoded temporary values, 
     *      which are not part of persisted LotteryConfig, but should
     *      be used only in constructor - for example, values to be
     *      assigned to storage variables, such as ERC20 token's
     *      name, symbol, and decimals.
     *
     *  @param _poolAddress - Address of the Main UniLottery Pool, which
     *      provides initial funds, and receives it's profit share.
     *
     *  @param _randomProviderAddress - Address of a Randomness Provider,
     *      to use for obtaining random seeds.
     *
     *  @param _storageAddress  - Address of a Lottery Storage.
     *      Storage contract is a separate contract which holds all 
     *      lottery token holder data, such as intermediate scores.
     *
     */
    function construct( 
            LotteryConfig memory config,
            address payable _poolAddress,
            address _randomProviderAddress,
            address _storageAddress )
                                                        external
    {
        // Check if contract wasn't already constructed!
        require( poolAddress == address( 0 )/*,
                 "Contract is already constructed!" */);

        // Set the Pool's Address - notice that it's not the
        // msg.sender, because lotteries aren't created directly
        // by the Pool, but by the Lottery Factory!
        poolAddress = _poolAddress;

        // Set the Randomness Provider address.
        randomnessProvider = _randomProviderAddress;


        // Check the minimum & maximum requirements for config
        // profit & lifetime parameters.

        require( config.maxLifetime <= MAX_LOTTERY_LIFETIME/*,
                 "Lottery maximum lifetime is too high!" */);

        require( config.poolProfitShare >= MIN_POOL_PROFITS &&
                 config.poolProfitShare <= MAX_POOL_PROFITS/*,
                 "Pool profit share is invalid!" */);

        require( config.ownerProfitShare >= MIN_OWNER_PROFITS &&
                 config.ownerProfitShare <= MAX_OWNER_PROFITS/*,
                 "Owner profit share is invalid!" */);

        require( config.minerProfitShare >= MIN_MINER_PROFITS &&
                 config.minerProfitShare <= MAX_MINER_PROFITS/*,
                 "Miner profit share is invalid!" */);

        // Check if time factor divisor is higher than 2 minutes.
        // That's because int40 wouldn't be able to handle precisions
        // of smaller time factor divisors.
        require( config.timeFactorDivisor >= 2 minutes /*,
                 "Time factor divisor is lower than 2 minutes!"*/ );

        // Check if winner profit share is good.
        uint32 totalWinnerShare = 
            (_100PERCENT) - config.poolProfitShare
                            - config.ownerProfitShare
                            - config.minerProfitShare;

        require( totalWinnerShare >= MIN_WINNER_PROFIT_SHARE/*,
                 "Winner profit share is too low!" */);

        // Check if ending algorithm params are good.
        require( config.randRatio_scorePart != 0    &&
                 config.randRatio_randPart  != 0    &&
                 ( config.randRatio_scorePart + 
                   config.randRatio_randPart    ) < 10000/*,
                 "Random Ratio params are invalid!" */);

        require( config.endingAlgoType == 
                    uint8( EndingAlgoType.MinedWinnerSelection ) ||
                 config.endingAlgoType == 
                    uint8( EndingAlgoType.WinnerSelfValidation ) ||
                 config.endingAlgoType == 
                    uint8( EndingAlgoType.RolledRandomness )/*,
                 "Wrong Ending Algorithm Type!" */);

        // Set the number of winners (winner count).
        // If using Computed Sequence winner prize shares, set that
        // value, and if it's zero, then we're using the Array-Mode
        // prize share specification.
        if( config.prizeSequence_winnerCount == 0 &&
            config.winnerProfitShares.length != 0 )
            config.prizeSequence_winnerCount = 
                uint16( config.winnerProfitShares.length );


        // Setup our Lottery Storage - initialize, and set the
        // Algorithm Config.

        LotteryStorage _lotStorage = LotteryStorage( _storageAddress );

        // Setup a Winner Score Config for the winner selection algo,
        // to be used in the Lottery Storage.
        LotteryStorage.WinnerAlgorithmConfig memory winnerConfig;

        // Algorithm type.
        winnerConfig.endingAlgoType = config.endingAlgoType;

        // Individual player max score parts.
        winnerConfig.maxPlayerScore_etherContributed =
            config.maxPlayerScore_etherContributed;

        winnerConfig.maxPlayerScore_tokenHoldingAmount =
            config.maxPlayerScore_tokenHoldingAmount;

        winnerConfig.maxPlayerScore_timeFactor =
            config.maxPlayerScore_timeFactor;

        winnerConfig.maxPlayerScore_refferalBonus =
            config.maxPlayerScore_refferalBonus;

        // Score-To-Random ratio parts.
        winnerConfig.randRatio_scorePart = config.randRatio_scorePart;
        winnerConfig.randRatio_randPart = config.randRatio_randPart;

        // Set winner count (no.of winners).
        winnerConfig.winnerCount = config.prizeSequence_winnerCount;


        // Initialize the storage (bind it to our contract).
        _lotStorage.initialize( winnerConfig );

        // Set our immutable variable.
        lotStorage = _lotStorage;


        // Now, set our config to the passed config.
        cfg = config;

        // Might be un-needed (can be replaced by Constant on the MainNet):
        WETHaddress = uniswapRouter.WETH();
    }


    /** PAYABLE [ IN  ] <<<<<<<<<<<<<<<<<<<<<<<<<<<<
     *
     *  Fallback Receive Ether function.
     *  Used to receive ETH funds back from Uniswap, on lottery's end,
     *  when removing liquidity.
     */
    receive()       external payable
    {
        emit FallbackEtherReceiver( msg.sender, msg.value );
    }



    /** PAYABLE [ IN  ] <<<<<<<<<<<<<<<<<<<<<<<<<<<<
     *  PAYABLE [ OUT ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>
     *
     *  Initialization function.
     *  Here, the most important startup operations are made - 
     *  such as minting initial token supply and transfering it to
     *  the Uniswap liquidity pair, in exchange for UNI-v2 tokens.
     *
     *  This function is called by the pool, when transfering
     *  initial funds to this contract.
     *
     *  What's payable?
     *  - Pool transfers initial funds to our contract.
     *  - We transfer that initial fund Ether to Uniswap liquidity pair
     *    when creating/providing it.
     */
    function initialize()   
                                        external
                                        payable
                                        poolOnly
                                        mutexLOCKED
                                        onlyOnStage( STAGE.INITIAL )
    {
        // Check if pool transfered correct amount of funds.
        require( address( this ).balance == cfg.initialFunds/*,
                 "Invalid amount of funds transfered!" */);

        // Set start date.
        startDate = uint32( block.timestamp );

        // Set the initial transfer hash value.
        transferHashValue = uint( keccak256( 
                abi.encodePacked( msg.sender, block.timestamp ) ) );

        // Set initial finish probability, to be used when finishing
        // stage starts.
        finishProbablity = cfg.finish_initialProbability;
        
        
        // ===== Active operations - mint & distribute! ===== //

        // Mint full initial supply of tokens to our contract address!
        _mint( address(this), 
               uint( cfg.initialTokenSupply ) * (10 ** decimals) );

        // Now - prepare to create a new Uniswap Liquidity Pair,
        // with whole our total token supply and initial funds ETH
        // as the two liquidity reserves.
        
        // Approve Uniswap Router to allow it to spend our tokens.
        // Set maximum amount available.
        _approve( address(this), address( uniswapRouter ), uint(-1) );

        // Provide liquidity - the Router will automatically
        // create a new Pair.
        
        uniswapRouter.addLiquidityETH 
        { value: address(this).balance }
        (
            address(this),          // address token,
            totalSupply(),          // uint amountTokenDesired,
            totalSupply(),          // uint amountTokenMin,
            address(this).balance,  // uint amountETHMin,
            address(this),          // address to,
            (block.timestamp + 1000)            // uint deadline
        );

        // Get the Pair address - that will be the exchange address.
        exchangeAddress = IUniswapFactory( uniswapRouter.factory() )
            .getPair( WETHaddress, address(this) );

        // We assume that the token reserves of the pair are good,
        // and that we own the full amount of liquidity tokens.

        // Find out which of the pair tokens is WETH - is it the 
        // first or second one. Use it later, when getting our share.
        if( IUniswapPair( exchangeAddress ).token0() == WETHaddress )
            uniswap_ethFirst = true;
        else
            uniswap_ethFirst = false;


        // Move to ACTIVE lottery stage.
        // Now, all token transfers will be allowed.
        lotteryStage = uint8( STAGE.ACTIVE );

        // Lottery is initialized. We're ready to emit event.
        emit LotteryInitialized();
    }


    // Return this lottery's initial funds, as were specified in the config.
    //
    function getInitialFunds()          external view
    returns( uint )
    {
        return cfg.initialFunds;
    }

    // Return active (still not returned to pool) initial fund value.
    // If no-longer-active, return 0 (default) - because funds were 
    // already returned back to the pool.
    //
    function getActiveInitialFunds()    external view
    returns( uint )
    {
        if( onStage( STAGE.ACTIVE ) )
            return cfg.initialFunds;
        return 0;
    }


    /**
     *  Get current Exchange's Token and ETH reserves.
     *  We're on Uniswap mode, so get reserves from Uniswap.
     */
    function getReserves() 
                                                        external view
    returns( uint _ethReserve, uint _tokenReserve )
    {
        // Use data from Uniswap pair contract.
        ( uint112 res0, uint112 res1, ) = 
            IUniswapPair( exchangeAddress ).getReserves();

        if( uniswap_ethFirst )
            return ( res0, res1 );
        else
            return ( res1, res0 );
    }


    /**
     *  Get our share (ETH amount) of the Uniswap Pair ETH reserve,
     *  of our Lottery tokens ULT-WETH liquidity pair.
     */
    function getCurrentEthFunds()
                                                        public view
    returns( uint ethAmount )
    {
        IUniswapPair pair = IUniswapPair( exchangeAddress );
        
        ( uint112 res0, uint112 res1, ) = pair.getReserves();
        uint resEth = uint( uniswap_ethFirst ? res0 : res1 );

        // Compute our amount of the ETH reserve, based on our
        // percentage of our liquidity token balance to total supply.
        uint liqTokenPercentage = 
            ( pair.balanceOf( address(this) ) * (_100PERCENT) ) /
            ( pair.totalSupply() );

        // Compute and return the ETH reserve.
        return ( resEth * liqTokenPercentage ) / (_100PERCENT);
    }


    /**
     *  Get current finish probability.
     *  If it's ACTIVE stage, return 0 automatically.
     */
    function getFinishProbability()
                                                        external view
    returns( uint32 )
    {
        if( onStage( STAGE.FINISHING ) )
            return finishProbablity;
        return 0;
    }


    
    /**
     *  Generate a referral ID for msg.sender, who must be a token holder.
     *  Referral ID is used to refer other wallets into playing our
     *  lottery.
     *  - Referrer gets bonus points for every wallet that bought 
     *    lottery tokens and specified his referral ID.
     *  - Referrees (wallets who got referred by registering a valid
     *    referral ID, corresponding to some referrer), get some
     *    bonus points for specifying (registering) a referral ID.
     *
     *  Referral ID is a uint256 number, which is generated by
     *  keccak256'ing the holder's address, holder's current
     *  token ballance, and current time.
     */
    function generateReferralID()
                                        external
                                        onlyOnStage( STAGE.ACTIVE )
    {
        uint256 refID = lotStorage.generateReferralID( msg.sender );

        // Emit approppriate events.
        emit ReferralIDGenerated( msg.sender, refID );
    }


    /**
     *  Register a referral for a msg.sender (must be token holder),
     *  using a valid referral ID got from a referrer.
     *  This function is called by a referree, who obtained a
     *  valid referral ID from some referrer, who previously
     *  generated it using generateReferralID().
     *
     *  You can only register a referral once!
     *  When you do so, you get bonus referral points!
     */
    function registerReferral( 
            uint256 referralID )
                                        external
                                        onlyOnStage( STAGE.ACTIVE )
    {
        address referrer = lotStorage.registerReferral( 
                msg.sender,
                cfg.playerScore_referralRegisteringBonus,
                referralID );

        // Emit approppriate events.
        emit ReferralRegistered( msg.sender, referrer, referralID );
    }


    /**
     *  The most important function of this contract - Transfer Function.
     *
     *  Here, all token burning, intermediate score tracking, and 
     *  finish condition checking is performed, according to the 
     *  properties specified in config.
     */
    function _transfer( address sender,
                        address receiver,
                        uint256 amount )
                                            internal
                                            override
    {
        // Check if transfers are allowed in current state.
        // On Non-Active stage, transfers are allowed only from/to
        // our contract.
        // As we don't have Standalone Mode on this lottery variation,
        // that means that tokens to/from our contract are travelling
        // only when we transfer them to Uniswap Pair, and when
        // Uniswap transfers them back to us, on liquidity remove.
        //
        // On this state, we also don't perform any burns nor
        // holding trackings - just transfer and return.

        if( !onStage( STAGE.ACTIVE )    &&
            !onStage( STAGE.FINISHING ) &&
            ( sender == address(this) || receiver == address(this) ||
              specialTransferModeEnabled ) )
        {
            super._transfer( sender, receiver, amount );
            return;
        }

        // Now, we know that we're NOT on special mode.
        // Perform standard checks & brecks.
        require( ( onStage( STAGE.ACTIVE ) || 
                   onStage( STAGE.FINISHING ) )/*,
                 "Token transfers are only allowed on ACTIVE stage!" */);
                 
        // Can't transfer zero tokens, or use address(0) as sender.
        require( amount != 0 && sender != address(0)/*,
                 "Amount is zero, or transfering from zero address." */);


        // Compute the Burn Amount - if buying tokens from an exchange,
        // we use a lower burn rate - to incentivize buying!
        // Otherwise (if selling or just transfering between wallets),
        // we use a higher burn rate.
        uint burnAmount;

        // It's a buy - sender is an exchange.
        if( sender == exchangeAddress )
            burnAmount = ( amount * cfg.burn_buyerRate ) / (_100PERCENT);
        else
            burnAmount = ( amount * cfg.burn_defaultRate ) / (_100PERCENT);
        
        // Now, compute the final amount to be gotten by the receiver.
        uint finalAmount = amount - burnAmount;

        // Check if receiver's balance won't exceed the max-allowed!
        // Receiver must not be an exchange.
        if( receiver != exchangeAddress )
        {
            require( !transferExceedsMaxBalance( receiver, finalAmount )/*,
                "Receiver's balance would exceed maximum after transfer!"*/);
        }

        // Now, update holder data array accordingly.
        bool holderCountChanged = updateHolderData_preTransfer( 
                sender, 
                receiver, 
                amount,             // Amount Sent (Pre-Fees)
                finalAmount         // Amount Received (Post-Fees).
        );

        // All is ok - perform the burn and token transfers block.timestamp.

        // Burn token amount from sender's balance.
        super._burn( sender, burnAmount );

        // Finally, transfer the final amount from sender to receiver.
        super._transfer( sender, receiver, finalAmount );


        // Compute new Pseudo-Random transfer hash, which must be
        // computed for every transfer, and is used in the
        // Finishing Stage as a pseudo-random unique value for 
        // every transfer, by which we determine whether lottery
        // should end on this transfer.
        //
        // Compute it like this: keccak the last (current) 
        // transferHashValue, msg.sender, sender, receiver, amount.

        transferHashValue = uint( keccak256( abi.encodePacked(
            transferHashValue, msg.sender, sender, receiver, amount ) ) );


        // Check if we should be starting a finishing stage block.timestamp.
        checkFinishingStageConditions();

        // If we're on finishing stage, check for ending conditions.
        // If ending check is satisfied, the checkForEnding() function
        // starts ending operations.
        if( onStage( STAGE.FINISHING ) )
            checkForEnding( holderCountChanged );
    }


    /**
     *  Callback function, which is called from Randomness Provider,
     *  after it obtains a random seed to be passed to us, after
     *  we have initiated The Ending Stage, on which random seed
     *  is used to generate random factors for Winner Selection
     *  algorithm.
     */ 
    function finish_randomnessProviderCallback(
            uint256 randomSeed,
            uint256 /*callID*/ )
                                                external
                                                randomnessProviderOnly
    {
        // Set the random seed in the Storage Contract.
        lotStorage.setRandomSeed( randomSeed );

        // If algo-type is not Mined Winner Selection, then by block.timestamp
        // we assume lottery as COMPL3T3D.
        if( cfg.endingAlgoType != uint8(EndingAlgoType.MinedWinnerSelection) )
        {
            lotteryStage = uint8( STAGE.COMPLETION );
            completionDate = uint32( block.timestamp );
        }
    }


    /**
     *  Function checks if we can initiate Alternative Seed generation.
     *
     *  Alternative approach to Lottery Random Seed is used only when
     *  Randomness Provider doesn't work, and doesn't call the
     *  above callback.
     *
     *  This alternative approach can be initiated by Miners, when
     *  these conditions are met:
     *  - Lottery is on Ending (Mining) stage.
     *  - Request to Randomness Provider was made at least X time ago,
     *    and our callback hasn't been called yet.
     *
     *  If these conditions are met, we can initiate the Alternative
     *  Random Seed generation, which generates a seed based on our
     *  state.
     */
    function alternativeSeedGenerationPossible()
                                                        internal view
    returns( bool )
    {
        return ( onStage( STAGE.ENDING_MINING ) &&
                 ( (block.timestamp - finish_timeRandomSeedRequested) >
                   cfg.REQUIRED_TIME_WAITING_FOR_RANDOM_SEED ) );
    }


    /**
     *  Return this lottery's config, using ABIEncoderV2.
     */
    /*function getLotteryConfig()
                                                    external view
    returns( LotteryConfig memory ourConfig )
    {
        return cfg;
    }*/


    /**
     *  Checks if Mining is currently available.
     */
    function isMiningAvailable()
                                                    external view
    returns( bool )
    {
        return onStage( STAGE.ENDING_MINING ) && 
               ( miningStep == 0 || 
                 ( miningStep == 1 && 
                   ( lotStorage.getRandomSeed() != 0 ||
                     alternativeSeedGenerationPossible() )
                 ) );
    }


    /** PAYABLE [ OUT ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
     *
     *  Mining function, to be executed on Ending (Mining) stage.
     *
     *  "Mining" approach is used in this lottery, to use external
     *  actors for executing the gas-expensive Ending Algorithm,
     *  and other ending operations, such as profit transfers.
     *
     *  "Miners" can be any external actors who call this function.
     *  When Miner successfully completes a Mining Step, he gets 
     *  a Mining Reward, which is a certain portion of lottery's profit
     *  share, dedicated to Miners.
     *
     *  NOT-IMPLEMENTED APPROACH:
     *
     *  All these operations are divided into "mining steps", which are
     *  smaller components, which fit into reasonable gas limits.
     *  All "steps" are designed to take up similar amount of gas.
     *
     *  For example, if total lottery profits (total ETH got from
     *  pulling liquidity out of Uniswap, minus initial funds),
     *  is 100 ETH, Miner Profit Share is 10%, and there are 5 mining
     *  steps total, then for a singe step executed, miner will get:
     *
     *  (100 * 0.1) / 5 = 2 ETH.
     *
     *  ---------------------------------
     *
     *  CURRENTLY IMPLEMENTED APPROACH:
     *
     *  As the above-defined approach would consume very much gas for
     *  inter-step intermediate state storage, we have thought that
     *  for block.timestamp, it's better to have only 2 mining steps, the second of
     *  which performs the whole Winner Selection Algorithm.
     *
     *  This is because performing the whole algorithm at once would save
     *  us up to 10x more gas in total, than executing it in steps.
     *
     *  However, this solution is not scalable, because algorithm has
     *  to fit into block gas limit (10,000,000 gas), so we are limited
     *  to a certain safe maximum number of token holders, which is
     *  empirically determined during testing, and defined in the
     *  MAX_SAFE_NUMBER_OF_HOLDERS constant, which is checked against the
     *  config value "finishCriteria_minNumberOfHolders" in constructor.
     *
     *  So, in this approach, there are only 2 mining steps:
     *
     *  1. Remove liquidity from Uniswap, transfer profit shares to
     *      the Pool and the Owner Address, and request Random Seed
     *      from the Randomness Provider.
     *      Reward: 25% of total Mining Rewards.
     *
     *  2. Perform the whole Winner Selection Algorithm inside the
     *      Lottery Storage contract.
     *      Reward: 75% of total Mining Rewards.
     *
     *  * Function transfers Ether out of our contract:
     *    - Transfers the current miner's reward to msg.sender.
     */
    function mine()
                                external
                                onlyOnStage( STAGE.ENDING_MINING )
    {
        uint currentStepReward;

        // Perform different operations on different mining steps.

        // Step 0:  Remove liquidity from Uniswap, transfer profits to
        //          Pool and Owner addresses. Also, request a Random Seed
        //          from the Randomness Provider.
        if( miningStep == 0 )
        {
            mine_requestRandomSeed();
            mine_removeUniswapLiquidityAndTransferProfits();

            // Compute total miner reward amount, then compute this 
            // step's reward later.
            uint totalMinerRewards = 
                ( ending_profitAmount * cfg.minerProfitShare ) / 
                ( _100PERCENT );

            // Step 0 reward is 10% for Algo type 1.
            if( cfg.endingAlgoType == uint8(EndingAlgoType.MinedWinnerSelection) )
            {
                currentStepReward = ( totalMinerRewards * (10 * PERCENT) ) /
                                    ( _100PERCENT );
            }
            // If other algo-types, second step is not normally needed,
            // so here we take 80% of miner rewards.
            // If Randomness Provider won't give us a seed after
            // specific amount of time, we'll initiate a second step,
            // with remaining 20% of miner rewords.
            else
            {
                currentStepReward = ( totalMinerRewards * (80 * PERCENT) ) /
                                    ( _100PERCENT );
            }

            require( currentStepReward <= totalMinerRewards/*, "BUG 1694" */);
        }

        // Step 1:
        //  If we use MinedWinnerSelection algo-type, then execute the 
        //  winner selection algorithm.
        //  Otherwise, check if Random Provider hasn't given us a
        //  random seed long enough, so that we have to generate a
        //  seed locally.
        else
        {
            // Check if we can go into this step when using specific
            // ending algorithm types.
            if( cfg.endingAlgoType != uint8(EndingAlgoType.MinedWinnerSelection) )
            {
                require( lotStorage.getRandomSeed() == 0 &&
                         alternativeSeedGenerationPossible()/*,
                         "Second Mining Step is not available for "
                         "current Algo-Type on these conditions!" */);
            }

            // Compute total miner reward amount, then compute this 
            // step's reward later.
            uint totalMinerRewards = 
                ( ending_profitAmount * cfg.minerProfitShare ) / 
                ( _100PERCENT );

            // Firstly, check if random seed is already obtained.
            // If not, check if we should generate it locally.
            if( lotStorage.getRandomSeed() == 0 )
            {
                if( alternativeSeedGenerationPossible() )
                {
                    // Set random seed inside the Storage Contract,
                    // but using our contract's transferHashValue as the
                    // random seed.
                    // We believe that this hash has enough randomness
                    // to be considered a fairly good random seed,
                    // because it has beed chain-computed for every
                    // token transfer that has occured in ACTIVE stage.
                    //
                    lotStorage.setRandomSeed( transferHashValue );

                    // If using Non-Mined algorithm types, reward for this
                    // step is 20% of miner funds.
                    if( cfg.endingAlgoType != 
                        uint8(EndingAlgoType.MinedWinnerSelection) )
                    {
                        currentStepReward = 
                            ( totalMinerRewards * (20 * PERCENT) ) /
                            ( _100PERCENT );
                    }
                }
                else
                {
                    // If alternative seed generation is not yet possible
                    // (not enough time passed since the rand.provider
                    // request was made), then mining is not available
                    // currently.
                    require( false/*, "Mining not yet available!" */);
                }
            }

            // Now, we know that  Random Seed is obtained.
            // If we use this algo-type, perform the actual
            // winner selection algorithm.
            if( cfg.endingAlgoType == uint8(EndingAlgoType.MinedWinnerSelection) )
            {
                mine_executeEndingAlgorithmStep();

                // Set the prize amount to SECOND STEP prize amount (90%).
                currentStepReward = ( totalMinerRewards * (90 * PERCENT) ) /
                                    ( _100PERCENT );
            }

            // Now we've completed both Mining Steps, it means MINING stage
            // is finally completed!
            // Transition to COMPLETION stage, and set lottery completion
            // time to NOW.

            lotteryStage = uint8( STAGE.COMPLETION );
            completionDate = uint32( block.timestamp );

            require( currentStepReward <= totalMinerRewards/*, "BUG 2007" */);
        }

        // Now, transfer the reward to miner!
        // Check for bugs too - if the computed amount doesn't exceed.

        // Increment the mining step - move to next step (if there is one).
        miningStep++;

        // Check & Lock the Re-Entrancy Lock for transfers.
        require( ! reEntrancyMutexLocked/*, "Re-Entrant call detected!" */);
        reEntrancyMutexLocked = true;

        // Finally, transfer the reward to message sender!
        msg.sender.transfer( currentStepReward );

        // UnLock ReEntrancy Lock.
        reEntrancyMutexLocked = false;
    }


    /**
     *  Function computes winner prize amount for winner at rank #N.
     *  Prerequisites: Must be called only on STAGE.COMPLETION stage,
     *  because we use the final profits amount here, and that value
     *  (ending_profitAmount) is known only on COMPLETION stage.
     *
     *  @param rankingPosition - ranking position of a winner.
     *  @return finalPrizeAmount - prize amount, in Wei, of this winner.
     */
    function getWinnerPrizeAmount(
            uint rankingPosition )
                                                        public view
    returns( uint finalPrizeAmount )
    {
        // Calculate total winner prize fund profit percentage & amount.
        uint winnerProfitPercentage = 
            (_100PERCENT) - cfg.poolProfitShare - 
            cfg.ownerProfitShare - cfg.minerProfitShare;

        uint totalPrizeAmount =
            ( ending_profitAmount * winnerProfitPercentage ) /
            ( _100PERCENT );


        // We compute the prize amounts differently for the algo-type
        // RolledRandomness, because distribution of these prizes is
        // non-deterministic - multiple holders could fall onto the
        // same ranking position, due to randomness of rolled score.
        //
        if( cfg.endingAlgoType == uint8(EndingAlgoType.RolledRandomness) )
        {
            // Here, we'll use Prize Sequence Factor approach differently.
            // We'll use the prizeSequenceFactor value not to compute
            // a geometric progression, but to compute an arithmetic
            // progression, where each ranking position will get a
            // prize equal to 
            // "totalPrizeAmount - rankingPosition * singleWinnerShare"
            //
            // singleWinnerShare is computed as a value corresponding
            // to single-winner's share of total prize amount.
            //
            // Using such an approach, winner at rank 0 would get a
            // prize equal to whole totalPrizeAmount, but, as the
            // scores are rolled using random factor, it's very unlikely
            // to get a such high score, so most likely such prize
            // won't ever be claimed, but it is a possibility.
            //
            // Most of the winners in this approach are likely to
            // roll scores in the middle, so would get prizes equal to
            // 1-10% of total prize funds.

            uint singleWinnerShare = totalPrizeAmount / 
                                     cfg.prizeSequence_winnerCount;

            return totalPrizeAmount - rankingPosition * singleWinnerShare;
        }

        // Now, we know that ending algorithm is normal (deterministic).
        // So, compute the prizes in a standard way.

        // If using Computed Sequence: loop for "rankingPosition"
        // iterations, while computing the prize shares.
        // If "rankingPosition" is larger than sequencedWinnerCount,
        // then compute the prize from sequence-leftover amount.
        if( cfg.prizeSequenceFactor != 0 )
        {
            require( rankingPosition < cfg.prizeSequence_winnerCount/*,
                     "Invalid ranking position!" */);

            // Leftover: If prizeSequenceFactor is 25%, it's 75%.
            uint leftoverPercentage = 
                (_100PERCENT) - cfg.prizeSequenceFactor;

            // Loop until the needed iteration.
            uint loopCount = ( 
                rankingPosition >= cfg.prizeSequence_sequencedWinnerCount ?
                cfg.prizeSequence_sequencedWinnerCount :
                rankingPosition
            );

            for( uint i = 0; i < loopCount; i++ )
            {
                totalPrizeAmount = 
                    ( totalPrizeAmount * leftoverPercentage ) /
                    ( _100PERCENT );
            }

            // Get end prize amount - sequenced, or leftover.
            // Leftover-mode.
            if( loopCount == cfg.prizeSequence_sequencedWinnerCount &&
                cfg.prizeSequence_winnerCount > 
                cfg.prizeSequence_sequencedWinnerCount )
            {
                // Now, totalPrizeAmount equals all leftover-group winner
                // prize funds.
                // So, just divide it by number of leftover winners.
                finalPrizeAmount = 
                    ( totalPrizeAmount ) /
                    ( cfg.prizeSequence_winnerCount -
                      cfg.prizeSequence_sequencedWinnerCount );
            }
            // Sequenced-mode
            else
            {
                finalPrizeAmount = 
                    ( totalPrizeAmount * cfg.prizeSequenceFactor ) /
                    ( _100PERCENT );
            }
        }

        // Else, if we're using Pre-Specified Array of winner profit
        // shares, just get the share at the corresponding index.
        else
        {
            require( rankingPosition < cfg.winnerProfitShares.length );

            finalPrizeAmount = 
                ( totalPrizeAmount *
                  cfg.winnerProfitShares[ rankingPosition ] ) /
                ( _100PERCENT );
        }
    }


    /**
     *  After lottery has completed, this function returns if msg.sender
     *  is one of lottery winners, and the position in winner rankings.
     *  
     *  Function must be used to obtain the ranking position before
     *  calling claimWinnerPrize().
     *
     *  @param addr - address whose status to check.
     */
    function getWinnerStatus( address addr )
                                                        external view
    returns( bool isWinner, uint32 rankingPosition, 
             uint prizeAmount )
    {
        if( !onStage( STAGE.COMPLETION ) || balanceOf( addr ) == 0 )
            return (false , 0, 0);

        ( isWinner, rankingPosition ) =
            lotStorage.getWinnerStatus( addr );

        if( isWinner )
        {
            prizeAmount = getWinnerPrizeAmount( rankingPosition );
            if( prizeAmount > address(this).balance )
                prizeAmount = address(this).balance;
        }
    }


    /**
     *  Compute the intermediate Active Stage player score.
     *  This score is Player Score, not randomized.
     *  @param addr - address to check.
     */
    function getPlayerIntermediateScore( address addr )
                                                        external view
    returns( uint )
    {
        return lotStorage.getPlayerActiveStageScore( addr );
    }


    /** PAYABLE [ OUT ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
     *
     *  Claim the winner prize of msg.sender, if he is one of the winners.
     *
     *  This function must be provided a ranking position of msg.sender,
     *  which must be obtained using the function above.
     *  
     *  The Lottery Storage then just checks if holder address in the
     *  winner array element at position rankingPosition is the same
     *  as msg.sender's.
     *
     *  If so, then claim request is valid, and we can give the appropriate
     *  prize to that winner.
     *  Prize can be determined by a computed factor-based sequence, or
     *  from the pre-specified winner array.
     *
     *  * This function transfers Ether out of our contract:
     *    - Sends the corresponding winner prize to the msg.sender.
     *
     *  @param rankingPosition - the position of Winner Array, that
     *      msg.sender says he is in (obtained using getWinnerStatus).
     */
    function claimWinnerPrize(
            uint32 rankingPosition )
                                    external
                                    onlyOnStage( STAGE.COMPLETION )
                                    mutexLOCKED
    {
        // Check if msg.sender hasn't already claimed his prize.
        require( ! prizeClaimersAddresses[ msg.sender ]/*,
                 "msg.sender has already claimed his prize!" */);

        // msg.sender must have at least some of UniLottery Tokens.
        require( balanceOf( msg.sender ) != 0/*,
                 "msg.sender's token balance can't be zero!" */);

        // Check if there are any prize funds left yet.
        require( address(this).balance != 0/*,
                 "All prize funds have already been claimed!" */);

        // If using Mined Selection Algo, check if msg.sender is 
        // really on that ranking position - algo was already executed.
        if( cfg.endingAlgoType == uint8(EndingAlgoType.MinedWinnerSelection) )
        {
            require( lotStorage.minedSelection_isAddressOnWinnerPosition(
                            msg.sender, rankingPosition )/*,
                     "msg.sender is not on specified winner position!" */);
        }
        // For other algorithms, get ranking position by executing
        // a specific algorithm of that algo-type.
        else
        {
            bool isWinner;
            ( isWinner, rankingPosition ) =
                lotStorage.getWinnerStatus( msg.sender );

            require( isWinner/*, "msg.sender is not a winner!" */);
        }

        // Compute the prize amount, using our internal function.
        uint finalPrizeAmount = getWinnerPrizeAmount( rankingPosition ); 

        // If prize is small and computation precision errors occured,
        // leading it to be larger than our balance, fix it.
        if( finalPrizeAmount > address(this).balance )
            finalPrizeAmount = address(this).balance;


        // Transfer the Winning Prize to msg.sender!
        msg.sender.transfer( finalPrizeAmount );


        // Mark msg.sender as already claimed his prize.
        prizeClaimersAddresses[ msg.sender ] = true;
    }


    /** PAYABLE [ OUT ] >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
     *
     *  Transfer the leftover Winner Prize Funds of this contract to the
     *  Main UniLottery Pool, if prize claim deadline has been exceeded.
     *
     *  Function can only be called from the Main Pool, and if some
     *  winners haven't managed to claim their prizes on time, their
     *  prizes will go back to UniLottery Pool.
     *
     *  * Function transfers Ether out of our contract:
     *    - Transfer the leftover funds to the Pool (msg.sender).
     */
    function getUnclaimedPrizes()
                                        external
                                        poolOnly
                                        onlyOnStage( STAGE.COMPLETION )
                                        mutexLOCKED
    {
        // Check if prize claim deadline has passed.
        require( completionDate != 0 &&
                 ( block.timestamp - completionDate ) > cfg.prizeClaimTime/*,
                 "Prize claim deadline not reached yet!" */);

        // Just transfer it all to the Pool.
        poolAddress.transfer( address(this).balance );
    }

}

/**
 *  The Lottery Storage contract.
 *
 *  This contract is used to store all Holder Data of a specific lottery
 *  contract - that includes lottery token holders list, and every
 *  holder's intermediate scores (HolderData structure).
 *
 *  When the lottery, that this storage belongs to, ends, then 
 *  this Storage contract also performs the whole winner selection
 *  algorithm.
 *
 *  Also, one of this contract's purposes is to split code,
 *  to avoid the 24kb code size limit error.
 *
 *  Notice, that Lottery and LotteryStorage contracts must have a
 *  1:1 relationship - every Lottery has only one Storage, and
 *  every Storage belongs to only one Lottery.
 *
 *  The LotteryStorage contracts are being created from the 
 *  LotteryStorageFactory contract, and only after that, the parent
 *  Lottery is created, so Lottery must initialize it's Storage,
 *  by calling initialize() function on freshly-created Storage,
 *  which set's the Lottery address, and locks it.
 */
contract LotteryStorage is CoreUniLotterySettings
{
    // ==================== Structs & Constants ==================== //

    // Struct of holder data & scores.
    struct HolderData 
    {
        // --------- Slot --------- //

        // If this holder provided a valid referral ID, this is the 
        // address of a referrer - the user who generated the said
        // referral ID.
        address referrer;

        // Bonus score points, which can be given in certain events,
        // such as when player registers a valid referral ID.
        int16 bonusScore;

        // Number of all child referrees, including multi-level ones.
        // Updated by traversing child->parent way, incrementing
        // every node's counter by one.
        // Used in Winner Selection Algorithm, to determine how much
        // to divide the accumulated referree scores by.
        uint16 referreeCount;


        // --------- Slot --------- //

        // If this holder has generated his own referral ID, this is
        // that ID. If he hasn't generated an ID, this is zero.
        uint256 referralID;


        // --------- Slot --------- //

        // The intermediate individual score factor variables.
        // Ether contributed: ( buys - sells ). Can be negative.
        int40 etherContributed;

        // Time x ether factor: (relativeTxTime * etherAmount).
        int40 timeFactors;

        // Token balance score factor of this holder - we use int,
        // for easier computation of player scores in our algorithms.
        int40 tokenBalance;

        // Accumulated referree score factors - ether contributed by
        // all referrees, time factors, and token balances of all
        // referrees.
        int40 referree_etherContributed;
        int40 referree_timeFactors;
        int40 referree_tokenBalance;
    }


    // Final Score (end lottery score * randomValue) structure.
    struct FinalScore 
    {
        address addr;           // 20 bytes \
        uint16 holderIndex;     // 2 bytes  | = 30 bytes => 1 slot.
        uint64 score;           // 8 bytes  /
    }


    // Winner Indexes structure - used to efficiently store Winner
    // indexes in holder's array, after completing the Winner Selection
    // Algorithm.
    // To save Space, we store these in a struct, with uint16 array
    // with 16 items - so this struct takes up excactly 1 slot.
    struct WinnerIndexStruct
    {
        uint16[ 16 ] indexes;
    }


    // A structure which is used by Winner Selection algorithm,
    // which is a subset of the LotteryConfig structure, containing
    // only items necessary for executing the Winner Selection algorigm.
    // More detailed member description can be found in LotteryConfig
    // structure description.
    // Takes up only one slot!
    struct WinnerAlgorithmConfig
    {
        // --------- Slot --------- //

        // Individual player max score parts.
        int16 maxPlayerScore_etherContributed;
        int16 maxPlayerScore_tokenHoldingAmount;
        int16 maxPlayerScore_timeFactor;
        int16 maxPlayerScore_refferalBonus;

        // Number of lottery winners.
        uint16 winnerCount;

        // Score-To-Random ration data (as a rational ratio number).
        // For example if 1:5, then scorePart = 1, and randPart = 5.
        uint16 randRatio_scorePart;
        uint16 randRatio_randPart;

        // The Ending Algorithm type.
        uint8 endingAlgoType;
    }


    // Structure containing the minimum and maximum values of
    // holder intermediate scores.
    // These values get updated on transfers during ACTIVE stage,
    // when holders buy/sell tokens.
    //
    // Used in winner selection algorithm, to normalize the scores in
    // a single loop, to avoid looping additional time to find min/max.
    //
    // Structure takes up only a single slot!
    //
    struct MinMaxHolderScores
    {
        // --------- Slot --------- //

        int40 holderScore_etherContributed_min;
        int40 holderScore_etherContributed_max;

        int40 holderScore_timeFactors_min;
        int40 holderScore_timeFactors_max;

        int40 holderScore_tokenBalance_min;
        int40 holderScore_tokenBalance_max;
    }

    // Referral score variant of the structure above.
    // Also, only a single slot!
    //
    struct MinMaxReferralScores
    {
        // --------- Slot --------- //

        // Min&Max values for referrer scores.
        int40 referralScore_etherContributed_min;
        int40 referralScore_etherContributed_max;

        int40 referralScore_timeFactors_min;
        int40 referralScore_timeFactors_max;

        int40 referralScore_tokenBalance_min;
        int40 referralScore_tokenBalance_max;
    }

    // ROOT_REFERRER constant.
    // Used to prevent cyclic dependencies on referral tree.
    address constant ROOT_REFERRER = address( 1 );

    // Max referral tree depth - maximum number of referrees that
    // a referrer can get.
    uint constant MAX_REFERRAL_DEPTH = 10;

    // Precision of division operations.
    int constant PRECISION = 10000;

    // Random number modulo to use when obtaining random numbers from
    // the random seed + nonce, using keccak256.
    // This is the maximum available Score Random Factor, plus one.
    // By default, 10^9 (one billion).
    //
    uint constant RANDOM_MODULO = (10 ** 9);

    // Maximum number of holders that the MinedWinnerSelection algorithm
    // can process. Related to block gas limit.
    uint constant MINEDSELECTION_MAX_NUMBER_OF_HOLDERS = 300;

    // Maximum number of holders that the WinnerSelfValidation algorithm
    // can process. Related to block gas limit.
    uint constant SELFVALIDATION_MAX_NUMBER_OF_HOLDERS = 1200;


    // ==================== State Variables ==================== //

    // --------- Slot --------- //

    // The Lottery address that this storage belongs to.
    // Is set by the "initialize()", called by corresponding Lottery.
    address lottery;

    // The Random Seed, that was passed to us from Randomness Provider,
    // or generated alternatively.
    uint64 randomSeed;

    // The actual number of winners that there will be. Set after
    // completing the Winner Selection Algorithm.
    uint16 numberOfWinners;

    // Bool indicating if Winner Selection Algorithm has been executed.
    bool algorithmCompleted;


    // --------- Slot --------- //

    // Winner Algorithm config. Specified in Initialization().
    WinnerAlgorithmConfig algConfig;

    // --------- Slot --------- //

    // The Min-Max holder score storage.
    MinMaxHolderScores public minMaxScores;
    MinMaxReferralScores public minMaxReferralScores;

    // --------- Slot --------- //

    // Array of holders.
    address[] public holders;

    // --------- Slot --------- //

    // Holder array indexes mapping, for O(1) array element access.
    mapping( address => uint ) holderIndexes;

    // --------- Slot --------- //

    // Mapping of holder data.
    mapping( address => HolderData ) public holderData;

    // --------- Slot --------- //

    // Mapping of referral IDs to addresses of holders who generated
    // those IDs.
    mapping( uint256 => address ) referrers;

    // --------- Slot --------- //

    // The array of final-sorted winners (set after Winner Selection
    // Algorithm completes), that contains the winners' indexes
    // in the "holders" array, to save space.
    //
    // Notice that by using uint16, we can fit 16 items into one slot!
    // So, if there are 160 winners, we only take up 10 slots, so
    // only 20,000 * 10 = 200,000 gas gets consumed!
    //
    WinnerIndexStruct[] sortedWinnerIndexes;



    // ==============       Internal (Private) Functions    ============== //

    // Lottery-Only modifier.
    modifier lotteryOnly
    {
        require( msg.sender == address( lottery )/*,
                 "Function can only be called by Lottery that this"
                 "Storage Contract belongs to!" */);
        _;
    }


    // ============== [ BEGIN ] LOTTERY QUICKSORT FUNCTIONS ============== //

    /**
     *  QuickSort and QuickSelect algorithm functionality code.
     *
     *  These algorithms are used to find the lottery winners in
     *  an array of final random-factored scores.
     *  As the highest-scorers win, we need to sort an array to
     *  identify them.
     *
     *  For this task, we use QuickSelect to partition array into
     *  winner part (elements with score larger than X, where X is
     *  n-th largest element, where n is number of winners),
     *  and others (non-winners), who are ignored to save computation
     *  power.
     *  Then we sort the winner part only, using QuickSort, and
     *  distribute prizes to winners accordingly.
     */

    // Swap function used in QuickSort algorithms.
    //
    function QSort_swap( FinalScore[] memory list, 
                         uint a, uint b )               
                                                        internal pure
    {
        FinalScore memory tmp = list[ a ];
        list[ a ] = list[ b ];
        list[ b ] = tmp;
    }

    // Standard Hoare's partition scheme function, used for both
    // QuickSort and QuickSelect.
    //
    function QSort_partition( 
            FinalScore[] memory list, 
            int lo, int hi )
                                                        internal pure
    returns( int newPivotIndex )
    {
        uint64 pivot = list[ uint( hi + lo ) / 2 ].score;
        int i = lo - 1;
        int j = hi + 1;

        while( true ) 
        {
            do {
                i++;
            } while( list[ uint( i ) ].score > pivot ) ;

            do {
                j--;
            } while( list[ uint( j ) ].score < pivot ) ;

            if( i >= j )
                return j;

            QSort_swap( list, uint( i ), uint( j ) );
        }
    }

    // QuickSelect's Lomuto partition scheme.
    //
    function QSort_LomutoPartition(
            FinalScore[] memory list,
            uint left, uint right, uint pivotIndex )
                                                        internal pure
    returns( uint newPivotIndex )
    {
        uint pivotValue = list[ pivotIndex ].score;
        QSort_swap( list, pivotIndex, right );  // Move pivot to end
        uint storeIndex = left;
        
        for( uint i = left; i < right; i++ )
        {
            if( list[ i ].score > pivotValue ) {
                QSort_swap( list, storeIndex, i );
                storeIndex++;
            }
        }

        // Move pivot to its final place, and return the pivot's index.
        QSort_swap( list, right, storeIndex );
        return storeIndex;
    }

    // QuickSelect algorithm (iterative).
    //
    function QSort_QuickSelect(
            FinalScore[] memory list,
            int left, int right, int k )
                                                        internal pure
    returns( int indexOfK )
    {
        while( true ) {
            if( left == right )
                return left;

            int pivotIndex = int( QSort_LomutoPartition( list, 
                    uint(left), uint(right), uint(right) ) );

            if( k == pivotIndex )
                return k;
            else if( k < pivotIndex )
                right = pivotIndex - 1;
            else
                left = pivotIndex + 1;
        }
    }

    // Standard QuickSort function.
    //
    function QSort_QuickSort(
            FinalScore[] memory list,
            int lo, int hi )
                                                        internal pure
    {
        if( lo < hi ) {
            int p = QSort_partition( list, lo, hi );
            QSort_QuickSort( list, lo, p );
            QSort_QuickSort( list, p + 1, hi );
        }
    }

    // ============== [ END ]   LOTTERY QUICKSORT FUNCTIONS ============== //

    // ------------ Ending Stage - Winner Selection Algorithm ------------ //

    /**
     *  Compute the individual player score factors for a holder.
     *  Function split from the below one (ending_Stage_2), to avoid
     *  "Stack too Deep" errors.
     */
    function computeHolderIndividualScores( 
            WinnerAlgorithmConfig memory cfg,
            MinMaxHolderScores memory minMax,
            HolderData memory hdata )
                                                        internal pure
    returns( int individualScore )
    {
        // Normalize the scores, by subtracting minimum and dividing
        // by maximum, to get the score values specified in cfg.
        // Use precision of 100, then round.
        //
        // Notice that we're using int arithmetics, so division 
        // truncates. That's why we use PRECISION, to simulate
        // rounding.
        //
        // This formula is better explained in example.
        // In this example, we use variable abbreviations defined
        // below, on formula's right side comments.
        //
        // Say, values are these in our example:
        // e = 4, eMin = 1, eMax = 8, MS = 5, P = 10.
        //
        // So, let's calculate the score using the formula:
        // ( ( ( (4 - 1) * 10 * 5 ) / (8 - 1) ) + (10 / 2) ) / 10 =
        // ( ( (    3    * 10 * 5 ) /    7    ) +     5    ) / 10 =
        // ( (         150          /    7    ) +     5    ) / 10 =
        // ( (         150          /    7    ) +     5    ) / 10 =
        // (                    20              +     5    ) / 10 =
        //                          25                       / 10 =
        //                        [ 2.5 ]                         = 2
        //
        // So, with truncation, we see that for e = 4, the score
        // is 2 out of 5 maximum.
        // That's because the minimum ether contributed was 1, and
        // maximum was 8.
        // So, 4 stays below the middle, and gets a nicely rounded 
        // score of 2.

        // Compute etherContributed.
        int score_etherContributed = ( (
            ( int( hdata.etherContributed -                      // e
                   minMax.holderScore_etherContributed_min )     // eMin
              * PRECISION * cfg.maxPlayerScore_etherContributed )// P * MS
            / int( minMax.holderScore_etherContributed_max -     // eMax
                   minMax.holderScore_etherContributed_min )     // eMin
        ) + (PRECISION / 2) ) / PRECISION;

        // Compute timeFactors.
        int score_timeFactors = ( (
            ( int( hdata.timeFactors -                          // e
                   minMax.holderScore_timeFactors_min )         // eMin
              * PRECISION * cfg.maxPlayerScore_timeFactor )     // P * MS
            / int( minMax.holderScore_timeFactors_max -         // eMax
                   minMax.holderScore_timeFactors_min )         // eMin
        ) + (PRECISION / 2) ) / PRECISION;

        // Compute tokenBalance.
        int score_tokenBalance = ( (
            ( int( hdata.tokenBalance -                         // e
                   minMax.holderScore_tokenBalance_min )        // eMin
              * PRECISION * cfg.maxPlayerScore_tokenHoldingAmount )
            / int( minMax.holderScore_tokenBalance_max -        // eMax
                   minMax.holderScore_tokenBalance_min )        // eMin
        ) + (PRECISION / 2) ) / PRECISION;

        // Return the accumulated individual score (excluding referrees).
        return score_etherContributed + score_timeFactors +
               score_tokenBalance;
    }


    /**
     *  Compute the unified Referree-Score of a player, who's got
     *  the accumulated factor-scores of all his referrees in his 
     *  holderData structure.
     *
     *  @param individualToReferralRatio - an int value, computed 
     *      before starting the winner score computation loop, in 
     *      the ending_Stage_2 initial part, to save computation
     *      time later.
     *      This is the ratio of the maximum available referral score,
     *      to the maximum available individual score, as defined in
     *      the config (for example, if max.ref.score is 20, and 
     *      max.ind.score is 40, then the ratio is 20/40 = 0.5).
     *      
     *      We use this ratio to transform the computed accumulated
     *      referree individual scores to the standard referrer's
     *      score, by multiplying by that ratio.
     */
    function computeReferreeScoresForHolder( 
            int individualToReferralRatio,
            WinnerAlgorithmConfig memory cfg,
            MinMaxReferralScores memory minMax,
            HolderData memory hdata )
                                                        internal pure
    returns( int unifiedReferreeScore )
    {
        // If number of referrees of this HODLer is Zero, then
        // his referree score is also zero.
        if( hdata.referreeCount == 0 )
            return 0;

        // Now, compute the Referree's Accumulated Scores.
        //
        // Here we use the same formula as when computing individual
        // scores (in the function above), but we use referree parts
        // instead.

        // Compute etherContributed.
        int referreeScore_etherContributed = ( (
            ( int( hdata.referree_etherContributed -
                   minMax.referralScore_etherContributed_min )
              * PRECISION * cfg.maxPlayerScore_etherContributed )
            / int( minMax.referralScore_etherContributed_max -
                   minMax.referralScore_etherContributed_min )
        ) );

        // Compute timeFactors.
        int referreeScore_timeFactors = ( (
            ( int( hdata.referree_timeFactors -
                   minMax.referralScore_timeFactors_min )
              * PRECISION * cfg.maxPlayerScore_timeFactor )
            / int( minMax.referralScore_timeFactors_max -
                   minMax.referralScore_timeFactors_min )
        ) );

        // Compute tokenBalance.
        int referreeScore_tokenBalance = ( (
            ( int( hdata.referree_tokenBalance -
                   minMax.referralScore_tokenBalance_min )
              * PRECISION * cfg.maxPlayerScore_tokenHoldingAmount )
            / int( minMax.referralScore_tokenBalance_max -
                   minMax.referralScore_tokenBalance_min )
        ) );


        // Accumulate 'em all !
        // Then, multiply it by the ratio of all individual max scores
        // (maxPlayerScore_etherContributed, timeFactor, tokenBalance),
        // to the maxPlayerScore_refferalBonus.
        // Use the same precision.
        unifiedReferreeScore = int( ( (
                ( ( referreeScore_etherContributed +
                    referreeScore_timeFactors +
                    referreeScore_tokenBalance ) + (PRECISION / 2)
                ) / PRECISION
            ) * individualToReferralRatio
        ) / PRECISION );
    }


    /**
     *  Update Min & Max values for individual holder scores.
     */
    function priv_updateMinMaxScores_individual(
            MinMaxHolderScores memory minMax,
            int40 _etherContributed,
            int40 _timeFactors,
            int40 _tokenBalance )
                                                    internal
                                                    pure
    {
        // etherContributed:
        if( _etherContributed > 
            minMax.holderScore_etherContributed_max )
            minMax.holderScore_etherContributed_max = 
                _etherContributed;

        if( _etherContributed <
            minMax.holderScore_etherContributed_min )
            minMax.holderScore_etherContributed_min = 
                _etherContributed;

        // timeFactors:
        if( _timeFactors > 
            minMax.holderScore_timeFactors_max )
            minMax.holderScore_timeFactors_max = 
                _timeFactors;

        if( _timeFactors <
            minMax.holderScore_timeFactors_min )
            minMax.holderScore_timeFactors_min = 
                _timeFactors;

        // tokenBalance:
        if( _tokenBalance > 
            minMax.holderScore_tokenBalance_max )
            minMax.holderScore_tokenBalance_max = 
                _tokenBalance;

        if( _tokenBalance <
            minMax.holderScore_tokenBalance_min )
            minMax.holderScore_tokenBalance_min = 
                _tokenBalance;
    }


    /**
     *  Update Min & Max values for referral scores.
     */
    function priv_updateMinMaxScores_referral(
            MinMaxReferralScores memory minMax,
            int40 _etherContributed,
            int40 _timeFactors,
            int40 _tokenBalance )
                                                    internal
                                                    pure
    {
        // etherContributed:
        if( _etherContributed > 
            minMax.referralScore_etherContributed_max )
            minMax.referralScore_etherContributed_max = 
                _etherContributed;

        if( _etherContributed <
            minMax.referralScore_etherContributed_min )
            minMax.referralScore_etherContributed_min = 
                _etherContributed;

        // timeFactors:
        if( _timeFactors > 
            minMax.referralScore_timeFactors_max )
            minMax.referralScore_timeFactors_max = 
                _timeFactors;

        if( _timeFactors <
            minMax.referralScore_timeFactors_min )
            minMax.referralScore_timeFactors_min = 
                _timeFactors;

        // tokenBalance:
        if( _tokenBalance > 
            minMax.referralScore_tokenBalance_max )
            minMax.referralScore_tokenBalance_max = 
                _tokenBalance;

        if( _tokenBalance <
            minMax.referralScore_tokenBalance_min )
            minMax.referralScore_tokenBalance_min = 
                _tokenBalance;
    }



    // =================== PUBLIC FUNCTIONS =================== //

    /**
     *  Update current holder's score with given change values, and
     *  Propagate the holder's current transfer's score changes
     *  through the referral chain, updating every parent referrer's
     *  accumulated referree scores, until the ROOT_REFERRER or zero
     *  address referrer is encountered.
     */
    function updateAndPropagateScoreChanges(
            address holder,
            int __etherContributed_change,
            int __timeFactors_change,
            int __tokenBalance_change )
                                                        public
                                                        lotteryOnly
    {
        // Convert the data into shrinked format - leave only
        // 4 decimals of Ether precision, and drop the decimal part
        // of ULT tokens absolutely.
        // Don't change TimeFactors, as it is already adjusted in
        // Lottery contract's code.
        int40 timeFactors_change = int40( __timeFactors_change );

        int40 etherContributed_change = int40(
            __etherContributed_change / int(1 ether / 10000) );
 
        int40 tokenBalance_change = int40(
            __tokenBalance_change / int(1 ether) );

        // Update current holder's score.
        holderData[ holder ].etherContributed += etherContributed_change;
        holderData[ holder ].timeFactors += timeFactors_change;
        holderData[ holder ].tokenBalance += tokenBalance_change;

        // Check if scores are exceeding current min/max scores, 
        // and if so, update the min/max scores.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_updateMinMaxScores_individual(
            minMaxCpy,
            holderData[ holder ].etherContributed,
            holderData[ holder ].timeFactors,
            holderData[ holder ].tokenBalance
        );

        // Propagate the score through the referral chain.
        // Dive at maximum to the depth of 10, to avoid "Outta Gas"
        // errors.
        uint depth = 0;
        address referrerAddr = holderData[ holder ].referrer;

        while( referrerAddr != ROOT_REFERRER && 
               referrerAddr != address( 0 )  &&
               depth < MAX_REFERRAL_DEPTH )
        {
            // Update this referrer's accumulated referree scores.
            holderData[ referrerAddr ].referree_etherContributed +=
                etherContributed_change;

            holderData[ referrerAddr ].referree_timeFactors +=
                timeFactors_change;

            holderData[ referrerAddr ].referree_tokenBalance +=
                tokenBalance_change;

            // Update MinMax according to this referrer's score.
            priv_updateMinMaxScores_referral(
                minMaxRefCpy,
                holderData[ referrerAddr ].referree_etherContributed,
                holderData[ referrerAddr ].referree_timeFactors,
                holderData[ referrerAddr ].referree_tokenBalance
            );

            // Move to the higher-level referrer.
            referrerAddr = holderData[ referrerAddr ].referrer;
            depth++;
        }

        // Check if MinMax have changed. If so, update it.
        if( keccak256( abi.encode( minMaxCpy ) ) != 
            keccak256( abi.encode( minMaxScores ) ) )
            minMaxScores = minMaxCpy;

        // Check referral part.
        if( keccak256( abi.encode( minMaxRefCpy ) ) != 
            keccak256( abi.encode( minMaxReferralScores ) ) )
            minMaxReferralScores = minMaxRefCpy;
    }


    /**
     *  Pure function to fix an in-memory copy of MinMaxScores,
     *  by changing equal min-max pairs to differ by one.
     *  This is needed to avoid division-by-zero in some calculations.
     */
    function priv_fixMinMaxIfEqual(
            MinMaxHolderScores memory minMaxCpy,
            MinMaxReferralScores memory minMaxRefCpy )
                                                            internal
                                                            pure
    {
        // Individual part
        if( minMaxCpy.holderScore_etherContributed_min ==
            minMaxCpy.holderScore_etherContributed_max )
            minMaxCpy.holderScore_etherContributed_max =
            minMaxCpy.holderScore_etherContributed_min + 1;

        if( minMaxCpy.holderScore_timeFactors_min ==
            minMaxCpy.holderScore_timeFactors_max )
            minMaxCpy.holderScore_timeFactors_max =
            minMaxCpy.holderScore_timeFactors_min + 1;

        if( minMaxCpy.holderScore_tokenBalance_min ==
            minMaxCpy.holderScore_tokenBalance_max )
            minMaxCpy.holderScore_tokenBalance_max =
            minMaxCpy.holderScore_tokenBalance_min + 1;

        // Referral part
        if( minMaxRefCpy.referralScore_etherContributed_min ==
            minMaxRefCpy.referralScore_etherContributed_max )
            minMaxRefCpy.referralScore_etherContributed_max =
            minMaxRefCpy.referralScore_etherContributed_min + 1;

        if( minMaxRefCpy.referralScore_timeFactors_min ==
            minMaxRefCpy.referralScore_timeFactors_max )
            minMaxRefCpy.referralScore_timeFactors_max =
            minMaxRefCpy.referralScore_timeFactors_min + 1;

        if( minMaxRefCpy.referralScore_tokenBalance_min ==
            minMaxRefCpy.referralScore_tokenBalance_max )
            minMaxRefCpy.referralScore_tokenBalance_max =
            minMaxRefCpy.referralScore_tokenBalance_min + 1;
    }


    /** 
     *  Function executes the Lottery Winner Selection Algorithm,
     *  and writes the final, sorted array, containing winner rankings.
     *
     *  This function is called from the Lottery's Mining Stage Step 2,
     *
     *  This is the final function that lottery performs actively - 
     *  and arguably the most important - because it determines 
     *  lottery winners through Winner Selection Algorithm.
     *
     *  The random seed must be already set, before calling this function.
     */
    function executeWinnerSelectionAlgorithm()
                                                        public
                                                        lotteryOnly
    {
        // Copy the Winner Algo Config into memory, to avoid using
        // 400-gas costing SLOAD every time we need to load something.
        WinnerAlgorithmConfig memory cfg = algConfig;

        // Can only be performed if algorithm is MinedWinnerSelection!
        require( cfg.endingAlgoType ==
                 uint8(Lottery.EndingAlgoType.MinedWinnerSelection)/*,
                 "Algorithm cannot be performed on current Algo-Type!" */);

        // Now, we gotta find the winners using a Randomized Score-Based
        // Winner Selection Algorithm.
        //
        // During transfers, all player intermediate scores 
        // (etherContributed, timeFactors, and tokenBalances) were
        // already set in every holder's HolderData structure,
        // during operations of updateHolderData_preTransfer() function.
        //
        // Minimum and maximum values are also known, so normalization
        // will be easy.
        // All referral tree score data were also properly propagated
        // during operations of updateAndPropagateScoreChanges() function.
        //
        // All we block.timestamp have to do, is loop through holder array, and
        // compute randomized final scores for every holder, into
        // the Final Score array.

        // Declare the Final Score array - computed for all holders.
        uint ARRLEN = 
            ( holders.length > MINEDSELECTION_MAX_NUMBER_OF_HOLDERS ?
              MINEDSELECTION_MAX_NUMBER_OF_HOLDERS : holders.length );

        FinalScore[] memory finalScores = new FinalScore[] ( ARRLEN );

        // Compute the precision-adjusted constant ratio of 
        // referralBonus max score to the player individual max scores.

        int individualToReferralRatio = 
            ( PRECISION * cfg.maxPlayerScore_refferalBonus ) /
            ( int( cfg.maxPlayerScore_etherContributed ) + 
              int( cfg.maxPlayerScore_timeFactor ) +
              int( cfg.maxPlayerScore_tokenHoldingAmount ) );

        // Max available player score.
        int maxAvailablePlayerScore = int(
                cfg.maxPlayerScore_etherContributed + 
                cfg.maxPlayerScore_timeFactor +
                cfg.maxPlayerScore_tokenHoldingAmount +
                cfg.maxPlayerScore_refferalBonus );


        // Random Factor of scores, to maintain random-to-determined
        // ratio equal to specific value (1:5 for example - 
        // "randPart" == 5/*, "scorePart" */== 1).
        //
        // maxAvailablePlayerScore * FACT   ---   scorePart
        // RANDOM_MODULO                    ---   randPart
        //
        //                                  RANDOM_MODULO * scorePart
        // maxAvailablePlayerScore * FACT = -------------------------
        //                                          randPart
        //
        //              RANDOM_MODULO * scorePart
        // FACT = --------------------------------------
        //          randPart * maxAvailablePlayerScore

        int SCORE_RAND_FACT =
            ( PRECISION * int(RANDOM_MODULO * cfg.randRatio_scorePart) ) /
            ( int(cfg.randRatio_randPart) * maxAvailablePlayerScore );


        // Fix Min-Max scores, to avoid division by zero, if min == max.
        // If min == max, make the difference equal to 1.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_fixMinMaxIfEqual( minMaxCpy, minMaxRefCpy );

        // Loop through all the holders.
        for( uint i = 0; i < ARRLEN; i++ )
        {
            // Fetch the needed holder data to in-memory hdata variable,
            // to save gas on score part computing functions.
            HolderData memory hdata;

            // Slot 1:
            hdata.etherContributed =
                holderData[ holders[ i ] ].etherContributed;
            hdata.timeFactors =
                holderData[ holders[ i ] ].timeFactors;
            hdata.tokenBalance =
                holderData[ holders[ i ] ].tokenBalance;
            hdata.referreeCount =
                holderData[ holders[ i ] ].referreeCount;

            // Slot 2:
            hdata.referree_etherContributed =
                holderData[ holders[ i ] ].referree_etherContributed;
            hdata.referree_timeFactors =
                holderData[ holders[ i ] ].referree_timeFactors;
            hdata.referree_tokenBalance =
                holderData[ holders[ i ] ].referree_tokenBalance;
            hdata.bonusScore =
                holderData[ holders[ i ] ].bonusScore;


            // Now, add bonus score, and compute total player's score:
            // Bonus part, individual score part, and referree score part.
            int totalPlayerScore = 
                    hdata.bonusScore
                    +
                    computeHolderIndividualScores(
                        cfg, minMaxCpy, hdata )
                    +
                    computeReferreeScoresForHolder( 
                        individualToReferralRatio, cfg, 
                        minMaxRefCpy, hdata );


            // Check if total player score <= 0. If so, make it equal
            // to 1, because otherwise randomization won't be possible.
            if( totalPlayerScore <= 0 )
                totalPlayerScore = 1;

            // Now, check if it's not more than max! If so, lowerify.
            // This could have happen'd because of bonus.
            if( totalPlayerScore > maxAvailablePlayerScore )
                totalPlayerScore = maxAvailablePlayerScore;


            // Multiply the score by the Random Modulo Adjustment
            // Factor, to get fairer ratio of random-to-determined data.
            totalPlayerScore =  ( totalPlayerScore * SCORE_RAND_FACT ) /
                                ( PRECISION );

            // Score is computed!
            // Now, randomize it, and add to Final Scores Array.
            // We use keccak to generate a random number from random seed,
            // using holder's address as a nonce.

            uint modulizedRandomNumber = uint(
                keccak256( abi.encodePacked( randomSeed, holders[ i ] ) )
            ) % RANDOM_MODULO;

            // Add the random number, to introduce the random factor.
            // Ratio of (current) totalPlayerScore to modulizedRandomNumber
            // is the same as ratio of randRatio_scorePart to 
            // randRatio_randPart.

            uint endScore = uint( totalPlayerScore ) + modulizedRandomNumber;

            // Finally, set this holder's final score data.
            finalScores[ i ].addr = holders[ i ];
            finalScores[ i ].holderIndex = uint16( i );
            finalScores[ i ].score = uint64( endScore );
        }

        // All final scores are block.timestamp computed.
        // Sort the array, to find out the highest scores!

        // Firstly, partition an array to only work on top K scores,
        // where K is the number of winners.
        // There can be a rare case where specified number of winners is
        // more than lottery token holders. We got that covered.

        require( finalScores.length > 0 );

        uint K = cfg.winnerCount - 1;
        if( K > finalScores.length-1 )
            K = finalScores.length-1;   // Must be THE LAST ELEMENT's INDEX.

        // Use QuickSelect to do this.
        QSort_QuickSelect( finalScores, 0, 
            int( finalScores.length - 1 ), int( K ) );

        // Now, QuickSort only the first K items, because the rest
        // item scores are not high enough to become winners.
        QSort_QuickSort( finalScores, 0, int( K ) );

        // Now, the winner array is sorted, with the highest scores
        // sitting at the first positions!
        // Let's set up the winner indexes array, where we'll store
        // the winners' indexes in the holders array.
        // So, if this array is [8, 2, 3], that means that
        // Winner #1 is holders[8], winner #2 is holders[2], and
        // winner #3 is holders[3].

        // Set the Number Of Winners variable.
        numberOfWinners = uint16( K + 1 );

        // Now, we can loop through the first numberOfWinners elements, to set
        // the holder indexes!
        // Loop through 16 elements at a time, to fill the structs.
        for( uint offset = 0; offset < numberOfWinners; offset += 16 )
        {
            WinnerIndexStruct memory windStruct;
            uint loopStop = ( offset + 16 > numberOfWinners ?
                              numberOfWinners : offset + 16 );

            for( uint i = offset; i < loopStop; i++ )
            {
                windStruct.indexes[ i - offset ] =finalScores[ i ].holderIndex;
            }

            // Push this block.timestamp-filled struct to the storage array!
            sortedWinnerIndexes.push( windStruct );
        }

        // That's it! We're done!
        algorithmCompleted = true;
    }


    /**
     *  Add a holder to holders array.
     *  @param holder   - address of a holder to add.
     */
    function addHolder( address holder )
                                                        public
                                                        lotteryOnly
    {
        // Add it to list, and set index in the mapping.
        holders.push( holder );
        holderIndexes[ holder ] = holders.length - 1;
    }

    /**
     *  Removes the holder 'sender' from the Holders Array.
     *  However, this holder's HolderData structure persists!
     *
     *  Notice that no index validity checks are performed, so, if
     *  'sender' is not present in "holderIndexes" mapping, this
     *  function will remove the 0th holder instead!
     *  This is not a problem for us, because Lottery calls this
     *  function only when it's absolutely certain that 'sender' is
     *  present in the holders array.
     *
     *  @param sender   - address of a holder to remove.
     *      Named 'sender', because when token sender sends away all
     *      his tokens, he must then be removed from holders array.
     */
    function removeHolder( address sender )
                                                        public
                                                        lotteryOnly
    {
        // Get index of the sender address in the holders array.
        uint index = holderIndexes[ sender ];

        // Remove the sender from array, by copying last element's
        // value into the index'th element, where sender was before.
        holders[ index ] = holders[ holders.length - 1 ];

        // Remove the last element of array, which we've just copied.
        holders.pop();

        // Update indexes: remove the sender's index from the mapping,
        // and change the previoulsy-last element's index to the
        // one where we copied it - where sender was before.
        delete holderIndexes[ sender ];
        holderIndexes[ holders[ index ] ] = index;
    }


    /**
     *  Get holder array length.
     */
    function getHolderCount()
                                                    public view
    returns( uint )
    {
        return holders.length;
    }


    /**
     *  Generate a referral ID for a token holder.
     *  Referral ID is used to refer other wallets into playing our
     *  lottery.
     *  - Referrer gets bonus points for every wallet that bought 
     *    lottery tokens and specified his referral ID.
     *  - Referrees (wallets who got referred by registering a valid
     *    referral ID, corresponding to some referrer), get some
     *    bonus points for specifying (registering) a referral ID.
     *
     *  Referral ID is a uint256 number, which is generated by
     *  keccak256'ing the holder's address, holder's current
     *  token ballance, and current time.
     */
    function generateReferralID( address holder )
                                                            public
                                                            lotteryOnly
    returns( uint256 referralID )
    {
        // Check if holder has some tokens, and doesn't
        // have his own referral ID yet.
        require( holderData[ holder ].tokenBalance != 0/*,
                 "holder doesn't have any lottery tokens!" */);

        require( holderData[ holder ].referralID == 0/*,
                 "Holder already has a referral ID!" */);

        // Generate a referral ID with keccak.
        uint256 refID = uint256( keccak256( abi.encodePacked( 
                holder, holderData[ holder ].tokenBalance, block.timestamp ) ) );

        // Specify the ID as current ID of this holder.
        holderData[ holder ].referralID = refID;

        // If this holder wasn't referred by anyone (his referrer is
        // not set), and he's block.timestamp generated his own ID, he won't
        // be able to register as a referree of someone else 
        // from block.timestamp on.
        // This is done to prevent circular dependency in referrals.
        // Do it by setting a referrer to ROOT_REFERRER address,
        // which is an invalid address (address(1)).
        if( holderData[ holder ].referrer == address( 0 ) )
            holderData[ holder ].referrer = ROOT_REFERRER;

        // Create a new referrer with this ID.
        referrers[ refID ] = holder;
        
        return refID;
    }


    /**
     *  Register a referral for a token holder, using a valid
     *  referral ID got from a referrer.
     *  This function is called by a referree, who obtained a
     *  valid referral ID from some referrer, who previously
     *  generated it using generateReferralID().
     *
     *  You can only register a referral once!
     *  When you do so, you get bonus referral points!
     */
    function registerReferral(
            address holder,
            int16 referralRegisteringBonus,
            uint256 referralID )
                                                            public
                                                            lotteryOnly
    returns( address _referrerAddress )
    {
        // Check if this holder has some tokens, and if he hasn't
        // registered a referral yet.
        require( holderData[ holder ].tokenBalance != 0/*,
                 "holder doesn't have any lottery tokens!" */);

        require( holderData[ holder ].referrer == address( 0 )/*,
                 "holder already has registered a referral!" */);

        // Create a local memory copy of minMaxReferralScores.
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        // Get the referrer's address from his ID, and specify
        // it as a referrer of holder.
        holderData[ holder ].referrer = referrers[ referralID ];

        // Bonus points are added to this holder's score for
        // registering a referral!
        holderData[ holder ].bonusScore = referralRegisteringBonus;

        // Increment number of referrees for every parent referrer,
        // by traversing a referral tree child->parent way.
        address referrerAddr = holderData[ holder ].referrer;

        // Set the return value.
        _referrerAddress = referrerAddr;

        // Traverse a tree.
        while( referrerAddr != ROOT_REFERRER && 
               referrerAddr != address( 0 ) )
        {
            // Increment referree count for this referrrer.
            holderData[ referrerAddr ].referreeCount++;

            // Update the Referrer Scores of the referrer, adding this
            // referree's scores to it's current values.
            holderData[ referrerAddr ].referree_etherContributed +=
                holderData[ holder ].etherContributed;

            holderData[ referrerAddr ].referree_timeFactors +=
                holderData[ holder ].timeFactors;

            holderData[ referrerAddr ].referree_tokenBalance +=
                holderData[ holder ].tokenBalance;

            // Update MinMax according to this referrer's score.
            priv_updateMinMaxScores_referral(
                minMaxRefCpy,
                holderData[ referrerAddr ].referree_etherContributed,
                holderData[ referrerAddr ].referree_timeFactors,
                holderData[ referrerAddr ].referree_tokenBalance
            );

            // Move to the higher-level referrer.
            referrerAddr = holderData[ referrerAddr ].referrer;
        }

        // Update MinMax Referral Scores if needed.
        if( keccak256( abi.encode( minMaxRefCpy ) ) != 
            keccak256( abi.encode( minMaxReferralScores ) ) )
            minMaxReferralScores = minMaxRefCpy;

        return _referrerAddress;
    }


    /**
     *  Sets our random seed to some value.
     *  Should be called from Lottery, after obtaining random seed from
     *  the Randomness Provider.
     */
    function setRandomSeed( uint _seed )
                                                    external
                                                    lotteryOnly
    {
        randomSeed = uint64( _seed );
    }


    /**
     *  Initialization function.
     *  Here, we bind our contract to the Lottery contract that 
     *  this Storage belongs to.
     *  The parent lottery must call this function - hence, we set
     *  "lottery" to msg.sender.
     *
     *  When this function is called, our contract must be not yet
     *  initialized - "lottery" address must be Zero!
     *
     *  Here, we also set our Winner Algorithm config, which is a
     *  subset of LotteryConfig, fitting into 1 storage slot.
     */
    function initialize(
            WinnerAlgorithmConfig memory _wcfg )
                                                        public
    {
        require( address( lottery ) == address( 0 )/*,
                 "Storage is already initialized!" */);

        // Set the Lottery address (msg.sender can't be zero),
        // and thus, set our contract to initialized!
        lottery = msg.sender;

        // Set the Winner-Algo-Config.
        algConfig = _wcfg;

        // NOT-NEEDED: Set initial min-max scores: min is INT_MAX.
        /*minMaxScores.holderScore_etherContributed_min = int80( 2 ** 78 );
        minMaxScores.holderScore_timeFactors_min    = int80( 2 ** 78 );
        minMaxScores.holderScore_tokenBalance_min   = int80( 2 ** 78 );
        */
    }


    // ==================== Views ==================== //


    // Returns the current random seed.
    // If the seed hasn't been set yet (or set to 0), returns 0.
    //
    function getRandomSeed()
                                                    external view
    returns( uint )
    {
        return randomSeed;
    }


    // Check if Winner Selection Algorithm has beed executed.
    //
    function minedSelection_algorithmAlreadyExecuted()
                                                        external view
    returns( bool )
    {
        return algorithmCompleted;
    }

    /**
     *  After lottery has completed, this function returns if "addr"
     *  is one of lottery winners, and the position in winner rankings.
     *  Function is used to obtain the ranking position before
     *  calling claimWinnerPrize() on Lottery.
     *
     *  This function should be called off-chain, and then using the
     *  retrieved data, one can call claimWinnerPrize().
     */
    function minedSelection_getWinnerStatus(
            address addr )
                                                        public view
    returns( bool isWinner, 
             uint32 rankingPosition )
    {
        // Loop through the whole winner indexes array, trying to
        // find if "addr" is one of the winner addresses.
        for( uint16 i = 0; i < numberOfWinners; i++ )
        {
            // Check if holder on this winner ranking's index position
            // is addr, if so, good!
            uint pos = sortedWinnerIndexes[ i / 16 ].indexes[ i % 16 ];

            if( holders[ pos ] == addr )
            {
                return ( true, i );
            }
        }

        // The "addr" is not a winner.
        return ( false, 0 );
    }

    /**
     *  Checks if address is on specified winner ranking position.
     *  Used in Lottery, to check if msg.sender is really the 
     *  winner #rankingPosition, as he claims to be.
     */
    function minedSelection_isAddressOnWinnerPosition( 
            address addr,
            uint32  rankingPosition )
                                                    external view
    returns( bool )
    {
        if( rankingPosition >= numberOfWinners )
            return false;

        // Just check if address at "holders" array 
        // index "sortedWinnerIndexes[ position ]" is really the "addr".
        uint pos = sortedWinnerIndexes[ rankingPosition / 16 ]
                    .indexes[ rankingPosition % 16 ];

        return ( holders[ pos ] == addr );
    }


    /**
     *  Returns an array of all winner addresses, sorted by their
     *  ranking position (winner #1 first, #2 second, etc.).
     */
    function minedSelection_getAllWinners()
                                                    external view
    returns( address[] memory )
    {
        address[] memory winners = new address[] ( numberOfWinners );

        for( uint i = 0; i < numberOfWinners; i++ )
        {
            uint pos = sortedWinnerIndexes[ i / 16 ].indexes[ i % 16 ];
            winners[ i ] = holders[ pos ];
        }

        return winners;
    }


    /**
     *  Compute the Lottery Active Stage Score of a token holder.
     *
     *  This function computes the Active Stage (pre-randomization)
     *  player score, and should generally be used to compute player
     *  intermediate scores - while lottery is still active or on
     *  finishing stage, before random random seed is obtained.
     */
    function getPlayerActiveStageScore( address holderAddr )
                                                            external view
    returns( uint playerScore )
    {
        // Copy the Winner Algo Config into memory, to avoid using
        // 400-gas costing SLOAD every time we need to load something.
        WinnerAlgorithmConfig memory cfg = algConfig;

        // Check if holderAddr is a holder at all!
        if( holders[ holderIndexes[ holderAddr ] ] != holderAddr )
            return 0;

        // Compute the precision-adjusted constant ratio of 
        // referralBonus max score to the player individual max scores.

        int individualToReferralRatio = 
            ( PRECISION * cfg.maxPlayerScore_refferalBonus ) /
            ( int( cfg.maxPlayerScore_etherContributed ) + 
              int( cfg.maxPlayerScore_timeFactor ) +
              int( cfg.maxPlayerScore_tokenHoldingAmount ) );

        // Max available player score.
        int maxAvailablePlayerScore = int(
                cfg.maxPlayerScore_etherContributed + 
                cfg.maxPlayerScore_timeFactor +
                cfg.maxPlayerScore_tokenHoldingAmount +
                cfg.maxPlayerScore_refferalBonus );

        // Fix Min-Max scores, to avoid division by zero, if min == max.
        // If min == max, make the difference equal to 1.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_fixMinMaxIfEqual( minMaxCpy, minMaxRefCpy );

        // Now, add bonus score, and compute total player's score:
        // Bonus part, individual score part, and referree score part.
        int totalPlayerScore = 
                holderData[ holderAddr ].bonusScore
                +
                computeHolderIndividualScores(
                    cfg, minMaxCpy, holderData[ holderAddr ] )
                +
                computeReferreeScoresForHolder( 
                    individualToReferralRatio, cfg, 
                    minMaxRefCpy, holderData[ holderAddr ] );


        // Check if total player score <= 0. If so, make it equal
        // to 1, because otherwise randomization won't be possible.
        if( totalPlayerScore <= 0 )
            totalPlayerScore = 1;

        // Now, check if it's not more than max! If so, lowerify.
        // This could have happen'd because of bonus.
        if( totalPlayerScore > maxAvailablePlayerScore )
            totalPlayerScore = maxAvailablePlayerScore;

        // Return the score!
        return uint( totalPlayerScore );
    }



    /**
     *  Internal sub-procedure of the function below, used to obtain
     *  a final, randomized score of a Single Holder.
     */
    function priv_getSingleHolderScore(
            address hold3r,
            int individualToReferralRatio,
            int maxAvailablePlayerScore,
            int SCORE_RAND_FACT,
            WinnerAlgorithmConfig memory cfg,
            MinMaxHolderScores memory minMaxCpy,
            MinMaxReferralScores memory minMaxRefCpy )
                                                        internal view
    returns( uint endScore )
    {
        // Fetch the needed holder data to in-memory hdata variable,
        // to save gas on score part computing functions.
        HolderData memory hdata;

        // Slot 1:
        hdata.etherContributed =
            holderData[ hold3r ].etherContributed;
        hdata.timeFactors =
            holderData[ hold3r ].timeFactors;
        hdata.tokenBalance =
            holderData[ hold3r ].tokenBalance;
        hdata.referreeCount =
            holderData[ hold3r ].referreeCount;

        // Slot 2:
        hdata.referree_etherContributed =
            holderData[ hold3r ].referree_etherContributed;
        hdata.referree_timeFactors =
            holderData[ hold3r ].referree_timeFactors;
        hdata.referree_tokenBalance =
            holderData[ hold3r ].referree_tokenBalance;
        hdata.bonusScore =
            holderData[ hold3r ].bonusScore;


        // Now, add bonus score, and compute total player's score:
        // Bonus part, individual score part, and referree score part.
        int totalPlayerScore = 
                hdata.bonusScore
                +
                computeHolderIndividualScores(
                    cfg, minMaxCpy, hdata )
                +
                computeReferreeScoresForHolder( 
                    individualToReferralRatio, cfg, 
                    minMaxRefCpy, hdata );


        // Check if total player score <= 0. If so, make it equal
        // to 1, because otherwise randomization won't be possible.
        if( totalPlayerScore <= 0 )
            totalPlayerScore = 1;

        // Now, check if it's not more than max! If so, lowerify.
        // This could have happen'd because of bonus.
        if( totalPlayerScore > maxAvailablePlayerScore )
            totalPlayerScore = maxAvailablePlayerScore;


        // Multiply the score by the Random Modulo Adjustment
        // Factor, to get fairer ratio of random-to-determined data.
        totalPlayerScore =  ( totalPlayerScore * SCORE_RAND_FACT ) /
                            ( PRECISION );

        // Score is computed!
        // Now, randomize it, and add to Final Scores Array.
        // We use keccak to generate a random number from random seed,
        // using holder's address as a nonce.

        uint modulizedRandomNumber = uint(
            keccak256( abi.encodePacked( randomSeed, hold3r ) )
        ) % RANDOM_MODULO;

        // Add the random number, to introduce the random factor.
        // Ratio of (current) totalPlayerScore to modulizedRandomNumber
        // is the same as ratio of randRatio_scorePart to 
        // randRatio_randPart.

        return uint( totalPlayerScore ) + modulizedRandomNumber;
    }


    /**
     *  Winner Self-Validation algo-type main function.
     *  Here, we compute scores for all lottery holders iteratively
     *  in O(n) time, and thus get the winner ranking position of
     *  the holder in question.
     *
     *  This function performs essentialy the same steps as the
     *  Mined-variant (executeWinnerSelectionAlgorithm), but doesn't
     *  write anything to blockchain.
     *
     *  @param holderAddr - address of a holder whose rank we want to find.
     */
    function winnerSelfValidation_getWinnerStatus(
            address holderAddr )
                                                        internal view
    returns( bool isWinner, uint rankingPosition )
    {
        // Copy the Winner Algo Config into memory, to avoid using
        // 400-gas costing SLOAD every time we need to load something.
        WinnerAlgorithmConfig memory cfg = algConfig;

        // Can only be performed if algorithm is WinnerSelfValidation!
        require( cfg.endingAlgoType ==
                 uint8(Lottery.EndingAlgoType.WinnerSelfValidation)/*,
                 "Algorithm cannot be performed on current Algo-Type!" */);

        // Check if holderAddr is a holder at all!
        require( holders[ holderIndexes[ holderAddr ] ] == holderAddr/*,
                 "holderAddr is not a lottery token holder!" */);

        // Now, we gotta find the winners using a Randomized Score-Based
        // Winner Selection Algorithm.
        //
        // During transfers, all player intermediate scores 
        // (etherContributed, timeFactors, and tokenBalances) were
        // already set in every holder's HolderData structure,
        // during operations of updateHolderData_preTransfer() function.
        //
        // Minimum and maximum values are also known, so normalization
        // will be easy.
        // All referral tree score data were also properly propagated
        // during operations of updateAndPropagateScoreChanges() function.
        //
        // All we block.timestamp have to do, is loop through holder array, and
        // compute randomized final scores for every holder.

        // Compute the precision-adjusted constant ratio of 
        // referralBonus max score to the player individual max scores.

        int individualToReferralRatio = 
            ( PRECISION * cfg.maxPlayerScore_refferalBonus ) /
            ( int( cfg.maxPlayerScore_etherContributed ) + 
              int( cfg.maxPlayerScore_timeFactor ) +
              int( cfg.maxPlayerScore_tokenHoldingAmount ) );

        // Max available player score.
        int maxAvailablePlayerScore = int(
                cfg.maxPlayerScore_etherContributed + 
                cfg.maxPlayerScore_timeFactor +
                cfg.maxPlayerScore_tokenHoldingAmount +
                cfg.maxPlayerScore_refferalBonus );


        // Random Factor of scores, to maintain random-to-determined
        // ratio equal to specific value (1:5 for example - 
        // "randPart" == 5/*, "scorePart" */== 1).
        //
        // maxAvailablePlayerScore * FACT   ---   scorePart
        // RANDOM_MODULO                    ---   randPart
        //
        //                                  RANDOM_MODULO * scorePart
        // maxAvailablePlayerScore * FACT = -------------------------
        //                                          randPart
        //
        //              RANDOM_MODULO * scorePart
        // FACT = --------------------------------------
        //          randPart * maxAvailablePlayerScore

        int SCORE_RAND_FACT =
            ( PRECISION * int(RANDOM_MODULO * cfg.randRatio_scorePart) ) /
            ( int(cfg.randRatio_randPart) * maxAvailablePlayerScore );


        // Fix Min-Max scores, to avoid division by zero, if min == max.
        // If min == max, make the difference equal to 1.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_fixMinMaxIfEqual( minMaxCpy, minMaxRefCpy );

        // How many holders had higher scores than "holderAddr".
        // Used to obtain the final winner rank of "holderAddr".
        uint numOfHoldersHigherThan = 0;

        // The final (randomized) score of "holderAddr".
        uint holderAddrsFinalScore = priv_getSingleHolderScore(
            holderAddr,
            individualToReferralRatio,
            maxAvailablePlayerScore,
            SCORE_RAND_FACT,
            cfg, minMaxCpy, minMaxRefCpy );

        // Index of holderAddr.
        uint holderAddrIndex = holderIndexes[ holderAddr ];


        // Loop through all the allowed holders.
        for( uint i = 0; 
             i < ( holders.length < SELFVALIDATION_MAX_NUMBER_OF_HOLDERS ? 
                   holders.length : SELFVALIDATION_MAX_NUMBER_OF_HOLDERS );
             i++ )
        {
            // Skip the holderAddr's index.
            if( i == holderAddrIndex )
                continue;

            // Compute the score using helper function.
            uint endScore = priv_getSingleHolderScore(
                holders[ i ],
                individualToReferralRatio,
                maxAvailablePlayerScore,
                SCORE_RAND_FACT,
                cfg, minMaxCpy, minMaxRefCpy );

            // Check if score is higher than HolderAddr's, and if so, check.
            if( endScore > holderAddrsFinalScore )
                numOfHoldersHigherThan++;
        }

        // All scores are checked!
        // Now, we can obtain holderAddr's winner rank based on how
        // many scores were above holderAddr's score!

        isWinner = ( numOfHoldersHigherThan < cfg.winnerCount ); 
        rankingPosition = numOfHoldersHigherThan;
    }



    /**
     *  Rolled-Randomness algo-type main function.
     *  Here, we only compute the score of the holder in question,
     *  and compare it to maximum-available final score, divided
     *  by no-of-winners.
     *
     *  @param holderAddr - address of a holder whose rank we want to find.
     */
    function rolledRandomness_getWinnerStatus(
            address holderAddr )
                                                        internal view
    returns( bool isWinner, uint rankingPosition )
    {
        // Copy the Winner Algo Config into memory, to avoid using
        // 400-gas costing SLOAD every time we need to load something.
        WinnerAlgorithmConfig memory cfg = algConfig;

        // Can only be performed if algorithm is RolledRandomness!
        require( cfg.endingAlgoType ==
                 uint8(Lottery.EndingAlgoType.RolledRandomness)/*,
                 "Algorithm cannot be performed on current Algo-Type!" */);

        // Check if holderAddr is a holder at all!
        require( holders[ holderIndexes[ holderAddr ] ] == holderAddr/*,
                 "holderAddr is not a lottery token holder!" */);

        // Now, we gotta find the winners using a Randomized Score-Based
        // Winner Selection Algorithm.
        //
        // During transfers, all player intermediate scores 
        // (etherContributed, timeFactors, and tokenBalances) were
        // already set in every holder's HolderData structure,
        // during operations of updateHolderData_preTransfer() function.
        //
        // Minimum and maximum values are also known, so normalization
        // will be easy.
        // All referral tree score data were also properly propagated
        // during operations of updateAndPropagateScoreChanges() function.
        //
        // All we block.timestamp have to do, is loop through holder array, and
        // compute randomized final scores for every holder.

        // Compute the precision-adjusted constant ratio of 
        // referralBonus max score to the player individual max scores.

        int individualToReferralRatio = 
            ( PRECISION * cfg.maxPlayerScore_refferalBonus ) /
            ( int( cfg.maxPlayerScore_etherContributed ) + 
              int( cfg.maxPlayerScore_timeFactor ) +
              int( cfg.maxPlayerScore_tokenHoldingAmount ) );

        // Max available player score.
        int maxAvailablePlayerScore = int(
                cfg.maxPlayerScore_etherContributed + 
                cfg.maxPlayerScore_timeFactor +
                cfg.maxPlayerScore_tokenHoldingAmount +
                cfg.maxPlayerScore_refferalBonus );


        // Random Factor of scores, to maintain random-to-determined
        // ratio equal to specific value (1:5 for example - 
        // "randPart" == 5, "scorePart" == 1).
        //
        // maxAvailablePlayerScore * FACT   ---   scorePart
        // RANDOM_MODULO                    ---   randPart
        //
        //                                  RANDOM_MODULO * scorePart
        // maxAvailablePlayerScore * FACT = -------------------------
        //                                          randPart
        //
        //              RANDOM_MODULO * scorePart
        // FACT = --------------------------------------
        //          randPart * maxAvailablePlayerScore

        int SCORE_RAND_FACT =
            ( PRECISION * int(RANDOM_MODULO * cfg.randRatio_scorePart) ) /
            ( int(cfg.randRatio_randPart) * maxAvailablePlayerScore );


        // Fix Min-Max scores, to avoid division by zero, if min == max.
        // If min == max, make the difference equal to 1.
        MinMaxHolderScores memory minMaxCpy = minMaxScores;
        MinMaxReferralScores memory minMaxRefCpy = minMaxReferralScores;

        priv_fixMinMaxIfEqual( minMaxCpy, minMaxRefCpy );

        // The final (randomized) score of "holderAddr".
        uint holderAddrsFinalScore = priv_getSingleHolderScore(
            holderAddr,
            individualToReferralRatio,
            maxAvailablePlayerScore,
            SCORE_RAND_FACT,
            cfg, minMaxCpy, minMaxRefCpy );

        // Now, compute the Max-Final-Random Score, divide it
        // by the Holder Count, and get the ranking by placing this
        // holder's score in it's corresponding part.
        //
        // In this approach, we assume linear randomness distribution.
        // In practice, distribution might be a bit different, but this
        // approach is the most efficient.
        //
        // Max-Final-Score (randomized) is the highest available score
        // that can be achieved, and is made by adding together the
        // maximum availabe Player Score Part and maximum available
        // Random Part (equals RANDOM_MODULO).
        // These parts have a ratio equal to config-specified
        // randRatio_scorePart to randRatio_randPart.
        //
        // So, if player's active stage's score is low (1), but rand-part
        // in ratio is huge, then the score is mostly random, so 
        // maxFinalScore is close to the RANDOM_MODULO - maximum random
        // value that can be rolled.
        //
        // If, however, we use 1:1 playerScore-to-Random Ratio, then
        // playerScore and RandomScore make up equal parts of end score,
        // so the maxFinalScore is actually two times larger than
        // RANDOM_MODULO, so player needs to score more
        // player-points to get larger prizes.
        //
        // In default configuration, playerScore-to-random ratio is 1:3,
        // so there's a good randomness factor, so even the low-scoring
        // players can reasonably hope to get larger prizes, but
        // the higher is player's active stage score, the more
        // chances of scoring a high final score a player gets, with
        // the higher-end of player scores basically guaranteeing
        // themselves a specific prize amount, if winnerCount is
        // big enough to overlap.

        int maxRandomPart      = int( RANDOM_MODULO - 1 );
        int maxPlayerScorePart = ( SCORE_RAND_FACT * maxAvailablePlayerScore )
                                 / PRECISION;

        uint maxFinalScore = uint( maxRandomPart + maxPlayerScorePart );

        // Compute the amount that single-holder's virtual part
        // might take up in the max-final score.
        uint singleHolderPart = maxFinalScore / holders.length;

        // Now, compute how many single-holder-parts are there in
        // this holder's score.
        uint holderAddrScorePartCount = holderAddrsFinalScore /
                                        singleHolderPart;

        // The ranking is that number, minus holders length.
        // If very high score is scored, default to position 0 (highest).
        rankingPosition = (
            holderAddrScorePartCount < holders.length ?
            holders.length - holderAddrScorePartCount : 0
        );

        isWinner = ( rankingPosition < cfg.winnerCount );
    }


    /**
     *  Genericized, algorithm type-dependent getWinnerStatus function.
     */
    function getWinnerStatus(
            address addr )
                                                        external view
    returns( bool isWinner, uint32 rankingPosition )
    {
        bool _isW;
        uint _rp;

        if( algConfig.endingAlgoType == 
            uint8(Lottery.EndingAlgoType.RolledRandomness) )
        {
            (_isW, _rp) = rolledRandomness_getWinnerStatus( addr );
            return ( _isW, uint32( _rp ) );
        }

        if( algConfig.endingAlgoType ==
            uint8(Lottery.EndingAlgoType.WinnerSelfValidation) )
        {
            (_isW, _rp) = winnerSelfValidation_getWinnerStatus( addr );
            return ( _isW, uint32( _rp ) );
        }

        if( algConfig.endingAlgoType ==
            uint8(Lottery.EndingAlgoType.MinedWinnerSelection) )
        {
            (_isW, _rp) = minedSelection_getWinnerStatus( addr );
            return ( _isW, uint32( _rp ) );
        }
    }

}

// 
/**
 *  This is a storage-stub contract of the Lottery Token, which contains
 *  only the state (storage) of a Lottery Token, and delegates all logic
 *  to the actual code implementation.
 *  This approach is very gas-efficient for deploying new lotteries.
 */
contract LotteryStub {
    // ============ ERC20 token contract's storage ============ //

    // ------- Slot ------- //

    // Balances of token holders.
    mapping (address => uint256) private _balances;

    // ------- Slot ------- //

    // Allowances of spenders for a specific token owner.
    mapping (address => mapping (address => uint256)) private _allowances;

    // ------- Slot ------- //

    // Total supply of the token.
    uint256 private _totalSupply;


    // ============== Lottery contract's storage ============== //

    // ------- Initial Slots ------- //

    // The config which is passed to constructor.
    Lottery.LotteryConfig internal cfg;

    // ------- Slot ------- //

    // The Lottery Storage contract, which stores all holder data,
    // such as scores, referral tree data, etc.
    LotteryStorage /*public*/ lotStorage;

    // ------- Slot ------- //

    // Pool address. Set on constructor from msg.sender.
    address payable /*public*/ poolAddress;

    // ------- Slot ------- //
    
    // Randomness Provider address.
    address /*public*/ randomnessProvider;

    // ------- Slot ------- //

    // Exchange address. In Uniswap mode, it's the Uniswap liquidity 
    // pair's address, where trades execute.
    address /*public*/ exchangeAddress;

    // Start date.
    uint32 /*public*/ startDate;

    // Completion (Mining Phase End) date.
    uint32 /*public*/ completionDate;
    
    // The date when Randomness Provider was called, requesting a
    // random seed for the lottery finish.
    // Also, when this variable becomes Non-Zero, it indicates that we're
    // on Ending Stage Part One: waiting for the random seed.
    uint32 finish_timeRandomSeedRequested;

    // ------- Slot ------- //

    // WETH address. Set by calling Router's getter, on constructor.
    address WETHaddress;

    // Is the WETH first or second token in our Uniswap Pair?
    bool uniswap_ethFirst;

    // If we are, or were before, on finishing stage, this is the
    // probability of lottery going to Ending Stage on this transaction.
    uint32 finishProbablity;
    
    // Re-Entrancy Lock (Mutex).
    // We protect for reentrancy in the Fund Transfer functions.
    bool reEntrancyMutexLocked;
    
    // On which stage we are currently.
    uint8 /*public*/ lotteryStage;
    
    // Indicator for whether the lottery fund gains have passed a 
    // minimum fund gain requirement.
    // After that time point (when this bool is set), the token sells
    // which could drop the fund value below the requirement, would
    // be denied.
    bool fundGainRequirementReached;
    
    // The current step of the Mining Stage.
    uint16 miningStep;

    // If we're currently on Special Transfer Mode - that is, we allow
    // direct transfers between parties even in NON-ACTIVE state.
    bool specialTransferModeEnabled;


    // ------- Slot ------- //
    
    // Per-Transaction Pseudo-Random hash value (transferHashValue).
    // This value is computed on every token transfer, by keccak'ing
    // the last (current) transferHashValue, msg.sender, block.timestamp, and 
    // transaction count.
    //
    // This is used on Finishing Stage, as a pseudo-random number,
    // which is used to check if we should end the lottery (move to
    // Ending Stage).
    uint256 transferHashValue;

    // ------- Slot ------- //

    // On lottery end, get & store the lottery total ETH return
    // (including initial funds), and profit amount.
    uint128 /*public*/ ending_totalReturn;
    uint128 /*public*/ ending_profitAmount;

    // ------- Slot ------- //

    // The mapping that contains TRUE for addresses that already claimed
    // their lottery winner prizes.
    // Used only in COMPLETION, on claimWinnerPrize(), to check if
    // msg.sender has already claimed his prize.
    mapping( address => bool ) /*public*/ prizeClaimersAddresses;



    // =================== OUR CONTRACT'S OWN STORAGE =================== //

    // The address of the delegate contract, containing actual logic.
    address payable public __delegateContract;


    // ===================          Functions         =================== //

    // Constructor.
    // Just set the delegate's address.
    function stub_construct( address payable _delegateAddr )
                                                                external
    {
        require( __delegateContract == address(0) );
        __delegateContract = _delegateAddr;
    }

    // Fallback payable function, which delegates any call to our
    // contract, into the delegate contract.
    fallback()
                external payable 
    {
        // DelegateCall the delegate code contract.
        ( bool success, bytes memory data ) =
            __delegateContract.delegatecall( msg.data );

        // Use inline assembly to be able to return value from the fallback.
        // (by default, returning a value from fallback is not possible,
        // but it's still possible to manually copy data to the
        // return buffer.
        assembly
        {
            // delegatecall returns 0 (false) on error.
            // Add 32 bytes to "data" pointer, because first slot (32 bytes)
            // contains the length, and we use return value's length
            // from returndatasize() opcode.
            switch success
                case 0  { revert( add( data, 32 ), returndatasize() ) }
                default { return( add( data, 32 ), returndatasize() ) }
        }
    }

    // Receive ether function.
    receive()   external payable
    { }

}

/**
 *  LotteryStorage contract's storage-stub.
 *  Uses delagate calls to execute actual code on this contract's behalf.
 */
contract LotteryStorageStub {
    // =============== LotteryStorage contract's storage ================ //

    // --------- Slot --------- //

    // The Lottery address that this storage belongs to.
    // Is set by the "initialize()", called by corresponding Lottery.
    address lottery;

    // The Random Seed, that was passed to us from Randomness Provider,
    // or generated alternatively.
    uint64 randomSeed;

    // The actual number of winners that there will be. Set after
    // completing the Winner Selection Algorithm.
    uint16 numberOfWinners;

    // Bool indicating if Winner Selection Algorithm has been executed.
    bool algorithmCompleted;


    // --------- Slot --------- //

    // Winner Algorithm config. Specified in Initialization().
    LotteryStorage.WinnerAlgorithmConfig algConfig;

    // --------- Slot --------- //

    // The Min-Max holder score storage.
    LotteryStorage.MinMaxHolderScores minMaxScores;

    // --------- Slot --------- //

    // Array of holders.
    address[] /*public*/ holders;

    // --------- Slot --------- //

    // Holder array indexes mapping, for O(1) array element access.
    mapping( address => uint ) holderIndexes;

    // --------- Slot --------- //

    // Mapping of holder data.
    mapping( address => LotteryStorage.HolderData ) /*public*/ holderData;

    // --------- Slot --------- //

    // Mapping of referral IDs to addresses of holders who generated
    // those IDs.
    mapping( uint256 => address ) referrers;

    // --------- Slot --------- //

    // The array of final-sorted winners (set after Winner Selection
    // Algorithm completes), that contains the winners' indexes
    // in the "holders" array, to save space.
    //
    // Notice that by using uint16, we can fit 16 items into one slot!
    // So, if there are 160 winners, we only take up 10 slots, so
    // only 20,000 * 10 = 200,000 gas gets consumed!
    //
    LotteryStorage.WinnerIndexStruct[] sortedWinnerIndexes;


    // =================== OUR CONTRACT'S OWN STORAGE =================== //

    // The address of the delegate contract, containing actual logic.
    address public __delegateContract;


    // ===================          Functions         =================== //


    // Constructor.
    // Just set the delegate's address.
    function stub_construct( address _delegateAddr )
                                                                external
    {
        require( __delegateContract == address(0) );
        __delegateContract = _delegateAddr;
    }


    // Fallback function, which delegates any call to our
    // contract, into the delegate contract.
    fallback()
                external
    {
        // DelegateCall the delegate code contract.
        ( bool success, bytes memory data ) =
            __delegateContract.delegatecall( msg.data );

        // Use inline assembly to be able to return value from the fallback.
        // (by default, returning a value from fallback is not possible,
        // but it's still possible to manually copy data to the
        // return buffer.
        assembly
        {
            // delegatecall returns 0 (false) on error.
            // Add 32 bytes to "data" pointer, because first slot (32 bytes)
            // contains the length, and we use return value's length
            // from returndatasize() opcode.
            switch success
                case 0  { revert( add( data, 32 ), returndatasize() ) }
                default { return( add( data, 32 ), returndatasize() ) }
        }
    }
}

Contract Security Audit

Contract ABI

[{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"__delegateContract","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_delegateAddr","type":"address"}],"name":"stub_construct","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

608060405234801561001057600080fd5b50610237806100206000396000f3fe60806040526004361061002d5760003560e01c806329130b4d146100c25780635fecd096146100e457610034565b3661003457005b60115460405160009160609173ffffffffffffffffffffffffffffffffffffffff9091169061006690849036906101d0565b600060405180830381855af49150503d80600081146100a1576040519150601f19603f3d011682016040523d82523d6000602084013e6100a6565b606091505b509150915081600081146100bb573d60208301f35b3d60208301fd5b3480156100ce57600080fd5b506100e26100dd366004610195565b61010f565b005b3480156100f057600080fd5b506100f9610179565b60405161010691906101e0565b60405180910390f35b60115473ffffffffffffffffffffffffffffffffffffffff161561013257600080fd5b601180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60115473ffffffffffffffffffffffffffffffffffffffff1681565b6000602082840312156101a6578081fd5b813573ffffffffffffffffffffffffffffffffffffffff811681146101c9578182fd5b9392505050565b6000828483379101908152919050565b73ffffffffffffffffffffffffffffffffffffffff9190911681526020019056fea2646970667358221220b48021a785e8f9304c2d4219a161b8a85c48f3346eb78c8082733b18156551e364736f6c63430007010033

Deployed Bytecode

0x60806040526004361061002d5760003560e01c806329130b4d146100c25780635fecd096146100e457610034565b3661003457005b60115460405160009160609173ffffffffffffffffffffffffffffffffffffffff9091169061006690849036906101d0565b600060405180830381855af49150503d80600081146100a1576040519150601f19603f3d011682016040523d82523d6000602084013e6100a6565b606091505b509150915081600081146100bb573d60208301f35b3d60208301fd5b3480156100ce57600080fd5b506100e26100dd366004610195565b61010f565b005b3480156100f057600080fd5b506100f9610179565b60405161010691906101e0565b60405180910390f35b60115473ffffffffffffffffffffffffffffffffffffffff161561013257600080fd5b601180547fffffffffffffffffffffffff00000000000000000000000000000000000000001673ffffffffffffffffffffffffffffffffffffffff92909216919091179055565b60115473ffffffffffffffffffffffffffffffffffffffff1681565b6000602082840312156101a6578081fd5b813573ffffffffffffffffffffffffffffffffffffffff811681146101c9578182fd5b9392505050565b6000828483379101908152919050565b73ffffffffffffffffffffffffffffffffffffffff9190911681526020019056fea2646970667358221220b48021a785e8f9304c2d4219a161b8a85c48f3346eb78c8082733b18156551e364736f6c63430007010033

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.