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