ETH Price: $2,172.77 (+1.32%)

Transaction Decoder

Block:
11477732 at Dec-18-2020 02:31:52 PM +UTC
Transaction Fee:
0.00477369 ETH $10.37
Gas Used:
51,330 Gas / 93 Gwei

Emitted Events:

263 0xb8ff313d33b0e841b6b83243f6e2935166de87c1.0xbb28353e4598c3b9199101a66e0989549b659a59a54d2c27fbb183f1932c8e6d( 0xbb28353e4598c3b9199101a66e0989549b659a59a54d2c27fbb183f1932c8e6d, 0x00000000000000000000000062715b1312185c3b9e117791a1d195cacd341f39, 00000000000000000000000000000000000000000000000031e6710719f87115 )

Account State Difference:

  Address   Before After State Difference Code
(Spark Pool)
8.385012515102642135 Eth8.389786205102642135 Eth0.00477369
0x62715B13...aCd341F39
12.145958641294560622 Eth
Nonce: 175
15.736870579110902403 Eth
Nonce: 176
3.590911937816341781
0xB8Ff313d...166de87C1 949.93938228507415348 Eth946.343696657257811699 Eth3.595685627816341781

Execution Trace

0xb8ff313d33b0e841b6b83243f6e2935166de87c1.CALL( )
  • WorkLockPoolingContract.DELEGATECALL( )
    • ETH 3.595685627816341781 0x62715b1312185c3b9e117791a1d195cacd341f39.CALL( )
      // 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 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);
          }
      }
      // 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.2|
      */
      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 NodeBrokenState(address indexed node, uint16 period);
          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 Set default `feeDelta` value for specified period
          * @dev This method increases gas cost for node in trade of decreasing cost for policy sponsor
          * @param _node Node address
          * @param _period Period to set
          */
          function setDefaultFeeDelta(address _node, uint16 _period) external onlyEscrowContract {
              NodeInfo storage node = nodes[_node];
              if (node.feeDelta[_period] == 0) {
                  node.feeDelta[_period] = DEFAULT_FEE_DELTA;
              }
          }
          /**
          * @notice Update node fee
          * @param _node Node address
          * @param _period Processed period
          */
          function updateFee(address _node, uint16 _period) external onlyEscrowContract {
              NodeInfo storage node = nodes[_node];
              if (node.previousFeePeriod == 0 || _period <= node.previousFeePeriod) {
                  return;
              }
              for (uint16 i = node.previousFeePeriod + 1; i <= _period; i++) {
                  int256 delta = node.feeDelta[i];
                  if (delta == DEFAULT_FEE_DELTA) {
                      // gas refund
                      node.feeDelta[i] = 0;
                      continue;
                  }
                  // broken state
                  if (delta < 0 && uint256(-delta) > node.feeRate) {
                      node.feeDelta[i] += int256(node.feeRate);
                      node.feeRate = 0;
                      emit NodeBrokenState(_node, _period);
                  // good state
                  } else {
                      node.feeRate = node.feeRate.addSigned(delta);
                      // gas refund
                      node.feeDelta[i] = 0;
                  }
              }
              node.previousFeePeriod = _period;
              node.fee += uint128(node.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();
          }
      }