ETH Price: $2,448.04 (+6.10%)

Transaction Decoder

Block:
11734708 at Jan-27-2021 12:44:20 AM +UTC
Transaction Fee:
0.0088062 ETH $21.56
Gas Used:
176,124 Gas / 50 Gwei

Emitted Events:

26 Dispatcher.0x262ab020cb638b76c90ba54ebb8ec0a4ff4412b8d6777f1edc4c23d6644a88cd( 0x262ab020cb638b76c90ba54ebb8ec0a4ff4412b8d6777f1edc4c23d6644a88cd, 0x000000000000000000000000ef0ac29f56a699ab41dd2a2ef5546a9cb874fdae, 0x00000000000000000000000000000000000000000000000000000000000048dd, 000000000000000000000000000000000000000000000000dce82a6bfa338e7d )
27 Dispatcher.0x6e826cfd4b2f0d3e70085110ff45fc6023aaa1ef2cd87f58f574aee310489ebc( 0x6e826cfd4b2f0d3e70085110ff45fc6023aaa1ef2cd87f58f574aee310489ebc, 0x000000000000000000000000ef0ac29f56a699ab41dd2a2ef5546a9cb874fdae, 0x00000000000000000000000000000000000000000000000000000000000048df, 00000000000000000000000000000000000000000000032d26d12e980b600000 )

Account State Difference:

  Address   Before After State Difference Code
0x67E4A942...2Dfa54D64
(BeePool)
694.360349090076837155 Eth694.369155290076837155 Eth0.0088062
0xA1AFCade...05e31c9B1
0.048465104159505674 Eth
Nonce: 89
0.039658904159505674 Eth
Nonce: 90
0.0088062
0xbbD3C0C7...6fcfCb2e2
(NuCypher: StakingEscrow)

Execution Trace

Dispatcher.CALL( )
  • StakingEscrow.DELEGATECALL( )
    • Dispatcher.b46ffb45( )
      • PolicyManager.ping( _node=0xeF0aC29f56a699ab41DD2a2EF5546A9Cb874Fdae, _processedPeriod1=18653, _processedPeriod2=0, _periodToSetDefault=18655 )
        File 1 of 4: Dispatcher
        {"Address.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n    /**\n     * @dev Returns true if `account` is a contract.\n     *\n     * [IMPORTANT]\n     * ====\n     * It is unsafe to assume that an address for which this function returns\n     * false is an externally-owned account (EOA) and not a contract.\n     *\n     * Among others, `isContract` will return false for the following\n     * types of addresses:\n     *\n     *  - an externally-owned account\n     *  - a contract in construction\n     *  - an address where a contract will be created\n     *  - an address where a contract lived, but was destroyed\n     * ====\n     */\n    function isContract(address account) internal view returns (bool) {\n        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts\n        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned\n        // for accounts without code, i.e. `keccak256(\u0027\u0027)`\n        bytes32 codehash;\n        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;\n        // solhint-disable-next-line no-inline-assembly\n        assembly { codehash := extcodehash(account) }\n        return (codehash != accountHash \u0026\u0026 codehash != 0x0);\n    }\n\n    /**\n     * @dev Replacement for Solidity\u0027s `transfer`: sends `amount` wei to\n     * `recipient`, forwarding all available gas and reverting on errors.\n     *\n     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n     * of certain opcodes, possibly making contracts go over the 2300 gas limit\n     * imposed by `transfer`, making them unable to receive funds via\n     * `transfer`. {sendValue} removes this limitation.\n     *\n     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n     *\n     * IMPORTANT: because control is transferred to `recipient`, care must be\n     * taken to not create reentrancy vulnerabilities. Consider using\n     * {ReentrancyGuard} or the\n     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n     *\n     * _Available since v2.4.0._\n     */\n    function sendValue(address payable recipient, uint256 amount) internal {\n        require(address(this).balance \u003e= amount, \"Address: insufficient balance\");\n\n        // solhint-disable-next-line avoid-call-value\n        (bool success, ) = recipient.call{value: amount}(\"\");\n        require(success, \"Address: unable to send value, recipient may have reverted\");\n    }\n}\n"},"Dispatcher.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"./Upgradeable.sol\";\nimport \"./Address.sol\";\n\n\n/**\n* @notice ERC897 - ERC DelegateProxy\n*/\ninterface ERCProxy {\n    function proxyType() external pure returns (uint256);\n    function implementation() external view returns (address);\n}\n\n\n/**\n* @notice Proxying requests to other contracts.\n* Client should use ABI of real contract and address of this contract\n*/\ncontract Dispatcher is Upgradeable, ERCProxy {\n    using Address for address;\n\n    event Upgraded(address indexed from, address indexed to, address owner);\n    event RolledBack(address indexed from, address indexed to, address owner);\n\n    /**\n    * @dev Set upgrading status before and after operations\n    */\n    modifier upgrading()\n    {\n        isUpgrade = UPGRADE_TRUE;\n        _;\n        isUpgrade = UPGRADE_FALSE;\n    }\n\n    /**\n    * @param _target Target contract address\n    */\n    constructor(address _target) upgrading {\n        require(_target.isContract());\n        // Checks that target contract inherits Dispatcher state\n        verifyState(_target);\n        // `verifyState` must work with its contract\n        verifyUpgradeableState(_target, _target);\n        target = _target;\n        finishUpgrade();\n        emit Upgraded(address(0), _target, msg.sender);\n    }\n\n    //------------------------ERC897------------------------\n    /**\n     * @notice ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy\n     */\n    function proxyType() external pure override returns (uint256) {\n        return 2;\n    }\n\n    /**\n     * @notice ERC897, gets the address of the implementation where every call will be delegated\n     */\n    function implementation() external view override returns (address) {\n        return target;\n    }\n    //------------------------------------------------------------\n\n    /**\n    * @notice Verify new contract storage and upgrade target\n    * @param _target New target contract address\n    */\n    function upgrade(address _target) public onlyOwner upgrading {\n        require(_target.isContract());\n        // Checks that target contract has \"correct\" (as much as possible) state layout\n        verifyState(_target);\n        //`verifyState` must work with its contract\n        verifyUpgradeableState(_target, _target);\n        if (target.isContract()) {\n            verifyUpgradeableState(target, _target);\n        }\n        previousTarget = target;\n        target = _target;\n        finishUpgrade();\n        emit Upgraded(previousTarget, _target, msg.sender);\n    }\n\n    /**\n    * @notice Rollback to previous target\n    * @dev Test storage carefully before upgrade again after rollback\n    */\n    function rollback() public onlyOwner upgrading {\n        require(previousTarget.isContract());\n        emit RolledBack(target, previousTarget, msg.sender);\n        // should be always true because layout previousTarget -\u003e target was already checked\n        // but `verifyState` is not 100% accurate so check again\n        verifyState(previousTarget);\n        if (target.isContract()) {\n            verifyUpgradeableState(previousTarget, target);\n        }\n        target = previousTarget;\n        previousTarget = address(0);\n        finishUpgrade();\n    }\n\n    /**\n    * @dev Call verifyState method for Upgradeable contract\n    */\n    function verifyUpgradeableState(address _from, address _to) private {\n        (bool callSuccess,) = _from.delegatecall(abi.encodeWithSelector(this.verifyState.selector, _to));\n        require(callSuccess);\n    }\n\n    /**\n    * @dev Call finishUpgrade method from the Upgradeable contract\n    */\n    function finishUpgrade() private {\n        (bool callSuccess,) = target.delegatecall(abi.encodeWithSelector(this.finishUpgrade.selector, target));\n        require(callSuccess);\n    }\n\n    function verifyState(address _testTarget) public override onlyWhileUpgrading {\n        //checks equivalence accessing state through new contract and current storage\n        require(address(uint160(delegateGet(_testTarget, this.owner.selector))) == owner());\n        require(address(uint160(delegateGet(_testTarget, this.target.selector))) == target);\n        require(address(uint160(delegateGet(_testTarget, this.previousTarget.selector))) == previousTarget);\n        require(uint8(delegateGet(_testTarget, this.isUpgrade.selector)) == isUpgrade);\n    }\n\n    /**\n    * @dev Override function using empty code because no reason to call this function in Dispatcher\n    */\n    function finishUpgrade(address) public override {}\n\n    /**\n    * @dev Receive function sends empty request to the target contract\n    */\n    receive() external payable {\n        assert(target.isContract());\n        // execute receive function from target contract using storage of the dispatcher\n        (bool callSuccess,) = target.delegatecall(\"\");\n        if (!callSuccess) {\n            revert();\n        }\n    }\n\n    /**\n    * @dev Fallback function sends all requests to the target contract\n    */\n    fallback() external payable {\n        assert(target.isContract());\n        // execute requested function from target contract using storage of the dispatcher\n        (bool callSuccess,) = target.delegatecall(msg.data);\n        if (callSuccess) {\n            // copy result of the request to the return data\n            // we can use the second return value from `delegatecall` (bytes memory)\n            // but it will consume a little more gas\n            assembly {\n                returndatacopy(0x0, 0x0, returndatasize())\n                return(0x0, returndatasize())\n            }\n        } else {\n            revert();\n        }\n    }\n\n}\n"},"Ownable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\n/**\n * @title Ownable\n * @dev The Ownable contract has an owner address, and provides basic authorization control\n * functions, this simplifies the implementation of \"user permissions\".\n */\nabstract contract Ownable {\n    address private _owner;\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev The Ownable constructor sets the original `owner` of the contract to the sender\n     * account.\n     */\n    constructor () {\n        _owner = msg.sender;\n        emit OwnershipTransferred(address(0), _owner);\n    }\n\n    /**\n     * @return the address of the owner.\n     */\n    function owner() public view returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        require(isOwner());\n        _;\n    }\n\n    /**\n     * @return true if `msg.sender` is the owner of the contract.\n     */\n    function isOwner() public view returns (bool) {\n        return msg.sender == _owner;\n    }\n\n    /**\n     * @dev Allows the current owner to relinquish control of the contract.\n     * @notice Renouncing to ownership will leave the contract without an owner.\n     * It will not be possible to call the functions with the `onlyOwner`\n     * modifier anymore.\n     */\n    function renounceOwnership() public onlyOwner {\n        emit OwnershipTransferred(_owner, address(0));\n        _owner = address(0);\n    }\n\n    /**\n     * @dev Allows the current owner to transfer control of the contract to a newOwner.\n     * @param newOwner The address to transfer ownership to.\n     */\n    function transferOwnership(address newOwner) public onlyOwner {\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers control of the contract to a newOwner.\n     * @param newOwner The address to transfer ownership to.\n     */\n    function _transferOwnership(address newOwner) internal {\n        require(newOwner != address(0));\n        emit OwnershipTransferred(_owner, newOwner);\n        _owner = newOwner;\n    }\n}\n"},"Upgradeable.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"./Ownable.sol\";\n\n\n/**\n* @notice Base contract for upgradeable contract\n* @dev Inherited contract should implement verifyState(address) method by checking storage variables\n* (see verifyState(address) in Dispatcher). Also contract should implement finishUpgrade(address)\n* if it is using constructor parameters by coping this parameters to the dispatcher storage\n*/\nabstract contract Upgradeable is Ownable {\n\n    event StateVerified(address indexed testTarget, address sender);\n    event UpgradeFinished(address indexed target, address sender);\n\n    /**\n    * @dev Contracts at the target must reserve the same location in storage for this address as in Dispatcher\n    * Stored data actually lives in the Dispatcher\n    * However the storage layout is specified here in the implementing contracts\n    */\n    address public target;\n\n    /**\n    * @dev Previous contract address (if available). Used for rollback\n    */\n    address public previousTarget;\n\n    /**\n    * @dev Upgrade status. Explicit `uint8` type is used instead of `bool` to save gas by excluding 0 value\n    */\n    uint8 public isUpgrade;\n\n    /**\n    * @dev Guarantees that next slot will be separated from the previous\n    */\n    uint256 stubSlot;\n\n    /**\n    * @dev Constants for `isUpgrade` field\n    */\n    uint8 constant UPGRADE_FALSE = 1;\n    uint8 constant UPGRADE_TRUE = 2;\n\n    /**\n    * @dev Checks that function executed while upgrading\n    * Recommended to add to `verifyState` and `finishUpgrade` methods\n    */\n    modifier onlyWhileUpgrading()\n    {\n        require(isUpgrade == UPGRADE_TRUE);\n        _;\n    }\n\n    /**\n    * @dev Method for verifying storage state.\n    * Should check that new target contract returns right storage value\n    */\n    function verifyState(address _testTarget) public virtual onlyWhileUpgrading {\n        emit StateVerified(_testTarget, msg.sender);\n    }\n\n    /**\n    * @dev Copy values from the new target to the current storage\n    * @param _target New target contract address\n    */\n    function finishUpgrade(address _target) public virtual onlyWhileUpgrading {\n        emit UpgradeFinished(_target, msg.sender);\n    }\n\n    /**\n    * @dev Base method to get data\n    * @param _target Target to call\n    * @param _selector Method selector\n    * @param _numberOfArguments Number of used arguments\n    * @param _argument1 First method argument\n    * @param _argument2 Second method argument\n    * @return memoryAddress Address in memory where the data is located\n    */\n    function delegateGetData(\n        address _target,\n        bytes4 _selector,\n        uint8 _numberOfArguments,\n        bytes32 _argument1,\n        bytes32 _argument2\n    )\n        internal returns (bytes32 memoryAddress)\n    {\n        assembly {\n            memoryAddress := mload(0x40)\n            mstore(memoryAddress, _selector)\n            if gt(_numberOfArguments, 0) {\n                mstore(add(memoryAddress, 0x04), _argument1)\n            }\n            if gt(_numberOfArguments, 1) {\n                mstore(add(memoryAddress, 0x24), _argument2)\n            }\n            switch delegatecall(gas(), _target, memoryAddress, add(0x04, mul(0x20, _numberOfArguments)), 0, 0)\n                case 0 {\n                    revert(memoryAddress, 0)\n                }\n                default {\n                    returndatacopy(memoryAddress, 0x0, returndatasize())\n                }\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" without parameters.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(address _target, bytes4 _selector)\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 0, 0, 0);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" with one parameter.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(address _target, bytes4 _selector, bytes32 _argument)\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 1, _argument, 0);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" with two parameters.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(\n        address _target,\n        bytes4 _selector,\n        bytes32 _argument1,\n        bytes32 _argument2\n    )\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 2, _argument1, _argument2);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n}\n"}}

        File 2 of 4: StakingEscrow
        {"AdditionalMath.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"SafeMath.sol\";\n\n\n/**\n* @notice Additional math operations\n*/\nlibrary AdditionalMath {\n    using SafeMath for uint256;\n\n    function max16(uint16 a, uint16 b) internal pure returns (uint16) {\n        return a \u003e= b ? a : b;\n    }\n\n    function min16(uint16 a, uint16 b) internal pure returns (uint16) {\n        return a \u003c b ? a : b;\n    }\n\n    /**\n    * @notice Division and ceil\n    */\n    function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {\n        return (a.add(b) - 1) / b;\n    }\n\n    /**\n    * @dev Adds signed value to unsigned value, throws on overflow.\n    */\n    function addSigned(uint256 a, int256 b) internal pure returns (uint256) {\n        if (b \u003e= 0) {\n            return a.add(uint256(b));\n        } else {\n            return a.sub(uint256(-b));\n        }\n    }\n\n    /**\n    * @dev Subtracts signed value from unsigned value, throws on overflow.\n    */\n    function subSigned(uint256 a, int256 b) internal pure returns (uint256) {\n        if (b \u003e= 0) {\n            return a.sub(uint256(b));\n        } else {\n            return a.add(uint256(-b));\n        }\n    }\n\n    /**\n    * @dev Multiplies two numbers, throws on overflow.\n    */\n    function mul32(uint32 a, uint32 b) internal pure returns (uint32) {\n        if (a == 0) {\n            return 0;\n        }\n        uint32 c = a * b;\n        assert(c / a == b);\n        return c;\n    }\n\n    /**\n    * @dev Adds two numbers, throws on overflow.\n    */\n    function add16(uint16 a, uint16 b) internal pure returns (uint16) {\n        uint16 c = a + b;\n        assert(c \u003e= a);\n        return c;\n    }\n\n    /**\n    * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).\n    */\n    function sub16(uint16 a, uint16 b) internal pure returns (uint16) {\n        assert(b \u003c= a);\n        return a - b;\n    }\n\n    /**\n    * @dev Adds signed value to unsigned value, throws on overflow.\n    */\n    function addSigned16(uint16 a, int16 b) internal pure returns (uint16) {\n        if (b \u003e= 0) {\n            return add16(a, uint16(b));\n        } else {\n            return sub16(a, uint16(-b));\n        }\n    }\n\n    /**\n    * @dev Subtracts signed value from unsigned value, throws on overflow.\n    */\n    function subSigned16(uint16 a, int16 b) internal pure returns (uint16) {\n        if (b \u003e= 0) {\n            return sub16(a, uint16(b));\n        } else {\n            return add16(a, uint16(-b));\n        }\n    }\n}\n"},"Bits.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n/**\n* @dev Taken from https://github.com/ethereum/solidity-examples/blob/master/src/bits/Bits.sol\n*/\nlibrary Bits {\n\n    uint256 internal constant ONE = uint256(1);\n\n    /**\n    * @notice Sets the bit at the given \u0027index\u0027 in \u0027self\u0027 to:\n    *  \u00271\u0027 - if the bit is \u00270\u0027\n    *  \u00270\u0027 - if the bit is \u00271\u0027\n    * @return The modified value\n    */\n    function toggleBit(uint256 self, uint8 index) internal pure returns (uint256) {\n        return self ^ ONE \u003c\u003c index;\n    }\n\n    /**\n    * @notice Get the value of the bit at the given \u0027index\u0027 in \u0027self\u0027.\n    */\n    function bit(uint256 self, uint8 index) internal pure returns (uint8) {\n        return uint8(self \u003e\u003e index \u0026 1);\n    }\n\n    /**\n    * @notice Check if the bit at the given \u0027index\u0027 in \u0027self\u0027 is set.\n    * @return  \u0027true\u0027 - if the value of the bit is \u00271\u0027,\n    *          \u0027false\u0027 - if the value of the bit is \u00270\u0027\n    */\n    function bitSet(uint256 self, uint8 index) internal pure returns (bool) {\n        return self \u003e\u003e index \u0026 1 == 1;\n    }\n\n}\n"},"ERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\nimport \"IERC20.sol\";\nimport \"SafeMath.sol\";\n\n\n/**\n * @title Standard ERC20 token\n *\n * @dev Implementation of the basic standard token.\n * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md\n * Originally based on code by FirstBlood:\n * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol\n *\n * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for\n * all accounts just by listening to said events. Note that this isn\u0027t required by the specification, and other\n * compliant implementations may not do it.\n */\ncontract ERC20 is IERC20 {\n    using SafeMath for uint256;\n\n    mapping (address =\u003e uint256) private _balances;\n\n    mapping (address =\u003e mapping (address =\u003e uint256)) private _allowed;\n\n    uint256 private _totalSupply;\n\n    /**\n     * @dev Total number of tokens in existence\n     */\n    function totalSupply() public view override returns (uint256) {\n        return _totalSupply;\n    }\n\n    /**\n     * @dev Gets the balance of the specified address.\n     * @param owner The address to query the balance of.\n     * @return An uint256 representing the amount owned by the passed address.\n     */\n    function balanceOf(address owner) public view override returns (uint256) {\n        return _balances[owner];\n    }\n\n    /**\n     * @dev Function to check the amount of tokens that an owner allowed to a spender.\n     * @param owner address The address which owns the funds.\n     * @param spender address The address which will spend the funds.\n     * @return A uint256 specifying the amount of tokens still available for the spender.\n     */\n    function allowance(address owner, address spender) public view override returns (uint256) {\n        return _allowed[owner][spender];\n    }\n\n    /**\n     * @dev Transfer token for a specified address\n     * @param to The address to transfer to.\n     * @param value The amount to be transferred.\n     */\n    function transfer(address to, uint256 value) public override returns (bool) {\n        _transfer(msg.sender, to, value);\n        return true;\n    }\n\n    /**\n     * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.\n     * Beware that changing an allowance with this method brings the risk that someone may use both the old\n     * and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this\n     * race condition is to first reduce the spender\u0027s allowance to 0 and set the desired value afterwards:\n     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n     * @param spender The address which will spend the funds.\n     * @param value The amount of tokens to be spent.\n     */\n    function approve(address spender, uint256 value) public override returns (bool) {\n\n        // To change the approve amount you first have to reduce the addresses`\n        //  allowance to zero by calling `approve(_spender, 0)` if it is not\n        //  already 0 to mitigate the race condition described here:\n        //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\n        require(value == 0 || _allowed[msg.sender][spender] == 0);\n\n        _approve(msg.sender, spender, value);\n        return true;\n    }\n\n    /**\n     * @dev Transfer tokens from one address to another.\n     * Note that while this function emits an Approval event, this is not required as per the specification,\n     * and other compliant implementations may not emit the event.\n     * @param from address The address which you want to send tokens from\n     * @param to address The address which you want to transfer to\n     * @param value uint256 the amount of tokens to be transferred\n     */\n    function transferFrom(address from, address to, uint256 value) public override returns (bool) {\n        _transfer(from, to, value);\n        _approve(from, msg.sender, _allowed[from][msg.sender].sub(value));\n        return true;\n    }\n\n    /**\n     * @dev Increase the amount of tokens that an owner allowed to a spender.\n     * approve should be called when allowed_[_spender] == 0. To increment\n     * allowed value is better to use this function to avoid 2 calls (and wait until\n     * the first transaction is mined)\n     * From MonolithDAO Token.sol\n     * Emits an Approval event.\n     * @param spender The address which will spend the funds.\n     * @param addedValue The amount of tokens to increase the allowance by.\n     */\n    function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {\n        _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));\n        return true;\n    }\n\n    /**\n     * @dev Decrease the amount of tokens that an owner allowed to a spender.\n     * approve should be called when allowed_[_spender] == 0. To decrement\n     * allowed value is better to use this function to avoid 2 calls (and wait until\n     * the first transaction is mined)\n     * From MonolithDAO Token.sol\n     * Emits an Approval event.\n     * @param spender The address which will spend the funds.\n     * @param subtractedValue The amount of tokens to decrease the allowance by.\n     */\n    function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {\n        _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));\n        return true;\n    }\n\n    /**\n     * @dev Transfer token for a specified addresses\n     * @param from The address to transfer from.\n     * @param to The address to transfer to.\n     * @param value The amount to be transferred.\n     */\n    function _transfer(address from, address to, uint256 value) internal {\n        require(to != address(0));\n\n        _balances[from] = _balances[from].sub(value);\n        _balances[to] = _balances[to].add(value);\n        emit Transfer(from, to, value);\n    }\n\n    /**\n     * @dev Internal function that mints an amount of the token and assigns it to\n     * an account. This encapsulates the modification of balances such that the\n     * proper events are emitted.\n     * @param account The account that will receive the created tokens.\n     * @param value The amount that will be created.\n     */\n    function _mint(address account, uint256 value) internal {\n        require(account != address(0));\n\n        _totalSupply = _totalSupply.add(value);\n        _balances[account] = _balances[account].add(value);\n        emit Transfer(address(0), account, value);\n    }\n\n    /**\n     * @dev Internal function that burns an amount of the token of a given\n     * account.\n     * @param account The account whose tokens will be burnt.\n     * @param value The amount that will be burnt.\n     */\n    function _burn(address account, uint256 value) internal {\n        require(account != address(0));\n\n        _totalSupply = _totalSupply.sub(value);\n        _balances[account] = _balances[account].sub(value);\n        emit Transfer(account, address(0), value);\n    }\n\n    /**\n     * @dev Approve an address to spend another addresses\u0027 tokens.\n     * @param owner The address that owns the tokens.\n     * @param spender The address that will spend the tokens.\n     * @param value The number of tokens that can be spent.\n     */\n    function _approve(address owner, address spender, uint256 value) internal {\n        require(spender != address(0));\n        require(owner != address(0));\n\n        _allowed[owner][spender] = value;\n        emit Approval(owner, spender, value);\n    }\n\n    /**\n     * @dev Internal function that burns an amount of the token of a given\n     * account, deducting from the sender\u0027s allowance for said account. Uses the\n     * internal burn function.\n     * Emits an Approval event (reflecting the reduced allowance).\n     * @param account The account whose tokens will be burnt.\n     * @param value The amount that will be burnt.\n     */\n    function _burnFrom(address account, uint256 value) internal {\n        _burn(account, value);\n        _approve(account, msg.sender, _allowed[account][msg.sender].sub(value));\n    }\n\n}\n"},"ERC20Detailed.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\nimport \"IERC20.sol\";\n\n\n/**\n * @title ERC20Detailed token\n * @dev The decimals are only for visualization purposes.\n * All the operations are done using the smallest and indivisible token unit,\n * just as on Ethereum all the operations are done in wei.\n */\nabstract contract ERC20Detailed is IERC20 {\n    string private _name;\n    string private _symbol;\n    uint8 private _decimals;\n\n    constructor (string memory name, string memory symbol, uint8 decimals) {\n        _name = name;\n        _symbol = symbol;\n        _decimals = decimals;\n    }\n\n    /**\n     * @return the name of the token.\n     */\n    function name() public view returns (string memory) {\n        return _name;\n    }\n\n    /**\n     * @return the symbol of the token.\n     */\n    function symbol() public view returns (string memory) {\n        return _symbol;\n    }\n\n    /**\n     * @return the number of decimals of the token.\n     */\n    function decimals() public view returns (uint8) {\n        return _decimals;\n    }\n}\n"},"IERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\n/**\n * @title ERC20 interface\n * @dev see https://github.com/ethereum/EIPs/issues/20\n */\ninterface IERC20 {\n    function transfer(address to, uint256 value) external returns (bool);\n\n    function approve(address spender, uint256 value) external returns (bool);\n\n    function transferFrom(address from, address to, uint256 value) external returns (bool);\n\n    function totalSupply() external view returns (uint256);\n\n    function balanceOf(address who) external view returns (uint256);\n\n    function allowance(address owner, address spender) external view returns (uint256);\n\n    event Transfer(address indexed from, address indexed to, uint256 value);\n\n    event Approval(address indexed owner, address indexed spender, uint256 value);\n}\n"},"IERC900History.sol":{"content":"// SPDX-License-Identifier: GPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\n// Minimum interface to interact with Aragon\u0027s Aggregator\ninterface IERC900History {\n    function totalStakedForAt(address addr, uint256 blockNumber) external view returns (uint256);\n    function totalStakedAt(uint256 blockNumber) external view returns (uint256);\n    function supportsHistory() external pure returns (bool);\n}\n"},"Issuer.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"NuCypherToken.sol\";\nimport \"Math.sol\";\nimport \"Upgradeable.sol\";\nimport \"AdditionalMath.sol\";\nimport \"SafeERC20.sol\";\n\n\n/**\n* @notice Contract for calculation of issued tokens\n* @dev |v3.3.1|\n*/\nabstract contract Issuer is Upgradeable {\n    using SafeERC20 for NuCypherToken;\n    using AdditionalMath for uint32;\n\n    event Donated(address indexed sender, uint256 value);\n    /// Issuer is initialized with a reserved reward\n    event Initialized(uint256 reservedReward);\n\n    uint128 constant MAX_UINT128 = uint128(0) - 1;\n\n    NuCypherToken public immutable token;\n    uint128 public immutable totalSupply;\n\n    // d * k2\n    uint256 public immutable mintingCoefficient;\n    // k1\n    uint256 public immutable lockDurationCoefficient1;\n    // k2\n    uint256 public immutable lockDurationCoefficient2;\n    uint32 public immutable secondsPerPeriod;\n    // kmax\n    uint16 public immutable maximumRewardedPeriods;\n\n    uint256 public immutable firstPhaseMaxIssuance;\n    uint256 public immutable firstPhaseTotalSupply;\n\n    /**\n    * Current supply is used in the minting formula and is stored to prevent different calculation\n    * for stakers which get reward in the same period. There are two values -\n    * supply for previous period (used in formula) and supply for current period which accumulates value\n    * before end of period.\n    */\n    uint128 public previousPeriodSupply;\n    uint128 public currentPeriodSupply;\n    uint16 public currentMintingPeriod;\n\n    /**\n    * @notice Constructor sets address of token contract and coefficients for minting\n    * @dev Minting formula for one sub-stake in one period for the first phase\n    firstPhaseMaxIssuance * (lockedValue / totalLockedValue) * (k1 + min(allLockedPeriods, kmax)) / k2\n    * @dev Minting formula for one sub-stake in one period for the second phase\n    (totalSupply - currentSupply) / d * (lockedValue / totalLockedValue) * (k1 + min(allLockedPeriods, kmax)) / k2\n    if allLockedPeriods \u003e maximumRewardedPeriods then allLockedPeriods = maximumRewardedPeriods\n    * @param _token Token contract\n    * @param _hoursPerPeriod Size of period in hours\n    * @param _issuanceDecayCoefficient (d) Coefficient which modifies the rate at which the maximum issuance decays,\n    * only applicable to Phase 2. d = 365 * half-life / LOG2 where default half-life = 2.\n    * See Equation 10 in Staking Protocol \u0026 Economics paper\n    * @param _lockDurationCoefficient1 (k1) Numerator of the coefficient which modifies the extent \n    * to which a stake\u0027s lock duration affects the subsidy it receives. Affects stakers differently. \n    * Applicable to Phase 1 and Phase 2. k1 = k2 * small_stake_multiplier where default small_stake_multiplier = 0.5.  \n    * See Equation 8 in Staking Protocol \u0026 Economics paper.\n    * @param _lockDurationCoefficient2 (k2) Denominator of the coefficient which modifies the extent\n    * to which a stake\u0027s lock duration affects the subsidy it receives. Affects stakers differently.\n    * Applicable to Phase 1 and Phase 2. k2 = maximum_rewarded_periods / (1 - small_stake_multiplier)\n    * where default maximum_rewarded_periods = 365 and default small_stake_multiplier = 0.5.\n    * See Equation 8 in Staking Protocol \u0026 Economics paper.\n    * @param _maximumRewardedPeriods (kmax) Number of periods beyond which a stake\u0027s lock duration\n    * no longer increases the subsidy it receives. kmax = reward_saturation * 365 where default reward_saturation = 1.\n    * See Equation 8 in Staking Protocol \u0026 Economics paper.\n    * @param _firstPhaseTotalSupply Total supply for the first phase\n    * @param _firstPhaseMaxIssuance (Imax) Maximum number of new tokens minted per period during Phase 1.\n    * See Equation 7 in Staking Protocol \u0026 Economics paper.\n    */\n    constructor(\n        NuCypherToken _token,\n        uint32 _hoursPerPeriod,\n        uint256 _issuanceDecayCoefficient,\n        uint256 _lockDurationCoefficient1,\n        uint256 _lockDurationCoefficient2,\n        uint16 _maximumRewardedPeriods,\n        uint256 _firstPhaseTotalSupply,\n        uint256 _firstPhaseMaxIssuance\n    ) {\n        uint256 localTotalSupply = _token.totalSupply();\n        require(localTotalSupply \u003e 0 \u0026\u0026\n            _issuanceDecayCoefficient != 0 \u0026\u0026\n            _hoursPerPeriod != 0 \u0026\u0026\n            _lockDurationCoefficient1 != 0 \u0026\u0026\n            _lockDurationCoefficient2 != 0 \u0026\u0026\n            _maximumRewardedPeriods != 0);\n        require(localTotalSupply \u003c= uint256(MAX_UINT128), \"Token contract has supply more than supported\");\n\n        uint256 maxLockDurationCoefficient = _maximumRewardedPeriods + _lockDurationCoefficient1;\n        uint256 localMintingCoefficient = _issuanceDecayCoefficient * _lockDurationCoefficient2;\n        require(maxLockDurationCoefficient \u003e _maximumRewardedPeriods \u0026\u0026\n            localMintingCoefficient / _issuanceDecayCoefficient ==  _lockDurationCoefficient2 \u0026\u0026\n            // worst case for `totalLockedValue * d * k2`, when totalLockedValue == totalSupply\n            localTotalSupply * localMintingCoefficient / localTotalSupply == localMintingCoefficient \u0026\u0026\n            // worst case for `(totalSupply - currentSupply) * lockedValue * (k1 + min(allLockedPeriods, kmax))`,\n            // when currentSupply == 0, lockedValue == totalSupply\n            localTotalSupply * localTotalSupply * maxLockDurationCoefficient / localTotalSupply / localTotalSupply ==\n                maxLockDurationCoefficient,\n            \"Specified parameters cause overflow\");\n\n        require(maxLockDurationCoefficient \u003c= _lockDurationCoefficient2,\n            \"Resulting locking duration coefficient must be less than 1\");\n        require(_firstPhaseTotalSupply \u003c= localTotalSupply, \"Too many tokens for the first phase\");\n        require(_firstPhaseMaxIssuance \u003c= _firstPhaseTotalSupply, \"Reward for the first phase is too high\");\n\n        token = _token;\n        secondsPerPeriod = _hoursPerPeriod.mul32(1 hours);\n        lockDurationCoefficient1 = _lockDurationCoefficient1;\n        lockDurationCoefficient2 = _lockDurationCoefficient2;\n        maximumRewardedPeriods = _maximumRewardedPeriods;\n        firstPhaseTotalSupply = _firstPhaseTotalSupply;\n        firstPhaseMaxIssuance = _firstPhaseMaxIssuance;\n        totalSupply = uint128(localTotalSupply);\n        mintingCoefficient = localMintingCoefficient;\n    }\n\n    /**\n    * @dev Checks contract initialization\n    */\n    modifier isInitialized()\n    {\n        require(currentMintingPeriod != 0);\n        _;\n    }\n\n    /**\n    * @return Number of current period\n    */\n    function getCurrentPeriod() public view returns (uint16) {\n        return uint16(block.timestamp / secondsPerPeriod);\n    }\n\n    /**\n    * @notice Initialize reserved tokens for reward\n    */\n    function initialize(uint256 _reservedReward, address _sourceOfFunds) external onlyOwner {\n        require(currentMintingPeriod == 0);\n        // Reserved reward must be sufficient for at least one period of the first phase\n        require(firstPhaseMaxIssuance \u003c= _reservedReward);\n        currentMintingPeriod = getCurrentPeriod();\n        currentPeriodSupply = totalSupply - uint128(_reservedReward);\n        previousPeriodSupply = currentPeriodSupply;\n        token.safeTransferFrom(_sourceOfFunds, address(this), _reservedReward);\n        emit Initialized(_reservedReward);\n    }\n\n    /**\n    * @notice Function to mint tokens for one period.\n    * @param _currentPeriod Current period number.\n    * @param _lockedValue The amount of tokens that were locked by user in specified period.\n    * @param _totalLockedValue The amount of tokens that were locked by all users in specified period.\n    * @param _allLockedPeriods The max amount of periods during which tokens will be locked after specified period.\n    * @return amount Amount of minted tokens.\n    */\n    function mint(\n        uint16 _currentPeriod,\n        uint256 _lockedValue,\n        uint256 _totalLockedValue,\n        uint16 _allLockedPeriods\n    )\n        internal returns (uint256 amount)\n    {\n        if (currentPeriodSupply == totalSupply) {\n            return 0;\n        }\n\n        if (_currentPeriod \u003e currentMintingPeriod) {\n            previousPeriodSupply = currentPeriodSupply;\n            currentMintingPeriod = _currentPeriod;\n        }\n\n        uint256 currentReward;\n        uint256 coefficient;\n\n        // first phase\n        // firstPhaseMaxIssuance * lockedValue * (k1 + min(allLockedPeriods, kmax)) / (totalLockedValue * k2)\n        if (previousPeriodSupply + firstPhaseMaxIssuance \u003c= firstPhaseTotalSupply) {\n            currentReward = firstPhaseMaxIssuance;\n            coefficient = lockDurationCoefficient2;\n        // second phase\n        // (totalSupply - currentSupply) * lockedValue * (k1 + min(allLockedPeriods, kmax)) / (totalLockedValue * d * k2)\n        } else {\n            currentReward = totalSupply - previousPeriodSupply;\n            coefficient = mintingCoefficient;\n        }\n\n        uint256 allLockedPeriods =\n            AdditionalMath.min16(_allLockedPeriods, maximumRewardedPeriods) + lockDurationCoefficient1;\n        amount = (uint256(currentReward) * _lockedValue * allLockedPeriods) /\n            (_totalLockedValue * coefficient);\n\n        // rounding the last reward\n        uint256 maxReward = getReservedReward();\n        if (amount == 0) {\n            amount = 1;\n        } else if (amount \u003e maxReward) {\n            amount = maxReward;\n        }\n\n        currentPeriodSupply += uint128(amount);\n    }\n\n    /**\n    * @notice Return tokens for future minting\n    * @param _amount Amount of tokens\n    */\n    function unMint(uint256 _amount) internal {\n        previousPeriodSupply -= uint128(_amount);\n        currentPeriodSupply -= uint128(_amount);\n    }\n\n    /**\n    * @notice Donate sender\u0027s tokens. Amount of tokens will be returned for future minting\n    * @param _value Amount to donate\n    */\n    function donate(uint256 _value) external isInitialized {\n        token.safeTransferFrom(msg.sender, address(this), _value);\n        unMint(_value);\n        emit Donated(msg.sender, _value);\n    }\n\n    /**\n    * @notice Returns the number of tokens that can be minted\n    */\n    function getReservedReward() public view returns (uint256) {\n        return totalSupply - currentPeriodSupply;\n    }\n\n    /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`\n    function verifyState(address _testTarget) public override virtual {\n        super.verifyState(_testTarget);\n        require(uint16(delegateGet(_testTarget, this.currentMintingPeriod.selector)) == currentMintingPeriod);\n        require(uint128(delegateGet(_testTarget, this.previousPeriodSupply.selector)) == previousPeriodSupply);\n        require(uint128(delegateGet(_testTarget, this.currentPeriodSupply.selector)) == currentPeriodSupply);\n    }\n\n}\n"},"Math.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\n/**\n * @title Math\n * @dev Assorted math operations\n */\nlibrary Math {\n    /**\n     * @dev Returns the largest of two numbers.\n     */\n    function max(uint256 a, uint256 b) internal pure returns (uint256) {\n        return a \u003e= b ? a : b;\n    }\n\n    /**\n     * @dev Returns the smallest of two numbers.\n     */\n    function min(uint256 a, uint256 b) internal pure returns (uint256) {\n        return a \u003c b ? a : b;\n    }\n\n    /**\n     * @dev Calculates the average of two numbers. Since these are integers,\n     * averages of an even and odd number cannot be represented, and will be\n     * rounded down.\n     */\n    function average(uint256 a, uint256 b) internal pure returns (uint256) {\n        // (a + b) / 2 can overflow, so we distribute\n        return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);\n    }\n}\n"},"NuCypherToken.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"ERC20.sol\";\nimport \"ERC20Detailed.sol\";\n\n\n/**\n* @title NuCypher token\n* @notice ERC20 token\n* @dev Optional approveAndCall() functionality to notify a contract if an approve() has occurred.\n*/\ncontract NuCypherToken is ERC20, ERC20Detailed(\u0027NuCypher\u0027, \u0027NU\u0027, 18) {\n\n    /**\n    * @notice Set amount of tokens\n    * @param _totalSupplyOfTokens Total number of tokens\n    */\n    constructor (uint256 _totalSupplyOfTokens) {\n        _mint(msg.sender, _totalSupplyOfTokens);\n    }\n\n    /**\n    * @notice Approves and then calls the receiving contract\n    *\n    * @dev call the receiveApproval function on the contract you want to be notified.\n    * receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)\n    */\n    function approveAndCall(address _spender, uint256 _value, bytes calldata _extraData)\n        external returns (bool success)\n    {\n        approve(_spender, _value);\n        TokenRecipient(_spender).receiveApproval(msg.sender, _value, address(this), _extraData);\n        return true;\n    }\n\n}\n\n\n/**\n* @dev Interface to use the receiveApproval method\n*/\ninterface TokenRecipient {\n\n    /**\n    * @notice Receives a notification of approval of the transfer\n    * @param _from Sender of approval\n    * @param _value  The amount of tokens to be spent\n    * @param _tokenContract Address of the token contract\n    * @param _extraData Extra data\n    */\n    function receiveApproval(address _from, uint256 _value, address _tokenContract, bytes calldata _extraData) external;\n\n}\n"},"Ownable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\n/**\n * @title Ownable\n * @dev The Ownable contract has an owner address, and provides basic authorization control\n * functions, this simplifies the implementation of \"user permissions\".\n */\nabstract contract Ownable {\n    address private _owner;\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev The Ownable constructor sets the original `owner` of the contract to the sender\n     * account.\n     */\n    constructor () {\n        _owner = msg.sender;\n        emit OwnershipTransferred(address(0), _owner);\n    }\n\n    /**\n     * @return the address of the owner.\n     */\n    function owner() public view returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        require(isOwner());\n        _;\n    }\n\n    /**\n     * @return true if `msg.sender` is the owner of the contract.\n     */\n    function isOwner() public view returns (bool) {\n        return msg.sender == _owner;\n    }\n\n    /**\n     * @dev Allows the current owner to relinquish control of the contract.\n     * @notice Renouncing to ownership will leave the contract without an owner.\n     * It will not be possible to call the functions with the `onlyOwner`\n     * modifier anymore.\n     */\n    function renounceOwnership() public onlyOwner {\n        emit OwnershipTransferred(_owner, address(0));\n        _owner = address(0);\n    }\n\n    /**\n     * @dev Allows the current owner to transfer control of the contract to a newOwner.\n     * @param newOwner The address to transfer ownership to.\n     */\n    function transferOwnership(address newOwner) public virtual onlyOwner {\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers control of the contract to a newOwner.\n     * @param newOwner The address to transfer ownership to.\n     */\n    function _transferOwnership(address newOwner) internal {\n        require(newOwner != address(0));\n        emit OwnershipTransferred(_owner, newOwner);\n        _owner = newOwner;\n    }\n}\n"},"SafeERC20.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\nimport \"IERC20.sol\";\nimport \"SafeMath.sol\";\n\n\n/**\n * @title SafeERC20\n * @dev Wrappers around ERC20 operations that throw on failure.\n * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,\n * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.\n */\nlibrary SafeERC20 {\n    using SafeMath for uint256;\n\n    function safeTransfer(IERC20 token, address to, uint256 value) internal {\n        require(token.transfer(to, value));\n    }\n\n    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {\n        require(token.transferFrom(from, to, value));\n    }\n\n    function safeApprove(IERC20 token, address spender, uint256 value) internal {\n        // safeApprove should only be called when setting an initial allowance,\n        // or when resetting it to zero. To increase and decrease it, use\n        // \u0027safeIncreaseAllowance\u0027 and \u0027safeDecreaseAllowance\u0027\n        require((value == 0) || (token.allowance(msg.sender, spender) == 0));\n        require(token.approve(spender, value));\n    }\n\n    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n        uint256 newAllowance = token.allowance(address(this), spender).add(value);\n        require(token.approve(spender, newAllowance));\n    }\n\n    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {\n        uint256 newAllowance = token.allowance(address(this), spender).sub(value);\n        require(token.approve(spender, newAllowance));\n    }\n}\n"},"SafeMath.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\n/**\n * @title SafeMath\n * @dev Unsigned math operations with safety checks that revert on error\n */\nlibrary SafeMath {\n    /**\n     * @dev Multiplies two unsigned integers, reverts on overflow.\n     */\n    function mul(uint256 a, uint256 b) internal pure returns (uint256) {\n        // Gas optimization: this is cheaper than requiring \u0027a\u0027 not being zero, but the\n        // benefit is lost if \u0027b\u0027 is also tested.\n        // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522\n        if (a == 0) {\n            return 0;\n        }\n\n        uint256 c = a * b;\n        require(c / a == b);\n\n        return c;\n    }\n\n    /**\n     * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.\n     */\n    function div(uint256 a, uint256 b) internal pure returns (uint256) {\n        // Solidity only automatically asserts when dividing by 0\n        require(b \u003e 0);\n        uint256 c = a / b;\n        // assert(a == b * c + a % b); // There is no case in which this doesn\u0027t hold\n\n        return c;\n    }\n\n    /**\n     * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).\n     */\n    function sub(uint256 a, uint256 b) internal pure returns (uint256) {\n        require(b \u003c= a);\n        uint256 c = a - b;\n\n        return c;\n    }\n\n    /**\n     * @dev Adds two unsigned integers, reverts on overflow.\n     */\n    function add(uint256 a, uint256 b) internal pure returns (uint256) {\n        uint256 c = a + b;\n        require(c \u003e= a);\n\n        return c;\n    }\n\n    /**\n     * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),\n     * reverts when dividing by zero.\n     */\n    function mod(uint256 a, uint256 b) internal pure returns (uint256) {\n        require(b != 0);\n        return a % b;\n    }\n}\n"},"Snapshot.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\n/**\n * @title Snapshot\n * @notice Manages snapshots of size 128 bits (32 bits for timestamp, 96 bits for value)\n * 96 bits is enough for storing NU token values, and 32 bits should be OK for block numbers\n * @dev Since each storage slot can hold two snapshots, new slots are allocated every other TX. Thus, gas cost of adding snapshots is 51400 and 36400 gas, alternately.\n * Based on Aragon\u0027s Checkpointing (https://https://github.com/aragonone/voting-connectors/blob/master/shared/contract-utils/contracts/Checkpointing.sol)\n * On average, adding snapshots spends ~6500 less gas than the 256-bit checkpoints of Aragon\u0027s Checkpointing\n */\nlibrary Snapshot {\n\n    function encodeSnapshot(uint32 _time, uint96 _value) internal pure returns(uint128) {\n        return uint128(uint256(_time) \u003c\u003c 96 | uint256(_value));\n    }\n\n    function decodeSnapshot(uint128 _snapshot) internal pure returns(uint32 time, uint96 value){\n        time = uint32(bytes4(bytes16(_snapshot)));\n        value = uint96(_snapshot);\n    }\n\n    function addSnapshot(uint128[] storage _self, uint256 _value) internal {\n        addSnapshot(_self, block.number, _value);\n    }\n\n    function addSnapshot(uint128[] storage _self, uint256 _time, uint256 _value) internal {\n        uint256 length = _self.length;\n        if (length != 0) {\n            (uint32 currentTime, ) = decodeSnapshot(_self[length - 1]);\n            if (uint32(_time) == currentTime) {\n                _self[length - 1] = encodeSnapshot(uint32(_time), uint96(_value));\n                return;\n            } else if (uint32(_time) \u003c currentTime){\n                revert();\n            }\n        }\n        _self.push(encodeSnapshot(uint32(_time), uint96(_value)));\n    }\n\n    function lastSnapshot(uint128[] storage _self) internal view returns (uint32, uint96) {\n        uint256 length = _self.length;\n        if (length \u003e 0) {\n            return decodeSnapshot(_self[length - 1]);\n        }\n\n        return (0, 0);\n    }\n\n    function lastValue(uint128[] storage _self) internal view returns (uint96) {\n        (, uint96 value) = lastSnapshot(_self);\n        return value;\n    }\n\n    function getValueAt(uint128[] storage _self, uint256 _time256) internal view returns (uint96) {\n        uint32 _time = uint32(_time256);\n        uint256 length = _self.length;\n\n        // Short circuit if there\u0027s no checkpoints yet\n        // Note that this also lets us avoid using SafeMath later on, as we\u0027ve established that\n        // there must be at least one checkpoint\n        if (length == 0) {\n            return 0;\n        }\n\n        // Check last checkpoint\n        uint256 lastIndex = length - 1;\n        (uint32 snapshotTime, uint96 snapshotValue) = decodeSnapshot(_self[length - 1]);\n        if (_time \u003e= snapshotTime) {\n            return snapshotValue;\n        }\n\n        // Check first checkpoint (if not already checked with the above check on last)\n        (snapshotTime, snapshotValue) = decodeSnapshot(_self[0]);\n        if (length == 1 || _time \u003c snapshotTime) {\n            return 0;\n        }\n\n        // Do binary search\n        // As we\u0027ve already checked both ends, we don\u0027t need to check the last checkpoint again\n        uint256 low = 0;\n        uint256 high = lastIndex - 1;\n        uint32 midTime;\n        uint96 midValue;\n\n        while (high \u003e low) {\n            uint256 mid = (high + low + 1) / 2; // average, ceil round\n            (midTime, midValue) = decodeSnapshot(_self[mid]);\n\n            if (_time \u003e midTime) {\n                low = mid;\n            } else if (_time \u003c midTime) {\n                // Note that we don\u0027t need SafeMath here because mid must always be greater than 0\n                // from the while condition\n                high = mid - 1;\n            } else {\n                // _time == midTime\n                return midValue;\n            }\n        }\n\n        (, snapshotValue) = decodeSnapshot(_self[low]);\n        return snapshotValue;\n    }\n}\n"},"StakingEscrow.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"IERC900History.sol\";\nimport \"Issuer.sol\";\nimport \"Bits.sol\";\nimport \"Snapshot.sol\";\nimport \"SafeMath.sol\";\nimport \"SafeERC20.sol\";\n\n\n/**\n* @notice PolicyManager interface\n*/\ninterface PolicyManagerInterface {\n    function register(address _node, uint16 _period) external;\n    function escrow() external view returns (address);\n    function ping(\n        address _node,\n        uint16 _processedPeriod1,\n        uint16 _processedPeriod2,\n        uint16 _periodToSetDefault\n    ) external;\n}\n\n\n/**\n* @notice Adjudicator interface\n*/\ninterface AdjudicatorInterface {\n    function escrow() external view returns (address);\n}\n\n\n/**\n* @notice WorkLock interface\n*/\ninterface WorkLockInterface {\n    function escrow() external view returns (address);\n}\n\n\n/**\n* @notice Contract holds and locks stakers tokens.\n* Each staker that locks their tokens will receive some compensation\n* @dev |v5.5.1|\n*/\ncontract StakingEscrow is Issuer, IERC900History {\n\n    using AdditionalMath for uint256;\n    using AdditionalMath for uint16;\n    using Bits for uint256;\n    using SafeMath for uint256;\n    using Snapshot for uint128[];\n    using SafeERC20 for NuCypherToken;\n\n    event Deposited(address indexed staker, uint256 value, uint16 periods);\n    event Locked(address indexed staker, uint256 value, uint16 firstPeriod, uint16 periods);\n    event Divided(\n        address indexed staker,\n        uint256 oldValue,\n        uint16 lastPeriod,\n        uint256 newValue,\n        uint16 periods\n    );\n    event Merged(address indexed staker, uint256 value1, uint256 value2, uint16 lastPeriod);\n    event Prolonged(address indexed staker, uint256 value, uint16 lastPeriod, uint16 periods);\n    event Withdrawn(address indexed staker, uint256 value);\n    event CommitmentMade(address indexed staker, uint16 indexed period, uint256 value);\n    event Minted(address indexed staker, uint16 indexed period, uint256 value);\n    event Slashed(address indexed staker, uint256 penalty, address indexed investigator, uint256 reward);\n    event ReStakeSet(address indexed staker, bool reStake);\n    event ReStakeLocked(address indexed staker, uint16 lockUntilPeriod);\n    event WorkerBonded(address indexed staker, address indexed worker, uint16 indexed startPeriod);\n    event WorkMeasurementSet(address indexed staker, bool measureWork);\n    event WindDownSet(address indexed staker, bool windDown);\n    event SnapshotSet(address indexed staker, bool snapshotsEnabled);\n\n    struct SubStakeInfo {\n        uint16 firstPeriod;\n        uint16 lastPeriod;\n        uint16 periods;\n        uint128 lockedValue;\n    }\n\n    struct Downtime {\n        uint16 startPeriod;\n        uint16 endPeriod;\n    }\n\n    struct StakerInfo {\n        uint256 value;\n        /*\n        * Stores periods that are committed but not yet rewarded.\n        * In order to optimize storage, only two values are used instead of an array.\n        * commitToNextPeriod() method invokes mint() method so there can only be two committed\n        * periods that are not yet rewarded: the current and the next periods.\n        */\n        uint16 currentCommittedPeriod;\n        uint16 nextCommittedPeriod;\n        uint16 lastCommittedPeriod;\n        uint16 lockReStakeUntilPeriod;\n        uint256 completedWork;\n        uint16 workerStartPeriod; // period when worker was bonded\n        address worker;\n        uint256 flags; // uint256 to acquire whole slot and minimize operations on it\n\n        uint256 reservedSlot1;\n        uint256 reservedSlot2;\n        uint256 reservedSlot3;\n        uint256 reservedSlot4;\n        uint256 reservedSlot5;\n\n        Downtime[] pastDowntime;\n        SubStakeInfo[] subStakes;\n        uint128[] history;\n\n    }\n\n    // used only for upgrading\n    uint16 internal constant RESERVED_PERIOD = 0;\n    uint16 internal constant MAX_CHECKED_VALUES = 5;\n    // to prevent high gas consumption in loops for slashing\n    uint16 public constant MAX_SUB_STAKES = 30;\n    uint16 internal constant MAX_UINT16 = 65535;\n\n    // indices for flags\n    uint8 internal constant RE_STAKE_DISABLED_INDEX = 0;\n    uint8 internal constant WIND_DOWN_INDEX = 1;\n    uint8 internal constant MEASURE_WORK_INDEX = 2;\n    uint8 internal constant SNAPSHOTS_DISABLED_INDEX = 3;\n\n    uint16 public immutable minLockedPeriods;\n    uint16 public immutable minWorkerPeriods;\n    uint256 public immutable minAllowableLockedTokens;\n    uint256 public immutable maxAllowableLockedTokens;\n    bool public immutable isTestContract;\n\n    mapping (address =\u003e StakerInfo) public stakerInfo;\n    address[] public stakers;\n    mapping (address =\u003e address) public stakerFromWorker;\n\n    mapping (uint16 =\u003e uint256) public lockedPerPeriod;\n    uint128[] public balanceHistory;\n\n    PolicyManagerInterface public policyManager;\n    AdjudicatorInterface public adjudicator;\n    WorkLockInterface public workLock;\n\n    /**\n    * @notice Constructor sets address of token contract and coefficients for minting\n    * @param _token Token contract\n    * @param _hoursPerPeriod Size of period in hours\n    * @param _issuanceDecayCoefficient (d) Coefficient which modifies the rate at which the maximum issuance decays,\n    * only applicable to Phase 2. d = 365 * half-life / LOG2 where default half-life = 2.\n    * See Equation 10 in Staking Protocol \u0026 Economics paper\n    * @param _lockDurationCoefficient1 (k1) Numerator of the coefficient which modifies the extent\n    * to which a stake\u0027s lock duration affects the subsidy it receives. Affects stakers differently.\n    * Applicable to Phase 1 and Phase 2. k1 = k2 * small_stake_multiplier where default small_stake_multiplier = 0.5.\n    * See Equation 8 in Staking Protocol \u0026 Economics paper.\n    * @param _lockDurationCoefficient2 (k2) Denominator of the coefficient which modifies the extent\n    * to which a stake\u0027s lock duration affects the subsidy it receives. Affects stakers differently.\n    * Applicable to Phase 1 and Phase 2. k2 = maximum_rewarded_periods / (1 - small_stake_multiplier)\n    * where default maximum_rewarded_periods = 365 and default small_stake_multiplier = 0.5.\n    * See Equation 8 in Staking Protocol \u0026 Economics paper.\n    * @param _maximumRewardedPeriods (kmax) Number of periods beyond which a stake\u0027s lock duration\n    * no longer increases the subsidy it receives. kmax = reward_saturation * 365 where default reward_saturation = 1.\n    * See Equation 8 in Staking Protocol \u0026 Economics paper.\n    * @param _firstPhaseTotalSupply Total supply for the first phase\n    * @param _firstPhaseMaxIssuance (Imax) Maximum number of new tokens minted per period during Phase 1.\n    * See Equation 7 in Staking Protocol \u0026 Economics paper.\n    * @param _minLockedPeriods Min amount of periods during which tokens can be locked\n    * @param _minAllowableLockedTokens Min amount of tokens that can be locked\n    * @param _maxAllowableLockedTokens Max amount of tokens that can be locked\n    * @param _minWorkerPeriods Min amount of periods while a worker can\u0027t be changed\n    * @param _isTestContract True if contract is only for tests\n    */\n    constructor(\n        NuCypherToken _token,\n        uint32 _hoursPerPeriod,\n        uint256 _issuanceDecayCoefficient,\n        uint256 _lockDurationCoefficient1,\n        uint256 _lockDurationCoefficient2,\n        uint16 _maximumRewardedPeriods,\n        uint256 _firstPhaseTotalSupply,\n        uint256 _firstPhaseMaxIssuance,\n        uint16 _minLockedPeriods,\n        uint256 _minAllowableLockedTokens,\n        uint256 _maxAllowableLockedTokens,\n        uint16 _minWorkerPeriods,\n        bool _isTestContract\n    )\n        Issuer(\n            _token,\n            _hoursPerPeriod,\n            _issuanceDecayCoefficient,\n            _lockDurationCoefficient1,\n            _lockDurationCoefficient2,\n            _maximumRewardedPeriods,\n            _firstPhaseTotalSupply,\n            _firstPhaseMaxIssuance\n        )\n    {\n        // constant `1` in the expression `_minLockedPeriods \u003e 1` uses to simplify the `lock` method\n        require(_minLockedPeriods \u003e 1 \u0026\u0026 _maxAllowableLockedTokens != 0);\n        minLockedPeriods = _minLockedPeriods;\n        minAllowableLockedTokens = _minAllowableLockedTokens;\n        maxAllowableLockedTokens = _maxAllowableLockedTokens;\n        minWorkerPeriods = _minWorkerPeriods;\n        isTestContract = _isTestContract;\n    }\n\n    /**\n    * @dev Checks the existence of a staker in the contract\n    */\n    modifier onlyStaker()\n    {\n        StakerInfo storage info = stakerInfo[msg.sender];\n        require(info.value \u003e 0 || info.nextCommittedPeriod != 0);\n        _;\n    }\n\n    //------------------------Initialization------------------------\n    /**\n    * @notice Set policy manager address\n    */\n    function setPolicyManager(PolicyManagerInterface _policyManager) external onlyOwner {\n        // Policy manager can be set only once\n        require(address(policyManager) == address(0));\n        // This escrow must be the escrow for the new policy manager\n        require(_policyManager.escrow() == address(this));\n        policyManager = _policyManager;\n    }\n\n    /**\n    * @notice Set adjudicator address\n    */\n    function setAdjudicator(AdjudicatorInterface _adjudicator) external onlyOwner {\n        // Adjudicator can be set only once\n        require(address(adjudicator) == address(0));\n        // This escrow must be the escrow for the new adjudicator\n        require(_adjudicator.escrow() == address(this));\n        adjudicator = _adjudicator;\n    }\n\n    /**\n    * @notice Set worklock address\n    */\n    function setWorkLock(WorkLockInterface _workLock) external onlyOwner {\n        // WorkLock can be set only once\n        require(address(workLock) == address(0) || isTestContract);\n        // This escrow must be the escrow for the new worklock\n        require(_workLock.escrow() == address(this));\n        workLock = _workLock;\n    }\n\n    //------------------------Main getters------------------------\n    /**\n    * @notice Get all tokens belonging to the staker\n    */\n    function getAllTokens(address _staker) external view returns (uint256) {\n        return stakerInfo[_staker].value;\n    }\n\n    /**\n    * @notice Get all flags for the staker\n    */\n    function getFlags(address _staker)\n        external view returns (\n            bool windDown,\n            bool reStake,\n            bool measureWork,\n            bool snapshots\n        )\n    {\n        StakerInfo storage info = stakerInfo[_staker];\n        windDown = info.flags.bitSet(WIND_DOWN_INDEX);\n        reStake = !info.flags.bitSet(RE_STAKE_DISABLED_INDEX);\n        measureWork = info.flags.bitSet(MEASURE_WORK_INDEX);\n        snapshots = !info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX);\n    }\n\n    /**\n    * @notice Get the start period. Use in the calculation of the last period of the sub stake\n    * @param _info Staker structure\n    * @param _currentPeriod Current period\n    */\n    function getStartPeriod(StakerInfo storage _info, uint16 _currentPeriod)\n        internal view returns (uint16)\n    {\n        // if the next period (after current) is committed\n        if (_info.flags.bitSet(WIND_DOWN_INDEX) \u0026\u0026 _info.nextCommittedPeriod \u003e _currentPeriod) {\n            return _currentPeriod + 1;\n        }\n        return _currentPeriod;\n    }\n\n    /**\n    * @notice Get the last period of the sub stake\n    * @param _subStake Sub stake structure\n    * @param _startPeriod Pre-calculated start period\n    */\n    function getLastPeriodOfSubStake(SubStakeInfo storage _subStake, uint16 _startPeriod)\n        internal view returns (uint16)\n    {\n        if (_subStake.lastPeriod != 0) {\n            return _subStake.lastPeriod;\n        }\n        uint32 lastPeriod = uint32(_startPeriod) + _subStake.periods;\n        if (lastPeriod \u003e uint32(MAX_UINT16)) {\n            return MAX_UINT16;\n        }\n        return uint16(lastPeriod);\n    }\n\n    /**\n    * @notice Get the last period of the sub stake\n    * @param _staker Staker\n    * @param _index Stake index\n    */\n    function getLastPeriodOfSubStake(address _staker, uint256 _index)\n        public view returns (uint16)\n    {\n        StakerInfo storage info = stakerInfo[_staker];\n        SubStakeInfo storage subStake = info.subStakes[_index];\n        uint16 startPeriod = getStartPeriod(info, getCurrentPeriod());\n        return getLastPeriodOfSubStake(subStake, startPeriod);\n    }\n\n\n    /**\n    * @notice Get the value of locked tokens for a staker in a specified period\n    * @dev Information may be incorrect for rewarded or not committed surpassed period\n    * @param _info Staker structure\n    * @param _currentPeriod Current period\n    * @param _period Next period\n    */\n    function getLockedTokens(StakerInfo storage _info, uint16 _currentPeriod, uint16 _period)\n        internal view returns (uint256 lockedValue)\n    {\n        lockedValue = 0;\n        uint16 startPeriod = getStartPeriod(_info, _currentPeriod);\n        for (uint256 i = 0; i \u003c _info.subStakes.length; i++) {\n            SubStakeInfo storage subStake = _info.subStakes[i];\n            if (subStake.firstPeriod \u003c= _period \u0026\u0026\n                getLastPeriodOfSubStake(subStake, startPeriod) \u003e= _period) {\n                lockedValue += subStake.lockedValue;\n            }\n        }\n    }\n\n    /**\n    * @notice Get the value of locked tokens for a staker in a future period\n    * @dev This function is used by PreallocationEscrow so its signature can\u0027t be updated.\n    * @param _staker Staker\n    * @param _periods Amount of periods that will be added to the current period\n    */\n    function getLockedTokens(address _staker, uint16 _periods)\n        external view returns (uint256 lockedValue)\n    {\n        StakerInfo storage info = stakerInfo[_staker];\n        uint16 currentPeriod = getCurrentPeriod();\n        uint16 nextPeriod = currentPeriod.add16(_periods);\n        return getLockedTokens(info, currentPeriod, nextPeriod);\n    }\n\n    /**\n    * @notice Get the last committed staker\u0027s period\n    * @param _staker Staker\n    */\n    function getLastCommittedPeriod(address _staker) public view returns (uint16) {\n        StakerInfo storage info = stakerInfo[_staker];\n        return info.nextCommittedPeriod != 0 ? info.nextCommittedPeriod : info.lastCommittedPeriod;\n    }\n\n    /**\n    * @notice Get the value of locked tokens for active stakers in (getCurrentPeriod() + _periods) period\n    * as well as stakers and their locked tokens\n    * @param _periods Amount of periods for locked tokens calculation\n    * @param _startIndex Start index for looking in stakers array\n    * @param _maxStakers Max stakers for looking, if set 0 then all will be used\n    * @return allLockedTokens Sum of locked tokens for active stakers\n    * @return activeStakers Array of stakers and their locked tokens. Stakers addresses stored as uint256\n    * @dev Note that activeStakers[0] in an array of uint256, but you want addresses. Careful when used directly!\n    */\n    function getActiveStakers(uint16 _periods, uint256 _startIndex, uint256 _maxStakers)\n        external view returns (uint256 allLockedTokens, uint256[2][] memory activeStakers)\n    {\n        require(_periods \u003e 0);\n\n        uint256 endIndex = stakers.length;\n        require(_startIndex \u003c endIndex);\n        if (_maxStakers != 0 \u0026\u0026 _startIndex + _maxStakers \u003c endIndex) {\n            endIndex = _startIndex + _maxStakers;\n        }\n        activeStakers = new uint256[2][](endIndex - _startIndex);\n        allLockedTokens = 0;\n\n        uint256 resultIndex = 0;\n        uint16 currentPeriod = getCurrentPeriod();\n        uint16 nextPeriod = currentPeriod.add16(_periods);\n\n        for (uint256 i = _startIndex; i \u003c endIndex; i++) {\n            address staker = stakers[i];\n            StakerInfo storage info = stakerInfo[staker];\n            if (info.currentCommittedPeriod != currentPeriod \u0026\u0026\n                info.nextCommittedPeriod != currentPeriod) {\n                continue;\n            }\n            uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);\n            if (lockedTokens != 0) {\n                activeStakers[resultIndex][0] = uint256(staker);\n                activeStakers[resultIndex++][1] = lockedTokens;\n                allLockedTokens += lockedTokens;\n            }\n        }\n        assembly {\n            mstore(activeStakers, resultIndex)\n        }\n    }\n\n    /**\n    * @notice Checks if `reStake` parameter is available for changing\n    * @param _staker Staker\n    */\n    function isReStakeLocked(address _staker) public view returns (bool) {\n        return getCurrentPeriod() \u003c stakerInfo[_staker].lockReStakeUntilPeriod;\n    }\n\n    /**\n    * @notice Get worker using staker\u0027s address\n    */\n    function getWorkerFromStaker(address _staker) external view returns (address) {\n        return stakerInfo[_staker].worker;\n    }\n\n    /**\n    * @notice Get work that completed by the staker\n    */\n    function getCompletedWork(address _staker) external view returns (uint256) {\n        return stakerInfo[_staker].completedWork;\n    }\n\n    /**\n    * @notice Find index of downtime structure that includes specified period\n    * @dev If specified period is outside all downtime periods, the length of the array will be returned\n    * @param _staker Staker\n    * @param _period Specified period number\n    */\n    function findIndexOfPastDowntime(address _staker, uint16 _period) external view returns (uint256 index) {\n        StakerInfo storage info = stakerInfo[_staker];\n        for (index = 0; index \u003c info.pastDowntime.length; index++) {\n            if (_period \u003c= info.pastDowntime[index].endPeriod) {\n                return index;\n            }\n        }\n    }\n\n    //------------------------Main methods------------------------\n    /**\n    * @notice Start or stop measuring the work of a staker\n    * @param _staker Staker\n    * @param _measureWork Value for `measureWork` parameter\n    * @return Work that was previously done\n    */\n    function setWorkMeasurement(address _staker, bool _measureWork) external returns (uint256) {\n        require(msg.sender == address(workLock));\n        StakerInfo storage info = stakerInfo[_staker];\n        if (info.flags.bitSet(MEASURE_WORK_INDEX) == _measureWork) {\n            return info.completedWork;\n        }\n        info.flags = info.flags.toggleBit(MEASURE_WORK_INDEX);\n        emit WorkMeasurementSet(_staker, _measureWork);\n        return info.completedWork;\n    }\n\n    /**\n    * @notice Bond worker\n    * @param _worker Worker address. Must be a real address, not a contract\n    */\n    function bondWorker(address _worker) external onlyStaker {\n        StakerInfo storage info = stakerInfo[msg.sender];\n        // Specified worker is already bonded with this staker\n        require(_worker != info.worker);\n        uint16 currentPeriod = getCurrentPeriod();\n        if (info.worker != address(0)) { // If this staker had a worker ...\n            // Check that enough time has passed to change it\n            require(currentPeriod \u003e= info.workerStartPeriod.add16(minWorkerPeriods));\n            // Remove the old relation \"worker-\u003estaker\"\n            stakerFromWorker[info.worker] = address(0);\n        }\n\n        if (_worker != address(0)) {\n            // Specified worker is already in use\n            require(stakerFromWorker[_worker] == address(0));\n            // Specified worker is a staker\n            require(stakerInfo[_worker].subStakes.length == 0 || _worker == msg.sender);\n            // Set new worker-\u003estaker relation\n            stakerFromWorker[_worker] = msg.sender;\n        }\n\n        // Bond new worker (or unbond if _worker == address(0))\n        info.worker = _worker;\n        info.workerStartPeriod = currentPeriod;\n        emit WorkerBonded(msg.sender, _worker, currentPeriod);\n    }\n\n    /**\n    * @notice Set `reStake` parameter. If true then all staking rewards will be added to locked stake\n    * Only if this parameter is not locked\n    * @param _reStake Value for parameter\n    */\n    function setReStake(bool _reStake) external {\n        require(!isReStakeLocked(msg.sender));\n        StakerInfo storage info = stakerInfo[msg.sender];\n        if (info.flags.bitSet(RE_STAKE_DISABLED_INDEX) == !_reStake) {\n            return;\n        }\n        info.flags = info.flags.toggleBit(RE_STAKE_DISABLED_INDEX);\n        emit ReStakeSet(msg.sender, _reStake);\n    }\n\n    /**\n    * @notice Lock `reStake` parameter. Only if this parameter is not locked\n    * @param _lockReStakeUntilPeriod Can\u0027t change `reStake` value until this period\n    */\n    function lockReStake(uint16 _lockReStakeUntilPeriod) external {\n        require(!isReStakeLocked(msg.sender) \u0026\u0026\n            _lockReStakeUntilPeriod \u003e getCurrentPeriod());\n        stakerInfo[msg.sender].lockReStakeUntilPeriod = _lockReStakeUntilPeriod;\n        emit ReStakeLocked(msg.sender, _lockReStakeUntilPeriod);\n    }\n\n    /**\n    * @notice Deposit tokens from WorkLock contract\n    * @param _staker Staker address\n    * @param _value Amount of tokens to deposit\n    * @param _periods Amount of periods during which tokens will be locked\n    */\n    function depositFromWorkLock(\n        address _staker,\n        uint256 _value,\n        uint16 _periods\n    )\n        external\n    {\n        require(msg.sender == address(workLock));\n        StakerInfo storage info = stakerInfo[_staker];\n        if (!info.flags.bitSet(WIND_DOWN_INDEX) \u0026\u0026 info.subStakes.length == 0) {\n            info.flags = info.flags.toggleBit(WIND_DOWN_INDEX);\n            emit WindDownSet(_staker, true);\n        }\n        deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods);\n    }\n\n    /**\n    * @notice Set `windDown` parameter.\n    * If true then stake\u0027s duration will be decreasing in each period with `commitToNextPeriod()`\n    * @param _windDown Value for parameter\n    */\n    function setWindDown(bool _windDown) external {\n        StakerInfo storage info = stakerInfo[msg.sender];\n        if (info.flags.bitSet(WIND_DOWN_INDEX) == _windDown) {\n            return;\n        }\n        info.flags = info.flags.toggleBit(WIND_DOWN_INDEX);\n        emit WindDownSet(msg.sender, _windDown);\n\n        // duration adjustment if next period is committed\n        uint16 nextPeriod = getCurrentPeriod() + 1;\n        if (info.nextCommittedPeriod != nextPeriod) {\n           return;\n        }\n\n        // adjust sub-stakes duration for the new value of winding down parameter\n        for (uint256 index = 0; index \u003c info.subStakes.length; index++) {\n            SubStakeInfo storage subStake = info.subStakes[index];\n            // sub-stake does not have fixed last period when winding down is disabled\n            if (!_windDown \u0026\u0026 subStake.lastPeriod == nextPeriod) {\n                subStake.lastPeriod = 0;\n                subStake.periods = 1;\n                continue;\n            }\n            // this sub-stake is no longer affected by winding down parameter\n            if (subStake.lastPeriod != 0 || subStake.periods == 0) {\n                continue;\n            }\n\n            subStake.periods = _windDown ? subStake.periods - 1 : subStake.periods + 1;\n            if (subStake.periods == 0) {\n                subStake.lastPeriod = nextPeriod;\n            }\n        }\n    }\n\n    /**\n    * @notice Activate/deactivate taking snapshots of balances\n    * @param _enableSnapshots True to activate snapshots, False to deactivate\n    */\n    function setSnapshots(bool _enableSnapshots) external {\n        StakerInfo storage info = stakerInfo[msg.sender];\n        if (info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX) == !_enableSnapshots) {\n            return;\n        }\n\n        uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());\n        if(_enableSnapshots){\n            info.history.addSnapshot(info.value);\n            balanceHistory.addSnapshot(lastGlobalBalance + info.value);\n        } else {\n            info.history.addSnapshot(0);\n            balanceHistory.addSnapshot(lastGlobalBalance - info.value);\n        }\n        info.flags = info.flags.toggleBit(SNAPSHOTS_DISABLED_INDEX);\n\n        emit SnapshotSet(msg.sender, _enableSnapshots);\n    }\n\n    /**\n    * @notice Adds a new snapshot to both the staker and global balance histories,\n    * assuming the staker\u0027s balance was already changed\n    * @param _info Reference to affected staker\u0027s struct\n    * @param _addition Variance in balance. It can be positive or negative.\n    */\n    function addSnapshot(StakerInfo storage _info, int256 _addition) internal {\n        if(!_info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX)){\n            _info.history.addSnapshot(_info.value);\n            uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());\n            balanceHistory.addSnapshot(lastGlobalBalance.addSigned(_addition));\n        }\n    }\n\n\n    /**\n    * @notice Batch deposit. Allowed only initial deposit for each staker\n    * @param _stakers Stakers\n    * @param _numberOfSubStakes Number of sub-stakes which belong to staker in _values and _periods arrays\n    * @param _values Amount of tokens to deposit for each staker\n    * @param _periods Amount of periods during which tokens will be locked for each staker\n    */\n    function batchDeposit(\n        address[] calldata _stakers,\n        uint256[] calldata _numberOfSubStakes,\n        uint256[] calldata _values,\n        uint16[] calldata _periods\n    )\n        external\n    {\n        uint256 subStakesLength = _values.length;\n        require(_stakers.length != 0 \u0026\u0026\n            _stakers.length == _numberOfSubStakes.length \u0026\u0026\n            subStakesLength \u003e= _stakers.length \u0026\u0026\n            _periods.length == subStakesLength);\n        uint16 previousPeriod = getCurrentPeriod() - 1;\n        uint16 nextPeriod = previousPeriod + 2;\n        uint256 sumValue = 0;\n\n        uint256 j = 0;\n        for (uint256 i = 0; i \u003c _stakers.length; i++) {\n            address staker = _stakers[i];\n            uint256 numberOfSubStakes = _numberOfSubStakes[i];\n            uint256 endIndex = j + numberOfSubStakes;\n            require(numberOfSubStakes \u003e 0 \u0026\u0026 subStakesLength \u003e= endIndex);\n            StakerInfo storage info = stakerInfo[staker];\n            require(info.subStakes.length == 0 \u0026\u0026 !info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX));\n            // A staker can\u0027t be a worker for another staker\n            require(stakerFromWorker[staker] == address(0));\n            stakers.push(staker);\n            policyManager.register(staker, previousPeriod);\n\n            for (; j \u003c endIndex; j++) {\n                uint256 value =  _values[j];\n                uint16 periods = _periods[j];\n                require(value \u003e= minAllowableLockedTokens \u0026\u0026 periods \u003e= minLockedPeriods);\n                info.value = info.value.add(value);\n                info.subStakes.push(SubStakeInfo(nextPeriod, 0, periods, uint128(value)));\n                sumValue = sumValue.add(value);\n                emit Deposited(staker, value, periods);\n                emit Locked(staker, value, nextPeriod, periods);\n            }\n            require(info.value \u003c= maxAllowableLockedTokens);\n            info.history.addSnapshot(info.value);\n        }\n        require(j == subStakesLength);\n        uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());\n        balanceHistory.addSnapshot(lastGlobalBalance + sumValue);\n        token.safeTransferFrom(msg.sender, address(this), sumValue);\n    }\n\n    /**\n    * @notice Implementation of the receiveApproval(address,uint256,address,bytes) method\n    * (see NuCypherToken contract). Deposit all tokens that were approved to transfer\n    * @param _from Staker\n    * @param _value Amount of tokens to deposit\n    * @param _tokenContract Token contract address\n    * @notice (param _extraData) Amount of periods during which tokens will be locked\n    */\n    function receiveApproval(\n        address _from,\n        uint256 _value,\n        address _tokenContract,\n        bytes calldata /* _extraData */\n    )\n        external\n    {\n        require(_tokenContract == address(token) \u0026\u0026 msg.sender == address(token));\n\n        // Copy first 32 bytes from _extraData, according to calldata memory layout:\n        //\n        // 0x00: method signature      4 bytes\n        // 0x04: _from                 32 bytes after encoding\n        // 0x24: _value                32 bytes after encoding\n        // 0x44: _tokenContract        32 bytes after encoding\n        // 0x64: _extraData pointer    32 bytes. Value must be 0x80 (offset of _extraData wrt to 1st parameter)\n        // 0x84: _extraData length     32 bytes\n        // 0xA4: _extraData data       Length determined by previous variable\n        //\n        // See https://solidity.readthedocs.io/en/latest/abi-spec.html#examples\n\n        uint256 payloadSize;\n        uint256 payload;\n        assembly {\n            payloadSize := calldataload(0x84)\n            payload := calldataload(0xA4)\n        }\n        payload = payload \u003e\u003e 8*(32 - payloadSize);\n        deposit(_from, _from, MAX_SUB_STAKES, _value, uint16(payload));\n    }\n\n    /**\n    * @notice Deposit tokens and create new sub-stake. Use this method to become a staker\n    * @param _staker Staker\n    * @param _value Amount of tokens to deposit\n    * @param _periods Amount of periods during which tokens will be locked\n    */\n    function deposit(address _staker, uint256 _value, uint16 _periods) external {\n        deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods);\n    }\n\n    /**\n    * @notice Deposit tokens and increase lock amount of an existing sub-stake\n    * @dev This is preferable way to stake tokens because will be fewer active sub-stakes in the result\n    * @param _index Index of the sub stake\n    * @param _value Amount of tokens which will be locked\n    */\n    function depositAndIncrease(uint256 _index, uint256 _value) external onlyStaker {\n        require(_index \u003c MAX_SUB_STAKES);\n        deposit(msg.sender, msg.sender, _index, _value, 0);\n    }\n\n    /**\n    * @notice Deposit tokens\n    * @dev Specify either index and zero periods (for an existing sub-stake)\n    * or index \u003e= MAX_SUB_STAKES and real value for periods (for a new sub-stake), not both\n    * @param _staker Staker\n    * @param _payer Owner of tokens\n    * @param _index Index of the sub stake\n    * @param _value Amount of tokens to deposit\n    * @param _periods Amount of periods during which tokens will be locked\n    */\n    function deposit(address _staker, address _payer, uint256 _index, uint256 _value, uint16 _periods) internal {\n        require(_value != 0);\n        StakerInfo storage info = stakerInfo[_staker];\n        // A staker can\u0027t be a worker for another staker\n        require(stakerFromWorker[_staker] == address(0) || stakerFromWorker[_staker] == info.worker);\n        // initial stake of the staker\n        if (info.subStakes.length == 0) {\n            stakers.push(_staker);\n            policyManager.register(_staker, getCurrentPeriod() - 1);\n        }\n        token.safeTransferFrom(_payer, address(this), _value);\n        info.value += _value;\n        lock(_staker, _index, _value, _periods);\n\n        addSnapshot(info, int256(_value));\n        if (_index \u003e= MAX_SUB_STAKES) {\n            emit Deposited(_staker, _value, _periods);\n        } else {\n            uint16 lastPeriod = getLastPeriodOfSubStake(_staker, _index);\n            emit Deposited(_staker, _value, lastPeriod - getCurrentPeriod());\n        }\n    }\n\n    /**\n    * @notice Lock some tokens as a new sub-stake\n    * @param _value Amount of tokens which will be locked\n    * @param _periods Amount of periods during which tokens will be locked\n    */\n    function lockAndCreate(uint256 _value, uint16 _periods) external onlyStaker {\n        lock(msg.sender, MAX_SUB_STAKES, _value, _periods);\n    }\n\n    /**\n    * @notice Increase lock amount of an existing sub-stake\n    * @param _index Index of the sub-stake\n    * @param _value Amount of tokens which will be locked\n    */\n    function lockAndIncrease(uint256 _index, uint256 _value) external onlyStaker {\n        require(_index \u003c MAX_SUB_STAKES);\n        lock(msg.sender, _index, _value, 0);\n    }\n\n    /**\n    * @notice Lock some tokens as a stake\n    * @dev Specify either index and zero periods (for an existing sub-stake)\n    * or index \u003e= MAX_SUB_STAKES and real value for periods (for a new sub-stake), not both\n    * @param _staker Staker\n    * @param _index Index of the sub stake\n    * @param _value Amount of tokens which will be locked\n    * @param _periods Amount of periods during which tokens will be locked\n    */\n    function lock(address _staker, uint256 _index, uint256 _value, uint16 _periods) internal {\n        if (_index \u003c MAX_SUB_STAKES) {\n            require(_value \u003e 0);\n        } else {\n            require(_value \u003e= minAllowableLockedTokens \u0026\u0026 _periods \u003e= minLockedPeriods);\n        }\n\n        uint16 currentPeriod = getCurrentPeriod();\n        uint16 nextPeriod = currentPeriod + 1;\n        StakerInfo storage info = stakerInfo[_staker];\n        uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);\n        uint256 requestedLockedTokens = _value.add(lockedTokens);\n        require(requestedLockedTokens \u003c= info.value \u0026\u0026 requestedLockedTokens \u003c= maxAllowableLockedTokens);\n\n        // next period is committed\n        if (info.nextCommittedPeriod == nextPeriod) {\n            lockedPerPeriod[nextPeriod] += _value;\n            emit CommitmentMade(_staker, nextPeriod, _value);\n        }\n\n        // if index was provided then increase existing sub-stake\n        if (_index \u003c MAX_SUB_STAKES) {\n            lockAndIncrease(info, currentPeriod, nextPeriod, _staker, _index, _value);\n        // otherwise create new\n        } else {\n            lockAndCreate(info, nextPeriod, _staker, _value, _periods);\n        }\n    }\n\n    /**\n    * @notice Lock some tokens as a new sub-stake\n    * @param _info Staker structure\n    * @param _nextPeriod Next period\n    * @param _staker Staker\n    * @param _value Amount of tokens which will be locked\n    * @param _periods Amount of periods during which tokens will be locked\n    */\n    function lockAndCreate(\n        StakerInfo storage _info,\n        uint16 _nextPeriod,\n        address _staker,\n        uint256 _value,\n        uint16 _periods\n    )\n        internal\n    {\n        uint16 duration = _periods;\n        // if winding down is enabled and next period is committed\n        // then sub-stakes duration were decreased\n        if (_info.nextCommittedPeriod == _nextPeriod \u0026\u0026 _info.flags.bitSet(WIND_DOWN_INDEX)) {\n            duration -= 1;\n        }\n        saveSubStake(_info, _nextPeriod, 0, duration, _value);\n\n        emit Locked(_staker, _value, _nextPeriod, _periods);\n    }\n\n    /**\n    * @notice Increase lock amount of an existing sub-stake\n    * @dev Probably will be created a new sub-stake but it will be active only one period\n    * @param _info Staker structure\n    * @param _currentPeriod Current period\n    * @param _nextPeriod Next period\n    * @param _staker Staker\n    * @param _index Index of the sub-stake\n    * @param _value Amount of tokens which will be locked\n    */\n    function lockAndIncrease(\n        StakerInfo storage _info,\n        uint16 _currentPeriod,\n        uint16 _nextPeriod,\n        address _staker,\n        uint256 _index,\n        uint256 _value\n    )\n        internal\n    {\n        SubStakeInfo storage subStake = _info.subStakes[_index];\n        (, uint16 lastPeriod) = checkLastPeriodOfSubStake(_info, subStake, _currentPeriod);\n\n        // create temporary sub-stake for current or previous committed periods\n        // to leave locked amount in this period unchanged\n        if (_info.currentCommittedPeriod != 0 \u0026\u0026\n            _info.currentCommittedPeriod \u003c= _currentPeriod ||\n            _info.nextCommittedPeriod != 0 \u0026\u0026\n            _info.nextCommittedPeriod \u003c= _currentPeriod)\n        {\n            saveSubStake(_info, subStake.firstPeriod, _currentPeriod, 0, subStake.lockedValue);\n        }\n\n        subStake.lockedValue += uint128(_value);\n        // all new locks should start from the next period\n        subStake.firstPeriod = _nextPeriod;\n\n        emit Locked(_staker, _value, _nextPeriod, lastPeriod - _currentPeriod);\n    }\n\n    /**\n    * @notice Checks that last period of sub-stake is greater than the current period\n    * @param _info Staker structure\n    * @param _subStake Sub-stake structure\n    * @param _currentPeriod Current period\n    * @return startPeriod Start period. Use in the calculation of the last period of the sub stake\n    * @return lastPeriod Last period of the sub stake\n    */\n    function checkLastPeriodOfSubStake(\n        StakerInfo storage _info,\n        SubStakeInfo storage _subStake,\n        uint16 _currentPeriod\n    )\n        internal view returns (uint16 startPeriod, uint16 lastPeriod)\n    {\n        startPeriod = getStartPeriod(_info, _currentPeriod);\n        lastPeriod = getLastPeriodOfSubStake(_subStake, startPeriod);\n        // The sub stake must be active at least in the next period\n        require(lastPeriod \u003e _currentPeriod);\n    }\n\n    /**\n    * @notice Save sub stake. First tries to override inactive sub stake\n    * @dev Inactive sub stake means that last period of sub stake has been surpassed and already rewarded\n    * @param _info Staker structure\n    * @param _firstPeriod First period of the sub stake\n    * @param _lastPeriod Last period of the sub stake\n    * @param _periods Duration of the sub stake in periods\n    * @param _lockedValue Amount of locked tokens\n    */\n    function saveSubStake(\n        StakerInfo storage _info,\n        uint16 _firstPeriod,\n        uint16 _lastPeriod,\n        uint16 _periods,\n        uint256 _lockedValue\n    )\n        internal\n    {\n        for (uint256 i = 0; i \u003c _info.subStakes.length; i++) {\n            SubStakeInfo storage subStake = _info.subStakes[i];\n            if (subStake.lastPeriod != 0 \u0026\u0026\n                (_info.currentCommittedPeriod == 0 ||\n                subStake.lastPeriod \u003c _info.currentCommittedPeriod) \u0026\u0026\n                (_info.nextCommittedPeriod == 0 ||\n                subStake.lastPeriod \u003c _info.nextCommittedPeriod))\n            {\n                subStake.firstPeriod = _firstPeriod;\n                subStake.lastPeriod = _lastPeriod;\n                subStake.periods = _periods;\n                subStake.lockedValue = uint128(_lockedValue);\n                return;\n            }\n        }\n        require(_info.subStakes.length \u003c MAX_SUB_STAKES);\n        _info.subStakes.push(SubStakeInfo(_firstPeriod, _lastPeriod, _periods, uint128(_lockedValue)));\n    }\n\n    /**\n    * @notice Divide sub stake into two parts\n    * @param _index Index of the sub stake\n    * @param _newValue New sub stake value\n    * @param _periods Amount of periods for extending sub stake\n    */\n    function divideStake(uint256 _index, uint256 _newValue, uint16 _periods) external onlyStaker {\n        StakerInfo storage info = stakerInfo[msg.sender];\n        require(_newValue \u003e= minAllowableLockedTokens \u0026\u0026 _periods \u003e 0);\n        SubStakeInfo storage subStake = info.subStakes[_index];\n        uint16 currentPeriod = getCurrentPeriod();\n        (, uint16 lastPeriod) = checkLastPeriodOfSubStake(info, subStake, currentPeriod);\n\n        uint256 oldValue = subStake.lockedValue;\n        subStake.lockedValue = uint128(oldValue.sub(_newValue));\n        require(subStake.lockedValue \u003e= minAllowableLockedTokens);\n        uint16 requestedPeriods = subStake.periods.add16(_periods);\n        saveSubStake(info, subStake.firstPeriod, 0, requestedPeriods, _newValue);\n        emit Divided(msg.sender, oldValue, lastPeriod, _newValue, _periods);\n        emit Locked(msg.sender, _newValue, subStake.firstPeriod, requestedPeriods);\n    }\n\n    /**\n    * @notice Prolong active sub stake\n    * @param _index Index of the sub stake\n    * @param _periods Amount of periods for extending sub stake\n    */\n    function prolongStake(uint256 _index, uint16 _periods) external onlyStaker {\n        StakerInfo storage info = stakerInfo[msg.sender];\n        // Incorrect parameters\n        require(_periods \u003e 0);\n        SubStakeInfo storage subStake = info.subStakes[_index];\n        uint16 currentPeriod = getCurrentPeriod();\n        (uint16 startPeriod, uint16 lastPeriod) = checkLastPeriodOfSubStake(info, subStake, currentPeriod);\n\n        subStake.periods = subStake.periods.add16(_periods);\n        // if the sub stake ends in the next committed period then reset the `lastPeriod` field\n        if (lastPeriod == startPeriod) {\n            subStake.lastPeriod = 0;\n        }\n        // The extended sub stake must not be less than the minimum value\n        require(uint32(lastPeriod - currentPeriod) + _periods \u003e= minLockedPeriods);\n        emit Locked(msg.sender, subStake.lockedValue, lastPeriod + 1, _periods);\n        emit Prolonged(msg.sender, subStake.lockedValue, lastPeriod, _periods);\n    }\n\n    /**\n    * @notice Merge two sub-stakes into one if their last periods are equal\n    * @dev It\u0027s possible that both sub-stakes will be active after this transaction.\n    * But only one of them will be active until next call `commitToNextPeriod` (in the next period)\n    * @param _index1 Index of the first sub-stake\n    * @param _index2 Index of the second sub-stake\n    */\n    function mergeStake(uint256 _index1, uint256 _index2) external onlyStaker {\n        require(_index1 != _index2); // must be different sub-stakes\n\n        StakerInfo storage info = stakerInfo[msg.sender];\n        SubStakeInfo storage subStake1 = info.subStakes[_index1];\n        SubStakeInfo storage subStake2 = info.subStakes[_index2];\n        uint16 currentPeriod = getCurrentPeriod();\n\n        (, uint16 lastPeriod1) = checkLastPeriodOfSubStake(info, subStake1, currentPeriod);\n        (, uint16 lastPeriod2) = checkLastPeriodOfSubStake(info, subStake2, currentPeriod);\n        // both sub-stakes must have equal last period to be mergeable\n        require(lastPeriod1 == lastPeriod2);\n        emit Merged(msg.sender, subStake1.lockedValue, subStake2.lockedValue, lastPeriod1);\n\n        if (subStake1.firstPeriod == subStake2.firstPeriod) {\n            subStake1.lockedValue += subStake2.lockedValue;\n            subStake2.lastPeriod = 1;\n            subStake2.periods = 0;\n        } else if (subStake1.firstPeriod \u003e subStake2.firstPeriod) {\n            subStake1.lockedValue += subStake2.lockedValue;\n            subStake2.lastPeriod = subStake1.firstPeriod - 1;\n            subStake2.periods = 0;\n        } else {\n            subStake2.lockedValue += subStake1.lockedValue;\n            subStake1.lastPeriod = subStake2.firstPeriod - 1;\n            subStake1.periods = 0;\n        }\n    }\n\n    /**\n    * @notice Remove unused sub-stake to decrease gas cost for several methods\n    */\n    function removeUnusedSubStake(uint16 _index) external onlyStaker {\n        StakerInfo storage info = stakerInfo[msg.sender];\n\n        uint256 lastIndex = info.subStakes.length - 1;\n        SubStakeInfo storage subStake = info.subStakes[_index];\n        require(subStake.lastPeriod != 0 \u0026\u0026\n                (info.currentCommittedPeriod == 0 ||\n                subStake.lastPeriod \u003c info.currentCommittedPeriod) \u0026\u0026\n                (info.nextCommittedPeriod == 0 ||\n                subStake.lastPeriod \u003c info.nextCommittedPeriod));\n\n        if (_index != lastIndex) {\n            SubStakeInfo storage lastSubStake = info.subStakes[lastIndex];\n            subStake.firstPeriod = lastSubStake.firstPeriod;\n            subStake.lastPeriod = lastSubStake.lastPeriod;\n            subStake.periods = lastSubStake.periods;\n            subStake.lockedValue = lastSubStake.lockedValue;\n        }\n        info.subStakes.pop();\n    }\n\n    /**\n    * @notice Withdraw available amount of tokens to staker\n    * @param _value Amount of tokens to withdraw\n    */\n    function withdraw(uint256 _value) external onlyStaker {\n        uint16 currentPeriod = getCurrentPeriod();\n        uint16 nextPeriod = currentPeriod + 1;\n        StakerInfo storage info = stakerInfo[msg.sender];\n        // the max locked tokens in most cases will be in the current period\n        // but when the staker locks more then we should use the next period\n        uint256 lockedTokens = Math.max(getLockedTokens(info, currentPeriod, nextPeriod),\n            getLockedTokens(info, currentPeriod, currentPeriod));\n        require(_value \u003c= info.value.sub(lockedTokens));\n        info.value -= _value;\n\n        addSnapshot(info, - int256(_value));\n        token.safeTransfer(msg.sender, _value);\n        emit Withdrawn(msg.sender, _value);\n\n        // unbond worker if staker withdraws last portion of NU\n        if (info.value == 0 \u0026\u0026\n            info.nextCommittedPeriod == 0 \u0026\u0026\n            info.worker != address(0))\n        {\n            stakerFromWorker[info.worker] = address(0);\n            info.worker = address(0);\n            emit WorkerBonded(msg.sender, address(0), currentPeriod);\n        }\n    }\n\n    /**\n    * @notice Make a commitment to the next period and mint for the previous period\n    */\n    function commitToNextPeriod() external isInitialized {\n        address staker = stakerFromWorker[msg.sender];\n        StakerInfo storage info = stakerInfo[staker];\n        // Staker must have a stake to make a commitment\n        require(info.value \u003e 0);\n        // Only worker with real address can make a commitment\n        require(msg.sender == tx.origin);\n\n        uint16 lastCommittedPeriod = getLastCommittedPeriod(staker);\n        (uint16 processedPeriod1, uint16 processedPeriod2) = mint(staker);\n        uint16 currentPeriod = getCurrentPeriod();\n        uint16 nextPeriod = currentPeriod + 1;\n\n        // the period has already been committed\n        if (info.nextCommittedPeriod == nextPeriod) {\n            return;\n        }\n\n        uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);\n        require(lockedTokens \u003e 0);\n        lockedPerPeriod[nextPeriod] += lockedTokens;\n\n        info.currentCommittedPeriod = info.nextCommittedPeriod;\n        info.nextCommittedPeriod = nextPeriod;\n\n        decreaseSubStakesDuration(info, nextPeriod);\n\n        // staker was inactive for several periods\n        if (lastCommittedPeriod \u003c currentPeriod) {\n            info.pastDowntime.push(Downtime(lastCommittedPeriod + 1, currentPeriod));\n        }\n\n        policyManager.ping(staker, processedPeriod1, processedPeriod2, nextPeriod);\n        emit CommitmentMade(staker, nextPeriod, lockedTokens);\n    }\n\n    /**\n    * @notice Decrease sub-stakes duration if `windDown` is enabled\n    */\n    function decreaseSubStakesDuration(StakerInfo storage _info, uint16 _nextPeriod) internal {\n        if (!_info.flags.bitSet(WIND_DOWN_INDEX)) {\n            return;\n        }\n        for (uint256 index = 0; index \u003c _info.subStakes.length; index++) {\n            SubStakeInfo storage subStake = _info.subStakes[index];\n            if (subStake.lastPeriod != 0 || subStake.periods == 0) {\n                continue;\n            }\n            subStake.periods--;\n            if (subStake.periods == 0) {\n                subStake.lastPeriod = _nextPeriod;\n            }\n        }\n    }\n\n    /**\n    * @notice Mint tokens for previous periods if staker locked their tokens and made a commitment\n    */\n    function mint() external onlyStaker {\n        // save last committed period to the storage if both periods will be empty after minting\n        // because we won\u0027t be able to calculate last committed period\n        // see getLastCommittedPeriod(address)\n        StakerInfo storage info = stakerInfo[msg.sender];\n        uint16 previousPeriod = getCurrentPeriod() - 1;\n        if (info.nextCommittedPeriod \u003c= previousPeriod \u0026\u0026 info.nextCommittedPeriod != 0) {\n            info.lastCommittedPeriod = info.nextCommittedPeriod;\n        }\n        (uint16 processedPeriod1, uint16 processedPeriod2) = mint(msg.sender);\n\n        if (processedPeriod1 != 0 || processedPeriod2 != 0) {\n            policyManager.ping(msg.sender, processedPeriod1, processedPeriod2, 0);\n        }\n    }\n\n    /**\n    * @notice Mint tokens for previous periods if staker locked their tokens and made a commitment\n    * @param _staker Staker\n    * @return processedPeriod1 Processed period: currentCommittedPeriod or zero\n    * @return processedPeriod2 Processed period: nextCommittedPeriod or zero\n    */\n    function mint(address _staker) internal returns (uint16 processedPeriod1, uint16 processedPeriod2) {\n        uint16 currentPeriod = getCurrentPeriod();\n        uint16 previousPeriod = currentPeriod - 1;\n        StakerInfo storage info = stakerInfo[_staker];\n\n        if (info.nextCommittedPeriod == 0 ||\n            info.currentCommittedPeriod == 0 \u0026\u0026\n            info.nextCommittedPeriod \u003e previousPeriod ||\n            info.currentCommittedPeriod \u003e previousPeriod) {\n            return (0, 0);\n        }\n\n        uint16 startPeriod = getStartPeriod(info, currentPeriod);\n        uint256 reward = 0;\n        bool reStake = !info.flags.bitSet(RE_STAKE_DISABLED_INDEX);\n\n        if (info.currentCommittedPeriod != 0) {\n            reward = mint(info, info.currentCommittedPeriod, currentPeriod, startPeriod, reStake);\n            processedPeriod1 = info.currentCommittedPeriod;\n            info.currentCommittedPeriod = 0;\n            if (reStake) {\n                lockedPerPeriod[info.nextCommittedPeriod] += reward;\n            }\n        }\n        if (info.nextCommittedPeriod \u003c= previousPeriod) {\n            reward += mint(info, info.nextCommittedPeriod, currentPeriod, startPeriod, reStake);\n            processedPeriod2 = info.nextCommittedPeriod;\n            info.nextCommittedPeriod = 0;\n        }\n\n        info.value += reward;\n        if (info.flags.bitSet(MEASURE_WORK_INDEX)) {\n            info.completedWork += reward;\n        }\n\n        addSnapshot(info, int256(reward));\n        emit Minted(_staker, previousPeriod, reward);\n    }\n\n    /**\n    * @notice Calculate reward for one period\n    * @param _info Staker structure\n    * @param _mintingPeriod Period for minting calculation\n    * @param _currentPeriod Current period\n    * @param _startPeriod Pre-calculated start period\n    */\n    function mint(\n        StakerInfo storage _info,\n        uint16 _mintingPeriod,\n        uint16 _currentPeriod,\n        uint16 _startPeriod,\n        bool _reStake\n    )\n        internal returns (uint256 reward)\n    {\n        reward = 0;\n        for (uint256 i = 0; i \u003c _info.subStakes.length; i++) {\n            SubStakeInfo storage subStake =  _info.subStakes[i];\n            uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);\n            if (subStake.firstPeriod \u003c= _mintingPeriod \u0026\u0026 lastPeriod \u003e= _mintingPeriod) {\n                uint256 subStakeReward = mint(\n                    _currentPeriod,\n                    subStake.lockedValue,\n                    lockedPerPeriod[_mintingPeriod],\n                    lastPeriod.sub16(_mintingPeriod));\n                reward += subStakeReward;\n                if (_reStake) {\n                    subStake.lockedValue += uint128(subStakeReward);\n                }\n            }\n        }\n        return reward;\n    }\n\n    //-------------------------Slashing-------------------------\n    /**\n    * @notice Slash the staker\u0027s stake and reward the investigator\n    * @param _staker Staker\u0027s address\n    * @param _penalty Penalty\n    * @param _investigator Investigator\n    * @param _reward Reward for the investigator\n    */\n    function slashStaker(\n        address _staker,\n        uint256 _penalty,\n        address _investigator,\n        uint256 _reward\n    )\n        public isInitialized\n    {\n        require(msg.sender == address(adjudicator));\n        require(_penalty \u003e 0);\n        StakerInfo storage info = stakerInfo[_staker];\n        if (info.value \u003c= _penalty) {\n            _penalty = info.value;\n        }\n        info.value -= _penalty;\n        if (_reward \u003e _penalty) {\n            _reward = _penalty;\n        }\n\n        uint16 currentPeriod = getCurrentPeriod();\n        uint16 nextPeriod = currentPeriod + 1;\n        uint16 startPeriod = getStartPeriod(info, currentPeriod);\n\n        (uint256 currentLock, uint256 nextLock, uint256 currentAndNextLock, uint256 shortestSubStakeIndex) =\n            getLockedTokensAndShortestSubStake(info, currentPeriod, nextPeriod, startPeriod);\n\n        // Decrease the stake if amount of locked tokens in the current period more than staker has\n        uint256 lockedTokens = currentLock + currentAndNextLock;\n        if (info.value \u003c lockedTokens) {\n           decreaseSubStakes(info, lockedTokens - info.value, currentPeriod, startPeriod, shortestSubStakeIndex);\n        }\n        // Decrease the stake if amount of locked tokens in the next period more than staker has\n        if (nextLock \u003e 0) {\n            lockedTokens = nextLock + currentAndNextLock -\n                (currentAndNextLock \u003e info.value ? currentAndNextLock - info.value : 0);\n            if (info.value \u003c lockedTokens) {\n               decreaseSubStakes(info, lockedTokens - info.value, nextPeriod, startPeriod, MAX_SUB_STAKES);\n            }\n        }\n\n        emit Slashed(_staker, _penalty, _investigator, _reward);\n        if (_penalty \u003e _reward) {\n            unMint(_penalty - _reward);\n        }\n        // TODO change to withdrawal pattern (#1499)\n        if (_reward \u003e 0) {\n            token.safeTransfer(_investigator, _reward);\n        }\n\n        addSnapshot(info, - int256(_penalty));\n\n    }\n\n    /**\n    * @notice Get the value of locked tokens for a staker in the current and the next period\n    * and find the shortest sub stake\n    * @param _info Staker structure\n    * @param _currentPeriod Current period\n    * @param _nextPeriod Next period\n    * @param _startPeriod Pre-calculated start period\n    * @return currentLock Amount of tokens that locked in the current period and unlocked in the next period\n    * @return nextLock Amount of tokens that locked in the next period and not locked in the current period\n    * @return currentAndNextLock Amount of tokens that locked in the current period and in the next period\n    * @return shortestSubStakeIndex Index of the shortest sub stake\n    */\n    function getLockedTokensAndShortestSubStake(\n        StakerInfo storage _info,\n        uint16 _currentPeriod,\n        uint16 _nextPeriod,\n        uint16 _startPeriod\n    )\n        internal view returns (\n            uint256 currentLock,\n            uint256 nextLock,\n            uint256 currentAndNextLock,\n            uint256 shortestSubStakeIndex\n        )\n    {\n        uint16 minDuration = MAX_UINT16;\n        uint16 minLastPeriod = MAX_UINT16;\n        shortestSubStakeIndex = MAX_SUB_STAKES;\n        currentLock = 0;\n        nextLock = 0;\n        currentAndNextLock = 0;\n\n        for (uint256 i = 0; i \u003c _info.subStakes.length; i++) {\n            SubStakeInfo storage subStake = _info.subStakes[i];\n            uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);\n            if (lastPeriod \u003c subStake.firstPeriod) {\n                continue;\n            }\n            if (subStake.firstPeriod \u003c= _currentPeriod \u0026\u0026\n                lastPeriod \u003e= _nextPeriod) {\n                currentAndNextLock += subStake.lockedValue;\n            } else if (subStake.firstPeriod \u003c= _currentPeriod \u0026\u0026\n                lastPeriod \u003e= _currentPeriod) {\n                currentLock += subStake.lockedValue;\n            } else if (subStake.firstPeriod \u003c= _nextPeriod \u0026\u0026\n                lastPeriod \u003e= _nextPeriod) {\n                nextLock += subStake.lockedValue;\n            }\n            uint16 duration = lastPeriod - subStake.firstPeriod;\n            if (subStake.firstPeriod \u003c= _currentPeriod \u0026\u0026\n                lastPeriod \u003e= _currentPeriod \u0026\u0026\n                (lastPeriod \u003c minLastPeriod ||\n                lastPeriod == minLastPeriod \u0026\u0026 duration \u003c minDuration))\n            {\n                shortestSubStakeIndex = i;\n                minDuration = duration;\n                minLastPeriod = lastPeriod;\n            }\n        }\n    }\n\n    /**\n    * @notice Decrease short sub stakes\n    * @param _info Staker structure\n    * @param _penalty Penalty rate\n    * @param _decreasePeriod The period when the decrease begins\n    * @param _startPeriod Pre-calculated start period\n    * @param _shortestSubStakeIndex Index of the shortest period\n    */\n    function decreaseSubStakes(\n        StakerInfo storage _info,\n        uint256 _penalty,\n        uint16 _decreasePeriod,\n        uint16 _startPeriod,\n        uint256 _shortestSubStakeIndex\n    )\n        internal\n    {\n        SubStakeInfo storage shortestSubStake = _info.subStakes[0];\n        uint16 minSubStakeLastPeriod = MAX_UINT16;\n        uint16 minSubStakeDuration = MAX_UINT16;\n        while(_penalty \u003e 0) {\n            if (_shortestSubStakeIndex \u003c MAX_SUB_STAKES) {\n                shortestSubStake = _info.subStakes[_shortestSubStakeIndex];\n                minSubStakeLastPeriod = getLastPeriodOfSubStake(shortestSubStake, _startPeriod);\n                minSubStakeDuration = minSubStakeLastPeriod - shortestSubStake.firstPeriod;\n                _shortestSubStakeIndex = MAX_SUB_STAKES;\n            } else {\n                (shortestSubStake, minSubStakeDuration, minSubStakeLastPeriod) =\n                    getShortestSubStake(_info, _decreasePeriod, _startPeriod);\n            }\n            if (minSubStakeDuration == MAX_UINT16) {\n                break;\n            }\n            uint256 appliedPenalty = _penalty;\n            if (_penalty \u003c shortestSubStake.lockedValue) {\n                shortestSubStake.lockedValue -= uint128(_penalty);\n                saveOldSubStake(_info, shortestSubStake.firstPeriod, _penalty, _decreasePeriod);\n                _penalty = 0;\n            } else {\n                shortestSubStake.lastPeriod = _decreasePeriod - 1;\n                _penalty -= shortestSubStake.lockedValue;\n                appliedPenalty = shortestSubStake.lockedValue;\n            }\n            if (_info.currentCommittedPeriod \u003e= _decreasePeriod \u0026\u0026\n                _info.currentCommittedPeriod \u003c= minSubStakeLastPeriod)\n            {\n                lockedPerPeriod[_info.currentCommittedPeriod] -= appliedPenalty;\n            }\n            if (_info.nextCommittedPeriod \u003e= _decreasePeriod \u0026\u0026\n                _info.nextCommittedPeriod \u003c= minSubStakeLastPeriod)\n            {\n                lockedPerPeriod[_info.nextCommittedPeriod] -= appliedPenalty;\n            }\n        }\n    }\n\n    /**\n    * @notice Get the shortest sub stake\n    * @param _info Staker structure\n    * @param _currentPeriod Current period\n    * @param _startPeriod Pre-calculated start period\n    * @return shortestSubStake The shortest sub stake\n    * @return minSubStakeDuration Duration of the shortest sub stake\n    * @return minSubStakeLastPeriod Last period of the shortest sub stake\n    */\n    function getShortestSubStake(\n        StakerInfo storage _info,\n        uint16 _currentPeriod,\n        uint16 _startPeriod\n    )\n        internal view returns (\n            SubStakeInfo storage shortestSubStake,\n            uint16 minSubStakeDuration,\n            uint16 minSubStakeLastPeriod\n        )\n    {\n        shortestSubStake = shortestSubStake;\n        minSubStakeDuration = MAX_UINT16;\n        minSubStakeLastPeriod = MAX_UINT16;\n        for (uint256 i = 0; i \u003c _info.subStakes.length; i++) {\n            SubStakeInfo storage subStake = _info.subStakes[i];\n            uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);\n            if (lastPeriod \u003c subStake.firstPeriod) {\n                continue;\n            }\n            uint16 duration = lastPeriod - subStake.firstPeriod;\n            if (subStake.firstPeriod \u003c= _currentPeriod \u0026\u0026\n                lastPeriod \u003e= _currentPeriod \u0026\u0026\n                (lastPeriod \u003c minSubStakeLastPeriod ||\n                lastPeriod == minSubStakeLastPeriod \u0026\u0026 duration \u003c minSubStakeDuration))\n            {\n                shortestSubStake = subStake;\n                minSubStakeDuration = duration;\n                minSubStakeLastPeriod = lastPeriod;\n            }\n        }\n    }\n\n    /**\n    * @notice Save the old sub stake values to prevent decreasing reward for the previous period\n    * @dev Saving happens only if the previous period is committed\n    * @param _info Staker structure\n    * @param _firstPeriod First period of the old sub stake\n    * @param _lockedValue Locked value of the old sub stake\n    * @param _currentPeriod Current period, when the old sub stake is already unlocked\n    */\n    function saveOldSubStake(\n        StakerInfo storage _info,\n        uint16 _firstPeriod,\n        uint256 _lockedValue,\n        uint16 _currentPeriod\n    )\n        internal\n    {\n        // Check that the old sub stake should be saved\n        bool oldCurrentCommittedPeriod = _info.currentCommittedPeriod != 0 \u0026\u0026\n            _info.currentCommittedPeriod \u003c _currentPeriod;\n        bool oldnextCommittedPeriod = _info.nextCommittedPeriod != 0 \u0026\u0026\n            _info.nextCommittedPeriod \u003c _currentPeriod;\n        bool crosscurrentCommittedPeriod = oldCurrentCommittedPeriod \u0026\u0026 _info.currentCommittedPeriod \u003e= _firstPeriod;\n        bool crossnextCommittedPeriod = oldnextCommittedPeriod \u0026\u0026 _info.nextCommittedPeriod \u003e= _firstPeriod;\n        if (!crosscurrentCommittedPeriod \u0026\u0026 !crossnextCommittedPeriod) {\n            return;\n        }\n        // Try to find already existent proper old sub stake\n        uint16 previousPeriod = _currentPeriod - 1;\n        for (uint256 i = 0; i \u003c _info.subStakes.length; i++) {\n            SubStakeInfo storage subStake = _info.subStakes[i];\n            if (subStake.lastPeriod == previousPeriod \u0026\u0026\n                ((crosscurrentCommittedPeriod ==\n                (oldCurrentCommittedPeriod \u0026\u0026 _info.currentCommittedPeriod \u003e= subStake.firstPeriod)) \u0026\u0026\n                (crossnextCommittedPeriod ==\n                (oldnextCommittedPeriod \u0026\u0026 _info.nextCommittedPeriod \u003e= subStake.firstPeriod))))\n            {\n                subStake.lockedValue += uint128(_lockedValue);\n                return;\n            }\n        }\n        saveSubStake(_info, _firstPeriod, previousPeriod, 0, _lockedValue);\n    }\n\n    //-------------Additional getters for stakers info-------------\n    /**\n    * @notice Return the length of the array of stakers\n    */\n    function getStakersLength() external view returns (uint256) {\n        return stakers.length;\n    }\n\n    /**\n    * @notice Return the length of the array of sub stakes\n    */\n    function getSubStakesLength(address _staker) external view returns (uint256) {\n        return stakerInfo[_staker].subStakes.length;\n    }\n\n    /**\n    * @notice Return the information about sub stake\n    */\n    function getSubStakeInfo(address _staker, uint256 _index)\n    // TODO change to structure when ABIEncoderV2 is released (#1501)\n//        public view returns (SubStakeInfo)\n        // TODO \"virtual\" only for tests, probably will be removed after #1512\n        external view virtual returns (uint16 firstPeriod, uint16 lastPeriod, uint16 periods, uint128 lockedValue)\n    {\n        SubStakeInfo storage info = stakerInfo[_staker].subStakes[_index];\n        firstPeriod = info.firstPeriod;\n        lastPeriod = info.lastPeriod;\n        periods = info.periods;\n        lockedValue = info.lockedValue;\n    }\n\n    /**\n    * @notice Return the length of the array of past downtime\n    */\n    function getPastDowntimeLength(address _staker) external view returns (uint256) {\n        return stakerInfo[_staker].pastDowntime.length;\n    }\n\n    /**\n    * @notice Return the information about past downtime\n    */\n    function  getPastDowntime(address _staker, uint256 _index)\n    // TODO change to structure when ABIEncoderV2 is released (#1501)\n//        public view returns (Downtime)\n        external view returns (uint16 startPeriod, uint16 endPeriod)\n    {\n        Downtime storage downtime = stakerInfo[_staker].pastDowntime[_index];\n        startPeriod = downtime.startPeriod;\n        endPeriod = downtime.endPeriod;\n    }\n\n    //------------------ ERC900 connectors ----------------------\n\n    function totalStakedForAt(address _owner, uint256 _blockNumber) public view override returns (uint256){\n        return stakerInfo[_owner].history.getValueAt(_blockNumber);\n    }\n\n    function totalStakedAt(uint256 _blockNumber) public view override returns (uint256){\n        return balanceHistory.getValueAt(_blockNumber);\n    }\n\n    function supportsHistory() external pure override returns (bool){\n        return true;\n    }\n\n    //------------------------Upgradeable------------------------\n    /**\n    * @dev Get StakerInfo structure by delegatecall\n    */\n    function delegateGetStakerInfo(address _target, bytes32 _staker)\n        internal returns (StakerInfo memory result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, this.stakerInfo.selector, 1, _staker, 0);\n        assembly {\n            result := memoryAddress\n        }\n    }\n\n    /**\n    * @dev Get SubStakeInfo structure by delegatecall\n    */\n    function delegateGetSubStakeInfo(address _target, bytes32 _staker, uint256 _index)\n        internal returns (SubStakeInfo memory result)\n    {\n        bytes32 memoryAddress = delegateGetData(\n            _target, this.getSubStakeInfo.selector, 2, _staker, bytes32(_index));\n        assembly {\n            result := memoryAddress\n        }\n    }\n\n    /**\n    * @dev Get Downtime structure by delegatecall\n    */\n    function delegateGetPastDowntime(address _target, bytes32 _staker, uint256 _index)\n        internal returns (Downtime memory result)\n    {\n        bytes32 memoryAddress = delegateGetData(\n            _target, this.getPastDowntime.selector, 2, _staker, bytes32(_index));\n        assembly {\n            result := memoryAddress\n        }\n    }\n\n    /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`\n    function verifyState(address _testTarget) public override virtual {\n        super.verifyState(_testTarget);\n        require(address(delegateGet(_testTarget, this.policyManager.selector)) == address(policyManager));\n        require(address(delegateGet(_testTarget, this.adjudicator.selector)) == address(adjudicator));\n        require(address(delegateGet(_testTarget, this.workLock.selector)) == address(workLock));\n        require(delegateGet(_testTarget, this.lockedPerPeriod.selector,\n            bytes32(bytes2(RESERVED_PERIOD))) == lockedPerPeriod[RESERVED_PERIOD]);\n        require(address(delegateGet(_testTarget, this.stakerFromWorker.selector, bytes32(0))) ==\n            stakerFromWorker[address(0)]);\n\n        require(delegateGet(_testTarget, this.getStakersLength.selector) == stakers.length);\n        if (stakers.length == 0) {\n            return;\n        }\n        address stakerAddress = stakers[0];\n        require(address(uint160(delegateGet(_testTarget, this.stakers.selector, 0))) == stakerAddress);\n        StakerInfo storage info = stakerInfo[stakerAddress];\n        bytes32 staker = bytes32(uint256(stakerAddress));\n        StakerInfo memory infoToCheck = delegateGetStakerInfo(_testTarget, staker);\n        require(infoToCheck.value == info.value \u0026\u0026\n            infoToCheck.currentCommittedPeriod == info.currentCommittedPeriod \u0026\u0026\n            infoToCheck.nextCommittedPeriod == info.nextCommittedPeriod \u0026\u0026\n            infoToCheck.flags == info.flags \u0026\u0026\n            infoToCheck.lockReStakeUntilPeriod == info.lockReStakeUntilPeriod \u0026\u0026\n            infoToCheck.lastCommittedPeriod == info.lastCommittedPeriod \u0026\u0026\n            infoToCheck.completedWork == info.completedWork \u0026\u0026\n            infoToCheck.worker == info.worker \u0026\u0026\n            infoToCheck.workerStartPeriod == info.workerStartPeriod);\n\n        require(delegateGet(_testTarget, this.getPastDowntimeLength.selector, staker) ==\n            info.pastDowntime.length);\n        for (uint256 i = 0; i \u003c info.pastDowntime.length \u0026\u0026 i \u003c MAX_CHECKED_VALUES; i++) {\n            Downtime storage downtime = info.pastDowntime[i];\n            Downtime memory downtimeToCheck = delegateGetPastDowntime(_testTarget, staker, i);\n            require(downtimeToCheck.startPeriod == downtime.startPeriod \u0026\u0026\n                downtimeToCheck.endPeriod == downtime.endPeriod);\n        }\n\n        require(delegateGet(_testTarget, this.getSubStakesLength.selector, staker) == info.subStakes.length);\n        for (uint256 i = 0; i \u003c info.subStakes.length \u0026\u0026 i \u003c MAX_CHECKED_VALUES; i++) {\n            SubStakeInfo storage subStakeInfo = info.subStakes[i];\n            SubStakeInfo memory subStakeInfoToCheck = delegateGetSubStakeInfo(_testTarget, staker, i);\n            require(subStakeInfoToCheck.firstPeriod == subStakeInfo.firstPeriod \u0026\u0026\n                subStakeInfoToCheck.lastPeriod == subStakeInfo.lastPeriod \u0026\u0026\n                subStakeInfoToCheck.periods == subStakeInfo.periods \u0026\u0026\n                subStakeInfoToCheck.lockedValue == subStakeInfo.lockedValue);\n        }\n\n        // it\u0027s not perfect because checks not only slot value but also decoding\n        // at least without additional functions\n        require(delegateGet(_testTarget, this.totalStakedForAt.selector, staker, bytes32(block.number)) ==\n            totalStakedForAt(stakerAddress, block.number));\n        require(delegateGet(_testTarget, this.totalStakedAt.selector, bytes32(block.number)) ==\n            totalStakedAt(block.number));\n\n        if (info.worker != address(0)) {\n            require(address(delegateGet(_testTarget, this.stakerFromWorker.selector, bytes32(uint256(info.worker)))) ==\n                stakerFromWorker[info.worker]);\n        }\n    }\n\n    /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade`\n    function finishUpgrade(address _target) public override virtual {\n        super.finishUpgrade(_target);\n        // Create fake period\n        lockedPerPeriod[RESERVED_PERIOD] = 111;\n\n        // Create fake worker\n        stakerFromWorker[address(0)] = address(this);\n    }\n}\n"},"Upgradeable.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"Ownable.sol\";\n\n\n/**\n* @notice Base contract for upgradeable contract\n* @dev Inherited contract should implement verifyState(address) method by checking storage variables\n* (see verifyState(address) in Dispatcher). Also contract should implement finishUpgrade(address)\n* if it is using constructor parameters by coping this parameters to the dispatcher storage\n*/\nabstract contract Upgradeable is Ownable {\n\n    event StateVerified(address indexed testTarget, address sender);\n    event UpgradeFinished(address indexed target, address sender);\n\n    /**\n    * @dev Contracts at the target must reserve the same location in storage for this address as in Dispatcher\n    * Stored data actually lives in the Dispatcher\n    * However the storage layout is specified here in the implementing contracts\n    */\n    address public target;\n\n    /**\n    * @dev Previous contract address (if available). Used for rollback\n    */\n    address public previousTarget;\n\n    /**\n    * @dev Upgrade status. Explicit `uint8` type is used instead of `bool` to save gas by excluding 0 value\n    */\n    uint8 public isUpgrade;\n\n    /**\n    * @dev Guarantees that next slot will be separated from the previous\n    */\n    uint256 stubSlot;\n\n    /**\n    * @dev Constants for `isUpgrade` field\n    */\n    uint8 constant UPGRADE_FALSE = 1;\n    uint8 constant UPGRADE_TRUE = 2;\n\n    /**\n    * @dev Checks that function executed while upgrading\n    * Recommended to add to `verifyState` and `finishUpgrade` methods\n    */\n    modifier onlyWhileUpgrading()\n    {\n        require(isUpgrade == UPGRADE_TRUE);\n        _;\n    }\n\n    /**\n    * @dev Method for verifying storage state.\n    * Should check that new target contract returns right storage value\n    */\n    function verifyState(address _testTarget) public virtual onlyWhileUpgrading {\n        emit StateVerified(_testTarget, msg.sender);\n    }\n\n    /**\n    * @dev Copy values from the new target to the current storage\n    * @param _target New target contract address\n    */\n    function finishUpgrade(address _target) public virtual onlyWhileUpgrading {\n        emit UpgradeFinished(_target, msg.sender);\n    }\n\n    /**\n    * @dev Base method to get data\n    * @param _target Target to call\n    * @param _selector Method selector\n    * @param _numberOfArguments Number of used arguments\n    * @param _argument1 First method argument\n    * @param _argument2 Second method argument\n    * @return memoryAddress Address in memory where the data is located\n    */\n    function delegateGetData(\n        address _target,\n        bytes4 _selector,\n        uint8 _numberOfArguments,\n        bytes32 _argument1,\n        bytes32 _argument2\n    )\n        internal returns (bytes32 memoryAddress)\n    {\n        assembly {\n            memoryAddress := mload(0x40)\n            mstore(memoryAddress, _selector)\n            if gt(_numberOfArguments, 0) {\n                mstore(add(memoryAddress, 0x04), _argument1)\n            }\n            if gt(_numberOfArguments, 1) {\n                mstore(add(memoryAddress, 0x24), _argument2)\n            }\n            switch delegatecall(gas(), _target, memoryAddress, add(0x04, mul(0x20, _numberOfArguments)), 0, 0)\n                case 0 {\n                    revert(memoryAddress, 0)\n                }\n                default {\n                    returndatacopy(memoryAddress, 0x0, returndatasize())\n                }\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" without parameters.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(address _target, bytes4 _selector)\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 0, 0, 0);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" with one parameter.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(address _target, bytes4 _selector, bytes32 _argument)\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 1, _argument, 0);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" with two parameters.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(\n        address _target,\n        bytes4 _selector,\n        bytes32 _argument1,\n        bytes32 _argument2\n    )\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 2, _argument1, _argument2);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n}\n"}}

        File 3 of 4: Dispatcher
        {"Address.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n/**\n * @dev Collection of functions related to the address type\n */\nlibrary Address {\n    /**\n     * @dev Returns true if `account` is a contract.\n     *\n     * [IMPORTANT]\n     * ====\n     * It is unsafe to assume that an address for which this function returns\n     * false is an externally-owned account (EOA) and not a contract.\n     *\n     * Among others, `isContract` will return false for the following\n     * types of addresses:\n     *\n     *  - an externally-owned account\n     *  - a contract in construction\n     *  - an address where a contract will be created\n     *  - an address where a contract lived, but was destroyed\n     * ====\n     */\n    function isContract(address account) internal view returns (bool) {\n        // According to EIP-1052, 0x0 is the value returned for not-yet created accounts\n        // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned\n        // for accounts without code, i.e. `keccak256(\u0027\u0027)`\n        bytes32 codehash;\n        bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;\n        // solhint-disable-next-line no-inline-assembly\n        assembly { codehash := extcodehash(account) }\n        return (codehash != accountHash \u0026\u0026 codehash != 0x0);\n    }\n\n    /**\n     * @dev Replacement for Solidity\u0027s `transfer`: sends `amount` wei to\n     * `recipient`, forwarding all available gas and reverting on errors.\n     *\n     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost\n     * of certain opcodes, possibly making contracts go over the 2300 gas limit\n     * imposed by `transfer`, making them unable to receive funds via\n     * `transfer`. {sendValue} removes this limitation.\n     *\n     * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].\n     *\n     * IMPORTANT: because control is transferred to `recipient`, care must be\n     * taken to not create reentrancy vulnerabilities. Consider using\n     * {ReentrancyGuard} or the\n     * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].\n     *\n     * _Available since v2.4.0._\n     */\n    function sendValue(address payable recipient, uint256 amount) internal {\n        require(address(this).balance \u003e= amount, \"Address: insufficient balance\");\n\n        // solhint-disable-next-line avoid-call-value\n        (bool success, ) = recipient.call{value: amount}(\"\");\n        require(success, \"Address: unable to send value, recipient may have reverted\");\n    }\n}\n"},"Dispatcher.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"./Upgradeable.sol\";\nimport \"./Address.sol\";\n\n\n/**\n* @notice ERC897 - ERC DelegateProxy\n*/\ninterface ERCProxy {\n    function proxyType() external pure returns (uint256);\n    function implementation() external view returns (address);\n}\n\n\n/**\n* @notice Proxying requests to other contracts.\n* Client should use ABI of real contract and address of this contract\n*/\ncontract Dispatcher is Upgradeable, ERCProxy {\n    using Address for address;\n\n    event Upgraded(address indexed from, address indexed to, address owner);\n    event RolledBack(address indexed from, address indexed to, address owner);\n\n    /**\n    * @dev Set upgrading status before and after operations\n    */\n    modifier upgrading()\n    {\n        isUpgrade = UPGRADE_TRUE;\n        _;\n        isUpgrade = UPGRADE_FALSE;\n    }\n\n    /**\n    * @param _target Target contract address\n    */\n    constructor(address _target) upgrading {\n        require(_target.isContract());\n        // Checks that target contract inherits Dispatcher state\n        verifyState(_target);\n        // `verifyState` must work with its contract\n        verifyUpgradeableState(_target, _target);\n        target = _target;\n        finishUpgrade();\n        emit Upgraded(address(0), _target, msg.sender);\n    }\n\n    //------------------------ERC897------------------------\n    /**\n     * @notice ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy\n     */\n    function proxyType() external pure override returns (uint256) {\n        return 2;\n    }\n\n    /**\n     * @notice ERC897, gets the address of the implementation where every call will be delegated\n     */\n    function implementation() external view override returns (address) {\n        return target;\n    }\n    //------------------------------------------------------------\n\n    /**\n    * @notice Verify new contract storage and upgrade target\n    * @param _target New target contract address\n    */\n    function upgrade(address _target) public onlyOwner upgrading {\n        require(_target.isContract());\n        // Checks that target contract has \"correct\" (as much as possible) state layout\n        verifyState(_target);\n        //`verifyState` must work with its contract\n        verifyUpgradeableState(_target, _target);\n        if (target.isContract()) {\n            verifyUpgradeableState(target, _target);\n        }\n        previousTarget = target;\n        target = _target;\n        finishUpgrade();\n        emit Upgraded(previousTarget, _target, msg.sender);\n    }\n\n    /**\n    * @notice Rollback to previous target\n    * @dev Test storage carefully before upgrade again after rollback\n    */\n    function rollback() public onlyOwner upgrading {\n        require(previousTarget.isContract());\n        emit RolledBack(target, previousTarget, msg.sender);\n        // should be always true because layout previousTarget -\u003e target was already checked\n        // but `verifyState` is not 100% accurate so check again\n        verifyState(previousTarget);\n        if (target.isContract()) {\n            verifyUpgradeableState(previousTarget, target);\n        }\n        target = previousTarget;\n        previousTarget = address(0);\n        finishUpgrade();\n    }\n\n    /**\n    * @dev Call verifyState method for Upgradeable contract\n    */\n    function verifyUpgradeableState(address _from, address _to) private {\n        (bool callSuccess,) = _from.delegatecall(abi.encodeWithSelector(this.verifyState.selector, _to));\n        require(callSuccess);\n    }\n\n    /**\n    * @dev Call finishUpgrade method from the Upgradeable contract\n    */\n    function finishUpgrade() private {\n        (bool callSuccess,) = target.delegatecall(abi.encodeWithSelector(this.finishUpgrade.selector, target));\n        require(callSuccess);\n    }\n\n    function verifyState(address _testTarget) public override onlyWhileUpgrading {\n        //checks equivalence accessing state through new contract and current storage\n        require(address(uint160(delegateGet(_testTarget, this.owner.selector))) == owner());\n        require(address(uint160(delegateGet(_testTarget, this.target.selector))) == target);\n        require(address(uint160(delegateGet(_testTarget, this.previousTarget.selector))) == previousTarget);\n        require(uint8(delegateGet(_testTarget, this.isUpgrade.selector)) == isUpgrade);\n    }\n\n    /**\n    * @dev Override function using empty code because no reason to call this function in Dispatcher\n    */\n    function finishUpgrade(address) public override {}\n\n    /**\n    * @dev Receive function sends empty request to the target contract\n    */\n    receive() external payable {\n        assert(target.isContract());\n        // execute receive function from target contract using storage of the dispatcher\n        (bool callSuccess,) = target.delegatecall(\"\");\n        if (!callSuccess) {\n            revert();\n        }\n    }\n\n    /**\n    * @dev Fallback function sends all requests to the target contract\n    */\n    fallback() external payable {\n        assert(target.isContract());\n        // execute requested function from target contract using storage of the dispatcher\n        (bool callSuccess,) = target.delegatecall(msg.data);\n        if (callSuccess) {\n            // copy result of the request to the return data\n            // we can use the second return value from `delegatecall` (bytes memory)\n            // but it will consume a little more gas\n            assembly {\n                returndatacopy(0x0, 0x0, returndatasize())\n                return(0x0, returndatasize())\n            }\n        } else {\n            revert();\n        }\n    }\n\n}\n"},"Ownable.sol":{"content":"// SPDX-License-Identifier: MIT\n\npragma solidity ^0.7.0;\n\n\n/**\n * @title Ownable\n * @dev The Ownable contract has an owner address, and provides basic authorization control\n * functions, this simplifies the implementation of \"user permissions\".\n */\nabstract contract Ownable {\n    address private _owner;\n\n    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\n\n    /**\n     * @dev The Ownable constructor sets the original `owner` of the contract to the sender\n     * account.\n     */\n    constructor () {\n        _owner = msg.sender;\n        emit OwnershipTransferred(address(0), _owner);\n    }\n\n    /**\n     * @return the address of the owner.\n     */\n    function owner() public view returns (address) {\n        return _owner;\n    }\n\n    /**\n     * @dev Throws if called by any account other than the owner.\n     */\n    modifier onlyOwner() {\n        require(isOwner());\n        _;\n    }\n\n    /**\n     * @return true if `msg.sender` is the owner of the contract.\n     */\n    function isOwner() public view returns (bool) {\n        return msg.sender == _owner;\n    }\n\n    /**\n     * @dev Allows the current owner to relinquish control of the contract.\n     * @notice Renouncing to ownership will leave the contract without an owner.\n     * It will not be possible to call the functions with the `onlyOwner`\n     * modifier anymore.\n     */\n    function renounceOwnership() public onlyOwner {\n        emit OwnershipTransferred(_owner, address(0));\n        _owner = address(0);\n    }\n\n    /**\n     * @dev Allows the current owner to transfer control of the contract to a newOwner.\n     * @param newOwner The address to transfer ownership to.\n     */\n    function transferOwnership(address newOwner) public onlyOwner {\n        _transferOwnership(newOwner);\n    }\n\n    /**\n     * @dev Transfers control of the contract to a newOwner.\n     * @param newOwner The address to transfer ownership to.\n     */\n    function _transferOwnership(address newOwner) internal {\n        require(newOwner != address(0));\n        emit OwnershipTransferred(_owner, newOwner);\n        _owner = newOwner;\n    }\n}\n"},"Upgradeable.sol":{"content":"// SPDX-License-Identifier: AGPL-3.0-or-later\n\npragma solidity ^0.7.0;\n\n\nimport \"./Ownable.sol\";\n\n\n/**\n* @notice Base contract for upgradeable contract\n* @dev Inherited contract should implement verifyState(address) method by checking storage variables\n* (see verifyState(address) in Dispatcher). Also contract should implement finishUpgrade(address)\n* if it is using constructor parameters by coping this parameters to the dispatcher storage\n*/\nabstract contract Upgradeable is Ownable {\n\n    event StateVerified(address indexed testTarget, address sender);\n    event UpgradeFinished(address indexed target, address sender);\n\n    /**\n    * @dev Contracts at the target must reserve the same location in storage for this address as in Dispatcher\n    * Stored data actually lives in the Dispatcher\n    * However the storage layout is specified here in the implementing contracts\n    */\n    address public target;\n\n    /**\n    * @dev Previous contract address (if available). Used for rollback\n    */\n    address public previousTarget;\n\n    /**\n    * @dev Upgrade status. Explicit `uint8` type is used instead of `bool` to save gas by excluding 0 value\n    */\n    uint8 public isUpgrade;\n\n    /**\n    * @dev Guarantees that next slot will be separated from the previous\n    */\n    uint256 stubSlot;\n\n    /**\n    * @dev Constants for `isUpgrade` field\n    */\n    uint8 constant UPGRADE_FALSE = 1;\n    uint8 constant UPGRADE_TRUE = 2;\n\n    /**\n    * @dev Checks that function executed while upgrading\n    * Recommended to add to `verifyState` and `finishUpgrade` methods\n    */\n    modifier onlyWhileUpgrading()\n    {\n        require(isUpgrade == UPGRADE_TRUE);\n        _;\n    }\n\n    /**\n    * @dev Method for verifying storage state.\n    * Should check that new target contract returns right storage value\n    */\n    function verifyState(address _testTarget) public virtual onlyWhileUpgrading {\n        emit StateVerified(_testTarget, msg.sender);\n    }\n\n    /**\n    * @dev Copy values from the new target to the current storage\n    * @param _target New target contract address\n    */\n    function finishUpgrade(address _target) public virtual onlyWhileUpgrading {\n        emit UpgradeFinished(_target, msg.sender);\n    }\n\n    /**\n    * @dev Base method to get data\n    * @param _target Target to call\n    * @param _selector Method selector\n    * @param _numberOfArguments Number of used arguments\n    * @param _argument1 First method argument\n    * @param _argument2 Second method argument\n    * @return memoryAddress Address in memory where the data is located\n    */\n    function delegateGetData(\n        address _target,\n        bytes4 _selector,\n        uint8 _numberOfArguments,\n        bytes32 _argument1,\n        bytes32 _argument2\n    )\n        internal returns (bytes32 memoryAddress)\n    {\n        assembly {\n            memoryAddress := mload(0x40)\n            mstore(memoryAddress, _selector)\n            if gt(_numberOfArguments, 0) {\n                mstore(add(memoryAddress, 0x04), _argument1)\n            }\n            if gt(_numberOfArguments, 1) {\n                mstore(add(memoryAddress, 0x24), _argument2)\n            }\n            switch delegatecall(gas(), _target, memoryAddress, add(0x04, mul(0x20, _numberOfArguments)), 0, 0)\n                case 0 {\n                    revert(memoryAddress, 0)\n                }\n                default {\n                    returndatacopy(memoryAddress, 0x0, returndatasize())\n                }\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" without parameters.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(address _target, bytes4 _selector)\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 0, 0, 0);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" with one parameter.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(address _target, bytes4 _selector, bytes32 _argument)\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 1, _argument, 0);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n\n    /**\n    * @dev Call \"getter\" with two parameters.\n    * Result should not exceed 32 bytes\n    */\n    function delegateGet(\n        address _target,\n        bytes4 _selector,\n        bytes32 _argument1,\n        bytes32 _argument2\n    )\n        internal returns (uint256 result)\n    {\n        bytes32 memoryAddress = delegateGetData(_target, _selector, 2, _argument1, _argument2);\n        assembly {\n            result := mload(memoryAddress)\n        }\n    }\n}\n"}}

        File 4 of 4: PolicyManager
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "./lib/ReEncryptionValidator.sol";
        import "./lib/SignatureVerifier.sol";
        import "./StakingEscrow.sol";
        import "./proxy/Upgradeable.sol";
        import "../zeppelin/math/SafeMath.sol";
        import "../zeppelin/math/Math.sol";
        /**
        * @notice Supervises stakers' behavior and punishes when something's wrong.
        * @dev |v2.1.2|
        */
        contract Adjudicator is Upgradeable {
            using SafeMath for uint256;
            using UmbralDeserializer for bytes;
            event CFragEvaluated(
                bytes32 indexed evaluationHash,
                address indexed investigator,
                bool correctness
            );
            event IncorrectCFragVerdict(
                bytes32 indexed evaluationHash,
                address indexed worker,
                address indexed staker
            );
            // used only for upgrading
            bytes32 constant RESERVED_CAPSULE_AND_CFRAG_BYTES = bytes32(0);
            address constant RESERVED_ADDRESS = address(0);
            StakingEscrow public immutable escrow;
            SignatureVerifier.HashAlgorithm public immutable hashAlgorithm;
            uint256 public immutable basePenalty;
            uint256 public immutable penaltyHistoryCoefficient;
            uint256 public immutable percentagePenaltyCoefficient;
            uint256 public immutable rewardCoefficient;
            mapping (address => uint256) public penaltyHistory;
            mapping (bytes32 => bool) public evaluatedCFrags;
            /**
            * @param _escrow Escrow contract
            * @param _hashAlgorithm Hashing algorithm
            * @param _basePenalty Base for the penalty calculation
            * @param _penaltyHistoryCoefficient Coefficient for calculating the penalty depending on the history
            * @param _percentagePenaltyCoefficient Coefficient for calculating the percentage penalty
            * @param _rewardCoefficient Coefficient for calculating the reward
            */
            constructor(
                StakingEscrow _escrow,
                SignatureVerifier.HashAlgorithm _hashAlgorithm,
                uint256 _basePenalty,
                uint256 _penaltyHistoryCoefficient,
                uint256 _percentagePenaltyCoefficient,
                uint256 _rewardCoefficient
            ) {
                // Sanity checks.
                require(_escrow.secondsPerPeriod() > 0 &&  // This contract has an escrow, and it's not the null address.
                    // The reward and penalty coefficients are set.
                    _percentagePenaltyCoefficient != 0 &&
                    _rewardCoefficient != 0);
                escrow = _escrow;
                hashAlgorithm = _hashAlgorithm;
                basePenalty = _basePenalty;
                percentagePenaltyCoefficient = _percentagePenaltyCoefficient;
                penaltyHistoryCoefficient = _penaltyHistoryCoefficient;
                rewardCoefficient = _rewardCoefficient;
            }
            /**
            * @notice Submit proof that a worker created wrong CFrag
            * @param _capsuleBytes Serialized capsule
            * @param _cFragBytes Serialized CFrag
            * @param _cFragSignature Signature of CFrag by worker
            * @param _taskSignature Signature of task specification by Bob
            * @param _requesterPublicKey Bob's signing public key, also known as "stamp"
            * @param _workerPublicKey Worker's signing public key, also known as "stamp"
            * @param _workerIdentityEvidence Signature of worker's public key by worker's eth-key
            * @param _preComputedData Additional pre-computed data for CFrag correctness verification
            */
            function evaluateCFrag(
                bytes memory _capsuleBytes,
                bytes memory _cFragBytes,
                bytes memory _cFragSignature,
                bytes memory _taskSignature,
                bytes memory _requesterPublicKey,
                bytes memory _workerPublicKey,
                bytes memory _workerIdentityEvidence,
                bytes memory _preComputedData
            )
                public
            {
                // 1. Check that CFrag is not evaluated yet
                bytes32 evaluationHash = SignatureVerifier.hash(
                    abi.encodePacked(_capsuleBytes, _cFragBytes), hashAlgorithm);
                require(!evaluatedCFrags[evaluationHash], "This CFrag has already been evaluated.");
                evaluatedCFrags[evaluationHash] = true;
                // 2. Verify correctness of re-encryption
                bool cFragIsCorrect = ReEncryptionValidator.validateCFrag(_capsuleBytes, _cFragBytes, _preComputedData);
                emit CFragEvaluated(evaluationHash, msg.sender, cFragIsCorrect);
                // 3. Verify associated public keys and signatures
                require(ReEncryptionValidator.checkSerializedCoordinates(_workerPublicKey),
                        "Staker's public key is invalid");
                require(ReEncryptionValidator.checkSerializedCoordinates(_requesterPublicKey),
                        "Requester's public key is invalid");
                UmbralDeserializer.PreComputedData memory precomp = _preComputedData.toPreComputedData();
                // Verify worker's signature of CFrag
                require(SignatureVerifier.verify(
                        _cFragBytes,
                        abi.encodePacked(_cFragSignature, precomp.lostBytes[1]),
                        _workerPublicKey,
                        hashAlgorithm),
                        "CFrag signature is invalid"
                );
                // Verify worker's signature of taskSignature and that it corresponds to cfrag.proof.metadata
                UmbralDeserializer.CapsuleFrag memory cFrag = _cFragBytes.toCapsuleFrag();
                require(SignatureVerifier.verify(
                        _taskSignature,
                        abi.encodePacked(cFrag.proof.metadata, precomp.lostBytes[2]),
                        _workerPublicKey,
                        hashAlgorithm),
                        "Task signature is invalid"
                );
                // Verify that _taskSignature is bob's signature of the task specification.
                // A task specification is: capsule + ursula pubkey + alice address + blockhash
                bytes32 stampXCoord;
                assembly {
                    stampXCoord := mload(add(_workerPublicKey, 32))
                }
                bytes memory stamp = abi.encodePacked(precomp.lostBytes[4], stampXCoord);
                require(SignatureVerifier.verify(
                        abi.encodePacked(_capsuleBytes,
                                         stamp,
                                         _workerIdentityEvidence,
                                         precomp.alicesKeyAsAddress,
                                         bytes32(0)),
                        abi.encodePacked(_taskSignature, precomp.lostBytes[3]),
                        _requesterPublicKey,
                        hashAlgorithm),
                        "Specification signature is invalid"
                );
                // 4. Extract worker address from stamp signature.
                address worker = SignatureVerifier.recover(
                    SignatureVerifier.hashEIP191(stamp, byte(0x45)), // Currently, we use version E (0x45) of EIP191 signatures
                    _workerIdentityEvidence);
                address staker = escrow.stakerFromWorker(worker);
                require(staker != address(0), "Worker must be related to a staker");
                // 5. Check that staker can be slashed
                uint256 stakerValue = escrow.getAllTokens(staker);
                require(stakerValue > 0, "Staker has no tokens");
                // 6. If CFrag was incorrect, slash staker
                if (!cFragIsCorrect) {
                    (uint256 penalty, uint256 reward) = calculatePenaltyAndReward(staker, stakerValue);
                    escrow.slashStaker(staker, penalty, msg.sender, reward);
                    emit IncorrectCFragVerdict(evaluationHash, worker, staker);
                }
            }
            /**
            * @notice Calculate penalty to the staker and reward to the investigator
            * @param _staker Staker's address
            * @param _stakerValue Amount of tokens that belong to the staker
            */
            function calculatePenaltyAndReward(address _staker, uint256 _stakerValue)
                internal returns (uint256 penalty, uint256 reward)
            {
                penalty = basePenalty.add(penaltyHistoryCoefficient.mul(penaltyHistory[_staker]));
                penalty = Math.min(penalty, _stakerValue.div(percentagePenaltyCoefficient));
                reward = penalty.div(rewardCoefficient);
                // TODO add maximum condition or other overflow protection or other penalty condition (#305?)
                penaltyHistory[_staker] = penaltyHistory[_staker].add(1);
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
            function verifyState(address _testTarget) public override virtual {
                super.verifyState(_testTarget);
                bytes32 evaluationCFragHash = SignatureVerifier.hash(
                    abi.encodePacked(RESERVED_CAPSULE_AND_CFRAG_BYTES), SignatureVerifier.HashAlgorithm.SHA256);
                require(delegateGet(_testTarget, this.evaluatedCFrags.selector, evaluationCFragHash) ==
                    (evaluatedCFrags[evaluationCFragHash] ? 1 : 0));
                require(delegateGet(_testTarget, this.penaltyHistory.selector, bytes32(bytes20(RESERVED_ADDRESS))) ==
                    penaltyHistory[RESERVED_ADDRESS]);
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade`
            function finishUpgrade(address _target) public override virtual {
                super.finishUpgrade(_target);
                // preparation for the verifyState method
                bytes32 evaluationCFragHash = SignatureVerifier.hash(
                    abi.encodePacked(RESERVED_CAPSULE_AND_CFRAG_BYTES), SignatureVerifier.HashAlgorithm.SHA256);
                evaluatedCFrags[evaluationCFragHash] = true;
                penaltyHistory[RESERVED_ADDRESS] = 123;
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "./UmbralDeserializer.sol";
        import "./SignatureVerifier.sol";
        /**
        * @notice Validates re-encryption correctness.
        */
        library ReEncryptionValidator {
            using UmbralDeserializer for bytes;
            //------------------------------//
            //   Umbral-specific constants  //
            //------------------------------//
            // See parameter `u` of `UmbralParameters` class in pyUmbral
            // https://github.com/nucypher/pyUmbral/blob/master/umbral/params.py
            uint8 public constant UMBRAL_PARAMETER_U_SIGN = 0x02;
            uint256 public constant UMBRAL_PARAMETER_U_XCOORD = 0x03c98795773ff1c241fc0b1cced85e80f8366581dda5c9452175ebd41385fa1f;
            uint256 public constant UMBRAL_PARAMETER_U_YCOORD = 0x7880ed56962d7c0ae44d6f14bb53b5fe64b31ea44a41d0316f3a598778f0f936;
            //------------------------------//
            // SECP256K1-specific constants //
            //------------------------------//
            // Base field order
            uint256 constant FIELD_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F;
            // -2 mod FIELD_ORDER
            uint256 constant MINUS_2 = 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2d;
            // (-1/2) mod FIELD_ORDER
            uint256 constant MINUS_ONE_HALF = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffff7ffffe17;
            //
            /**
            * @notice Check correctness of re-encryption
            * @param _capsuleBytes Capsule
            * @param _cFragBytes Capsule frag
            * @param _precomputedBytes Additional precomputed data
            */
            function validateCFrag(
                bytes memory _capsuleBytes,
                bytes memory _cFragBytes,
                bytes memory _precomputedBytes
            )
                internal pure returns (bool)
            {
                UmbralDeserializer.Capsule memory _capsule = _capsuleBytes.toCapsule();
                UmbralDeserializer.CapsuleFrag memory _cFrag = _cFragBytes.toCapsuleFrag();
                UmbralDeserializer.PreComputedData memory _precomputed = _precomputedBytes.toPreComputedData();
                // Extract Alice's address and check that it corresponds to the one provided
                address alicesAddress = SignatureVerifier.recover(
                    _precomputed.hashedKFragValidityMessage,
                    abi.encodePacked(_cFrag.proof.kFragSignature, _precomputed.lostBytes[0])
                );
                require(alicesAddress == _precomputed.alicesKeyAsAddress, "Bad KFrag signature");
                // Compute proof's challenge scalar h, used in all ZKP verification equations
                uint256 h = computeProofChallengeScalar(_capsule, _cFrag);
                //////
                // Verifying 1st equation: z*E == h*E_1 + E_2
                //////
                // Input validation: E
                require(checkCompressedPoint(
                    _capsule.pointE.sign,
                    _capsule.pointE.xCoord,
                    _precomputed.pointEyCoord),
                    "Precomputed Y coordinate of E doesn't correspond to compressed E point"
                );
                // Input validation: z*E
                require(isOnCurve(_precomputed.pointEZxCoord, _precomputed.pointEZyCoord),
                        "Point zE is not a valid EC point"
                );
                require(ecmulVerify(
                    _capsule.pointE.xCoord,         // E_x
                    _precomputed.pointEyCoord,      // E_y
                    _cFrag.proof.bnSig,             // z
                    _precomputed.pointEZxCoord,     // zE_x
                    _precomputed.pointEZyCoord),    // zE_y
                    "Precomputed z*E value is incorrect"
                );
                // Input validation: E1
                require(checkCompressedPoint(
                    _cFrag.pointE1.sign,          // E1_sign
                    _cFrag.pointE1.xCoord,        // E1_x
                    _precomputed.pointE1yCoord),  // E1_y
                    "Precomputed Y coordinate of E1 doesn't correspond to compressed E1 point"
                );
                // Input validation: h*E1
                require(isOnCurve(_precomputed.pointE1HxCoord, _precomputed.pointE1HyCoord),
                        "Point h*E1 is not a valid EC point"
                );
                require(ecmulVerify(
                    _cFrag.pointE1.xCoord,          // E1_x
                    _precomputed.pointE1yCoord,     // E1_y
                    h,
                    _precomputed.pointE1HxCoord,    // hE1_x
                    _precomputed.pointE1HyCoord),   // hE1_y
                    "Precomputed h*E1 value is incorrect"
                );
                // Input validation: E2
                require(checkCompressedPoint(
                    _cFrag.proof.pointE2.sign,        // E2_sign
                    _cFrag.proof.pointE2.xCoord,      // E2_x
                    _precomputed.pointE2yCoord),      // E2_y
                    "Precomputed Y coordinate of E2 doesn't correspond to compressed E2 point"
                );
                bool equation_holds = eqAffineJacobian(
                    [_precomputed.pointEZxCoord,  _precomputed.pointEZyCoord],
                    addAffineJacobian(
                        [_cFrag.proof.pointE2.xCoord, _precomputed.pointE2yCoord],
                        [_precomputed.pointE1HxCoord, _precomputed.pointE1HyCoord]
                    )
                );
                if (!equation_holds){
                    return false;
                }
                //////
                // Verifying 2nd equation: z*V == h*V_1 + V_2
                //////
                // Input validation: V
                require(checkCompressedPoint(
                    _capsule.pointV.sign,
                    _capsule.pointV.xCoord,
                    _precomputed.pointVyCoord),
                    "Precomputed Y coordinate of V doesn't correspond to compressed V point"
                );
                // Input validation: z*V
                require(isOnCurve(_precomputed.pointVZxCoord, _precomputed.pointVZyCoord),
                        "Point zV is not a valid EC point"
                );
                require(ecmulVerify(
                    _capsule.pointV.xCoord,         // V_x
                    _precomputed.pointVyCoord,      // V_y
                    _cFrag.proof.bnSig,             // z
                    _precomputed.pointVZxCoord,     // zV_x
                    _precomputed.pointVZyCoord),    // zV_y
                    "Precomputed z*V value is incorrect"
                );
                // Input validation: V1
                require(checkCompressedPoint(
                    _cFrag.pointV1.sign,         // V1_sign
                    _cFrag.pointV1.xCoord,       // V1_x
                    _precomputed.pointV1yCoord), // V1_y
                    "Precomputed Y coordinate of V1 doesn't correspond to compressed V1 point"
                );
                // Input validation: h*V1
                require(isOnCurve(_precomputed.pointV1HxCoord, _precomputed.pointV1HyCoord),
                    "Point h*V1 is not a valid EC point"
                );
                require(ecmulVerify(
                    _cFrag.pointV1.xCoord,          // V1_x
                    _precomputed.pointV1yCoord,     // V1_y
                    h,
                    _precomputed.pointV1HxCoord,    // h*V1_x
                    _precomputed.pointV1HyCoord),   // h*V1_y
                    "Precomputed h*V1 value is incorrect"
                );
                // Input validation: V2
                require(checkCompressedPoint(
                    _cFrag.proof.pointV2.sign,        // V2_sign
                    _cFrag.proof.pointV2.xCoord,      // V2_x
                    _precomputed.pointV2yCoord),      // V2_y
                    "Precomputed Y coordinate of V2 doesn't correspond to compressed V2 point"
                );
                equation_holds = eqAffineJacobian(
                    [_precomputed.pointVZxCoord,  _precomputed.pointVZyCoord],
                    addAffineJacobian(
                        [_cFrag.proof.pointV2.xCoord, _precomputed.pointV2yCoord],
                        [_precomputed.pointV1HxCoord, _precomputed.pointV1HyCoord]
                    )
                );
                if (!equation_holds){
                    return false;
                }
                //////
                // Verifying 3rd equation: z*U == h*U_1 + U_2
                //////
                // We don't have to validate U since it's fixed and hard-coded
                // Input validation: z*U
                require(isOnCurve(_precomputed.pointUZxCoord, _precomputed.pointUZyCoord),
                        "Point z*U is not a valid EC point"
                );
                require(ecmulVerify(
                    UMBRAL_PARAMETER_U_XCOORD,      // U_x
                    UMBRAL_PARAMETER_U_YCOORD,      // U_y
                    _cFrag.proof.bnSig,             // z
                    _precomputed.pointUZxCoord,     // zU_x
                    _precomputed.pointUZyCoord),    // zU_y
                    "Precomputed z*U value is incorrect"
                );
                // Input validation: U1  (a.k.a. KFragCommitment)
                require(checkCompressedPoint(
                    _cFrag.proof.pointKFragCommitment.sign,     // U1_sign
                    _cFrag.proof.pointKFragCommitment.xCoord,   // U1_x
                    _precomputed.pointU1yCoord),                // U1_y
                    "Precomputed Y coordinate of U1 doesn't correspond to compressed U1 point"
                );
                // Input validation: h*U1
                require(isOnCurve(_precomputed.pointU1HxCoord, _precomputed.pointU1HyCoord),
                        "Point h*U1 is not a valid EC point"
                );
                require(ecmulVerify(
                    _cFrag.proof.pointKFragCommitment.xCoord,   // U1_x
                    _precomputed.pointU1yCoord,                 // U1_y
                    h,
                    _precomputed.pointU1HxCoord,    // h*V1_x
                    _precomputed.pointU1HyCoord),   // h*V1_y
                    "Precomputed h*V1 value is incorrect"
                );
                // Input validation: U2  (a.k.a. KFragPok ("proof of knowledge"))
                require(checkCompressedPoint(
                    _cFrag.proof.pointKFragPok.sign,    // U2_sign
                    _cFrag.proof.pointKFragPok.xCoord,  // U2_x
                    _precomputed.pointU2yCoord),        // U2_y
                    "Precomputed Y coordinate of U2 doesn't correspond to compressed U2 point"
                );
                equation_holds = eqAffineJacobian(
                    [_precomputed.pointUZxCoord,  _precomputed.pointUZyCoord],
                    addAffineJacobian(
                        [_cFrag.proof.pointKFragPok.xCoord, _precomputed.pointU2yCoord],
                        [_precomputed.pointU1HxCoord, _precomputed.pointU1HyCoord]
                    )
                );
                return equation_holds;
            }
            function computeProofChallengeScalar(
                UmbralDeserializer.Capsule memory _capsule,
                UmbralDeserializer.CapsuleFrag memory _cFrag
            ) internal pure returns (uint256) {
                // Compute h = hash_to_bignum(e, e1, e2, v, v1, v2, u, u1, u2, metadata)
                bytes memory hashInput = abi.encodePacked(
                    // Point E
                    _capsule.pointE.sign,
                    _capsule.pointE.xCoord,
                    // Point E1
                    _cFrag.pointE1.sign,
                    _cFrag.pointE1.xCoord,
                    // Point E2
                    _cFrag.proof.pointE2.sign,
                    _cFrag.proof.pointE2.xCoord
                );
                hashInput = abi.encodePacked(
                    hashInput,
                    // Point V
                    _capsule.pointV.sign,
                    _capsule.pointV.xCoord,
                    // Point V1
                    _cFrag.pointV1.sign,
                    _cFrag.pointV1.xCoord,
                    // Point V2
                    _cFrag.proof.pointV2.sign,
                    _cFrag.proof.pointV2.xCoord
                );
                hashInput = abi.encodePacked(
                    hashInput,
                    // Point U
                    bytes1(UMBRAL_PARAMETER_U_SIGN),
                    bytes32(UMBRAL_PARAMETER_U_XCOORD),
                    // Point U1
                    _cFrag.proof.pointKFragCommitment.sign,
                    _cFrag.proof.pointKFragCommitment.xCoord,
                    // Point U2
                    _cFrag.proof.pointKFragPok.sign,
                    _cFrag.proof.pointKFragPok.xCoord,
                    // Re-encryption metadata
                    _cFrag.proof.metadata
                );
                uint256 h = extendedKeccakToBN(hashInput);
                return h;
            }
            function extendedKeccakToBN (bytes memory _data) internal pure returns (uint256) {
                bytes32 upper;
                bytes32 lower;
                // Umbral prepends to the data a customization string of 64-bytes.
                // In the case of hash_to_curvebn is 'hash_to_curvebn', padded with zeroes.
                bytes memory input = abi.encodePacked(bytes32("hash_to_curvebn"), bytes32(0x00), _data);
                (upper, lower) = (keccak256(abi.encodePacked(uint8(0x00), input)),
                                  keccak256(abi.encodePacked(uint8(0x01), input)));
                // Let n be the order of secp256k1's group (n = 2^256 - 0x1000003D1)
                // n_minus_1 = n - 1
                // delta = 2^256 mod n_minus_1
                uint256 delta = 0x14551231950b75fc4402da1732fc9bec0;
                uint256 n_minus_1 = 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140;
                uint256 upper_half = mulmod(uint256(upper), delta, n_minus_1);
                return 1 + addmod(upper_half, uint256(lower), n_minus_1);
            }
            /// @notice Tests if a compressed point is valid, wrt to its corresponding Y coordinate
            /// @param _pointSign The sign byte from the compressed notation: 0x02 if the Y coord is even; 0x03 otherwise
            /// @param _pointX The X coordinate of an EC point in affine representation
            /// @param _pointY The Y coordinate of an EC point in affine representation
            /// @return true iff _pointSign and _pointX are the compressed representation of (_pointX, _pointY)
        \tfunction checkCompressedPoint(
        \t\tuint8 _pointSign,
        \t\tuint256 _pointX,
        \t\tuint256 _pointY
        \t) internal pure returns(bool) {
        \t\tbool correct_sign = _pointY % 2 == _pointSign - 2;
        \t\treturn correct_sign && isOnCurve(_pointX, _pointY);
        \t}
            /// @notice Tests if the given serialized coordinates represent a valid EC point
            /// @param _coords The concatenation of serialized X and Y coordinates
            /// @return true iff coordinates X and Y are a valid point
            function checkSerializedCoordinates(bytes memory _coords) internal pure returns(bool) {
                require(_coords.length == 64, "Serialized coordinates should be 64 B");
                uint256 coordX;
                uint256 coordY;
                assembly {
                    coordX := mload(add(_coords, 32))
                    coordY := mload(add(_coords, 64))
                }
        \t\treturn isOnCurve(coordX, coordY);
        \t}
            /// @notice Tests if a point is on the secp256k1 curve
            /// @param Px The X coordinate of an EC point in affine representation
            /// @param Py The Y coordinate of an EC point in affine representation
            /// @return true if (Px, Py) is a valid secp256k1 point; false otherwise
            function isOnCurve(uint256 Px, uint256 Py) internal pure returns (bool) {
                uint256 p = FIELD_ORDER;
                if (Px >= p || Py >= p){
                    return false;
                }
                uint256 y2 = mulmod(Py, Py, p);
                uint256 x3_plus_7 = addmod(mulmod(mulmod(Px, Px, p), Px, p), 7, p);
                return y2 == x3_plus_7;
            }
            // https://ethresear.ch/t/you-can-kinda-abuse-ecrecover-to-do-ecmul-in-secp256k1-today/2384/4
            function ecmulVerify(
            \tuint256 x1,
            \tuint256 y1,
            \tuint256 scalar,
            \tuint256 qx,
            \tuint256 qy
            ) internal pure returns(bool) {
        \t    uint256 curve_order = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141;
        \t    address signer = ecrecover(0, uint8(27 + (y1 % 2)), bytes32(x1), bytes32(mulmod(scalar, x1, curve_order)));
        \t    address xyAddress = address(uint256(keccak256(abi.encodePacked(qx, qy))) & 0x00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF);
        \t    return xyAddress == signer;
        \t}
            /// @notice Equality test of two points, in affine and Jacobian coordinates respectively
            /// @param P An EC point in affine coordinates
            /// @param Q An EC point in Jacobian coordinates
            /// @return true if P and Q represent the same point in affine coordinates; false otherwise
            function eqAffineJacobian(
            \tuint256[2] memory P,
            \tuint256[3] memory Q
            ) internal pure returns(bool){
                uint256 Qz = Q[2];
                if(Qz == 0){
                    return false;       // Q is zero but P isn't.
                }
                uint256 p = FIELD_ORDER;
                uint256 Q_z_squared = mulmod(Qz, Qz, p);
                return mulmod(P[0], Q_z_squared, p) == Q[0] && mulmod(P[1], mulmod(Q_z_squared, Qz, p), p) == Q[1];
            }
            /// @notice Adds two points in affine coordinates, with the result in Jacobian
            /// @dev Based on the addition formulas from http://www.hyperelliptic.org/EFD/g1p/auto-code/shortw/jacobian-0/addition/add-2001-b.op3
            /// @param P An EC point in affine coordinates
            /// @param Q An EC point in affine coordinates
            /// @return R An EC point in Jacobian coordinates with the sum, represented by an array of 3 uint256
            function addAffineJacobian(
            \tuint[2] memory P,
            \tuint[2] memory Q
            ) internal pure returns (uint[3] memory R) {
                uint256 p = FIELD_ORDER;
                uint256 a   = P[0];
                uint256 c   = P[1];
                uint256 t0  = Q[0];
                uint256 t1  = Q[1];
                if ((a == t0) && (c == t1)){
                    return doubleJacobian([a, c, 1]);
                }
                uint256 d = addmod(t1, p-c, p); // d = t1 - c
                uint256 b = addmod(t0, p-a, p); // b = t0 - a
                uint256 e = mulmod(b, b, p); // e = b^2
                uint256 f = mulmod(e, b, p);  // f = b^3
                uint256 g = mulmod(a, e, p);
                R[0] = addmod(mulmod(d, d, p), p-addmod(mulmod(2, g, p), f, p), p);
                R[1] = addmod(mulmod(d, addmod(g, p-R[0], p), p), p-mulmod(c, f, p), p);
                R[2] = b;
            }
            /// @notice Point doubling in Jacobian coordinates
            /// @param P An EC point in Jacobian coordinates.
            /// @return Q An EC point in Jacobian coordinates
            function doubleJacobian(uint[3] memory P) internal pure returns (uint[3] memory Q) {
                uint256 z = P[2];
                if (z == 0)
                    return Q;
                uint256 p = FIELD_ORDER;
                uint256 x = P[0];
                uint256 _2y = mulmod(2, P[1], p);
                uint256 _4yy = mulmod(_2y, _2y, p);
                uint256 s = mulmod(_4yy, x, p);
                uint256 m = mulmod(3, mulmod(x, x, p), p);
                uint256 t = addmod(mulmod(m, m, p), mulmod(MINUS_2, s, p),p);
                Q[0] = t;
                Q[1] = addmod(mulmod(m, addmod(s, p - t, p), p), mulmod(MINUS_ONE_HALF, mulmod(_4yy, _4yy, p), p), p);
                Q[2] = mulmod(_2y, z, p);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        /**
        * @notice Deserialization library for Umbral objects
        */
        library UmbralDeserializer {
            struct Point {
                uint8 sign;
                uint256 xCoord;
            }
            struct Capsule {
                Point pointE;
                Point pointV;
                uint256 bnSig;
            }
            struct CorrectnessProof {
                Point pointE2;
                Point pointV2;
                Point pointKFragCommitment;
                Point pointKFragPok;
                uint256 bnSig;
                bytes kFragSignature; // 64 bytes
                bytes metadata; // any length
            }
            struct CapsuleFrag {
                Point pointE1;
                Point pointV1;
                bytes32 kFragId;
                Point pointPrecursor;
                CorrectnessProof proof;
            }
            struct PreComputedData {
                uint256 pointEyCoord;
                uint256 pointEZxCoord;
                uint256 pointEZyCoord;
                uint256 pointE1yCoord;
                uint256 pointE1HxCoord;
                uint256 pointE1HyCoord;
                uint256 pointE2yCoord;
                uint256 pointVyCoord;
                uint256 pointVZxCoord;
                uint256 pointVZyCoord;
                uint256 pointV1yCoord;
                uint256 pointV1HxCoord;
                uint256 pointV1HyCoord;
                uint256 pointV2yCoord;
                uint256 pointUZxCoord;
                uint256 pointUZyCoord;
                uint256 pointU1yCoord;
                uint256 pointU1HxCoord;
                uint256 pointU1HyCoord;
                uint256 pointU2yCoord;
                bytes32 hashedKFragValidityMessage;
                address alicesKeyAsAddress;
                bytes5  lostBytes;
            }
            uint256 constant BIGNUM_SIZE = 32;
            uint256 constant POINT_SIZE = 33;
            uint256 constant SIGNATURE_SIZE = 64;
            uint256 constant CAPSULE_SIZE = 2 * POINT_SIZE + BIGNUM_SIZE;
            uint256 constant CORRECTNESS_PROOF_SIZE = 4 * POINT_SIZE + BIGNUM_SIZE + SIGNATURE_SIZE;
            uint256 constant CAPSULE_FRAG_SIZE = 3 * POINT_SIZE + BIGNUM_SIZE;
            uint256 constant FULL_CAPSULE_FRAG_SIZE = CAPSULE_FRAG_SIZE + CORRECTNESS_PROOF_SIZE;
            uint256 constant PRECOMPUTED_DATA_SIZE = (20 * BIGNUM_SIZE) + 32 + 20 + 5;
            /**
            * @notice Deserialize to capsule (not activated)
            */
            function toCapsule(bytes memory _capsuleBytes)
                internal pure returns (Capsule memory capsule)
            {
                require(_capsuleBytes.length == CAPSULE_SIZE);
                uint256 pointer = getPointer(_capsuleBytes);
                pointer = copyPoint(pointer, capsule.pointE);
                pointer = copyPoint(pointer, capsule.pointV);
                capsule.bnSig = uint256(getBytes32(pointer));
            }
            /**
            * @notice Deserialize to correctness proof
            * @param _pointer Proof bytes memory pointer
            * @param _proofBytesLength Proof bytes length
            */
            function toCorrectnessProof(uint256 _pointer, uint256 _proofBytesLength)
                internal pure returns (CorrectnessProof memory proof)
            {
                require(_proofBytesLength >= CORRECTNESS_PROOF_SIZE);
                _pointer = copyPoint(_pointer, proof.pointE2);
                _pointer = copyPoint(_pointer, proof.pointV2);
                _pointer = copyPoint(_pointer, proof.pointKFragCommitment);
                _pointer = copyPoint(_pointer, proof.pointKFragPok);
                proof.bnSig = uint256(getBytes32(_pointer));
                _pointer += BIGNUM_SIZE;
                proof.kFragSignature = new bytes(SIGNATURE_SIZE);
                // TODO optimize, just two mload->mstore (#1500)
                _pointer = copyBytes(_pointer, proof.kFragSignature, SIGNATURE_SIZE);
                if (_proofBytesLength > CORRECTNESS_PROOF_SIZE) {
                    proof.metadata = new bytes(_proofBytesLength - CORRECTNESS_PROOF_SIZE);
                    copyBytes(_pointer, proof.metadata, proof.metadata.length);
                }
            }
            /**
            * @notice Deserialize to correctness proof
            */
            function toCorrectnessProof(bytes memory _proofBytes)
                internal pure returns (CorrectnessProof memory proof)
            {
                uint256 pointer = getPointer(_proofBytes);
                return toCorrectnessProof(pointer, _proofBytes.length);
            }
            /**
            * @notice Deserialize to CapsuleFrag
            */
            function toCapsuleFrag(bytes memory _cFragBytes)
                internal pure returns (CapsuleFrag memory cFrag)
            {
                uint256 cFragBytesLength = _cFragBytes.length;
                require(cFragBytesLength >= FULL_CAPSULE_FRAG_SIZE);
                uint256 pointer = getPointer(_cFragBytes);
                pointer = copyPoint(pointer, cFrag.pointE1);
                pointer = copyPoint(pointer, cFrag.pointV1);
                cFrag.kFragId = getBytes32(pointer);
                pointer += BIGNUM_SIZE;
                pointer = copyPoint(pointer, cFrag.pointPrecursor);
                cFrag.proof = toCorrectnessProof(pointer, cFragBytesLength - CAPSULE_FRAG_SIZE);
            }
            /**
            * @notice Deserialize to precomputed data
            */
            function toPreComputedData(bytes memory _preComputedData)
                internal pure returns (PreComputedData memory data)
            {
                require(_preComputedData.length == PRECOMPUTED_DATA_SIZE);
                uint256 initial_pointer = getPointer(_preComputedData);
                uint256 pointer = initial_pointer;
                data.pointEyCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointEZxCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointEZyCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointE1yCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointE1HxCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointE1HyCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointE2yCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointVyCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointVZxCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointVZyCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointV1yCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointV1HxCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointV1HyCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointV2yCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointUZxCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointUZyCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointU1yCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointU1HxCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointU1HyCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.pointU2yCoord = uint256(getBytes32(pointer));
                pointer += BIGNUM_SIZE;
                data.hashedKFragValidityMessage = getBytes32(pointer);
                pointer += 32;
                data.alicesKeyAsAddress = address(bytes20(getBytes32(pointer)));
                pointer += 20;
                // Lost bytes: a bytes5 variable holding the following byte values:
                //     0: kfrag signature recovery value v
                //     1: cfrag signature recovery value v
                //     2: metadata signature recovery value v
                //     3: specification signature recovery value v
                //     4: ursula pubkey sign byte
                data.lostBytes = bytes5(getBytes32(pointer));
                pointer += 5;
                require(pointer == initial_pointer + PRECOMPUTED_DATA_SIZE);
            }
            // TODO extract to external library if needed (#1500)
            /**
            * @notice Get the memory pointer for start of array
            */
            function getPointer(bytes memory _bytes) internal pure returns (uint256 pointer) {
                assembly {
                    pointer := add(_bytes, 32) // skip array length
                }
            }
            /**
            * @notice Copy point data from memory in the pointer position
            */
            function copyPoint(uint256 _pointer, Point memory _point)
                internal pure returns (uint256 resultPointer)
            {
                // TODO optimize, copy to point memory directly (#1500)
                uint8 temp;
                uint256 xCoord;
                assembly {
                    temp := byte(0, mload(_pointer))
                    xCoord := mload(add(_pointer, 1))
                }
                _point.sign = temp;
                _point.xCoord = xCoord;
                resultPointer = _pointer + POINT_SIZE;
            }
            /**
            * @notice Read 1 byte from memory in the pointer position
            */
            function getByte(uint256 _pointer) internal pure returns (byte result) {
                bytes32 word;
                assembly {
                    word := mload(_pointer)
                }
                result = word[0];
                return result;
            }
            /**
            * @notice Read 32 bytes from memory in the pointer position
            */
            function getBytes32(uint256 _pointer) internal pure returns (bytes32 result) {
                assembly {
                    result := mload(_pointer)
                }
            }
            /**
            * @notice Copy bytes from the source pointer to the target array
            * @dev Assumes that enough memory has been allocated to store in target.
            * Also assumes that '_target' was the last thing that was allocated
            * @param _bytesPointer Source memory pointer
            * @param _target Target array
            * @param _bytesLength Number of bytes to copy
            */
            function copyBytes(uint256 _bytesPointer, bytes memory _target, uint256 _bytesLength)
                internal
                pure
                returns (uint256 resultPointer)
            {
                // Exploiting the fact that '_target' was the last thing to be allocated,
                // we can write entire words, and just overwrite any excess.
                assembly {
                    // evm operations on words
                    let words := div(add(_bytesLength, 31), 32)
                    let source := _bytesPointer
                    let destination := add(_target, 32)
                    for
                        { let i := 0 } // start at arr + 32 -> first byte corresponds to length
                        lt(i, words)
                        { i := add(i, 1) }
                    {
                        let offset := mul(i, 32)
                        mstore(add(destination, offset), mload(add(source, offset)))
                    }
                    mstore(add(_target, add(32, mload(_target))), 0)
                }
                resultPointer = _bytesPointer + _bytesLength;
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        /**
        * @notice Library to recover address and verify signatures
        * @dev Simple wrapper for `ecrecover`
        */
        library SignatureVerifier {
            enum HashAlgorithm {KECCAK256, SHA256, RIPEMD160}
            // Header for Version E as defined by EIP191. First byte ('E') is also the version
            bytes25 constant EIP191_VERSION_E_HEADER = "Ethereum Signed Message:\
        ";
            /**
            * @notice Recover signer address from hash and signature
            * @param _hash 32 bytes message hash
            * @param _signature Signature of hash - 32 bytes r + 32 bytes s + 1 byte v (could be 0, 1, 27, 28)
            */
            function recover(bytes32 _hash, bytes memory _signature)
                internal
                pure
                returns (address)
            {
                require(_signature.length == 65);
                bytes32 r;
                bytes32 s;
                uint8 v;
                assembly {
                    r := mload(add(_signature, 32))
                    s := mload(add(_signature, 64))
                    v := byte(0, mload(add(_signature, 96)))
                }
                // Version of signature should be 27 or 28, but 0 and 1 are also possible versions
                if (v < 27) {
                    v += 27;
                }
                require(v == 27 || v == 28);
                return ecrecover(_hash, v, r, s);
            }
            /**
            * @notice Transform public key to address
            * @param _publicKey secp256k1 public key
            */
            function toAddress(bytes memory _publicKey) internal pure returns (address) {
                return address(uint160(uint256(keccak256(_publicKey))));
            }
            /**
            * @notice Hash using one of pre built hashing algorithm
            * @param _message Signed message
            * @param _algorithm Hashing algorithm
            */
            function hash(bytes memory _message, HashAlgorithm _algorithm)
                internal
                pure
                returns (bytes32 result)
            {
                if (_algorithm == HashAlgorithm.KECCAK256) {
                    result = keccak256(_message);
                } else if (_algorithm == HashAlgorithm.SHA256) {
                    result = sha256(_message);
                } else {
                    result = ripemd160(_message);
                }
            }
            /**
            * @notice Verify ECDSA signature
            * @dev Uses one of pre built hashing algorithm
            * @param _message Signed message
            * @param _signature Signature of message hash
            * @param _publicKey secp256k1 public key in uncompressed format without prefix byte (64 bytes)
            * @param _algorithm Hashing algorithm
            */
            function verify(
                bytes memory _message,
                bytes memory _signature,
                bytes memory _publicKey,
                HashAlgorithm _algorithm
            )
                internal
                pure
                returns (bool)
            {
                require(_publicKey.length == 64);
                return toAddress(_publicKey) == recover(hash(_message, _algorithm), _signature);
            }
            /**
            * @notice Hash message according to EIP191 signature specification
            * @dev It always assumes Keccak256 is used as hashing algorithm
            * @dev Only supports version 0 and version E (0x45)
            * @param _message Message to sign
            * @param _version EIP191 version to use
            */
            function hashEIP191(
                bytes memory _message,
                byte _version
            )
                internal
                view
                returns (bytes32 result)
            {
                if(_version == byte(0x00)){  // Version 0: Data with intended validator
                    address validator = address(this);
                    return keccak256(abi.encodePacked(byte(0x19), byte(0x00), validator, _message));
                } else if (_version == byte(0x45)){  // Version E: personal_sign messages
                    uint256 length = _message.length;
                    require(length > 0, "Empty message not allowed for version E");
                    // Compute text-encoded length of message
                    uint256 digits = 0;
                    while (length != 0) {
                        digits++;
                        length /= 10;
                    }
                    bytes memory lengthAsText = new bytes(digits);
                    length = _message.length;
                    uint256 index = digits - 1;
                    while (length != 0) {
                        lengthAsText[index--] = byte(uint8(48 + length % 10));
                        length /= 10;
                    }
                    return keccak256(abi.encodePacked(byte(0x19), EIP191_VERSION_E_HEADER, lengthAsText, _message));
                } else {
                    revert("Unsupported EIP191 version");
                }
            }
            /**
            * @notice Verify EIP191 signature
            * @dev It always assumes Keccak256 is used as hashing algorithm
            * @dev Only supports version 0 and version E (0x45)
            * @param _message Signed message
            * @param _signature Signature of message hash
            * @param _publicKey secp256k1 public key in uncompressed format without prefix byte (64 bytes)
            * @param _version EIP191 version to use
            */
            function verifyEIP191(
                bytes memory _message,
                bytes memory _signature,
                bytes memory _publicKey,
                byte _version
            )
                internal
                view
                returns (bool)
            {
                require(_publicKey.length == 64);
                return toAddress(_publicKey) == recover(hashEIP191(_message, _version), _signature);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../aragon/interfaces/IERC900History.sol";
        import "./Issuer.sol";
        import "./lib/Bits.sol";
        import "./lib/Snapshot.sol";
        import "../zeppelin/math/SafeMath.sol";
        import "../zeppelin/token/ERC20/SafeERC20.sol";
        /**
        * @notice PolicyManager interface
        */
        interface PolicyManagerInterface {
            function register(address _node, uint16 _period) external;
            function escrow() external view returns (address);
            function ping(
                address _node,
                uint16 _processedPeriod1,
                uint16 _processedPeriod2,
                uint16 _periodToSetDefault
            ) external;
        }
        /**
        * @notice Adjudicator interface
        */
        interface AdjudicatorInterface {
            function escrow() external view returns (address);
        }
        /**
        * @notice WorkLock interface
        */
        interface WorkLockInterface {
            function escrow() external view returns (address);
        }
        /**
        * @notice Contract holds and locks stakers tokens.
        * Each staker that locks their tokens will receive some compensation
        * @dev |v5.4.4|
        */
        contract StakingEscrow is Issuer, IERC900History {
            using AdditionalMath for uint256;
            using AdditionalMath for uint16;
            using Bits for uint256;
            using SafeMath for uint256;
            using Snapshot for uint128[];
            using SafeERC20 for NuCypherToken;
            event Deposited(address indexed staker, uint256 value, uint16 periods);
            event Locked(address indexed staker, uint256 value, uint16 firstPeriod, uint16 periods);
            event Divided(
                address indexed staker,
                uint256 oldValue,
                uint16 lastPeriod,
                uint256 newValue,
                uint16 periods
            );
            event Merged(address indexed staker, uint256 value1, uint256 value2, uint16 lastPeriod);
            event Prolonged(address indexed staker, uint256 value, uint16 lastPeriod, uint16 periods);
            event Withdrawn(address indexed staker, uint256 value);
            event CommitmentMade(address indexed staker, uint16 indexed period, uint256 value);
            event Minted(address indexed staker, uint16 indexed period, uint256 value);
            event Slashed(address indexed staker, uint256 penalty, address indexed investigator, uint256 reward);
            event ReStakeSet(address indexed staker, bool reStake);
            event ReStakeLocked(address indexed staker, uint16 lockUntilPeriod);
            event WorkerBonded(address indexed staker, address indexed worker, uint16 indexed startPeriod);
            event WorkMeasurementSet(address indexed staker, bool measureWork);
            event WindDownSet(address indexed staker, bool windDown);
            event SnapshotSet(address indexed staker, bool snapshotsEnabled);
            struct SubStakeInfo {
                uint16 firstPeriod;
                uint16 lastPeriod;
                uint16 periods;
                uint128 lockedValue;
            }
            struct Downtime {
                uint16 startPeriod;
                uint16 endPeriod;
            }
            struct StakerInfo {
                uint256 value;
                /*
                * Stores periods that are committed but not yet rewarded.
                * In order to optimize storage, only two values are used instead of an array.
                * commitToNextPeriod() method invokes mint() method so there can only be two committed
                * periods that are not yet rewarded: the current and the next periods.
                */
                uint16 currentCommittedPeriod;
                uint16 nextCommittedPeriod;
                uint16 lastCommittedPeriod;
                uint16 lockReStakeUntilPeriod;
                uint256 completedWork;
                uint16 workerStartPeriod; // period when worker was bonded
                address worker;
                uint256 flags; // uint256 to acquire whole slot and minimize operations on it
                uint256 reservedSlot1;
                uint256 reservedSlot2;
                uint256 reservedSlot3;
                uint256 reservedSlot4;
                uint256 reservedSlot5;
                Downtime[] pastDowntime;
                SubStakeInfo[] subStakes;
                uint128[] history;
            }
            // used only for upgrading
            uint16 internal constant RESERVED_PERIOD = 0;
            uint16 internal constant MAX_CHECKED_VALUES = 5;
            // to prevent high gas consumption in loops for slashing
            uint16 public constant MAX_SUB_STAKES = 30;
            uint16 internal constant MAX_UINT16 = 65535;
            // indices for flags
            uint8 internal constant RE_STAKE_DISABLED_INDEX = 0;
            uint8 internal constant WIND_DOWN_INDEX = 1;
            uint8 internal constant MEASURE_WORK_INDEX = 2;
            uint8 internal constant SNAPSHOTS_DISABLED_INDEX = 3;
            uint16 public immutable minLockedPeriods;
            uint16 public immutable minWorkerPeriods;
            uint256 public immutable minAllowableLockedTokens;
            uint256 public immutable maxAllowableLockedTokens;
            bool public immutable isTestContract;
            mapping (address => StakerInfo) public stakerInfo;
            address[] public stakers;
            mapping (address => address) public stakerFromWorker;
            mapping (uint16 => uint256) public lockedPerPeriod;
            uint128[] public balanceHistory;
            PolicyManagerInterface public policyManager;
            AdjudicatorInterface public adjudicator;
            WorkLockInterface public workLock;
            /**
            * @notice Constructor sets address of token contract and coefficients for minting
            * @param _token Token contract
            * @param _hoursPerPeriod Size of period in hours
            * @param _issuanceDecayCoefficient (d) Coefficient which modifies the rate at which the maximum issuance decays,
            * only applicable to Phase 2. d = 365 * half-life / LOG2 where default half-life = 2.
            * See Equation 10 in Staking Protocol & Economics paper
            * @param _lockDurationCoefficient1 (k1) Numerator of the coefficient which modifies the extent
            * to which a stake's lock duration affects the subsidy it receives. Affects stakers differently.
            * Applicable to Phase 1 and Phase 2. k1 = k2 * small_stake_multiplier where default small_stake_multiplier = 0.5.
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _lockDurationCoefficient2 (k2) Denominator of the coefficient which modifies the extent
            * to which a stake's lock duration affects the subsidy it receives. Affects stakers differently.
            * Applicable to Phase 1 and Phase 2. k2 = maximum_rewarded_periods / (1 - small_stake_multiplier)
            * where default maximum_rewarded_periods = 365 and default small_stake_multiplier = 0.5.
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _maximumRewardedPeriods (kmax) Number of periods beyond which a stake's lock duration
            * no longer increases the subsidy it receives. kmax = reward_saturation * 365 where default reward_saturation = 1.
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _firstPhaseTotalSupply Total supply for the first phase
            * @param _firstPhaseMaxIssuance (Imax) Maximum number of new tokens minted per period during Phase 1.
            * See Equation 7 in Staking Protocol & Economics paper.
            * @param _minLockedPeriods Min amount of periods during which tokens can be locked
            * @param _minAllowableLockedTokens Min amount of tokens that can be locked
            * @param _maxAllowableLockedTokens Max amount of tokens that can be locked
            * @param _minWorkerPeriods Min amount of periods while a worker can't be changed
            * @param _isTestContract True if contract is only for tests
            */
            constructor(
                NuCypherToken _token,
                uint32 _hoursPerPeriod,
                uint256 _issuanceDecayCoefficient,
                uint256 _lockDurationCoefficient1,
                uint256 _lockDurationCoefficient2,
                uint16 _maximumRewardedPeriods,
                uint256 _firstPhaseTotalSupply,
                uint256 _firstPhaseMaxIssuance,
                uint16 _minLockedPeriods,
                uint256 _minAllowableLockedTokens,
                uint256 _maxAllowableLockedTokens,
                uint16 _minWorkerPeriods,
                bool _isTestContract
            )
                Issuer(
                    _token,
                    _hoursPerPeriod,
                    _issuanceDecayCoefficient,
                    _lockDurationCoefficient1,
                    _lockDurationCoefficient2,
                    _maximumRewardedPeriods,
                    _firstPhaseTotalSupply,
                    _firstPhaseMaxIssuance
                )
            {
                // constant `1` in the expression `_minLockedPeriods > 1` uses to simplify the `lock` method
                require(_minLockedPeriods > 1 && _maxAllowableLockedTokens != 0);
                minLockedPeriods = _minLockedPeriods;
                minAllowableLockedTokens = _minAllowableLockedTokens;
                maxAllowableLockedTokens = _maxAllowableLockedTokens;
                minWorkerPeriods = _minWorkerPeriods;
                isTestContract = _isTestContract;
            }
            /**
            * @dev Checks the existence of a staker in the contract
            */
            modifier onlyStaker()
            {
                StakerInfo storage info = stakerInfo[msg.sender];
                require(info.value > 0 || info.nextCommittedPeriod != 0);
                _;
            }
            //------------------------Initialization------------------------
            /**
            * @notice Set policy manager address
            */
            function setPolicyManager(PolicyManagerInterface _policyManager) external onlyOwner {
                // Policy manager can be set only once
                require(address(policyManager) == address(0));
                // This escrow must be the escrow for the new policy manager
                require(_policyManager.escrow() == address(this));
                policyManager = _policyManager;
            }
            /**
            * @notice Set adjudicator address
            */
            function setAdjudicator(AdjudicatorInterface _adjudicator) external onlyOwner {
                // Adjudicator can be set only once
                require(address(adjudicator) == address(0));
                // This escrow must be the escrow for the new adjudicator
                require(_adjudicator.escrow() == address(this));
                adjudicator = _adjudicator;
            }
            /**
            * @notice Set worklock address
            */
            function setWorkLock(WorkLockInterface _workLock) external onlyOwner {
                // WorkLock can be set only once
                require(address(workLock) == address(0) || isTestContract);
                // This escrow must be the escrow for the new worklock
                require(_workLock.escrow() == address(this));
                workLock = _workLock;
            }
            //------------------------Main getters------------------------
            /**
            * @notice Get all tokens belonging to the staker
            */
            function getAllTokens(address _staker) external view returns (uint256) {
                return stakerInfo[_staker].value;
            }
            /**
            * @notice Get all flags for the staker
            */
            function getFlags(address _staker)
                external view returns (
                    bool windDown,
                    bool reStake,
                    bool measureWork,
                    bool snapshots
                )
            {
                StakerInfo storage info = stakerInfo[_staker];
                windDown = info.flags.bitSet(WIND_DOWN_INDEX);
                reStake = !info.flags.bitSet(RE_STAKE_DISABLED_INDEX);
                measureWork = info.flags.bitSet(MEASURE_WORK_INDEX);
                snapshots = !info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX);
            }
            /**
            * @notice Get the start period. Use in the calculation of the last period of the sub stake
            * @param _info Staker structure
            * @param _currentPeriod Current period
            */
            function getStartPeriod(StakerInfo storage _info, uint16 _currentPeriod)
                internal view returns (uint16)
            {
                // if the next period (after current) is committed
                if (_info.flags.bitSet(WIND_DOWN_INDEX) && _info.nextCommittedPeriod > _currentPeriod) {
                    return _currentPeriod + 1;
                }
                return _currentPeriod;
            }
            /**
            * @notice Get the last period of the sub stake
            * @param _subStake Sub stake structure
            * @param _startPeriod Pre-calculated start period
            */
            function getLastPeriodOfSubStake(SubStakeInfo storage _subStake, uint16 _startPeriod)
                internal view returns (uint16)
            {
                if (_subStake.lastPeriod != 0) {
                    return _subStake.lastPeriod;
                }
                uint32 lastPeriod = uint32(_startPeriod) + _subStake.periods;
                if (lastPeriod > uint32(MAX_UINT16)) {
                    return MAX_UINT16;
                }
                return uint16(lastPeriod);
            }
            /**
            * @notice Get the last period of the sub stake
            * @param _staker Staker
            * @param _index Stake index
            */
            function getLastPeriodOfSubStake(address _staker, uint256 _index)
                public view returns (uint16)
            {
                StakerInfo storage info = stakerInfo[_staker];
                SubStakeInfo storage subStake = info.subStakes[_index];
                uint16 startPeriod = getStartPeriod(info, getCurrentPeriod());
                return getLastPeriodOfSubStake(subStake, startPeriod);
            }
            /**
            * @notice Get the value of locked tokens for a staker in a specified period
            * @dev Information may be incorrect for rewarded or not committed surpassed period
            * @param _info Staker structure
            * @param _currentPeriod Current period
            * @param _period Next period
            */
            function getLockedTokens(StakerInfo storage _info, uint16 _currentPeriod, uint16 _period)
                internal view returns (uint256 lockedValue)
            {
                lockedValue = 0;
                uint16 startPeriod = getStartPeriod(_info, _currentPeriod);
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    if (subStake.firstPeriod <= _period &&
                        getLastPeriodOfSubStake(subStake, startPeriod) >= _period) {
                        lockedValue += subStake.lockedValue;
                    }
                }
            }
            /**
            * @notice Get the value of locked tokens for a staker in a future period
            * @dev This function is used by PreallocationEscrow so its signature can't be updated.
            * @param _staker Staker
            * @param _periods Amount of periods that will be added to the current period
            */
            function getLockedTokens(address _staker, uint16 _periods)
                external view returns (uint256 lockedValue)
            {
                StakerInfo storage info = stakerInfo[_staker];
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod.add16(_periods);
                return getLockedTokens(info, currentPeriod, nextPeriod);
            }
            /**
            * @notice Get the last committed staker's period
            * @param _staker Staker
            */
            function getLastCommittedPeriod(address _staker) public view returns (uint16) {
                StakerInfo storage info = stakerInfo[_staker];
                return info.nextCommittedPeriod != 0 ? info.nextCommittedPeriod : info.lastCommittedPeriod;
            }
            /**
            * @notice Get the value of locked tokens for active stakers in (getCurrentPeriod() + _periods) period
            * as well as stakers and their locked tokens
            * @param _periods Amount of periods for locked tokens calculation
            * @param _startIndex Start index for looking in stakers array
            * @param _maxStakers Max stakers for looking, if set 0 then all will be used
            * @return allLockedTokens Sum of locked tokens for active stakers
            * @return activeStakers Array of stakers and their locked tokens. Stakers addresses stored as uint256
            * @dev Note that activeStakers[0] in an array of uint256, but you want addresses. Careful when used directly!
            */
            function getActiveStakers(uint16 _periods, uint256 _startIndex, uint256 _maxStakers)
                external view returns (uint256 allLockedTokens, uint256[2][] memory activeStakers)
            {
                require(_periods > 0);
                uint256 endIndex = stakers.length;
                require(_startIndex < endIndex);
                if (_maxStakers != 0 && _startIndex + _maxStakers < endIndex) {
                    endIndex = _startIndex + _maxStakers;
                }
                activeStakers = new uint256[2][](endIndex - _startIndex);
                allLockedTokens = 0;
                uint256 resultIndex = 0;
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod.add16(_periods);
                for (uint256 i = _startIndex; i < endIndex; i++) {
                    address staker = stakers[i];
                    StakerInfo storage info = stakerInfo[staker];
                    if (info.currentCommittedPeriod != currentPeriod &&
                        info.nextCommittedPeriod != currentPeriod) {
                        continue;
                    }
                    uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);
                    if (lockedTokens != 0) {
                        activeStakers[resultIndex][0] = uint256(staker);
                        activeStakers[resultIndex++][1] = lockedTokens;
                        allLockedTokens += lockedTokens;
                    }
                }
                assembly {
                    mstore(activeStakers, resultIndex)
                }
            }
            /**
            * @notice Checks if `reStake` parameter is available for changing
            * @param _staker Staker
            */
            function isReStakeLocked(address _staker) public view returns (bool) {
                return getCurrentPeriod() < stakerInfo[_staker].lockReStakeUntilPeriod;
            }
            /**
            * @notice Get worker using staker's address
            */
            function getWorkerFromStaker(address _staker) external view returns (address) {
                return stakerInfo[_staker].worker;
            }
            /**
            * @notice Get work that completed by the staker
            */
            function getCompletedWork(address _staker) external view returns (uint256) {
                return stakerInfo[_staker].completedWork;
            }
            /**
            * @notice Find index of downtime structure that includes specified period
            * @dev If specified period is outside all downtime periods, the length of the array will be returned
            * @param _staker Staker
            * @param _period Specified period number
            */
            function findIndexOfPastDowntime(address _staker, uint16 _period) external view returns (uint256 index) {
                StakerInfo storage info = stakerInfo[_staker];
                for (index = 0; index < info.pastDowntime.length; index++) {
                    if (_period <= info.pastDowntime[index].endPeriod) {
                        return index;
                    }
                }
            }
            //------------------------Main methods------------------------
            /**
            * @notice Start or stop measuring the work of a staker
            * @param _staker Staker
            * @param _measureWork Value for `measureWork` parameter
            * @return Work that was previously done
            */
            function setWorkMeasurement(address _staker, bool _measureWork) external returns (uint256) {
                require(msg.sender == address(workLock));
                StakerInfo storage info = stakerInfo[_staker];
                if (info.flags.bitSet(MEASURE_WORK_INDEX) == _measureWork) {
                    return info.completedWork;
                }
                info.flags = info.flags.toggleBit(MEASURE_WORK_INDEX);
                emit WorkMeasurementSet(_staker, _measureWork);
                return info.completedWork;
            }
            /**
            * @notice Bond worker
            * @param _worker Worker address. Must be a real address, not a contract
            */
            function bondWorker(address _worker) external onlyStaker {
                StakerInfo storage info = stakerInfo[msg.sender];
                // Specified worker is already bonded with this staker
                require(_worker != info.worker);
                uint16 currentPeriod = getCurrentPeriod();
                if (info.worker != address(0)) { // If this staker had a worker ...
                    // Check that enough time has passed to change it
                    require(currentPeriod >= info.workerStartPeriod.add16(minWorkerPeriods));
                    // Remove the old relation "worker->staker"
                    stakerFromWorker[info.worker] = address(0);
                }
                if (_worker != address(0)) {
                    // Specified worker is already in use
                    require(stakerFromWorker[_worker] == address(0));
                    // Specified worker is a staker
                    require(stakerInfo[_worker].subStakes.length == 0 || _worker == msg.sender);
                    // Set new worker->staker relation
                    stakerFromWorker[_worker] = msg.sender;
                }
                // Bond new worker (or unbond if _worker == address(0))
                info.worker = _worker;
                info.workerStartPeriod = currentPeriod;
                emit WorkerBonded(msg.sender, _worker, currentPeriod);
            }
            /**
            * @notice Set `reStake` parameter. If true then all staking rewards will be added to locked stake
            * Only if this parameter is not locked
            * @param _reStake Value for parameter
            */
            function setReStake(bool _reStake) external {
                require(!isReStakeLocked(msg.sender));
                StakerInfo storage info = stakerInfo[msg.sender];
                if (info.flags.bitSet(RE_STAKE_DISABLED_INDEX) == !_reStake) {
                    return;
                }
                info.flags = info.flags.toggleBit(RE_STAKE_DISABLED_INDEX);
                emit ReStakeSet(msg.sender, _reStake);
            }
            /**
            * @notice Lock `reStake` parameter. Only if this parameter is not locked
            * @param _lockReStakeUntilPeriod Can't change `reStake` value until this period
            */
            function lockReStake(uint16 _lockReStakeUntilPeriod) external {
                require(!isReStakeLocked(msg.sender) &&
                    _lockReStakeUntilPeriod > getCurrentPeriod());
                stakerInfo[msg.sender].lockReStakeUntilPeriod = _lockReStakeUntilPeriod;
                emit ReStakeLocked(msg.sender, _lockReStakeUntilPeriod);
            }
            /**
            * @notice Deposit tokens from WorkLock contract
            * @param _staker Staker address
            * @param _value Amount of tokens to deposit
            * @param _periods Amount of periods during which tokens will be locked
            */
            function depositFromWorkLock(
                address _staker,
                uint256 _value,
                uint16 _periods
            )
                external
            {
                require(msg.sender == address(workLock));
                StakerInfo storage info = stakerInfo[_staker];
                if (!info.flags.bitSet(WIND_DOWN_INDEX) && info.subStakes.length == 0) {
                    info.flags = info.flags.toggleBit(WIND_DOWN_INDEX);
                    emit WindDownSet(_staker, true);
                }
                deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods);
            }
            /**
            * @notice Set `windDown` parameter.
            * If true then stake's duration will be decreasing in each period with `commitToNextPeriod()`
            * @param _windDown Value for parameter
            */
            function setWindDown(bool _windDown) external {
                StakerInfo storage info = stakerInfo[msg.sender];
                if (info.flags.bitSet(WIND_DOWN_INDEX) == _windDown) {
                    return;
                }
                info.flags = info.flags.toggleBit(WIND_DOWN_INDEX);
                emit WindDownSet(msg.sender, _windDown);
                // duration adjustment if next period is committed
                uint16 nextPeriod = getCurrentPeriod() + 1;
                if (info.nextCommittedPeriod != nextPeriod) {
                   return;
                }
                // adjust sub-stakes duration for the new value of winding down parameter
                for (uint256 index = 0; index < info.subStakes.length; index++) {
                    SubStakeInfo storage subStake = info.subStakes[index];
                    // sub-stake does not have fixed last period when winding down is disabled
                    if (!_windDown && subStake.lastPeriod == nextPeriod) {
                        subStake.lastPeriod = 0;
                        subStake.periods = 1;
                        continue;
                    }
                    // this sub-stake is no longer affected by winding down parameter
                    if (subStake.lastPeriod != 0 || subStake.periods == 0) {
                        continue;
                    }
                    subStake.periods = _windDown ? subStake.periods - 1 : subStake.periods + 1;
                    if (subStake.periods == 0) {
                        subStake.lastPeriod = nextPeriod;
                    }
                }
            }
            /**
            * @notice Activate/deactivate taking snapshots of balances
            * @param _enableSnapshots True to activate snapshots, False to deactivate
            */
            function setSnapshots(bool _enableSnapshots) external {
                StakerInfo storage info = stakerInfo[msg.sender];
                if (info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX) == !_enableSnapshots) {
                    return;
                }
                uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());
                if(_enableSnapshots){
                    info.history.addSnapshot(info.value);
                    balanceHistory.addSnapshot(lastGlobalBalance + info.value);
                } else {
                    info.history.addSnapshot(0);
                    balanceHistory.addSnapshot(lastGlobalBalance - info.value);
                }
                info.flags = info.flags.toggleBit(SNAPSHOTS_DISABLED_INDEX);
                emit SnapshotSet(msg.sender, _enableSnapshots);
            }
            /**
            * @notice Adds a new snapshot to both the staker and global balance histories,
            * assuming the staker's balance was already changed
            * @param _info Reference to affected staker's struct
            * @param _addition Variance in balance. It can be positive or negative.
            */
            function addSnapshot(StakerInfo storage _info, int256 _addition) internal {
                if(!_info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX)){
                    _info.history.addSnapshot(_info.value);
                    uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());
                    balanceHistory.addSnapshot(lastGlobalBalance.addSigned(_addition));
                }
            }
            /**
            * @notice Batch deposit. Allowed only initial deposit for each staker
            * @param _stakers Stakers
            * @param _numberOfSubStakes Number of sub-stakes which belong to staker in _values and _periods arrays
            * @param _values Amount of tokens to deposit for each staker
            * @param _periods Amount of periods during which tokens will be locked for each staker
            */
            function batchDeposit(
                address[] calldata _stakers,
                uint256[] calldata _numberOfSubStakes,
                uint256[] calldata _values,
                uint16[] calldata _periods
            )
                external
            {
                uint256 subStakesLength = _values.length;
                require(_stakers.length != 0 &&
                    _stakers.length == _numberOfSubStakes.length &&
                    subStakesLength >= _stakers.length &&
                    _periods.length == subStakesLength);
                uint16 previousPeriod = getCurrentPeriod() - 1;
                uint16 nextPeriod = previousPeriod + 2;
                uint256 sumValue = 0;
                uint256 j = 0;
                for (uint256 i = 0; i < _stakers.length; i++) {
                    address staker = _stakers[i];
                    uint256 numberOfSubStakes = _numberOfSubStakes[i];
                    uint256 endIndex = j + numberOfSubStakes;
                    require(numberOfSubStakes > 0 && subStakesLength >= endIndex);
                    StakerInfo storage info = stakerInfo[staker];
                    require(info.subStakes.length == 0 && !info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX));
                    // A staker can't be a worker for another staker
                    require(stakerFromWorker[staker] == address(0));
                    stakers.push(staker);
                    policyManager.register(staker, previousPeriod);
                    for (; j < endIndex; j++) {
                        uint256 value =  _values[j];
                        uint16 periods = _periods[j];
                        require(value >= minAllowableLockedTokens && periods >= minLockedPeriods);
                        info.value = info.value.add(value);
                        info.subStakes.push(SubStakeInfo(nextPeriod, 0, periods, uint128(value)));
                        sumValue = sumValue.add(value);
                        emit Deposited(staker, value, periods);
                        emit Locked(staker, value, nextPeriod, periods);
                    }
                    require(info.value <= maxAllowableLockedTokens);
                    info.history.addSnapshot(info.value);
                }
                require(j == subStakesLength);
                uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());
                balanceHistory.addSnapshot(lastGlobalBalance + sumValue);
                token.safeTransferFrom(msg.sender, address(this), sumValue);
            }
            /**
            * @notice Implementation of the receiveApproval(address,uint256,address,bytes) method
            * (see NuCypherToken contract). Deposit all tokens that were approved to transfer
            * @param _from Staker
            * @param _value Amount of tokens to deposit
            * @param _tokenContract Token contract address
            * @notice (param _extraData) Amount of periods during which tokens will be locked
            */
            function receiveApproval(
                address _from,
                uint256 _value,
                address _tokenContract,
                bytes calldata /* _extraData */
            )
                external
            {
                require(_tokenContract == address(token) && msg.sender == address(token));
                // Copy first 32 bytes from _extraData, according to calldata memory layout:
                //
                // 0x00: method signature      4 bytes
                // 0x04: _from                 32 bytes after encoding
                // 0x24: _value                32 bytes after encoding
                // 0x44: _tokenContract        32 bytes after encoding
                // 0x64: _extraData pointer    32 bytes. Value must be 0x80 (offset of _extraData wrt to 1st parameter)
                // 0x84: _extraData length     32 bytes
                // 0xA4: _extraData data       Length determined by previous variable
                //
                // See https://solidity.readthedocs.io/en/latest/abi-spec.html#examples
                uint256 payloadSize;
                uint256 payload;
                assembly {
                    payloadSize := calldataload(0x84)
                    payload := calldataload(0xA4)
                }
                payload = payload >> 8*(32 - payloadSize);
                deposit(_from, _from, MAX_SUB_STAKES, _value, uint16(payload));
            }
            /**
            * @notice Deposit tokens and create new sub-stake. Use this method to become a staker
            * @param _staker Staker
            * @param _value Amount of tokens to deposit
            * @param _periods Amount of periods during which tokens will be locked
            */
            function deposit(address _staker, uint256 _value, uint16 _periods) external {
                deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods);
            }
            /**
            * @notice Deposit tokens and increase lock amount of an existing sub-stake
            * @dev This is preferable way to stake tokens because will be fewer active sub-stakes in the result
            * @param _index Index of the sub stake
            * @param _value Amount of tokens which will be locked
            */
            function depositAndIncrease(uint256 _index, uint256 _value) external onlyStaker {
                require(_index < MAX_SUB_STAKES);
                deposit(msg.sender, msg.sender, _index, _value, 0);
            }
            /**
            * @notice Deposit tokens
            * @dev Specify either index and zero periods (for an existing sub-stake)
            * or index >= MAX_SUB_STAKES and real value for periods (for a new sub-stake), not both
            * @param _staker Staker
            * @param _payer Owner of tokens
            * @param _index Index of the sub stake
            * @param _value Amount of tokens to deposit
            * @param _periods Amount of periods during which tokens will be locked
            */
            function deposit(address _staker, address _payer, uint256 _index, uint256 _value, uint16 _periods) internal {
                require(_value != 0);
                StakerInfo storage info = stakerInfo[_staker];
                // A staker can't be a worker for another staker
                require(stakerFromWorker[_staker] == address(0) || stakerFromWorker[_staker] == info.worker);
                // initial stake of the staker
                if (info.subStakes.length == 0) {
                    stakers.push(_staker);
                    policyManager.register(_staker, getCurrentPeriod() - 1);
                }
                token.safeTransferFrom(_payer, address(this), _value);
                info.value += _value;
                lock(_staker, _index, _value, _periods);
                addSnapshot(info, int256(_value));
                if (_index >= MAX_SUB_STAKES) {
                    emit Deposited(_staker, _value, _periods);
                } else {
                    uint16 lastPeriod = getLastPeriodOfSubStake(_staker, _index);
                    emit Deposited(_staker, _value, lastPeriod - getCurrentPeriod());
                }
            }
            /**
            * @notice Lock some tokens as a new sub-stake
            * @param _value Amount of tokens which will be locked
            * @param _periods Amount of periods during which tokens will be locked
            */
            function lockAndCreate(uint256 _value, uint16 _periods) external onlyStaker {
                lock(msg.sender, MAX_SUB_STAKES, _value, _periods);
            }
            /**
            * @notice Increase lock amount of an existing sub-stake
            * @param _index Index of the sub-stake
            * @param _value Amount of tokens which will be locked
            */
            function lockAndIncrease(uint256 _index, uint256 _value) external onlyStaker {
                require(_index < MAX_SUB_STAKES);
                lock(msg.sender, _index, _value, 0);
            }
            /**
            * @notice Lock some tokens as a stake
            * @dev Specify either index and zero periods (for an existing sub-stake)
            * or index >= MAX_SUB_STAKES and real value for periods (for a new sub-stake), not both
            * @param _staker Staker
            * @param _index Index of the sub stake
            * @param _value Amount of tokens which will be locked
            * @param _periods Amount of periods during which tokens will be locked
            */
            function lock(address _staker, uint256 _index, uint256 _value, uint16 _periods) internal {
                if (_index < MAX_SUB_STAKES) {
                    require(_value > 0);
                } else {
                    require(_value >= minAllowableLockedTokens && _periods >= minLockedPeriods);
                }
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod + 1;
                StakerInfo storage info = stakerInfo[_staker];
                uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);
                uint256 requestedLockedTokens = _value.add(lockedTokens);
                require(requestedLockedTokens <= info.value && requestedLockedTokens <= maxAllowableLockedTokens);
                // next period is committed
                if (info.nextCommittedPeriod == nextPeriod) {
                    lockedPerPeriod[nextPeriod] += _value;
                    emit CommitmentMade(_staker, nextPeriod, _value);
                }
                // if index was provided then increase existing sub-stake
                if (_index < MAX_SUB_STAKES) {
                    lockAndIncrease(info, currentPeriod, nextPeriod, _staker, _index, _value);
                // otherwise create new
                } else {
                    lockAndCreate(info, nextPeriod, _staker, _value, _periods);
                }
            }
            /**
            * @notice Lock some tokens as a new sub-stake
            * @param _info Staker structure
            * @param _nextPeriod Next period
            * @param _staker Staker
            * @param _value Amount of tokens which will be locked
            * @param _periods Amount of periods during which tokens will be locked
            */
            function lockAndCreate(
                StakerInfo storage _info,
                uint16 _nextPeriod,
                address _staker,
                uint256 _value,
                uint16 _periods
            )
                internal
            {
                uint16 duration = _periods;
                // if winding down is enabled and next period is committed
                // then sub-stakes duration were decreased
                if (_info.nextCommittedPeriod == _nextPeriod && _info.flags.bitSet(WIND_DOWN_INDEX)) {
                    duration -= 1;
                }
                saveSubStake(_info, _nextPeriod, 0, duration, _value);
                emit Locked(_staker, _value, _nextPeriod, _periods);
            }
            /**
            * @notice Increase lock amount of an existing sub-stake
            * @dev Probably will be created a new sub-stake but it will be active only one period
            * @param _info Staker structure
            * @param _currentPeriod Current period
            * @param _nextPeriod Next period
            * @param _staker Staker
            * @param _index Index of the sub-stake
            * @param _value Amount of tokens which will be locked
            */
            function lockAndIncrease(
                StakerInfo storage _info,
                uint16 _currentPeriod,
                uint16 _nextPeriod,
                address _staker,
                uint256 _index,
                uint256 _value
            )
                internal
            {
                SubStakeInfo storage subStake = _info.subStakes[_index];
                (, uint16 lastPeriod) = checkLastPeriodOfSubStake(_info, subStake, _currentPeriod);
                // create temporary sub-stake for current or previous committed periods
                // to leave locked amount in this period unchanged
                if (_info.currentCommittedPeriod != 0 &&
                    _info.currentCommittedPeriod <= _currentPeriod ||
                    _info.nextCommittedPeriod != 0 &&
                    _info.nextCommittedPeriod <= _currentPeriod)
                {
                    saveSubStake(_info, subStake.firstPeriod, _currentPeriod, 0, subStake.lockedValue);
                }
                subStake.lockedValue += uint128(_value);
                // all new locks should start from the next period
                subStake.firstPeriod = _nextPeriod;
                emit Locked(_staker, _value, _nextPeriod, lastPeriod - _currentPeriod);
            }
            /**
            * @notice Checks that last period of sub-stake is greater than the current period
            * @param _info Staker structure
            * @param _subStake Sub-stake structure
            * @param _currentPeriod Current period
            * @return startPeriod Start period. Use in the calculation of the last period of the sub stake
            * @return lastPeriod Last period of the sub stake
            */
            function checkLastPeriodOfSubStake(
                StakerInfo storage _info,
                SubStakeInfo storage _subStake,
                uint16 _currentPeriod
            )
                internal view returns (uint16 startPeriod, uint16 lastPeriod)
            {
                startPeriod = getStartPeriod(_info, _currentPeriod);
                lastPeriod = getLastPeriodOfSubStake(_subStake, startPeriod);
                // The sub stake must be active at least in the next period
                require(lastPeriod > _currentPeriod);
            }
            /**
            * @notice Save sub stake. First tries to override inactive sub stake
            * @dev Inactive sub stake means that last period of sub stake has been surpassed and already rewarded
            * @param _info Staker structure
            * @param _firstPeriod First period of the sub stake
            * @param _lastPeriod Last period of the sub stake
            * @param _periods Duration of the sub stake in periods
            * @param _lockedValue Amount of locked tokens
            */
            function saveSubStake(
                StakerInfo storage _info,
                uint16 _firstPeriod,
                uint16 _lastPeriod,
                uint16 _periods,
                uint256 _lockedValue
            )
                internal
            {
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    if (subStake.lastPeriod != 0 &&
                        (_info.currentCommittedPeriod == 0 ||
                        subStake.lastPeriod < _info.currentCommittedPeriod) &&
                        (_info.nextCommittedPeriod == 0 ||
                        subStake.lastPeriod < _info.nextCommittedPeriod))
                    {
                        subStake.firstPeriod = _firstPeriod;
                        subStake.lastPeriod = _lastPeriod;
                        subStake.periods = _periods;
                        subStake.lockedValue = uint128(_lockedValue);
                        return;
                    }
                }
                require(_info.subStakes.length < MAX_SUB_STAKES);
                _info.subStakes.push(SubStakeInfo(_firstPeriod, _lastPeriod, _periods, uint128(_lockedValue)));
            }
            /**
            * @notice Divide sub stake into two parts
            * @param _index Index of the sub stake
            * @param _newValue New sub stake value
            * @param _periods Amount of periods for extending sub stake
            */
            function divideStake(uint256 _index, uint256 _newValue, uint16 _periods) external onlyStaker {
                StakerInfo storage info = stakerInfo[msg.sender];
                require(_newValue >= minAllowableLockedTokens && _periods > 0);
                SubStakeInfo storage subStake = info.subStakes[_index];
                uint16 currentPeriod = getCurrentPeriod();
                (, uint16 lastPeriod) = checkLastPeriodOfSubStake(info, subStake, currentPeriod);
                uint256 oldValue = subStake.lockedValue;
                subStake.lockedValue = uint128(oldValue.sub(_newValue));
                require(subStake.lockedValue >= minAllowableLockedTokens);
                uint16 requestedPeriods = subStake.periods.add16(_periods);
                saveSubStake(info, subStake.firstPeriod, 0, requestedPeriods, _newValue);
                emit Divided(msg.sender, oldValue, lastPeriod, _newValue, _periods);
                emit Locked(msg.sender, _newValue, subStake.firstPeriod, requestedPeriods);
            }
            /**
            * @notice Prolong active sub stake
            * @param _index Index of the sub stake
            * @param _periods Amount of periods for extending sub stake
            */
            function prolongStake(uint256 _index, uint16 _periods) external onlyStaker {
                StakerInfo storage info = stakerInfo[msg.sender];
                // Incorrect parameters
                require(_periods > 0);
                SubStakeInfo storage subStake = info.subStakes[_index];
                uint16 currentPeriod = getCurrentPeriod();
                (uint16 startPeriod, uint16 lastPeriod) = checkLastPeriodOfSubStake(info, subStake, currentPeriod);
                subStake.periods = subStake.periods.add16(_periods);
                // if the sub stake ends in the next committed period then reset the `lastPeriod` field
                if (lastPeriod == startPeriod) {
                    subStake.lastPeriod = 0;
                }
                // The extended sub stake must not be less than the minimum value
                require(uint32(lastPeriod - currentPeriod) + _periods >= minLockedPeriods);
                emit Locked(msg.sender, subStake.lockedValue, lastPeriod + 1, _periods);
                emit Prolonged(msg.sender, subStake.lockedValue, lastPeriod, _periods);
            }
            /**
            * @notice Merge two sub-stakes into one if their last periods are equal
            * @dev It's possible that both sub-stakes will be active after this transaction.
            * But only one of them will be active until next call `commitToNextPeriod` (in the next period)
            * @param _index1 Index of the first sub-stake
            * @param _index2 Index of the second sub-stake
            */
            function mergeStake(uint256 _index1, uint256 _index2) external onlyStaker {
                require(_index1 != _index2); // must be different sub-stakes
                StakerInfo storage info = stakerInfo[msg.sender];
                SubStakeInfo storage subStake1 = info.subStakes[_index1];
                SubStakeInfo storage subStake2 = info.subStakes[_index2];
                uint16 currentPeriod = getCurrentPeriod();
                (, uint16 lastPeriod1) = checkLastPeriodOfSubStake(info, subStake1, currentPeriod);
                (, uint16 lastPeriod2) = checkLastPeriodOfSubStake(info, subStake2, currentPeriod);
                // both sub-stakes must have equal last period to be mergeable
                require(lastPeriod1 == lastPeriod2);
                emit Merged(msg.sender, subStake1.lockedValue, subStake2.lockedValue, lastPeriod1);
                if (subStake1.firstPeriod == subStake2.firstPeriod) {
                    subStake1.lockedValue += subStake2.lockedValue;
                    subStake2.lastPeriod = 1;
                    subStake2.periods = 0;
                } else if (subStake1.firstPeriod > subStake2.firstPeriod) {
                    subStake1.lockedValue += subStake2.lockedValue;
                    subStake2.lastPeriod = subStake1.firstPeriod - 1;
                    subStake2.periods = 0;
                } else {
                    subStake2.lockedValue += subStake1.lockedValue;
                    subStake1.lastPeriod = subStake2.firstPeriod - 1;
                    subStake1.periods = 0;
                }
            }
            /**
            * @notice Withdraw available amount of tokens to staker
            * @param _value Amount of tokens to withdraw
            */
            function withdraw(uint256 _value) external onlyStaker {
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod + 1;
                StakerInfo storage info = stakerInfo[msg.sender];
                // the max locked tokens in most cases will be in the current period
                // but when the staker locks more then we should use the next period
                uint256 lockedTokens = Math.max(getLockedTokens(info, currentPeriod, nextPeriod),
                    getLockedTokens(info, currentPeriod, currentPeriod));
                require(_value <= info.value.sub(lockedTokens));
                info.value -= _value;
                addSnapshot(info, - int256(_value));
                token.safeTransfer(msg.sender, _value);
                emit Withdrawn(msg.sender, _value);
                // unbond worker if staker withdraws last portion of NU
                if (info.value == 0 &&
                    info.nextCommittedPeriod == 0 &&
                    info.worker != address(0))
                {
                    stakerFromWorker[info.worker] = address(0);
                    info.worker = address(0);
                    emit WorkerBonded(msg.sender, address(0), currentPeriod);
                }
            }
            /**
            * @notice Make a commitment to the next period and mint for the previous period
            */
            function commitToNextPeriod() external isInitialized {
                address staker = stakerFromWorker[msg.sender];
                StakerInfo storage info = stakerInfo[staker];
                // Staker must have a stake to make a commitment
                require(info.value > 0);
                // Only worker with real address can make a commitment
                require(msg.sender == tx.origin);
                uint16 lastCommittedPeriod = getLastCommittedPeriod(staker);
                (uint16 processedPeriod1, uint16 processedPeriod2) = mint(staker);
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod + 1;
                // the period has already been committed
                if (info.nextCommittedPeriod == nextPeriod) {
                    return;
                }
                uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);
                require(lockedTokens > 0);
                lockedPerPeriod[nextPeriod] += lockedTokens;
                info.currentCommittedPeriod = info.nextCommittedPeriod;
                info.nextCommittedPeriod = nextPeriod;
                decreaseSubStakesDuration(info, nextPeriod);
                // staker was inactive for several periods
                if (lastCommittedPeriod < currentPeriod) {
                    info.pastDowntime.push(Downtime(lastCommittedPeriod + 1, currentPeriod));
                }
                policyManager.ping(staker, processedPeriod1, processedPeriod2, nextPeriod);
                emit CommitmentMade(staker, nextPeriod, lockedTokens);
            }
            /**
            * @notice Decrease sub-stakes duration if `windDown` is enabled
            */
            function decreaseSubStakesDuration(StakerInfo storage _info, uint16 _nextPeriod) internal {
                if (!_info.flags.bitSet(WIND_DOWN_INDEX)) {
                    return;
                }
                for (uint256 index = 0; index < _info.subStakes.length; index++) {
                    SubStakeInfo storage subStake = _info.subStakes[index];
                    if (subStake.lastPeriod != 0 || subStake.periods == 0) {
                        continue;
                    }
                    subStake.periods--;
                    if (subStake.periods == 0) {
                        subStake.lastPeriod = _nextPeriod;
                    }
                }
            }
            /**
            * @notice Mint tokens for previous periods if staker locked their tokens and made a commitment
            */
            function mint() external onlyStaker {
                // save last committed period to the storage if both periods will be empty after minting
                // because we won't be able to calculate last committed period
                // see getLastCommittedPeriod(address)
                StakerInfo storage info = stakerInfo[msg.sender];
                uint16 previousPeriod = getCurrentPeriod() - 1;
                if (info.nextCommittedPeriod <= previousPeriod && info.nextCommittedPeriod != 0) {
                    info.lastCommittedPeriod = info.nextCommittedPeriod;
                }
                (uint16 processedPeriod1, uint16 processedPeriod2) = mint(msg.sender);
                if (processedPeriod1 != 0 || processedPeriod2 != 0) {
                    policyManager.ping(msg.sender, processedPeriod1, processedPeriod2, 0);
                }
            }
            /**
            * @notice Mint tokens for previous periods if staker locked their tokens and made a commitment
            * @param _staker Staker
            * @return processedPeriod1 Processed period: currentCommittedPeriod or zero
            * @return processedPeriod2 Processed period: nextCommittedPeriod or zero
            */
            function mint(address _staker) internal returns (uint16 processedPeriod1, uint16 processedPeriod2) {
                uint16 currentPeriod = getCurrentPeriod();
                uint16 previousPeriod = currentPeriod - 1;
                StakerInfo storage info = stakerInfo[_staker];
                if (info.nextCommittedPeriod == 0 ||
                    info.currentCommittedPeriod == 0 &&
                    info.nextCommittedPeriod > previousPeriod ||
                    info.currentCommittedPeriod > previousPeriod) {
                    return (0, 0);
                }
                uint16 startPeriod = getStartPeriod(info, currentPeriod);
                uint256 reward = 0;
                bool reStake = !info.flags.bitSet(RE_STAKE_DISABLED_INDEX);
                if (info.currentCommittedPeriod != 0) {
                    reward = mint(info, info.currentCommittedPeriod, currentPeriod, startPeriod, reStake);
                    processedPeriod1 = info.currentCommittedPeriod;
                    info.currentCommittedPeriod = 0;
                    if (reStake) {
                        lockedPerPeriod[info.nextCommittedPeriod] += reward;
                    }
                }
                if (info.nextCommittedPeriod <= previousPeriod) {
                    reward += mint(info, info.nextCommittedPeriod, currentPeriod, startPeriod, reStake);
                    processedPeriod2 = info.nextCommittedPeriod;
                    info.nextCommittedPeriod = 0;
                }
                info.value += reward;
                if (info.flags.bitSet(MEASURE_WORK_INDEX)) {
                    info.completedWork += reward;
                }
                addSnapshot(info, int256(reward));
                emit Minted(_staker, previousPeriod, reward);
            }
            /**
            * @notice Calculate reward for one period
            * @param _info Staker structure
            * @param _mintingPeriod Period for minting calculation
            * @param _currentPeriod Current period
            * @param _startPeriod Pre-calculated start period
            */
            function mint(
                StakerInfo storage _info,
                uint16 _mintingPeriod,
                uint16 _currentPeriod,
                uint16 _startPeriod,
                bool _reStake
            )
                internal returns (uint256 reward)
            {
                reward = 0;
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake =  _info.subStakes[i];
                    uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
                    if (subStake.firstPeriod <= _mintingPeriod && lastPeriod >= _mintingPeriod) {
                        uint256 subStakeReward = mint(
                            _currentPeriod,
                            subStake.lockedValue,
                            lockedPerPeriod[_mintingPeriod],
                            lastPeriod.sub16(_mintingPeriod));
                        reward += subStakeReward;
                        if (_reStake) {
                            subStake.lockedValue += uint128(subStakeReward);
                        }
                    }
                }
                return reward;
            }
            //-------------------------Slashing-------------------------
            /**
            * @notice Slash the staker's stake and reward the investigator
            * @param _staker Staker's address
            * @param _penalty Penalty
            * @param _investigator Investigator
            * @param _reward Reward for the investigator
            */
            function slashStaker(
                address _staker,
                uint256 _penalty,
                address _investigator,
                uint256 _reward
            )
                public isInitialized
            {
                require(msg.sender == address(adjudicator));
                require(_penalty > 0);
                StakerInfo storage info = stakerInfo[_staker];
                if (info.value <= _penalty) {
                    _penalty = info.value;
                }
                info.value -= _penalty;
                if (_reward > _penalty) {
                    _reward = _penalty;
                }
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod + 1;
                uint16 startPeriod = getStartPeriod(info, currentPeriod);
                (uint256 currentLock, uint256 nextLock, uint256 currentAndNextLock, uint256 shortestSubStakeIndex) =
                    getLockedTokensAndShortestSubStake(info, currentPeriod, nextPeriod, startPeriod);
                // Decrease the stake if amount of locked tokens in the current period more than staker has
                uint256 lockedTokens = currentLock + currentAndNextLock;
                if (info.value < lockedTokens) {
                   decreaseSubStakes(info, lockedTokens - info.value, currentPeriod, startPeriod, shortestSubStakeIndex);
                }
                // Decrease the stake if amount of locked tokens in the next period more than staker has
                if (nextLock > 0) {
                    lockedTokens = nextLock + currentAndNextLock -
                        (currentAndNextLock > info.value ? currentAndNextLock - info.value : 0);
                    if (info.value < lockedTokens) {
                       decreaseSubStakes(info, lockedTokens - info.value, nextPeriod, startPeriod, MAX_SUB_STAKES);
                    }
                }
                emit Slashed(_staker, _penalty, _investigator, _reward);
                if (_penalty > _reward) {
                    unMint(_penalty - _reward);
                }
                // TODO change to withdrawal pattern (#1499)
                if (_reward > 0) {
                    token.safeTransfer(_investigator, _reward);
                }
                addSnapshot(info, - int256(_penalty));
            }
            /**
            * @notice Get the value of locked tokens for a staker in the current and the next period
            * and find the shortest sub stake
            * @param _info Staker structure
            * @param _currentPeriod Current period
            * @param _nextPeriod Next period
            * @param _startPeriod Pre-calculated start period
            * @return currentLock Amount of tokens that locked in the current period and unlocked in the next period
            * @return nextLock Amount of tokens that locked in the next period and not locked in the current period
            * @return currentAndNextLock Amount of tokens that locked in the current period and in the next period
            * @return shortestSubStakeIndex Index of the shortest sub stake
            */
            function getLockedTokensAndShortestSubStake(
                StakerInfo storage _info,
                uint16 _currentPeriod,
                uint16 _nextPeriod,
                uint16 _startPeriod
            )
                internal view returns (
                    uint256 currentLock,
                    uint256 nextLock,
                    uint256 currentAndNextLock,
                    uint256 shortestSubStakeIndex
                )
            {
                uint16 minDuration = MAX_UINT16;
                uint16 minLastPeriod = MAX_UINT16;
                shortestSubStakeIndex = MAX_SUB_STAKES;
                currentLock = 0;
                nextLock = 0;
                currentAndNextLock = 0;
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
                    if (lastPeriod < subStake.firstPeriod) {
                        continue;
                    }
                    if (subStake.firstPeriod <= _currentPeriod &&
                        lastPeriod >= _nextPeriod) {
                        currentAndNextLock += subStake.lockedValue;
                    } else if (subStake.firstPeriod <= _currentPeriod &&
                        lastPeriod >= _currentPeriod) {
                        currentLock += subStake.lockedValue;
                    } else if (subStake.firstPeriod <= _nextPeriod &&
                        lastPeriod >= _nextPeriod) {
                        nextLock += subStake.lockedValue;
                    }
                    uint16 duration = lastPeriod - subStake.firstPeriod;
                    if (subStake.firstPeriod <= _currentPeriod &&
                        lastPeriod >= _currentPeriod &&
                        (lastPeriod < minLastPeriod ||
                        lastPeriod == minLastPeriod && duration < minDuration))
                    {
                        shortestSubStakeIndex = i;
                        minDuration = duration;
                        minLastPeriod = lastPeriod;
                    }
                }
            }
            /**
            * @notice Decrease short sub stakes
            * @param _info Staker structure
            * @param _penalty Penalty rate
            * @param _decreasePeriod The period when the decrease begins
            * @param _startPeriod Pre-calculated start period
            * @param _shortestSubStakeIndex Index of the shortest period
            */
            function decreaseSubStakes(
                StakerInfo storage _info,
                uint256 _penalty,
                uint16 _decreasePeriod,
                uint16 _startPeriod,
                uint256 _shortestSubStakeIndex
            )
                internal
            {
                SubStakeInfo storage shortestSubStake = _info.subStakes[0];
                uint16 minSubStakeLastPeriod = MAX_UINT16;
                uint16 minSubStakeDuration = MAX_UINT16;
                while(_penalty > 0) {
                    if (_shortestSubStakeIndex < MAX_SUB_STAKES) {
                        shortestSubStake = _info.subStakes[_shortestSubStakeIndex];
                        minSubStakeLastPeriod = getLastPeriodOfSubStake(shortestSubStake, _startPeriod);
                        minSubStakeDuration = minSubStakeLastPeriod - shortestSubStake.firstPeriod;
                        _shortestSubStakeIndex = MAX_SUB_STAKES;
                    } else {
                        (shortestSubStake, minSubStakeDuration, minSubStakeLastPeriod) =
                            getShortestSubStake(_info, _decreasePeriod, _startPeriod);
                    }
                    if (minSubStakeDuration == MAX_UINT16) {
                        break;
                    }
                    uint256 appliedPenalty = _penalty;
                    if (_penalty < shortestSubStake.lockedValue) {
                        shortestSubStake.lockedValue -= uint128(_penalty);
                        saveOldSubStake(_info, shortestSubStake.firstPeriod, _penalty, _decreasePeriod);
                        _penalty = 0;
                    } else {
                        shortestSubStake.lastPeriod = _decreasePeriod - 1;
                        _penalty -= shortestSubStake.lockedValue;
                        appliedPenalty = shortestSubStake.lockedValue;
                    }
                    if (_info.currentCommittedPeriod >= _decreasePeriod &&
                        _info.currentCommittedPeriod <= minSubStakeLastPeriod)
                    {
                        lockedPerPeriod[_info.currentCommittedPeriod] -= appliedPenalty;
                    }
                    if (_info.nextCommittedPeriod >= _decreasePeriod &&
                        _info.nextCommittedPeriod <= minSubStakeLastPeriod)
                    {
                        lockedPerPeriod[_info.nextCommittedPeriod] -= appliedPenalty;
                    }
                }
            }
            /**
            * @notice Get the shortest sub stake
            * @param _info Staker structure
            * @param _currentPeriod Current period
            * @param _startPeriod Pre-calculated start period
            * @return shortestSubStake The shortest sub stake
            * @return minSubStakeDuration Duration of the shortest sub stake
            * @return minSubStakeLastPeriod Last period of the shortest sub stake
            */
            function getShortestSubStake(
                StakerInfo storage _info,
                uint16 _currentPeriod,
                uint16 _startPeriod
            )
                internal view returns (
                    SubStakeInfo storage shortestSubStake,
                    uint16 minSubStakeDuration,
                    uint16 minSubStakeLastPeriod
                )
            {
                shortestSubStake = shortestSubStake;
                minSubStakeDuration = MAX_UINT16;
                minSubStakeLastPeriod = MAX_UINT16;
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
                    if (lastPeriod < subStake.firstPeriod) {
                        continue;
                    }
                    uint16 duration = lastPeriod - subStake.firstPeriod;
                    if (subStake.firstPeriod <= _currentPeriod &&
                        lastPeriod >= _currentPeriod &&
                        (lastPeriod < minSubStakeLastPeriod ||
                        lastPeriod == minSubStakeLastPeriod && duration < minSubStakeDuration))
                    {
                        shortestSubStake = subStake;
                        minSubStakeDuration = duration;
                        minSubStakeLastPeriod = lastPeriod;
                    }
                }
            }
            /**
            * @notice Save the old sub stake values to prevent decreasing reward for the previous period
            * @dev Saving happens only if the previous period is committed
            * @param _info Staker structure
            * @param _firstPeriod First period of the old sub stake
            * @param _lockedValue Locked value of the old sub stake
            * @param _currentPeriod Current period, when the old sub stake is already unlocked
            */
            function saveOldSubStake(
                StakerInfo storage _info,
                uint16 _firstPeriod,
                uint256 _lockedValue,
                uint16 _currentPeriod
            )
                internal
            {
                // Check that the old sub stake should be saved
                bool oldCurrentCommittedPeriod = _info.currentCommittedPeriod != 0 &&
                    _info.currentCommittedPeriod < _currentPeriod;
                bool oldnextCommittedPeriod = _info.nextCommittedPeriod != 0 &&
                    _info.nextCommittedPeriod < _currentPeriod;
                bool crosscurrentCommittedPeriod = oldCurrentCommittedPeriod && _info.currentCommittedPeriod >= _firstPeriod;
                bool crossnextCommittedPeriod = oldnextCommittedPeriod && _info.nextCommittedPeriod >= _firstPeriod;
                if (!crosscurrentCommittedPeriod && !crossnextCommittedPeriod) {
                    return;
                }
                // Try to find already existent proper old sub stake
                uint16 previousPeriod = _currentPeriod - 1;
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    if (subStake.lastPeriod == previousPeriod &&
                        ((crosscurrentCommittedPeriod ==
                        (oldCurrentCommittedPeriod && _info.currentCommittedPeriod >= subStake.firstPeriod)) &&
                        (crossnextCommittedPeriod ==
                        (oldnextCommittedPeriod && _info.nextCommittedPeriod >= subStake.firstPeriod))))
                    {
                        subStake.lockedValue += uint128(_lockedValue);
                        return;
                    }
                }
                saveSubStake(_info, _firstPeriod, previousPeriod, 0, _lockedValue);
            }
            //-------------Additional getters for stakers info-------------
            /**
            * @notice Return the length of the array of stakers
            */
            function getStakersLength() external view returns (uint256) {
                return stakers.length;
            }
            /**
            * @notice Return the length of the array of sub stakes
            */
            function getSubStakesLength(address _staker) external view returns (uint256) {
                return stakerInfo[_staker].subStakes.length;
            }
            /**
            * @notice Return the information about sub stake
            */
            function getSubStakeInfo(address _staker, uint256 _index)
            // TODO change to structure when ABIEncoderV2 is released (#1501)
        //        public view returns (SubStakeInfo)
                // TODO "virtual" only for tests, probably will be removed after #1512
                external view virtual returns (uint16 firstPeriod, uint16 lastPeriod, uint16 periods, uint128 lockedValue)
            {
                SubStakeInfo storage info = stakerInfo[_staker].subStakes[_index];
                firstPeriod = info.firstPeriod;
                lastPeriod = info.lastPeriod;
                periods = info.periods;
                lockedValue = info.lockedValue;
            }
            /**
            * @notice Return the length of the array of past downtime
            */
            function getPastDowntimeLength(address _staker) external view returns (uint256) {
                return stakerInfo[_staker].pastDowntime.length;
            }
            /**
            * @notice Return the information about past downtime
            */
            function  getPastDowntime(address _staker, uint256 _index)
            // TODO change to structure when ABIEncoderV2 is released (#1501)
        //        public view returns (Downtime)
                external view returns (uint16 startPeriod, uint16 endPeriod)
            {
                Downtime storage downtime = stakerInfo[_staker].pastDowntime[_index];
                startPeriod = downtime.startPeriod;
                endPeriod = downtime.endPeriod;
            }
            //------------------ ERC900 connectors ----------------------
            function totalStakedForAt(address _owner, uint256 _blockNumber) public view override returns (uint256){
                return stakerInfo[_owner].history.getValueAt(_blockNumber);
            }
            function totalStakedAt(uint256 _blockNumber) public view override returns (uint256){
                return balanceHistory.getValueAt(_blockNumber);
            }
            function supportsHistory() external pure override returns (bool){
                return true;
            }
            //------------------------Upgradeable------------------------
            /**
            * @dev Get StakerInfo structure by delegatecall
            */
            function delegateGetStakerInfo(address _target, bytes32 _staker)
                internal returns (StakerInfo memory result)
            {
                bytes32 memoryAddress = delegateGetData(_target, this.stakerInfo.selector, 1, _staker, 0);
                assembly {
                    result := memoryAddress
                }
            }
            /**
            * @dev Get SubStakeInfo structure by delegatecall
            */
            function delegateGetSubStakeInfo(address _target, bytes32 _staker, uint256 _index)
                internal returns (SubStakeInfo memory result)
            {
                bytes32 memoryAddress = delegateGetData(
                    _target, this.getSubStakeInfo.selector, 2, _staker, bytes32(_index));
                assembly {
                    result := memoryAddress
                }
            }
            /**
            * @dev Get Downtime structure by delegatecall
            */
            function delegateGetPastDowntime(address _target, bytes32 _staker, uint256 _index)
                internal returns (Downtime memory result)
            {
                bytes32 memoryAddress = delegateGetData(
                    _target, this.getPastDowntime.selector, 2, _staker, bytes32(_index));
                assembly {
                    result := memoryAddress
                }
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
            function verifyState(address _testTarget) public override virtual {
                super.verifyState(_testTarget);
                require(address(delegateGet(_testTarget, this.policyManager.selector)) == address(policyManager));
                require(address(delegateGet(_testTarget, this.adjudicator.selector)) == address(adjudicator));
                require(address(delegateGet(_testTarget, this.workLock.selector)) == address(workLock));
                require(delegateGet(_testTarget, this.lockedPerPeriod.selector,
                    bytes32(bytes2(RESERVED_PERIOD))) == lockedPerPeriod[RESERVED_PERIOD]);
                require(address(delegateGet(_testTarget, this.stakerFromWorker.selector, bytes32(0))) ==
                    stakerFromWorker[address(0)]);
                require(delegateGet(_testTarget, this.getStakersLength.selector) == stakers.length);
                if (stakers.length == 0) {
                    return;
                }
                address stakerAddress = stakers[0];
                require(address(uint160(delegateGet(_testTarget, this.stakers.selector, 0))) == stakerAddress);
                StakerInfo storage info = stakerInfo[stakerAddress];
                bytes32 staker = bytes32(uint256(stakerAddress));
                StakerInfo memory infoToCheck = delegateGetStakerInfo(_testTarget, staker);
                require(infoToCheck.value == info.value &&
                    infoToCheck.currentCommittedPeriod == info.currentCommittedPeriod &&
                    infoToCheck.nextCommittedPeriod == info.nextCommittedPeriod &&
                    infoToCheck.flags == info.flags &&
                    infoToCheck.lockReStakeUntilPeriod == info.lockReStakeUntilPeriod &&
                    infoToCheck.lastCommittedPeriod == info.lastCommittedPeriod &&
                    infoToCheck.completedWork == info.completedWork &&
                    infoToCheck.worker == info.worker &&
                    infoToCheck.workerStartPeriod == info.workerStartPeriod);
                require(delegateGet(_testTarget, this.getPastDowntimeLength.selector, staker) ==
                    info.pastDowntime.length);
                for (uint256 i = 0; i < info.pastDowntime.length && i < MAX_CHECKED_VALUES; i++) {
                    Downtime storage downtime = info.pastDowntime[i];
                    Downtime memory downtimeToCheck = delegateGetPastDowntime(_testTarget, staker, i);
                    require(downtimeToCheck.startPeriod == downtime.startPeriod &&
                        downtimeToCheck.endPeriod == downtime.endPeriod);
                }
                require(delegateGet(_testTarget, this.getSubStakesLength.selector, staker) == info.subStakes.length);
                for (uint256 i = 0; i < info.subStakes.length && i < MAX_CHECKED_VALUES; i++) {
                    SubStakeInfo storage subStakeInfo = info.subStakes[i];
                    SubStakeInfo memory subStakeInfoToCheck = delegateGetSubStakeInfo(_testTarget, staker, i);
                    require(subStakeInfoToCheck.firstPeriod == subStakeInfo.firstPeriod &&
                        subStakeInfoToCheck.lastPeriod == subStakeInfo.lastPeriod &&
                        subStakeInfoToCheck.periods == subStakeInfo.periods &&
                        subStakeInfoToCheck.lockedValue == subStakeInfo.lockedValue);
                }
                // it's not perfect because checks not only slot value but also decoding
                // at least without additional functions
                require(delegateGet(_testTarget, this.totalStakedForAt.selector, staker, bytes32(block.number)) ==
                    totalStakedForAt(stakerAddress, block.number));
                require(delegateGet(_testTarget, this.totalStakedAt.selector, bytes32(block.number)) ==
                    totalStakedAt(block.number));
                if (info.worker != address(0)) {
                    require(address(delegateGet(_testTarget, this.stakerFromWorker.selector, bytes32(uint256(info.worker)))) ==
                        stakerFromWorker[info.worker]);
                }
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade`
            function finishUpgrade(address _target) public override virtual {
                super.finishUpgrade(_target);
                // Create fake period
                lockedPerPeriod[RESERVED_PERIOD] = 111;
                // Create fake worker
                stakerFromWorker[address(0)] = address(this);
            }
        }// SPDX-License-Identifier: GPL-3.0-or-later
        pragma solidity ^0.7.0;
        // Minimum interface to interact with Aragon's Aggregator
        interface IERC900History {
            function totalStakedForAt(address addr, uint256 blockNumber) external view returns (uint256);
            function totalStakedAt(uint256 blockNumber) external view returns (uint256);
            function supportsHistory() external pure returns (bool);
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "./NuCypherToken.sol";
        import "../zeppelin/math/Math.sol";
        import "./proxy/Upgradeable.sol";
        import "./lib/AdditionalMath.sol";
        import "../zeppelin/token/ERC20/SafeERC20.sol";
        /**
        * @notice Contract for calculation of issued tokens
        * @dev |v3.3.1|
        */
        abstract contract Issuer is Upgradeable {
            using SafeERC20 for NuCypherToken;
            using AdditionalMath for uint32;
            event Donated(address indexed sender, uint256 value);
            /// Issuer is initialized with a reserved reward
            event Initialized(uint256 reservedReward);
            uint128 constant MAX_UINT128 = uint128(0) - 1;
            NuCypherToken public immutable token;
            uint128 public immutable totalSupply;
            // d * k2
            uint256 public immutable mintingCoefficient;
            // k1
            uint256 public immutable lockDurationCoefficient1;
            // k2
            uint256 public immutable lockDurationCoefficient2;
            uint32 public immutable secondsPerPeriod;
            // kmax
            uint16 public immutable maximumRewardedPeriods;
            uint256 public immutable firstPhaseMaxIssuance;
            uint256 public immutable firstPhaseTotalSupply;
            /**
            * Current supply is used in the minting formula and is stored to prevent different calculation
            * for stakers which get reward in the same period. There are two values -
            * supply for previous period (used in formula) and supply for current period which accumulates value
            * before end of period.
            */
            uint128 public previousPeriodSupply;
            uint128 public currentPeriodSupply;
            uint16 public currentMintingPeriod;
            /**
            * @notice Constructor sets address of token contract and coefficients for minting
            * @dev Minting formula for one sub-stake in one period for the first phase
            firstPhaseMaxIssuance * (lockedValue / totalLockedValue) * (k1 + min(allLockedPeriods, kmax)) / k2
            * @dev Minting formula for one sub-stake in one period for the second phase
            (totalSupply - currentSupply) / d * (lockedValue / totalLockedValue) * (k1 + min(allLockedPeriods, kmax)) / k2
            if allLockedPeriods > maximumRewardedPeriods then allLockedPeriods = maximumRewardedPeriods
            * @param _token Token contract
            * @param _hoursPerPeriod Size of period in hours
            * @param _issuanceDecayCoefficient (d) Coefficient which modifies the rate at which the maximum issuance decays,
            * only applicable to Phase 2. d = 365 * half-life / LOG2 where default half-life = 2.
            * See Equation 10 in Staking Protocol & Economics paper
            * @param _lockDurationCoefficient1 (k1) Numerator of the coefficient which modifies the extent 
            * to which a stake's lock duration affects the subsidy it receives. Affects stakers differently. 
            * Applicable to Phase 1 and Phase 2. k1 = k2 * small_stake_multiplier where default small_stake_multiplier = 0.5.  
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _lockDurationCoefficient2 (k2) Denominator of the coefficient which modifies the extent
            * to which a stake's lock duration affects the subsidy it receives. Affects stakers differently.
            * Applicable to Phase 1 and Phase 2. k2 = maximum_rewarded_periods / (1 - small_stake_multiplier)
            * where default maximum_rewarded_periods = 365 and default small_stake_multiplier = 0.5.
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _maximumRewardedPeriods (kmax) Number of periods beyond which a stake's lock duration
            * no longer increases the subsidy it receives. kmax = reward_saturation * 365 where default reward_saturation = 1.
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _firstPhaseTotalSupply Total supply for the first phase
            * @param _firstPhaseMaxIssuance (Imax) Maximum number of new tokens minted per period during Phase 1.
            * See Equation 7 in Staking Protocol & Economics paper.
            */
            constructor(
                NuCypherToken _token,
                uint32 _hoursPerPeriod,
                uint256 _issuanceDecayCoefficient,
                uint256 _lockDurationCoefficient1,
                uint256 _lockDurationCoefficient2,
                uint16 _maximumRewardedPeriods,
                uint256 _firstPhaseTotalSupply,
                uint256 _firstPhaseMaxIssuance
            ) {
                uint256 localTotalSupply = _token.totalSupply();
                require(localTotalSupply > 0 &&
                    _issuanceDecayCoefficient != 0 &&
                    _hoursPerPeriod != 0 &&
                    _lockDurationCoefficient1 != 0 &&
                    _lockDurationCoefficient2 != 0 &&
                    _maximumRewardedPeriods != 0);
                require(localTotalSupply <= uint256(MAX_UINT128), "Token contract has supply more than supported");
                uint256 maxLockDurationCoefficient = _maximumRewardedPeriods + _lockDurationCoefficient1;
                uint256 localMintingCoefficient = _issuanceDecayCoefficient * _lockDurationCoefficient2;
                require(maxLockDurationCoefficient > _maximumRewardedPeriods &&
                    localMintingCoefficient / _issuanceDecayCoefficient ==  _lockDurationCoefficient2 &&
                    // worst case for `totalLockedValue * d * k2`, when totalLockedValue == totalSupply
                    localTotalSupply * localMintingCoefficient / localTotalSupply == localMintingCoefficient &&
                    // worst case for `(totalSupply - currentSupply) * lockedValue * (k1 + min(allLockedPeriods, kmax))`,
                    // when currentSupply == 0, lockedValue == totalSupply
                    localTotalSupply * localTotalSupply * maxLockDurationCoefficient / localTotalSupply / localTotalSupply ==
                        maxLockDurationCoefficient,
                    "Specified parameters cause overflow");
                require(maxLockDurationCoefficient <= _lockDurationCoefficient2,
                    "Resulting locking duration coefficient must be less than 1");
                require(_firstPhaseTotalSupply <= localTotalSupply, "Too many tokens for the first phase");
                require(_firstPhaseMaxIssuance <= _firstPhaseTotalSupply, "Reward for the first phase is too high");
                token = _token;
                secondsPerPeriod = _hoursPerPeriod.mul32(1 hours);
                lockDurationCoefficient1 = _lockDurationCoefficient1;
                lockDurationCoefficient2 = _lockDurationCoefficient2;
                maximumRewardedPeriods = _maximumRewardedPeriods;
                firstPhaseTotalSupply = _firstPhaseTotalSupply;
                firstPhaseMaxIssuance = _firstPhaseMaxIssuance;
                totalSupply = uint128(localTotalSupply);
                mintingCoefficient = localMintingCoefficient;
            }
            /**
            * @dev Checks contract initialization
            */
            modifier isInitialized()
            {
                require(currentMintingPeriod != 0);
                _;
            }
            /**
            * @return Number of current period
            */
            function getCurrentPeriod() public view returns (uint16) {
                return uint16(block.timestamp / secondsPerPeriod);
            }
            /**
            * @notice Initialize reserved tokens for reward
            */
            function initialize(uint256 _reservedReward, address _sourceOfFunds) external onlyOwner {
                require(currentMintingPeriod == 0);
                // Reserved reward must be sufficient for at least one period of the first phase
                require(firstPhaseMaxIssuance <= _reservedReward);
                currentMintingPeriod = getCurrentPeriod();
                currentPeriodSupply = totalSupply - uint128(_reservedReward);
                previousPeriodSupply = currentPeriodSupply;
                token.safeTransferFrom(_sourceOfFunds, address(this), _reservedReward);
                emit Initialized(_reservedReward);
            }
            /**
            * @notice Function to mint tokens for one period.
            * @param _currentPeriod Current period number.
            * @param _lockedValue The amount of tokens that were locked by user in specified period.
            * @param _totalLockedValue The amount of tokens that were locked by all users in specified period.
            * @param _allLockedPeriods The max amount of periods during which tokens will be locked after specified period.
            * @return amount Amount of minted tokens.
            */
            function mint(
                uint16 _currentPeriod,
                uint256 _lockedValue,
                uint256 _totalLockedValue,
                uint16 _allLockedPeriods
            )
                internal returns (uint256 amount)
            {
                if (currentPeriodSupply == totalSupply) {
                    return 0;
                }
                if (_currentPeriod > currentMintingPeriod) {
                    previousPeriodSupply = currentPeriodSupply;
                    currentMintingPeriod = _currentPeriod;
                }
                uint256 currentReward;
                uint256 coefficient;
                // first phase
                // firstPhaseMaxIssuance * lockedValue * (k1 + min(allLockedPeriods, kmax)) / (totalLockedValue * k2)
                if (previousPeriodSupply + firstPhaseMaxIssuance <= firstPhaseTotalSupply) {
                    currentReward = firstPhaseMaxIssuance;
                    coefficient = lockDurationCoefficient2;
                // second phase
                // (totalSupply - currentSupply) * lockedValue * (k1 + min(allLockedPeriods, kmax)) / (totalLockedValue * d * k2)
                } else {
                    currentReward = totalSupply - previousPeriodSupply;
                    coefficient = mintingCoefficient;
                }
                uint256 allLockedPeriods =
                    AdditionalMath.min16(_allLockedPeriods, maximumRewardedPeriods) + lockDurationCoefficient1;
                amount = (uint256(currentReward) * _lockedValue * allLockedPeriods) /
                    (_totalLockedValue * coefficient);
                // rounding the last reward
                uint256 maxReward = getReservedReward();
                if (amount == 0) {
                    amount = 1;
                } else if (amount > maxReward) {
                    amount = maxReward;
                }
                currentPeriodSupply += uint128(amount);
            }
            /**
            * @notice Return tokens for future minting
            * @param _amount Amount of tokens
            */
            function unMint(uint256 _amount) internal {
                previousPeriodSupply -= uint128(_amount);
                currentPeriodSupply -= uint128(_amount);
            }
            /**
            * @notice Donate sender's tokens. Amount of tokens will be returned for future minting
            * @param _value Amount to donate
            */
            function donate(uint256 _value) external isInitialized {
                token.safeTransferFrom(msg.sender, address(this), _value);
                unMint(_value);
                emit Donated(msg.sender, _value);
            }
            /**
            * @notice Returns the number of tokens that can be minted
            */
            function getReservedReward() public view returns (uint256) {
                return totalSupply - currentPeriodSupply;
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
            function verifyState(address _testTarget) public override virtual {
                super.verifyState(_testTarget);
                require(uint16(delegateGet(_testTarget, this.currentMintingPeriod.selector)) == currentMintingPeriod);
                require(uint128(delegateGet(_testTarget, this.previousPeriodSupply.selector)) == previousPeriodSupply);
                require(uint128(delegateGet(_testTarget, this.currentPeriodSupply.selector)) == currentPeriodSupply);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../zeppelin/token/ERC20/ERC20.sol";
        import "../zeppelin/token/ERC20/ERC20Detailed.sol";
        /**
        * @title NuCypher token
        * @notice ERC20 token
        * @dev Optional approveAndCall() functionality to notify a contract if an approve() has occurred.
        */
        contract NuCypherToken is ERC20, ERC20Detailed('NuCypher', 'NU', 18) {
            /**
            * @notice Set amount of tokens
            * @param _totalSupplyOfTokens Total number of tokens
            */
            constructor (uint256 _totalSupplyOfTokens) {
                _mint(msg.sender, _totalSupplyOfTokens);
            }
            /**
            * @notice Approves and then calls the receiving contract
            *
            * @dev call the receiveApproval function on the contract you want to be notified.
            * receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
            */
            function approveAndCall(address _spender, uint256 _value, bytes calldata _extraData)
                external returns (bool success)
            {
                approve(_spender, _value);
                TokenRecipient(_spender).receiveApproval(msg.sender, _value, address(this), _extraData);
                return true;
            }
        }
        /**
        * @dev Interface to use the receiveApproval method
        */
        interface TokenRecipient {
            /**
            * @notice Receives a notification of approval of the transfer
            * @param _from Sender of approval
            * @param _value  The amount of tokens to be spent
            * @param _tokenContract Address of the token contract
            * @param _extraData Extra data
            */
            function receiveApproval(address _from, uint256 _value, address _tokenContract, bytes calldata _extraData) external;
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.7.0;
        import "./IERC20.sol";
        import "../../math/SafeMath.sol";
        /**
         * @title Standard ERC20 token
         *
         * @dev Implementation of the basic standard token.
         * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md
         * Originally based on code by FirstBlood:
         * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
         *
         * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for
         * all accounts just by listening to said events. Note that this isn't required by the specification, and other
         * compliant implementations may not do it.
         */
        contract ERC20 is IERC20 {
            using SafeMath for uint256;
            mapping (address => uint256) private _balances;
            mapping (address => mapping (address => uint256)) private _allowed;
            uint256 private _totalSupply;
            /**
             * @dev Total number of tokens in existence
             */
            function totalSupply() public view override returns (uint256) {
                return _totalSupply;
            }
            /**
             * @dev Gets the balance of the specified address.
             * @param owner The address to query the balance of.
             * @return An uint256 representing the amount owned by the passed address.
             */
            function balanceOf(address owner) public view override returns (uint256) {
                return _balances[owner];
            }
            /**
             * @dev Function to check the amount of tokens that an owner allowed to a spender.
             * @param owner address The address which owns the funds.
             * @param spender address The address which will spend the funds.
             * @return A uint256 specifying the amount of tokens still available for the spender.
             */
            function allowance(address owner, address spender) public view override returns (uint256) {
                return _allowed[owner][spender];
            }
            /**
             * @dev Transfer token for a specified address
             * @param to The address to transfer to.
             * @param value The amount to be transferred.
             */
            function transfer(address to, uint256 value) public override returns (bool) {
                _transfer(msg.sender, to, value);
                return true;
            }
            /**
             * @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
             * 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
             * @param spender The address which will spend the funds.
             * @param value The amount of tokens to be spent.
             */
            function approve(address spender, uint256 value) public override returns (bool) {
                // To change the approve amount you first have to reduce the addresses`
                //  allowance to zero by calling `approve(_spender, 0)` if it is not
                //  already 0 to mitigate the race condition described here:
                //  https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
                require(value == 0 || _allowed[msg.sender][spender] == 0);
                _approve(msg.sender, spender, value);
                return true;
            }
            /**
             * @dev Transfer tokens from one address to another.
             * Note that while this function emits an Approval event, this is not required as per the specification,
             * and other compliant implementations may not emit the event.
             * @param from address The address which you want to send tokens from
             * @param to address The address which you want to transfer to
             * @param value uint256 the amount of tokens to be transferred
             */
            function transferFrom(address from, address to, uint256 value) public override returns (bool) {
                _transfer(from, to, value);
                _approve(from, msg.sender, _allowed[from][msg.sender].sub(value));
                return true;
            }
            /**
             * @dev Increase the amount of tokens that an owner allowed to a spender.
             * approve should be called when allowed_[_spender] == 0. To increment
             * allowed value is better to use this function to avoid 2 calls (and wait until
             * the first transaction is mined)
             * From MonolithDAO Token.sol
             * Emits an Approval event.
             * @param spender The address which will spend the funds.
             * @param addedValue The amount of tokens to increase the allowance by.
             */
            function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
                _approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));
                return true;
            }
            /**
             * @dev Decrease the amount of tokens that an owner allowed to a spender.
             * approve should be called when allowed_[_spender] == 0. To decrement
             * allowed value is better to use this function to avoid 2 calls (and wait until
             * the first transaction is mined)
             * From MonolithDAO Token.sol
             * Emits an Approval event.
             * @param spender The address which will spend the funds.
             * @param subtractedValue The amount of tokens to decrease the allowance by.
             */
            function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
                _approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));
                return true;
            }
            /**
             * @dev Transfer token for a specified addresses
             * @param from The address to transfer from.
             * @param to The address to transfer to.
             * @param value The amount to be transferred.
             */
            function _transfer(address from, address to, uint256 value) internal {
                require(to != address(0));
                _balances[from] = _balances[from].sub(value);
                _balances[to] = _balances[to].add(value);
                emit Transfer(from, to, value);
            }
            /**
             * @dev Internal function that mints an amount of the token and assigns it to
             * an account. This encapsulates the modification of balances such that the
             * proper events are emitted.
             * @param account The account that will receive the created tokens.
             * @param value The amount that will be created.
             */
            function _mint(address account, uint256 value) internal {
                require(account != address(0));
                _totalSupply = _totalSupply.add(value);
                _balances[account] = _balances[account].add(value);
                emit Transfer(address(0), account, value);
            }
            /**
             * @dev Internal function that burns an amount of the token of a given
             * account.
             * @param account The account whose tokens will be burnt.
             * @param value The amount that will be burnt.
             */
            function _burn(address account, uint256 value) internal {
                require(account != address(0));
                _totalSupply = _totalSupply.sub(value);
                _balances[account] = _balances[account].sub(value);
                emit Transfer(account, address(0), value);
            }
            /**
             * @dev Approve an address to spend another addresses' tokens.
             * @param owner The address that owns the tokens.
             * @param spender The address that will spend the tokens.
             * @param value The number of tokens that can be spent.
             */
            function _approve(address owner, address spender, uint256 value) internal {
                require(spender != address(0));
                require(owner != address(0));
                _allowed[owner][spender] = value;
                emit Approval(owner, spender, value);
            }
            /**
             * @dev Internal function that burns an amount of the token of a given
             * account, deducting from the sender's allowance for said account. Uses the
             * internal burn function.
             * Emits an Approval event (reflecting the reduced allowance).
             * @param account The account whose tokens will be burnt.
             * @param value The amount that will be burnt.
             */
            function _burnFrom(address account, uint256 value) internal {
                _burn(account, value);
                _approve(account, msg.sender, _allowed[account][msg.sender].sub(value));
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.7.0;
        /**
         * @title ERC20 interface
         * @dev see https://github.com/ethereum/EIPs/issues/20
         */
        interface IERC20 {
            function transfer(address to, uint256 value) external returns (bool);
            function approve(address spender, uint256 value) external returns (bool);
            function transferFrom(address from, address to, uint256 value) external returns (bool);
            function totalSupply() external view returns (uint256);
            function balanceOf(address who) external view returns (uint256);
            function allowance(address owner, address spender) external view returns (uint256);
            event Transfer(address indexed from, address indexed to, uint256 value);
            event Approval(address indexed owner, address indexed spender, uint256 value);
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.7.0;
        /**
         * @title SafeMath
         * @dev Unsigned math operations with safety checks that revert on error
         */
        library SafeMath {
            /**
             * @dev Multiplies two unsigned integers, reverts on 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-solidity/pull/522
                if (a == 0) {
                    return 0;
                }
                uint256 c = a * b;
                require(c / a == b);
                return c;
            }
            /**
             * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
             */
            function div(uint256 a, uint256 b) internal pure returns (uint256) {
                // Solidity only automatically asserts when dividing by 0
                require(b > 0);
                uint256 c = a / b;
                // assert(a == b * c + a % b); // There is no case in which this doesn't hold
                return c;
            }
            /**
             * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
             */
            function sub(uint256 a, uint256 b) internal pure returns (uint256) {
                require(b <= a);
                uint256 c = a - b;
                return c;
            }
            /**
             * @dev Adds two unsigned integers, reverts on overflow.
             */
            function add(uint256 a, uint256 b) internal pure returns (uint256) {
                uint256 c = a + b;
                require(c >= a);
                return c;
            }
            /**
             * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo),
             * reverts when dividing by zero.
             */
            function mod(uint256 a, uint256 b) internal pure returns (uint256) {
                require(b != 0);
                return a % b;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.7.0;
        import "./IERC20.sol";
        /**
         * @title ERC20Detailed token
         * @dev The decimals are only for visualization purposes.
         * All the operations are done using the smallest and indivisible token unit,
         * just as on Ethereum all the operations are done in wei.
         */
        abstract contract ERC20Detailed is IERC20 {
            string private _name;
            string private _symbol;
            uint8 private _decimals;
            constructor (string memory name, string memory symbol, uint8 decimals) {
                _name = name;
                _symbol = symbol;
                _decimals = decimals;
            }
            /**
             * @return the name of the token.
             */
            function name() public view returns (string memory) {
                return _name;
            }
            /**
             * @return the symbol of the token.
             */
            function symbol() public view returns (string memory) {
                return _symbol;
            }
            /**
             * @return the number of decimals of the token.
             */
            function decimals() public view returns (uint8) {
                return _decimals;
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.7.0;
        /**
         * @title Math
         * @dev Assorted math operations
         */
        library Math {
            /**
             * @dev Returns the largest of two numbers.
             */
            function max(uint256 a, uint256 b) internal pure returns (uint256) {
                return a >= b ? a : b;
            }
            /**
             * @dev Returns the smallest of two numbers.
             */
            function min(uint256 a, uint256 b) internal pure returns (uint256) {
                return a < b ? a : b;
            }
            /**
             * @dev Calculates the average of two numbers. Since these are integers,
             * averages of an even and odd number cannot be represented, and will be
             * rounded down.
             */
            function average(uint256 a, uint256 b) internal pure returns (uint256) {
                // (a + b) / 2 can overflow, so we distribute
                return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../../zeppelin/ownership/Ownable.sol";
        /**
        * @notice Base contract for upgradeable contract
        * @dev Inherited contract should implement verifyState(address) method by checking storage variables
        * (see verifyState(address) in Dispatcher). Also contract should implement finishUpgrade(address)
        * if it is using constructor parameters by coping this parameters to the dispatcher storage
        */
        abstract contract Upgradeable is Ownable {
            event StateVerified(address indexed testTarget, address sender);
            event UpgradeFinished(address indexed target, address sender);
            /**
            * @dev Contracts at the target must reserve the same location in storage for this address as in Dispatcher
            * Stored data actually lives in the Dispatcher
            * However the storage layout is specified here in the implementing contracts
            */
            address public target;
            /**
            * @dev Previous contract address (if available). Used for rollback
            */
            address public previousTarget;
            /**
            * @dev Upgrade status. Explicit `uint8` type is used instead of `bool` to save gas by excluding 0 value
            */
            uint8 public isUpgrade;
            /**
            * @dev Guarantees that next slot will be separated from the previous
            */
            uint256 stubSlot;
            /**
            * @dev Constants for `isUpgrade` field
            */
            uint8 constant UPGRADE_FALSE = 1;
            uint8 constant UPGRADE_TRUE = 2;
            /**
            * @dev Checks that function executed while upgrading
            * Recommended to add to `verifyState` and `finishUpgrade` methods
            */
            modifier onlyWhileUpgrading()
            {
                require(isUpgrade == UPGRADE_TRUE);
                _;
            }
            /**
            * @dev Method for verifying storage state.
            * Should check that new target contract returns right storage value
            */
            function verifyState(address _testTarget) public virtual onlyWhileUpgrading {
                emit StateVerified(_testTarget, msg.sender);
            }
            /**
            * @dev Copy values from the new target to the current storage
            * @param _target New target contract address
            */
            function finishUpgrade(address _target) public virtual onlyWhileUpgrading {
                emit UpgradeFinished(_target, msg.sender);
            }
            /**
            * @dev Base method to get data
            * @param _target Target to call
            * @param _selector Method selector
            * @param _numberOfArguments Number of used arguments
            * @param _argument1 First method argument
            * @param _argument2 Second method argument
            * @return memoryAddress Address in memory where the data is located
            */
            function delegateGetData(
                address _target,
                bytes4 _selector,
                uint8 _numberOfArguments,
                bytes32 _argument1,
                bytes32 _argument2
            )
                internal returns (bytes32 memoryAddress)
            {
                assembly {
                    memoryAddress := mload(0x40)
                    mstore(memoryAddress, _selector)
                    if gt(_numberOfArguments, 0) {
                        mstore(add(memoryAddress, 0x04), _argument1)
                    }
                    if gt(_numberOfArguments, 1) {
                        mstore(add(memoryAddress, 0x24), _argument2)
                    }
                    switch delegatecall(gas(), _target, memoryAddress, add(0x04, mul(0x20, _numberOfArguments)), 0, 0)
                        case 0 {
                            revert(memoryAddress, 0)
                        }
                        default {
                            returndatacopy(memoryAddress, 0x0, returndatasize())
                        }
                }
            }
            /**
            * @dev Call "getter" without parameters.
            * Result should not exceed 32 bytes
            */
            function delegateGet(address _target, bytes4 _selector)
                internal returns (uint256 result)
            {
                bytes32 memoryAddress = delegateGetData(_target, _selector, 0, 0, 0);
                assembly {
                    result := mload(memoryAddress)
                }
            }
            /**
            * @dev Call "getter" with one parameter.
            * Result should not exceed 32 bytes
            */
            function delegateGet(address _target, bytes4 _selector, bytes32 _argument)
                internal returns (uint256 result)
            {
                bytes32 memoryAddress = delegateGetData(_target, _selector, 1, _argument, 0);
                assembly {
                    result := mload(memoryAddress)
                }
            }
            /**
            * @dev Call "getter" with two parameters.
            * Result should not exceed 32 bytes
            */
            function delegateGet(
                address _target,
                bytes4 _selector,
                bytes32 _argument1,
                bytes32 _argument2
            )
                internal returns (uint256 result)
            {
                bytes32 memoryAddress = delegateGetData(_target, _selector, 2, _argument1, _argument2);
                assembly {
                    result := mload(memoryAddress)
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.7.0;
        /**
         * @title Ownable
         * @dev The Ownable contract has an owner address, and provides basic authorization control
         * functions, this simplifies the implementation of "user permissions".
         */
        abstract contract Ownable {
            address private _owner;
            event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
            /**
             * @dev The Ownable constructor sets the original `owner` of the contract to the sender
             * account.
             */
            constructor () {
                _owner = msg.sender;
                emit OwnershipTransferred(address(0), _owner);
            }
            /**
             * @return the address of the owner.
             */
            function owner() public view returns (address) {
                return _owner;
            }
            /**
             * @dev Throws if called by any account other than the owner.
             */
            modifier onlyOwner() {
                require(isOwner());
                _;
            }
            /**
             * @return true if `msg.sender` is the owner of the contract.
             */
            function isOwner() public view returns (bool) {
                return msg.sender == _owner;
            }
            /**
             * @dev Allows the current owner to relinquish control of the contract.
             * @notice Renouncing to ownership will leave the contract without an owner.
             * It will not be possible to call the functions with the `onlyOwner`
             * modifier anymore.
             */
            function renounceOwnership() public onlyOwner {
                emit OwnershipTransferred(_owner, address(0));
                _owner = address(0);
            }
            /**
             * @dev Allows the current owner to transfer control of the contract to a newOwner.
             * @param newOwner The address to transfer ownership to.
             */
            function transferOwnership(address newOwner) public virtual onlyOwner {
                _transferOwnership(newOwner);
            }
            /**
             * @dev Transfers control of the contract to a newOwner.
             * @param newOwner The address to transfer ownership to.
             */
            function _transferOwnership(address newOwner) internal {
                require(newOwner != address(0));
                emit OwnershipTransferred(_owner, newOwner);
                _owner = newOwner;
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../../zeppelin/math/SafeMath.sol";
        /**
        * @notice Additional math operations
        */
        library AdditionalMath {
            using SafeMath for uint256;
            function max16(uint16 a, uint16 b) internal pure returns (uint16) {
                return a >= b ? a : b;
            }
            function min16(uint16 a, uint16 b) internal pure returns (uint16) {
                return a < b ? a : b;
            }
            /**
            * @notice Division and ceil
            */
            function divCeil(uint256 a, uint256 b) internal pure returns (uint256) {
                return (a.add(b) - 1) / b;
            }
            /**
            * @dev Adds signed value to unsigned value, throws on overflow.
            */
            function addSigned(uint256 a, int256 b) internal pure returns (uint256) {
                if (b >= 0) {
                    return a.add(uint256(b));
                } else {
                    return a.sub(uint256(-b));
                }
            }
            /**
            * @dev Subtracts signed value from unsigned value, throws on overflow.
            */
            function subSigned(uint256 a, int256 b) internal pure returns (uint256) {
                if (b >= 0) {
                    return a.sub(uint256(b));
                } else {
                    return a.add(uint256(-b));
                }
            }
            /**
            * @dev Multiplies two numbers, throws on overflow.
            */
            function mul32(uint32 a, uint32 b) internal pure returns (uint32) {
                if (a == 0) {
                    return 0;
                }
                uint32 c = a * b;
                assert(c / a == b);
                return c;
            }
            /**
            * @dev Adds two numbers, throws on overflow.
            */
            function add16(uint16 a, uint16 b) internal pure returns (uint16) {
                uint16 c = a + b;
                assert(c >= a);
                return c;
            }
            /**
            * @dev Subtracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
            */
            function sub16(uint16 a, uint16 b) internal pure returns (uint16) {
                assert(b <= a);
                return a - b;
            }
            /**
            * @dev Adds signed value to unsigned value, throws on overflow.
            */
            function addSigned16(uint16 a, int16 b) internal pure returns (uint16) {
                if (b >= 0) {
                    return add16(a, uint16(b));
                } else {
                    return sub16(a, uint16(-b));
                }
            }
            /**
            * @dev Subtracts signed value from unsigned value, throws on overflow.
            */
            function subSigned16(uint16 a, int16 b) internal pure returns (uint16) {
                if (b >= 0) {
                    return sub16(a, uint16(b));
                } else {
                    return add16(a, uint16(-b));
                }
            }
        }
        // SPDX-License-Identifier: MIT
        pragma solidity ^0.7.0;
        import "./IERC20.sol";
        import "../../math/SafeMath.sol";
        /**
         * @title SafeERC20
         * @dev Wrappers around ERC20 operations that throw on failure.
         * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract,
         * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
         */
        library SafeERC20 {
            using SafeMath for uint256;
            function safeTransfer(IERC20 token, address to, uint256 value) internal {
                require(token.transfer(to, value));
            }
            function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
                require(token.transferFrom(from, to, value));
            }
            function safeApprove(IERC20 token, address spender, uint256 value) internal {
                // safeApprove should only be called when setting an initial allowance,
                // or when resetting it to zero. To increase and decrease it, use
                // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
                require((value == 0) || (token.allowance(msg.sender, spender) == 0));
                require(token.approve(spender, value));
            }
            function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                uint256 newAllowance = token.allowance(address(this), spender).add(value);
                require(token.approve(spender, newAllowance));
            }
            function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
                uint256 newAllowance = token.allowance(address(this), spender).sub(value);
                require(token.approve(spender, newAllowance));
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        /**
        * @dev Taken from https://github.com/ethereum/solidity-examples/blob/master/src/bits/Bits.sol
        */
        library Bits {
            uint256 internal constant ONE = uint256(1);
            /**
            * @notice Sets the bit at the given 'index' in 'self' to:
            *  '1' - if the bit is '0'
            *  '0' - if the bit is '1'
            * @return The modified value
            */
            function toggleBit(uint256 self, uint8 index) internal pure returns (uint256) {
                return self ^ ONE << index;
            }
            /**
            * @notice Get the value of the bit at the given 'index' in 'self'.
            */
            function bit(uint256 self, uint8 index) internal pure returns (uint8) {
                return uint8(self >> index & 1);
            }
            /**
            * @notice Check if the bit at the given 'index' in 'self' is set.
            * @return  'true' - if the value of the bit is '1',
            *          'false' - if the value of the bit is '0'
            */
            function bitSet(uint256 self, uint8 index) internal pure returns (bool) {
                return self >> index & 1 == 1;
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        /**
         * @title Snapshot
         * @notice Manages snapshots of size 128 bits (32 bits for timestamp, 96 bits for value)
         * 96 bits is enough for storing NU token values, and 32 bits should be OK for block numbers
         * @dev Since each storage slot can hold two snapshots, new slots are allocated every other TX. Thus, gas cost of adding snapshots is 51400 and 36400 gas, alternately.
         * Based on Aragon's Checkpointing (https://https://github.com/aragonone/voting-connectors/blob/master/shared/contract-utils/contracts/Checkpointing.sol)
         * On average, adding snapshots spends ~6500 less gas than the 256-bit checkpoints of Aragon's Checkpointing
         */
        library Snapshot {
            function encodeSnapshot(uint32 _time, uint96 _value) internal pure returns(uint128) {
                return uint128(uint256(_time) << 96 | uint256(_value));
            }
            function decodeSnapshot(uint128 _snapshot) internal pure returns(uint32 time, uint96 value){
                time = uint32(bytes4(bytes16(_snapshot)));
                value = uint96(_snapshot);
            }
            function addSnapshot(uint128[] storage _self, uint256 _value) internal {
                addSnapshot(_self, block.number, _value);
            }
            function addSnapshot(uint128[] storage _self, uint256 _time, uint256 _value) internal {
                uint256 length = _self.length;
                if (length != 0) {
                    (uint32 currentTime, ) = decodeSnapshot(_self[length - 1]);
                    if (uint32(_time) == currentTime) {
                        _self[length - 1] = encodeSnapshot(uint32(_time), uint96(_value));
                        return;
                    } else if (uint32(_time) < currentTime){
                        revert();
                    }
                }
                _self.push(encodeSnapshot(uint32(_time), uint96(_value)));
            }
            function lastSnapshot(uint128[] storage _self) internal view returns (uint32, uint96) {
                uint256 length = _self.length;
                if (length > 0) {
                    return decodeSnapshot(_self[length - 1]);
                }
                return (0, 0);
            }
            function lastValue(uint128[] storage _self) internal view returns (uint96) {
                (, uint96 value) = lastSnapshot(_self);
                return value;
            }
            function getValueAt(uint128[] storage _self, uint256 _time256) internal view returns (uint96) {
                uint32 _time = uint32(_time256);
                uint256 length = _self.length;
                // Short circuit if there's no checkpoints yet
                // Note that this also lets us avoid using SafeMath later on, as we've established that
                // there must be at least one checkpoint
                if (length == 0) {
                    return 0;
                }
                // Check last checkpoint
                uint256 lastIndex = length - 1;
                (uint32 snapshotTime, uint96 snapshotValue) = decodeSnapshot(_self[length - 1]);
                if (_time >= snapshotTime) {
                    return snapshotValue;
                }
                // Check first checkpoint (if not already checked with the above check on last)
                (snapshotTime, snapshotValue) = decodeSnapshot(_self[0]);
                if (length == 1 || _time < snapshotTime) {
                    return 0;
                }
                // Do binary search
                // As we've already checked both ends, we don't need to check the last checkpoint again
                uint256 low = 0;
                uint256 high = lastIndex - 1;
                uint32 midTime;
                uint96 midValue;
                while (high > low) {
                    uint256 mid = (high + low + 1) / 2; // average, ceil round
                    (midTime, midValue) = decodeSnapshot(_self[mid]);
                    if (_time > midTime) {
                        low = mid;
                    } else if (_time < midTime) {
                        // Note that we don't need SafeMath here because mid must always be greater than 0
                        // from the while condition
                        high = mid - 1;
                    } else {
                        // _time == midTime
                        return midValue;
                    }
                }
                (, snapshotValue) = decodeSnapshot(_self[low]);
                return snapshotValue;
            }
        }
        // SPDX-License-Identifier: GPL-3.0-or-later
        pragma solidity ^0.7.0;
        interface IForwarder {
            function isForwarder() external pure returns (bool);
            function canForward(address sender, bytes calldata evmCallScript) external view returns (bool);
            function forward(bytes calldata evmCallScript) external;
            
        }
        // SPDX-License-Identifier: GPL-3.0-or-later
        pragma solidity ^0.7.0;
        interface TokenManager {
            function mint(address _receiver, uint256 _amount) external;
            function issue(uint256 _amount) external;
            function assign(address _receiver, uint256 _amount) external;
            function burn(address _holder, uint256 _amount) external;
        }
        // SPDX-License-Identifier: GPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "./IForwarder.sol";
        // Interface for Voting contract, as found in https://github.com/aragon/aragon-apps/blob/master/apps/voting/contracts/Voting.sol
        interface Voting is IForwarder{
            enum VoterState { Absent, Yea, Nay }
            // Public getters
            function token() external returns (address);
            function supportRequiredPct() external returns (uint64);
            function minAcceptQuorumPct() external returns (uint64);
            function voteTime() external returns (uint64);
            function votesLength() external returns (uint256);
            // Setters
            function changeSupportRequiredPct(uint64 _supportRequiredPct) external;
            function changeMinAcceptQuorumPct(uint64 _minAcceptQuorumPct) external;
            // Creating new votes
            function newVote(bytes calldata _executionScript, string memory _metadata) external returns (uint256 voteId);
            function newVote(bytes calldata _executionScript, string memory _metadata, bool _castVote, bool _executesIfDecided)
                external returns (uint256 voteId);
            // Voting
            function canVote(uint256 _voteId, address _voter) external view returns (bool);
            function vote(uint256 _voteId, bool _supports, bool _executesIfDecided) external;
            // Executing a passed vote
            function canExecute(uint256 _voteId) external view returns (bool);
            function executeVote(uint256 _voteId) external;
            // Additional info
            function getVote(uint256 _voteId) external view
                returns (
                    bool open,
                    bool executed,
                    uint64 startDate,
                    uint64 snapshotBlock,
                    uint64 supportRequired,
                    uint64 minAcceptQuorum,
                    uint256 yea,
                    uint256 nay,
                    uint256 votingPower,
                    bytes memory script
                );
            function getVoterState(uint256 _voteId, address _voter) external view returns (VoterState);
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../zeppelin/math/SafeMath.sol";
        /**
        * @notice Multi-signature contract with off-chain signing
        */
        contract MultiSig {
            using SafeMath for uint256;
            event Executed(address indexed sender, uint256 indexed nonce, address indexed destination, uint256 value);
            event OwnerAdded(address indexed owner);
            event OwnerRemoved(address indexed owner);
            event RequirementChanged(uint16 required);
            uint256 constant public MAX_OWNER_COUNT = 50;
            uint256 public nonce;
            uint8 public required;
            mapping (address => bool) public isOwner;
            address[] public owners;
            /**
            * @notice Only this contract can call method
            */
            modifier onlyThisContract() {
                require(msg.sender == address(this));
                _;
            }
            receive() external payable {}
            /**
            * @param _required Number of required signings
            * @param _owners List of initial owners.
            */
            constructor (uint8 _required, address[] memory _owners) {
                require(_owners.length <= MAX_OWNER_COUNT &&
                    _required <= _owners.length &&
                    _required > 0);
                for (uint256 i = 0; i < _owners.length; i++) {
                    address owner = _owners[i];
                    require(!isOwner[owner] && owner != address(0));
                    isOwner[owner] = true;
                }
                owners = _owners;
                required = _required;
            }
            /**
            * @notice Get unsigned hash for transaction parameters
            * @dev Follows ERC191 signature scheme: https://github.com/ethereum/EIPs/issues/191
            * @param _sender Trustee who will execute the transaction
            * @param _destination Destination address
            * @param _value Amount of ETH to transfer
            * @param _data Call data
            * @param _nonce Nonce
            */
            function getUnsignedTransactionHash(
                address _sender,
                address _destination,
                uint256 _value,
                bytes memory _data,
                uint256 _nonce
            )
                public view returns (bytes32)
            {
                return keccak256(
                    abi.encodePacked(byte(0x19), byte(0), address(this), _sender, _destination, _value, _data, _nonce));
            }
            /**
            * @dev Note that address recovered from signatures must be strictly increasing
            * @param _sigV Array of signatures values V
            * @param _sigR Array of signatures values R
            * @param _sigS Array of signatures values S
            * @param _destination Destination address
            * @param _value Amount of ETH to transfer
            * @param _data Call data
            */
            function execute(
                uint8[] calldata _sigV,
                bytes32[] calldata _sigR,
                bytes32[] calldata _sigS,
                address _destination,
                uint256 _value,
                bytes calldata _data
            )
                external
            {
                require(_sigR.length >= required &&
                    _sigR.length == _sigS.length &&
                    _sigR.length == _sigV.length);
                bytes32 txHash = getUnsignedTransactionHash(msg.sender, _destination, _value, _data, nonce);
                address lastAdd = address(0);
                for (uint256 i = 0; i < _sigR.length; i++) {
                    address recovered = ecrecover(txHash, _sigV[i], _sigR[i], _sigS[i]);
                    require(recovered > lastAdd && isOwner[recovered]);
                    lastAdd = recovered;
                }
                emit Executed(msg.sender, nonce, _destination, _value);
                nonce = nonce.add(1);
                (bool callSuccess,) = _destination.call{value: _value}(_data);
                require(callSuccess);
            }
            /**
            * @notice Allows to add a new owner
            * @dev Transaction has to be sent by `execute` method.
            * @param _owner Address of new owner
            */
            function addOwner(address _owner)
                external
                onlyThisContract
            {
                require(owners.length < MAX_OWNER_COUNT &&
                    _owner != address(0) &&
                    !isOwner[_owner]);
                isOwner[_owner] = true;
                owners.push(_owner);
                emit OwnerAdded(_owner);
            }
            /**
            * @notice Allows to remove an owner
            * @dev Transaction has to be sent by `execute` method.
            * @param _owner Address of owner
            */
            function removeOwner(address _owner)
                external
                onlyThisContract
            {
                require(owners.length > required && isOwner[_owner]);
                isOwner[_owner] = false;
                for (uint256 i = 0; i < owners.length - 1; i++) {
                    if (owners[i] == _owner) {
                        owners[i] = owners[owners.length - 1];
                        break;
                    }
                }
                owners.pop();
                emit OwnerRemoved(_owner);
            }
            /**
            * @notice Returns the number of owners of this MultiSig
            */
            function getNumberOfOwners() external view returns (uint256) {
                return owners.length;
            }
            /**
            * @notice Allows to change the number of required signatures
            * @dev Transaction has to be sent by `execute` method
            * @param _required Number of required signatures
            */
            function changeRequirement(uint8 _required)
                external
                onlyThisContract
            {
                require(_required <= owners.length && _required > 0);
                required = _required;
                emit RequirementChanged(_required);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../zeppelin/token/ERC20/SafeERC20.sol";
        import "../zeppelin/math/SafeMath.sol";
        import "../zeppelin/math/Math.sol";
        import "../zeppelin/utils/Address.sol";
        import "./lib/AdditionalMath.sol";
        import "./lib/SignatureVerifier.sol";
        import "./StakingEscrow.sol";
        import "./NuCypherToken.sol";
        import "./proxy/Upgradeable.sol";
        /**
        * @notice Contract holds policy data and locks accrued policy fees
        * @dev |v6.1.3|
        */
        contract PolicyManager is Upgradeable {
            using SafeERC20 for NuCypherToken;
            using SafeMath for uint256;
            using AdditionalMath for uint256;
            using AdditionalMath for int256;
            using AdditionalMath for uint16;
            using Address for address payable;
            event PolicyCreated(
                bytes16 indexed policyId,
                address indexed sponsor,
                address indexed owner,
                uint256 feeRate,
                uint64 startTimestamp,
                uint64 endTimestamp,
                uint256 numberOfNodes
            );
            event ArrangementRevoked(
                bytes16 indexed policyId,
                address indexed sender,
                address indexed node,
                uint256 value
            );
            event RefundForArrangement(
                bytes16 indexed policyId,
                address indexed sender,
                address indexed node,
                uint256 value
            );
            event PolicyRevoked(bytes16 indexed policyId, address indexed sender, uint256 value);
            event RefundForPolicy(bytes16 indexed policyId, address indexed sender, uint256 value);
            event MinFeeRateSet(address indexed node, uint256 value);
            // TODO #1501
            // Range range
            event FeeRateRangeSet(address indexed sender, uint256 min, uint256 defaultValue, uint256 max);
            event Withdrawn(address indexed node, address indexed recipient, uint256 value);
            struct ArrangementInfo {
                address node;
                uint256 indexOfDowntimePeriods;
                uint16 lastRefundedPeriod;
            }
            struct Policy {
                bool disabled;
                address payable sponsor;
                address owner;
                uint128 feeRate;
                uint64 startTimestamp;
                uint64 endTimestamp;
                uint256 reservedSlot1;
                uint256 reservedSlot2;
                uint256 reservedSlot3;
                uint256 reservedSlot4;
                uint256 reservedSlot5;
                ArrangementInfo[] arrangements;
            }
            struct NodeInfo {
                uint128 fee;
                uint16 previousFeePeriod;
                uint256 feeRate;
                uint256 minFeeRate;
                mapping (uint16 => int256) feeDelta;
            }
            // TODO used only for `delegateGetNodeInfo`, probably will be removed after #1512
            struct MemoryNodeInfo {
                uint128 fee;
                uint16 previousFeePeriod;
                uint256 feeRate;
                uint256 minFeeRate;
            }
            struct Range {
                uint128 min;
                uint128 defaultValue;
                uint128 max;
            }
            bytes16 internal constant RESERVED_POLICY_ID = bytes16(0);
            address internal constant RESERVED_NODE = address(0);
            uint256 internal constant MAX_BALANCE = uint256(uint128(0) - 1);
            // controlled overflow to get max int256
            int256 public constant DEFAULT_FEE_DELTA = int256((uint256(0) - 1) >> 1);
            StakingEscrow public immutable escrow;
            uint32 public immutable secondsPerPeriod;
            mapping (bytes16 => Policy) public policies;
            mapping (address => NodeInfo) public nodes;
            Range public feeRateRange;
            /**
            * @notice Constructor sets address of the escrow contract
            * @param _escrow Escrow contract
            */
            constructor(StakingEscrow _escrow) {
                // if the input address is not the StakingEscrow then calling `secondsPerPeriod` will throw error
                uint32 localSecondsPerPeriod = _escrow.secondsPerPeriod();
                require(localSecondsPerPeriod > 0);
                secondsPerPeriod = localSecondsPerPeriod;
                escrow = _escrow;
            }
            /**
            * @dev Checks that sender is the StakingEscrow contract
            */
            modifier onlyEscrowContract()
            {
                require(msg.sender == address(escrow));
                _;
            }
            /**
            * @return Number of current period
            */
            function getCurrentPeriod() public view returns (uint16) {
                return uint16(block.timestamp / secondsPerPeriod);
            }
            /**
            * @notice Register a node
            * @param _node Node address
            * @param _period Initial period
            */
            function register(address _node, uint16 _period) external onlyEscrowContract {
                NodeInfo storage nodeInfo = nodes[_node];
                require(nodeInfo.previousFeePeriod == 0 && _period < getCurrentPeriod());
                nodeInfo.previousFeePeriod = _period;
            }
            /**
            * @notice Set minimum, default & maximum fee rate for all stakers and all policies ('global fee range')
            */
            // TODO # 1501
            // function setFeeRateRange(Range calldata _range) external onlyOwner {
            function setFeeRateRange(uint128 _min, uint128 _default, uint128 _max) external onlyOwner {
                require(_min <= _default && _default <= _max);
                feeRateRange = Range(_min, _default, _max);
                emit FeeRateRangeSet(msg.sender, _min, _default, _max);
            }
            /**
            * @notice Set the minimum acceptable fee rate (set by staker for their associated worker)
            * @dev Input value must fall within `feeRateRange` (global fee range)
            */
            function setMinFeeRate(uint256 _minFeeRate) external {
                require(_minFeeRate >= feeRateRange.min &&
                    _minFeeRate <= feeRateRange.max,
                    "The staker's min fee rate must fall within the global fee range");
                NodeInfo storage nodeInfo = nodes[msg.sender];
                if (nodeInfo.minFeeRate == _minFeeRate) {
                    return;
                }
                nodeInfo.minFeeRate = _minFeeRate;
                emit MinFeeRateSet(msg.sender, _minFeeRate);
            }
            /**
            * @notice Get the minimum acceptable fee rate (set by staker for their associated worker)
            */
            function getMinFeeRate(NodeInfo storage _nodeInfo) internal view returns (uint256) {
                // if minFeeRate has not been set or chosen value falls outside the global fee range
                // a default value is returned instead
                if (_nodeInfo.minFeeRate == 0 ||
                    _nodeInfo.minFeeRate < feeRateRange.min ||
                    _nodeInfo.minFeeRate > feeRateRange.max) {
                    return feeRateRange.defaultValue;
                } else {
                    return _nodeInfo.minFeeRate;
                }
            }
            /**
            * @notice Get the minimum acceptable fee rate (set by staker for their associated worker)
            */
            function getMinFeeRate(address _node) public view returns (uint256) {
                NodeInfo storage nodeInfo = nodes[_node];
                return getMinFeeRate(nodeInfo);
            }
            /**
            * @notice Create policy
            * @dev Generate policy id before creation
            * @param _policyId Policy id
            * @param _policyOwner Policy owner. Zero address means sender is owner
            * @param _endTimestamp End timestamp of the policy in seconds
            * @param _nodes Nodes that will handle policy
            */
            function createPolicy(
                bytes16 _policyId,
                address _policyOwner,
                uint64 _endTimestamp,
                address[] calldata _nodes
            )
                external payable
            {
                Policy storage policy = policies[_policyId];
                require(
                    _policyId != RESERVED_POLICY_ID &&
                    policy.feeRate == 0 &&
                    !policy.disabled &&
                    _endTimestamp > block.timestamp &&
                    msg.value > 0
                );
                require(address(this).balance <= MAX_BALANCE);
                uint16 currentPeriod = getCurrentPeriod();
                uint16 endPeriod = uint16(_endTimestamp / secondsPerPeriod) + 1;
                uint256 numberOfPeriods = endPeriod - currentPeriod;
                policy.sponsor = msg.sender;
                policy.startTimestamp = uint64(block.timestamp);
                policy.endTimestamp = _endTimestamp;
                policy.feeRate = uint128(msg.value.div(_nodes.length) / numberOfPeriods);
                require(policy.feeRate > 0 && policy.feeRate * numberOfPeriods * _nodes.length  == msg.value);
                if (_policyOwner != msg.sender && _policyOwner != address(0)) {
                    policy.owner = _policyOwner;
                }
                for (uint256 i = 0; i < _nodes.length; i++) {
                    address node = _nodes[i];
                    require(node != RESERVED_NODE);
                    NodeInfo storage nodeInfo = nodes[node];
                    require(nodeInfo.previousFeePeriod != 0 &&
                        nodeInfo.previousFeePeriod < currentPeriod &&
                        policy.feeRate >= getMinFeeRate(nodeInfo));
                    // Check default value for feeDelta
                    if (nodeInfo.feeDelta[currentPeriod] == DEFAULT_FEE_DELTA) {
                        nodeInfo.feeDelta[currentPeriod] = int256(policy.feeRate);
                    } else {
                        // Overflow protection removed, because ETH total supply less than uint255/int256
                        nodeInfo.feeDelta[currentPeriod] += int256(policy.feeRate);
                    }
                    if (nodeInfo.feeDelta[endPeriod] == DEFAULT_FEE_DELTA) {
                        nodeInfo.feeDelta[endPeriod] = -int256(policy.feeRate);
                    } else {
                        nodeInfo.feeDelta[endPeriod] -= int256(policy.feeRate);
                    }
                    // Reset to default value if needed
                    if (nodeInfo.feeDelta[currentPeriod] == 0) {
                        nodeInfo.feeDelta[currentPeriod] = DEFAULT_FEE_DELTA;
                    }
                    if (nodeInfo.feeDelta[endPeriod] == 0) {
                        nodeInfo.feeDelta[endPeriod] = DEFAULT_FEE_DELTA;
                    }
                    policy.arrangements.push(ArrangementInfo(node, 0, 0));
                }
                emit PolicyCreated(
                    _policyId,
                    msg.sender,
                    _policyOwner == address(0) ? msg.sender : _policyOwner,
                    policy.feeRate,
                    policy.startTimestamp,
                    policy.endTimestamp,
                    _nodes.length
                );
            }
            /**
            * @notice Get policy owner
            */
            function getPolicyOwner(bytes16 _policyId) public view returns (address) {
                Policy storage policy = policies[_policyId];
                return policy.owner == address(0) ? policy.sponsor : policy.owner;
            }
            /**
            * @notice Call from StakingEscrow to update node info once per period.
            * Set default `feeDelta` value for specified period and update node fee
            * @param _node Node address
            * @param _processedPeriod1 Processed period
            * @param _processedPeriod2 Processed period
            * @param _periodToSetDefault Period to set
            */
            function ping(
                address _node,
                uint16 _processedPeriod1,
                uint16 _processedPeriod2,
                uint16 _periodToSetDefault
            )
                external onlyEscrowContract
            {
                NodeInfo storage node = nodes[_node];
                if (_processedPeriod1 != 0) {
                    updateFee(node, _processedPeriod1);
                }
                if (_processedPeriod2 != 0) {
                    updateFee(node, _processedPeriod2);
                }
                // This code increases gas cost for node in trade of decreasing cost for policy sponsor
                if (_periodToSetDefault != 0 && node.feeDelta[_periodToSetDefault] == 0) {
                    node.feeDelta[_periodToSetDefault] = DEFAULT_FEE_DELTA;
                }
            }
            /**
            * @notice Update node fee
            * @param _info Node info structure
            * @param _period Processed period
            */
            function updateFee(NodeInfo storage _info, uint16 _period) internal {
                if (_info.previousFeePeriod == 0 || _period <= _info.previousFeePeriod) {
                    return;
                }
                for (uint16 i = _info.previousFeePeriod + 1; i <= _period; i++) {
                    int256 delta = _info.feeDelta[i];
                    if (delta == DEFAULT_FEE_DELTA) {
                        // gas refund
                        _info.feeDelta[i] = 0;
                        continue;
                    }
                    _info.feeRate = _info.feeRate.addSigned(delta);
                    // gas refund
                    _info.feeDelta[i] = 0;
                }
                _info.previousFeePeriod = _period;
                _info.fee += uint128(_info.feeRate);
            }
            /**
            * @notice Withdraw fee by node
            */
            function withdraw() external returns (uint256) {
                return withdraw(msg.sender);
            }
            /**
            * @notice Withdraw fee by node
            * @param _recipient Recipient of the fee
            */
            function withdraw(address payable _recipient) public returns (uint256) {
                NodeInfo storage node = nodes[msg.sender];
                uint256 fee = node.fee;
                require(fee != 0);
                node.fee = 0;
                _recipient.sendValue(fee);
                emit Withdrawn(msg.sender, _recipient, fee);
                return fee;
            }
            /**
            * @notice Calculate amount of refund
            * @param _policy Policy
            * @param _arrangement Arrangement
            */
            function calculateRefundValue(Policy storage _policy, ArrangementInfo storage _arrangement)
                internal view returns (uint256 refundValue, uint256 indexOfDowntimePeriods, uint16 lastRefundedPeriod)
            {
                uint16 policyStartPeriod = uint16(_policy.startTimestamp / secondsPerPeriod);
                uint16 maxPeriod = AdditionalMath.min16(getCurrentPeriod(), uint16(_policy.endTimestamp / secondsPerPeriod));
                uint16 minPeriod = AdditionalMath.max16(policyStartPeriod, _arrangement.lastRefundedPeriod);
                uint16 downtimePeriods = 0;
                uint256 length = escrow.getPastDowntimeLength(_arrangement.node);
                uint256 initialIndexOfDowntimePeriods;
                if (_arrangement.lastRefundedPeriod == 0) {
                    initialIndexOfDowntimePeriods = escrow.findIndexOfPastDowntime(_arrangement.node, policyStartPeriod);
                } else {
                    initialIndexOfDowntimePeriods = _arrangement.indexOfDowntimePeriods;
                }
                for (indexOfDowntimePeriods = initialIndexOfDowntimePeriods;
                     indexOfDowntimePeriods < length;
                     indexOfDowntimePeriods++)
                {
                    (uint16 startPeriod, uint16 endPeriod) =
                        escrow.getPastDowntime(_arrangement.node, indexOfDowntimePeriods);
                    if (startPeriod > maxPeriod) {
                        break;
                    } else if (endPeriod < minPeriod) {
                        continue;
                    }
                    downtimePeriods += AdditionalMath.min16(maxPeriod, endPeriod)
                        .sub16(AdditionalMath.max16(minPeriod, startPeriod)) + 1;
                    if (maxPeriod <= endPeriod) {
                        break;
                    }
                }
                uint16 lastCommittedPeriod = escrow.getLastCommittedPeriod(_arrangement.node);
                if (indexOfDowntimePeriods == length && lastCommittedPeriod < maxPeriod) {
                    // Overflow protection removed:
                    // lastCommittedPeriod < maxPeriod and minPeriod <= maxPeriod + 1
                    downtimePeriods += maxPeriod - AdditionalMath.max16(minPeriod - 1, lastCommittedPeriod);
                }
                refundValue = _policy.feeRate * downtimePeriods;
                lastRefundedPeriod = maxPeriod + 1;
            }
            /**
            * @notice Revoke/refund arrangement/policy by the sponsor
            * @param _policyId Policy id
            * @param _node Node that will be excluded or RESERVED_NODE if full policy should be used
            ( @param _forceRevoke Force revoke arrangement/policy
            */
            function refundInternal(bytes16 _policyId, address _node, bool _forceRevoke)
                internal returns (uint256 refundValue)
            {
                refundValue = 0;
                Policy storage policy = policies[_policyId];
                require(!policy.disabled);
                uint16 endPeriod = uint16(policy.endTimestamp / secondsPerPeriod) + 1;
                uint256 numberOfActive = policy.arrangements.length;
                uint256 i = 0;
                for (; i < policy.arrangements.length; i++) {
                    ArrangementInfo storage arrangement = policy.arrangements[i];
                    address node = arrangement.node;
                    if (node == RESERVED_NODE || _node != RESERVED_NODE && _node != node) {
                        numberOfActive--;
                        continue;
                    }
                    uint256 nodeRefundValue;
                    (nodeRefundValue, arrangement.indexOfDowntimePeriods, arrangement.lastRefundedPeriod) =
                        calculateRefundValue(policy, arrangement);
                    if (_forceRevoke) {
                        NodeInfo storage nodeInfo = nodes[node];
                        // Check default value for feeDelta
                        uint16 lastRefundedPeriod = arrangement.lastRefundedPeriod;
                        if (nodeInfo.feeDelta[lastRefundedPeriod] == DEFAULT_FEE_DELTA) {
                            nodeInfo.feeDelta[lastRefundedPeriod] = -int256(policy.feeRate);
                        } else {
                            nodeInfo.feeDelta[lastRefundedPeriod] -= int256(policy.feeRate);
                        }
                        if (nodeInfo.feeDelta[endPeriod] == DEFAULT_FEE_DELTA) {
                            nodeInfo.feeDelta[endPeriod] = -int256(policy.feeRate);
                        } else {
                            nodeInfo.feeDelta[endPeriod] += int256(policy.feeRate);
                        }
                        // Reset to default value if needed
                        if (nodeInfo.feeDelta[lastRefundedPeriod] == 0) {
                            nodeInfo.feeDelta[lastRefundedPeriod] = DEFAULT_FEE_DELTA;
                        }
                        if (nodeInfo.feeDelta[endPeriod] == 0) {
                            nodeInfo.feeDelta[endPeriod] = DEFAULT_FEE_DELTA;
                        }
                        nodeRefundValue += uint256(endPeriod - lastRefundedPeriod) * policy.feeRate;
                    }
                    if (_forceRevoke || arrangement.lastRefundedPeriod >= endPeriod) {
                        arrangement.node = RESERVED_NODE;
                        arrangement.indexOfDowntimePeriods = 0;
                        arrangement.lastRefundedPeriod = 0;
                        numberOfActive--;
                        emit ArrangementRevoked(_policyId, msg.sender, node, nodeRefundValue);
                    } else {
                        emit RefundForArrangement(_policyId, msg.sender, node, nodeRefundValue);
                    }
                    refundValue += nodeRefundValue;
                    if (_node != RESERVED_NODE) {
                       break;
                    }
                }
                address payable policySponsor = policy.sponsor;
                if (_node == RESERVED_NODE) {
                    if (numberOfActive == 0) {
                        policy.disabled = true;
                        // gas refund
                        policy.sponsor = address(0);
                        policy.owner = address(0);
                        policy.feeRate = 0;
                        policy.startTimestamp = 0;
                        policy.endTimestamp = 0;
                        emit PolicyRevoked(_policyId, msg.sender, refundValue);
                    } else {
                        emit RefundForPolicy(_policyId, msg.sender, refundValue);
                    }
                } else {
                    // arrangement not found
                    require(i < policy.arrangements.length);
                }
                if (refundValue > 0) {
                    policySponsor.sendValue(refundValue);
                }
            }
            /**
            * @notice Calculate amount of refund
            * @param _policyId Policy id
            * @param _node Node or RESERVED_NODE if all nodes should be used
            */
            function calculateRefundValueInternal(bytes16 _policyId, address _node)
                internal view returns (uint256 refundValue)
            {
                refundValue = 0;
                Policy storage policy = policies[_policyId];
                require((policy.owner == msg.sender || policy.sponsor == msg.sender) && !policy.disabled);
                uint256 i = 0;
                for (; i < policy.arrangements.length; i++) {
                    ArrangementInfo storage arrangement = policy.arrangements[i];
                    if (arrangement.node == RESERVED_NODE || _node != RESERVED_NODE && _node != arrangement.node) {
                        continue;
                    }
                    (uint256 nodeRefundValue,,) = calculateRefundValue(policy, arrangement);
                    refundValue += nodeRefundValue;
                    if (_node != RESERVED_NODE) {
                       break;
                    }
                }
                if (_node != RESERVED_NODE) {
                    // arrangement not found
                    require(i < policy.arrangements.length);
                }
            }
            /**
            * @notice Revoke policy by the sponsor
            * @param _policyId Policy id
            */
            function revokePolicy(bytes16 _policyId) external returns (uint256 refundValue) {
                require(getPolicyOwner(_policyId) == msg.sender);
                return refundInternal(_policyId, RESERVED_NODE, true);
            }
            /**
            * @notice Revoke arrangement by the sponsor
            * @param _policyId Policy id
            * @param _node Node that will be excluded
            */
            function revokeArrangement(bytes16 _policyId, address _node)
                external returns (uint256 refundValue)
            {
                require(_node != RESERVED_NODE);
                require(getPolicyOwner(_policyId) == msg.sender);
                return refundInternal(_policyId, _node, true);
            }
            /**
            * @notice Get unsigned hash for revocation
            * @param _policyId Policy id
            * @param _node Node that will be excluded
            * @return Revocation hash, EIP191 version 0x45 ('E')
            */
            function getRevocationHash(bytes16 _policyId, address _node) public view returns (bytes32) {
                return SignatureVerifier.hashEIP191(abi.encodePacked(_policyId, _node), byte(0x45));
            }
            /**
            * @notice Check correctness of signature
            * @param _policyId Policy id
            * @param _node Node that will be excluded, zero address if whole policy will be revoked
            * @param _signature Signature of owner
            */
            function checkOwnerSignature(bytes16 _policyId, address _node, bytes memory _signature) internal view {
                bytes32 hash = getRevocationHash(_policyId, _node);
                address recovered = SignatureVerifier.recover(hash, _signature);
                require(getPolicyOwner(_policyId) == recovered);
            }
            /**
            * @notice Revoke policy or arrangement using owner's signature
            * @param _policyId Policy id
            * @param _node Node that will be excluded, zero address if whole policy will be revoked
            * @param _signature Signature of owner, EIP191 version 0x45 ('E')
            */
            function revoke(bytes16 _policyId, address _node, bytes calldata _signature)
                external returns (uint256 refundValue)
            {
                checkOwnerSignature(_policyId, _node, _signature);
                return refundInternal(_policyId, _node, true);
            }
            /**
            * @notice Refund part of fee by the sponsor
            * @param _policyId Policy id
            */
            function refund(bytes16 _policyId) external {
                Policy storage policy = policies[_policyId];
                require(policy.owner == msg.sender || policy.sponsor == msg.sender);
                refundInternal(_policyId, RESERVED_NODE, false);
            }
            /**
            * @notice Refund part of one node's fee by the sponsor
            * @param _policyId Policy id
            * @param _node Node address
            */
            function refund(bytes16 _policyId, address _node)
                external returns (uint256 refundValue)
            {
                require(_node != RESERVED_NODE);
                Policy storage policy = policies[_policyId];
                require(policy.owner == msg.sender || policy.sponsor == msg.sender);
                return refundInternal(_policyId, _node, false);
            }
            /**
            * @notice Calculate amount of refund
            * @param _policyId Policy id
            */
            function calculateRefundValue(bytes16 _policyId)
                external view returns (uint256 refundValue)
            {
                return calculateRefundValueInternal(_policyId, RESERVED_NODE);
            }
            /**
            * @notice Calculate amount of refund
            * @param _policyId Policy id
            * @param _node Node
            */
            function calculateRefundValue(bytes16 _policyId, address _node)
                external view returns (uint256 refundValue)
            {
                require(_node != RESERVED_NODE);
                return calculateRefundValueInternal(_policyId, _node);
            }
            /**
            * @notice Get number of arrangements in the policy
            * @param _policyId Policy id
            */
            function getArrangementsLength(bytes16 _policyId) external view returns (uint256) {
                return policies[_policyId].arrangements.length;
            }
            /**
            * @notice Get information about staker's fee rate
            * @param _node Address of staker
            * @param _period Period to get fee delta
            */
            function getNodeFeeDelta(address _node, uint16 _period)
                // TODO "virtual" only for tests, probably will be removed after #1512
                external view virtual returns (int256)
            {
                return nodes[_node].feeDelta[_period];
            }
            /**
            * @notice Return the information about arrangement
            */
            function getArrangementInfo(bytes16 _policyId, uint256 _index)
            // TODO change to structure when ABIEncoderV2 is released (#1501)
        //        public view returns (ArrangementInfo)
                external view returns (address node, uint256 indexOfDowntimePeriods, uint16 lastRefundedPeriod)
            {
                ArrangementInfo storage info = policies[_policyId].arrangements[_index];
                node = info.node;
                indexOfDowntimePeriods = info.indexOfDowntimePeriods;
                lastRefundedPeriod = info.lastRefundedPeriod;
            }
            /**
            * @dev Get Policy structure by delegatecall
            */
            function delegateGetPolicy(address _target, bytes16 _policyId)
                internal returns (Policy memory result)
            {
                bytes32 memoryAddress = delegateGetData(_target, this.policies.selector, 1, bytes32(_policyId), 0);
                assembly {
                    result := memoryAddress
                }
            }
            /**
            * @dev Get ArrangementInfo structure by delegatecall
            */
            function delegateGetArrangementInfo(address _target, bytes16 _policyId, uint256 _index)
                internal returns (ArrangementInfo memory result)
            {
                bytes32 memoryAddress = delegateGetData(
                    _target, this.getArrangementInfo.selector, 2, bytes32(_policyId), bytes32(_index));
                assembly {
                    result := memoryAddress
                }
            }
            /**
            * @dev Get NodeInfo structure by delegatecall
            */
            function delegateGetNodeInfo(address _target, address _node)
                internal returns (MemoryNodeInfo memory result)
            {
                bytes32 memoryAddress = delegateGetData(_target, this.nodes.selector, 1, bytes32(uint256(_node)), 0);
                assembly {
                    result := memoryAddress
                }
            }
            /**
            * @dev Get feeRateRange structure by delegatecall
            */
            function delegateGetFeeRateRange(address _target) internal returns (Range memory result) {
                bytes32 memoryAddress = delegateGetData(_target, this.feeRateRange.selector, 0, 0, 0);
                assembly {
                    result := memoryAddress
                }
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
            function verifyState(address _testTarget) public override virtual {
                super.verifyState(_testTarget);
                Range memory rangeToCheck = delegateGetFeeRateRange(_testTarget);
                require(feeRateRange.min == rangeToCheck.min &&
                    feeRateRange.defaultValue == rangeToCheck.defaultValue &&
                    feeRateRange.max == rangeToCheck.max);
                Policy storage policy = policies[RESERVED_POLICY_ID];
                Policy memory policyToCheck = delegateGetPolicy(_testTarget, RESERVED_POLICY_ID);
                require(policyToCheck.sponsor == policy.sponsor &&
                    policyToCheck.owner == policy.owner &&
                    policyToCheck.feeRate == policy.feeRate &&
                    policyToCheck.startTimestamp == policy.startTimestamp &&
                    policyToCheck.endTimestamp == policy.endTimestamp &&
                    policyToCheck.disabled == policy.disabled);
                require(delegateGet(_testTarget, this.getArrangementsLength.selector, RESERVED_POLICY_ID) ==
                    policy.arrangements.length);
                if (policy.arrangements.length > 0) {
                    ArrangementInfo storage arrangement = policy.arrangements[0];
                    ArrangementInfo memory arrangementToCheck = delegateGetArrangementInfo(
                        _testTarget, RESERVED_POLICY_ID, 0);
                    require(arrangementToCheck.node == arrangement.node &&
                        arrangementToCheck.indexOfDowntimePeriods == arrangement.indexOfDowntimePeriods &&
                        arrangementToCheck.lastRefundedPeriod == arrangement.lastRefundedPeriod);
                }
                NodeInfo storage nodeInfo = nodes[RESERVED_NODE];
                MemoryNodeInfo memory nodeInfoToCheck = delegateGetNodeInfo(_testTarget, RESERVED_NODE);
                require(nodeInfoToCheck.fee == nodeInfo.fee &&
                    nodeInfoToCheck.feeRate == nodeInfo.feeRate &&
                    nodeInfoToCheck.previousFeePeriod == nodeInfo.previousFeePeriod &&
                    nodeInfoToCheck.minFeeRate == nodeInfo.minFeeRate);
                require(int256(delegateGet(_testTarget, this.getNodeFeeDelta.selector,
                    bytes32(bytes20(RESERVED_NODE)), bytes32(uint256(11)))) == nodeInfo.feeDelta[11]);
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade`
            function finishUpgrade(address _target) public override virtual {
                super.finishUpgrade(_target);
                // Create fake Policy and NodeInfo to use them in verifyState(address)
                Policy storage policy = policies[RESERVED_POLICY_ID];
                policy.sponsor = msg.sender;
                policy.owner = address(this);
                policy.startTimestamp = 1;
                policy.endTimestamp = 2;
                policy.feeRate = 3;
                policy.disabled = true;
                policy.arrangements.push(ArrangementInfo(RESERVED_NODE, 11, 22));
                NodeInfo storage nodeInfo = nodes[RESERVED_NODE];
                nodeInfo.fee = 100;
                nodeInfo.feeRate = 33;
                nodeInfo.previousFeePeriod = 44;
                nodeInfo.feeDelta[11] = 55;
                nodeInfo.minFeeRate = 777;
            }
        }// SPDX-License-Identifier: MIT
        pragma solidity ^0.7.0;
        /**
         * @dev Collection of functions related to the address type
         */
        library Address {
            /**
             * @dev Returns true if `account` is a contract.
             *
             * [IMPORTANT]
             * ====
             * It is unsafe to assume that an address for which this function returns
             * false is an externally-owned account (EOA) and not a contract.
             *
             * Among others, `isContract` will return false for the following
             * types of addresses:
             *
             *  - an externally-owned account
             *  - a contract in construction
             *  - an address where a contract will be created
             *  - an address where a contract lived, but was destroyed
             * ====
             */
            function isContract(address account) internal view returns (bool) {
                // According to EIP-1052, 0x0 is the value returned for not-yet created accounts
                // and 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 is returned
                // for accounts without code, i.e. `keccak256('')`
                bytes32 codehash;
                bytes32 accountHash = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470;
                // solhint-disable-next-line no-inline-assembly
                assembly { codehash := extcodehash(account) }
                return (codehash != accountHash && codehash != 0x0);
            }
            /**
             * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
             * `recipient`, forwarding all available gas and reverting on errors.
             *
             * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
             * of certain opcodes, possibly making contracts go over the 2300 gas limit
             * imposed by `transfer`, making them unable to receive funds via
             * `transfer`. {sendValue} removes this limitation.
             *
             * https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
             *
             * IMPORTANT: because control is transferred to `recipient`, care must be
             * taken to not create reentrancy vulnerabilities. Consider using
             * {ReentrancyGuard} or the
             * https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
             *
             * _Available since v2.4.0._
             */
            function sendValue(address payable recipient, uint256 amount) internal {
                require(address(this).balance >= amount, "Address: insufficient balance");
                // solhint-disable-next-line avoid-call-value
                (bool success, ) = recipient.call{value: amount}("");
                require(success, "Address: unable to send value, recipient may have reverted");
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "./Upgradeable.sol";
        import "../../zeppelin/utils/Address.sol";
        /**
        * @notice ERC897 - ERC DelegateProxy
        */
        interface ERCProxy {
            function proxyType() external pure returns (uint256);
            function implementation() external view returns (address);
        }
        /**
        * @notice Proxying requests to other contracts.
        * Client should use ABI of real contract and address of this contract
        */
        contract Dispatcher is Upgradeable, ERCProxy {
            using Address for address;
            event Upgraded(address indexed from, address indexed to, address owner);
            event RolledBack(address indexed from, address indexed to, address owner);
            /**
            * @dev Set upgrading status before and after operations
            */
            modifier upgrading()
            {
                isUpgrade = UPGRADE_TRUE;
                _;
                isUpgrade = UPGRADE_FALSE;
            }
            /**
            * @param _target Target contract address
            */
            constructor(address _target) upgrading {
                require(_target.isContract());
                // Checks that target contract inherits Dispatcher state
                verifyState(_target);
                // `verifyState` must work with its contract
                verifyUpgradeableState(_target, _target);
                target = _target;
                finishUpgrade();
                emit Upgraded(address(0), _target, msg.sender);
            }
            //------------------------ERC897------------------------
            /**
             * @notice ERC897, whether it is a forwarding (1) or an upgradeable (2) proxy
             */
            function proxyType() external pure override returns (uint256) {
                return 2;
            }
            /**
             * @notice ERC897, gets the address of the implementation where every call will be delegated
             */
            function implementation() external view override returns (address) {
                return target;
            }
            //------------------------------------------------------------
            /**
            * @notice Verify new contract storage and upgrade target
            * @param _target New target contract address
            */
            function upgrade(address _target) public onlyOwner upgrading {
                require(_target.isContract());
                // Checks that target contract has "correct" (as much as possible) state layout
                verifyState(_target);
                //`verifyState` must work with its contract
                verifyUpgradeableState(_target, _target);
                if (target.isContract()) {
                    verifyUpgradeableState(target, _target);
                }
                previousTarget = target;
                target = _target;
                finishUpgrade();
                emit Upgraded(previousTarget, _target, msg.sender);
            }
            /**
            * @notice Rollback to previous target
            * @dev Test storage carefully before upgrade again after rollback
            */
            function rollback() public onlyOwner upgrading {
                require(previousTarget.isContract());
                emit RolledBack(target, previousTarget, msg.sender);
                // should be always true because layout previousTarget -> target was already checked
                // but `verifyState` is not 100% accurate so check again
                verifyState(previousTarget);
                if (target.isContract()) {
                    verifyUpgradeableState(previousTarget, target);
                }
                target = previousTarget;
                previousTarget = address(0);
                finishUpgrade();
            }
            /**
            * @dev Call verifyState method for Upgradeable contract
            */
            function verifyUpgradeableState(address _from, address _to) private {
                (bool callSuccess,) = _from.delegatecall(abi.encodeWithSelector(this.verifyState.selector, _to));
                require(callSuccess);
            }
            /**
            * @dev Call finishUpgrade method from the Upgradeable contract
            */
            function finishUpgrade() private {
                (bool callSuccess,) = target.delegatecall(abi.encodeWithSelector(this.finishUpgrade.selector, target));
                require(callSuccess);
            }
            function verifyState(address _testTarget) public override onlyWhileUpgrading {
                //checks equivalence accessing state through new contract and current storage
                require(address(uint160(delegateGet(_testTarget, this.owner.selector))) == owner());
                require(address(uint160(delegateGet(_testTarget, this.target.selector))) == target);
                require(address(uint160(delegateGet(_testTarget, this.previousTarget.selector))) == previousTarget);
                require(uint8(delegateGet(_testTarget, this.isUpgrade.selector)) == isUpgrade);
            }
            /**
            * @dev Override function using empty code because no reason to call this function in Dispatcher
            */
            function finishUpgrade(address) public override {}
            /**
            * @dev Receive function sends empty request to the target contract
            */
            receive() external payable {
                assert(target.isContract());
                // execute receive function from target contract using storage of the dispatcher
                (bool callSuccess,) = target.delegatecall("");
                if (!callSuccess) {
                    revert();
                }
            }
            /**
            * @dev Fallback function sends all requests to the target contract
            */
            fallback() external payable {
                assert(target.isContract());
                // execute requested function from target contract using storage of the dispatcher
                (bool callSuccess,) = target.delegatecall(msg.data);
                if (callSuccess) {
                    // copy result of the request to the return data
                    // we can use the second return value from `delegatecall` (bytes memory)
                    // but it will consume a little more gas
                    assembly {
                        returndatacopy(0x0, 0x0, returndatasize())
                        return(0x0, returndatasize())
                    }
                } else {
                    revert();
                }
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../../zeppelin/ownership/Ownable.sol";
        import "../../zeppelin/utils/Address.sol";
        import "../../zeppelin/token/ERC20/SafeERC20.sol";
        import "./StakingInterface.sol";
        import "../../zeppelin/proxy/Initializable.sol";
        /**
        * @notice Router for accessing interface contract
        */
        contract StakingInterfaceRouter is Ownable {
            BaseStakingInterface public target;
            /**
            * @param _target Address of the interface contract
            */
            constructor(BaseStakingInterface _target) {
                require(address(_target.token()) != address(0));
                target = _target;
            }
            /**
            * @notice Upgrade interface
            * @param _target New contract address
            */
            function upgrade(BaseStakingInterface _target) external onlyOwner {
                require(address(_target.token()) != address(0));
                target = _target;
            }
        }
        /**
        * @notice Internal base class for AbstractStakingContract and InitializableStakingContract
        */
        abstract contract RawStakingContract {
            using Address for address;
            /**
            * @dev Returns address of StakingInterfaceRouter
            */
            function router() public view virtual returns (StakingInterfaceRouter);
            /**
            * @dev Checks permission for calling fallback function
            */
            function isFallbackAllowed() public virtual returns (bool);
            /**
            * @dev Withdraw tokens from staking contract
            */
            function withdrawTokens(uint256 _value) public virtual;
            /**
            * @dev Withdraw ETH from staking contract
            */
            function withdrawETH() public virtual;
            receive() external payable {}
            /**
            * @dev Function sends all requests to the target contract
            */
            fallback() external payable {
                require(isFallbackAllowed());
                address target = address(router().target());
                require(target.isContract());
                // execute requested function from target contract
                (bool callSuccess, ) = target.delegatecall(msg.data);
                if (callSuccess) {
                    // copy result of the request to the return data
                    // we can use the second return value from `delegatecall` (bytes memory)
                    // but it will consume a little more gas
                    assembly {
                        returndatacopy(0x0, 0x0, returndatasize())
                        return(0x0, returndatasize())
                    }
                } else {
                    revert();
                }
            }
        }
        /**
        * @notice Base class for any staking contract (not usable with openzeppelin proxy)
        * @dev Implement `isFallbackAllowed()` or override fallback function
        * Implement `withdrawTokens(uint256)` and `withdrawETH()` functions
        */
        abstract contract AbstractStakingContract is RawStakingContract {
            StakingInterfaceRouter immutable router_;
            NuCypherToken public immutable token;
            /**
            * @param _router Interface router contract address
            */
            constructor(StakingInterfaceRouter _router) {
                router_ = _router;
                NuCypherToken localToken = _router.target().token();
                require(address(localToken) != address(0));
                token = localToken;
            }
            /**
            * @dev Returns address of StakingInterfaceRouter
            */
            function router() public view override returns (StakingInterfaceRouter) {
                return router_;
            }
        }
        /**
        * @notice Base class for any staking contract usable with openzeppelin proxy
        * @dev Implement `isFallbackAllowed()` or override fallback function
        * Implement `withdrawTokens(uint256)` and `withdrawETH()` functions
        */
        abstract contract InitializableStakingContract is Initializable, RawStakingContract {
            StakingInterfaceRouter router_;
            NuCypherToken public token;
            /**
            * @param _router Interface router contract address
            */
            function initialize(StakingInterfaceRouter _router) public initializer {
                router_ = _router;
                NuCypherToken localToken = _router.target().token();
                require(address(localToken) != address(0));
                token = localToken;
            }
            /**
            * @dev Returns address of StakingInterfaceRouter
            */
            function router() public view override returns (StakingInterfaceRouter) {
                return router_;
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "./AbstractStakingContract.sol";
        import "../NuCypherToken.sol";
        import "../StakingEscrow.sol";
        import "../PolicyManager.sol";
        import "../WorkLock.sol";
        /**
        * @notice Base StakingInterface
        */
        contract BaseStakingInterface {
            address public immutable stakingInterfaceAddress;
            NuCypherToken public immutable token;
            StakingEscrow public immutable escrow;
            PolicyManager public immutable policyManager;
            WorkLock public immutable workLock;
            /**
            * @notice Constructor sets addresses of the contracts
            * @param _token Token contract
            * @param _escrow Escrow contract
            * @param _policyManager PolicyManager contract
            * @param _workLock WorkLock contract
            */
            constructor(
                NuCypherToken _token,
                StakingEscrow _escrow,
                PolicyManager _policyManager,
                WorkLock _workLock
            ) {
                require(_token.totalSupply() > 0 &&
                    _escrow.secondsPerPeriod() > 0 &&
                    _policyManager.secondsPerPeriod() > 0 &&
                    // in case there is no worklock contract
                    (address(_workLock) == address(0) || _workLock.boostingRefund() > 0));
                token = _token;
                escrow = _escrow;
                policyManager = _policyManager;
                workLock = _workLock;
                stakingInterfaceAddress = address(this);
            }
            /**
            * @dev Checks executing through delegate call
            */
            modifier onlyDelegateCall()
            {
                require(stakingInterfaceAddress != address(this));
                _;
            }
            /**
            * @dev Checks the existence of the worklock contract
            */
            modifier workLockSet()
            {
                require(address(workLock) != address(0));
                _;
            }
        }
        /**
        * @notice Interface for accessing main contracts from a staking contract
        * @dev All methods must be stateless because this code will be executed by delegatecall call, use immutable fields.
        * @dev |v1.5.2|
        */
        contract StakingInterface is BaseStakingInterface {
            event DepositedAsStaker(address indexed sender, uint256 value, uint16 periods);
            event WithdrawnAsStaker(address indexed sender, uint256 value);
            event DepositedAndIncreased(address indexed sender, uint256 index, uint256 value);
            event LockedAndCreated(address indexed sender, uint256 value, uint16 periods);
            event LockedAndIncreased(address indexed sender, uint256 index, uint256 value);
            event Divided(address indexed sender, uint256 index, uint256 newValue, uint16 periods);
            event Merged(address indexed sender, uint256 index1, uint256 index2);
            event Minted(address indexed sender);
            event PolicyFeeWithdrawn(address indexed sender, uint256 value);
            event MinFeeRateSet(address indexed sender, uint256 value);
            event ReStakeSet(address indexed sender, bool reStake);
            event ReStakeLocked(address indexed sender, uint16 lockUntilPeriod);
            event WorkerBonded(address indexed sender, address worker);
            event Prolonged(address indexed sender, uint256 index, uint16 periods);
            event WindDownSet(address indexed sender, bool windDown);
            event Bid(address indexed sender, uint256 depositedETH);
            event Claimed(address indexed sender, uint256 claimedTokens);
            event Refund(address indexed sender, uint256 refundETH);
            event BidCanceled(address indexed sender);
            event CompensationWithdrawn(address indexed sender);
            /**
            * @notice Constructor sets addresses of the contracts
            * @param _token Token contract
            * @param _escrow Escrow contract
            * @param _policyManager PolicyManager contract
            * @param _workLock WorkLock contract
            */
            constructor(
                NuCypherToken _token,
                StakingEscrow _escrow,
                PolicyManager _policyManager,
                WorkLock _workLock
            )
                BaseStakingInterface(_token, _escrow, _policyManager, _workLock)
            {
            }
            /**
            * @notice Bond worker in the staking escrow
            * @param _worker Worker address
            */
            function bondWorker(address _worker) public onlyDelegateCall {
                escrow.bondWorker(_worker);
                emit WorkerBonded(msg.sender, _worker);
            }
            /**
            * @notice Set `reStake` parameter in the staking escrow
            * @param _reStake Value for parameter
            */
            function setReStake(bool _reStake) public onlyDelegateCall {
                escrow.setReStake(_reStake);
                emit ReStakeSet(msg.sender, _reStake);
            }
            /**
            * @notice Lock `reStake` parameter in the staking escrow
            * @param _lockReStakeUntilPeriod Can't change `reStake` value until this period
            */
            function lockReStake(uint16 _lockReStakeUntilPeriod) public onlyDelegateCall {
                escrow.lockReStake(_lockReStakeUntilPeriod);
                emit ReStakeLocked(msg.sender, _lockReStakeUntilPeriod);
            }
            /**
            * @notice Deposit tokens to the staking escrow
            * @param _value Amount of token to deposit
            * @param _periods Amount of periods during which tokens will be locked
            */
            function depositAsStaker(uint256 _value, uint16 _periods) public onlyDelegateCall {
                require(token.balanceOf(address(this)) >= _value);
                token.approve(address(escrow), _value);
                escrow.deposit(address(this), _value, _periods);
                emit DepositedAsStaker(msg.sender, _value, _periods);
            }
            /**
            * @notice Deposit tokens to the staking escrow
            * @param _index Index of the sub-stake
            * @param _value Amount of tokens which will be locked
            */
            function depositAndIncrease(uint256 _index, uint256 _value) public onlyDelegateCall {
                require(token.balanceOf(address(this)) >= _value);
                token.approve(address(escrow), _value);
                escrow.depositAndIncrease(_index, _value);
                emit DepositedAndIncreased(msg.sender, _index, _value);
            }
            /**
            * @notice Withdraw available amount of tokens from the staking escrow to the staking contract
            * @param _value Amount of token to withdraw
            */
            function withdrawAsStaker(uint256 _value) public onlyDelegateCall {
                escrow.withdraw(_value);
                emit WithdrawnAsStaker(msg.sender, _value);
            }
            /**
            * @notice Lock some tokens in the staking escrow
            * @param _value Amount of tokens which should lock
            * @param _periods Amount of periods during which tokens will be locked
            */
            function lockAndCreate(uint256 _value, uint16 _periods) public onlyDelegateCall {
                escrow.lockAndCreate(_value, _periods);
                emit LockedAndCreated(msg.sender, _value, _periods);
            }
            /**
            * @notice Lock some tokens in the staking escrow
            * @param _index Index of the sub-stake
            * @param _value Amount of tokens which will be locked
            */
            function lockAndIncrease(uint256 _index, uint256 _value) public onlyDelegateCall {
                escrow.lockAndIncrease(_index, _value);
                emit LockedAndIncreased(msg.sender, _index, _value);
            }
            /**
            * @notice Divide stake into two parts
            * @param _index Index of stake
            * @param _newValue New stake value
            * @param _periods Amount of periods for extending stake
            */
            function divideStake(uint256 _index, uint256 _newValue, uint16 _periods) public onlyDelegateCall {
                escrow.divideStake(_index, _newValue, _periods);
                emit Divided(msg.sender, _index, _newValue, _periods);
            }
            /**
            * @notice Merge two sub-stakes into one
            * @param _index1 Index of the first sub-stake
            * @param _index2 Index of the second sub-stake
            */
            function mergeStake(uint256 _index1, uint256 _index2) public onlyDelegateCall {
                escrow.mergeStake(_index1, _index2);
                emit Merged(msg.sender, _index1, _index2);
            }
            /**
            * @notice Mint tokens in the staking escrow
            */
            function mint() public onlyDelegateCall {
                escrow.mint();
                emit Minted(msg.sender);
            }
            /**
            * @notice Withdraw available policy fees from the policy manager to the staking contract
            */
            function withdrawPolicyFee() public onlyDelegateCall {
                uint256 value = policyManager.withdraw();
                emit PolicyFeeWithdrawn(msg.sender, value);
            }
            /**
            * @notice Set the minimum fee that the staker will accept in the policy manager contract
            */
            function setMinFeeRate(uint256 _minFeeRate) public onlyDelegateCall {
                policyManager.setMinFeeRate(_minFeeRate);
                emit MinFeeRateSet(msg.sender, _minFeeRate);
            }
            /**
            * @notice Prolong active sub stake
            * @param _index Index of the sub stake
            * @param _periods Amount of periods for extending sub stake
            */
            function prolongStake(uint256 _index, uint16 _periods) public onlyDelegateCall {
                escrow.prolongStake(_index, _periods);
                emit Prolonged(msg.sender, _index, _periods);
            }
            /**
            * @notice Set `windDown` parameter in the staking escrow
            * @param _windDown Value for parameter
            */
            function setWindDown(bool _windDown) public onlyDelegateCall {
                escrow.setWindDown(_windDown);
                emit WindDownSet(msg.sender, _windDown);
            }
            /**
            * @notice Bid for tokens by transferring ETH
            */
            function bid(uint256 _value) public payable onlyDelegateCall workLockSet {
                workLock.bid{value: _value}();
                emit Bid(msg.sender, _value);
            }
            /**
            * @notice Cancel bid and refund deposited ETH
            */
            function cancelBid() public onlyDelegateCall workLockSet {
                workLock.cancelBid();
                emit BidCanceled(msg.sender);
            }
            /**
            * @notice Withdraw compensation after force refund
            */
            function withdrawCompensation() public onlyDelegateCall workLockSet {
                workLock.withdrawCompensation();
                emit CompensationWithdrawn(msg.sender);
            }
            /**
            * @notice Claimed tokens will be deposited and locked as stake in the StakingEscrow contract
            */
            function claim() public onlyDelegateCall workLockSet {
                uint256 claimedTokens = workLock.claim();
                emit Claimed(msg.sender, claimedTokens);
            }
            /**
            * @notice Refund ETH for the completed work
            */
            function refund() public onlyDelegateCall workLockSet {
                uint256 refundETH = workLock.refund();
                emit Refund(msg.sender, refundETH);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../zeppelin/math/SafeMath.sol";
        import "../zeppelin/token/ERC20/SafeERC20.sol";
        import "../zeppelin/utils/Address.sol";
        import "../zeppelin/ownership/Ownable.sol";
        import "./NuCypherToken.sol";
        import "./StakingEscrow.sol";
        import "./lib/AdditionalMath.sol";
        /**
        * @notice The WorkLock distribution contract
        */
        contract WorkLock is Ownable {
            using SafeERC20 for NuCypherToken;
            using SafeMath for uint256;
            using AdditionalMath for uint256;
            using Address for address payable;
            using Address for address;
            event Deposited(address indexed sender, uint256 value);
            event Bid(address indexed sender, uint256 depositedETH);
            event Claimed(address indexed sender, uint256 claimedTokens);
            event Refund(address indexed sender, uint256 refundETH, uint256 completedWork);
            event Canceled(address indexed sender, uint256 value);
            event BiddersChecked(address indexed sender, uint256 startIndex, uint256 endIndex);
            event ForceRefund(address indexed sender, address indexed bidder, uint256 refundETH);
            event CompensationWithdrawn(address indexed sender, uint256 value);
            event Shutdown(address indexed sender);
            struct WorkInfo {
                uint256 depositedETH;
                uint256 completedWork;
                bool claimed;
                uint128 index;
            }
            uint16 public constant SLOWING_REFUND = 100;
            uint256 private constant MAX_ETH_SUPPLY = 2e10 ether;
            NuCypherToken public immutable token;
            StakingEscrow public immutable escrow;
            /*
            * @dev WorkLock calculations:
            * bid = minBid + bonusETHPart
            * bonusTokenSupply = tokenSupply - bidders.length * minAllowableLockedTokens
            * bonusDepositRate = bonusTokenSupply / bonusETHSupply
            * claimedTokens = minAllowableLockedTokens + bonusETHPart * bonusDepositRate
            * bonusRefundRate = bonusDepositRate * SLOWING_REFUND / boostingRefund
            * refundETH = completedWork / refundRate
            */
            uint256 public immutable boostingRefund;
            uint256 public immutable minAllowedBid;
            uint16 public immutable stakingPeriods;
            // copy from the escrow contract
            uint256 public immutable maxAllowableLockedTokens;
            uint256 public immutable minAllowableLockedTokens;
            uint256 public tokenSupply;
            uint256 public startBidDate;
            uint256 public endBidDate;
            uint256 public endCancellationDate;
            uint256 public bonusETHSupply;
            mapping(address => WorkInfo) public workInfo;
            mapping(address => uint256) public compensation;
            address[] public bidders;
            // if value == bidders.length then WorkLock is fully checked
            uint256 public nextBidderToCheck;
            /**
            * @dev Checks timestamp regarding cancellation window
            */
            modifier afterCancellationWindow()
            {
                require(block.timestamp >= endCancellationDate,
                    "Operation is allowed when cancellation phase is over");
                _;
            }
            /**
            * @param _token Token contract
            * @param _escrow Escrow contract
            * @param _startBidDate Timestamp when bidding starts
            * @param _endBidDate Timestamp when bidding will end
            * @param _endCancellationDate Timestamp when cancellation will ends
            * @param _boostingRefund Coefficient to boost refund ETH
            * @param _stakingPeriods Amount of periods during which tokens will be locked after claiming
            * @param _minAllowedBid Minimum allowed ETH amount for bidding
            */
            constructor(
                NuCypherToken _token,
                StakingEscrow _escrow,
                uint256 _startBidDate,
                uint256 _endBidDate,
                uint256 _endCancellationDate,
                uint256 _boostingRefund,
                uint16 _stakingPeriods,
                uint256 _minAllowedBid
            ) {
                uint256 totalSupply = _token.totalSupply();
                require(totalSupply > 0 &&                              // token contract is deployed and accessible
                    _escrow.secondsPerPeriod() > 0 &&                   // escrow contract is deployed and accessible
                    _escrow.token() == _token &&                        // same token address for worklock and escrow
                    _endBidDate > _startBidDate &&                      // bidding period lasts some time
                    _endBidDate > block.timestamp &&                    // there is time to make a bid
                    _endCancellationDate >= _endBidDate &&              // cancellation window includes bidding
                    _minAllowedBid > 0 &&                               // min allowed bid was set
                    _boostingRefund > 0 &&                              // boosting coefficient was set
                    _stakingPeriods >= _escrow.minLockedPeriods());     // staking duration is consistent with escrow contract
                // worst case for `ethToWork()` and `workToETH()`,
                // when ethSupply == MAX_ETH_SUPPLY and tokenSupply == totalSupply
                require(MAX_ETH_SUPPLY * totalSupply * SLOWING_REFUND / MAX_ETH_SUPPLY / totalSupply == SLOWING_REFUND &&
                    MAX_ETH_SUPPLY * totalSupply * _boostingRefund / MAX_ETH_SUPPLY / totalSupply == _boostingRefund);
                token = _token;
                escrow = _escrow;
                startBidDate = _startBidDate;
                endBidDate = _endBidDate;
                endCancellationDate = _endCancellationDate;
                boostingRefund = _boostingRefund;
                stakingPeriods = _stakingPeriods;
                minAllowedBid = _minAllowedBid;
                maxAllowableLockedTokens = _escrow.maxAllowableLockedTokens();
                minAllowableLockedTokens = _escrow.minAllowableLockedTokens();
            }
            /**
            * @notice Deposit tokens to contract
            * @param _value Amount of tokens to transfer
            */
            function tokenDeposit(uint256 _value) external {
                require(block.timestamp < endBidDate, "Can't deposit more tokens after end of bidding");
                token.safeTransferFrom(msg.sender, address(this), _value);
                tokenSupply += _value;
                emit Deposited(msg.sender, _value);
            }
            /**
            * @notice Calculate amount of tokens that will be get for specified amount of ETH
            * @dev This value will be fixed only after end of bidding
            */
            function ethToTokens(uint256 _ethAmount) public view returns (uint256) {
                if (_ethAmount < minAllowedBid) {
                    return 0;
                }
                // when all participants bid with the same minimum amount of eth
                if (bonusETHSupply == 0) {
                    return tokenSupply / bidders.length;
                }
                uint256 bonusETH = _ethAmount - minAllowedBid;
                uint256 bonusTokenSupply = tokenSupply - bidders.length * minAllowableLockedTokens;
                return minAllowableLockedTokens + bonusETH.mul(bonusTokenSupply).div(bonusETHSupply);
            }
            /**
            * @notice Calculate amount of work that need to be done to refund specified amount of ETH
            */
            function ethToWork(uint256 _ethAmount, uint256 _tokenSupply, uint256 _ethSupply)
                internal view returns (uint256)
            {
                return _ethAmount.mul(_tokenSupply).mul(SLOWING_REFUND).divCeil(_ethSupply.mul(boostingRefund));
            }
            /**
            * @notice Calculate amount of work that need to be done to refund specified amount of ETH
            * @dev This value will be fixed only after end of bidding
            * @param _ethToReclaim Specified sum of ETH staker wishes to reclaim following completion of work
            * @param _restOfDepositedETH Remaining ETH in staker's deposit once ethToReclaim sum has been subtracted
            * @dev _ethToReclaim + _restOfDepositedETH = depositedETH
            */
            function ethToWork(uint256 _ethToReclaim, uint256 _restOfDepositedETH) internal view returns (uint256) {
                uint256 baseETHSupply = bidders.length * minAllowedBid;
                // when all participants bid with the same minimum amount of eth
                if (bonusETHSupply == 0) {
                    return ethToWork(_ethToReclaim, tokenSupply, baseETHSupply);
                }
                uint256 baseETH = 0;
                uint256 bonusETH = 0;
                // If the staker's total remaining deposit (including the specified sum of ETH to reclaim)
                // is lower than the minimum bid size,
                // then only the base part is used to calculate the work required to reclaim ETH
                if (_ethToReclaim + _restOfDepositedETH <= minAllowedBid) {
                    baseETH = _ethToReclaim;
                // If the staker's remaining deposit (not including the specified sum of ETH to reclaim)
                // is still greater than the minimum bid size,
                // then only the bonus part is used to calculate the work required to reclaim ETH
                } else if (_restOfDepositedETH >= minAllowedBid) {
                    bonusETH = _ethToReclaim;
                // If the staker's remaining deposit (not including the specified sum of ETH to reclaim)
                // is lower than the minimum bid size,
                // then both the base and bonus parts must be used to calculate the work required to reclaim ETH
                } else {
                    bonusETH = _ethToReclaim + _restOfDepositedETH - minAllowedBid;
                    baseETH = _ethToReclaim - bonusETH;
                }
                uint256 baseTokenSupply = bidders.length * minAllowableLockedTokens;
                uint256 work = 0;
                if (baseETH > 0) {
                    work = ethToWork(baseETH, baseTokenSupply, baseETHSupply);
                }
                if (bonusETH > 0) {
                    uint256 bonusTokenSupply = tokenSupply - baseTokenSupply;
                    work += ethToWork(bonusETH, bonusTokenSupply, bonusETHSupply);
                }
                return work;
            }
            /**
            * @notice Calculate amount of work that need to be done to refund specified amount of ETH
            * @dev This value will be fixed only after end of bidding
            */
            function ethToWork(uint256 _ethAmount) public view returns (uint256) {
                return ethToWork(_ethAmount, 0);
            }
            /**
            * @notice Calculate amount of ETH that will be refund for completing specified amount of work
            */
            function workToETH(uint256 _completedWork, uint256 _ethSupply, uint256 _tokenSupply)
                internal view returns (uint256)
            {
                return _completedWork.mul(_ethSupply).mul(boostingRefund).div(_tokenSupply.mul(SLOWING_REFUND));
            }
            /**
            * @notice Calculate amount of ETH that will be refund for completing specified amount of work
            * @dev This value will be fixed only after end of bidding
            */
            function workToETH(uint256 _completedWork, uint256 _depositedETH) public view returns (uint256) {
                uint256 baseETHSupply = bidders.length * minAllowedBid;
                // when all participants bid with the same minimum amount of eth
                if (bonusETHSupply == 0) {
                    return workToETH(_completedWork, baseETHSupply, tokenSupply);
                }
                uint256 bonusWork = 0;
                uint256 bonusETH = 0;
                uint256 baseTokenSupply = bidders.length * minAllowableLockedTokens;
                if (_depositedETH > minAllowedBid) {
                    bonusETH = _depositedETH - minAllowedBid;
                    uint256 bonusTokenSupply = tokenSupply - baseTokenSupply;
                    bonusWork = ethToWork(bonusETH, bonusTokenSupply, bonusETHSupply);
                    if (_completedWork <= bonusWork) {
                        return workToETH(_completedWork, bonusETHSupply, bonusTokenSupply);
                    }
                }
                _completedWork -= bonusWork;
                return bonusETH + workToETH(_completedWork, baseETHSupply, baseTokenSupply);
            }
            /**
            * @notice Get remaining work to full refund
            */
            function getRemainingWork(address _bidder) external view returns (uint256) {
                WorkInfo storage info = workInfo[_bidder];
                uint256 completedWork = escrow.getCompletedWork(_bidder).sub(info.completedWork);
                uint256 remainingWork = ethToWork(info.depositedETH);
                if (remainingWork <= completedWork) {
                    return 0;
                }
                return remainingWork - completedWork;
            }
            /**
            * @notice Get length of bidders array
            */
            function getBiddersLength() external view returns (uint256) {
                return bidders.length;
            }
            /**
            * @notice Bid for tokens by transferring ETH
            */
            function bid() external payable {
                require(block.timestamp >= startBidDate, "Bidding is not open yet");
                require(block.timestamp < endBidDate, "Bidding is already finished");
                WorkInfo storage info = workInfo[msg.sender];
                // first bid
                if (info.depositedETH == 0) {
                    require(msg.value >= minAllowedBid, "Bid must be at least minimum");
                    require(bidders.length < tokenSupply / minAllowableLockedTokens, "Not enough tokens for more bidders");
                    info.index = uint128(bidders.length);
                    bidders.push(msg.sender);
                    bonusETHSupply = bonusETHSupply.add(msg.value - minAllowedBid);
                } else {
                    bonusETHSupply = bonusETHSupply.add(msg.value);
                }
                info.depositedETH = info.depositedETH.add(msg.value);
                emit Bid(msg.sender, msg.value);
            }
            /**
            * @notice Cancel bid and refund deposited ETH
            */
            function cancelBid() external {
                require(block.timestamp < endCancellationDate,
                    "Cancellation allowed only during cancellation window");
                WorkInfo storage info = workInfo[msg.sender];
                require(info.depositedETH > 0, "No bid to cancel");
                require(!info.claimed, "Tokens are already claimed");
                uint256 refundETH = info.depositedETH;
                info.depositedETH = 0;
                // remove from bidders array, move last bidder to the empty place
                uint256 lastIndex = bidders.length - 1;
                if (info.index != lastIndex) {
                    address lastBidder = bidders[lastIndex];
                    bidders[info.index] = lastBidder;
                    workInfo[lastBidder].index = info.index;
                }
                bidders.pop();
                if (refundETH > minAllowedBid) {
                    bonusETHSupply = bonusETHSupply.sub(refundETH - minAllowedBid);
                }
                msg.sender.sendValue(refundETH);
                emit Canceled(msg.sender, refundETH);
            }
            /**
            * @notice Cancels distribution, makes possible to retrieve all bids and owner gets all tokens
            */
            function shutdown() external onlyOwner {
                require(!isClaimingAvailable(), "Claiming has already been enabled");
                internalShutdown();
            }
            /**
            * @notice Cancels distribution, makes possible to retrieve all bids and owner gets all tokens
            */
            function internalShutdown() internal {
                startBidDate = 0;
                endBidDate = 0;
                endCancellationDate = uint256(0) - 1; // "infinite" cancellation window
                token.safeTransfer(owner(), tokenSupply);
                emit Shutdown(msg.sender);
            }
            /**
            * @notice Make force refund to bidders who can get tokens more than maximum allowed
            * @param _biddersForRefund Sorted list of unique bidders. Only bidders who must receive a refund
            */
            function forceRefund(address payable[] calldata _biddersForRefund) external afterCancellationWindow {
                require(nextBidderToCheck != bidders.length, "Bidders have already been checked");
                uint256 length = _biddersForRefund.length;
                require(length > 0, "Must be at least one bidder for a refund");
                uint256 minNumberOfBidders = tokenSupply.divCeil(maxAllowableLockedTokens);
                if (bidders.length < minNumberOfBidders) {
                    internalShutdown();
                    return;
                }
                address previousBidder = _biddersForRefund[0];
                uint256 minBid = workInfo[previousBidder].depositedETH;
                uint256 maxBid = minBid;
                // get minimum and maximum bids
                for (uint256 i = 1; i < length; i++) {
                    address bidder = _biddersForRefund[i];
                    uint256 depositedETH = workInfo[bidder].depositedETH;
                    require(bidder > previousBidder && depositedETH > 0, "Addresses must be an array of unique bidders");
                    if (minBid > depositedETH) {
                        minBid = depositedETH;
                    } else if (maxBid < depositedETH) {
                        maxBid = depositedETH;
                    }
                    previousBidder = bidder;
                }
                uint256[] memory refunds = new uint256[](length);
                // first step - align at a minimum bid
                if (minBid != maxBid) {
                    for (uint256 i = 0; i < length; i++) {
                        address bidder = _biddersForRefund[i];
                        WorkInfo storage info = workInfo[bidder];
                        if (info.depositedETH > minBid) {
                            refunds[i] = info.depositedETH - minBid;
                            info.depositedETH = minBid;
                            bonusETHSupply -= refunds[i];
                        }
                    }
                }
                require(ethToTokens(minBid) > maxAllowableLockedTokens,
                    "At least one of bidders has allowable bid");
                // final bids adjustment (only for bonus part)
                // (min_whale_bid * token_supply - max_stake * eth_supply) / (token_supply - max_stake * n_whales)
                uint256 maxBonusTokens = maxAllowableLockedTokens - minAllowableLockedTokens;
                uint256 minBonusETH = minBid - minAllowedBid;
                uint256 bonusTokenSupply = tokenSupply - bidders.length * minAllowableLockedTokens;
                uint256 refundETH = minBonusETH.mul(bonusTokenSupply)
                                        .sub(maxBonusTokens.mul(bonusETHSupply))
                                        .divCeil(bonusTokenSupply - maxBonusTokens.mul(length));
                uint256 resultBid = minBid.sub(refundETH);
                bonusETHSupply -= length * refundETH;
                for (uint256 i = 0; i < length; i++) {
                    address bidder = _biddersForRefund[i];
                    WorkInfo storage info = workInfo[bidder];
                    refunds[i] += refundETH;
                    info.depositedETH = resultBid;
                }
                // reset verification
                nextBidderToCheck = 0;
                // save a refund
                for (uint256 i = 0; i < length; i++) {
                    address bidder = _biddersForRefund[i];
                    compensation[bidder] += refunds[i];
                    emit ForceRefund(msg.sender, bidder, refunds[i]);
                }
            }
            /**
            * @notice Withdraw compensation after force refund
            */
            function withdrawCompensation() external {
                uint256 refund = compensation[msg.sender];
                require(refund > 0, "There is no compensation");
                compensation[msg.sender] = 0;
                msg.sender.sendValue(refund);
                emit CompensationWithdrawn(msg.sender, refund);
            }
            /**
            * @notice Check that the claimed tokens are within `maxAllowableLockedTokens` for all participants,
            * starting from the last point `nextBidderToCheck`
            * @dev Method stops working when the remaining gas is less than `_gasToSaveState`
            * and saves the state in `nextBidderToCheck`.
            * If all bidders have been checked then `nextBidderToCheck` will be equal to the length of the bidders array
            */
            function verifyBiddingCorrectness(uint256 _gasToSaveState) external afterCancellationWindow returns (uint256) {
                require(nextBidderToCheck != bidders.length, "Bidders have already been checked");
                // all participants bid with the same minimum amount of eth
                uint256 index = nextBidderToCheck;
                if (bonusETHSupply == 0) {
                    require(tokenSupply / bidders.length <= maxAllowableLockedTokens, "Not enough bidders");
                    index = bidders.length;
                }
                uint256 maxBonusTokens = maxAllowableLockedTokens - minAllowableLockedTokens;
                uint256 bonusTokenSupply = tokenSupply - bidders.length * minAllowableLockedTokens;
                uint256 maxBidFromMaxStake = minAllowedBid + maxBonusTokens.mul(bonusETHSupply).div(bonusTokenSupply);
                while (index < bidders.length && gasleft() > _gasToSaveState) {
                    address bidder = bidders[index];
                    require(workInfo[bidder].depositedETH <= maxBidFromMaxStake, "Bid is greater than max allowable bid");
                    index++;
                }
                if (index != nextBidderToCheck) {
                    emit BiddersChecked(msg.sender, nextBidderToCheck, index);
                    nextBidderToCheck = index;
                }
                return nextBidderToCheck;
            }
            /**
            * @notice Checks if claiming available
            */
            function isClaimingAvailable() public view returns (bool) {
                return block.timestamp >= endCancellationDate &&
                    nextBidderToCheck == bidders.length;
            }
            /**
            * @notice Claimed tokens will be deposited and locked as stake in the StakingEscrow contract.
            */
            function claim() external returns (uint256 claimedTokens) {
                require(isClaimingAvailable(), "Claiming has not been enabled yet");
                WorkInfo storage info = workInfo[msg.sender];
                require(!info.claimed, "Tokens are already claimed");
                claimedTokens = ethToTokens(info.depositedETH);
                require(claimedTokens > 0, "Nothing to claim");
                info.claimed = true;
                token.approve(address(escrow), claimedTokens);
                escrow.depositFromWorkLock(msg.sender, claimedTokens, stakingPeriods);
                info.completedWork = escrow.setWorkMeasurement(msg.sender, true);
                emit Claimed(msg.sender, claimedTokens);
            }
            /**
            * @notice Get available refund for bidder
            */
            function getAvailableRefund(address _bidder) public view returns (uint256) {
                WorkInfo storage info = workInfo[_bidder];
                // nothing to refund
                if (info.depositedETH == 0) {
                    return 0;
                }
                uint256 currentWork = escrow.getCompletedWork(_bidder);
                uint256 completedWork = currentWork.sub(info.completedWork);
                // no work that has been completed since last refund
                if (completedWork == 0) {
                    return 0;
                }
                uint256 refundETH = workToETH(completedWork, info.depositedETH);
                if (refundETH > info.depositedETH) {
                    refundETH = info.depositedETH;
                }
                return refundETH;
            }
            /**
            * @notice Refund ETH for the completed work
            */
            function refund() external returns (uint256 refundETH) {
                WorkInfo storage info = workInfo[msg.sender];
                require(info.claimed, "Tokens must be claimed before refund");
                refundETH = getAvailableRefund(msg.sender);
                require(refundETH > 0, "Nothing to refund: there is no ETH to refund or no completed work");
                if (refundETH == info.depositedETH) {
                    escrow.setWorkMeasurement(msg.sender, false);
                }
                info.depositedETH = info.depositedETH.sub(refundETH);
                // convert refund back to work to eliminate potential rounding errors
                uint256 completedWork = ethToWork(refundETH, info.depositedETH);
                info.completedWork = info.completedWork.add(completedWork);
                emit Refund(msg.sender, refundETH, completedWork);
                msg.sender.sendValue(refundETH);
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        /**
         * @title Initializable
         *
         * @dev Helper contract to support initializer functions. To use it, replace
         * the constructor with a function that has the `initializer` modifier.
         * WARNING: Unlike constructors, initializer functions must be manually
         * invoked. This applies both to deploying an Initializable contract, as well
         * as extending an Initializable contract via inheritance.
         * WARNING: When used with inheritance, manual care must be taken to not invoke
         * a parent initializer twice, or ensure that all initializers are idempotent,
         * because this is not dealt with automatically as with constructors.
         */
        contract Initializable {
          /**
           * @dev Indicates that the contract has been initialized.
           */
          bool private initialized;
          /**
           * @dev Indicates that the contract is in the process of being initialized.
           */
          bool private initializing;
          /**
           * @dev Modifier to use in the initializer function of a contract.
           */
          modifier initializer() {
            require(initializing || isConstructor() || !initialized, "Contract instance has already been initialized");
            bool isTopLevelCall = !initializing;
            if (isTopLevelCall) {
              initializing = true;
              initialized = true;
            }
            _;
            if (isTopLevelCall) {
              initializing = false;
            }
          }
          /// @dev Returns true if and only if the function is running in the constructor
          function isConstructor() private view returns (bool) {
            // extcodesize checks the size of the code stored in an address, and
            // address returns the current address. Since the code is still not
            // deployed when running a constructor, any checks on its code size will
            // yield zero, making it an effective way to detect if a contract is
            // under construction or not.
            address self = address(this);
            uint256 cs;
            assembly { cs := extcodesize(self) }
            return cs == 0;
          }
          // Reserved storage space to allow for layout changes in the future.
          uint256[50] private ______gap;
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../../zeppelin/ownership/Ownable.sol";
        import "../../zeppelin/math/SafeMath.sol";
        import "./AbstractStakingContract.sol";
        /**
        * @notice Contract acts as delegate for sub-stakers and owner
        **/
        contract PoolingStakingContract is AbstractStakingContract, Ownable {
            using SafeMath for uint256;
            using Address for address payable;
            using SafeERC20 for NuCypherToken;
            event TokensDeposited(address indexed sender, uint256 value, uint256 depositedTokens);
            event TokensWithdrawn(address indexed sender, uint256 value, uint256 depositedTokens);
            event ETHWithdrawn(address indexed sender, uint256 value);
            event DepositSet(address indexed sender, bool value);
            struct Delegator {
                uint256 depositedTokens;
                uint256 withdrawnReward;
                uint256 withdrawnETH;
            }
            StakingEscrow public immutable escrow;
            uint256 public totalDepositedTokens;
            uint256 public totalWithdrawnReward;
            uint256 public totalWithdrawnETH;
            uint256 public ownerFraction;
            uint256 public ownerWithdrawnReward;
            uint256 public ownerWithdrawnETH;
            mapping (address => Delegator) public delegators;
            bool depositIsEnabled = true;
            /**
            * @param _router Address of the StakingInterfaceRouter contract
            * @param _ownerFraction Base owner's portion of reward
            */
            constructor(
                StakingInterfaceRouter _router,
                uint256 _ownerFraction
            )
                AbstractStakingContract(_router)
            {
                escrow = _router.target().escrow();
                ownerFraction = _ownerFraction;
            }
            /**
            * @notice Enabled deposit
            */
            function enableDeposit() external onlyOwner {
                depositIsEnabled = true;
                emit DepositSet(msg.sender, depositIsEnabled);
            }
            /**
            * @notice Disable deposit
            */
            function disableDeposit() external onlyOwner {
                depositIsEnabled = false;
                emit DepositSet(msg.sender, depositIsEnabled);
            }
            /**
            * @notice Transfer tokens as delegator
            * @param _value Amount of tokens to transfer
            */
            function depositTokens(uint256 _value) external {
                require(depositIsEnabled, "Deposit must be enabled");
                require(_value > 0, "Value must be not empty");
                totalDepositedTokens = totalDepositedTokens.add(_value);
                Delegator storage delegator = delegators[msg.sender];
                delegator.depositedTokens += _value;
                token.safeTransferFrom(msg.sender, address(this), _value);
                emit TokensDeposited(msg.sender, _value, delegator.depositedTokens);
            }
            /**
            * @notice Get available reward for all delegators and owner
            */
            function getAvailableReward() public view returns (uint256) {
                uint256 stakedTokens = escrow.getAllTokens(address(this));
                uint256 freeTokens = token.balanceOf(address(this));
                uint256 reward = stakedTokens + freeTokens - totalDepositedTokens;
                if (reward > freeTokens) {
                    return freeTokens;
                }
                return reward;
            }
            /**
            * @notice Get cumulative reward
            */
            function getCumulativeReward() public view returns (uint256) {
                return getAvailableReward().add(totalWithdrawnReward);
            }
            /**
            * @notice Get available reward in tokens for pool owner
            */
            function getAvailableOwnerReward() public view returns (uint256) {
                uint256 reward = getCumulativeReward();
                uint256 maxAllowableReward;
                if (totalDepositedTokens != 0) {
                    maxAllowableReward = reward.mul(ownerFraction).div(totalDepositedTokens.add(ownerFraction));
                } else {
                    maxAllowableReward = reward;
                }
                return maxAllowableReward.sub(ownerWithdrawnReward);
            }
            /**
            * @notice Get available reward in tokens for delegator
            */
            function getAvailableReward(address _delegator) public view returns (uint256) {
                if (totalDepositedTokens == 0) {
                    return 0;
                }
                uint256 reward = getCumulativeReward();
                Delegator storage delegator = delegators[_delegator];
                uint256 maxAllowableReward = reward.mul(delegator.depositedTokens)
                    .div(totalDepositedTokens.add(ownerFraction));
                return maxAllowableReward > delegator.withdrawnReward ? maxAllowableReward - delegator.withdrawnReward : 0;
            }
            /**
            * @notice Withdraw reward in tokens to owner
            */
            function withdrawOwnerReward() public onlyOwner {
                uint256 balance = token.balanceOf(address(this));
                uint256 availableReward = getAvailableOwnerReward();
                if (availableReward > balance) {
                    availableReward = balance;
                }
                require(availableReward > 0, "There is no available reward to withdraw");
                ownerWithdrawnReward  = ownerWithdrawnReward.add(availableReward);
                totalWithdrawnReward = totalWithdrawnReward.add(availableReward);
                token.safeTransfer(msg.sender, availableReward);
                emit TokensWithdrawn(msg.sender, availableReward, 0);
            }
            /**
            * @notice Withdraw amount of tokens to delegator
            * @param _value Amount of tokens to withdraw
            */
            function withdrawTokens(uint256 _value) public override {
                uint256 balance = token.balanceOf(address(this));
                require(_value <= balance, "Not enough tokens in the contract");
                uint256 availableReward = getAvailableReward(msg.sender);
                Delegator storage delegator = delegators[msg.sender];
                require(_value <= availableReward + delegator.depositedTokens,
                    "Requested amount of tokens exceeded allowed portion");
                if (_value <= availableReward) {
                    delegator.withdrawnReward += _value;
                    totalWithdrawnReward += _value;
                } else {
                    delegator.withdrawnReward = delegator.withdrawnReward.add(availableReward);
                    totalWithdrawnReward = totalWithdrawnReward.add(availableReward);
                    uint256 depositToWithdraw = _value - availableReward;
                    uint256 newDepositedTokens = delegator.depositedTokens - depositToWithdraw;
                    uint256 newWithdrawnReward = delegator.withdrawnReward.mul(newDepositedTokens).div(delegator.depositedTokens);
                    uint256 newWithdrawnETH = delegator.withdrawnETH.mul(newDepositedTokens).div(delegator.depositedTokens);
                    totalDepositedTokens -= depositToWithdraw;
                    totalWithdrawnReward -= (delegator.withdrawnReward - newWithdrawnReward);
                    totalWithdrawnETH -= (delegator.withdrawnETH - newWithdrawnETH);
                    delegator.depositedTokens = newDepositedTokens;
                    delegator.withdrawnReward = newWithdrawnReward;
                    delegator.withdrawnETH = newWithdrawnETH;
                }
                token.safeTransfer(msg.sender, _value);
                emit TokensWithdrawn(msg.sender, _value, delegator.depositedTokens);
            }
            /**
            * @notice Get available ether for owner
            */
            function getAvailableOwnerETH() public view returns (uint256) {
                // TODO boilerplate code
                uint256 balance = address(this).balance;
                balance = balance.add(totalWithdrawnETH);
                uint256 maxAllowableETH = balance.mul(ownerFraction).div(totalDepositedTokens.add(ownerFraction));
                uint256 availableETH = maxAllowableETH.sub(ownerWithdrawnETH);
                if (availableETH > balance) {
                    availableETH = balance;
                }
                return availableETH;
            }
            /**
            * @notice Get available ether for delegator
            */
            function getAvailableETH(address _delegator) public view returns (uint256) {
                Delegator storage delegator = delegators[_delegator];
                // TODO boilerplate code
                uint256 balance = address(this).balance;
                balance = balance.add(totalWithdrawnETH);
                uint256 maxAllowableETH = balance.mul(delegator.depositedTokens)
                    .div(totalDepositedTokens.add(ownerFraction));
                uint256 availableETH = maxAllowableETH.sub(delegator.withdrawnETH);
                if (availableETH > balance) {
                    availableETH = balance;
                }
                return availableETH;
            }
            /**
            * @notice Withdraw available amount of ETH to pool owner
            */
            function withdrawOwnerETH() public onlyOwner {
                uint256 availableETH = getAvailableOwnerETH();
                require(availableETH > 0, "There is no available ETH to withdraw");
                ownerWithdrawnETH = ownerWithdrawnETH.add(availableETH);
                totalWithdrawnETH = totalWithdrawnETH.add(availableETH);
                msg.sender.sendValue(availableETH);
                emit ETHWithdrawn(msg.sender, availableETH);
            }
            /**
            * @notice Withdraw available amount of ETH to delegator
            */
            function withdrawETH() public override {
                uint256 availableETH = getAvailableETH(msg.sender);
                require(availableETH > 0, "There is no available ETH to withdraw");
                Delegator storage delegator = delegators[msg.sender];
                delegator.withdrawnETH = delegator.withdrawnETH.add(availableETH);
                totalWithdrawnETH = totalWithdrawnETH.add(availableETH);
                msg.sender.sendValue(availableETH);
                emit ETHWithdrawn(msg.sender, availableETH);
            }
            /**
            * @notice Calling fallback function is allowed only for the owner
            **/
            function isFallbackAllowed() public view override returns (bool) {
                return msg.sender == owner();
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../../zeppelin/ownership/Ownable.sol";
        import "../../zeppelin/math/SafeMath.sol";
        import "./AbstractStakingContract.sol";
        /**
        * @notice Contract holds tokens for vesting.
        * Also tokens can be used as a stake in the staking escrow contract
        */
        contract PreallocationEscrow is AbstractStakingContract, Ownable {
            using SafeMath for uint256;
            using SafeERC20 for NuCypherToken;
            using Address for address payable;
            event TokensDeposited(address indexed sender, uint256 value, uint256 duration);
            event TokensWithdrawn(address indexed owner, uint256 value);
            event ETHWithdrawn(address indexed owner, uint256 value);
            StakingEscrow public immutable stakingEscrow;
            uint256 public lockedValue;
            uint256 public endLockTimestamp;
            /**
            * @param _router Address of the StakingInterfaceRouter contract
            */
            constructor(StakingInterfaceRouter _router) AbstractStakingContract(_router) {
                stakingEscrow = _router.target().escrow();
            }
            /**
            * @notice Initial tokens deposit
            * @param _sender Token sender
            * @param _value Amount of token to deposit
            * @param _duration Duration of tokens locking
            */
            function initialDeposit(address _sender, uint256 _value, uint256 _duration) internal {
                require(lockedValue == 0 && _value > 0);
                endLockTimestamp = block.timestamp.add(_duration);
                lockedValue = _value;
                token.safeTransferFrom(_sender, address(this), _value);
                emit TokensDeposited(_sender, _value, _duration);
            }
            /**
            * @notice Initial tokens deposit
            * @param _value Amount of token to deposit
            * @param _duration Duration of tokens locking
            */
            function initialDeposit(uint256 _value, uint256 _duration) external {
                initialDeposit(msg.sender, _value, _duration);
            }
            /**
            * @notice Implementation of the receiveApproval(address,uint256,address,bytes) method
            * (see NuCypherToken contract). Initial tokens deposit
            * @param _from Sender
            * @param _value Amount of tokens to deposit
            * @param _tokenContract Token contract address
            * @notice (param _extraData) Amount of seconds during which tokens will be locked
            */
            function receiveApproval(
                address _from,
                uint256 _value,
                address _tokenContract,
                bytes calldata /* _extraData */
            )
                external
            {
                require(_tokenContract == address(token) && msg.sender == address(token));
                // Copy first 32 bytes from _extraData, according to calldata memory layout:
                //
                // 0x00: method signature      4 bytes
                // 0x04: _from                 32 bytes after encoding
                // 0x24: _value                32 bytes after encoding
                // 0x44: _tokenContract        32 bytes after encoding
                // 0x64: _extraData pointer    32 bytes. Value must be 0x80 (offset of _extraData wrt to 1st parameter)
                // 0x84: _extraData length     32 bytes
                // 0xA4: _extraData data       Length determined by previous variable
                //
                // See https://solidity.readthedocs.io/en/latest/abi-spec.html#examples
                uint256 payloadSize;
                uint256 payload;
                assembly {
                    payloadSize := calldataload(0x84)
                    payload := calldataload(0xA4)
                }
                payload = payload >> 8*(32 - payloadSize);
                initialDeposit(_from, _value, payload);
            }
            /**
            * @notice Get locked tokens value
            */
            function getLockedTokens() public view returns (uint256) {
                if (endLockTimestamp <= block.timestamp) {
                    return 0;
                }
                return lockedValue;
            }
            /**
            * @notice Withdraw available amount of tokens to owner
            * @param _value Amount of token to withdraw
            */
            function withdrawTokens(uint256 _value) public override onlyOwner {
                uint256 balance = token.balanceOf(address(this));
                require(balance >= _value);
                // Withdrawal invariant for PreallocationEscrow:
                // After withdrawing, the sum of all escrowed tokens (either here or in StakingEscrow) must exceed the locked amount
                require(balance - _value + stakingEscrow.getAllTokens(address(this)) >= getLockedTokens());
                token.safeTransfer(msg.sender, _value);
                emit TokensWithdrawn(msg.sender, _value);
            }
            /**
            * @notice Withdraw available ETH to the owner
            */
            function withdrawETH() public override onlyOwner {
                uint256 balance = address(this).balance;
                require(balance != 0);
                msg.sender.sendValue(balance);
                emit ETHWithdrawn(msg.sender, balance);
            }
            /**
            * @notice Calling fallback function is allowed only for the owner
            */
            function isFallbackAllowed() public view override returns (bool) {
                return msg.sender == owner();
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../../zeppelin/ownership/Ownable.sol";
        import "../../zeppelin/math/SafeMath.sol";
        import "./AbstractStakingContract.sol";
        /**
         * @notice Contract acts as delegate for sub-stakers and owner
         * @author @vzotova and @roma_k
         **/
        contract WorkLockPoolingContract is InitializableStakingContract, Ownable {
            using SafeMath for uint256;
            using Address for address payable;
            using SafeERC20 for NuCypherToken;
            event TokensDeposited(
                address indexed sender,
                uint256 value,
                uint256 depositedTokens
            );
            event TokensWithdrawn(
                address indexed sender,
                uint256 value,
                uint256 depositedTokens
            );
            event ETHWithdrawn(address indexed sender, uint256 value);
            event DepositSet(address indexed sender, bool value);
            event Bid(address indexed sender, uint256 depositedETH);
            event Claimed(address indexed sender, uint256 claimedTokens);
            event Refund(address indexed sender, uint256 refundETH);
            struct Delegator {
                uint256 depositedTokens;
                uint256 withdrawnReward;
                uint256 withdrawnETH;
                uint256 depositedETHWorkLock;
                uint256 refundedETHWorkLock;
                bool claimedWorkLockTokens;
            }
            uint256 public constant BASIS_FRACTION = 100;
            StakingEscrow public escrow;
            WorkLock public workLock;
            address public workerOwner;
            uint256 public totalDepositedTokens;
            uint256 public workLockClaimedTokens;
            uint256 public totalWithdrawnReward;
            uint256 public totalWithdrawnETH;
            uint256 public totalWorkLockETHReceived;
            uint256 public totalWorkLockETHRefunded;
            uint256 public totalWorkLockETHWithdrawn;
            uint256 workerFraction;
            uint256 public workerWithdrawnReward;
            mapping(address => Delegator) public delegators;
            bool depositIsEnabled = true;
            /**
             * @notice Initialize function for using with OpenZeppelin proxy
             * @param _workerFraction Share of token reward that worker node owner will get.
             * Use value up to BASIS_FRACTION, if _workerFraction = BASIS_FRACTION -> means 100% reward as commission
             * @param _router StakingInterfaceRouter address
             * @param _workerOwner Owner of worker node, only this address can withdraw worker commission
             */
            function initialize(
                uint256 _workerFraction,
                StakingInterfaceRouter _router,
                address _workerOwner
            ) public initializer {
                require(_workerOwner != address(0) && _workerFraction <= BASIS_FRACTION);
                InitializableStakingContract.initialize(_router);
                _transferOwnership(msg.sender);
                escrow = _router.target().escrow();
                workLock = _router.target().workLock();
                workerFraction = _workerFraction;
                workerOwner = _workerOwner;
            }
            /**
             * @notice Enabled deposit
             */
            function enableDeposit() external onlyOwner {
                depositIsEnabled = true;
                emit DepositSet(msg.sender, depositIsEnabled);
            }
            /**
             * @notice Disable deposit
             */
            function disableDeposit() external onlyOwner {
                depositIsEnabled = false;
                emit DepositSet(msg.sender, depositIsEnabled);
            }
            /**
             * @notice Calculate worker's fraction depending on deposited tokens
             */
            function getWorkerFraction() public view returns (uint256) {
                return workerFraction;
            }
            /**
             * @notice Transfer tokens as delegator
             * @param _value Amount of tokens to transfer
             */
            function depositTokens(uint256 _value) external {
                require(depositIsEnabled, "Deposit must be enabled");
                require(_value > 0, "Value must be not empty");
                totalDepositedTokens = totalDepositedTokens.add(_value);
                Delegator storage delegator = delegators[msg.sender];
                delegator.depositedTokens = delegator.depositedTokens.add(_value);
                token.safeTransferFrom(msg.sender, address(this), _value);
                emit TokensDeposited(msg.sender, _value, delegator.depositedTokens);
            }
            /**
             * @notice Delegator can transfer ETH directly to workLock
             */
            function escrowETH() external payable {
                Delegator storage delegator = delegators[msg.sender];
                delegator.depositedETHWorkLock = delegator.depositedETHWorkLock.add(msg.value);
                totalWorkLockETHReceived = totalWorkLockETHReceived.add(msg.value);
                workLock.bid{value: msg.value}();
                emit Bid(msg.sender, msg.value);
            }
            /**
             * @dev Hide method from StakingInterface
             */
            function bid(uint256) public payable {
                revert();
            }
            /**
             * @dev Hide method from StakingInterface
             */
            function withdrawCompensation() public pure {
                revert();
            }
            /**
             * @dev Hide method from StakingInterface
             */
            function cancelBid() public pure {
                revert();
            }
            /**
             * @dev Hide method from StakingInterface
             */
            function claim() public pure {
                revert();
            }
            /**
             * @notice Claim tokens in WorkLock and save number of claimed tokens
             */
            function claimTokensFromWorkLock() public {
                workLockClaimedTokens = workLock.claim();
                totalDepositedTokens = totalDepositedTokens.add(workLockClaimedTokens);
                emit Claimed(address(this), workLockClaimedTokens);
            }
            /**
             * @notice Calculate and save number of claimed tokens for specified delegator
             */
            function calculateAndSaveTokensAmount() external {
                Delegator storage delegator = delegators[msg.sender];
                calculateAndSaveTokensAmount(delegator);
            }
            /**
             * @notice Calculate and save number of claimed tokens for specified delegator
             */
            function calculateAndSaveTokensAmount(Delegator storage _delegator) internal {
                if (workLockClaimedTokens == 0 ||
                    _delegator.depositedETHWorkLock == 0 ||
                    _delegator.claimedWorkLockTokens)
                {
                    return;
                }
                uint256 delegatorTokensShare = _delegator.depositedETHWorkLock.mul(workLockClaimedTokens)
                    .div(totalWorkLockETHReceived);
                _delegator.depositedTokens = _delegator.depositedTokens.add(delegatorTokensShare);
                _delegator.claimedWorkLockTokens = true;
                emit Claimed(msg.sender, delegatorTokensShare);
            }
            /**
             * @notice Get available reward for all delegators and owner
             */
            function getAvailableReward() public view returns (uint256) {
                uint256 stakedTokens = escrow.getAllTokens(address(this));
                uint256 freeTokens = token.balanceOf(address(this));
                uint256 reward = stakedTokens.add(freeTokens).sub(totalDepositedTokens);
                if (reward > freeTokens) {
                    return freeTokens;
                }
                return reward;
            }
            /**
             * @notice Get cumulative reward
             */
            function getCumulativeReward() public view returns (uint256) {
                return getAvailableReward().add(totalWithdrawnReward);
            }
            /**
             * @notice Get available reward in tokens for worker node owner
             */
            function getAvailableWorkerReward() public view returns (uint256) {
                uint256 reward = getCumulativeReward();
                uint256 maxAllowableReward;
                if (totalDepositedTokens != 0) {
                    uint256 fraction = getWorkerFraction();
                    maxAllowableReward = reward.mul(fraction).div(BASIS_FRACTION);
                } else {
                    maxAllowableReward = reward;
                }
                if (maxAllowableReward > workerWithdrawnReward) {
                    return maxAllowableReward - workerWithdrawnReward;
                }
                return 0;
            }
            /**
             * @notice Get available reward in tokens for delegator
             */
            function getAvailableReward(address _delegator)
                public
                view
                returns (uint256)
            {
                if (totalDepositedTokens == 0) {
                    return 0;
                }
                uint256 reward = getCumulativeReward();
                Delegator storage delegator = delegators[_delegator];
                uint256 fraction = getWorkerFraction();
                uint256 maxAllowableReward = reward.mul(delegator.depositedTokens).mul(BASIS_FRACTION - fraction).div(
                    totalDepositedTokens.mul(BASIS_FRACTION)
                );
                return
                    maxAllowableReward > delegator.withdrawnReward
                        ? maxAllowableReward - delegator.withdrawnReward
                        : 0;
            }
            /**
             * @notice Withdraw reward in tokens to worker node owner
             */
            function withdrawWorkerReward() external {
                require(msg.sender == workerOwner);
                uint256 balance = token.balanceOf(address(this));
                uint256 availableReward = getAvailableWorkerReward();
                if (availableReward > balance) {
                    availableReward = balance;
                }
                require(
                    availableReward > 0,
                    "There is no available reward to withdraw"
                );
                workerWithdrawnReward = workerWithdrawnReward.add(availableReward);
                totalWithdrawnReward = totalWithdrawnReward.add(availableReward);
                token.safeTransfer(msg.sender, availableReward);
                emit TokensWithdrawn(msg.sender, availableReward, 0);
            }
            /**
             * @notice Withdraw reward to delegator
             * @param _value Amount of tokens to withdraw
             */
            function withdrawTokens(uint256 _value) public override {
                uint256 balance = token.balanceOf(address(this));
                require(_value <= balance, "Not enough tokens in the contract");
                Delegator storage delegator = delegators[msg.sender];
                calculateAndSaveTokensAmount(delegator);
                uint256 availableReward = getAvailableReward(msg.sender);
                require( _value <= availableReward, "Requested amount of tokens exceeded allowed portion");
                delegator.withdrawnReward = delegator.withdrawnReward.add(_value);
                totalWithdrawnReward = totalWithdrawnReward.add(_value);
                token.safeTransfer(msg.sender, _value);
                emit TokensWithdrawn(msg.sender, _value, delegator.depositedTokens);
            }
            /**
             * @notice Withdraw reward, deposit and fee to delegator
             */
            function withdrawAll() public {
                uint256 balance = token.balanceOf(address(this));
                Delegator storage delegator = delegators[msg.sender];
                calculateAndSaveTokensAmount(delegator);
                uint256 availableReward = getAvailableReward(msg.sender);
                uint256 value = availableReward.add(delegator.depositedTokens);
                require(value <= balance, "Not enough tokens in the contract");
                // TODO remove double reading
                uint256 availableWorkerReward = getAvailableWorkerReward();
                // potentially could be less then due reward
                uint256 availableETH = getAvailableETH(msg.sender);
                // prevent losing reward for worker after calculations
                uint256 workerReward = availableWorkerReward.mul(delegator.depositedTokens).div(totalDepositedTokens);
                if (workerReward > 0) {
                    require(value.add(workerReward) <= balance, "Not enough tokens in the contract");
                    token.safeTransfer(workerOwner, workerReward);
                    emit TokensWithdrawn(workerOwner, workerReward, 0);
                }
                uint256 withdrawnToDecrease = workerWithdrawnReward.mul(delegator.depositedTokens).div(totalDepositedTokens);
                workerWithdrawnReward = workerWithdrawnReward.sub(withdrawnToDecrease);
                totalWithdrawnReward = totalWithdrawnReward.sub(withdrawnToDecrease).sub(delegator.withdrawnReward);
                totalDepositedTokens = totalDepositedTokens.sub(delegator.depositedTokens);
                delegator.withdrawnReward = 0;
                delegator.depositedTokens = 0;
                token.safeTransfer(msg.sender, value);
                emit TokensWithdrawn(msg.sender, value, 0);
                totalWithdrawnETH = totalWithdrawnETH.sub(delegator.withdrawnETH);
                delegator.withdrawnETH = 0;
                if (availableETH > 0) {
                    msg.sender.sendValue(availableETH);
                    emit ETHWithdrawn(msg.sender, availableETH);
                }
            }
            /**
             * @notice Get available ether for delegator
             */
            function getAvailableETH(address _delegator) public view returns (uint256) {
                Delegator storage delegator = delegators[_delegator];
                uint256 balance = address(this).balance;
                // ETH balance + already withdrawn - (refunded - refundWithdrawn)
                balance = balance.add(totalWithdrawnETH).add(totalWorkLockETHWithdrawn).sub(totalWorkLockETHRefunded);
                uint256 maxAllowableETH = balance.mul(delegator.depositedTokens).div(totalDepositedTokens);
                uint256 availableETH = maxAllowableETH.sub(delegator.withdrawnETH);
                if (availableETH > balance) {
                    availableETH = balance;
                }
                return availableETH;
            }
            /**
             * @notice Withdraw available amount of ETH to delegator
             */
            function withdrawETH() public override {
                Delegator storage delegator = delegators[msg.sender];
                calculateAndSaveTokensAmount(delegator);
                uint256 availableETH = getAvailableETH(msg.sender);
                require(availableETH > 0, "There is no available ETH to withdraw");
                delegator.withdrawnETH = delegator.withdrawnETH.add(availableETH);
                totalWithdrawnETH = totalWithdrawnETH.add(availableETH);
                msg.sender.sendValue(availableETH);
                emit ETHWithdrawn(msg.sender, availableETH);
            }
            /**
             * @notice Withdraw compensation and refund from WorkLock and save these numbers
             */
            function refund() public {
                uint256 balance = address(this).balance;
                if (workLock.compensation(address(this)) > 0) {
                    workLock.withdrawCompensation();
                }
                workLock.refund();
                uint256 refundETH = address(this).balance - balance;
                totalWorkLockETHRefunded = totalWorkLockETHRefunded.add(refundETH);
                emit Refund(address(this), refundETH);
            }
            /**
             * @notice Get available refund for delegator
             */
            function getAvailableRefund(address _delegator) public view returns (uint256) {
                Delegator storage delegator = delegators[_delegator];
                uint256 maxAllowableETH = totalWorkLockETHRefunded.mul(delegator.depositedETHWorkLock)
                    .div(totalWorkLockETHReceived);
                uint256 availableETH = maxAllowableETH.sub(delegator.refundedETHWorkLock);
                uint256 balance = totalWorkLockETHRefunded.sub(totalWorkLockETHWithdrawn);
                if (availableETH > balance) {
                    availableETH = balance;
                }
                return availableETH;
            }
            /**
             * @notice Withdraw available amount of ETH to delegator
             */
            function withdrawRefund() external {
                uint256 availableETH = getAvailableRefund(msg.sender);
                require(availableETH > 0, "There is no available ETH to withdraw");
                Delegator storage delegator = delegators[msg.sender];
                delegator.refundedETHWorkLock = delegator.refundedETHWorkLock.add(availableETH);
                totalWorkLockETHWithdrawn = totalWorkLockETHWithdrawn.add(availableETH);
                msg.sender.sendValue(availableETH);
                emit Refund(msg.sender, availableETH);
            }
            /**
             * @notice Calling fallback function is allowed only for the owner
             */
            function isFallbackAllowed() public override view returns (bool) {
                return msg.sender == owner();
            }
        }
        // SPDX-License-Identifier: AGPL-3.0-or-later
        pragma solidity ^0.7.0;
        import "../aragon/interfaces/IERC900History.sol";
        import "./Issuer.sol";
        import "./lib/Bits.sol";
        import "./lib/Snapshot.sol";
        import "../zeppelin/math/SafeMath.sol";
        import "../zeppelin/token/ERC20/SafeERC20.sol";
        /**
        * @notice PolicyManager interface
        */
        interface PolicyManagerInterface {
            function register(address _node, uint16 _period) external;
            function updateFee(address _node, uint16 _period) external;
            function escrow() external view returns (address);
            function setDefaultFeeDelta(address _node, uint16 _period) external;
        }
        /**
        * @notice Adjudicator interface
        */
        interface AdjudicatorInterface {
            function escrow() external view returns (address);
        }
        /**
        * @notice WorkLock interface
        */
        interface WorkLockInterface {
            function escrow() external view returns (address);
        }
        /**
        * @notice Contract holds and locks stakers tokens.
        * Each staker that locks their tokens will receive some compensation
        * @dev |v5.4.2|
        */
        contract StakingEscrow is Issuer, IERC900History {
            using AdditionalMath for uint256;
            using AdditionalMath for uint16;
            using Bits for uint256;
            using SafeMath for uint256;
            using Snapshot for uint128[];
            using SafeERC20 for NuCypherToken;
            event Deposited(address indexed staker, uint256 value, uint16 periods);
            event Locked(address indexed staker, uint256 value, uint16 firstPeriod, uint16 periods);
            event Divided(
                address indexed staker,
                uint256 oldValue,
                uint16 lastPeriod,
                uint256 newValue,
                uint16 periods
            );
            event Merged(address indexed staker, uint256 value1, uint256 value2, uint16 lastPeriod);
            event Prolonged(address indexed staker, uint256 value, uint16 lastPeriod, uint16 periods);
            event Withdrawn(address indexed staker, uint256 value);
            event CommitmentMade(address indexed staker, uint16 indexed period, uint256 value);
            event Minted(address indexed staker, uint16 indexed period, uint256 value);
            event Slashed(address indexed staker, uint256 penalty, address indexed investigator, uint256 reward);
            event ReStakeSet(address indexed staker, bool reStake);
            event ReStakeLocked(address indexed staker, uint16 lockUntilPeriod);
            event WorkerBonded(address indexed staker, address indexed worker, uint16 indexed startPeriod);
            event WorkMeasurementSet(address indexed staker, bool measureWork);
            event WindDownSet(address indexed staker, bool windDown);
            event SnapshotSet(address indexed staker, bool snapshotsEnabled);
            struct SubStakeInfo {
                uint16 firstPeriod;
                uint16 lastPeriod;
                uint16 periods;
                uint128 lockedValue;
            }
            struct Downtime {
                uint16 startPeriod;
                uint16 endPeriod;
            }
            struct StakerInfo {
                uint256 value;
                /*
                * Stores periods that are committed but not yet rewarded.
                * In order to optimize storage, only two values are used instead of an array.
                * commitToNextPeriod() method invokes mint() method so there can only be two committed
                * periods that are not yet rewarded: the current and the next periods.
                */
                uint16 currentCommittedPeriod;
                uint16 nextCommittedPeriod;
                uint16 lastCommittedPeriod;
                uint16 lockReStakeUntilPeriod;
                uint256 completedWork;
                uint16 workerStartPeriod; // period when worker was bonded
                address worker;
                uint256 flags; // uint256 to acquire whole slot and minimize operations on it
                uint256 reservedSlot1;
                uint256 reservedSlot2;
                uint256 reservedSlot3;
                uint256 reservedSlot4;
                uint256 reservedSlot5;
                Downtime[] pastDowntime;
                SubStakeInfo[] subStakes;
                uint128[] history;
            }
            // used only for upgrading
            uint16 internal constant RESERVED_PERIOD = 0;
            uint16 internal constant MAX_CHECKED_VALUES = 5;
            // to prevent high gas consumption in loops for slashing
            uint16 public constant MAX_SUB_STAKES = 30;
            uint16 internal constant MAX_UINT16 = 65535;
            // indices for flags
            uint8 internal constant RE_STAKE_DISABLED_INDEX = 0;
            uint8 internal constant WIND_DOWN_INDEX = 1;
            uint8 internal constant MEASURE_WORK_INDEX = 2;
            uint8 internal constant SNAPSHOTS_DISABLED_INDEX = 3;
            uint16 public immutable minLockedPeriods;
            uint16 public immutable minWorkerPeriods;
            uint256 public immutable minAllowableLockedTokens;
            uint256 public immutable maxAllowableLockedTokens;
            bool public immutable isTestContract;
            mapping (address => StakerInfo) public stakerInfo;
            address[] public stakers;
            mapping (address => address) public stakerFromWorker;
            mapping (uint16 => uint256) public lockedPerPeriod;
            uint128[] public balanceHistory;
            PolicyManagerInterface public policyManager;
            AdjudicatorInterface public adjudicator;
            WorkLockInterface public workLock;
            /**
            * @notice Constructor sets address of token contract and coefficients for minting
            * @param _token Token contract
            * @param _hoursPerPeriod Size of period in hours
            * @param _issuanceDecayCoefficient (d) Coefficient which modifies the rate at which the maximum issuance decays,
            * only applicable to Phase 2. d = 365 * half-life / LOG2 where default half-life = 2.
            * See Equation 10 in Staking Protocol & Economics paper
            * @param _lockDurationCoefficient1 (k1) Numerator of the coefficient which modifies the extent
            * to which a stake's lock duration affects the subsidy it receives. Affects stakers differently.
            * Applicable to Phase 1 and Phase 2. k1 = k2 * small_stake_multiplier where default small_stake_multiplier = 0.5.
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _lockDurationCoefficient2 (k2) Denominator of the coefficient which modifies the extent
            * to which a stake's lock duration affects the subsidy it receives. Affects stakers differently.
            * Applicable to Phase 1 and Phase 2. k2 = maximum_rewarded_periods / (1 - small_stake_multiplier)
            * where default maximum_rewarded_periods = 365 and default small_stake_multiplier = 0.5.
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _maximumRewardedPeriods (kmax) Number of periods beyond which a stake's lock duration
            * no longer increases the subsidy it receives. kmax = reward_saturation * 365 where default reward_saturation = 1.
            * See Equation 8 in Staking Protocol & Economics paper.
            * @param _firstPhaseTotalSupply Total supply for the first phase
            * @param _firstPhaseMaxIssuance (Imax) Maximum number of new tokens minted per period during Phase 1.
            * See Equation 7 in Staking Protocol & Economics paper.
            * @param _minLockedPeriods Min amount of periods during which tokens can be locked
            * @param _minAllowableLockedTokens Min amount of tokens that can be locked
            * @param _maxAllowableLockedTokens Max amount of tokens that can be locked
            * @param _minWorkerPeriods Min amount of periods while a worker can't be changed
            * @param _isTestContract True if contract is only for tests
            */
            constructor(
                NuCypherToken _token,
                uint32 _hoursPerPeriod,
                uint256 _issuanceDecayCoefficient,
                uint256 _lockDurationCoefficient1,
                uint256 _lockDurationCoefficient2,
                uint16 _maximumRewardedPeriods,
                uint256 _firstPhaseTotalSupply,
                uint256 _firstPhaseMaxIssuance,
                uint16 _minLockedPeriods,
                uint256 _minAllowableLockedTokens,
                uint256 _maxAllowableLockedTokens,
                uint16 _minWorkerPeriods,
                bool _isTestContract
            )
                Issuer(
                    _token,
                    _hoursPerPeriod,
                    _issuanceDecayCoefficient,
                    _lockDurationCoefficient1,
                    _lockDurationCoefficient2,
                    _maximumRewardedPeriods,
                    _firstPhaseTotalSupply,
                    _firstPhaseMaxIssuance
                )
            {
                // constant `1` in the expression `_minLockedPeriods > 1` uses to simplify the `lock` method
                require(_minLockedPeriods > 1 && _maxAllowableLockedTokens != 0);
                minLockedPeriods = _minLockedPeriods;
                minAllowableLockedTokens = _minAllowableLockedTokens;
                maxAllowableLockedTokens = _maxAllowableLockedTokens;
                minWorkerPeriods = _minWorkerPeriods;
                isTestContract = _isTestContract;
            }
            /**
            * @dev Checks the existence of a staker in the contract
            */
            modifier onlyStaker()
            {
                StakerInfo storage info = stakerInfo[msg.sender];
                require(info.value > 0 || info.nextCommittedPeriod != 0);
                _;
            }
            //------------------------Initialization------------------------
            /**
            * @notice Set policy manager address
            */
            function setPolicyManager(PolicyManagerInterface _policyManager) external onlyOwner {
                // Policy manager can be set only once
                require(address(policyManager) == address(0));
                // This escrow must be the escrow for the new policy manager
                require(_policyManager.escrow() == address(this));
                policyManager = _policyManager;
            }
            /**
            * @notice Set adjudicator address
            */
            function setAdjudicator(AdjudicatorInterface _adjudicator) external onlyOwner {
                // Adjudicator can be set only once
                require(address(adjudicator) == address(0));
                // This escrow must be the escrow for the new adjudicator
                require(_adjudicator.escrow() == address(this));
                adjudicator = _adjudicator;
            }
            /**
            * @notice Set worklock address
            */
            function setWorkLock(WorkLockInterface _workLock) external onlyOwner {
                // WorkLock can be set only once
                require(address(workLock) == address(0) || isTestContract);
                // This escrow must be the escrow for the new worklock
                require(_workLock.escrow() == address(this));
                workLock = _workLock;
            }
            //------------------------Main getters------------------------
            /**
            * @notice Get all tokens belonging to the staker
            */
            function getAllTokens(address _staker) external view returns (uint256) {
                return stakerInfo[_staker].value;
            }
            /**
            * @notice Get all flags for the staker
            */
            function getFlags(address _staker)
                external view returns (
                    bool windDown,
                    bool reStake,
                    bool measureWork,
                    bool snapshots
                )
            {
                StakerInfo storage info = stakerInfo[_staker];
                windDown = info.flags.bitSet(WIND_DOWN_INDEX);
                reStake = !info.flags.bitSet(RE_STAKE_DISABLED_INDEX);
                measureWork = info.flags.bitSet(MEASURE_WORK_INDEX);
                snapshots = !info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX);
            }
            /**
            * @notice Get the start period. Use in the calculation of the last period of the sub stake
            * @param _info Staker structure
            * @param _currentPeriod Current period
            */
            function getStartPeriod(StakerInfo storage _info, uint16 _currentPeriod)
                internal view returns (uint16)
            {
                // if the next period (after current) is committed
                if (_info.flags.bitSet(WIND_DOWN_INDEX) && _info.nextCommittedPeriod > _currentPeriod) {
                    return _currentPeriod + 1;
                }
                return _currentPeriod;
            }
            /**
            * @notice Get the last period of the sub stake
            * @param _subStake Sub stake structure
            * @param _startPeriod Pre-calculated start period
            */
            function getLastPeriodOfSubStake(SubStakeInfo storage _subStake, uint16 _startPeriod)
                internal view returns (uint16)
            {
                if (_subStake.lastPeriod != 0) {
                    return _subStake.lastPeriod;
                }
                uint32 lastPeriod = uint32(_startPeriod) + _subStake.periods;
                if (lastPeriod > uint32(MAX_UINT16)) {
                    return MAX_UINT16;
                }
                return uint16(lastPeriod);
            }
            /**
            * @notice Get the last period of the sub stake
            * @param _staker Staker
            * @param _index Stake index
            */
            function getLastPeriodOfSubStake(address _staker, uint256 _index)
                public view returns (uint16)
            {
                StakerInfo storage info = stakerInfo[_staker];
                SubStakeInfo storage subStake = info.subStakes[_index];
                uint16 startPeriod = getStartPeriod(info, getCurrentPeriod());
                return getLastPeriodOfSubStake(subStake, startPeriod);
            }
            /**
            * @notice Get the value of locked tokens for a staker in a specified period
            * @dev Information may be incorrect for rewarded or not committed surpassed period
            * @param _info Staker structure
            * @param _currentPeriod Current period
            * @param _period Next period
            */
            function getLockedTokens(StakerInfo storage _info, uint16 _currentPeriod, uint16 _period)
                internal view returns (uint256 lockedValue)
            {
                lockedValue = 0;
                uint16 startPeriod = getStartPeriod(_info, _currentPeriod);
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    if (subStake.firstPeriod <= _period &&
                        getLastPeriodOfSubStake(subStake, startPeriod) >= _period) {
                        lockedValue += subStake.lockedValue;
                    }
                }
            }
            /**
            * @notice Get the value of locked tokens for a staker in a future period
            * @dev This function is used by PreallocationEscrow so its signature can't be updated.
            * @param _staker Staker
            * @param _periods Amount of periods that will be added to the current period
            */
            function getLockedTokens(address _staker, uint16 _periods)
                external view returns (uint256 lockedValue)
            {
                StakerInfo storage info = stakerInfo[_staker];
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod.add16(_periods);
                return getLockedTokens(info, currentPeriod, nextPeriod);
            }
            /**
            * @notice Get the last committed staker's period
            * @param _staker Staker
            */
            function getLastCommittedPeriod(address _staker) public view returns (uint16) {
                StakerInfo storage info = stakerInfo[_staker];
                return info.nextCommittedPeriod != 0 ? info.nextCommittedPeriod : info.lastCommittedPeriod;
            }
            /**
            * @notice Get the value of locked tokens for active stakers in (getCurrentPeriod() + _periods) period
            * as well as stakers and their locked tokens
            * @param _periods Amount of periods for locked tokens calculation
            * @param _startIndex Start index for looking in stakers array
            * @param _maxStakers Max stakers for looking, if set 0 then all will be used
            * @return allLockedTokens Sum of locked tokens for active stakers
            * @return activeStakers Array of stakers and their locked tokens. Stakers addresses stored as uint256
            * @dev Note that activeStakers[0] in an array of uint256, but you want addresses. Careful when used directly!
            */
            function getActiveStakers(uint16 _periods, uint256 _startIndex, uint256 _maxStakers)
                external view returns (uint256 allLockedTokens, uint256[2][] memory activeStakers)
            {
                require(_periods > 0);
                uint256 endIndex = stakers.length;
                require(_startIndex < endIndex);
                if (_maxStakers != 0 && _startIndex + _maxStakers < endIndex) {
                    endIndex = _startIndex + _maxStakers;
                }
                activeStakers = new uint256[2][](endIndex - _startIndex);
                allLockedTokens = 0;
                uint256 resultIndex = 0;
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod.add16(_periods);
                for (uint256 i = _startIndex; i < endIndex; i++) {
                    address staker = stakers[i];
                    StakerInfo storage info = stakerInfo[staker];
                    if (info.currentCommittedPeriod != currentPeriod &&
                        info.nextCommittedPeriod != currentPeriod) {
                        continue;
                    }
                    uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);
                    if (lockedTokens != 0) {
                        activeStakers[resultIndex][0] = uint256(staker);
                        activeStakers[resultIndex++][1] = lockedTokens;
                        allLockedTokens += lockedTokens;
                    }
                }
                assembly {
                    mstore(activeStakers, resultIndex)
                }
            }
            /**
            * @notice Checks if `reStake` parameter is available for changing
            * @param _staker Staker
            */
            function isReStakeLocked(address _staker) public view returns (bool) {
                return getCurrentPeriod() < stakerInfo[_staker].lockReStakeUntilPeriod;
            }
            /**
            * @notice Get worker using staker's address
            */
            function getWorkerFromStaker(address _staker) external view returns (address) {
                return stakerInfo[_staker].worker;
            }
            /**
            * @notice Get work that completed by the staker
            */
            function getCompletedWork(address _staker) external view returns (uint256) {
                return stakerInfo[_staker].completedWork;
            }
            /**
            * @notice Find index of downtime structure that includes specified period
            * @dev If specified period is outside all downtime periods, the length of the array will be returned
            * @param _staker Staker
            * @param _period Specified period number
            */
            function findIndexOfPastDowntime(address _staker, uint16 _period) external view returns (uint256 index) {
                StakerInfo storage info = stakerInfo[_staker];
                for (index = 0; index < info.pastDowntime.length; index++) {
                    if (_period <= info.pastDowntime[index].endPeriod) {
                        return index;
                    }
                }
            }
            //------------------------Main methods------------------------
            /**
            * @notice Start or stop measuring the work of a staker
            * @param _staker Staker
            * @param _measureWork Value for `measureWork` parameter
            * @return Work that was previously done
            */
            function setWorkMeasurement(address _staker, bool _measureWork) external returns (uint256) {
                require(msg.sender == address(workLock));
                StakerInfo storage info = stakerInfo[_staker];
                if (info.flags.bitSet(MEASURE_WORK_INDEX) == _measureWork) {
                    return info.completedWork;
                }
                info.flags = info.flags.toggleBit(MEASURE_WORK_INDEX);
                emit WorkMeasurementSet(_staker, _measureWork);
                return info.completedWork;
            }
            /**
            * @notice Bond worker
            * @param _worker Worker address. Must be a real address, not a contract
            */
            function bondWorker(address _worker) external onlyStaker {
                StakerInfo storage info = stakerInfo[msg.sender];
                // Specified worker is already bonded with this staker
                require(_worker != info.worker);
                uint16 currentPeriod = getCurrentPeriod();
                if (info.worker != address(0)) { // If this staker had a worker ...
                    // Check that enough time has passed to change it
                    require(currentPeriod >= info.workerStartPeriod.add16(minWorkerPeriods));
                    // Remove the old relation "worker->staker"
                    stakerFromWorker[info.worker] = address(0);
                }
                if (_worker != address(0)) {
                    // Specified worker is already in use
                    require(stakerFromWorker[_worker] == address(0));
                    // Specified worker is a staker
                    require(stakerInfo[_worker].subStakes.length == 0 || _worker == msg.sender);
                    // Set new worker->staker relation
                    stakerFromWorker[_worker] = msg.sender;
                }
                // Bond new worker (or unbond if _worker == address(0))
                info.worker = _worker;
                info.workerStartPeriod = currentPeriod;
                emit WorkerBonded(msg.sender, _worker, currentPeriod);
            }
            /**
            * @notice Set `reStake` parameter. If true then all staking rewards will be added to locked stake
            * Only if this parameter is not locked
            * @param _reStake Value for parameter
            */
            function setReStake(bool _reStake) external {
                require(!isReStakeLocked(msg.sender));
                StakerInfo storage info = stakerInfo[msg.sender];
                if (info.flags.bitSet(RE_STAKE_DISABLED_INDEX) == !_reStake) {
                    return;
                }
                info.flags = info.flags.toggleBit(RE_STAKE_DISABLED_INDEX);
                emit ReStakeSet(msg.sender, _reStake);
            }
            /**
            * @notice Lock `reStake` parameter. Only if this parameter is not locked
            * @param _lockReStakeUntilPeriod Can't change `reStake` value until this period
            */
            function lockReStake(uint16 _lockReStakeUntilPeriod) external {
                require(!isReStakeLocked(msg.sender) &&
                    _lockReStakeUntilPeriod > getCurrentPeriod());
                stakerInfo[msg.sender].lockReStakeUntilPeriod = _lockReStakeUntilPeriod;
                emit ReStakeLocked(msg.sender, _lockReStakeUntilPeriod);
            }
            /**
            * @notice Deposit tokens from WorkLock contract
            * @param _staker Staker address
            * @param _value Amount of tokens to deposit
            * @param _periods Amount of periods during which tokens will be locked
            */
            function depositFromWorkLock(
                address _staker,
                uint256 _value,
                uint16 _periods
            )
                external
            {
                require(msg.sender == address(workLock));
                StakerInfo storage info = stakerInfo[_staker];
                if (!info.flags.bitSet(WIND_DOWN_INDEX) && info.subStakes.length == 0) {
                    info.flags = info.flags.toggleBit(WIND_DOWN_INDEX);
                    emit WindDownSet(_staker, true);
                }
                deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods);
            }
            /**
            * @notice Set `windDown` parameter.
            * If true then stake's duration will be decreasing in each period with `commitToNextPeriod()`
            * @param _windDown Value for parameter
            */
            function setWindDown(bool _windDown) external onlyStaker {
                StakerInfo storage info = stakerInfo[msg.sender];
                if (info.flags.bitSet(WIND_DOWN_INDEX) == _windDown) {
                    return;
                }
                info.flags = info.flags.toggleBit(WIND_DOWN_INDEX);
                emit WindDownSet(msg.sender, _windDown);
                // duration adjustment if next period is committed
                uint16 nextPeriod = getCurrentPeriod() + 1;
                if (info.nextCommittedPeriod != nextPeriod) {
                   return;
                }
                // adjust sub-stakes duration for the new value of winding down parameter
                for (uint256 index = 0; index < info.subStakes.length; index++) {
                    SubStakeInfo storage subStake = info.subStakes[index];
                    // sub-stake does not have fixed last period when winding down is disabled
                    if (!_windDown && subStake.lastPeriod == nextPeriod) {
                        subStake.lastPeriod = 0;
                        subStake.periods = 1;
                        continue;
                    }
                    // this sub-stake is no longer affected by winding down parameter
                    if (subStake.lastPeriod != 0 || subStake.periods == 0) {
                        continue;
                    }
                    subStake.periods = _windDown ? subStake.periods - 1 : subStake.periods + 1;
                    if (subStake.periods == 0) {
                        subStake.lastPeriod = nextPeriod;
                    }
                }
            }
            /**
            * @notice Activate/deactivate taking snapshots of balances
            * @param _enableSnapshots True to activate snapshots, False to deactivate
            */
            function setSnapshots(bool _enableSnapshots) external {
                StakerInfo storage info = stakerInfo[msg.sender];
                if (info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX) == !_enableSnapshots) {
                    return;
                }
                uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());
                if(_enableSnapshots){
                    info.history.addSnapshot(info.value);
                    balanceHistory.addSnapshot(lastGlobalBalance + info.value);
                } else {
                    info.history.addSnapshot(0);
                    balanceHistory.addSnapshot(lastGlobalBalance - info.value);
                }
                info.flags = info.flags.toggleBit(SNAPSHOTS_DISABLED_INDEX);
                emit SnapshotSet(msg.sender, _enableSnapshots);
            }
            /**
            * @notice Adds a new snapshot to both the staker and global balance histories,
            * assuming the staker's balance was already changed
            * @param _info Reference to affected staker's struct
            * @param _addition Variance in balance. It can be positive or negative.
            */
            function addSnapshot(StakerInfo storage _info, int256 _addition) internal {
                if(!_info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX)){
                    _info.history.addSnapshot(_info.value);
                    uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());
                    balanceHistory.addSnapshot(lastGlobalBalance.addSigned(_addition));
                }
            }
            /**
            * @notice Batch deposit. Allowed only initial deposit for each staker
            * @param _stakers Stakers
            * @param _numberOfSubStakes Number of sub-stakes which belong to staker in _values and _periods arrays
            * @param _values Amount of tokens to deposit for each staker
            * @param _periods Amount of periods during which tokens will be locked for each staker
            */
            function batchDeposit(
                address[] calldata _stakers,
                uint256[] calldata _numberOfSubStakes,
                uint256[] calldata _values,
                uint16[] calldata _periods
            )
                external
            {
                uint256 subStakesLength = _values.length;
                require(_stakers.length != 0 &&
                    _stakers.length == _numberOfSubStakes.length &&
                    subStakesLength >= _stakers.length &&
                    _periods.length == subStakesLength);
                uint16 previousPeriod = getCurrentPeriod() - 1;
                uint16 nextPeriod = previousPeriod + 2;
                uint256 sumValue = 0;
                uint256 j = 0;
                for (uint256 i = 0; i < _stakers.length; i++) {
                    address staker = _stakers[i];
                    uint256 numberOfSubStakes = _numberOfSubStakes[i];
                    uint256 endIndex = j + numberOfSubStakes;
                    require(numberOfSubStakes > 0 && subStakesLength >= endIndex);
                    StakerInfo storage info = stakerInfo[staker];
                    require(info.subStakes.length == 0);
                    // A staker can't be a worker for another staker
                    require(stakerFromWorker[staker] == address(0));
                    stakers.push(staker);
                    policyManager.register(staker, previousPeriod);
                    for (; j < endIndex; j++) {
                        uint256 value =  _values[j];
                        uint16 periods = _periods[j];
                        require(value >= minAllowableLockedTokens && periods >= minLockedPeriods);
                        info.value = info.value.add(value);
                        info.subStakes.push(SubStakeInfo(nextPeriod, 0, periods, uint128(value)));
                        sumValue = sumValue.add(value);
                        emit Deposited(staker, value, periods);
                        emit Locked(staker, value, nextPeriod, periods);
                    }
                    require(info.value <= maxAllowableLockedTokens);
                    info.history.addSnapshot(info.value);
                }
                require(j == subStakesLength);
                uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());
                balanceHistory.addSnapshot(lastGlobalBalance + sumValue);
                token.safeTransferFrom(msg.sender, address(this), sumValue);
            }
            /**
            * @notice Implementation of the receiveApproval(address,uint256,address,bytes) method
            * (see NuCypherToken contract). Deposit all tokens that were approved to transfer
            * @param _from Staker
            * @param _value Amount of tokens to deposit
            * @param _tokenContract Token contract address
            * @notice (param _extraData) Amount of periods during which tokens will be locked
            */
            function receiveApproval(
                address _from,
                uint256 _value,
                address _tokenContract,
                bytes calldata /* _extraData */
            )
                external
            {
                require(_tokenContract == address(token) && msg.sender == address(token));
                // Copy first 32 bytes from _extraData, according to calldata memory layout:
                //
                // 0x00: method signature      4 bytes
                // 0x04: _from                 32 bytes after encoding
                // 0x24: _value                32 bytes after encoding
                // 0x44: _tokenContract        32 bytes after encoding
                // 0x64: _extraData pointer    32 bytes. Value must be 0x80 (offset of _extraData wrt to 1st parameter)
                // 0x84: _extraData length     32 bytes
                // 0xA4: _extraData data       Length determined by previous variable
                //
                // See https://solidity.readthedocs.io/en/latest/abi-spec.html#examples
                uint256 payloadSize;
                uint256 payload;
                assembly {
                    payloadSize := calldataload(0x84)
                    payload := calldataload(0xA4)
                }
                payload = payload >> 8*(32 - payloadSize);
                deposit(_from, _from, MAX_SUB_STAKES, _value, uint16(payload));
            }
            /**
            * @notice Deposit tokens and create new sub-stake. Use this method to become a staker
            * @param _staker Staker
            * @param _value Amount of tokens to deposit
            * @param _periods Amount of periods during which tokens will be locked
            */
            function deposit(address _staker, uint256 _value, uint16 _periods) external {
                deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods);
            }
            /**
            * @notice Deposit tokens and increase lock amount of an existing sub-stake
            * @dev This is preferable way to stake tokens because will be fewer active sub-stakes in the result
            * @param _index Index of the sub stake
            * @param _value Amount of tokens which will be locked
            */
            function depositAndIncrease(uint256 _index, uint256 _value) external onlyStaker {
                require(_index < MAX_SUB_STAKES);
                deposit(msg.sender, msg.sender, _index, _value, 0);
            }
            /**
            * @notice Deposit tokens
            * @dev Specify either index and zero periods (for an existing sub-stake)
            * or index >= MAX_SUB_STAKES and real value for periods (for a new sub-stake), not both
            * @param _staker Staker
            * @param _payer Owner of tokens
            * @param _index Index of the sub stake
            * @param _value Amount of tokens to deposit
            * @param _periods Amount of periods during which tokens will be locked
            */
            function deposit(address _staker, address _payer, uint256 _index, uint256 _value, uint16 _periods) internal {
                require(_value != 0);
                StakerInfo storage info = stakerInfo[_staker];
                // A staker can't be a worker for another staker
                require(stakerFromWorker[_staker] == address(0) || stakerFromWorker[_staker] == info.worker);
                // initial stake of the staker
                if (info.subStakes.length == 0) {
                    stakers.push(_staker);
                    policyManager.register(_staker, getCurrentPeriod() - 1);
                }
                token.safeTransferFrom(_payer, address(this), _value);
                info.value += _value;
                lock(_staker, _index, _value, _periods);
                addSnapshot(info, int256(_value));
                if (_index >= MAX_SUB_STAKES) {
                    emit Deposited(_staker, _value, _periods);
                } else {
                    uint16 lastPeriod = getLastPeriodOfSubStake(_staker, _index);
                    emit Deposited(_staker, _value, lastPeriod - getCurrentPeriod());
                }
            }
            /**
            * @notice Lock some tokens as a new sub-stake
            * @param _value Amount of tokens which will be locked
            * @param _periods Amount of periods during which tokens will be locked
            */
            function lockAndCreate(uint256 _value, uint16 _periods) external onlyStaker {
                lock(msg.sender, MAX_SUB_STAKES, _value, _periods);
            }
            /**
            * @notice Increase lock amount of an existing sub-stake
            * @param _index Index of the sub-stake
            * @param _value Amount of tokens which will be locked
            */
            function lockAndIncrease(uint256 _index, uint256 _value) external onlyStaker {
                require(_index < MAX_SUB_STAKES);
                lock(msg.sender, _index, _value, 0);
            }
            /**
            * @notice Lock some tokens as a stake
            * @dev Specify either index and zero periods (for an existing sub-stake)
            * or index >= MAX_SUB_STAKES and real value for periods (for a new sub-stake), not both
            * @param _staker Staker
            * @param _index Index of the sub stake
            * @param _value Amount of tokens which will be locked
            * @param _periods Amount of periods during which tokens will be locked
            */
            function lock(address _staker, uint256 _index, uint256 _value, uint16 _periods) internal {
                if (_index < MAX_SUB_STAKES) {
                    require(_value > 0);
                } else {
                    require(_value >= minAllowableLockedTokens && _periods >= minLockedPeriods);
                }
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod + 1;
                StakerInfo storage info = stakerInfo[_staker];
                uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);
                uint256 requestedLockedTokens = _value.add(lockedTokens);
                require(requestedLockedTokens <= info.value && requestedLockedTokens <= maxAllowableLockedTokens);
                // next period is committed
                if (info.nextCommittedPeriod == nextPeriod) {
                    lockedPerPeriod[nextPeriod] += _value;
                    emit CommitmentMade(_staker, nextPeriod, _value);
                }
                // if index was provided then increase existing sub-stake
                if (_index < MAX_SUB_STAKES) {
                    lockAndIncrease(info, currentPeriod, nextPeriod, _staker, _index, _value);
                // otherwise create new
                } else {
                    lockAndCreate(info, nextPeriod, _staker, _value, _periods);
                }
            }
            /**
            * @notice Lock some tokens as a new sub-stake
            * @param _info Staker structure
            * @param _nextPeriod Next period
            * @param _staker Staker
            * @param _value Amount of tokens which will be locked
            * @param _periods Amount of periods during which tokens will be locked
            */
            function lockAndCreate(
                StakerInfo storage _info,
                uint16 _nextPeriod,
                address _staker,
                uint256 _value,
                uint16 _periods
            )
                internal
            {
                uint16 duration = _periods;
                // if winding down is enabled and next period is committed
                // then sub-stakes duration were decreased
                if (_info.nextCommittedPeriod == _nextPeriod && _info.flags.bitSet(WIND_DOWN_INDEX)) {
                    duration -= 1;
                }
                saveSubStake(_info, _nextPeriod, 0, duration, _value);
                emit Locked(_staker, _value, _nextPeriod, _periods);
            }
            /**
            * @notice Increase lock amount of an existing sub-stake
            * @dev Probably will be created a new sub-stake but it will be active only one period
            * @param _info Staker structure
            * @param _currentPeriod Current period
            * @param _nextPeriod Next period
            * @param _staker Staker
            * @param _index Index of the sub-stake
            * @param _value Amount of tokens which will be locked
            */
            function lockAndIncrease(
                StakerInfo storage _info,
                uint16 _currentPeriod,
                uint16 _nextPeriod,
                address _staker,
                uint256 _index,
                uint256 _value
            )
                internal
            {
                SubStakeInfo storage subStake = _info.subStakes[_index];
                (, uint16 lastPeriod) = checkLastPeriodOfSubStake(_info, subStake, _currentPeriod);
                // create temporary sub-stake for current or previous committed periods
                // to leave locked amount in this period unchanged
                if (_info.currentCommittedPeriod != 0 &&
                    _info.currentCommittedPeriod <= _currentPeriod ||
                    _info.nextCommittedPeriod != 0 &&
                    _info.nextCommittedPeriod <= _currentPeriod)
                {
                    saveSubStake(_info, subStake.firstPeriod, _currentPeriod, 0, subStake.lockedValue);
                }
                subStake.lockedValue += uint128(_value);
                // all new locks should start from the next period
                subStake.firstPeriod = _nextPeriod;
                emit Locked(_staker, _value, _nextPeriod, lastPeriod - _currentPeriod);
            }
            /**
            * @notice Checks that last period of sub-stake is greater than the current period
            * @param _info Staker structure
            * @param _subStake Sub-stake structure
            * @param _currentPeriod Current period
            * @return startPeriod Start period. Use in the calculation of the last period of the sub stake
            * @return lastPeriod Last period of the sub stake
            */
            function checkLastPeriodOfSubStake(
                StakerInfo storage _info,
                SubStakeInfo storage _subStake,
                uint16 _currentPeriod
            )
                internal view returns (uint16 startPeriod, uint16 lastPeriod)
            {
                startPeriod = getStartPeriod(_info, _currentPeriod);
                lastPeriod = getLastPeriodOfSubStake(_subStake, startPeriod);
                // The sub stake must be active at least in the next period
                require(lastPeriod > _currentPeriod);
            }
            /**
            * @notice Save sub stake. First tries to override inactive sub stake
            * @dev Inactive sub stake means that last period of sub stake has been surpassed and already rewarded
            * @param _info Staker structure
            * @param _firstPeriod First period of the sub stake
            * @param _lastPeriod Last period of the sub stake
            * @param _periods Duration of the sub stake in periods
            * @param _lockedValue Amount of locked tokens
            */
            function saveSubStake(
                StakerInfo storage _info,
                uint16 _firstPeriod,
                uint16 _lastPeriod,
                uint16 _periods,
                uint256 _lockedValue
            )
                internal
            {
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    if (subStake.lastPeriod != 0 &&
                        (_info.currentCommittedPeriod == 0 ||
                        subStake.lastPeriod < _info.currentCommittedPeriod) &&
                        (_info.nextCommittedPeriod == 0 ||
                        subStake.lastPeriod < _info.nextCommittedPeriod))
                    {
                        subStake.firstPeriod = _firstPeriod;
                        subStake.lastPeriod = _lastPeriod;
                        subStake.periods = _periods;
                        subStake.lockedValue = uint128(_lockedValue);
                        return;
                    }
                }
                require(_info.subStakes.length < MAX_SUB_STAKES);
                _info.subStakes.push(SubStakeInfo(_firstPeriod, _lastPeriod, _periods, uint128(_lockedValue)));
            }
            /**
            * @notice Divide sub stake into two parts
            * @param _index Index of the sub stake
            * @param _newValue New sub stake value
            * @param _periods Amount of periods for extending sub stake
            */
            function divideStake(uint256 _index, uint256 _newValue, uint16 _periods) external onlyStaker {
                StakerInfo storage info = stakerInfo[msg.sender];
                require(_newValue >= minAllowableLockedTokens && _periods > 0);
                SubStakeInfo storage subStake = info.subStakes[_index];
                uint16 currentPeriod = getCurrentPeriod();
                (, uint16 lastPeriod) = checkLastPeriodOfSubStake(info, subStake, currentPeriod);
                uint256 oldValue = subStake.lockedValue;
                subStake.lockedValue = uint128(oldValue.sub(_newValue));
                require(subStake.lockedValue >= minAllowableLockedTokens);
                uint16 requestedPeriods = subStake.periods.add16(_periods);
                saveSubStake(info, subStake.firstPeriod, 0, requestedPeriods, _newValue);
                emit Divided(msg.sender, oldValue, lastPeriod, _newValue, _periods);
                emit Locked(msg.sender, _newValue, subStake.firstPeriod, requestedPeriods);
            }
            /**
            * @notice Prolong active sub stake
            * @param _index Index of the sub stake
            * @param _periods Amount of periods for extending sub stake
            */
            function prolongStake(uint256 _index, uint16 _periods) external onlyStaker {
                StakerInfo storage info = stakerInfo[msg.sender];
                // Incorrect parameters
                require(_periods > 0);
                SubStakeInfo storage subStake = info.subStakes[_index];
                uint16 currentPeriod = getCurrentPeriod();
                (uint16 startPeriod, uint16 lastPeriod) = checkLastPeriodOfSubStake(info, subStake, currentPeriod);
                subStake.periods = subStake.periods.add16(_periods);
                // if the sub stake ends in the next committed period then reset the `lastPeriod` field
                if (lastPeriod == startPeriod) {
                    subStake.lastPeriod = 0;
                }
                // The extended sub stake must not be less than the minimum value
                require(uint32(lastPeriod - currentPeriod) + _periods >= minLockedPeriods);
                emit Locked(msg.sender, subStake.lockedValue, lastPeriod + 1, _periods);
                emit Prolonged(msg.sender, subStake.lockedValue, lastPeriod, _periods);
            }
            /**
            * @notice Merge two sub-stakes into one if their last periods are equal
            * @dev It's possible that both sub-stakes will be active after this transaction.
            * But only one of them will be active until next call `commitToNextPeriod` (in the next period)
            * @param _index1 Index of the first sub-stake
            * @param _index2 Index of the second sub-stake
            */
            function mergeStake(uint256 _index1, uint256 _index2) external onlyStaker {
                require(_index1 != _index2); // must be different sub-stakes
                StakerInfo storage info = stakerInfo[msg.sender];
                SubStakeInfo storage subStake1 = info.subStakes[_index1];
                SubStakeInfo storage subStake2 = info.subStakes[_index2];
                uint16 currentPeriod = getCurrentPeriod();
                (, uint16 lastPeriod1) = checkLastPeriodOfSubStake(info, subStake1, currentPeriod);
                (, uint16 lastPeriod2) = checkLastPeriodOfSubStake(info, subStake2, currentPeriod);
                // both sub-stakes must have equal last period to be mergeable
                require(lastPeriod1 == lastPeriod2);
                emit Merged(msg.sender, subStake1.lockedValue, subStake2.lockedValue, lastPeriod1);
                if (subStake1.firstPeriod == subStake2.firstPeriod) {
                    subStake1.lockedValue += subStake2.lockedValue;
                    subStake2.lastPeriod = 1;
                    subStake2.periods = 0;
                } else if (subStake1.firstPeriod > subStake2.firstPeriod) {
                    subStake1.lockedValue += subStake2.lockedValue;
                    subStake2.lastPeriod = subStake1.firstPeriod - 1;
                    subStake2.periods = 0;
                } else {
                    subStake2.lockedValue += subStake1.lockedValue;
                    subStake1.lastPeriod = subStake2.firstPeriod - 1;
                    subStake1.periods = 0;
                }
            }
            /**
            * @notice Withdraw available amount of tokens to staker
            * @param _value Amount of tokens to withdraw
            */
            function withdraw(uint256 _value) external onlyStaker {
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod + 1;
                StakerInfo storage info = stakerInfo[msg.sender];
                // the max locked tokens in most cases will be in the current period
                // but when the staker locks more then we should use the next period
                uint256 lockedTokens = Math.max(getLockedTokens(info, currentPeriod, nextPeriod),
                    getLockedTokens(info, currentPeriod, currentPeriod));
                require(_value <= info.value.sub(lockedTokens));
                info.value -= _value;
                addSnapshot(info, - int256(_value));
                token.safeTransfer(msg.sender, _value);
                emit Withdrawn(msg.sender, _value);
                // unbond worker if staker withdraws last portion of NU
                if (info.value == 0 &&
                    info.nextCommittedPeriod == 0 &&
                    info.worker != address(0))
                {
                    stakerFromWorker[info.worker] = address(0);
                    info.worker = address(0);
                    emit WorkerBonded(msg.sender, address(0), currentPeriod);
                }
            }
            /**
            * @notice Make a commitment to the next period and mint for the previous period
            */
            function commitToNextPeriod() external isInitialized {
                address staker = stakerFromWorker[msg.sender];
                StakerInfo storage info = stakerInfo[staker];
                // Staker must have a stake to make a commitment
                require(info.value > 0);
                // Only worker with real address can make a commitment
                require(msg.sender == tx.origin);
                uint16 lastCommittedPeriod = getLastCommittedPeriod(staker);
                mint(staker);
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod + 1;
                // the period has already been committed
                if (info.nextCommittedPeriod == nextPeriod) {
                    return;
                }
                uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod);
                require(lockedTokens > 0);
                lockedPerPeriod[nextPeriod] += lockedTokens;
                info.currentCommittedPeriod = info.nextCommittedPeriod;
                info.nextCommittedPeriod = nextPeriod;
                decreaseSubStakesDuration(info, nextPeriod);
                // staker was inactive for several periods
                if (lastCommittedPeriod < currentPeriod) {
                    info.pastDowntime.push(Downtime(lastCommittedPeriod + 1, currentPeriod));
                }
                policyManager.setDefaultFeeDelta(staker, nextPeriod);
                emit CommitmentMade(staker, nextPeriod, lockedTokens);
            }
            /**
            * @notice Decrease sub-stakes duration if `windDown` is enabled
            */
            function decreaseSubStakesDuration(StakerInfo storage _info, uint16 _nextPeriod) internal {
                if (!_info.flags.bitSet(WIND_DOWN_INDEX)) {
                    return;
                }
                for (uint256 index = 0; index < _info.subStakes.length; index++) {
                    SubStakeInfo storage subStake = _info.subStakes[index];
                    if (subStake.lastPeriod != 0 || subStake.periods == 0) {
                        continue;
                    }
                    subStake.periods--;
                    if (subStake.periods == 0) {
                        subStake.lastPeriod = _nextPeriod;
                    }
                }
            }
            /**
            * @notice Mint tokens for previous periods if staker locked their tokens and made a commitment
            */
            function mint() external onlyStaker {
                // save last committed period to the storage if both periods will be empty after minting
                // because we won't be able to calculate last committed period
                // see getLastCommittedPeriod(address)
                StakerInfo storage info = stakerInfo[msg.sender];
                uint16 previousPeriod = getCurrentPeriod() - 1;
                if (info.nextCommittedPeriod <= previousPeriod && info.nextCommittedPeriod != 0) {
                    info.lastCommittedPeriod = info.nextCommittedPeriod;
                }
                mint(msg.sender);
            }
            /**
            * @notice Mint tokens for previous periods if staker locked their tokens and made a commitment
            * @param _staker Staker
            */
            function mint(address _staker) internal {
                uint16 currentPeriod = getCurrentPeriod();
                uint16 previousPeriod = currentPeriod  - 1;
                StakerInfo storage info = stakerInfo[_staker];
                if (info.nextCommittedPeriod == 0 ||
                    info.currentCommittedPeriod == 0 &&
                    info.nextCommittedPeriod > previousPeriod ||
                    info.currentCommittedPeriod > previousPeriod) {
                    return;
                }
                uint16 startPeriod = getStartPeriod(info, currentPeriod);
                uint256 reward = 0;
                bool reStake = !info.flags.bitSet(RE_STAKE_DISABLED_INDEX);
                if (info.currentCommittedPeriod != 0) {
                    reward = mint(_staker, info, info.currentCommittedPeriod, currentPeriod, startPeriod, reStake);
                    info.currentCommittedPeriod = 0;
                    if (reStake) {
                        lockedPerPeriod[info.nextCommittedPeriod] += reward;
                    }
                }
                if (info.nextCommittedPeriod <= previousPeriod) {
                    reward += mint(_staker, info, info.nextCommittedPeriod, currentPeriod, startPeriod, reStake);
                    info.nextCommittedPeriod = 0;
                }
                info.value += reward;
                if (info.flags.bitSet(MEASURE_WORK_INDEX)) {
                    info.completedWork += reward;
                }
                addSnapshot(info, int256(reward));
                emit Minted(_staker, previousPeriod, reward);
            }
            /**
            * @notice Calculate reward for one period
            * @param _staker Staker's address
            * @param _info Staker structure
            * @param _mintingPeriod Period for minting calculation
            * @param _currentPeriod Current period
            * @param _startPeriod Pre-calculated start period
            */
            function mint(
                address _staker,
                StakerInfo storage _info,
                uint16 _mintingPeriod,
                uint16 _currentPeriod,
                uint16 _startPeriod,
                bool _reStake
            )
                internal returns (uint256 reward)
            {
                reward = 0;
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake =  _info.subStakes[i];
                    uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
                    if (subStake.firstPeriod <= _mintingPeriod && lastPeriod >= _mintingPeriod) {
                        uint256 subStakeReward = mint(
                            _currentPeriod,
                            subStake.lockedValue,
                            lockedPerPeriod[_mintingPeriod],
                            lastPeriod.sub16(_mintingPeriod));
                        reward += subStakeReward;
                        if (_reStake) {
                            subStake.lockedValue += uint128(subStakeReward);
                        }
                    }
                }
                policyManager.updateFee(_staker, _mintingPeriod);
                return reward;
            }
            //-------------------------Slashing-------------------------
            /**
            * @notice Slash the staker's stake and reward the investigator
            * @param _staker Staker's address
            * @param _penalty Penalty
            * @param _investigator Investigator
            * @param _reward Reward for the investigator
            */
            function slashStaker(
                address _staker,
                uint256 _penalty,
                address _investigator,
                uint256 _reward
            )
                public isInitialized
            {
                require(msg.sender == address(adjudicator));
                require(_penalty > 0);
                StakerInfo storage info = stakerInfo[_staker];
                if (info.value <= _penalty) {
                    _penalty = info.value;
                }
                info.value -= _penalty;
                if (_reward > _penalty) {
                    _reward = _penalty;
                }
                uint16 currentPeriod = getCurrentPeriod();
                uint16 nextPeriod = currentPeriod + 1;
                uint16 startPeriod = getStartPeriod(info, currentPeriod);
                (uint256 currentLock, uint256 nextLock, uint256 currentAndNextLock, uint256 shortestSubStakeIndex) =
                    getLockedTokensAndShortestSubStake(info, currentPeriod, nextPeriod, startPeriod);
                // Decrease the stake if amount of locked tokens in the current period more than staker has
                uint256 lockedTokens = currentLock + currentAndNextLock;
                if (info.value < lockedTokens) {
                   decreaseSubStakes(info, lockedTokens - info.value, currentPeriod, startPeriod, shortestSubStakeIndex);
                }
                // Decrease the stake if amount of locked tokens in the next period more than staker has
                if (nextLock > 0) {
                    lockedTokens = nextLock + currentAndNextLock -
                        (currentAndNextLock > info.value ? currentAndNextLock - info.value : 0);
                    if (info.value < lockedTokens) {
                       decreaseSubStakes(info, lockedTokens - info.value, nextPeriod, startPeriod, MAX_SUB_STAKES);
                    }
                }
                emit Slashed(_staker, _penalty, _investigator, _reward);
                if (_penalty > _reward) {
                    unMint(_penalty - _reward);
                }
                // TODO change to withdrawal pattern (#1499)
                if (_reward > 0) {
                    token.safeTransfer(_investigator, _reward);
                }
                addSnapshot(info, - int256(_penalty));
            }
            /**
            * @notice Get the value of locked tokens for a staker in the current and the next period
            * and find the shortest sub stake
            * @param _info Staker structure
            * @param _currentPeriod Current period
            * @param _nextPeriod Next period
            * @param _startPeriod Pre-calculated start period
            * @return currentLock Amount of tokens that locked in the current period and unlocked in the next period
            * @return nextLock Amount of tokens that locked in the next period and not locked in the current period
            * @return currentAndNextLock Amount of tokens that locked in the current period and in the next period
            * @return shortestSubStakeIndex Index of the shortest sub stake
            */
            function getLockedTokensAndShortestSubStake(
                StakerInfo storage _info,
                uint16 _currentPeriod,
                uint16 _nextPeriod,
                uint16 _startPeriod
            )
                internal view returns (
                    uint256 currentLock,
                    uint256 nextLock,
                    uint256 currentAndNextLock,
                    uint256 shortestSubStakeIndex
                )
            {
                uint16 minDuration = MAX_UINT16;
                uint16 minLastPeriod = MAX_UINT16;
                shortestSubStakeIndex = MAX_SUB_STAKES;
                currentLock = 0;
                nextLock = 0;
                currentAndNextLock = 0;
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
                    if (lastPeriod < subStake.firstPeriod) {
                        continue;
                    }
                    if (subStake.firstPeriod <= _currentPeriod &&
                        lastPeriod >= _nextPeriod) {
                        currentAndNextLock += subStake.lockedValue;
                    } else if (subStake.firstPeriod <= _currentPeriod &&
                        lastPeriod >= _currentPeriod) {
                        currentLock += subStake.lockedValue;
                    } else if (subStake.firstPeriod <= _nextPeriod &&
                        lastPeriod >= _nextPeriod) {
                        nextLock += subStake.lockedValue;
                    }
                    uint16 duration = lastPeriod - subStake.firstPeriod;
                    if (subStake.firstPeriod <= _currentPeriod &&
                        lastPeriod >= _currentPeriod &&
                        (lastPeriod < minLastPeriod ||
                        lastPeriod == minLastPeriod && duration < minDuration))
                    {
                        shortestSubStakeIndex = i;
                        minDuration = duration;
                        minLastPeriod = lastPeriod;
                    }
                }
            }
            /**
            * @notice Decrease short sub stakes
            * @param _info Staker structure
            * @param _penalty Penalty rate
            * @param _decreasePeriod The period when the decrease begins
            * @param _startPeriod Pre-calculated start period
            * @param _shortestSubStakeIndex Index of the shortest period
            */
            function decreaseSubStakes(
                StakerInfo storage _info,
                uint256 _penalty,
                uint16 _decreasePeriod,
                uint16 _startPeriod,
                uint256 _shortestSubStakeIndex
            )
                internal
            {
                SubStakeInfo storage shortestSubStake = _info.subStakes[0];
                uint16 minSubStakeLastPeriod = MAX_UINT16;
                uint16 minSubStakeDuration = MAX_UINT16;
                while(_penalty > 0) {
                    if (_shortestSubStakeIndex < MAX_SUB_STAKES) {
                        shortestSubStake = _info.subStakes[_shortestSubStakeIndex];
                        minSubStakeLastPeriod = getLastPeriodOfSubStake(shortestSubStake, _startPeriod);
                        minSubStakeDuration = minSubStakeLastPeriod - shortestSubStake.firstPeriod;
                        _shortestSubStakeIndex = MAX_SUB_STAKES;
                    } else {
                        (shortestSubStake, minSubStakeDuration, minSubStakeLastPeriod) =
                            getShortestSubStake(_info, _decreasePeriod, _startPeriod);
                    }
                    if (minSubStakeDuration == MAX_UINT16) {
                        break;
                    }
                    uint256 appliedPenalty = _penalty;
                    if (_penalty < shortestSubStake.lockedValue) {
                        shortestSubStake.lockedValue -= uint128(_penalty);
                        saveOldSubStake(_info, shortestSubStake.firstPeriod, _penalty, _decreasePeriod);
                        _penalty = 0;
                    } else {
                        shortestSubStake.lastPeriod = _decreasePeriod - 1;
                        _penalty -= shortestSubStake.lockedValue;
                        appliedPenalty = shortestSubStake.lockedValue;
                    }
                    if (_info.currentCommittedPeriod >= _decreasePeriod &&
                        _info.currentCommittedPeriod <= minSubStakeLastPeriod)
                    {
                        lockedPerPeriod[_info.currentCommittedPeriod] -= appliedPenalty;
                    }
                    if (_info.nextCommittedPeriod >= _decreasePeriod &&
                        _info.nextCommittedPeriod <= minSubStakeLastPeriod)
                    {
                        lockedPerPeriod[_info.nextCommittedPeriod] -= appliedPenalty;
                    }
                }
            }
            /**
            * @notice Get the shortest sub stake
            * @param _info Staker structure
            * @param _currentPeriod Current period
            * @param _startPeriod Pre-calculated start period
            * @return shortestSubStake The shortest sub stake
            * @return minSubStakeDuration Duration of the shortest sub stake
            * @return minSubStakeLastPeriod Last period of the shortest sub stake
            */
            function getShortestSubStake(
                StakerInfo storage _info,
                uint16 _currentPeriod,
                uint16 _startPeriod
            )
                internal view returns (
                    SubStakeInfo storage shortestSubStake,
                    uint16 minSubStakeDuration,
                    uint16 minSubStakeLastPeriod
                )
            {
                shortestSubStake = shortestSubStake;
                minSubStakeDuration = MAX_UINT16;
                minSubStakeLastPeriod = MAX_UINT16;
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
                    if (lastPeriod < subStake.firstPeriod) {
                        continue;
                    }
                    uint16 duration = lastPeriod - subStake.firstPeriod;
                    if (subStake.firstPeriod <= _currentPeriod &&
                        lastPeriod >= _currentPeriod &&
                        (lastPeriod < minSubStakeLastPeriod ||
                        lastPeriod == minSubStakeLastPeriod && duration < minSubStakeDuration))
                    {
                        shortestSubStake = subStake;
                        minSubStakeDuration = duration;
                        minSubStakeLastPeriod = lastPeriod;
                    }
                }
            }
            /**
            * @notice Save the old sub stake values to prevent decreasing reward for the previous period
            * @dev Saving happens only if the previous period is committed
            * @param _info Staker structure
            * @param _firstPeriod First period of the old sub stake
            * @param _lockedValue Locked value of the old sub stake
            * @param _currentPeriod Current period, when the old sub stake is already unlocked
            */
            function saveOldSubStake(
                StakerInfo storage _info,
                uint16 _firstPeriod,
                uint256 _lockedValue,
                uint16 _currentPeriod
            )
                internal
            {
                // Check that the old sub stake should be saved
                bool oldCurrentCommittedPeriod = _info.currentCommittedPeriod != 0 &&
                    _info.currentCommittedPeriod < _currentPeriod;
                bool oldnextCommittedPeriod = _info.nextCommittedPeriod != 0 &&
                    _info.nextCommittedPeriod < _currentPeriod;
                bool crosscurrentCommittedPeriod = oldCurrentCommittedPeriod && _info.currentCommittedPeriod >= _firstPeriod;
                bool crossnextCommittedPeriod = oldnextCommittedPeriod && _info.nextCommittedPeriod >= _firstPeriod;
                if (!crosscurrentCommittedPeriod && !crossnextCommittedPeriod) {
                    return;
                }
                // Try to find already existent proper old sub stake
                uint16 previousPeriod = _currentPeriod - 1;
                for (uint256 i = 0; i < _info.subStakes.length; i++) {
                    SubStakeInfo storage subStake = _info.subStakes[i];
                    if (subStake.lastPeriod == previousPeriod &&
                        ((crosscurrentCommittedPeriod ==
                        (oldCurrentCommittedPeriod && _info.currentCommittedPeriod >= subStake.firstPeriod)) &&
                        (crossnextCommittedPeriod ==
                        (oldnextCommittedPeriod && _info.nextCommittedPeriod >= subStake.firstPeriod))))
                    {
                        subStake.lockedValue += uint128(_lockedValue);
                        return;
                    }
                }
                saveSubStake(_info, _firstPeriod, previousPeriod, 0, _lockedValue);
            }
            //-------------Additional getters for stakers info-------------
            /**
            * @notice Return the length of the array of stakers
            */
            function getStakersLength() external view returns (uint256) {
                return stakers.length;
            }
            /**
            * @notice Return the length of the array of sub stakes
            */
            function getSubStakesLength(address _staker) external view returns (uint256) {
                return stakerInfo[_staker].subStakes.length;
            }
            /**
            * @notice Return the information about sub stake
            */
            function getSubStakeInfo(address _staker, uint256 _index)
            // TODO change to structure when ABIEncoderV2 is released (#1501)
        //        public view returns (SubStakeInfo)
                // TODO "virtual" only for tests, probably will be removed after #1512
                external view virtual returns (uint16 firstPeriod, uint16 lastPeriod, uint16 periods, uint128 lockedValue)
            {
                SubStakeInfo storage info = stakerInfo[_staker].subStakes[_index];
                firstPeriod = info.firstPeriod;
                lastPeriod = info.lastPeriod;
                periods = info.periods;
                lockedValue = info.lockedValue;
            }
            /**
            * @notice Return the length of the array of past downtime
            */
            function getPastDowntimeLength(address _staker) external view returns (uint256) {
                return stakerInfo[_staker].pastDowntime.length;
            }
            /**
            * @notice Return the information about past downtime
            */
            function  getPastDowntime(address _staker, uint256 _index)
            // TODO change to structure when ABIEncoderV2 is released (#1501)
        //        public view returns (Downtime)
                external view returns (uint16 startPeriod, uint16 endPeriod)
            {
                Downtime storage downtime = stakerInfo[_staker].pastDowntime[_index];
                startPeriod = downtime.startPeriod;
                endPeriod = downtime.endPeriod;
            }
            //------------------ ERC900 connectors ----------------------
            function totalStakedForAt(address _owner, uint256 _blockNumber) public view override returns (uint256){
                return stakerInfo[_owner].history.getValueAt(_blockNumber);
            }
            function totalStakedAt(uint256 _blockNumber) public view override returns (uint256){
                return balanceHistory.getValueAt(_blockNumber);
            }
            function supportsHistory() external pure override returns (bool){
                return true;
            }
            //------------------------Upgradeable------------------------
            /**
            * @dev Get StakerInfo structure by delegatecall
            */
            function delegateGetStakerInfo(address _target, bytes32 _staker)
                internal returns (StakerInfo memory result)
            {
                bytes32 memoryAddress = delegateGetData(_target, this.stakerInfo.selector, 1, _staker, 0);
                assembly {
                    result := memoryAddress
                }
            }
            /**
            * @dev Get SubStakeInfo structure by delegatecall
            */
            function delegateGetSubStakeInfo(address _target, bytes32 _staker, uint256 _index)
                internal returns (SubStakeInfo memory result)
            {
                bytes32 memoryAddress = delegateGetData(
                    _target, this.getSubStakeInfo.selector, 2, _staker, bytes32(_index));
                assembly {
                    result := memoryAddress
                }
            }
            /**
            * @dev Get Downtime structure by delegatecall
            */
            function delegateGetPastDowntime(address _target, bytes32 _staker, uint256 _index)
                internal returns (Downtime memory result)
            {
                bytes32 memoryAddress = delegateGetData(
                    _target, this.getPastDowntime.selector, 2, _staker, bytes32(_index));
                assembly {
                    result := memoryAddress
                }
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
            function verifyState(address _testTarget) public override virtual {
                super.verifyState(_testTarget);
                require(address(delegateGet(_testTarget, this.policyManager.selector)) == address(policyManager));
                require(address(delegateGet(_testTarget, this.adjudicator.selector)) == address(adjudicator));
                require(address(delegateGet(_testTarget, this.workLock.selector)) == address(workLock));
                require(delegateGet(_testTarget, this.lockedPerPeriod.selector,
                    bytes32(bytes2(RESERVED_PERIOD))) == lockedPerPeriod[RESERVED_PERIOD]);
                require(address(delegateGet(_testTarget, this.stakerFromWorker.selector, bytes32(0))) ==
                    stakerFromWorker[address(0)]);
                require(delegateGet(_testTarget, this.getStakersLength.selector) == stakers.length);
                if (stakers.length == 0) {
                    return;
                }
                address stakerAddress = stakers[0];
                require(address(uint160(delegateGet(_testTarget, this.stakers.selector, 0))) == stakerAddress);
                StakerInfo storage info = stakerInfo[stakerAddress];
                bytes32 staker = bytes32(uint256(stakerAddress));
                StakerInfo memory infoToCheck = delegateGetStakerInfo(_testTarget, staker);
                require(infoToCheck.value == info.value &&
                    infoToCheck.currentCommittedPeriod == info.currentCommittedPeriod &&
                    infoToCheck.nextCommittedPeriod == info.nextCommittedPeriod &&
                    infoToCheck.flags == info.flags &&
                    infoToCheck.lockReStakeUntilPeriod == info.lockReStakeUntilPeriod &&
                    infoToCheck.lastCommittedPeriod == info.lastCommittedPeriod &&
                    infoToCheck.completedWork == info.completedWork &&
                    infoToCheck.worker == info.worker &&
                    infoToCheck.workerStartPeriod == info.workerStartPeriod);
                require(delegateGet(_testTarget, this.getPastDowntimeLength.selector, staker) ==
                    info.pastDowntime.length);
                for (uint256 i = 0; i < info.pastDowntime.length && i < MAX_CHECKED_VALUES; i++) {
                    Downtime storage downtime = info.pastDowntime[i];
                    Downtime memory downtimeToCheck = delegateGetPastDowntime(_testTarget, staker, i);
                    require(downtimeToCheck.startPeriod == downtime.startPeriod &&
                        downtimeToCheck.endPeriod == downtime.endPeriod);
                }
                require(delegateGet(_testTarget, this.getSubStakesLength.selector, staker) == info.subStakes.length);
                for (uint256 i = 0; i < info.subStakes.length && i < MAX_CHECKED_VALUES; i++) {
                    SubStakeInfo storage subStakeInfo = info.subStakes[i];
                    SubStakeInfo memory subStakeInfoToCheck = delegateGetSubStakeInfo(_testTarget, staker, i);
                    require(subStakeInfoToCheck.firstPeriod == subStakeInfo.firstPeriod &&
                        subStakeInfoToCheck.lastPeriod == subStakeInfo.lastPeriod &&
                        subStakeInfoToCheck.periods == subStakeInfo.periods &&
                        subStakeInfoToCheck.lockedValue == subStakeInfo.lockedValue);
                }
                // it's not perfect because checks not only slot value but also decoding
                // at least without additional functions
                require(delegateGet(_testTarget, this.totalStakedForAt.selector, staker, bytes32(block.number)) ==
                    totalStakedForAt(stakerAddress, block.number));
                require(delegateGet(_testTarget, this.totalStakedAt.selector, bytes32(block.number)) ==
                    totalStakedAt(block.number));
                if (info.worker != address(0)) {
                    require(address(delegateGet(_testTarget, this.stakerFromWorker.selector, bytes32(uint256(info.worker)))) ==
                        stakerFromWorker[info.worker]);
                }
            }
            /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade`
            function finishUpgrade(address _target) public override virtual {
                super.finishUpgrade(_target);
                // Create fake period
                lockedPerPeriod[RESERVED_PERIOD] = 111;
                // Create fake worker
                stakerFromWorker[address(0)] = address(this);
            }
        }