From 0c6a7de0f31a16e42b3e0a64f4f3f1641edc2e38 Mon Sep 17 00:00:00 2001 From: Mike-CZ Date: Thu, 31 Oct 2024 09:50:28 +0100 Subject: [PATCH] Remove stake lock (#84) --- contracts/sfc/ConstantsManager.sol | 36 --- contracts/sfc/NetworkInitializer.sol | 3 - contracts/sfc/NodeDriver.sol | 24 +- contracts/sfc/NodeDriverAuth.sol | 24 +- contracts/sfc/NodeDriverI.sol | 12 +- contracts/sfc/SFC.sol | 23 +- contracts/sfc/SFCBase.sol | 55 ---- contracts/sfc/SFCI.sol | 54 +--- contracts/sfc/SFCLib.sol | 460 ++------------------------- contracts/sfc/SFCState.sol | 28 +- contracts/sfc/Updater.sol | 3 - contracts/test/UnitTestSFC.sol | 53 +-- 12 files changed, 36 insertions(+), 739 deletions(-) diff --git a/contracts/sfc/ConstantsManager.sol b/contracts/sfc/ConstantsManager.sol index b5283ea..42c50c0 100644 --- a/contracts/sfc/ConstantsManager.sol +++ b/contracts/sfc/ConstantsManager.sol @@ -15,12 +15,6 @@ contract ConstantsManager is Ownable { uint256 public burntFeeShare; // The percentage of fees to transfer to treasury address, e.g., 10% uint256 public treasuryFeeShare; - // The ratio of the reward rate at base rate (no lock), e.g., 30% - uint256 public unlockedRewardRatio; - // The minimum duration of a stake/delegation lockup, e.g. 2 weeks - uint256 public minLockupDuration; - // The maximum duration of a stake/delegation lockup, e.g. 1 year - uint256 public maxLockupDuration; // the number of epochs that undelegated stake is locked for uint256 public withdrawalPeriodEpochs; // the number of seconds that undelegated stake is locked for @@ -87,36 +81,6 @@ contract ConstantsManager is Ownable { treasuryFeeShare = v; } - function updateUnlockedRewardRatio(uint256 v) external virtual onlyOwner { - if (v < (5 * Decimal.unit()) / 100) { - revert ValueTooSmall(); - } - if (v > Decimal.unit() / 2) { - revert ValueTooLarge(); - } - unlockedRewardRatio = v; - } - - function updateMinLockupDuration(uint256 v) external virtual onlyOwner { - if (v < 86400) { - revert ValueTooSmall(); - } - if (v > 86400 * 30) { - revert ValueTooLarge(); - } - minLockupDuration = v; - } - - function updateMaxLockupDuration(uint256 v) external virtual onlyOwner { - if (v < 86400 * 30) { - revert ValueTooSmall(); - } - if (v > 86400 * 1460) { - revert ValueTooLarge(); - } - maxLockupDuration = v; - } - function updateWithdrawalPeriodEpochs(uint256 v) external virtual onlyOwner { if (v < 2) { revert ValueTooSmall(); diff --git a/contracts/sfc/NetworkInitializer.sol b/contracts/sfc/NetworkInitializer.sol index 51d6b5a..d205dd4 100644 --- a/contracts/sfc/NetworkInitializer.sol +++ b/contracts/sfc/NetworkInitializer.sol @@ -28,9 +28,6 @@ contract NetworkInitializer { consts.updateValidatorCommission((15 * Decimal.unit()) / 100); consts.updateBurntFeeShare((20 * Decimal.unit()) / 100); consts.updateTreasuryFeeShare((10 * Decimal.unit()) / 100); - consts.updateUnlockedRewardRatio((30 * Decimal.unit()) / 100); - consts.updateMinLockupDuration(86400 * 14); - consts.updateMaxLockupDuration(86400 * 365); consts.updateWithdrawalPeriodEpochs(3); consts.updateWithdrawalPeriodTime(60 * 60 * 24 * 7); consts.updateBaseRewardPerSecond(2668658453701531600); diff --git a/contracts/sfc/NodeDriver.sol b/contracts/sfc/NodeDriver.sol index c58e44c..ad15cae 100644 --- a/contracts/sfc/NodeDriver.sol +++ b/contracts/sfc/NodeDriver.sol @@ -110,28 +110,8 @@ contract NodeDriver is Initializable { ); } - function setGenesisDelegation( - address delegator, - uint256 toValidatorID, - uint256 stake, - uint256 lockedStake, - uint256 lockupFromEpoch, - uint256 lockupEndTime, - uint256 lockupDuration, - uint256 earlyUnlockPenalty, - uint256 rewards - ) external onlyNode { - backend.setGenesisDelegation( - delegator, - toValidatorID, - stake, - lockedStake, - lockupFromEpoch, - lockupEndTime, - lockupDuration, - earlyUnlockPenalty, - rewards - ); + function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external onlyNode { + backend.setGenesisDelegation(delegator, toValidatorID, stake); } function deactivateValidator(uint256 validatorID, uint256 status) external onlyNode { diff --git a/contracts/sfc/NodeDriverAuth.sol b/contracts/sfc/NodeDriverAuth.sol index 2801fff..444aafe 100644 --- a/contracts/sfc/NodeDriverAuth.sol +++ b/contracts/sfc/NodeDriverAuth.sol @@ -141,28 +141,8 @@ contract NodeDriverAuth is Initializable, Ownable { ); } - function setGenesisDelegation( - address delegator, - uint256 toValidatorID, - uint256 stake, - uint256 lockedStake, - uint256 lockupFromEpoch, - uint256 lockupEndTime, - uint256 lockupDuration, - uint256 earlyUnlockPenalty, - uint256 rewards - ) external onlyDriver { - sfc.setGenesisDelegation( - delegator, - toValidatorID, - stake, - lockedStake, - lockupFromEpoch, - lockupEndTime, - lockupDuration, - earlyUnlockPenalty, - rewards - ); + function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external onlyDriver { + sfc.setGenesisDelegation(delegator, toValidatorID, stake); } function deactivateValidator(uint256 validatorID, uint256 status) external onlyDriver { diff --git a/contracts/sfc/NodeDriverI.sol b/contracts/sfc/NodeDriverI.sol index 987eeb1..fdabbec 100644 --- a/contracts/sfc/NodeDriverI.sol +++ b/contracts/sfc/NodeDriverI.sol @@ -13,17 +13,7 @@ interface NodeDriverI { uint256 deactivatedTime ) external; - function setGenesisDelegation( - address delegator, - uint256 toValidatorID, - uint256 stake, - uint256 lockedStake, - uint256 lockupFromEpoch, - uint256 lockupEndTime, - uint256 lockupDuration, - uint256 earlyUnlockPenalty, - uint256 rewards - ) external; + function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external; function deactivateValidator(uint256 validatorID, uint256 status) external; diff --git a/contracts/sfc/SFC.sol b/contracts/sfc/SFC.sol index 0135406..6255c0f 100644 --- a/contracts/sfc/SFC.sol +++ b/contracts/sfc/SFC.sol @@ -87,8 +87,7 @@ contract SFC is SFCBase, Version { } function rewardsStash(address delegator, uint256 validatorID) public view returns (uint256) { - Rewards memory stash = _rewardsStash[delegator][validatorID]; - return stash.lockupBaseReward + stash.lockupExtraReward + stash.unlockedReward; + return _rewardsStash[delegator][validatorID]; } /* @@ -295,25 +294,9 @@ contract SFC is SFCBase, Version { uint256 commissionRewardFull = _calcValidatorCommission(rawReward, c.validatorCommission()); uint256 selfStake = getStake[validatorAddr][validatorID]; if (selfStake != 0) { - uint256 lCommissionRewardFull = (commissionRewardFull * getLockedStake(validatorAddr, validatorID)) / - selfStake; - uint256 uCommissionRewardFull = commissionRewardFull - lCommissionRewardFull; - Rewards memory lCommissionReward = _scaleLockupReward( - lCommissionRewardFull, - getLockupInfo[validatorAddr][validatorID].duration - ); - Rewards memory uCommissionReward = _scaleLockupReward(uCommissionRewardFull, 0); - _rewardsStash[validatorAddr][validatorID] = sumRewards( - _rewardsStash[validatorAddr][validatorID], - lCommissionReward, - uCommissionReward - ); - getStashedLockupRewards[validatorAddr][validatorID] = sumRewards( - getStashedLockupRewards[validatorAddr][validatorID], - lCommissionReward, - uCommissionReward - ); + _rewardsStash[validatorAddr][validatorID] += commissionRewardFull; } + // accounting reward per token for delegators uint256 delegatorsReward = rawReward - commissionRewardFull; // note: use latest stake for the sake of rewards distribution accuracy, not snapshot.receivedStake diff --git a/contracts/sfc/SFCBase.sol b/contracts/sfc/SFCBase.sol index 75016da..c58ce64 100644 --- a/contracts/sfc/SFCBase.sol +++ b/contracts/sfc/SFCBase.sol @@ -60,20 +60,10 @@ contract SFCBase is SFCState { error GovVotesRecountFailed(); // staking - error LockedStakeGreaterThanTotalStake(); error InsufficientSelfStake(); - error NotEnoughUnlockedStake(); - error NotEnoughLockedStake(); error NotEnoughTimePassed(); error NotEnoughEpochsPassed(); error StakeIsFullySlashed(); - error IncorrectDuration(); - error ValidatorLockupTooShort(); - error TooManyReLocks(); - error TooFrequentReLocks(); - error LockupDurationDecreased(); - error AlreadyLockedUp(); - error NotLockedUp(); // stashing error NothingToStash(); @@ -132,37 +122,6 @@ contract SFCBase is SFCState { totalSupply = totalSupply + amount; } - function sumRewards(Rewards memory a, Rewards memory b) internal pure returns (Rewards memory) { - return - Rewards( - a.lockupExtraReward + b.lockupExtraReward, - a.lockupBaseReward + b.lockupBaseReward, - a.unlockedReward + b.unlockedReward - ); - } - - function sumRewards(Rewards memory a, Rewards memory b, Rewards memory c) internal pure returns (Rewards memory) { - return sumRewards(sumRewards(a, b), c); - } - - function _scaleLockupReward( - uint256 fullReward, - uint256 lockupDuration - ) internal view returns (Rewards memory reward) { - reward = Rewards(0, 0, 0); - uint256 unlockedRewardRatio = c.unlockedRewardRatio(); - if (lockupDuration != 0) { - uint256 maxLockupExtraRatio = Decimal.unit() - unlockedRewardRatio; - uint256 lockupExtraRatio = (maxLockupExtraRatio * lockupDuration) / c.maxLockupDuration(); - uint256 totalScaledReward = (fullReward * (unlockedRewardRatio + lockupExtraRatio)) / Decimal.unit(); - reward.lockupBaseReward = (fullReward * unlockedRewardRatio) / Decimal.unit(); - reward.lockupExtraReward = totalScaledReward - reward.lockupBaseReward; - } else { - reward.unlockedReward = (fullReward * unlockedRewardRatio) / Decimal.unit(); - } - return reward; - } - function _recountVotes(address delegator, address validatorAuth, bool strict) internal { if (voteBookAddress != address(0)) { // Don't allow recountVotes to use up all the gas @@ -220,20 +179,6 @@ contract SFCBase is SFCState { return (rawReward * commission) / Decimal.unit(); } - function getLockedStake(address delegator, uint256 toValidatorID) public view returns (uint256) { - if (!isLockedUp(delegator, toValidatorID)) { - return 0; - } - return getLockupInfo[delegator][toValidatorID].lockedStake; - } - - function isLockedUp(address delegator, uint256 toValidatorID) public view returns (bool) { - return - getLockupInfo[delegator][toValidatorID].endTime != 0 && - getLockupInfo[delegator][toValidatorID].lockedStake != 0 && - _now() <= getLockupInfo[delegator][toValidatorID].endTime; - } - function _now() internal view virtual returns (uint256) { return block.timestamp; } diff --git a/contracts/sfc/SFCI.sol b/contracts/sfc/SFCI.sol index 205fff2..39bd84c 100644 --- a/contracts/sfc/SFCI.sol +++ b/contracts/sfc/SFCI.sol @@ -11,23 +11,9 @@ interface SFCI { event Delegated(address indexed delegator, uint256 indexed toValidatorID, uint256 amount); event Undelegated(address indexed delegator, uint256 indexed toValidatorID, uint256 indexed wrID, uint256 amount); event Withdrawn(address indexed delegator, uint256 indexed toValidatorID, uint256 indexed wrID, uint256 amount); - event ClaimedRewards( - address indexed delegator, - uint256 indexed toValidatorID, - uint256 lockupExtraReward, - uint256 lockupBaseReward, - uint256 unlockedReward - ); - event RestakedRewards( - address indexed delegator, - uint256 indexed toValidatorID, - uint256 lockupExtraReward, - uint256 lockupBaseReward, - uint256 unlockedReward - ); + event ClaimedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards); + event RestakedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards); event BurntFTM(uint256 amount); - event LockedUpStake(address indexed delegator, uint256 indexed validatorID, uint256 duration, uint256 amount); - event UnlockedStake(address indexed delegator, uint256 indexed validatorID, uint256 amount, uint256 penalty); event UpdatedSlashingRefundRatio(uint256 indexed validatorID, uint256 refundRatio); event RefundedSlashedLegacyDelegation(address indexed delegator, uint256 indexed validatorID, uint256 amount); @@ -53,18 +39,8 @@ interface SFCI { uint256 totalSupply ); - function getLockupInfo( - address, - uint256 - ) external view returns (uint256 lockedStake, uint256 fromEpoch, uint256 endTime, uint256 duration); - function getStake(address, uint256) external view returns (uint256); - function getStashedLockupRewards( - address, - uint256 - ) external view returns (uint256 lockupExtraReward, uint256 lockupBaseReward, uint256 unlockedReward); - function getValidator( uint256 ) @@ -106,8 +82,6 @@ interface SFCI { function totalActiveStake() external view returns (uint256); - function totalSlashedStake() external view returns (uint256); - function totalStake() external view returns (uint256); function totalSupply() external view returns (uint256); @@ -142,8 +116,6 @@ interface SFCI { function rewardsStash(address delegator, uint256 validatorID) external view returns (uint256); - function getLockedStake(address delegator, uint256 toValidatorID) external view returns (uint256); - function createValidator(bytes calldata pubkey) external payable; function getSelfStake(uint256 validatorID) external view returns (uint256); @@ -186,16 +158,6 @@ interface SFCI { function sealEpochValidators(uint256[] calldata nextValidatorIDs) external; - function isLockedUp(address delegator, uint256 toValidatorID) external view returns (bool); - - function getUnlockedStake(address delegator, uint256 toValidatorID) external view returns (uint256); - - function lockStake(uint256 toValidatorID, uint256 lockupDuration, uint256 amount) external; - - function relockStake(uint256 toValidatorID, uint256 lockupDuration, uint256 amount) external; - - function unlockStake(uint256 toValidatorID, uint256 amount) external returns (uint256); - function initialize( uint256 sealedEpoch, uint256 _totalSupply, @@ -216,17 +178,7 @@ interface SFCI { uint256 deactivatedTime ) external; - function setGenesisDelegation( - address delegator, - uint256 toValidatorID, - uint256 stake, - uint256 lockedStake, - uint256 lockupFromEpoch, - uint256 lockupEndTime, - uint256 lockupDuration, - uint256 earlyUnlockPenalty, - uint256 rewards - ) external; + function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external; function updateVoteBookAddress(address v) external; diff --git a/contracts/sfc/SFCLib.sol b/contracts/sfc/SFCLib.sol index 33f660c..c11ca45 100644 --- a/contracts/sfc/SFCLib.sol +++ b/contracts/sfc/SFCLib.sol @@ -14,23 +14,9 @@ contract SFCLib is SFCBase { event Delegated(address indexed delegator, uint256 indexed toValidatorID, uint256 amount); event Undelegated(address indexed delegator, uint256 indexed toValidatorID, uint256 indexed wrID, uint256 amount); event Withdrawn(address indexed delegator, uint256 indexed toValidatorID, uint256 indexed wrID, uint256 amount); - event ClaimedRewards( - address indexed delegator, - uint256 indexed toValidatorID, - uint256 lockupExtraReward, - uint256 lockupBaseReward, - uint256 unlockedReward - ); - event RestakedRewards( - address indexed delegator, - uint256 indexed toValidatorID, - uint256 lockupExtraReward, - uint256 lockupBaseReward, - uint256 unlockedReward - ); + event ClaimedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards); + event RestakedRewards(address indexed delegator, uint256 indexed toValidatorID, uint256 rewards); event BurntFTM(uint256 amount); - event LockedUpStake(address indexed delegator, uint256 indexed validatorID, uint256 duration, uint256 amount); - event UnlockedStake(address indexed delegator, uint256 indexed validatorID, uint256 amount, uint256 penalty); event UpdatedSlashingRefundRatio(uint256 indexed validatorID, uint256 refundRatio); event RefundedSlashedLegacyDelegation(address indexed delegator, uint256 indexed validatorID, uint256 amount); @@ -63,32 +49,9 @@ contract SFCLib is SFCBase { } } - function setGenesisDelegation( - address delegator, - uint256 toValidatorID, - uint256 stake, - uint256 lockedStake, - uint256 lockupFromEpoch, - uint256 lockupEndTime, - uint256 lockupDuration, - uint256 earlyUnlockPenalty, - uint256 rewards - ) external onlyDriver { + function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external onlyDriver { _rawDelegate(delegator, toValidatorID, stake, false); - _rewardsStash[delegator][toValidatorID].unlockedReward = rewards; _mintNativeToken(stake); - if (lockedStake != 0) { - if (lockedStake > stake) { - revert LockedStakeGreaterThanTotalStake(); - } - LockedDelegation storage ld = getLockupInfo[delegator][toValidatorID]; - ld.lockedStake = lockedStake; - ld.fromEpoch = lockupFromEpoch; - ld.endTime = lockupEndTime; - ld.duration = lockupDuration; - getStashedLockupRewards[delegator][toValidatorID].lockupExtraReward = earlyUnlockPenalty; - emit LockedUpStake(delegator, toValidatorID, lockupDuration, lockedStake); - } } /* @@ -248,10 +211,6 @@ contract SFCLib is SFCBase { revert ZeroAmount(); } - if (amount > getUnlockedStake(delegator, toValidatorID)) { - revert NotEnoughUnlockedStake(); - } - if (getWithdrawalRequest[delegator][toValidatorID][wrID].amount != 0) { revert RequestExists(); } @@ -354,60 +313,12 @@ contract SFCLib is SFCBase { return currentSealedEpoch; } - // find highest epoch such that _isLockedUpAtEpoch returns true (using binary search) - function _highestLockupEpoch(address delegator, uint256 validatorID) internal view returns (uint256) { - uint256 fromEpoch = getLockupInfo[delegator][validatorID].fromEpoch; - uint256 r = currentSealedEpoch; - if (_isLockedUpAtEpoch(delegator, validatorID, r)) { - return r; - } - if (!_isLockedUpAtEpoch(delegator, validatorID, fromEpoch)) { - return 0; - } - if (fromEpoch > r) { - return 0; - } - while (fromEpoch < r) { - uint256 m = (fromEpoch + r) / 2; - if (_isLockedUpAtEpoch(delegator, validatorID, m)) { - fromEpoch = m + 1; - } else { - r = m; - } - } - if (r == 0) { - return 0; - } - return r - 1; - } - - function _newRewards(address delegator, uint256 toValidatorID) internal view returns (Rewards memory) { + function _newRewards(address delegator, uint256 toValidatorID) internal view returns (uint256) { uint256 stashedUntil = stashedRewardsUntilEpoch[delegator][toValidatorID]; uint256 payableUntil = _highestPayableEpoch(toValidatorID); - uint256 lockedUntil = _highestLockupEpoch(delegator, toValidatorID); - if (lockedUntil > payableUntil) { - lockedUntil = payableUntil; - } - if (lockedUntil < stashedUntil) { - lockedUntil = stashedUntil; - } - - LockedDelegation storage ld = getLockupInfo[delegator][toValidatorID]; uint256 wholeStake = getStake[delegator][toValidatorID]; - uint256 unlockedStake = wholeStake - ld.lockedStake; - uint256 fullReward; - - // count reward for locked stake during lockup epochs - fullReward = _newRewardsOf(ld.lockedStake, toValidatorID, stashedUntil, lockedUntil); - Rewards memory plReward = _scaleLockupReward(fullReward, ld.duration); - // count reward for unlocked stake during lockup epochs - fullReward = _newRewardsOf(unlockedStake, toValidatorID, stashedUntil, lockedUntil); - Rewards memory puReward = _scaleLockupReward(fullReward, 0); - // count lockup reward for unlocked stake during unlocked epochs - fullReward = _newRewardsOf(wholeStake, toValidatorID, lockedUntil, payableUntil); - Rewards memory wuReward = _scaleLockupReward(fullReward, 0); - - return sumRewards(plReward, puReward, wuReward); + uint256 fullReward = _newRewardsOf(wholeStake, toValidatorID, stashedUntil, payableUntil); + return fullReward; } function _newRewardsOf( @@ -424,14 +335,9 @@ contract SFCLib is SFCBase { return ((currentRate - stashedRate) * stakeAmount) / Decimal.unit(); } - function _pendingRewards(address delegator, uint256 toValidatorID) internal view returns (Rewards memory) { - Rewards memory reward = _newRewards(delegator, toValidatorID); - return sumRewards(_rewardsStash[delegator][toValidatorID], reward); - } - function pendingRewards(address delegator, uint256 toValidatorID) public view returns (uint256) { - Rewards memory reward = _pendingRewards(delegator, toValidatorID); - return reward.unlockedReward + reward.lockupBaseReward + reward.lockupExtraReward; + uint256 reward = _newRewards(delegator, toValidatorID); + return _rewardsStash[delegator][toValidatorID] + reward; } function stashRewards(address delegator, uint256 toValidatorID) external { @@ -441,72 +347,42 @@ contract SFCLib is SFCBase { } function _stashRewards(address delegator, uint256 toValidatorID) internal returns (bool updated) { - Rewards memory nonStashedReward = _newRewards(delegator, toValidatorID); + uint256 nonStashedReward = _newRewards(delegator, toValidatorID); stashedRewardsUntilEpoch[delegator][toValidatorID] = _highestPayableEpoch(toValidatorID); - _rewardsStash[delegator][toValidatorID] = sumRewards(_rewardsStash[delegator][toValidatorID], nonStashedReward); - getStashedLockupRewards[delegator][toValidatorID] = sumRewards( - getStashedLockupRewards[delegator][toValidatorID], - nonStashedReward - ); - if (!isLockedUp(delegator, toValidatorID)) { - delete getLockupInfo[delegator][toValidatorID]; - delete getStashedLockupRewards[delegator][toValidatorID]; - } - _truncateLegacyPenalty(delegator, toValidatorID); - return - nonStashedReward.lockupBaseReward != 0 || - nonStashedReward.lockupExtraReward != 0 || - nonStashedReward.unlockedReward != 0; + _rewardsStash[delegator][toValidatorID] += nonStashedReward; + return nonStashedReward != 0; } - function _claimRewards(address delegator, uint256 toValidatorID) internal returns (Rewards memory rewards) { + function _claimRewards(address delegator, uint256 toValidatorID) internal returns (uint256) { _stashRewards(delegator, toValidatorID); - rewards = _rewardsStash[delegator][toValidatorID]; - uint256 totalReward = rewards.unlockedReward + rewards.lockupBaseReward + rewards.lockupExtraReward; - if (totalReward == 0) { + uint256 rewards = _rewardsStash[delegator][toValidatorID]; + if (rewards == 0) { revert ZeroRewards(); } delete _rewardsStash[delegator][toValidatorID]; // It's important that we mint after erasing (protection against Re-Entrancy) - _mintNativeToken(totalReward); + _mintNativeToken(rewards); return rewards; } function claimRewards(uint256 toValidatorID) public { address delegator = msg.sender; - Rewards memory rewards = _claimRewards(delegator, toValidatorID); + uint256 rewards = _claimRewards(delegator, toValidatorID); // It's important that we transfer after erasing (protection against Re-Entrancy) - (bool sent, ) = _receiverOf(delegator).call{ - value: rewards.lockupExtraReward + rewards.lockupBaseReward + rewards.unlockedReward - }(""); - + (bool sent, ) = _receiverOf(delegator).call{value: rewards}(""); if (!sent) { revert TransferFailed(); } - emit ClaimedRewards( - delegator, - toValidatorID, - rewards.lockupExtraReward, - rewards.lockupBaseReward, - rewards.unlockedReward - ); + emit ClaimedRewards(delegator, toValidatorID, rewards); } function restakeRewards(uint256 toValidatorID) public { address delegator = msg.sender; - Rewards memory rewards = _claimRewards(delegator, toValidatorID); - - uint256 lockupReward = rewards.lockupExtraReward + rewards.lockupBaseReward; - _delegate(delegator, toValidatorID, lockupReward + rewards.unlockedReward); - getLockupInfo[delegator][toValidatorID].lockedStake += lockupReward; - emit RestakedRewards( - delegator, - toValidatorID, - rewards.lockupExtraReward, - rewards.lockupBaseReward, - rewards.unlockedReward - ); + uint256 rewards = _claimRewards(delegator, toValidatorID); + + _delegate(delegator, toValidatorID, rewards); + emit RestakedRewards(delegator, toValidatorID, rewards); } // burnFTM allows SFC to burn an arbitrary amount of FTM tokens @@ -525,184 +401,6 @@ contract SFCLib is SFCBase { return getEpochSnapshot[epoch].endTime; } - function _isLockedUpAtEpoch(address delegator, uint256 toValidatorID, uint256 epoch) internal view returns (bool) { - return - getLockupInfo[delegator][toValidatorID].fromEpoch <= epoch && - epochEndTime(epoch) <= getLockupInfo[delegator][toValidatorID].endTime; - } - - function getUnlockedStake(address delegator, uint256 toValidatorID) public view returns (uint256) { - if (!isLockedUp(delegator, toValidatorID)) { - return getStake[delegator][toValidatorID]; - } - return getStake[delegator][toValidatorID] - getLockupInfo[delegator][toValidatorID].lockedStake; - } - - function _lockStake( - address delegator, - uint256 toValidatorID, - uint256 lockupDuration, - uint256 amount, - bool relock - ) internal { - if (_redirected(delegator)) { - revert Redirected(); - } - - if (amount > getUnlockedStake(delegator, toValidatorID)) { - revert NotEnoughUnlockedStake(); - } - - if (getValidator[toValidatorID].status != OK_STATUS) { - revert ValidatorNotActive(); - } - - if (lockupDuration < c.minLockupDuration() || lockupDuration > c.maxLockupDuration()) { - revert IncorrectDuration(); - } - - uint256 endTime = _now() + lockupDuration; - address validatorAddr = getValidator[toValidatorID].auth; - if ( - delegator != validatorAddr && - getLockupInfo[validatorAddr][toValidatorID].endTime + 30 * 24 * 60 * 60 < endTime - ) { - revert ValidatorLockupTooShort(); - } - - _stashRewards(delegator, toValidatorID); - _delStalePenalties(delegator, toValidatorID); - - // stash the previous penalty and clean getStashedLockupRewards - LockedDelegation storage ld = getLockupInfo[delegator][toValidatorID]; - if (relock) { - Penalty[] storage penalties = getStashedPenalties[delegator][toValidatorID]; - - uint256 penalty = _popNonStashedUnlockPenalty(delegator, toValidatorID, ld.lockedStake, ld.lockedStake); - if (penalty != 0) { - penalties.push(Penalty(penalty, ld.endTime)); - if (penalties.length > 30) { - revert TooManyReLocks(); - } - if ( - amount <= ld.lockedStake / 100 && penalties.length > 3 && endTime < ld.endTime + 14 * 24 * 60 * 60 - ) { - revert TooFrequentReLocks(); - } - } - } - - // check lockup duration after _stashRewards, which has erased previous lockup if it has unlocked already - if (lockupDuration < ld.duration) { - revert LockupDurationDecreased(); - } - - ld.lockedStake = ld.lockedStake + amount; - ld.fromEpoch = currentEpoch(); - ld.endTime = endTime; - ld.duration = lockupDuration; - - emit LockedUpStake(delegator, toValidatorID, lockupDuration, amount); - } - - function lockStake(uint256 toValidatorID, uint256 lockupDuration, uint256 amount) public { - address delegator = msg.sender; - if (amount == 0) { - revert ZeroAmount(); - } - if (isLockedUp(delegator, toValidatorID)) { - revert AlreadyLockedUp(); - } - _lockStake(delegator, toValidatorID, lockupDuration, amount, false); - } - - function relockStake(uint256 toValidatorID, uint256 lockupDuration, uint256 amount) public { - address delegator = msg.sender; - if (!isLockedUp(delegator, toValidatorID)) { - revert NotLockedUp(); - } - _lockStake(delegator, toValidatorID, lockupDuration, amount, true); - } - - function _popNonStashedUnlockPenalty( - address delegator, - uint256 toValidatorID, - uint256 unlockAmount, - uint256 totalAmount - ) internal returns (uint256) { - Rewards storage r = getStashedLockupRewards[delegator][toValidatorID]; - uint256 lockupExtraRewardShare = (r.lockupExtraReward * unlockAmount) / totalAmount; - uint256 lockupBaseRewardShare = (r.lockupBaseReward * unlockAmount) / totalAmount; - uint256 penalty = lockupExtraRewardShare + lockupBaseRewardShare / 2; - r.lockupExtraReward = r.lockupExtraReward - lockupExtraRewardShare; - r.lockupBaseReward = r.lockupBaseReward - lockupBaseRewardShare; - return penalty; - } - - function _popStashedUnlockPenalty( - address delegator, - uint256 toValidatorID, - uint256 unlockAmount, - uint256 totalAmount - ) internal returns (uint256) { - _delStalePenalties(delegator, toValidatorID); - Penalty[] storage penalties = getStashedPenalties[delegator][toValidatorID]; - uint256 total = 0; - for (uint256 i = 0; i < penalties.length; i++) { - uint256 penalty = (penalties[i].amount * unlockAmount) / totalAmount; - penalties[i].amount = penalties[i].amount - penalty; - total = total + penalty; - } - return total; - } - - function _popWholeUnlockPenalty( - address delegator, - uint256 toValidatorID, - uint256 unlockAmount, - uint256 totalAmount - ) internal returns (uint256) { - uint256 nonStashed = _popNonStashedUnlockPenalty(delegator, toValidatorID, unlockAmount, totalAmount); - uint256 stashed = _popStashedUnlockPenalty(delegator, toValidatorID, unlockAmount, totalAmount); - return nonStashed + stashed; - } - - function unlockStake(uint256 toValidatorID, uint256 amount) external returns (uint256) { - address delegator = msg.sender; - LockedDelegation storage ld = getLockupInfo[delegator][toValidatorID]; - - if (amount == 0) { - revert ZeroAmount(); - } - if (!isLockedUp(delegator, toValidatorID)) { - revert NotLockedUp(); - } - if (amount > ld.lockedStake) { - revert NotEnoughLockedStake(); - } - if (_redirected(delegator)) { - revert Redirected(); - } - - _stashRewards(delegator, toValidatorID); - - uint256 penalty = _popWholeUnlockPenalty(delegator, toValidatorID, amount, ld.lockedStake); - if (penalty > amount) { - penalty = amount; - } - ld.lockedStake -= amount; - if (penalty != 0) { - _rawUndelegate(delegator, toValidatorID, penalty, true, false, false); - (bool success, ) = treasuryAddress.call{value: penalty}(""); - if (!success) { - revert TransferFailed(); - } - } - - emit UnlockedStake(delegator, toValidatorID, amount, penalty); - return penalty; - } - function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio) external onlyOwner { if (!isSlashed(validatorID)) { revert ValidatorNotSlashed(); @@ -714,18 +412,6 @@ contract SFCLib is SFCBase { emit UpdatedSlashingRefundRatio(validatorID, refundRatio); } - function _delStalePenalties(address delegator, uint256 toValidatorID) public { - Penalty[] storage penalties = getStashedPenalties[delegator][toValidatorID]; - for (uint256 i = 0; i < penalties.length; ) { - if (penalties[i].end < _now() || penalties[i].amount == 0) { - penalties[i] = penalties[penalties.length - 1]; - penalties.pop(); - } else { - i++; - } - } - } - function _redirected(address addr) internal view returns (bool) { return getRedirection[addr] != address(0); } @@ -737,106 +423,4 @@ contract SFCLib is SFCBase { } return payable(address(uint160(to))); } - - // code below can be erased after 1 year since deployment of multipenalties - - function _getAvgEpochStep(uint256 duration) internal view virtual returns (uint256) { - // estimate number of epochs such that we would make approximately 15 iterations - uint256 tryEpochs = currentSealedEpoch / 5; - if (tryEpochs > 10000) { - tryEpochs = 10000; - } - uint256 tryEndTime = getEpochSnapshot[currentSealedEpoch - tryEpochs].endTime; - if (tryEndTime == 0 || tryEpochs == 0) { - return 0; - } - uint256 secondsPerEpoch = (_now() - tryEndTime) / tryEpochs; - return duration / (secondsPerEpoch * 15 + 1); - } - - function _getAvgReceivedStake(uint256 validatorID, uint256 duration, uint256 step) internal view returns (uint256) { - uint256 receivedStakeSum = getValidator[validatorID].receivedStake; - uint256 samples = 1; - - uint256 until = _now() - duration; - for (uint256 i = 1; i <= 30; i++) { - uint256 e = currentSealedEpoch - i * step; - EpochSnapshot storage s = getEpochSnapshot[e]; - if (s.endTime < until) { - break; - } - uint256 sample = s.receivedStake[validatorID]; - if (sample != 0) { - samples++; - receivedStakeSum += sample; - } - } - return receivedStakeSum / samples; - } - - function _getAvgUptime( - uint256 validatorID, - uint256 duration, - uint256 step - ) internal view virtual returns (uint256) { - uint256 until = _now() - duration; - uint256 oldUptimeCounter = 0; - uint256 newUptimeCounter = 0; - for (uint256 i = 0; i <= 30; i++) { - uint256 e = currentSealedEpoch - i * step; - EpochSnapshot storage s = getEpochSnapshot[e]; - uint256 endTime = s.endTime; - if (endTime < until) { - if (i <= 2) { - return duration; - } - break; - } - uint256 uptimeCounter = s.accumulatedUptime[validatorID]; - if (uptimeCounter != 0) { - oldUptimeCounter = uptimeCounter; - if (newUptimeCounter == 0) { - newUptimeCounter = uptimeCounter; - } - } - } - uint256 uptime = newUptimeCounter - oldUptimeCounter; - if (uptime > (duration * 4) / 5) { - return duration; - } - return uptime; - } - - function _truncateLegacyPenalty(address delegator, uint256 toValidatorID) internal { - Rewards storage r = getStashedLockupRewards[delegator][toValidatorID]; - uint256 storedPenalty = r.lockupExtraReward + r.lockupBaseReward / 2; - if (storedPenalty == 0) { - return; - } - LockedDelegation storage ld = getLockupInfo[delegator][toValidatorID]; - uint256 duration = ld.duration; - uint256 lockedStake = ld.lockedStake; - uint256 step = _getAvgEpochStep(duration); - if (step == 0) { - return; - } - uint256 rps = (_getAvgUptime(toValidatorID, duration, step) * 2092846271) / duration; // corresponds to 6.6% APR - uint256 selfStake = getStake[delegator][toValidatorID]; - - uint256 avgFullReward = (((selfStake * rps * duration) / 1e18) * (Decimal.unit() - c.validatorCommission())) / - Decimal.unit(); // reward for self-stake - if (getValidator[toValidatorID].auth == delegator) { - // reward for received portion of stake - uint256 receivedStakeAvg = (_getAvgReceivedStake(toValidatorID, duration, step) * 11) / 10; - avgFullReward += (((receivedStakeAvg * rps * duration) / 1e18) * c.validatorCommission()) / Decimal.unit(); - } - avgFullReward = (avgFullReward * lockedStake) / selfStake; - Rewards memory avgReward = _scaleLockupReward(avgFullReward, duration); - uint256 maxReasonablePenalty = avgReward.lockupBaseReward / 2 + avgReward.lockupExtraReward; - maxReasonablePenalty = maxReasonablePenalty; - if (storedPenalty > maxReasonablePenalty) { - r.lockupExtraReward = (r.lockupExtraReward * maxReasonablePenalty) / storedPenalty; - r.lockupBaseReward = (r.lockupBaseReward * maxReasonablePenalty) / storedPenalty; - } - } } diff --git a/contracts/sfc/SFCState.sol b/contracts/sfc/SFCState.sol index 68906db..e64ca65 100644 --- a/contracts/sfc/SFCState.sol +++ b/contracts/sfc/SFCState.sol @@ -22,12 +22,6 @@ contract SFCState is Initializable, Ownable { NodeDriverAuth internal node; - struct Rewards { - uint256 lockupExtraReward; - uint256 lockupBaseReward; - uint256 unlockedReward; - } - // last sealed epoch (currentEpoch - 1) uint256 public currentSealedEpoch; mapping(uint256 => Validator) public getValidator; @@ -38,11 +32,12 @@ contract SFCState is Initializable, Ownable { // total stake of all validators - includes slashed/offline validators uint256 public totalStake; + // total stake of active (OK_STATUS) validators (total weight) uint256 public totalActiveStake; // delegator => validator ID => stashed rewards (to be claimed/restaked) - mapping(address => mapping(uint256 => Rewards)) internal _rewardsStash; + mapping(address => mapping(uint256 => uint256)) internal _rewardsStash; // delegator => validator ID => last epoch number for which were rewards stashed mapping(address => mapping(uint256 => uint256)) public stashedRewardsUntilEpoch; @@ -56,21 +51,9 @@ contract SFCState is Initializable, Ownable { // delegator => validator ID => withdrawal ID => withdrawal request mapping(address => mapping(uint256 => mapping(uint256 => WithdrawalRequest))) public getWithdrawalRequest; - struct LockedDelegation { - uint256 lockedStake; - uint256 fromEpoch; - uint256 endTime; - uint256 duration; - } - // delegator => validator ID => current stake (locked+unlocked) mapping(address => mapping(uint256 => uint256)) public getStake; - // delegator => validator ID => locked stake info - mapping(address => mapping(uint256 => LockedDelegation)) public getLockupInfo; - - mapping(address => mapping(uint256 => Rewards)) public getStashedLockupRewards; - struct EpochSnapshot { // validator ID => validator weight in the epoch mapping(uint256 => uint256) receivedStake; @@ -113,13 +96,6 @@ contract SFCState is Initializable, Ownable { // the governance contract (to recalculate votes when the stake changes) address public voteBookAddress; - struct Penalty { - uint256 amount; - uint256 end; - } - // delegator => validatorID => penalties info - mapping(address => mapping(uint256 => Penalty[])) public getStashedPenalties; - // validator ID => amount of pubkey updates mapping(uint256 => uint256) internal validatorPubkeyChanges; diff --git a/contracts/sfc/Updater.sol b/contracts/sfc/Updater.sol index 634167a..5085a8d 100644 --- a/contracts/sfc/Updater.sol +++ b/contracts/sfc/Updater.sol @@ -88,9 +88,6 @@ contract Updater { consts.updateValidatorCommission((15 * Decimal.unit()) / 100); consts.updateBurntFeeShare((20 * Decimal.unit()) / 100); consts.updateTreasuryFeeShare((10 * Decimal.unit()) / 100); - consts.updateUnlockedRewardRatio((30 * Decimal.unit()) / 100); - consts.updateMinLockupDuration(86400 * 14); - consts.updateMaxLockupDuration(86400 * 365); consts.updateWithdrawalPeriodEpochs(3); consts.updateWithdrawalPeriodTime(60 * 60 * 24 * 7); consts.updateBaseRewardPerSecond(2668658453701531600); diff --git a/contracts/test/UnitTestSFC.sol b/contracts/test/UnitTestSFC.sol index db4176d..1531981 100644 --- a/contracts/test/UnitTestSFC.sol +++ b/contracts/test/UnitTestSFC.sol @@ -52,10 +52,6 @@ contract UnitTestSFC is SFC, UnitTestSFCBase { } contract UnitTestSFCLib is SFCLib, UnitTestSFCBase { - function highestLockupEpoch(address delegator, uint256 validatorID) external view returns (uint256) { - return _highestLockupEpoch(delegator, validatorID); - } - function _now() internal view override returns (uint256) { return time; } @@ -66,14 +62,6 @@ contract UnitTestSFCLib is SFCLib, UnitTestSFCBase { } return SFCBase.isNode(addr); } - - function _getAvgEpochStep(uint256) internal pure override returns (uint256) { - return 1; - } - - function _getAvgUptime(uint256, uint256 duration, uint256) internal pure override returns (uint256) { - return duration; - } } contract UnitTestNetworkInitializer { @@ -97,9 +85,6 @@ contract UnitTestNetworkInitializer { consts.updateValidatorCommission((15 * Decimal.unit()) / 100); consts.updateBurntFeeShare((20 * Decimal.unit()) / 100); consts.updateTreasuryFeeShare((10 * Decimal.unit()) / 100); - consts.updateUnlockedRewardRatio((30 * Decimal.unit()) / 100); - consts.updateMinLockupDuration(86400 * 14); - consts.updateMaxLockupDuration(86400 * 365); consts.updateWithdrawalPeriodEpochs(3); consts.updateWithdrawalPeriodTime(60 * 60 * 24 * 7); consts.updateBaseRewardPerSecond(6183414351851851852); @@ -130,18 +115,8 @@ interface SFCUnitTestI { uint256 totalSupply ); - function getLockupInfo( - address, - uint256 - ) external view returns (uint256 lockedStake, uint256 fromEpoch, uint256 endTime, uint256 duration); - function getStake(address, uint256) external view returns (uint256); - function getStashedLockupRewards( - address, - uint256 - ) external view returns (uint256 lockupExtraReward, uint256 lockupBaseReward, uint256 unlockedReward); - function getValidator( uint256 ) @@ -185,8 +160,6 @@ interface SFCUnitTestI { function totalActiveStake() external view returns (uint256); - function totalSlashedStake() external view returns (uint256); - function totalStake() external view returns (uint256); function totalSupply() external view returns (uint256); @@ -221,8 +194,6 @@ interface SFCUnitTestI { function rewardsStash(address delegator, uint256 validatorID) external view returns (uint256); - function getLockedStake(address delegator, uint256 toValidatorID) external view returns (uint256); - function createValidator(bytes calldata pubkey) external payable; function getSelfStake(uint256 validatorID) external view returns (uint256); @@ -267,16 +238,6 @@ interface SFCUnitTestI { function sealEpochValidators(uint256[] calldata nextValidatorIDs) external; - function isLockedUp(address delegator, uint256 toValidatorID) external view returns (bool); - - function getUnlockedStake(address delegator, uint256 toValidatorID) external view returns (uint256); - - function lockStake(uint256 toValidatorID, uint256 lockupDuration, uint256 amount) external; - - function relockStake(uint256 toValidatorID, uint256 lockupDuration, uint256 amount) external; - - function unlockStake(uint256 toValidatorID, uint256 amount) external returns (uint256); - function initialize( uint256 sealedEpoch, uint256 _totalSupply, @@ -297,17 +258,7 @@ interface SFCUnitTestI { uint256 deactivatedTime ) external; - function setGenesisDelegation( - address delegator, - uint256 toValidatorID, - uint256 stake, - uint256 lockedStake, - uint256 lockupFromEpoch, - uint256 lockupEndTime, - uint256 lockupDuration, - uint256 earlyUnlockPenalty, - uint256 rewards - ) external; + function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external; function _syncValidator(uint256 validatorID, bool syncPubkey) external; @@ -319,8 +270,6 @@ interface SFCUnitTestI { function advanceTime(uint256) external; - function highestLockupEpoch(address, uint256) external view returns (uint256); - function enableNonNodeCalls() external; function disableNonNodeCalls() external;