diff --git a/contracts/sfc/Staker.sol b/contracts/sfc/Staker.sol index c26f0d0..3be11ce 100644 --- a/contracts/sfc/Staker.sol +++ b/contracts/sfc/Staker.sol @@ -71,6 +71,13 @@ contract Stakers is Ownable, StakersConstants { uint256 stakeTotalAmount; uint256 delegationsTotalAmount; uint256 totalSupply; + uint256 totalLockedAmount; + } + + struct LockedAmount { + uint256 fromEpoch; + uint256 startTime; + uint256 endTime; } uint256 private reserved1; @@ -140,6 +147,9 @@ contract Stakers is Ownable, StakersConstants { mapping(address => mapping(uint256 => WithdrawalRequest)) public withdrawalRequests; + uint256 public firstLockedUpEpoch; + mapping(uint256 => LockedAmount) public lockedStakes; // stakerID -> LockedAmount + /* Getters */ @@ -408,6 +418,17 @@ contract Stakers is Ownable, StakersConstants { return 0; // avoid division by zero } uint256 weightedTotalStake = stake.add((delegatedTotal.mul(commission)).div(RATIO_UNIT)); + + if (firstLockedUpEpoch > 0 && epoch >= firstLockedUpEpoch) { + if (lockedStakes[stakerID].fromEpoch >= epoch && epochSnapshots[epoch.sub(1)].endTime > lockedStakes[stakerID].endTime) { + uint256 totalLockedAmount = epochSnapshots[epoch].totalLockedAmount; + uint256 lockedReward = rawReward.mul(RATIO_UNIT - unlockedRatio()).div(RATIO_UNIT).mul(weightedTotalStake).div(totalLockedAmount); + rawReward = (rawReward.mul(unlockedRatio()).div(RATIO_UNIT)).add(lockedReward); + } else { + rawReward = rawReward.mul(unlockedRatio()).div(RATIO_UNIT); + } + } + return (rawReward.mul(weightedTotalStake)).div(totalStake); } @@ -584,6 +605,7 @@ contract Stakers is Ownable, StakersConstants { require(stakers[stakerID].stakeAmount != 0, "staker doesn't exist"); require(stakers[stakerID].deactivatedTime == 0, "staker is deactivated"); require(stakers[stakerID].paidUntilEpoch == currentSealedEpoch, "not all rewards claimed"); // for rewards burning + require(lockedStakes[stakerID].fromEpoch == 0 || lockedStakes[stakerID].endTime > block.timestamp, "stake is locked"); _mayBurnRewardsOnDeactivation(false, stakerID, stakerSfcAddr, stakers[stakerID].stakeAmount, stakers[stakerID].stakeAmount); @@ -601,6 +623,7 @@ contract Stakers is Ownable, StakersConstants { require(stakers[stakerID].stakeAmount != 0, "staker doesn't exist"); require(stakers[stakerID].deactivatedTime == 0, "staker is deactivated"); require(stakers[stakerID].paidUntilEpoch == currentSealedEpoch, "not all rewards claimed"); // for rewards burning + require(lockedStakes[stakerID].fromEpoch == 0 || lockedStakes[stakerID].endTime > block.timestamp, "stake is locked"); require(amount >= minStakeDecrease(), "too small amount"); // avoid confusing wrID and amount // don't allow to withdraw full as a request, because amount==0 originally meant "not existing" @@ -804,6 +827,27 @@ contract Stakers is Ownable, StakersConstants { emit UpdatedBaseRewardPerSec(value); } + function startLockedUp(uint256 epochNum) onlyOwner external { + if (epochNum > 0) { + firstLockedUpEpoch = epochNum; + } + } + + function lockUpStake(uint256 lockDuration) external { + uint256 stakerID = _sfcAddressToStakerID(msg.sender); + require(stakers[stakerID].stakeAmount != 0, "staker doesn't exist"); + require(stakers[stakerID].deactivatedTime == 0, "staker is deactivated"); + require(lockDuration >= 86400 * 14 && lockDuration <= 86400 * 365, "incorrect duration"); + require(lockedStakes[stakerID].endTime > block.timestamp.add(lockDuration), "stake already locked up"); + require(firstLockedUpEpoch == 0 || firstLockedUpEpoch > currentSealedEpoch, "feature was not activated"); +// require(stakers[stakerID].paidUntilEpoch == currentSealedEpoch, "not all rewards claimed"); // for rewards burning + LockedAmount memory lStake = LockedAmount( + currentSealedEpoch, + block.timestamp, + block.timestamp.add(lockDuration)); + lockedStakes[stakerID] = lStake; + } + event UpdatedDelegation(address indexed delegator, uint256 indexed oldStakerID, uint256 indexed newStakerID, uint256 amount); // syncDelegator updates the delegator data on node, if it differs for some reason diff --git a/contracts/sfc/StakerConstants.sol b/contracts/sfc/StakerConstants.sol index 169a01f..1d5d1ef 100644 --- a/contracts/sfc/StakerConstants.sol +++ b/contracts/sfc/StakerConstants.sol @@ -74,6 +74,13 @@ contract StakersConstants { return (30 * RATIO_UNIT) / 100; // 30% } + /** + * @dev The ratio of the reward rate at base rate (no lock), e.g., 30% + */ + function unlockedRatio() public pure returns (uint256) { + return (30 * RATIO_UNIT) / 100; // 30% + } + /** * @dev the period of time that stake is locked */