Balance:
485.203655351313406067 Ether
EtherValue:
$825,840.88 (@ $1,702.05/ETH)
Token:
More Info
[ Download CSV Export ]
OVERVIEW
WorkLock is a novel, permissionless decentralized network node setup mechanism developed at NuCypher.
Latest 25 internal transaction
[ Download CSV Export ]
Contract Name:
WorkLock
Compiler Version
v0.7.0+commit.9e61f92b
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion, GNU AGPLv3 license, Audited
Contract Source Code (Solidity Multiple files format)Audit Report
// SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.7.0; import "./SafeMath.sol"; import "./SafeERC20.sol"; import "./Address.sol"; import "./Ownable.sol"; import "./NuCypherToken.sol"; import "./StakingEscrow.sol"; import "./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; import "./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; /** * @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; /** * @dev Taken from https://github.com/ethereum/solidity-examples/blob/master/src/bits/Bits.sol */ library Bits { uint256 internal constant ONE = uint256(1); /** * @notice Sets the bit at the given 'index' in 'self' to: * '1' - if the bit is '0' * '0' - if the bit is '1' * @return The modified value */ function toggleBit(uint256 self, uint8 index) internal pure returns (uint256) { return self ^ ONE << index; } /** * @notice Get the value of the bit at the given 'index' in 'self'. */ function bit(uint256 self, uint8 index) internal pure returns (uint8) { return uint8(self >> index & 1); } /** * @notice Check if the bit at the given 'index' in 'self' is set. * @return 'true' - if the value of the bit is '1', * 'false' - if the value of the bit is '0' */ function bitSet(uint256 self, uint8 index) internal pure returns (bool) { return self >> index & 1 == 1; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; import "./IERC20.sol"; import "./SafeMath.sol"; /** * @title Standard ERC20 token * * @dev Implementation of the basic standard token. * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md * Originally based on code by FirstBlood: * https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol * * This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for * all accounts just by listening to said events. Note that this isn't required by the specification, and other * compliant implementations may not do it. */ contract ERC20 is IERC20 { using SafeMath for uint256; mapping (address => uint256) private _balances; 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; import "./IERC20.sol"; /** * @title ERC20Detailed token * @dev The decimals are only for visualization purposes. * All the operations are done using the smallest and indivisible token unit, * just as on Ethereum all the operations are done in wei. */ abstract contract ERC20Detailed is IERC20 { string private _name; string private _symbol; uint8 private _decimals; constructor (string memory name, string memory symbol, uint8 decimals) { _name = name; _symbol = symbol; _decimals = decimals; } /** * @return the name of the token. */ function name() public view returns (string memory) { return _name; } /** * @return the symbol of the token. */ function symbol() public view returns (string memory) { return _symbol; } /** * @return the number of decimals of the token. */ function decimals() public view returns (uint8) { return _decimals; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.7.0; /** * @title ERC20 interface * @dev see https://github.com/ethereum/EIPs/issues/20 */ interface IERC20 { function transfer(address to, uint256 value) external returns (bool); function approve(address spender, uint256 value) external returns (bool); function 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: 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 "./Math.sol"; import "./Upgradeable.sol"; import "./AdditionalMath.sol"; import "./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: MIT pragma solidity ^0.7.0; /** * @title Math * @dev Assorted math operations */ library Math { /** * @dev Returns the largest of two numbers. */ function max(uint256 a, uint256 b) internal pure returns (uint256) { return a >= b ? a : b; } /** * @dev Returns the smallest of two numbers. */ function min(uint256 a, uint256 b) internal pure returns (uint256) { return a < b ? a : b; } /** * @dev Calculates the average of two numbers. Since these are integers, * averages of an even and odd number cannot be represented, and will be * rounded down. */ function average(uint256 a, uint256 b) internal pure returns (uint256) { // (a + b) / 2 can overflow, so we distribute return (a / 2) + (b / 2) + ((a % 2 + b % 2) / 2); } }
// SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.7.0; import "./ERC20.sol"; import "./ERC20Detailed.sol"; /** * @title NuCypher token * @notice ERC20 token * @dev Optional approveAndCall() functionality to notify a contract if an approve() has occurred. */ contract NuCypherToken is ERC20, ERC20Detailed('NuCypher', 'NU', 18) { /** * @notice Set amount of tokens * @param _totalSupplyOfTokens Total number of tokens */ constructor (uint256 _totalSupplyOfTokens) { _mint(msg.sender, _totalSupplyOfTokens); } /** * @notice Approves and then calls the receiving contract * * @dev call the receiveApproval function on the contract you want to be notified. * 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; /** * @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 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: MIT pragma solidity ^0.7.0; import "./IERC20.sol"; import "./SafeMath.sol"; /** * @title SafeERC20 * @dev Wrappers around ERC20 operations that throw on failure. * To use this library you can add a `using SafeERC20 for ERC20;` statement to your contract, * which allows you to call the safe operations as `token.safeTransfer(...)`, etc. */ library SafeERC20 { using SafeMath for uint256; function safeTransfer(IERC20 token, address to, uint256 value) internal { require(token.transfer(to, value)); } 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: MIT pragma solidity ^0.7.0; /** * @title SafeMath * @dev Unsigned math operations with safety checks that revert on error */ library SafeMath { /** * @dev Multiplies two unsigned integers, reverts on overflow. */ function mul(uint256 a, uint256 b) internal pure returns (uint256) { // Gas optimization: this is cheaper than requiring 'a' not being zero, but the // benefit is lost if 'b' is also tested. // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b); return c; } /** * @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero. */ function div(uint256 a, uint256 b) internal pure returns (uint256) { // Solidity only automatically asserts when dividing by 0 require(b > 0); uint256 c = a / b; // assert(a == b * c + a % b); // There is no case in which this doesn't hold return c; } /** * @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend). */ function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a); uint256 c = a - b; return c; } /** * @dev Adds two unsigned integers, reverts on overflow. */ function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a); return c; } /** * @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), * reverts when dividing by zero. */ function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0); return a % b; } }
// SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.7.0; /** * @title Snapshot * @notice Manages snapshots of size 128 bits (32 bits for timestamp, 96 bits for value) * 96 bits is enough for storing NU token values, and 32 bits should be OK for block numbers * @dev Since each storage slot can hold two snapshots, new slots are allocated every other TX. Thus, gas cost of adding snapshots is 51400 and 36400 gas, alternately. * Based on Aragon's Checkpointing (https://https://github.com/aragonone/voting-connectors/blob/master/shared/contract-utils/contracts/Checkpointing.sol) * On average, adding snapshots spends ~6500 less gas than the 256-bit checkpoints of Aragon's Checkpointing */ library Snapshot { function encodeSnapshot(uint32 _time, uint96 _value) internal pure returns(uint128) { return uint128(uint256(_time) << 96 | uint256(_value)); } function decodeSnapshot(uint128 _snapshot) internal pure returns(uint32 time, uint96 value){ time = uint32(bytes4(bytes16(_snapshot))); value = uint96(_snapshot); } function addSnapshot(uint128[] storage _self, uint256 _value) internal { 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: AGPL-3.0-or-later pragma solidity ^0.7.0; import "./IERC900History.sol"; import "./Issuer.sol"; import "./Bits.sol"; import "./Snapshot.sol"; import "./SafeMath.sol"; import "./SafeERC20.sol"; /** * @notice PolicyManager interface */ interface PolicyManagerInterface { function register(address _node, uint16 _period) external; function updateFee(address _node, uint16 _period) external; function escrow() external view returns (address); function setDefaultFeeDelta(address _node, uint16 _period) external; } /** * @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.3.1| */ 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 Enable `reStake` and lock this parameter even if parameter is locked * @param _staker Staker address * @param _info Staker structure * @param _lockReStakeUntilPeriod Can't change `reStake` value until this period */ function forceLockReStake( address _staker, StakerInfo storage _info, uint16 _lockReStakeUntilPeriod ) internal { // reset bit when `reStake` is already disabled if (_info.flags.bitSet(RE_STAKE_DISABLED_INDEX) == true) { _info.flags = _info.flags.toggleBit(RE_STAKE_DISABLED_INDEX); emit ReStakeSet(_staker, true); } // lock `reStake` parameter if it's not locked or locked for too short duration if (_lockReStakeUntilPeriod > _info.lockReStakeUntilPeriod) { _info.lockReStakeUntilPeriod = _lockReStakeUntilPeriod; emit ReStakeLocked(_staker, _lockReStakeUntilPeriod); } } /** * @notice Deposit tokens and lock `reStake` parameter from WorkLock contract * @param _staker Staker address * @param _value Amount of tokens to deposit * @param _periods Amount of periods during which tokens will be locked * and number of period after which `reStake` can be changed */ function depositFromWorkLock( address _staker, uint256 _value, uint16 _periods ) external { require(msg.sender == address(workLock)); deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods); StakerInfo storage info = stakerInfo[_staker]; uint16 lockReStakeUntilPeriod = getCurrentPeriod().add16(_periods).add16(1); forceLockReStake(_staker, info, lockReStakeUntilPeriod); } /** * @notice Set `windDown` parameter. * If true then stake's duration will be decreasing in each period with `commitToNextPeriod()` * @param _windDown Value for parameter */ function setWindDown(bool _windDown) external onlyStaker { StakerInfo storage info = stakerInfo[msg.sender]; if (info.flags.bitSet(WIND_DOWN_INDEX) == _windDown) { return; } info.flags = info.flags.toggleBit(WIND_DOWN_INDEX); uint16 currentPeriod = getCurrentPeriod(); uint16 nextPeriod = currentPeriod + 1; emit WindDownSet(msg.sender, _windDown); // duration adjustment if next period is committed if (info.nextCommittedPeriod != nextPeriod) { return; } // adjust sub-stakes duration for the new value of winding down parameter for (uint256 index = 0; index < info.subStakes.length; index++) { SubStakeInfo storage subStake = info.subStakes[index]; // sub-stake does not have fixed last period when winding down is disabled if (!_windDown && subStake.lastPeriod == nextPeriod) { subStake.lastPeriod = 0; subStake.periods = 1; continue; } // this sub-stake is no longer affected by winding down parameter if (subStake.lastPeriod != 0 || subStake.periods == 0) { continue; } subStake.periods = _windDown ? subStake.periods - 1 : subStake.periods + 1; if (subStake.periods == 0) { subStake.lastPeriod = nextPeriod; } } } /** * @notice Activate/deactivate taking snapshots of balances * @param _enableSnapshots True to activate snapshots, False to deactivate */ function setSnapshots(bool _enableSnapshots) external { StakerInfo storage info = stakerInfo[msg.sender]; if (info.flags.bitSet(SNAPSHOTS_DISABLED_INDEX) == !_enableSnapshots) { return; } uint256 lastGlobalBalance = uint256(balanceHistory.lastValue()); if(_enableSnapshots){ 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 * @param _lockReStakeUntilPeriod Can't change `reStake` value until this period. Zero value will disable locking */ function batchDeposit( address[] calldata _stakers, uint256[] calldata _numberOfSubStakes, uint256[] calldata _values, uint16[] calldata _periods, uint16 _lockReStakeUntilPeriod ) // `onlyOwner` modifier is for prevent malicious using of `forceLockReStake` // remove `onlyOwner` if `forceLockReStake` will be removed external onlyOwner { uint256 subStakesLength = _values.length; require(_stakers.length != 0 && _stakers.length == _numberOfSubStakes.length && subStakesLength >= _stakers.length && _periods.length == subStakesLength); uint16 previousPeriod = getCurrentPeriod() - 1; uint16 nextPeriod = previousPeriod + 2; uint256 sumValue = 0; uint256 j = 0; for (uint256 i = 0; i < _stakers.length; i++) { 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); if (_lockReStakeUntilPeriod >= nextPeriod) { forceLockReStake(staker, info, _lockReStakeUntilPeriod); } } require(j == subStakesLength); uint256 lastGlobalBalance = uint256(balanceHistory.lastValue()); balanceHistory.addSnapshot(lastGlobalBalance + sumValue); token.safeTransferFrom(msg.sender, address(this), sumValue); } /** * @notice Implementation of the receiveApproval(address,uint256,address,bytes) method * (see NuCypherToken contract). Deposit all tokens that were approved to transfer * @param _from Staker * @param _value Amount of tokens to deposit * @param _tokenContract Token contract address * @notice (param _extraData) Amount of periods during which tokens will be locked */ function receiveApproval( address _from, uint256 _value, address _tokenContract, bytes calldata /* _extraData */ ) external { require(_tokenContract == address(token) && msg.sender == address(token)); // Copy first 32 bytes from _extraData, according to calldata memory layout: // // 0x00: method signature 4 bytes // 0x04: _from 32 bytes after encoding // 0x24: _value 32 bytes after encoding // 0x44: _tokenContract 32 bytes after encoding // 0x64: _extraData pointer 32 bytes. Value must be 0x80 (offset of _extraData wrt to 1st parameter) // 0x84: _extraData length 32 bytes // 0xA4: _extraData data Length determined by previous variable // // See https://solidity.readthedocs.io/en/latest/abi-spec.html#examples uint256 payloadSize; uint256 payload; assembly { payloadSize := calldataload(0x84) payload := calldataload(0xA4) } payload = payload >> 8*(32 - payloadSize); deposit(_from, _from, MAX_SUB_STAKES, _value, uint16(payload)); } /** * @notice Deposit tokens and create new sub-stake. Use this method to become a staker * @param _staker Staker * @param _value Amount of tokens to deposit * @param _periods Amount of periods during which tokens will be locked */ function deposit(address _staker, uint256 _value, uint16 _periods) external { deposit(_staker, msg.sender, MAX_SUB_STAKES, _value, _periods); } /** * @notice Deposit tokens and increase lock amount of an existing sub-stake * @dev This is preferable way to stake tokens because will be fewer active sub-stakes in the result * @param _index Index of the sub stake * @param _value Amount of tokens which will be locked */ function depositAndIncrease(uint256 _index, uint256 _value) external onlyStaker { require(_index < MAX_SUB_STAKES); deposit(msg.sender, msg.sender, _index, _value, 0); } /** * @notice Deposit tokens * @dev Specify either index and zero periods (for an existing sub-stake) * or index >= MAX_SUB_STAKES and real value for periods (for a new sub-stake), not both * @param _staker Staker * @param _payer Owner of tokens * @param _index Index of the sub stake * @param _value Amount of tokens to deposit * @param _periods Amount of periods during which tokens will be locked */ function deposit(address _staker, address _payer, uint256 _index, uint256 _value, uint16 _periods) internal { require(_value != 0); StakerInfo storage info = stakerInfo[_staker]; // A staker can't be a worker for another staker require(stakerFromWorker[_staker] == address(0) || stakerFromWorker[_staker] == info.worker); // initial stake of the staker if (info.subStakes.length == 0) { stakers.push(_staker); policyManager.register(_staker, getCurrentPeriod() - 1); } token.safeTransferFrom(_payer, address(this), _value); info.value += _value; lock(_staker, _index, _value, _periods); addSnapshot(info, int256(_value)); if (_index >= MAX_SUB_STAKES) { emit Deposited(_staker, _value, _periods); } else { uint16 lastPeriod = getLastPeriodOfSubStake(_staker, _index); emit Deposited(_staker, _value, lastPeriod - getCurrentPeriod()); } } /** * @notice Lock some tokens as a new sub-stake * @param _value Amount of tokens which will be locked * @param _periods Amount of periods during which tokens will be locked */ function lockAndCreate(uint256 _value, uint16 _periods) external onlyStaker { lock(msg.sender, MAX_SUB_STAKES, _value, _periods); } /** * @notice Increase lock amount of an existing sub-stake * @param _index Index of the sub-stake * @param _value Amount of tokens which will be locked */ function lockAndIncrease(uint256 _index, uint256 _value) external onlyStaker { require(_index < MAX_SUB_STAKES); lock(msg.sender, _index, _value, 0); } /** * @notice Lock some tokens as a stake * @dev Specify either index and zero periods (for an existing sub-stake) * or index >= MAX_SUB_STAKES and real value for periods (for a new sub-stake), not both * @param _staker Staker * @param _index Index of the sub stake * @param _value Amount of tokens which will be locked * @param _periods Amount of periods during which tokens will be locked */ function lock(address _staker, uint256 _index, uint256 _value, uint16 _periods) internal { if (_index < MAX_SUB_STAKES) { require(_value > 0); } else { require(_value >= minAllowableLockedTokens && _periods >= minLockedPeriods); } uint16 currentPeriod = getCurrentPeriod(); uint16 nextPeriod = currentPeriod + 1; StakerInfo storage info = stakerInfo[_staker]; uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod); uint256 requestedLockedTokens = _value.add(lockedTokens); require(requestedLockedTokens <= info.value && requestedLockedTokens <= maxAllowableLockedTokens); // next period is committed if (info.nextCommittedPeriod == nextPeriod) { lockedPerPeriod[nextPeriod] += _value; emit CommitmentMade(_staker, nextPeriod, _value); } // if index was provided then increase existing sub-stake if (_index < MAX_SUB_STAKES) { lockAndIncrease(info, currentPeriod, nextPeriod, _staker, _index, _value); // otherwise create new } else { lockAndCreate(info, nextPeriod, _staker, _value, _periods); } } /** * @notice Lock some tokens as a new sub-stake * @param _info Staker structure * @param _nextPeriod Next period * @param _staker Staker * @param _value Amount of tokens which will be locked * @param _periods Amount of periods during which tokens will be locked */ function lockAndCreate( StakerInfo storage _info, uint16 _nextPeriod, address _staker, uint256 _value, uint16 _periods ) internal { uint16 duration = _periods; // if winding down is enabled and next period is committed // then sub-stakes duration were decreased if (_info.nextCommittedPeriod == _nextPeriod && _info.flags.bitSet(WIND_DOWN_INDEX)) { duration -= 1; } saveSubStake(_info, _nextPeriod, 0, duration, _value); emit Locked(_staker, _value, _nextPeriod, _periods); } /** * @notice Increase lock amount of an existing sub-stake * @dev Probably will be created a new sub-stake but it will be active only one period * @param _info Staker structure * @param _currentPeriod Current period * @param _nextPeriod Next period * @param _staker Staker * @param _index Index of the sub-stake * @param _value Amount of tokens which will be locked */ function lockAndIncrease( StakerInfo storage _info, uint16 _currentPeriod, uint16 _nextPeriod, address _staker, uint256 _index, uint256 _value ) internal { SubStakeInfo storage subStake = _info.subStakes[_index]; (, uint16 lastPeriod) = checkLastPeriodOfSubStake(_info, subStake, _currentPeriod); // create temporary sub-stake for current or previous committed periods // to leave locked amount in this period unchanged if (_info.currentCommittedPeriod != 0 && _info.currentCommittedPeriod <= _currentPeriod || _info.nextCommittedPeriod != 0 && _info.nextCommittedPeriod <= _currentPeriod) { saveSubStake(_info, subStake.firstPeriod, _currentPeriod, 0, subStake.lockedValue); } subStake.lockedValue += uint128(_value); // all new locks should start from the next period subStake.firstPeriod = _nextPeriod; emit Locked(_staker, _value, _nextPeriod, lastPeriod - _currentPeriod); } /** * @notice Checks that last period of sub-stake is greater than the current period * @param _info Staker structure * @param _subStake Sub-stake structure * @param _currentPeriod Current period * @return startPeriod Start period. Use in the calculation of the last period of the sub stake * @return lastPeriod Last period of the sub stake */ function checkLastPeriodOfSubStake( StakerInfo storage _info, SubStakeInfo storage _subStake, uint16 _currentPeriod ) internal view returns (uint16 startPeriod, uint16 lastPeriod) { startPeriod = getStartPeriod(_info, _currentPeriod); lastPeriod = getLastPeriodOfSubStake(_subStake, startPeriod); // The sub stake must be active at least in the next period require(lastPeriod > _currentPeriod); } /** * @notice Save sub stake. First tries to override inactive sub stake * @dev Inactive sub stake means that last period of sub stake has been surpassed and already rewarded * @param _info Staker structure * @param _firstPeriod First period of the sub stake * @param _lastPeriod Last period of the sub stake * @param _periods Duration of the sub stake in periods * @param _lockedValue Amount of locked tokens */ function saveSubStake( StakerInfo storage _info, uint16 _firstPeriod, uint16 _lastPeriod, uint16 _periods, uint256 _lockedValue ) internal { for (uint256 i = 0; i < _info.subStakes.length; i++) { SubStakeInfo storage subStake = _info.subStakes[i]; if (subStake.lastPeriod != 0 && (_info.currentCommittedPeriod == 0 || subStake.lastPeriod < _info.currentCommittedPeriod) && (_info.nextCommittedPeriod == 0 || subStake.lastPeriod < _info.nextCommittedPeriod)) { subStake.firstPeriod = _firstPeriod; subStake.lastPeriod = _lastPeriod; subStake.periods = _periods; subStake.lockedValue = uint128(_lockedValue); return; } } require(_info.subStakes.length < MAX_SUB_STAKES); _info.subStakes.push(SubStakeInfo(_firstPeriod, _lastPeriod, _periods, uint128(_lockedValue))); } /** * @notice Divide sub stake into two parts * @param _index Index of the sub stake * @param _newValue New sub stake value * @param _periods Amount of periods for extending sub stake */ function divideStake(uint256 _index, uint256 _newValue, uint16 _periods) external onlyStaker { StakerInfo storage info = stakerInfo[msg.sender]; require(_newValue >= minAllowableLockedTokens && _periods > 0); SubStakeInfo storage subStake = info.subStakes[_index]; uint16 currentPeriod = getCurrentPeriod(); (, uint16 lastPeriod) = checkLastPeriodOfSubStake(info, subStake, currentPeriod); uint256 oldValue = subStake.lockedValue; subStake.lockedValue = uint128(oldValue.sub(_newValue)); require(subStake.lockedValue >= minAllowableLockedTokens); uint16 requestedPeriods = subStake.periods.add16(_periods); saveSubStake(info, subStake.firstPeriod, 0, requestedPeriods, _newValue); emit Divided(msg.sender, oldValue, lastPeriod, _newValue, _periods); emit Locked(msg.sender, _newValue, subStake.firstPeriod, requestedPeriods); } /** * @notice Prolong active sub stake * @param _index Index of the sub stake * @param _periods Amount of periods for extending sub stake */ function prolongStake(uint256 _index, uint16 _periods) external onlyStaker { StakerInfo storage info = stakerInfo[msg.sender]; // Incorrect parameters require(_periods > 0); SubStakeInfo storage subStake = info.subStakes[_index]; uint16 currentPeriod = getCurrentPeriod(); (uint16 startPeriod, uint16 lastPeriod) = checkLastPeriodOfSubStake(info, subStake, currentPeriod); subStake.periods = subStake.periods.add16(_periods); // if the sub stake ends in the next committed period then reset the `lastPeriod` field if (lastPeriod == startPeriod) { subStake.lastPeriod = 0; } // The extended sub stake must not be less than the minimum value require(uint32(lastPeriod - currentPeriod) + _periods >= minLockedPeriods); emit Locked(msg.sender, subStake.lockedValue, lastPeriod + 1, _periods); emit Prolonged(msg.sender, subStake.lockedValue, lastPeriod, _periods); } /** * @notice Merge two sub-stakes into one if their last periods are equal * @dev It's possible that both sub-stakes will be active after this transaction. * But only one of them will be active until next call `commitToNextPeriod` (in the next period) * @param _index1 Index of the first sub-stake * @param _index2 Index of the second sub-stake */ function mergeStake(uint256 _index1, uint256 _index2) external onlyStaker { require(_index1 != _index2); // must be different sub-stakes StakerInfo storage info = stakerInfo[msg.sender]; SubStakeInfo storage subStake1 = info.subStakes[_index1]; SubStakeInfo storage subStake2 = info.subStakes[_index2]; uint16 currentPeriod = getCurrentPeriod(); (, uint16 lastPeriod1) = checkLastPeriodOfSubStake(info, subStake1, currentPeriod); (, uint16 lastPeriod2) = checkLastPeriodOfSubStake(info, subStake2, currentPeriod); // both sub-stakes must have equal last period to be mergeable require(lastPeriod1 == lastPeriod2); emit Merged(msg.sender, subStake1.lockedValue, subStake2.lockedValue, lastPeriod1); if (subStake1.firstPeriod == subStake2.firstPeriod) { subStake1.lockedValue += subStake2.lockedValue; subStake2.lastPeriod = 1; subStake2.periods = 0; } else if (subStake1.firstPeriod > subStake2.firstPeriod) { subStake1.lockedValue += subStake2.lockedValue; subStake2.lastPeriod = subStake1.firstPeriod - 1; subStake2.periods = 0; } else { subStake2.lockedValue += subStake1.lockedValue; subStake1.lastPeriod = subStake2.firstPeriod - 1; subStake1.periods = 0; } } /** * @notice Withdraw available amount of tokens to staker * @param _value Amount of tokens to withdraw */ function withdraw(uint256 _value) external onlyStaker { uint16 currentPeriod = getCurrentPeriod(); uint16 nextPeriod = currentPeriod + 1; StakerInfo storage info = stakerInfo[msg.sender]; // the max locked tokens in most cases will be in the current period // but when the staker locks more then we should use the next period uint256 lockedTokens = Math.max(getLockedTokens(info, currentPeriod, nextPeriod), getLockedTokens(info, currentPeriod, currentPeriod)); require(_value <= info.value.sub(lockedTokens)); info.value -= _value; addSnapshot(info, - int256(_value)); token.safeTransfer(msg.sender, _value); emit Withdrawn(msg.sender, _value); // unbond worker if staker withdraws last portion of NU if (info.value == 0 && info.nextCommittedPeriod == 0 && info.worker != address(0)) { stakerFromWorker[info.worker] = address(0); info.worker = address(0); emit WorkerBonded(msg.sender, address(0), currentPeriod); } } /** * @notice Make a commitment to the next period and mint for the previous period */ function commitToNextPeriod() external isInitialized { address staker = stakerFromWorker[msg.sender]; StakerInfo storage info = stakerInfo[staker]; // Staker must have a stake to make a commitment require(info.value > 0); // Only worker with real address can make a commitment require(msg.sender == tx.origin); uint16 lastCommittedPeriod = getLastCommittedPeriod(staker); mint(staker); uint16 currentPeriod = getCurrentPeriod(); uint16 nextPeriod = currentPeriod + 1; // the period has already been committed if (info.nextCommittedPeriod == nextPeriod) { return; } uint256 lockedTokens = getLockedTokens(info, currentPeriod, nextPeriod); require(lockedTokens > 0); lockedPerPeriod[nextPeriod] += lockedTokens; info.currentCommittedPeriod = info.nextCommittedPeriod; info.nextCommittedPeriod = nextPeriod; decreaseSubStakesDuration(info, nextPeriod); // staker was inactive for several periods if (lastCommittedPeriod < currentPeriod) { info.pastDowntime.push(Downtime(lastCommittedPeriod + 1, currentPeriod)); } policyManager.setDefaultFeeDelta(staker, nextPeriod); emit CommitmentMade(staker, nextPeriod, lockedTokens); } /** * @notice Decrease sub-stakes duration if `windDown` is enabled */ function decreaseSubStakesDuration(StakerInfo storage _info, uint16 _nextPeriod) internal { if (!_info.flags.bitSet(WIND_DOWN_INDEX)) { return; } for (uint256 index = 0; index < _info.subStakes.length; index++) { SubStakeInfo storage subStake = _info.subStakes[index]; if (subStake.lastPeriod != 0 || subStake.periods == 0) { continue; } subStake.periods--; if (subStake.periods == 0) { subStake.lastPeriod = _nextPeriod; } } } /** * @notice Mint tokens for previous periods if staker locked their tokens and made a commitment */ function mint() external onlyStaker { // save last committed period to the storage if both periods will be empty after minting // because we won't be able to calculate last committed period // see getLastCommittedPeriod(address) StakerInfo storage info = stakerInfo[msg.sender]; uint16 previousPeriod = getCurrentPeriod() - 1; if (info.nextCommittedPeriod <= previousPeriod && info.nextCommittedPeriod != 0) { info.lastCommittedPeriod = info.nextCommittedPeriod; } mint(msg.sender); } /** * @notice Mint tokens for previous periods if staker locked their tokens and made a commitment * @param _staker Staker */ function mint(address _staker) internal { uint16 currentPeriod = getCurrentPeriod(); uint16 previousPeriod = currentPeriod - 1; StakerInfo storage info = stakerInfo[_staker]; if (info.nextCommittedPeriod == 0 || info.currentCommittedPeriod == 0 && info.nextCommittedPeriod > previousPeriod || info.currentCommittedPeriod > previousPeriod) { return; } uint16 startPeriod = getStartPeriod(info, currentPeriod); uint256 reward = 0; bool reStake = !info.flags.bitSet(RE_STAKE_DISABLED_INDEX); if (info.currentCommittedPeriod != 0) { reward = mint(_staker, info, info.currentCommittedPeriod, currentPeriod, startPeriod, reStake); info.currentCommittedPeriod = 0; if (reStake) { lockedPerPeriod[info.nextCommittedPeriod] += reward; } } if (info.nextCommittedPeriod <= previousPeriod) { reward += mint(_staker, info, info.nextCommittedPeriod, currentPeriod, startPeriod, reStake); info.nextCommittedPeriod = 0; } info.value += reward; if (info.flags.bitSet(MEASURE_WORK_INDEX)) { info.completedWork += reward; } addSnapshot(info, int256(reward)); emit Minted(_staker, previousPeriod, reward); } /** * @notice Calculate reward for one period * @param _staker Staker's address * @param _info Staker structure * @param _mintingPeriod Period for minting calculation * @param _currentPeriod Current period * @param _startPeriod Pre-calculated start period */ function mint( address _staker, StakerInfo storage _info, uint16 _mintingPeriod, uint16 _currentPeriod, uint16 _startPeriod, bool _reStake ) internal returns (uint256 reward) { reward = 0; for (uint256 i = 0; i < _info.subStakes.length; i++) { SubStakeInfo storage subStake = _info.subStakes[i]; uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod); if (subStake.firstPeriod <= _mintingPeriod && lastPeriod >= _mintingPeriod) { uint256 subStakeReward = mint( _currentPeriod, subStake.lockedValue, lockedPerPeriod[_mintingPeriod], lastPeriod.sub16(_mintingPeriod)); reward += subStakeReward; if (_reStake) { subStake.lockedValue += uint128(subStakeReward); } } } policyManager.updateFee(_staker, _mintingPeriod); return reward; } //-------------------------Slashing------------------------- /** * @notice Slash the staker's stake and reward the investigator * @param _staker Staker's address * @param _penalty Penalty * @param _investigator Investigator * @param _reward Reward for the investigator */ function slashStaker( address _staker, uint256 _penalty, address _investigator, uint256 _reward ) public isInitialized { require(msg.sender == address(adjudicator)); require(_penalty > 0); StakerInfo storage info = stakerInfo[_staker]; if (info.value <= _penalty) { _penalty = info.value; } info.value -= _penalty; if (_reward > _penalty) { _reward = _penalty; } uint16 currentPeriod = getCurrentPeriod(); uint16 nextPeriod = currentPeriod + 1; uint16 startPeriod = getStartPeriod(info, currentPeriod); (uint256 currentLock, uint256 nextLock, uint256 currentAndNextLock, uint256 shortestSubStakeIndex) = getLockedTokensAndShortestSubStake(info, currentPeriod, nextPeriod, startPeriod); // Decrease the stake if amount of locked tokens in the current period more than staker has uint256 lockedTokens = currentLock + currentAndNextLock; if (info.value < lockedTokens) { decreaseSubStakes(info, lockedTokens - info.value, currentPeriod, startPeriod, shortestSubStakeIndex); } // Decrease the stake if amount of locked tokens in the next period more than staker has if (nextLock > 0) { lockedTokens = nextLock + currentAndNextLock - (currentAndNextLock > info.value ? currentAndNextLock - info.value : 0); if (info.value < lockedTokens) { decreaseSubStakes(info, lockedTokens - info.value, nextPeriod, startPeriod, MAX_SUB_STAKES); } } emit Slashed(_staker, _penalty, _investigator, _reward); if (_penalty > _reward) { unMint(_penalty - _reward); } // TODO change to withdrawal pattern (#1499) if (_reward > 0) { token.safeTransfer(_investigator, _reward); } addSnapshot(info, - int256(_penalty)); } /** * @notice Get the value of locked tokens for a staker in the current and the next period * and find the shortest sub stake * @param _info Staker structure * @param _currentPeriod Current period * @param _nextPeriod Next period * @param _startPeriod Pre-calculated start period * @return currentLock Amount of tokens that locked in the current period and unlocked in the next period * @return nextLock Amount of tokens that locked in the next period and not locked in the current period * @return currentAndNextLock Amount of tokens that locked in the current period and in the next period * @return shortestSubStakeIndex Index of the shortest sub stake */ function getLockedTokensAndShortestSubStake( StakerInfo storage _info, uint16 _currentPeriod, uint16 _nextPeriod, uint16 _startPeriod ) internal view returns ( uint256 currentLock, uint256 nextLock, uint256 currentAndNextLock, uint256 shortestSubStakeIndex ) { uint16 minDuration = MAX_UINT16; uint16 minLastPeriod = MAX_UINT16; shortestSubStakeIndex = MAX_SUB_STAKES; currentLock = 0; nextLock = 0; currentAndNextLock = 0; for (uint256 i = 0; i < _info.subStakes.length; i++) { SubStakeInfo storage subStake = _info.subStakes[i]; uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod); if (lastPeriod < subStake.firstPeriod) { continue; } if (subStake.firstPeriod <= _currentPeriod && lastPeriod >= _nextPeriod) { currentAndNextLock += subStake.lockedValue; } else if (subStake.firstPeriod <= _currentPeriod && lastPeriod >= _currentPeriod) { currentLock += subStake.lockedValue; } else if (subStake.firstPeriod <= _nextPeriod && lastPeriod >= _nextPeriod) { nextLock += subStake.lockedValue; } uint16 duration = lastPeriod - subStake.firstPeriod; if (subStake.firstPeriod <= _currentPeriod && lastPeriod >= _currentPeriod && (lastPeriod < minLastPeriod || lastPeriod == minLastPeriod && duration < minDuration)) { shortestSubStakeIndex = i; minDuration = duration; minLastPeriod = lastPeriod; } } } /** * @notice Decrease short sub stakes * @param _info Staker structure * @param _penalty Penalty rate * @param _decreasePeriod The period when the decrease begins * @param _startPeriod Pre-calculated start period * @param _shortestSubStakeIndex Index of the shortest period */ function decreaseSubStakes( StakerInfo storage _info, uint256 _penalty, uint16 _decreasePeriod, uint16 _startPeriod, uint256 _shortestSubStakeIndex ) internal { SubStakeInfo storage shortestSubStake = _info.subStakes[0]; uint16 minSubStakeLastPeriod = MAX_UINT16; uint16 minSubStakeDuration = MAX_UINT16; while(_penalty > 0) { if (_shortestSubStakeIndex < MAX_SUB_STAKES) { shortestSubStake = _info.subStakes[_shortestSubStakeIndex]; minSubStakeLastPeriod = getLastPeriodOfSubStake(shortestSubStake, _startPeriod); minSubStakeDuration = minSubStakeLastPeriod - shortestSubStake.firstPeriod; _shortestSubStakeIndex = MAX_SUB_STAKES; } else { (shortestSubStake, minSubStakeDuration, minSubStakeLastPeriod) = getShortestSubStake(_info, _decreasePeriod, _startPeriod); } if (minSubStakeDuration == MAX_UINT16) { break; } uint256 appliedPenalty = _penalty; if (_penalty < shortestSubStake.lockedValue) { shortestSubStake.lockedValue -= uint128(_penalty); saveOldSubStake(_info, shortestSubStake.firstPeriod, _penalty, _decreasePeriod); _penalty = 0; } else { shortestSubStake.lastPeriod = _decreasePeriod - 1; _penalty -= shortestSubStake.lockedValue; appliedPenalty = shortestSubStake.lockedValue; } if (_info.currentCommittedPeriod >= _decreasePeriod && _info.currentCommittedPeriod <= minSubStakeLastPeriod) { lockedPerPeriod[_info.currentCommittedPeriod] -= appliedPenalty; } if (_info.nextCommittedPeriod >= _decreasePeriod && _info.nextCommittedPeriod <= minSubStakeLastPeriod) { lockedPerPeriod[_info.nextCommittedPeriod] -= appliedPenalty; } } } /** * @notice Get the shortest sub stake * @param _info Staker structure * @param _currentPeriod Current period * @param _startPeriod Pre-calculated start period * @return shortestSubStake The shortest sub stake * @return minSubStakeDuration Duration of the shortest sub stake * @return minSubStakeLastPeriod Last period of the shortest sub stake */ function getShortestSubStake( StakerInfo storage _info, uint16 _currentPeriod, uint16 _startPeriod ) internal view returns ( SubStakeInfo storage shortestSubStake, uint16 minSubStakeDuration, uint16 minSubStakeLastPeriod ) { shortestSubStake = shortestSubStake; minSubStakeDuration = MAX_UINT16; minSubStakeLastPeriod = MAX_UINT16; for (uint256 i = 0; i < _info.subStakes.length; i++) { SubStakeInfo storage subStake = _info.subStakes[i]; uint16 lastPeriod = getLastPeriodOfSubStake(subStake, _startPeriod); if (lastPeriod < subStake.firstPeriod) { continue; } uint16 duration = lastPeriod - subStake.firstPeriod; if (subStake.firstPeriod <= _currentPeriod && lastPeriod >= _currentPeriod && (lastPeriod < minSubStakeLastPeriod || lastPeriod == minSubStakeLastPeriod && duration < minSubStakeDuration)) { shortestSubStake = subStake; minSubStakeDuration = duration; minSubStakeLastPeriod = lastPeriod; } } } /** * @notice Save the old sub stake values to prevent decreasing reward for the previous period * @dev Saving happens only if the previous period is committed * @param _info Staker structure * @param _firstPeriod First period of the old sub stake * @param _lockedValue Locked value of the old sub stake * @param _currentPeriod Current period, when the old sub stake is already unlocked */ function saveOldSubStake( StakerInfo storage _info, uint16 _firstPeriod, uint256 _lockedValue, uint16 _currentPeriod ) internal { // Check that the old sub stake should be saved bool oldCurrentCommittedPeriod = _info.currentCommittedPeriod != 0 && _info.currentCommittedPeriod < _currentPeriod; bool oldnextCommittedPeriod = _info.nextCommittedPeriod != 0 && _info.nextCommittedPeriod < _currentPeriod; bool crosscurrentCommittedPeriod = oldCurrentCommittedPeriod && _info.currentCommittedPeriod >= _firstPeriod; bool crossnextCommittedPeriod = oldnextCommittedPeriod && _info.nextCommittedPeriod >= _firstPeriod; if (!crosscurrentCommittedPeriod && !crossnextCommittedPeriod) { return; } // Try to find already existent proper old sub stake uint16 previousPeriod = _currentPeriod - 1; for (uint256 i = 0; i < _info.subStakes.length; i++) { SubStakeInfo storage subStake = _info.subStakes[i]; if (subStake.lastPeriod == previousPeriod && ((crosscurrentCommittedPeriod == (oldCurrentCommittedPeriod && _info.currentCommittedPeriod >= subStake.firstPeriod)) && (crossnextCommittedPeriod == (oldnextCommittedPeriod && _info.nextCommittedPeriod >= subStake.firstPeriod)))) { subStake.lockedValue += uint128(_lockedValue); return; } } saveSubStake(_info, _firstPeriod, previousPeriod, 0, _lockedValue); } //-------------Additional getters for stakers info------------- /** * @notice Return the length of the array of stakers */ function getStakersLength() external view returns (uint256) { return stakers.length; } /** * @notice Return the length of the array of sub stakes */ function getSubStakesLength(address _staker) external view returns (uint256) { return stakerInfo[_staker].subStakes.length; } /** * @notice Return the information about sub stake */ function getSubStakeInfo(address _staker, uint256 _index) // TODO change to structure when ABIEncoderV2 is released (#1501) // public view returns (SubStakeInfo) // TODO "virtual" only for tests, probably will be removed after #1512 external view virtual returns (uint16 firstPeriod, uint16 lastPeriod, uint16 periods, uint128 lockedValue) { SubStakeInfo storage info = stakerInfo[_staker].subStakes[_index]; firstPeriod = info.firstPeriod; lastPeriod = info.lastPeriod; periods = info.periods; lockedValue = info.lockedValue; } /** * @notice Return the length of the array of past downtime */ function getPastDowntimeLength(address _staker) external view returns (uint256) { return stakerInfo[_staker].pastDowntime.length; } /** * @notice Return the information about past downtime */ function getPastDowntime(address _staker, uint256 _index) // TODO change to structure when ABIEncoderV2 is released (#1501) // public view returns (Downtime) external view returns (uint16 startPeriod, uint16 endPeriod) { Downtime storage downtime = stakerInfo[_staker].pastDowntime[_index]; startPeriod = downtime.startPeriod; endPeriod = downtime.endPeriod; } //------------------ ERC900 connectors ---------------------- function totalStakedForAt(address _owner, uint256 _blockNumber) public view override returns (uint256){ return stakerInfo[_owner].history.getValueAt(_blockNumber); } function totalStakedAt(uint256 _blockNumber) public view override returns (uint256){ return balanceHistory.getValueAt(_blockNumber); } function supportsHistory() external pure override returns (bool){ return true; } //------------------------Upgradeable------------------------ /** * @dev Get StakerInfo structure by delegatecall */ function delegateGetStakerInfo(address _target, bytes32 _staker) internal returns (StakerInfo memory result) { bytes32 memoryAddress = delegateGetData(_target, this.stakerInfo.selector, 1, _staker, 0); assembly { result := memoryAddress } } /** * @dev Get SubStakeInfo structure by delegatecall */ function delegateGetSubStakeInfo(address _target, bytes32 _staker, uint256 _index) internal returns (SubStakeInfo memory result) { bytes32 memoryAddress = delegateGetData( _target, this.getSubStakeInfo.selector, 2, _staker, bytes32(_index)); assembly { result := memoryAddress } } /** * @dev Get Downtime structure by delegatecall */ function delegateGetPastDowntime(address _target, bytes32 _staker, uint256 _index) internal returns (Downtime memory result) { bytes32 memoryAddress = delegateGetData( _target, this.getPastDowntime.selector, 2, _staker, bytes32(_index)); assembly { result := memoryAddress } } /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `verifyState` function verifyState(address _testTarget) public override virtual { super.verifyState(_testTarget); require(address(delegateGet(_testTarget, this.policyManager.selector)) == address(policyManager)); require(address(delegateGet(_testTarget, this.adjudicator.selector)) == address(adjudicator)); require(address(delegateGet(_testTarget, this.workLock.selector)) == address(workLock)); require(delegateGet(_testTarget, this.lockedPerPeriod.selector, bytes32(bytes2(RESERVED_PERIOD))) == lockedPerPeriod[RESERVED_PERIOD]); require(address(delegateGet(_testTarget, this.stakerFromWorker.selector, bytes32(0))) == stakerFromWorker[address(0)]); require(delegateGet(_testTarget, this.getStakersLength.selector) == stakers.length); if (stakers.length == 0) { return; } address stakerAddress = stakers[0]; require(address(uint160(delegateGet(_testTarget, this.stakers.selector, 0))) == stakerAddress); StakerInfo storage info = stakerInfo[stakerAddress]; bytes32 staker = bytes32(uint256(stakerAddress)); StakerInfo memory infoToCheck = delegateGetStakerInfo(_testTarget, staker); require(infoToCheck.value == info.value && infoToCheck.currentCommittedPeriod == info.currentCommittedPeriod && infoToCheck.nextCommittedPeriod == info.nextCommittedPeriod && infoToCheck.flags == info.flags && infoToCheck.lockReStakeUntilPeriod == info.lockReStakeUntilPeriod && infoToCheck.lastCommittedPeriod == info.lastCommittedPeriod && infoToCheck.completedWork == info.completedWork && infoToCheck.worker == info.worker && infoToCheck.workerStartPeriod == info.workerStartPeriod); require(delegateGet(_testTarget, this.getPastDowntimeLength.selector, staker) == info.pastDowntime.length); for (uint256 i = 0; i < info.pastDowntime.length && i < MAX_CHECKED_VALUES; i++) { Downtime storage downtime = info.pastDowntime[i]; Downtime memory downtimeToCheck = delegateGetPastDowntime(_testTarget, staker, i); require(downtimeToCheck.startPeriod == downtime.startPeriod && downtimeToCheck.endPeriod == downtime.endPeriod); } require(delegateGet(_testTarget, this.getSubStakesLength.selector, staker) == info.subStakes.length); for (uint256 i = 0; i < info.subStakes.length && i < MAX_CHECKED_VALUES; i++) { SubStakeInfo storage subStakeInfo = info.subStakes[i]; SubStakeInfo memory subStakeInfoToCheck = delegateGetSubStakeInfo(_testTarget, staker, i); require(subStakeInfoToCheck.firstPeriod == subStakeInfo.firstPeriod && subStakeInfoToCheck.lastPeriod == subStakeInfo.lastPeriod && subStakeInfoToCheck.periods == subStakeInfo.periods && subStakeInfoToCheck.lockedValue == subStakeInfo.lockedValue); } // it's not perfect because checks not only slot value but also decoding // at least without additional functions require(delegateGet(_testTarget, this.totalStakedForAt.selector, staker, bytes32(block.number)) == totalStakedForAt(stakerAddress, block.number)); require(delegateGet(_testTarget, this.totalStakedAt.selector, bytes32(block.number)) == totalStakedAt(block.number)); if (info.worker != address(0)) { require(address(delegateGet(_testTarget, this.stakerFromWorker.selector, bytes32(uint256(info.worker)))) == stakerFromWorker[info.worker]); } } /// @dev the `onlyWhileUpgrading` modifier works through a call to the parent `finishUpgrade` function finishUpgrade(address _target) public override virtual { super.finishUpgrade(_target); // Create fake period lockedPerPeriod[RESERVED_PERIOD] = 111; // Create fake worker stakerFromWorker[address(0)] = address(this); } }
// SPDX-License-Identifier: AGPL-3.0-or-later pragma solidity ^0.7.0; import "./Ownable.sol"; /** * @notice Base contract for upgradeable contract * @dev Inherited contract should implement verifyState(address) method by checking storage variables * (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) } } }
Contract Security Audit
- Trail of Bits - January 22nd, 2020 - Security Audit Report
[{"inputs":[{"internalType":"contract NuCypherToken","name":"_token","type":"address"},{"internalType":"contract StakingEscrow","name":"_escrow","type":"address"},{"internalType":"uint256","name":"_startBidDate","type":"uint256"},{"internalType":"uint256","name":"_endBidDate","type":"uint256"},{"internalType":"uint256","name":"_endCancellationDate","type":"uint256"},{"internalType":"uint256","name":"_boostingRefund","type":"uint256"},{"internalType":"uint16","name":"_stakingPeriods","type":"uint16"},{"internalType":"uint256","name":"_minAllowedBid","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"depositedETH","type":"uint256"}],"name":"Bid","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"startIndex","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endIndex","type":"uint256"}],"name":"BiddersChecked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Canceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"claimedTokens","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"CompensationWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Deposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"bidder","type":"address"},{"indexed":false,"internalType":"uint256","name":"refundETH","type":"uint256"}],"name":"ForceRefund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"refundETH","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"completedWork","type":"uint256"}],"name":"Refund","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"Shutdown","type":"event"},{"inputs":[],"name":"SLOWING_REFUND","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bid","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"bidders","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"bonusETHSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boostingRefund","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelBid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claim","outputs":[{"internalType":"uint256","name":"claimedTokens","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"compensation","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endBidDate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"endCancellationDate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"escrow","outputs":[{"internalType":"contract StakingEscrow","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethAmount","type":"uint256"}],"name":"ethToTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_ethAmount","type":"uint256"}],"name":"ethToWork","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable[]","name":"_biddersForRefund","type":"address[]"}],"name":"forceRefund","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_bidder","type":"address"}],"name":"getAvailableRefund","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBiddersLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_bidder","type":"address"}],"name":"getRemainingWork","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isClaimingAvailable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxAllowableLockedTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minAllowableLockedTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minAllowedBid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextBidderToCheck","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"refund","outputs":[{"internalType":"uint256","name":"refundETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"shutdown","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakingPeriods","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"startBidDate","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract NuCypherToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"tokenDeposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tokenSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_gasToSaveState","type":"uint256"}],"name":"verifyBiddingCorrectness","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawCompensation","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"workInfo","outputs":[{"internalType":"uint256","name":"depositedETH","type":"uint256"},{"internalType":"uint256","name":"completedWork","type":"uint256"},{"internalType":"bool","name":"claimed","type":"bool"},{"internalType":"uint128","name":"index","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_completedWork","type":"uint256"},{"internalType":"uint256","name":"_depositedETH","type":"uint256"}],"name":"workToETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}]
Contract Creation Code
6101606040523480156200001257600080fd5b5060405162002f2438038062002f2483398181016040526101008110156200003957600080fd5b50805160208201516040808401516060850151608086015160a087015160c088015160e090980151600080546001600160a01b031916331780825596519899979895979496939592949391926001600160a01b0392909216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a36000886001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b158015620000f357600080fd5b505afa15801562000108573d6000803e3d6000fd5b505050506040513d60208110156200011f57600080fd5b505190508015801590620001a057506000886001600160a01b031663407f80016040518163ffffffff1660e01b815260040160206040518083038186803b1580156200016a57600080fd5b505afa1580156200017f573d6000803e3d6000fd5b505050506040513d60208110156200019657600080fd5b505163ffffffff16115b8015620002255750886001600160a01b0316886001600160a01b031663fc0c546a6040518163ffffffff1660e01b815260040160206040518083038186803b158015620001ec57600080fd5b505afa15801562000201573d6000803e3d6000fd5b505050506040513d60208110156200021857600080fd5b50516001600160a01b0316145b80156200023157508686115b80156200023d57504286115b80156200024a5750858510155b8015620002575750600082115b8015620002645750600084115b8015620002e05750876001600160a01b0316633ac5743d6040518163ffffffff1660e01b815260040160206040518083038186803b158015620002a657600080fd5b505afa158015620002bb573d6000803e3d6000fd5b505050506040513d6020811015620002d257600080fd5b505161ffff90811690841610155b620002ea57600080fd5b6064816b409f9cbc7c4a04c2200000006c193e5939a08ce9dbd480000000820204816200031357fe5b041480156200033d575083816b409f9cbc7c4a04c220000000818302810204816200033a57fe5b04145b6200034757600080fd5b6001600160601b031960608a811b821660805289901b1660a05260028790556003869055600485815560c08590526001600160f01b031960f085901b166101005260e0839052604080516337db29d160e11b815290516001600160a01b038b1692636fb653a292808201926020929091829003018186803b158015620003cc57600080fd5b505afa158015620003e1573d6000803e3d6000fd5b505050506040513d6020811015620003f857600080fd5b50516101205260408051638cab6c4f60e01b815290516001600160a01b038a1691638cab6c4f916004808301926020929190829003018186803b1580156200043f57600080fd5b505afa15801562000454573d6000803e3d6000fd5b505050506040513d60208110156200046b57600080fd5b50516101405250505050505050505060805160601c60a05160601c60c05160e0516101005160f01c610120516101405161298a6200059a6000398061095c52806110d35280611415528061176f52806117a952806118f05280611def528061228b525080611199528061186f52806119115280611b535280611d835280611e10525080610cf15280611a645250806108e75280610a3d5280610e5d528061108f52806110f5528061111d528061164e528061167d52806116f6528061174b528061194f5280611e3552806121c652806121fd528061222e528061225e52508061105c5280612406528061244852508061076a5280610c1f5280610d225280610da65280610f38528061124252806120e4525080610c59528061207952806121255280612507525061298a6000f3fe60806040526004361061021a5760003560e01c806387a15dc911610123578063b15cf964116100ab578063e1fe7d231161006f578063e1fe7d231461068b578063e2fdcc17146106b5578063f2fde38b146106ca578063fc0c546a146106fd578063fc0e74d1146107125761021a565b8063b15cf96414610590578063bc859f44146105a5578063be7ff580146105cf578063cbce49ed146105e4578063cff29dfd146106615761021a565b80638da5cb5b116100f25780638da5cb5b146104f65780638f32d59b146105275780639435c8871461053c5780639b51ac8514610551578063a4c89322146105665761021a565b806387a15dc91461048e57806388657c7a146104a3578063897db45c146104cc5780638cab6c4f146104e15761021a565b80635bf997f5116101a6578063715018a611610175578063715018a6146103f057806372f96040146104055780637824407f146104385780637f4fad691461044d57806387865033146104625761021a565b80635bf997f514610381578063617e677e1461039657806366ade78e146103ab5780636fb653a2146103db5761021a565b80631f45002c116101ed5780631f45002c146102ad5780634e71d92d1461030f5780634f1910a1146103245780635707bee514610339578063590e1ae31461036c5761021a565b80630594851c1461021f578063124a1f4f146102645780631998aeef1461028e5780631bb8976214610298575b600080fd5b34801561022b57600080fd5b506102526004803603602081101561024257600080fd5b50356001600160a01b0316610727565b60408051918252519081900360200190f35b34801561027057600080fd5b506102526004803603602081101561028757600080fd5b5035610810565b610296610823565b005b3480156102a457600080fd5b50610252610ac3565b3480156102b957600080fd5b506102e0600480360360208110156102d057600080fd5b50356001600160a01b0316610ac9565b604080519485526020850193909352901515838301526001600160801b03166060830152519081900360800190f35b34801561031b57600080fd5b50610252610afd565b34801561033057600080fd5b50610252610e5b565b34801561034557600080fd5b506102526004803603602081101561035c57600080fd5b50356001600160a01b0316610e7f565b34801561037857600080fd5b50610252610e91565b34801561038d57600080fd5b5061025261105a565b3480156103a257600080fd5b5061025261107e565b3480156103b757600080fd5b50610252600480360360408110156103ce57600080fd5b5080359060200135611084565b3480156103e757600080fd5b50610252611197565b3480156103fc57600080fd5b506102966111bb565b34801561041157600080fd5b506102526004803603602081101561042857600080fd5b50356001600160a01b0316611216565b34801561044457600080fd5b5061025261132b565b34801561045957600080fd5b50610252611331565b34801561046e57600080fd5b50610477611337565b6040805161ffff9092168252519081900360200190f35b34801561049a57600080fd5b5061029661133c565b3480156104af57600080fd5b506104b86113f1565b604080519115158252519081900360200190f35b3480156104d857600080fd5b5061025261140d565b3480156104ed57600080fd5b50610252611413565b34801561050257600080fd5b5061050b611437565b604080516001600160a01b039092168252519081900360200190f35b34801561053357600080fd5b506104b8611446565b34801561054857600080fd5b50610296611457565b34801561055d57600080fd5b506102526116ec565b34801561057257600080fd5b506102526004803603602081101561058957600080fd5b50356116f2565b34801561059c57600080fd5b506102526117d1565b3480156105b157600080fd5b50610252600480360360208110156105c857600080fd5b50356117d7565b3480156105db57600080fd5b50610477611a62565b3480156105f057600080fd5b506102966004803603602081101561060757600080fd5b81019060208101813564010000000081111561062257600080fd5b82018360208201111561063457600080fd5b8035906020019184602083028401116401000000008311171561065657600080fd5b509092509050611a86565b34801561066d57600080fd5b5061050b6004803603602081101561068457600080fd5b5035612005565b34801561069757600080fd5b50610296600480360360208110156106ae57600080fd5b503561202c565b3480156106c157600080fd5b5061050b6120e2565b3480156106d657600080fd5b50610296600480360360208110156106ed57600080fd5b50356001600160a01b0316612106565b34801561070957600080fd5b5061050b612123565b34801561071e57600080fd5b50610296612147565b6001600160a01b0380821660008181526006602090815260408083206001810154825163d094adbf60e01b8152600481019690965291519395909486946107e0947f00000000000000000000000000000000000000000000000000000000000000009093169263d094adbf9260248082019391829003018186803b1580156107ae57600080fd5b505afa1580156107c2573d6000803e3d6000fd5b505050506040513d60208110156107d857600080fd5b5051906121a6565b905060006107f18360000154610810565b9050818111610806576000935050505061080b565b039150505b919050565b600061081d8260006121bb565b92915050565b60025442101561087a576040805162461bcd60e51b815260206004820152601760248201527f42696464696e67206973206e6f74206f70656e20796574000000000000000000604482015290519081900360640190fd5b60035442106108d0576040805162461bcd60e51b815260206004820152601b60248201527f42696464696e6720697320616c72656164792066696e69736865640000000000604482015290519081900360640190fd5b3360009081526006602052604090208054610a6b577f000000000000000000000000000000000000000000000000000000000000000034101561095a576040805162461bcd60e51b815260206004820152601c60248201527f426964206d757374206265206174206c65617374206d696e696d756d00000000604482015290519081900360640190fd5b7f00000000000000000000000000000000000000000000000000000000000000006001548161098557fe5b04600880549050106109c85760405162461bcd60e51b81526004018080602001828103825260228152602001806128b56022913960400191505060405180910390fd5b600880546002830180546001600160801b0390921661010002610100600160881b031990921691909117905580546001810182556000919091527ff3f7a9fe364faab93b216da50a3214154f22a0a2b415b23a84c8169e8b636ee30180546001600160a01b03191633179055600554610a63907f000000000000000000000000000000000000000000000000000000000000000034036122ee565b600555610a7c565b600554610a7890346122ee565b6005555b8054610a8890346122ee565b815560408051348152905133917fe684a55f31b79eca403df938249029212a5925ec6be8012e099b45bc1019e5d2919081900360200190a250565b60085490565b60066020526000908152604090208054600182015460029092015490919060ff81169061010090046001600160801b031684565b6000610b076113f1565b610b425760405162461bcd60e51b815260040180806020018281038252602181526020018061281f6021913960400191505060405180910390fd5b336000908152600660205260409020600281015460ff1615610bab576040805162461bcd60e51b815260206004820152601a60248201527f546f6b656e732061726520616c726561647920636c61696d6564000000000000604482015290519081900360640190fd5b8054610bb6906116f2565b915060008211610c00576040805162461bcd60e51b815260206004820152601060248201526f4e6f7468696e6720746f20636c61696d60801b604482015290519081900360640190fd5b60028101805460ff191660011790556040805163095ea7b360e01b81527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0390811660048301526024820185905291517f0000000000000000000000000000000000000000000000000000000000000000929092169163095ea7b3916044808201926020929091908290030181600087803b158015610ca557600080fd5b505af1158015610cb9573d6000803e3d6000fd5b505050506040513d6020811015610ccf57600080fd5b505060408051630b8f81e160e11b81523360048201526024810184905261ffff7f000000000000000000000000000000000000000000000000000000000000000016604482015290516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169163171f03c291606480830192600092919082900301818387803b158015610d6957600080fd5b505af1158015610d7d573d6000803e3d6000fd5b5050604080516312796b7560e21b81523360048201526001602482015290516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001693506349e5add4925060448083019260209291908290030181600087803b158015610df057600080fd5b505af1158015610e04573d6000803e3d6000fd5b505050506040513d6020811015610e1a57600080fd5b5051600182015560408051838152905133917fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a919081900360200190a25090565b7f000000000000000000000000000000000000000000000000000000000000000081565b60076020526000908152604090205481565b336000908152600660205260408120600281015460ff16610ee35760405162461bcd60e51b815260040180806020018281038252602481526020018061279c6024913960400191505060405180910390fd5b610eec33611216565b915060008211610f2d5760405162461bcd60e51b81526004018080602001828103825260418152602001806128406041913960600191505060405180910390fd5b8054821415610fdd577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166349e5add43360006040518363ffffffff1660e01b815260040180836001600160a01b03168152602001821515815260200192505050602060405180830381600087803b158015610fb057600080fd5b505af1158015610fc4573d6000803e3d6000fd5b505050506040513d6020811015610fda57600080fd5b50505b8054610fe990836121a6565b808255600090610ffa9084906121bb565b600183015490915061100c90826122ee565b60018301556040805184815260208101839052815133927f73f04af9dcc582a923ec15d3eea990fe34adabfff2879e28d44572e01a54abb6928290030190a26110553384612307565b505090565b7f000000000000000000000000000000000000000000000000000000000000000081565b60045481565b6008546005546000917f000000000000000000000000000000000000000000000000000000000000000002906110c9576110c184826001546123f1565b91505061081d565b60085460009081907f0000000000000000000000000000000000000000000000000000000000000000027f000000000000000000000000000000000000000000000000000000000000000086111561117a577f000000000000000000000000000000000000000000000000000000000000000086039150600081600154039050611156838260055461243d565b93508388116111785761116c88600554836123f1565b9550505050505061081d565b505b828703965061118a8785836123f1565b9091019695505050505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b6111c3611446565b6111cc57600080fd5b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6001600160a01b0381166000908152600660205260408120805461123e57600091505061080b565b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d094adbf856040518263ffffffff1660e01b815260040180826001600160a01b0316815260200191505060206040518083038186803b1580156112ad57600080fd5b505afa1580156112c1573d6000803e3d6000fd5b505050506040513d60208110156112d757600080fd5b505160018301549091506000906112ef9083906121a6565b905080611302576000935050505061080b565b6000611312828560000154611084565b8454909150811115611322575082545b95945050505050565b60015481565b60025481565b606481565b336000908152600760205260409020548061139e576040805162461bcd60e51b815260206004820152601860248201527f5468657265206973206e6f20636f6d70656e736174696f6e0000000000000000604482015290519081900360640190fd5b336000818152600760205260408120556113b89082612307565b60408051828152905133917f1480b15e3eab83a9b923578ffe330e3dce94536007ec9c0b48a5dfbde8c49181919081900360200190a250565b600060045442101580156114085750600854600954145b905090565b60035481565b7f000000000000000000000000000000000000000000000000000000000000000081565b6000546001600160a01b031690565b6000546001600160a01b0316331490565b60045442106114975760405162461bcd60e51b81526004018080602001828103825260348152602001806128816034913960400191505060405180910390fd5b33600090815260066020526040902080546114ec576040805162461bcd60e51b815260206004820152601060248201526f139bc8189a59081d1bc818d85b98d95b60821b604482015290519081900360640190fd5b600281015460ff1615611546576040805162461bcd60e51b815260206004820152601a60248201527f546f6b656e732061726520616c726561647920636c61696d6564000000000000604482015290519081900360640190fd5b80546000825560085460028301546000199091019061010090046001600160801b0316811461161f5760006008828154811061157e57fe5b6000918252602090912001546002850154600880546001600160a01b0390931693508392909161010090046001600160801b03169081106115bb57fe5b600091825260208083209190910180546001600160a01b0319166001600160a01b039485161790556002878101549490931682526006905260409020018054610100600160881b031916610100928390046001600160801b03169092029190911790555b600880548061162a57fe5b600082815260209020810160001990810180546001600160a01b03191690550190557f00000000000000000000000000000000000000000000000000000000000000008211156116a7576005546116a3907f000000000000000000000000000000000000000000000000000000000000000084036121a6565b6005555b6116b13383612307565b60408051838152905133917ff3a6ef5718c05d9183af076f5753197b68b04552a763c34796637d6134bdd0f2919081900360200190a2505050565b60055481565b60007f00000000000000000000000000000000000000000000000000000000000000008210156117245750600061080b565b600554611740576008546001548161173857fe5b04905061080b565b6008546001546005547f00000000000000000000000000000000000000000000000000000000000000008503927f000000000000000000000000000000000000000000000000000000000000000002909103906117a7906117a18484612477565b9061249e565b7f000000000000000000000000000000000000000000000000000000000000000001949350505050565b60095481565b600060045442101561181a5760405162461bcd60e51b81526004018080602001828103825260348152602001806129006034913960400191505060405180910390fd5b600854600954141561185d5760405162461bcd60e51b81526004018080602001828103825260218152602001806127276021913960400191505060405180910390fd5b6009546005546118e5576008546001547f000000000000000000000000000000000000000000000000000000000000000091908161189757fe5b0411156118e0576040805162461bcd60e51b81526020600482015260126024820152714e6f7420656e6f756768206269646465727360701b604482015290519081900360640190fd5b506008545b6008546001546005547f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000081900393029091039060009061194d9083906117a1908690612477565b7f00000000000000000000000000000000000000000000000000000000000000000190505b600854841080156119825750855a115b15611a085760006008858154811061199657fe5b60009182526020808320909101546001600160a01b031680835260069091526040909120549091508210156119fc5760405162461bcd60e51b81526004018080602001828103825260258152602001806127c06025913960400191505060405180910390fd5b50600190930192611972565b6009548414611a55576009546040805191825260208201869052805133927f97d5ad02b58968c48057bb16e90b9468ec753e30169bf00692b7ba02b47b090e92908290030190a260098490555b5050600954949350505050565b7f000000000000000000000000000000000000000000000000000000000000000081565b600454421015611ac75760405162461bcd60e51b81526004018080602001828103825260348152602001806129006034913960400191505060405180910390fd5b6008546009541415611b0a5760405162461bcd60e51b81526004018080602001828103825260218152602001806127276021913960400191505060405180910390fd5b8080611b475760405162461bcd60e51b81526004018080602001828103825260288152602001806127746028913960400191505060405180910390fd5b600154600090611b77907f00000000000000000000000000000000000000000000000000000000000000006124c0565b600854909150811115611b9357611b8c6124df565b5050612001565b600084846000818110611ba257fe5b602090810292909201356001600160a01b03166000818152600690935260409092205491925081905060015b85811015611c80576000888883818110611be457fe5b6001600160a01b0360209182029390930135831660008181526006909252604090912054909350918716831190508015611c1e5750600081115b611c595760405162461bcd60e51b815260040180806020018281038252602c815260200180612748602c913960400191505060405180910390fd5b80851115611c6957809450611c75565b80841015611c75578093505b509350600101611bce565b5060608567ffffffffffffffff81118015611c9a57600080fd5b50604051908082528060200260200182016040528015611cc4578160200160208202803683370190505b509050818314611d815760005b86811015611d7f576000898983818110611ce757fe5b905060200201356001600160a01b03169050600060066000836001600160a01b03166001600160a01b0316815260200190815260200160002090508581600001541115611d755785816000015403848481518110611d4157fe5b60209081029190910101528581558351849084908110611d5d57fe5b60200260200101516005600082825403925050819055505b5050600101611cd1565b505b7f0000000000000000000000000000000000000000000000000000000000000000611dab846116f2565b11611de75760405162461bcd60e51b81526004018080602001828103825260298152602001806128d76029913960400191505060405180910390fd5b6008546001547f00000000000000000000000000000000000000000000000000000000000000007f0000000000000000000000000000000000000000000000000000000000000000819003927f0000000000000000000000000000000000000000000000000000000000000000870392910290036000611e9a611e6a858c612477565b8303611e94611e846005548861247790919063ffffffff16565b611e8e8787612477565b906121a6565b906124c0565b90506000611ea888836121a6565b600580548d850290039055905060005b8b811015611f335760008e8e83818110611ece57fe5b905060200201356001600160a01b03169050600060066000836001600160a01b03166001600160a01b03168152602001908152602001600020905084898481518110611f1657fe5b602090810291909101018051909101905283905550600101611eb8565b50600060098190555b8b811015611ff45760008e8e83818110611f5257fe5b905060200201356001600160a01b03169050878281518110611f7057fe5b6020908102919091018101516001600160a01b03831660008181526007909352604090922080549091019055885133907f2887b6da4c721c91735aada87ddc427d4ef240ef9b54e759ef10c0792b409373908b9086908110611fce57fe5b60200260200101516040518082815260200191505060405180910390a350600101611f3c565b5050505050505050505050505b5050565b6008818154811061201257fe5b6000918252602090912001546001600160a01b0316905081565b600354421061206c5760405162461bcd60e51b815260040180806020018281038252602e8152602001806126f9602e913960400191505060405180910390fd5b6120a16001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001633308461255b565b600180548201905560408051828152905133917f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c4919081900360200190a250565b7f000000000000000000000000000000000000000000000000000000000000000081565b61210e611446565b61211757600080fd5b612120816125fe565b50565b7f000000000000000000000000000000000000000000000000000000000000000081565b61214f611446565b61215857600080fd5b6121606113f1565b1561219c5760405162461bcd60e51b81526004018080602001828103825260218152602001806129346021913960400191505060405180910390fd5b6121a46124df565b565b6000828211156121b557600080fd5b50900390565b6008546005546000917f000000000000000000000000000000000000000000000000000000000000000002906121f8576110c1846001548361243d565b6000807f00000000000000000000000000000000000000000000000000000000000000008587011161222c57859150612286565b7f0000000000000000000000000000000000000000000000000000000000000000851061225a575084612286565b50507f000000000000000000000000000000000000000000000000000000000000000084840103808503905b6008547f000000000000000000000000000000000000000000000000000000000000000002600083156122c1576122be84838761243d565b90505b82156122e3576000826001540390506122dd848260055461243d565b82019150505b979650505050505050565b60008282018381101561230057600080fd5b9392505050565b8047101561235c576040805162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e6365000000604482015290519081900360640190fd5b6040516000906001600160a01b0384169083908381818185875af1925050503d80600081146123a7576040519150601f19603f3d011682016040523d82523d6000602084013e6123ac565b606091505b50509050806123ec5760405162461bcd60e51b815260040180806020018281038252603a8152602001806127e5603a913960400191505060405180910390fd5b505050565b6000612435612401836064612477565b6117a17f000000000000000000000000000000000000000000000000000000000000000061242f8888612477565b90612477565b949350505050565b600061243561246c837f0000000000000000000000000000000000000000000000000000000000000000612477565b611e94606461242f88885b6000826124865750600061081d565b8282028284828161249357fe5b041461230057600080fd5b60008082116124ac57600080fd5b60008284816124b757fe5b04949350505050565b60008160016124cf85836122ee565b03816124d757fe5b049392505050565b6000600281905560035560001960045561252e6124fa611437565b6001546001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016919061266c565b60405133907f28b4c24cb1012c094cd2f59f98e89d791973295f8fda6eaa118022d6d318960a90600090a2565b836001600160a01b03166323b872dd8484846040518463ffffffff1660e01b815260040180846001600160a01b03168152602001836001600160a01b031681526020018281526020019350505050602060405180830381600087803b1580156125c357600080fd5b505af11580156125d7573d6000803e3d6000fd5b505050506040513d60208110156125ed57600080fd5b50516125f857600080fd5b50505050565b6001600160a01b03811661261157600080fd5b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b826001600160a01b031663a9059cbb83836040518363ffffffff1660e01b815260040180836001600160a01b0316815260200182815260200192505050602060405180830381600087803b1580156126c357600080fd5b505af11580156126d7573d6000803e3d6000fd5b505050506040513d60208110156126ed57600080fd5b50516123ec57600080fdfe43616e2774206465706f736974206d6f726520746f6b656e7320616674657220656e64206f662062696464696e6742696464657273206861766520616c7265616479206265656e20636865636b6564416464726573736573206d75737420626520616e206172726179206f6620756e6971756520626964646572734d757374206265206174206c65617374206f6e652062696464657220666f72206120726566756e64546f6b656e73206d75737420626520636c61696d6564206265666f726520726566756e644269642069732067726561746572207468616e206d617820616c6c6f7761626c6520626964416464726573733a20756e61626c6520746f2073656e642076616c75652c20726563697069656e74206d61792068617665207265766572746564436c61696d696e6720686173206e6f74206265656e20656e61626c6564207965744e6f7468696e6720746f20726566756e643a207468657265206973206e6f2045544820746f20726566756e64206f72206e6f20636f6d706c6574656420776f726b43616e63656c6c6174696f6e20616c6c6f776564206f6e6c7920647572696e672063616e63656c6c6174696f6e2077696e646f774e6f7420656e6f75676820746f6b656e7320666f72206d6f726520626964646572734174206c65617374206f6e65206f6620626964646572732068617320616c6c6f7761626c65206269644f7065726174696f6e20697320616c6c6f776564207768656e2063616e63656c6c6174696f6e207068617365206973206f766572436c61696d696e672068617320616c7265616479206265656e20656e61626c6564a26469706673582212209391d2edb42d3db3db71331f2d28e571cf258e989bdbfbfe3593d481730e68e864736f6c634300070000330000000000000000000000004fe83213d56308330ec302a8bd641f1d0113a4cc000000000000000000000000bbd3c0c794f40c4f993b03f65343acc6fcfcb2e2000000000000000000000000000000000000000000000000000000005f4d8f00000000000000000000000000000000000000000000000000000000005f7278ff000000000000000000000000000000000000000000000000000000005f751bff000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000b40000000000000000000000000000000000000000000000004563918244f40000
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000004fe83213d56308330ec302a8bd641f1d0113a4cc000000000000000000000000bbd3c0c794f40c4f993b03f65343acc6fcfcb2e2000000000000000000000000000000000000000000000000000000005f4d8f00000000000000000000000000000000000000000000000000000000005f7278ff000000000000000000000000000000000000000000000000000000005f751bff000000000000000000000000000000000000000000000000000000000000032000000000000000000000000000000000000000000000000000000000000000b40000000000000000000000000000000000000000000000004563918244f40000
-----Decoded View---------------
Arg [0] : _token (address): 0x4fe83213d56308330ec302a8bd641f1d0113a4cc
Arg [1] : _escrow (address): 0xbbd3c0c794f40c4f993b03f65343acc6fcfcb2e2
Arg [2] : _startBidDate (uint256): 1598918400
Arg [3] : _endBidDate (uint256): 1601337599
Arg [4] : _endCancellationDate (uint256): 1601510399
Arg [5] : _boostingRefund (uint256): 800
Arg [6] : _stakingPeriods (uint16): 180
Arg [7] : _minAllowedBid (uint256): 5000000000000000000
-----Encoded View---------------
8 Constructor Arguments found :
Arg [0] : 0000000000000000000000004fe83213d56308330ec302a8bd641f1d0113a4cc
Arg [1] : 000000000000000000000000bbd3c0c794f40c4f993b03f65343acc6fcfcb2e2
Arg [2] : 000000000000000000000000000000000000000000000000000000005f4d8f00
Arg [3] : 000000000000000000000000000000000000000000000000000000005f7278ff
Arg [4] : 000000000000000000000000000000000000000000000000000000005f751bff
Arg [5] : 0000000000000000000000000000000000000000000000000000000000000320
Arg [6] : 00000000000000000000000000000000000000000000000000000000000000b4
Arg [7] : 0000000000000000000000000000000000000000000000004563918244f40000
Deployed ByteCode Sourcemap
317:22193:16:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11217:409;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;11217:409:16;-1:-1:-1;;;;;11217:409:16;;:::i;:::-;;;;;;;;;;;;;;;;9464:117;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;9464:117:16;;:::i;11857:860::-;;;:::i;:::-;;11689:98;;;;;;;;;;;;;:::i;2362:44::-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2362:44:16;-1:-1:-1;;;;;2362:44:16;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2362:44:16;;;;;;;;;;;;;;20187:657;;;;;;;;;;;;;:::i;1954:38::-;;;;;;;;;;;;;:::i;2412:47::-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2412:47:16;-1:-1:-1;;;;;2412:47:16;;:::i;21671:837::-;;;;;;;;;;;;;:::i;1909:39::-;;;;;;;;;;;;;:::i;2286:34::-;;;;;;;;;;;;;:::i;10122:1026::-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;10122:1026:16;;;;;;;:::i;2079:49::-;;;;;;;;;;;;;:::i;1381:137:10:-;;;;;;;;;;;;;:::i;20911:691:16:-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;20911:691:16;-1:-1:-1;;;;;20911:691:16;;:::i;2190:26::-;;;;;;;;;;;;;:::i;2222:27::-;;;;;;;;;;;;;:::i;1292:43::-;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;17862:288;;;;;;;;;;;;;:::i;19897:171::-;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;2255:25;;;;;;;;;;;;;:::i;2134:49::-;;;;;;;;;;;;;:::i;693:77:10:-;;;;;;;;;;;;;:::i;:::-;;;;-1:-1:-1;;;;;693:77:10;;;;;;;;;;;;;;1013:90;;;;;;;;;;;;;:::i;12788:977:16:-;;;;;;;;;;;;;:::i;2327:29::-;;;;;;;;;;;;;:::i;5997:562::-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;5997:562:16;;:::i;2561:32::-;;;;;;;;;;;;;:::i;18579:1254::-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;18579:1254:16;;:::i;1998:38::-;;;;;;;;;;;;;:::i;14624:3162::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;14624:3162:16;;-1:-1:-1;14624:3162:16;-1:-1:-1;14624:3162:16;:::i;2466:24::-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;2466:24:16;;:::i;5535:293::-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;5535:293:16;;:::i;1442:37::-;;;;;;;;;;;;;:::i;1689:107:10:-;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1689:107:10;-1:-1:-1;;;;;1689:107:10;;:::i;1400:36:16:-;;;;;;;;;;;;;:::i;13884:152::-;;;;;;;;;;;;;:::i;11217:409::-;-1:-1:-1;;;;;11326:17:16;;;11283:7;11326:17;;;:8;:17;;;;;;;;11414:18;;;;11377:32;;-1:-1:-1;;;11377:32:16;;;;;;;;;;;11283:7;;11326:17;;11283:7;;11377:56;;:6;:23;;;;;;:32;;;;;;;;;;;:23;:32;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;11377:32:16;;:36;:56::i;:::-;11353:80;;11443:21;11467:28;11477:4;:17;;;11467:9;:28::i;:::-;11443:52;;11526:13;11509;:30;11505:69;;11562:1;11555:8;;;;;;;11505:69;11590:29;;-1:-1:-1;;11217:409:16;;;;:::o;9464:117::-;9524:7;9550:24;9560:10;9572:1;9550:9;:24::i;:::-;9543:31;9464:117;-1:-1:-1;;9464:117:16:o;11857:860::-;11926:12;;11907:15;:31;;11899:67;;;;;-1:-1:-1;;;11899:67:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;12002:10;;11984:15;:28;11976:68;;;;;-1:-1:-1;;;11976:68:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;12087:10;12054:21;12078:20;;;:8;:20;;;;;12134:17;;12130:477;;12193:13;12180:9;:26;;12172:67;;;;;-1:-1:-1;;;12172:67:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;12292:24;12278:11;;:38;;;;;;12261:7;:14;;;;:55;12253:102;;;;-1:-1:-1;;;12253:102:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12390:7;:14;;12369:10;;;:36;;-1:-1:-1;;;;;12369:36:16;;;;;-1:-1:-1;;;;;;12369:36:16;;;;;;;;;12419:24;;12369:10;12419:24;;;;-1:-1:-1;12419:24:16;;;;;;;;-1:-1:-1;;;;;;12419:24:16;12432:10;12419:24;;;12474:14;;:45;;12505:13;12493:9;:25;12474:18;:45::i;:::-;12457:14;:62;12130:477;;;12567:14;;:29;;12586:9;12567:18;:29::i;:::-;12550:14;:46;12130:477;12637:17;;:32;;12659:9;12637:21;:32::i;:::-;12617:52;;12684:26;;;12700:9;12684:26;;;;12688:10;;12684:26;;;;;;;;;;11857:860;:::o;11689:98::-;11766:7;:14;11689:98;:::o;2362:44::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2362:44:16;;:::o;20187:657::-;20222:21;20263;:19;:21::i;:::-;20255:67;;;;-1:-1:-1;;;20255:67:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;20365:10;20332:21;20356:20;;;:8;:20;;;;;20395:12;;;;;;20394:13;20386:52;;;;;-1:-1:-1;;;20386:52:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;20476:17;;20464:30;;:11;:30::i;:::-;20448:46;;20528:1;20512:13;:17;20504:46;;;;;-1:-1:-1;;;20504:46:16;;;;;;;;;;;;-1:-1:-1;;;20504:46:16;;;;;;;;;;;;;;;20561:12;;;:19;;-1:-1:-1;;20561:19:16;20576:4;20561:19;;;20590:45;;;-1:-1:-1;;;20590:45:16;;20612:6;-1:-1:-1;;;;;20590:45:16;;;;;;;;;;;;;;;:5;:13;;;;;;;:45;;;;;;;;;;;;;;;20561:12;20590:13;:45;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;20645:69:16;;;-1:-1:-1;;;20645:69:16;;20672:10;20645:69;;;;;;;;;;;20699:14;20645:69;;;;;;;-1:-1:-1;;;;;20645:6:16;:26;;;;:69;;;;;-1:-1:-1;;20645:69:16;;;;;;;-1:-1:-1;20645:26:16;:69;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;20745:43:16;;;-1:-1:-1;;;20745:43:16;;20771:10;20745:43;;;;20783:4;20745:43;;;;;;-1:-1:-1;;;;;20745:6:16;:25;;-1:-1:-1;20745:25:16;;-1:-1:-1;20745:43:16;;;;;;;;;;;;;;-1:-1:-1;20745:25:16;:43;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;20745:43:16;20724:18;;;:64;20803:34;;;;;;;;20811:10;;20803:34;;;;;;20745:43;20803:34;;;20187:657;;:::o;1954:38::-;;;:::o;2412:47::-;;;;;;;;;;;;;:::o;21671:837::-;21769:10;21707:17;21760:20;;;:8;:20;;;;;21798:12;;;;;;21790:61;;;;-1:-1:-1;;;21790:61:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21873:30;21892:10;21873:18;:30::i;:::-;21861:42;;21933:1;21921:9;:13;21913:91;;;;-1:-1:-1;;;21913:91:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;22032:17;;22019:30;;22015:105;;;22065:6;-1:-1:-1;;;;;22065:25:16;;22091:10;22103:5;22065:44;;;;;;;;;;;;;-1:-1:-1;;;;;22065:44:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;22015:105:16;22149:17;;:32;;22171:9;22149:21;:32::i;:::-;22129:52;;;:17;;22293:39;;22303:9;;22293;:39::i;:::-;22364:18;;;;22269:63;;-1:-1:-1;22364:37:16;;22269:63;22364:22;:37::i;:::-;22343:18;;;:58;22416:44;;;;;;;;;;;;;;22423:10;;22416:44;;;;;;;;22470:31;:10;22491:9;22470:20;:31::i;:::-;21671:837;;;:::o;1909:39::-;;;:::o;2286:34::-;;;;:::o;10122:1026::-;10252:7;:14;10369;;10209:7;;10269:13;10252:30;;10365:110;;10411:53;10421:14;10437:13;10452:11;;10411:9;:53::i;:::-;10404:60;;;;;10365:110;10572:7;:14;10485:17;;;;10589:24;10572:41;10644:13;10628:29;;10624:395;;;10700:13;10684;:29;10673:40;;10727:24;10768:15;10754:11;;:29;10727:56;;10809:53;10819:8;10829:16;10847:14;;10809:9;:53::i;:::-;10797:65;;10899:9;10881:14;:27;10877:132;;10935:59;10945:14;10961;;10977:16;10935:9;:59::i;:::-;10928:66;;;;;;;;;10877:132;10624:395;;11047:9;11029:27;;;;11084:57;11094:14;11110:13;11125:15;11084:9;:57::i;:::-;11073:68;;;;10122:1026;-1:-1:-1;;;;;;10122:1026:16:o;2079:49::-;;;:::o;1381:137:10:-;897:9;:7;:9::i;:::-;889:18;;;;;;1479:1:::1;1463:6:::0;;1442:40:::1;::::0;-1:-1:-1;;;;;1463:6:10;;::::1;::::0;1442:40:::1;::::0;1479:1;;1442:40:::1;1509:1;1492:19:::0;;-1:-1:-1;;;;;;1492:19:10::1;::::0;;1381:137::o;20911:691:16:-;-1:-1:-1;;;;;21020:17:16;;20977:7;21020:17;;;:8;:17;;;;;21080;;21076:61;;21125:1;21118:8;;;;;21076:61;21147:19;21169:6;-1:-1:-1;;;;;21169:23:16;;21193:7;21169:32;;;;;;;;;;;;;-1:-1:-1;;;;;21169:32:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;21169:32:16;21251:18;;;;21169:32;;-1:-1:-1;21211:21:16;;21235:35;;21169:32;;21235:15;:35::i;:::-;21211:59;-1:-1:-1;21345:18:16;21341:57;;21386:1;21379:8;;;;;;;21341:57;21408:17;21428:43;21438:13;21453:4;:17;;;21428:9;:43::i;:::-;21497:17;;21408:63;;-1:-1:-1;21485:29:16;;21481:89;;;-1:-1:-1;21542:17:16;;21481:89;21586:9;20911:691;-1:-1:-1;;;;;20911:691:16:o;2190:26::-;;;;:::o;2222:27::-;;;;:::o;1292:43::-;1332:3;1292:43;:::o;17862:288::-;17943:10;17913:14;17930:24;;;:12;:24;;;;;;17972:10;17964:47;;;;;-1:-1:-1;;;17964:47:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;18034:10;18048:1;18021:24;;;:12;:24;;;;;:28;18059;;18080:6;18059:20;:28::i;:::-;18102:41;;;;;;;;18124:10;;18102:41;;;;;;;;;;17862:288;:::o;19897:171::-;19949:4;19991:19;;19972:15;:38;;:89;;;;-1:-1:-1;20047:7:16;:14;20026:17;;:35;19972:89;19965:96;;19897:171;:::o;2255:25::-;;;;:::o;2134:49::-;;;:::o;693:77:10:-;731:7;757:6;-1:-1:-1;;;;;757:6:10;693:77;:::o;1013:90::-;1053:4;1090:6;-1:-1:-1;;;;;1090:6:10;1076:10;:20;;1013:90::o;12788:977:16:-;12854:19;;12836:15;:37;12828:114;;;;-1:-1:-1;;;12828:114:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12985:10;12952:21;12976:20;;;:8;:20;;;;;13014:17;;13006:50;;;;;-1:-1:-1;;;13006:50:16;;;;;;;;;;;;-1:-1:-1;;;13006:50:16;;;;;;;;;;;;;;;13075:12;;;;;;13074:13;13066:52;;;;;-1:-1:-1;;;13066:52:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;13148:17;;13128;13175:21;;13301:7;:14;13333:10;;;;-1:-1:-1;;13301:18:16;;;;13333:10;;;-1:-1:-1;;;;;13333:10:16;:23;;13329:192;;13372:18;13393:7;13401:9;13393:18;;;;;;;;;;;;;;;;;;13433:10;;;;13425:7;:19;;-1:-1:-1;;;;;13393:18:16;;;;-1:-1:-1;13393:18:16;;13425:7;;13393:18;13433:10;;-1:-1:-1;;;;;13433:10:16;;13425:19;;;;;;;;;;;;;;;;;;:32;;-1:-1:-1;;;;;;13425:32:16;-1:-1:-1;;;;;13425:32:16;;;;;;13500:10;;;;;13471:20;;;;;;:8;:20;;;;;:26;:39;;-1:-1:-1;;;;;;13471:39:16;13425:32;13500:10;;;;-1:-1:-1;;;;;13500:10:16;13471:39;;;;;;;;;13329:192;13530:7;:13;;;;;;;;;;;;;;;;-1:-1:-1;;13530:13:16;;;;;-1:-1:-1;;;;;;13530:13:16;;;;;;13570;13558:25;;13554:118;;;13616:14;;:45;;13647:13;13635:25;;13616:18;:45::i;:::-;13599:14;:62;13554:118;13681:31;:10;13702:9;13681:20;:31::i;:::-;13727;;;;;;;;13736:10;;13727:31;;;;;;;;;;12788:977;;;:::o;2327:29::-;;;;:::o;5997:562::-;6059:7;6095:13;6082:10;:26;6078:65;;;-1:-1:-1;6131:1:16;6124:8;;6078:65;6230:14;;6226:85;;6286:7;:14;6272:11;;6286:14;6272:28;;;;;6265:35;;;;6226:85;6417:7;:14;6403:11;;6537:14;;6353:13;6340:26;;;6434:24;6417:41;6403:55;;;;6502:50;;:30;6340:26;6403:55;6502:12;:30::i;:::-;:34;;:50::i;:::-;6475:24;:77;;5997:562;-1:-1:-1;;;;5997:562:16:o;2561:32::-;;;;:::o;18579:1254::-;18680:7;2749:19;;2730:15;:38;;2722:115;;;;-1:-1:-1;;;2722:115:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18728:7:::1;:14:::0;18707:17:::1;::::0;:35:::1;;18699:81;;;;-1:-1:-1::0;;;18699:81:16::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;18875:17;::::0;18906:14:::1;::::0;18902:173:::1;;18963:7;:14:::0;18949:11:::1;::::0;18981:24:::1;::::0;18963:14;;18949:28:::1;;;;;:56;;18941:87;;;::::0;;-1:-1:-1;;;18941:87:16;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;18941:87:16;;;;;;;;;;;;;::::1;;-1:-1:-1::0;19050:7:16::1;:14:::0;18902:173:::1;19212:7;:14:::0;19198:11:::1;::::0;19327:14:::1;::::0;19137:24:::1;19110;:51:::0;;::::1;::::0;19212:41:::1;19198:55:::0;;::::1;::::0;19085:22:::1;::::0;19308:56:::1;::::0;19198:55;;19308:34:::1;::::0;19110:51;;19308:18:::1;:34::i;:56::-;19292:13;:72;19263:101;;19376:254;19391:7;:14:::0;19383:22;::::1;:53:::0;::::1;;;;19421:15;19409:9;:27;19383:53;19376:254;;;19452:14;19469:7;19477:5;19469:14;;;;;;;;;::::0;;;::::1;::::0;;;;;::::1;::::0;-1:-1:-1;;;;;19469:14:16::1;19505:16:::0;;;:8:::1;:16:::0;;;;;;;:29;19469:14;;-1:-1:-1;19505:51:16;-1:-1:-1;19505:51:16::1;19497:101;;;;-1:-1:-1::0;;;19497:101:16::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1::0;19612:7:16::1;::::0;;::::1;::::0;19376:254:::1;;;19653:17;;19644:5;:26;19640:153;;19718:17;::::0;19691:52:::1;::::0;;;;;::::1;::::0;::::1;::::0;;;;;19706:10:::1;::::0;19691:52:::1;::::0;;;;;;;::::1;19757:17;:25:::0;;;19640:153:::1;-1:-1:-1::0;;19809:17:16::1;::::0;;18579:1254;-1:-1:-1;;;;18579:1254:16:o;1998:38::-;;;:::o;14624:3162::-;2749:19;;2730:15;:38;;2722:115;;;;-1:-1:-1;;;2722:115:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;14763:7:::1;:14:::0;14742:17:::1;::::0;:35:::1;;14734:81;;;;-1:-1:-1::0;;;14734:81:16::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;14843:17:::0;14885:10;14877:63:::1;;;;-1:-1:-1::0;;;14877:63:16::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;14980:11;::::0;14951:26:::1;::::0;14980:45:::1;::::0;15000:24:::1;14980:19;:45::i;:::-;15039:7;:14:::0;14951:74;;-1:-1:-1;15039:35:16;-1:-1:-1;15035:104:16::1;;;15090:18;:16;:18::i;:::-;15122:7;;;;15035:104;15149:22;15174:17;;15192:1;15174:20;;;;;;;;::::0;;::::1;::::0;;;::::1;;-1:-1:-1::0;;;;;15174:20:16::1;15204:14;15221:24:::0;;;:8:::1;:24:::0;;;;;;;:37;15174:20;;-1:-1:-1;15221:37:16;;-1:-1:-1;15359:1:16::1;15342:497;15366:6;15362:1;:10;15342:497;;;15393:14;15410:17;;15428:1;15410:20;;;;;;;-1:-1:-1::0;;;;;15410:20:16::1;::::0;;::::1;::::0;;;::::1;;::::0;::::1;15444;15467:16:::0;;;:8:::1;:16:::0;;;;;;;:29;15410:20;;-1:-1:-1;15467:29:16;15518:23;::::1;::::0;::::1;::::0;-1:-1:-1;15518:43:16;::::1;;;;15560:1;15545:12;:16;15518:43;15510:100;;;;-1:-1:-1::0;;;15510:100:16::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;15637:12;15628:6;:21;15624:168;;;15678:12;15669:21;;15624:168;;;15724:12;15715:6;:21;15711:81;;;15765:12;15756:21;;15711:81;-1:-1:-1::0;15822:6:16;-1:-1:-1;15374:3:16::1;;15342:497;;;;15849:24;15890:6;15876:21;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;::::0;-1:-1:-1;15876:21:16::1;;15849:48;;15968:6;15958;:16;15954:438;;15995:9;15990:392;16014:6;16010:1;:10;15990:392;;;16045:14;16062:17;;16080:1;16062:20;;;;;;;;;;;;;-1:-1:-1::0;;;;;16062:20:16::1;16045:37;;16100:21;16124:8;:16;16133:6;-1:-1:-1::0;;;;;16124:16:16::1;-1:-1:-1::0;;;;;16124:16:16::1;;;;;;;;;;;;16100:40;;16182:6;16162:4;:17;;;:26;16158:210;;;16245:6;16225:4;:17;;;:26;16212:7;16220:1;16212:10;;;;;;;;;::::0;;::::1;::::0;;;;;:39;16273:26;;;16339:10;;:7;;16347:1;;16339:10;::::1;;;;;;;;;;;16321:14;;:28;;;;;;;;;;;16158:210;-1:-1:-1::0;;16022:3:16::1;;15990:392;;;;15954:438;16432:24;16410:19;16422:6;16410:11;:19::i;:::-;:46;16402:112;;;;-1:-1:-1::0;;;16402:112:16::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;16868:7;:14:::0;16854:11:::1;::::0;16739:24:::1;16712;:51:::0;;::::1;::::0;16804:13:::1;16795:22:::0;::::1;::::0;16868:41;::::1;16854:55:::0;::::1;16687:22;16939:194;17106:26;16712:51:::0;17125:6;17106:18:::1;:26::i;:::-;17087:16;:45;16939:106;17010:34;17029:14;;17010;:18;;:34;;;;:::i;:::-;16939:33;:11:::0;16955:16;16939:15:::1;:33::i;:::-;:70:::0;::::1;:106::i;:::-;:147:::0;::::1;:194::i;:::-;16919:214:::0;-1:-1:-1;17143:17:16::1;17163:21;:6:::0;16919:214;17163:10:::1;:21::i;:::-;17194:14;:36:::0;;17212:18;;::::1;17194:36:::0;::::1;::::0;;17143:41;-1:-1:-1;17194:14:16::1;17240:233;17264:6;17260:1;:10;17240:233;;;17291:14;17308:17;;17326:1;17308:20;;;;;;;;;;;;;-1:-1:-1::0;;;;;17308:20:16::1;17291:37;;17342:21;17366:8;:16;17375:6;-1:-1:-1::0;;;;;17366:16:16::1;-1:-1:-1::0;;;;;17366:16:16::1;;;;;;;;;;;;17342:40;;17410:9;17396:7;17404:1;17396:10;;;;;;;;;::::0;;::::1;::::0;;;;;:23;;;;::::1;::::0;;17433:29;;;-1:-1:-1;17272:3:16::1;;17240:233;;;-1:-1:-1::0;17533:1:16::1;17513:17;:21:::0;;;17570:209:::1;17594:6;17590:1;:10;17570:209;;;17621:14;17638:17;;17656:1;17638:20;;;;;;;;;;;;;-1:-1:-1::0;;;;;17638:20:16::1;17621:37;;17696:7;17704:1;17696:10;;;;;;;;;::::0;;::::1;::::0;;;;;;;-1:-1:-1;;;;;17672:20:16;::::1;;::::0;;;:12:::1;:20:::0;;;;;;;:34;;;;::::1;::::0;;17757:10;;17737::::1;::::0;17725:43:::1;::::0;17757:7;;17765:1;;17757:10;::::1;;;;;;;;;;;17725:43;;;;;;;;;;;;;;;;;;-1:-1:-1::0;17602:3:16::1;;17570:209;;;;2847:1;;;;;;;;;;;;14624:3162:::0;;:::o;2466:24::-;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;;;2466:24:16;;-1:-1:-1;2466:24:16;:::o;5535:293::-;5618:10;;5600:15;:28;5592:87;;;;-1:-1:-1;;;5592:87:16;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5689:57;-1:-1:-1;;;;;5689:5:16;:22;5712:10;5732:4;5739:6;5689:22;:57::i;:::-;5756:11;:21;;;;;;5792:29;;;;;;;;5802:10;;5792:29;;;;;;;;;;5535:293;:::o;1442:37::-;;;:::o;1689:107:10:-;897:9;:7;:9::i;:::-;889:18;;;;;;1761:28:::1;1780:8;1761:18;:28::i;:::-;1689:107:::0;:::o;1400:36:16:-;;;:::o;13884:152::-;897:9:10;:7;:9::i;:::-;889:18;;;;;;13942:21:16::1;:19;:21::i;:::-;13941:22;13933:68;;;;-1:-1:-1::0;;;13933:68:16::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;14011:18;:16;:18::i;:::-;13884:152::o:0;1245:145:12:-;1303:7;1335:1;1330;:6;;1322:15;;;;;;-1:-1:-1;1359:5:12;;;1245:145::o;7361:1926:16:-;7499:7;:14;7616;;7455:7;;7516:13;7499:30;;7612:109;;7658:52;7668:13;7683:11;;7696:13;7658:9;:52::i;7612:109::-;7731:15;7760:16;8069:13;8046:19;8030:13;:35;:52;8026:837;;8108:13;8098:23;;8026:837;;;8408:13;8385:19;:36;8381:482;;-1:-1:-1;8448:13:16;8381:482;;;-1:-1:-1;;8791:13:16;8753:35;;;:51;8828:24;;;;8381:482;8899:7;:14;8916:24;8899:41;8873:23;8980:11;;8976:99;;9014:50;9024:7;9033:15;9050:13;9014:9;:50::i;:::-;9007:57;;8976:99;9089:12;;9085:174;;9117:24;9158:15;9144:11;;:29;9117:56;;9195:53;9205:8;9215:16;9233:14;;9195:9;:53::i;:::-;9187:61;;;;9085:174;;9276:4;7361:1926;-1:-1:-1;;;;;;;7361:1926:16:o;1473:145:12:-;1531:7;1562:5;;;1585:6;;;;1577:15;;;;;;1610:1;1473:145;-1:-1:-1;;;1473:145:12:o;2285:366:1:-;2399:6;2374:21;:31;;2366:73;;;;;-1:-1:-1;;;2366:73:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;2523:33;;2505:12;;-1:-1:-1;;;;;2523:14:1;;;2545:6;;2505:12;2523:33;2505:12;2523:33;2545:6;2523:14;:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2504:52;;;2574:7;2566:78;;;;-1:-1:-1;;;2566:78:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2285:366;;;:::o;9700:241:16:-;9816:7;9846:88;9901:32;:12;1332:3;9901:16;:32::i;:::-;9846:50;9881:14;9846:30;:14;9865:10;9846:18;:30::i;:::-;:34;;:50::i;:88::-;9839:95;9700:241;-1:-1:-1;;;;9700:241:16:o;6674:237::-;6786:7;6816:88;6873:30;:10;6888:14;6873;:30::i;:::-;6816:48;1332:3;6816:28;:10;6831:12;265:421:12;323:7;563:6;559:45;;-1:-1:-1;592:1:12;585:8;;559:45;626:5;;;630:1;626;:5;:1;649:5;;;;;:10;641:19;;;;;816:296;874:7;971:1;967;:5;959:14;;;;;;983:9;999:1;995;:5;;;;;;;816:296;-1:-1:-1;;;;816:296:12:o;472:113:0:-;534:7;577:1;572;561:8;:1;577;561:5;:8::i;:::-;:12;560:18;;;;;;;472:113;-1:-1:-1;;;472:113:0:o;14155:259:16:-;14217:1;14202:12;:16;;;14228:10;:14;-1:-1:-1;;14252:19:16;:36;14332:40;14351:7;:5;:7::i;:::-;14360:11;;-1:-1:-1;;;;;14332:5:16;:18;;:40;:18;:40::i;:::-;14387:20;;14396:10;;14387:20;;;;;14155:259::o;564:151:11:-;672:5;-1:-1:-1;;;;;672:18:11;;691:4;697:2;701:5;672:35;;;;;;;;;;;;;-1:-1:-1;;;;;672:35:11;;;;;;-1:-1:-1;;;;;672:35:11;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;672:35:11;664:44;;;;;;564:151;;;;:::o;1940:183:10:-;-1:-1:-1;;;;;2013:22:10;;2005:31;;;;;;2072:6;;;2051:38;;-1:-1:-1;;;;;2051:38:10;;;;2072:6;;;2051:38;;;2099:6;:17;;-1:-1:-1;;;;;;2099:17:10;-1:-1:-1;;;;;2099:17:10;;;;;;;;;;1940:183::o;435:123:11:-;525:5;-1:-1:-1;;;;;525:14:11;;540:2;544:5;525:25;;;;;;;;;;;;;-1:-1:-1;;;;;525:25:11;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;525:25:11;517:34;;;;
Swarm Source
ipfs://9391d2edb42d3db3db71331f2d28e571cf258e989bdbfbfe3593d481730e68e8
Make sure to use the "Vote Down" button for any spammy posts, and the "Vote Up" for interesting conversations.
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.