# Contract Diff Checker

Contract Name:
WorkLock

Contract Source Code:

```// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.7.0;

import "./SafeMath.sol";

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

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

```// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

/**
* @dev Collection of functions related to the address type
*/
/**
* @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
*
*  - 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
// for accounts without code, i.e. `keccak256('')`
bytes32 codehash;
// 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.
*
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
*
* _Available since v2.4.0._
*/
function sendValue(address payable recipient, uint256 amount) internal {

// 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;

/**
* @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: MIT

pragma solidity ^0.7.0;

import "./IERC20.sol";
import "./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;

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.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
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.
*/
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.
*/

_balances[from] = _balances[from].sub(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 {

}

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

_totalSupply = _totalSupply.sub(value);
_balances[account] = _balances[account].sub(value);
}

/**
* @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.
*/

_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;

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 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 totalSupply() external view returns (uint256);

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

}
```

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

pragma solidity ^0.7.0;

// Minimum interface to interact with Aragon's Aggregator
interface IERC900History {
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 "./Math.sol";
import "./SafeERC20.sol";

/**
* @notice Contract for calculation of issued tokens
* @dev |v3.3.1|
*/
abstract contract Issuer is Upgradeable {
using SafeERC20 for NuCypherToken;

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;
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 =
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);
}

/**
* @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 {
unMint(_value);
emit Donated(msg.sender, _value);
}

/**
* @notice Returns the number of tokens that can be minted
*/
function getReservedReward() public view returns (uint256) {
}

/// @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: 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 "./ERC20.sol";
import "./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.
*/
external returns (bool success)
{
approve(_spender, _value);
return true;
}

}

/**
* @dev Interface to use the receiveApproval method
*/
interface TokenRecipient {

/**
* @param _from Sender of approval
* @param _value  The amount of tokens to be spent
* @param _tokenContract Address of the token contract
*/

}
```

```// 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 {

/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor () {
_owner = msg.sender;
}

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

/**
* @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 onlyOwner {
_transferOwnership(newOwner);
}

/**
* @dev Transfers control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
```

```// SPDX-License-Identifier: MIT

pragma solidity ^0.7.0;

import "./IERC20.sol";
import "./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));
}

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 {
require(token.approve(spender, newAllowance));
}

function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
require(token.approve(spender, newAllowance));
}
}
```

```// 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: 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 {
}

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: AGPL-3.0-or-later

pragma solidity ^0.7.0;

import "./IERC900History.sol";
import "./Issuer.sol";
import "./Bits.sol";
import "./Snapshot.sol";
import "./SafeMath.sol";
import "./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;
}

/**
*/
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.3.1|
*/
contract StakingEscrow is Issuer, IERC900History {

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(
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 Minted(address indexed staker, uint16 indexed period, uint256 value);
event ReStakeSet(address indexed staker, bool reStake);
event ReStakeLocked(address indexed staker, uint16 lockUntilPeriod);
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
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;

}

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;

mapping (uint16 => uint256) public lockedPerPeriod;
uint128[] public balanceHistory;

PolicyManagerInterface public policyManager;
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
// This escrow must be the escrow for the new policy manager
policyManager = _policyManager;
}

/**
*/
// Adjudicator can be set only once
// This escrow must be the escrow for the new adjudicator
}

/**
*/
function setWorkLock(WorkLockInterface _workLock) external onlyOwner {
// WorkLock can be set only once
// This escrow must be the escrow for the new worklock
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
*/
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
*/
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
*/
external view returns (uint256 lockedValue)
{
StakerInfo storage info = stakerInfo[_staker];
uint16 currentPeriod = getCurrentPeriod();
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();

for (uint256 i = _startIndex; i < endIndex; 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
*/
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) {
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
// Remove the old relation "worker->staker"
}

// Specified worker is already in use
// 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 Enable `reStake` and lock this parameter even if parameter is locked
* @param _info Staker structure
* @param _lockReStakeUntilPeriod Can't change `reStake` value until this period
*/
function forceLockReStake(
StakerInfo storage _info,
uint16 _lockReStakeUntilPeriod
)
internal
{
// reset bit when `reStake` is already disabled
if (_info.flags.bitSet(RE_STAKE_DISABLED_INDEX) == true) {
_info.flags = _info.flags.toggleBit(RE_STAKE_DISABLED_INDEX);
emit ReStakeSet(_staker, true);
}
// lock `reStake` parameter if it's not locked or locked for too short duration
if (_lockReStakeUntilPeriod > _info.lockReStakeUntilPeriod) {
_info.lockReStakeUntilPeriod = _lockReStakeUntilPeriod;
emit ReStakeLocked(_staker, _lockReStakeUntilPeriod);
}
}

/**
* @notice Deposit tokens and lock `reStake` parameter from WorkLock contract
* @param _value Amount of tokens to deposit
* @param _periods Amount of periods during which tokens will be locked
* and number of period after which `reStake` can be changed
*/
function depositFromWorkLock(
uint256 _value,
uint16 _periods
)
external
{
deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods);
StakerInfo storage info = stakerInfo[_staker];
forceLockReStake(_staker, info, lockReStakeUntilPeriod);
}

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

uint16 currentPeriod = getCurrentPeriod();
uint16 nextPeriod = currentPeriod + 1;
emit WindDownSet(msg.sender, _windDown);

// duration adjustment if next period is committed
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){
} else {
}
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.
*/
if(!_info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX)){
uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());
}
}

/**
* @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
* @param _lockReStakeUntilPeriod Can't change `reStake` value until this period. Zero value will disable locking
*/
function batchDeposit(
uint256[] calldata _numberOfSubStakes,
uint256[] calldata _values,
uint16[] calldata _periods,
uint16 _lockReStakeUntilPeriod
)
// `onlyOwner` modifier is for prevent malicious using of `forceLockReStake`
// remove `onlyOwner` if `forceLockReStake` will be removed
external onlyOwner
{
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++) {
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
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.subStakes.push(SubStakeInfo(nextPeriod, 0, periods, uint128(value)));
emit Deposited(staker, value, periods);
emit Locked(staker, value, nextPeriod, periods);
}
require(info.value <= maxAllowableLockedTokens);

if (_lockReStakeUntilPeriod >= nextPeriod) {
forceLockReStake(staker, info, _lockReStakeUntilPeriod);
}
}
require(j == subStakesLength);
uint256 lastGlobalBalance = uint256(balanceHistory.lastValue());
}

/**
* (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
*/
uint256 _value,
)
external
{

// 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
//

assembly {
}
}

/**
* @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);
}
info.value += _value;
lock(_staker, _index, _value, _periods);

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);
require(requestedLockedTokens <= info.value && requestedLockedTokens <= maxAllowableLockedTokens);

// next period is committed
if (info.nextCommittedPeriod == nextPeriod) {
lockedPerPeriod[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,
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,
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);
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);

// 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;

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 &&
{
}
}

/**
* @notice Make a commitment to the next period and mint for the previous period
*/
function commitToNextPeriod() external isInitialized {
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);
}

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

emit Minted(_staker, previousPeriod, reward);
}

/**
* @notice Calculate reward for one period
* @param _info Staker structure
* @param _mintingPeriod Period for minting calculation
* @param _currentPeriod Current period
* @param _startPeriod Pre-calculated start period
*/
function mint(
StakerInfo storage _info,
uint16 _mintingPeriod,
uint16 _currentPeriod,
uint16 _startPeriod,
bool _reStake
)
internal returns (uint256 reward)
{
reward = 0;
for (uint256 i = 0; i < _info.subStakes.length; i++) {
SubStakeInfo storage subStake =  _info.subStakes[i];
uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod);
if (subStake.firstPeriod <= _mintingPeriod && lastPeriod >= _mintingPeriod) {
uint256 subStakeReward = mint(
_currentPeriod,
subStake.lockedValue,
lockedPerPeriod[_mintingPeriod],
lastPeriod.sub16(_mintingPeriod));
reward += subStakeReward;
if (_reStake) {
subStake.lockedValue += uint128(subStakeReward);
}
}
}
policyManager.updateFee(_staker, _mintingPeriod);
return reward;
}

//-------------------------Slashing-------------------------
/**
* @notice Slash the staker's stake and reward the investigator
* @param _penalty Penalty
* @param _investigator Investigator
* @param _reward Reward for the investigator
*/
function slashStaker(
uint256 _penalty,
uint256 _reward
)
public isInitialized
{
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);
}

}

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

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

/**
* @dev Get StakerInfo structure by delegatecall
*/
internal returns (StakerInfo memory result)
{
bytes32 memoryAddress = delegateGetData(_target, this.stakerInfo.selector, 1, _staker, 0);
assembly {
}
}

/**
* @dev Get SubStakeInfo structure by delegatecall
*/
function delegateGetSubStakeInfo(address _target, bytes32 _staker, uint256 _index)
internal returns (SubStakeInfo memory result)
{
_target, this.getSubStakeInfo.selector, 2, _staker, bytes32(_index));
assembly {
}
}

/**
* @dev Get Downtime structure by delegatecall
*/
function delegateGetPastDowntime(address _target, bytes32 _staker, uint256 _index)
internal returns (Downtime memory result)
{
_target, this.getPastDowntime.selector, 2, _staker, bytes32(_index));
assembly {
}
}

/// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState`
function verifyState(address _testTarget) public override virtual {
super.verifyState(_testTarget);
require(delegateGet(_testTarget, this.lockedPerPeriod.selector,
bytes32(bytes2(RESERVED_PERIOD))) == lockedPerPeriod[RESERVED_PERIOD]);

require(delegateGet(_testTarget, this.getStakersLength.selector) == stakers.length);
if (stakers.length == 0) {
return;
}
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)) ==
require(delegateGet(_testTarget, this.totalStakedAt.selector, bytes32(block.number)) ==
totalStakedAt(block.number));

stakerFromWorker[info.worker]);
}
}

/// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade`
// Create fake period
lockedPerPeriod[RESERVED_PERIOD] = 111;

// Create fake worker
}
}
```

```// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.7.0;

import "./Ownable.sol";

/**
* @notice Base contract for upgradeable contract
* @dev Inherited contract should implement verifyState(address) method by checking storage variables
* if it is using constructor parameters by coping this parameters to the dispatcher storage
*/
abstract contract Upgradeable is Ownable {

/**
* @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
*/

/**
* @dev Previous contract address (if available). Used for rollback
*/

/**
* @dev Upgrade status. Explicit `uint8` type is used instead of `bool` to save gas by excluding 0 value
*/

/**
* @dev Guarantees that next slot will be separated from the previous
*/
uint256 stubSlot;

/**
* @dev Constants for `isUpgrade` field
*/

/**
* @dev Checks that function executed while upgrading
*/
{
_;
}

/**
* @dev Method for verifying storage state.
* Should check that new target contract returns right storage value
*/
emit StateVerified(_testTarget, msg.sender);
}

/**
* @dev Copy values from the new target to the current storage
* @param _target New target contract address
*/
}

/**
* @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
*/
function delegateGetData(
bytes4 _selector,
uint8 _numberOfArguments,
bytes32 _argument1,
bytes32 _argument2
)
{
assembly {
if gt(_numberOfArguments, 0) {
}
if gt(_numberOfArguments, 1) {
}
case 0 {
}
default {
}
}
}

/**
* @dev Call "getter" without parameters.
* Result should not exceed 32 bytes
*/
internal returns (uint256 result)
{
bytes32 memoryAddress = delegateGetData(_target, _selector, 0, 0, 0);
assembly {
}
}

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

/**
* @dev Call "getter" with two parameters.
* Result should not exceed 32 bytes
*/
function delegateGet(
bytes4 _selector,
bytes32 _argument1,
bytes32 _argument2
)
internal returns (uint256 result)
{
bytes32 memoryAddress = delegateGetData(_target, _selector, 2, _argument1, _argument2);
assembly {
}
}
}
```

```// SPDX-License-Identifier: AGPL-3.0-or-later

pragma solidity ^0.7.0;

import "./SafeMath.sol";
import "./SafeERC20.sol";
import "./Ownable.sol";
import "./NuCypherToken.sol";
import "./StakingEscrow.sol";

/**
* @notice The WorkLock distribution contract
*/
contract WorkLock is Ownable {
using SafeERC20 for NuCypherToken;
using SafeMath for uint256;

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 CompensationWithdrawn(address indexed sender, uint256 value);

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;

// 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");
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) {
}

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);
} else {
}

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");
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) {
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;
}

uint256 minBid = workInfo[previousBidder].depositedETH;
uint256 maxBid = minBid;

// get minimum and maximum bids
for (uint256 i = 1; i < length; 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++) {
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++) {
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++) {
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) {
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];
claimedTokens = ethToTokens(info.depositedETH);
require(claimedTokens > 0, "Nothing to claim");

info.claimed = true;
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);