From 9b4aa51a89f3d66bbeb5a1c2ada4f73445f58005 Mon Sep 17 00:00:00 2001 From: szotov Date: Fri, 10 May 2019 19:14:57 +0300 Subject: [PATCH] Draft of the WorkLock implementation --- .../sol/source/contracts/StakingEscrow.sol | 70 +++++++++++++++++-- .../eth/sol/source/contracts/WorkLock.sol | 66 +++++++++++++++++ 2 files changed, 130 insertions(+), 6 deletions(-) create mode 100644 nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol diff --git a/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol b/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol index 6ee16dd4a..9889565d0 100644 --- a/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol +++ b/nucypher/blockchain/eth/sol/source/contracts/StakingEscrow.sol @@ -23,6 +23,14 @@ contract AdjudicatorInterface { } +/** +* @notice WorkLock interface +**/ +contract WorkLockInterface { + function escrow() public view returns (address); +} + + /** * @notice Contract holds and locks stakers tokens. * Each staker that locks their tokens will receive some compensation @@ -79,8 +87,10 @@ contract StakingEscrow is Issuer { address worker; // period when worker was set uint16 workerStartPeriod; - // downtime + // last confirmed active period uint16 lastActivePeriod; + bool measureWork; + uint256 workDone; Downtime[] pastDowntime; SubStakeInfo[] subStakes; } @@ -110,6 +120,7 @@ contract StakingEscrow is Issuer { uint256 public maxAllowableLockedTokens; PolicyManagerInterface public policyManager; AdjudicatorInterface public adjudicator; + WorkLockInterface public workLock; /** * @notice Constructor sets address of token contract and coefficients for mining @@ -181,6 +192,16 @@ contract StakingEscrow is Issuer { adjudicator = _adjudicator; } + /** + * @notice Set worklock address + **/ + function setWorkLock(WorkLockInterface _workLock) external onlyOwner { + // Two-part require... + require(address(workLock) == address(0) && // Can't workLock once it is set. + _workLock.escrow() == address(this)); // This is the escrow for the new workLock. + workLock = _workLock; + } + //------------------------Main getters------------------------ /** * @notice Get all tokens belonging to the staker @@ -351,9 +372,29 @@ contract StakingEscrow is Issuer { return workerToStaker[_worker]; } + /** + * @notice Get work that done by the staker + **/ + function getWorkDone(address _staker) public view returns (uint256) { + return stakerInfo[_staker].workDone; + } + //------------------------Main methods------------------------ /** - * @notice Set worker + * @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) public returns (uint256) { + require(msg.sender == address(workLock)); + MinerInfo storage info = stakerInfo[_staker]; + info.measureWork = _measureWork; + return info.workDone; + // TODO event + } + + /** @notice Set worker * @param _worker Worker address. Must be a real address, not a contract **/ function setWorker(address _worker) public onlyStaker { @@ -414,6 +455,7 @@ contract StakingEscrow is Issuer { * @param _values Amount of tokens to deposit for each staker * @param _periods Amount of periods during which tokens will be locked for each staker **/ + // TODO remove? function preDeposit(address[] memory _stakers, uint256[] memory _values, uint16[] memory _periods) public isInitialized { @@ -472,7 +514,7 @@ contract StakingEscrow is Issuer { payload := calldataload(0xA4) } payload = payload >> 8*(32 - payloadSize); - deposit(_from, _value, uint16(payload)); + deposit(_from, _from, _value, uint16(payload)); } /** @@ -481,7 +523,7 @@ contract StakingEscrow is Issuer { * @param _periods Amount of periods during which tokens will be locked **/ function deposit(uint256 _value, uint16 _periods) public { - deposit(msg.sender, _value, _periods); + deposit(msg.sender, msg.sender, _value, _periods); } /** @@ -490,7 +532,18 @@ contract StakingEscrow is Issuer { * @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) internal isInitialized { + function deposit(address _staker, uint256 _value, uint16 _periods) public { + deposit(_miner, msg.sender, _value, _periods); + } + + /** + * @notice Deposit tokens + * @param _staker Staker + * @param _payor Owner of tokens + * @param _value Amount of tokens to deposit + * @param _periods Amount of periods during which tokens will be locked + **/ + function deposit(address _staker, address _payor, uint256 _value, uint16 _periods) internal isInitialized { require(_value != 0); StakerInfo storage info = stakerInfo[_staker]; require(workerToStaker[_staker] == address(0) || workerToStaker[_staker] == info.worker, @@ -501,7 +554,7 @@ contract StakingEscrow is Issuer { policyManager.register(_staker, getCurrentPeriod()); } info.value = info.value.add(_value); - token.safeTransferFrom(_staker, address(this), _value); + token.safeTransferFrom(_payor, address(this), _value); lock(_staker, _value, _periods); emit Deposited(_staker, _value, _periods); } @@ -752,6 +805,9 @@ contract StakingEscrow is Issuer { } info.value = info.value.add(reward); + if (info.measureWork) { + info.workDone = info.workDone.add(reward); + } emit Mined(_staker, previousPeriod, reward); } @@ -1219,6 +1275,7 @@ contract StakingEscrow is Issuer { require(delegateGet(_testTarget, "maxAllowableLockedTokens()") == maxAllowableLockedTokens); require(address(delegateGet(_testTarget, "policyManager()")) == address(policyManager)); require(address(delegateGet(_testTarget, "adjudicator()")) == address(adjudicator)); + require(address(delegateGet(_testTarget, "workLock()")) == address(workLock)); require(delegateGet(_testTarget, "lockedPerPeriod(uint16)", bytes32(bytes2(RESERVED_PERIOD))) == lockedPerPeriod[RESERVED_PERIOD]); require(address(delegateGet(_testTarget, "workerToStaker(address)", bytes32(0))) == @@ -1241,6 +1298,7 @@ contract StakingEscrow is Issuer { infoToCheck.lastActivePeriod == info.lastActivePeriod && infoToCheck.worker == info.worker && infoToCheck.workerStartPeriod == info.workerStartPeriod); + // TODO check work require(delegateGet(_testTarget, "getPastDowntimeLength(address)", staker) == info.pastDowntime.length); diff --git a/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol b/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol new file mode 100644 index 000000000..57c26e275 --- /dev/null +++ b/nucypher/blockchain/eth/sol/source/contracts/WorkLock.sol @@ -0,0 +1,66 @@ +pragma solidity ^0.5.3; + + +import "zeppelin/math/SafeMath.sol"; +import "contracts/NuCypherToken.sol"; +import "contracts/MinersEscrow.sol"; + + +/** +* @notice The WorkLock distribution contract +**/ +contract WorkLock { + using SafeMath for uint256; + + // TODO events + + struct WorkInfo { + uint256 depositedETH; + uint256 workDone; + } + + NuCypherToken public token; + MinersEscrow public escrow; + // ETH -> NU + uint256 public depositRate; + // Work (reward in NU) -> ETH + uint256 public refundRate; + uint16 public lockedPeriods; + mapping(address => WorkInfo) workInfo; + + /** + * @notice Claim tokens by transferring ETH. Claimed tokens will be deposited and locked as stake + * in the MinersEscrow contract. + **/ + function claim() public payable returns (uint256 claimedTokens) { + claimedTokens = msg.value.mul(depositRate); + require(token.balanceOf(address(this)) >= claimedTokens, "Not enough tokens in the contract"); + WorkInfo storage info = workInfo[msg.sender]; + if (info.depositedETH == 0) { + info.workDone = escrow.setWorkMeasurement(msg.sender, true); + } + info.depositedETH = info.depositedETH.add(msg.value); + token.approve(address(escrow), claimedTokens); + escrow.deposit(msg.sender, claimedTokens, lockedPeriods); + } + + /** + * @notice Refund ETH for the work done + **/ + function refund() public { + WorkInfo storage info = workInfo[msg.sender]; + require(info.depositedETH > 0, "Nothing deposited"); + uint256 currentWork = escrow.getWorkDone(msg.sender); + uint256 workDone = currentWork.sub(info.workDone); + require(workDone > 0, "No work has been done."); + uint256 refundETH = workDone.div(refundRate); + if (refundETH > info.depositedETH) { + refundETH = info.depositedETH; + escrow.setWorkMeasurement(msg.sender, false); + } + info.depositedETH = info.depositedETH.sub(refundETH); + info.workDone = currentWork; + msg.sender.transfer(refundETH); + } + +}