diff --git a/contracts/sfc/NetworkInitializer.sol b/contracts/sfc/NetworkInitializer.sol index d57b3b2..eda1145 100644 --- a/contracts/sfc/NetworkInitializer.sol +++ b/contracts/sfc/NetworkInitializer.sol @@ -3,7 +3,6 @@ pragma solidity ^0.8.9; import "./SFCI.sol"; import "./NodeDriver.sol"; -import "./SFCLib.sol"; import "./ConstantsManager.sol"; contract NetworkInitializer { diff --git a/contracts/sfc/SFC.sol b/contracts/sfc/SFC.sol index 0f7e31d..a73d097 100644 --- a/contracts/sfc/SFC.sol +++ b/contracts/sfc/SFC.sol @@ -3,40 +3,171 @@ pragma solidity ^0.8.9; import "./GasPriceConstants.sol"; import "../version/Version.sol"; -import "./SFCBase.sol"; +import "./SFCState.sol"; /** * @dev Stakers contract defines data structure and methods for validators / validators. */ -contract SFC is SFCBase, Version { - function _delegate(address implementation) internal { - assembly { - // Copy msg.data. We take full control of memory in this inline assembly - // block because it will not return to Solidity code. We overwrite the - // Solidity scratch pad at memory position 0. - calldatacopy(0, 0, calldatasize()) - - // Call the implementation. - // out and outsize are 0 because we don't know the size yet. - let result := delegatecall(gas(), implementation, 0, calldatasize(), 0, 0) - - // Copy the returned data. - returndatacopy(0, 0, returndatasize()) - - switch result - // delegatecall returns 0 on error. - case 0 { - revert(0, returndatasize()) - } - default { - return(0, returndatasize()) +contract SFC is SFCState, Version { + + uint256 internal constant OK_STATUS = 0; + uint256 internal constant WITHDRAWN_BIT = 1; + uint256 internal constant OFFLINE_BIT = 1 << 3; + uint256 internal constant DOUBLESIGN_BIT = 1 << 7; + uint256 internal constant CHEATER_MASK = DOUBLESIGN_BIT; + + error ValidatorNotExist(); + error ZeroAmount(); + error GovernanceRecalculationFailed(); + error AlreadyLockedUp(); + error NotLockedUp(); + error UnlockingMoreThanLocked(); + error ValidatorNotSlashed(); + error NotARatio(); + + event DeactivatedValidator(uint256 indexed validatorID, uint256 deactivatedEpoch, uint256 deactivatedTime); + event ChangedValidatorStatus(uint256 indexed validatorID, uint256 status); + + modifier onlyDriver() { + require(msg.sender == address(node), "caller is not the NodeDriverAuth contract"); + _; + } + + function currentEpoch() public view returns (uint256) { + return currentSealedEpoch + 1; + } + + function _calcRawValidatorEpochTxReward( + uint256 epochFee, + uint256 txRewardWeight, + uint256 totalTxRewardWeight + ) internal view returns (uint256) { + if (txRewardWeight == 0) { + return 0; + } + uint256 txReward = (epochFee * txRewardWeight) / totalTxRewardWeight; + // fee reward except burntFeeShare and treasuryFeeShare + return (txReward * (Decimal.unit() - c.burntFeeShare() - c.treasuryFeeShare())) / Decimal.unit(); + } + + function _calcRawValidatorEpochBaseReward( + uint256 epochDuration, + uint256 _baseRewardPerSecond, + uint256 baseRewardWeight, + uint256 totalBaseRewardWeight + ) internal pure returns (uint256) { + if (baseRewardWeight == 0) { + return 0; + } + uint256 totalReward = epochDuration * _baseRewardPerSecond; + return (totalReward * baseRewardWeight) / totalBaseRewardWeight; + } + + function _mintNativeToken(uint256 amount) internal { + // balance will be increased after the transaction is processed + node.incBalance(address(this), amount); + 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 + (bool success, ) = voteBookAddress.call{gas: 8000000}( + abi.encodeWithSignature("recountVotes(address,address)", delegator, validatorAuth) + ); + // Don't revert if recountVotes failed unless strict mode enabled + + // GovernanceRecalculationFailed + require(success || !strict, "gov votes recounting failed"); + } + } + + function _setValidatorDeactivated(uint256 validatorID, uint256 status) internal { + if (getValidator[validatorID].status == OK_STATUS && status != OK_STATUS) { + totalActiveStake = totalActiveStake - getValidator[validatorID].receivedStake; + } + // status as a number is proportional to severity + if (status > getValidator[validatorID].status) { + getValidator[validatorID].status = status; + if (getValidator[validatorID].deactivatedEpoch == 0) { + getValidator[validatorID].deactivatedEpoch = currentEpoch(); + getValidator[validatorID].deactivatedTime = block.timestamp; + emit DeactivatedValidator( + validatorID, + getValidator[validatorID].deactivatedEpoch, + getValidator[validatorID].deactivatedTime + ); } + emit ChangedValidatorStatus(validatorID, status); + } + } + + function _syncValidator(uint256 validatorID, bool syncPubkey) internal { + _requireValidatorToExists(validatorID); + // emit special log for node + uint256 weight = getValidator[validatorID].receivedStake; + if (getValidator[validatorID].status != OK_STATUS) { + weight = 0; + } + node.updateValidatorWeight(validatorID, weight); + if (syncPubkey && weight != 0) { + node.updateValidatorPubkey(validatorID, getValidatorPubkey[validatorID]); + } + } + + function _requireValidatorToExists(uint256 validatorID) private view { + if (getValidator[validatorID].createdTime == 0) { + revert ValidatorNotExist(); + } + } + + function _calcValidatorCommission(uint256 rawReward, uint256 commission) internal pure returns (uint256) { + 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; } - fallback() external payable { - require(msg.data.length != 0, "transfers not allowed"); - _delegate(libAddress); + function isLockedUp(address delegator, uint256 toValidatorID) public view returns (bool) { + return + getLockupInfo[delegator][toValidatorID].endTime != 0 && + getLockupInfo[delegator][toValidatorID].lockedStake != 0 && + block.timestamp <= getLockupInfo[delegator][toValidatorID].endTime; } receive() external payable { @@ -92,22 +223,17 @@ contract SFC is SFCBase, Version { uint256 sealedEpoch, uint256 _totalSupply, address nodeDriver, - address lib, + address lib, // to be removed address _c, address owner ) external initializer { Ownable.initialize(owner); currentSealedEpoch = sealedEpoch; node = NodeDriverAuth(nodeDriver); - libAddress = lib; c = ConstantsManager(_c); totalSupply = _totalSupply; minGasPrice = GP.initialMinGasPrice(); - getEpochSnapshot[sealedEpoch].endTime = _now(); - } - - function updateLibAddress(address v) external onlyOwner { - libAddress = v; + getEpochSnapshot[sealedEpoch].endTime = block.timestamp; } function updateTreasuryAddress(address v) external onlyOwner { @@ -126,28 +252,13 @@ contract SFC is SFCBase, Version { voteBookAddress = v; } - function migrateValidatorPubkeyUniquenessFlag(uint256 start, uint256 end) external { - for (uint256 vid = start; vid < end; vid++) { - bytes memory pubkey = getValidatorPubkey[vid]; - if (pubkey.length > 0 && pubkeyHashToValidatorID[keccak256(pubkey)] != vid) { - require(pubkeyHashToValidatorID[keccak256(pubkey)] == 0, "already exists"); - pubkeyHashToValidatorID[keccak256(pubkey)] = vid; - } - } - } - function updateValidatorPubkey(bytes calldata pubkey) external { - require(getValidator[1].auth == 0x541E408443A592C38e01Bed0cB31f9De8c1322d0, "not mainnet"); require(pubkey.length == 66 && pubkey[0] == 0xc0, "malformed pubkey"); uint256 validatorID = getValidatorID[msg.sender]; require(validatorID <= 59 || validatorID == 64, "not legacy validator"); - require(_validatorExists(validatorID), "validator doesn't exist"); + _requireValidatorToExists(validatorID); require(keccak256(pubkey) != keccak256(getValidatorPubkey[validatorID]), "same pubkey"); require(pubkeyHashToValidatorID[keccak256(pubkey)] == 0, "already used"); - require( - validatorPubkeyChanges[validatorID] == 0 || validatorID == 64 || validatorID <= 12, - "allowed only once" - ); validatorPubkeyChanges[validatorID]++; pubkeyHashToValidatorID[keccak256(pubkey)] = validatorID; @@ -155,32 +266,6 @@ contract SFC is SFCBase, Version { _syncValidator(validatorID, true); } - function setRedirectionAuthorizer(address v) external onlyOwner { - require(redirectionAuthorizer != v, "same"); - redirectionAuthorizer = v; - } - - event AnnouncedRedirection(address indexed from, address indexed to); - - function announceRedirection(address to) external { - emit AnnouncedRedirection(msg.sender, to); - } - - function initiateRedirection(address from, address to) external { - require(msg.sender == redirectionAuthorizer, "not authorized"); - require(getRedirection[from] != to, "already complete"); - require(from != to, "same address"); - getRedirectionRequest[from] = to; - } - - function redirect(address to) external { - address from = msg.sender; - require(to != address(0), "zero address"); - require(getRedirectionRequest[from] == to, "no request"); - getRedirection[from] = to; - getRedirectionRequest[from] = address(0); - } - /* Epoch callbacks */ @@ -190,7 +275,7 @@ contract SFC is SFCBase, Version { uint256[] memory validatorIDs, uint256[] memory offlineTime, uint256[] memory offlineBlocks - ) internal { + ) private { // mark offline nodes for (uint256 i = 0; i < validatorIDs.length; i++) { if ( @@ -356,15 +441,15 @@ contract SFC is SFCBase, Version { { EpochSnapshot storage prevSnapshot = getEpochSnapshot[currentSealedEpoch]; uint256 epochDuration = 1; - if (_now() > prevSnapshot.endTime) { - epochDuration = _now() - prevSnapshot.endTime; + if (block.timestamp > prevSnapshot.endTime) { + epochDuration = block.timestamp - prevSnapshot.endTime; } _sealEpoch_rewards(epochDuration, snapshot, prevSnapshot, validatorIDs, uptimes, originatedTxsFee); _sealEpoch_minGasPrice(epochDuration, epochGas); } currentSealedEpoch = currentEpoch(); - snapshot.endTime = _now(); + snapshot.endTime = block.timestamp; snapshot.endBlock = block.number; snapshot.baseRewardPerSecond = c.baseRewardPerSecond(); snapshot.totalSupply = totalSupply; @@ -382,4 +467,622 @@ contract SFC is SFCBase, Version { snapshot.validatorIDs = nextValidatorIDs; node.updateMinGasPrice(minGasPrice); } + + event CreatedValidator( + uint256 indexed validatorID, + address indexed auth, + uint256 createdEpoch, + uint256 createdTime + ); + 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 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); + + /* + Constructor + */ + + function setGenesisValidator( + address auth, + uint256 validatorID, + bytes calldata pubkey, + uint256 createdEpoch, + uint256 createdTime + ) external onlyDriver { + _rawCreateValidator( + auth, + validatorID, + pubkey, + createdEpoch, + createdTime + ); + if (validatorID > lastValidatorID) { + lastValidatorID = validatorID; + } + } + + function setGenesisDelegation( + address delegator, + uint256 toValidatorID, + uint256 stake, + uint256 lockedStake, // to be removed + uint256 lockupFromEpoch, + uint256 lockupEndTime, + uint256 lockupDuration, + uint256 earlyUnlockPenalty, + uint256 rewards + ) external onlyDriver { + _rawDelegate(delegator, toValidatorID, stake, false); + _rewardsStash[delegator][toValidatorID].unlockedReward = rewards; + _mintNativeToken(stake); + } + + /* + Methods + */ + + function createValidator(bytes calldata pubkey) external payable { + require(msg.value >= c.minSelfStake(), "insufficient self-stake"); + require(pubkey.length > 0, "empty pubkey"); + require(pubkeyHashToValidatorID[keccak256(pubkey)] == 0, "already used"); + uint256 validatorID = ++lastValidatorID; + _rawCreateValidator(msg.sender, validatorID, pubkey, currentEpoch(), block.timestamp); + _delegate(msg.sender, lastValidatorID, msg.value); + } + + function _rawCreateValidator( + address auth, + uint256 validatorID, + bytes memory pubkey, + uint256 createdEpoch, + uint256 createdTime + ) private { + require(getValidatorID[auth] == 0, "validator already exists"); + getValidatorID[auth] = validatorID; + getValidator[validatorID].createdEpoch = createdEpoch; + getValidator[validatorID].createdTime = createdTime; + getValidator[validatorID].auth = auth; + getValidatorPubkey[validatorID] = pubkey; + pubkeyHashToValidatorID[keccak256(pubkey)] = validatorID; + + emit CreatedValidator(validatorID, auth, createdEpoch, createdTime); + } + + function getSelfStake(uint256 validatorID) public view returns (uint256) { + return getStake[getValidator[validatorID].auth][validatorID]; + } + + function _checkDelegatedStakeLimit(uint256 validatorID) private view returns (bool) { + return + getValidator[validatorID].receivedStake <= + (getSelfStake(validatorID) * c.maxDelegatedRatio()) / Decimal.unit(); + } + + function delegate(uint256 toValidatorID) external payable { + _delegate(msg.sender, toValidatorID, msg.value); + } + + + + function _delegate(address delegator, uint256 toValidatorID, uint256 amount) private { + _requireValidatorToExists(toValidatorID); + require(getValidator[toValidatorID].status == OK_STATUS, "validator isn't active"); + _rawDelegate(delegator, toValidatorID, amount, true); + require(_checkDelegatedStakeLimit(toValidatorID), "validator's delegations limit is exceeded"); + } + + function _rawDelegate(address delegator, uint256 toValidatorID, uint256 amount, bool strict) internal { + if (amount == 0) { + revert ZeroAmount(); + } + + _stashRewards(delegator, toValidatorID); + + getStake[delegator][toValidatorID] = getStake[delegator][toValidatorID] + amount; + uint256 origStake = getValidator[toValidatorID].receivedStake; + getValidator[toValidatorID].receivedStake = origStake + amount; + totalStake = totalStake + amount; + if (getValidator[toValidatorID].status == OK_STATUS) { + totalActiveStake = totalActiveStake + amount; + } + + _syncValidator(toValidatorID, origStake == 0); + + emit Delegated(delegator, toValidatorID, amount); + + _recountVotes(delegator, getValidator[toValidatorID].auth, strict); + } + + function _rawUndelegate( + address delegator, + uint256 toValidatorID, + uint256 amount, + bool strict, + bool forceful, + bool checkDelegatedStake + ) internal { + getStake[delegator][toValidatorID] -= amount; + getValidator[toValidatorID].receivedStake = getValidator[toValidatorID].receivedStake - amount; + totalStake = totalStake - amount; + if (getValidator[toValidatorID].status == OK_STATUS) { + totalActiveStake = totalActiveStake - amount; + } + + uint256 selfStakeAfterwards = getSelfStake(toValidatorID); + if (selfStakeAfterwards != 0 && getValidator[toValidatorID].status == OK_STATUS) { + if (!(selfStakeAfterwards >= c.minSelfStake())) { + if (forceful) { + revert("insufficient self-stake"); + } else { + _setValidatorDeactivated(toValidatorID, WITHDRAWN_BIT); + } + } + require( + !checkDelegatedStake || _checkDelegatedStakeLimit(toValidatorID), + "validator's delegations limit is exceeded" + ); + } else { + _setValidatorDeactivated(toValidatorID, WITHDRAWN_BIT); + } + + _recountVotes(delegator, getValidator[toValidatorID].auth, strict); + } + + function undelegate(uint256 toValidatorID, uint256 wrID, uint256 amount) public { + address delegator = msg.sender; + + _stashRewards(delegator, toValidatorID); + + if (amount == 0) { + revert ZeroAmount(); + } + require(amount <= getUnlockedStake(delegator, toValidatorID), "not enough unlocked stake"); + + require(getWithdrawalRequest[delegator][toValidatorID][wrID].amount == 0, "wrID already exists"); + + _rawUndelegate(delegator, toValidatorID, amount, true, false, true); + + getWithdrawalRequest[delegator][toValidatorID][wrID].amount = amount; + getWithdrawalRequest[delegator][toValidatorID][wrID].epoch = currentEpoch(); + getWithdrawalRequest[delegator][toValidatorID][wrID].time = block.timestamp; + + _syncValidator(toValidatorID, false); + + emit Undelegated(delegator, toValidatorID, wrID, amount); + } + + function isSlashed(uint256 validatorID) public view returns (bool) { + return getValidator[validatorID].status & CHEATER_MASK != 0; + } + + function getSlashingPenalty( + uint256 amount, + bool isCheater, + uint256 refundRatio + ) internal pure returns (uint256 penalty) { + if (!isCheater || refundRatio >= Decimal.unit()) { + return 0; + } + // round penalty upwards (ceiling) to prevent dust amount attacks + penalty = (amount * (Decimal.unit() - refundRatio)) / Decimal.unit() + 1; + if (penalty > amount) { + return amount; + } + return penalty; + } + + function _withdraw(address delegator, uint256 toValidatorID, uint256 wrID, address payable receiver) private { + WithdrawalRequest memory request = getWithdrawalRequest[delegator][toValidatorID][wrID]; + require(request.epoch != 0, "request doesn't exist"); + + uint256 requestTime = request.time; + uint256 requestEpoch = request.epoch; + if ( + getValidator[toValidatorID].deactivatedTime != 0 && + getValidator[toValidatorID].deactivatedTime < requestTime + ) { + requestTime = getValidator[toValidatorID].deactivatedTime; + requestEpoch = getValidator[toValidatorID].deactivatedEpoch; + } + + require(block.timestamp >= requestTime + c.withdrawalPeriodTime(), "not enough time passed"); + require(currentEpoch() >= requestEpoch + c.withdrawalPeriodEpochs(), "not enough epochs passed"); + + uint256 amount = getWithdrawalRequest[delegator][toValidatorID][wrID].amount; + bool isCheater = isSlashed(toValidatorID); + uint256 penalty = getSlashingPenalty(amount, isCheater, slashingRefundRatio[toValidatorID]); + delete getWithdrawalRequest[delegator][toValidatorID][wrID]; + + totalSlashedStake += penalty; + require(amount > penalty, "stake is fully slashed"); + // It's important that we transfer after erasing (protection against Re-Entrancy) + (bool sent, ) = receiver.call{value: amount - penalty}(""); + require(sent, "Failed to send FTM"); + _burnFTM(penalty); + + emit Withdrawn(delegator, toValidatorID, wrID, amount); + } + + function withdraw(uint256 toValidatorID, uint256 wrID) public { + _withdraw(msg.sender, toValidatorID, wrID, payable(msg.sender)); + } + + function deactivateValidator(uint256 validatorID, uint256 status) external onlyDriver { + require(status != OK_STATUS, "wrong status"); + + _setValidatorDeactivated(validatorID, status); + _syncValidator(validatorID, false); + address validatorAddr = getValidator[validatorID].auth; + _recountVotes(validatorAddr, validatorAddr, false); + } + + function _highestPayableEpoch(uint256 validatorID) private view returns (uint256) { + if (getValidator[validatorID].deactivatedEpoch != 0) { + if (currentSealedEpoch < getValidator[validatorID].deactivatedEpoch) { + return currentSealedEpoch; + } + return getValidator[validatorID].deactivatedEpoch; + } + return currentSealedEpoch; + } + + // find highest epoch such that _isLockedUpAtEpoch returns true (using binary search) + function _highestLockupEpoch(address delegator, uint256 validatorID) internal view returns (uint256) { + uint256 l = getLockupInfo[delegator][validatorID].fromEpoch; + uint256 r = currentSealedEpoch; + if (_isLockedUpAtEpoch(delegator, validatorID, r)) { + return r; + } + if (!_isLockedUpAtEpoch(delegator, validatorID, l)) { + return 0; + } + if (l > r) { + return 0; + } + while (l < r) { + uint256 m = (l + r) / 2; + if (_isLockedUpAtEpoch(delegator, validatorID, m)) { + l = m + 1; + } else { + r = m; + } + } + if (r == 0) { + return 0; + } + return r - 1; + } + + function _newRewards(address delegator, uint256 toValidatorID) private view returns (Rewards memory) { + 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); + } + + function _newRewardsOf( + uint256 stakeAmount, + uint256 toValidatorID, + uint256 fromEpoch, + uint256 toEpoch + ) private view returns (uint256) { + if (fromEpoch >= toEpoch) { + return 0; + } + uint256 stashedRate = getEpochSnapshot[fromEpoch].accumulatedRewardPerToken[toValidatorID]; + uint256 currentRate = getEpochSnapshot[toEpoch].accumulatedRewardPerToken[toValidatorID]; + return ((currentRate - stashedRate) * stakeAmount) / Decimal.unit(); + } + + function _pendingRewards(address delegator, uint256 toValidatorID) private 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; + } + + function stashRewards(address delegator, uint256 toValidatorID) external { + require(_stashRewards(delegator, toValidatorID), "nothing to stash"); + } + + function _stashRewards(address delegator, uint256 toValidatorID) private returns (bool updated) { + Rewards memory 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]; + } + return + nonStashedReward.lockupBaseReward != 0 || + nonStashedReward.lockupExtraReward != 0 || + nonStashedReward.unlockedReward != 0; + } + + function _claimRewards(address delegator, uint256 toValidatorID) private returns (Rewards memory rewards) { + _stashRewards(delegator, toValidatorID); + rewards = _rewardsStash[delegator][toValidatorID]; + uint256 totalReward = rewards.unlockedReward + rewards.lockupBaseReward + rewards.lockupExtraReward; + require(totalReward != 0, "zero rewards"); + delete _rewardsStash[delegator][toValidatorID]; + // It's important that we mint after erasing (protection against Re-Entrancy) + _mintNativeToken(totalReward); + return rewards; + } + + function claimRewards(uint256 toValidatorID) public { + address delegator = msg.sender; + Rewards memory rewards = _claimRewards(delegator, toValidatorID); + // It's important that we transfer after erasing (protection against Re-Entrancy) + (bool sent, ) = delegator.call{ + value: rewards.lockupExtraReward + rewards.lockupBaseReward + rewards.unlockedReward + }(""); + require(sent, "Failed to send FTM"); + + emit ClaimedRewards( + delegator, + toValidatorID, + rewards.lockupExtraReward, + rewards.lockupBaseReward, + rewards.unlockedReward + ); + } + + 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 + ); + } + + // burnFTM allows SFC to burn an arbitrary amount of FTM tokens + function burnFTM(uint256 amount) external onlyOwner { + _burnFTM(amount); + } + + function _burnFTM(uint256 amount) private { + if (amount != 0) { + payable(address(0)).transfer(amount); + emit BurntFTM(amount); + } + } + + function _isLockedUpAtEpoch(address delegator, uint256 toValidatorID, uint256 epoch) private view returns (bool) { + return + getLockupInfo[delegator][toValidatorID].fromEpoch <= epoch && + getEpochSnapshot[epoch].endTime <= 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 + ) private { + require(amount <= getUnlockedStake(delegator, toValidatorID), "not enough stake"); + require(getValidator[toValidatorID].status == OK_STATUS, "validator isn't active"); + + require( + lockupDuration >= c.minLockupDuration() && lockupDuration <= c.maxLockupDuration(), + "incorrect duration" + ); + uint256 endTime = block.timestamp + lockupDuration; + address validatorAddr = getValidator[toValidatorID].auth; + if (delegator != validatorAddr) { + require( + getLockupInfo[validatorAddr][toValidatorID].endTime + 30 * 24 * 60 * 60 >= endTime, + "validator's lockup will end too early" + ); + } + + _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)); + require(penalties.length <= 30, "too many ongoing relocks"); + require( + amount > ld.lockedStake / 100 || penalties.length <= 3 || endTime >= ld.endTime + 14 * 24 * 60 * 60, + "too frequent relocks" + ); + } + } + + // check lockup duration after _stashRewards, which has erased previous lockup if it has unlocked already + require(lockupDuration >= ld.duration, "lockup duration cannot decrease"); + + 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 + ) private 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 + ) private 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 + ) private 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 UnlockingMoreThanLocked(); + } + + _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}(""); + require(success, "Failed to send penalty"); + } + + emit UnlockedStake(delegator, toValidatorID, amount, penalty); + return penalty; + } + + function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio) external onlyOwner { + if (!isSlashed(validatorID)) { + revert ValidatorNotSlashed(); + } + if (refundRatio > Decimal.unit()) { + revert NotARatio(); + } + slashingRefundRatio[validatorID] = refundRatio; + emit UpdatedSlashingRefundRatio(validatorID, refundRatio); + } + + function _delStalePenalties(address delegator, uint256 toValidatorID) private { + Penalty[] storage penalties = getStashedPenalties[delegator][toValidatorID]; + for (uint256 i = 0; i < penalties.length; ) { + if (penalties[i].end < block.timestamp || penalties[i].amount == 0) { + penalties[i] = penalties[penalties.length - 1]; + penalties.pop(); + } else { + i++; + } + } + } } diff --git a/contracts/sfc/SFCBase.sol b/contracts/sfc/SFCBase.sol deleted file mode 100644 index 066a3e4..0000000 --- a/contracts/sfc/SFCBase.sol +++ /dev/null @@ -1,161 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.9; - -import "./SFCState.sol"; - -contract SFCBase is SFCState { - uint256 internal constant OK_STATUS = 0; - uint256 internal constant WITHDRAWN_BIT = 1; - uint256 internal constant OFFLINE_BIT = 1 << 3; - uint256 internal constant DOUBLESIGN_BIT = 1 << 7; - uint256 internal constant CHEATER_MASK = DOUBLESIGN_BIT; - - event DeactivatedValidator(uint256 indexed validatorID, uint256 deactivatedEpoch, uint256 deactivatedTime); - event ChangedValidatorStatus(uint256 indexed validatorID, uint256 status); - - function isNode(address addr) internal view virtual returns (bool) { - return addr == address(node); - } - - modifier onlyDriver() { - require(isNode(msg.sender), "caller is not the NodeDriverAuth contract"); - _; - } - - function currentEpoch() public view returns (uint256) { - return currentSealedEpoch + 1; - } - - function _calcRawValidatorEpochTxReward( - uint256 epochFee, - uint256 txRewardWeight, - uint256 totalTxRewardWeight - ) internal view returns (uint256) { - if (txRewardWeight == 0) { - return 0; - } - uint256 txReward = (epochFee * txRewardWeight) / totalTxRewardWeight; - // fee reward except burntFeeShare and treasuryFeeShare - return (txReward * (Decimal.unit() - c.burntFeeShare() - c.treasuryFeeShare())) / Decimal.unit(); - } - - function _calcRawValidatorEpochBaseReward( - uint256 epochDuration, - uint256 _baseRewardPerSecond, - uint256 baseRewardWeight, - uint256 totalBaseRewardWeight - ) internal pure returns (uint256) { - if (baseRewardWeight == 0) { - return 0; - } - uint256 totalReward = epochDuration * _baseRewardPerSecond; - return (totalReward * baseRewardWeight) / totalBaseRewardWeight; - } - - function _mintNativeToken(uint256 amount) internal { - // balance will be increased after the transaction is processed - node.incBalance(address(this), amount); - 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 - (bool success, ) = voteBookAddress.call{gas: 8000000}( - abi.encodeWithSignature("recountVotes(address,address)", delegator, validatorAuth) - ); - // Don't revert if recountVotes failed unless strict mode enabled - require(success || !strict, "gov votes recounting failed"); - } - } - - function _setValidatorDeactivated(uint256 validatorID, uint256 status) internal { - if (getValidator[validatorID].status == OK_STATUS && status != OK_STATUS) { - totalActiveStake = totalActiveStake - getValidator[validatorID].receivedStake; - } - // status as a number is proportional to severity - if (status > getValidator[validatorID].status) { - getValidator[validatorID].status = status; - if (getValidator[validatorID].deactivatedEpoch == 0) { - getValidator[validatorID].deactivatedEpoch = currentEpoch(); - getValidator[validatorID].deactivatedTime = _now(); - emit DeactivatedValidator( - validatorID, - getValidator[validatorID].deactivatedEpoch, - getValidator[validatorID].deactivatedTime - ); - } - emit ChangedValidatorStatus(validatorID, status); - } - } - - function _syncValidator(uint256 validatorID, bool syncPubkey) public { - require(_validatorExists(validatorID), "validator doesn't exist"); - // emit special log for node - uint256 weight = getValidator[validatorID].receivedStake; - if (getValidator[validatorID].status != OK_STATUS) { - weight = 0; - } - node.updateValidatorWeight(validatorID, weight); - if (syncPubkey && weight != 0) { - node.updateValidatorPubkey(validatorID, getValidatorPubkey[validatorID]); - } - } - - function _validatorExists(uint256 validatorID) internal view returns (bool) { - return getValidator[validatorID].createdTime != 0; - } - - function _calcValidatorCommission(uint256 rawReward, uint256 commission) internal pure returns (uint256) { - 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/SFCLib.sol b/contracts/sfc/SFCLib.sol deleted file mode 100644 index 231d767..0000000 --- a/contracts/sfc/SFCLib.sol +++ /dev/null @@ -1,846 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.9; - -import "../common/Decimal.sol"; -import "./GasPriceConstants.sol"; -import "./SFCBase.sol"; -import "./NodeDriver.sol"; - -contract SFCLib is SFCBase { - event CreatedValidator( - uint256 indexed validatorID, - address indexed auth, - uint256 createdEpoch, - uint256 createdTime - ); - 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 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); - - /* - Constructor - */ - - function setGenesisValidator( - address auth, - uint256 validatorID, - bytes calldata pubkey, - uint256 status, - uint256 createdEpoch, - uint256 createdTime, - uint256 deactivatedEpoch, - uint256 deactivatedTime - ) external onlyDriver { - _rawCreateValidator( - auth, - validatorID, - pubkey, - status, - createdEpoch, - createdTime, - deactivatedEpoch, - deactivatedTime - ); - if (validatorID > lastValidatorID) { - lastValidatorID = validatorID; - } - } - - function setGenesisDelegation( - address delegator, - uint256 toValidatorID, - uint256 stake, - uint256 lockedStake, - uint256 lockupFromEpoch, - uint256 lockupEndTime, - uint256 lockupDuration, - uint256 earlyUnlockPenalty, - uint256 rewards - ) external onlyDriver { - _rawDelegate(delegator, toValidatorID, stake, false); - _rewardsStash[delegator][toValidatorID].unlockedReward = rewards; - _mintNativeToken(stake); - if (lockedStake != 0) { - require(lockedStake <= stake, "locked stake is greater than the whole stake"); - 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); - } - } - - /* - Methods - */ - - function createValidator(bytes calldata pubkey) external payable { - require(msg.value >= c.minSelfStake(), "insufficient self-stake"); - require(pubkey.length > 0, "empty pubkey"); - require(pubkeyHashToValidatorID[keccak256(pubkey)] == 0, "already used"); - _createValidator(msg.sender, pubkey); - _delegate(msg.sender, lastValidatorID, msg.value); - } - - function _createValidator(address auth, bytes memory pubkey) internal { - uint256 validatorID = ++lastValidatorID; - _rawCreateValidator(auth, validatorID, pubkey, OK_STATUS, currentEpoch(), _now(), 0, 0); - } - - function _rawCreateValidator( - address auth, - uint256 validatorID, - bytes memory pubkey, - uint256 status, - uint256 createdEpoch, - uint256 createdTime, - uint256 deactivatedEpoch, - uint256 deactivatedTime - ) internal { - require(getValidatorID[auth] == 0, "validator already exists"); - getValidatorID[auth] = validatorID; - getValidator[validatorID].status = status; - getValidator[validatorID].createdEpoch = createdEpoch; - getValidator[validatorID].createdTime = createdTime; - getValidator[validatorID].deactivatedTime = deactivatedTime; - getValidator[validatorID].deactivatedEpoch = deactivatedEpoch; - getValidator[validatorID].auth = auth; - getValidatorPubkey[validatorID] = pubkey; - pubkeyHashToValidatorID[keccak256(pubkey)] = validatorID; - - emit CreatedValidator(validatorID, auth, createdEpoch, createdTime); - if (deactivatedEpoch != 0) { - emit DeactivatedValidator(validatorID, deactivatedEpoch, deactivatedTime); - } - if (status != 0) { - emit ChangedValidatorStatus(validatorID, status); - } - } - - function getSelfStake(uint256 validatorID) public view returns (uint256) { - return getStake[getValidator[validatorID].auth][validatorID]; - } - - function _checkDelegatedStakeLimit(uint256 validatorID) internal view returns (bool) { - return - getValidator[validatorID].receivedStake <= - (getSelfStake(validatorID) * c.maxDelegatedRatio()) / Decimal.unit(); - } - - function delegate(uint256 toValidatorID) external payable { - _delegate(msg.sender, toValidatorID, msg.value); - } - - function _delegate(address delegator, uint256 toValidatorID, uint256 amount) internal { - require(_validatorExists(toValidatorID), "validator doesn't exist"); - require(getValidator[toValidatorID].status == OK_STATUS, "validator isn't active"); - _rawDelegate(delegator, toValidatorID, amount, true); - require(_checkDelegatedStakeLimit(toValidatorID), "validator's delegations limit is exceeded"); - } - - function _rawDelegate(address delegator, uint256 toValidatorID, uint256 amount, bool strict) internal { - require(amount > 0, "zero amount"); - - _stashRewards(delegator, toValidatorID); - - getStake[delegator][toValidatorID] = getStake[delegator][toValidatorID] + amount; - uint256 origStake = getValidator[toValidatorID].receivedStake; - getValidator[toValidatorID].receivedStake = origStake + amount; - totalStake = totalStake + amount; - if (getValidator[toValidatorID].status == OK_STATUS) { - totalActiveStake = totalActiveStake + amount; - } - - _syncValidator(toValidatorID, origStake == 0); - - emit Delegated(delegator, toValidatorID, amount); - - _recountVotes(delegator, getValidator[toValidatorID].auth, strict); - } - - function recountVotes(address delegator, address validatorAuth, bool strict, uint256 gas) external { - (bool success, ) = voteBookAddress.call{gas: gas}( - abi.encodeWithSignature("recountVotes(address,address)", delegator, validatorAuth) - ); - require(success || !strict, "gov votes recounting failed"); - } - - function _rawUndelegate( - address delegator, - uint256 toValidatorID, - uint256 amount, - bool strict, - bool forceful, - bool checkDelegatedStake - ) internal { - getStake[delegator][toValidatorID] -= amount; - getValidator[toValidatorID].receivedStake = getValidator[toValidatorID].receivedStake - amount; - totalStake = totalStake - amount; - if (getValidator[toValidatorID].status == OK_STATUS) { - totalActiveStake = totalActiveStake - amount; - } - - uint256 selfStakeAfterwards = getSelfStake(toValidatorID); - if (selfStakeAfterwards != 0 && getValidator[toValidatorID].status == OK_STATUS) { - if (!(selfStakeAfterwards >= c.minSelfStake())) { - if (forceful) { - revert("insufficient self-stake"); - } else { - _setValidatorDeactivated(toValidatorID, WITHDRAWN_BIT); - } - } - require( - !checkDelegatedStake || _checkDelegatedStakeLimit(toValidatorID), - "validator's delegations limit is exceeded" - ); - } else { - _setValidatorDeactivated(toValidatorID, WITHDRAWN_BIT); - } - - _recountVotes(delegator, getValidator[toValidatorID].auth, strict); - } - - function undelegate(uint256 toValidatorID, uint256 wrID, uint256 amount) public { - address delegator = msg.sender; - - _stashRewards(delegator, toValidatorID); - - require(amount > 0, "zero amount"); - require(amount <= getUnlockedStake(delegator, toValidatorID), "not enough unlocked stake"); - - require(getWithdrawalRequest[delegator][toValidatorID][wrID].amount == 0, "wrID already exists"); - - _rawUndelegate(delegator, toValidatorID, amount, true, false, true); - - getWithdrawalRequest[delegator][toValidatorID][wrID].amount = amount; - getWithdrawalRequest[delegator][toValidatorID][wrID].epoch = currentEpoch(); - getWithdrawalRequest[delegator][toValidatorID][wrID].time = _now(); - - _syncValidator(toValidatorID, false); - - emit Undelegated(delegator, toValidatorID, wrID, amount); - } - - function isSlashed(uint256 validatorID) public view returns (bool) { - return getValidator[validatorID].status & CHEATER_MASK != 0; - } - - function getSlashingPenalty( - uint256 amount, - bool isCheater, - uint256 refundRatio - ) internal pure returns (uint256 penalty) { - if (!isCheater || refundRatio >= Decimal.unit()) { - return 0; - } - // round penalty upwards (ceiling) to prevent dust amount attacks - penalty = (amount * (Decimal.unit() - refundRatio)) / Decimal.unit() + 1; - if (penalty > amount) { - return amount; - } - return penalty; - } - - function _withdraw(address delegator, uint256 toValidatorID, uint256 wrID, address payable receiver) private { - WithdrawalRequest memory request = getWithdrawalRequest[delegator][toValidatorID][wrID]; - require(request.epoch != 0, "request doesn't exist"); - - uint256 requestTime = request.time; - uint256 requestEpoch = request.epoch; - if ( - getValidator[toValidatorID].deactivatedTime != 0 && - getValidator[toValidatorID].deactivatedTime < requestTime - ) { - requestTime = getValidator[toValidatorID].deactivatedTime; - requestEpoch = getValidator[toValidatorID].deactivatedEpoch; - } - - require(_now() >= requestTime + c.withdrawalPeriodTime(), "not enough time passed"); - require(currentEpoch() >= requestEpoch + c.withdrawalPeriodEpochs(), "not enough epochs passed"); - - uint256 amount = getWithdrawalRequest[delegator][toValidatorID][wrID].amount; - bool isCheater = isSlashed(toValidatorID); - uint256 penalty = getSlashingPenalty(amount, isCheater, slashingRefundRatio[toValidatorID]); - delete getWithdrawalRequest[delegator][toValidatorID][wrID]; - - totalSlashedStake += penalty; - require(amount > penalty, "stake is fully slashed"); - // It's important that we transfer after erasing (protection against Re-Entrancy) - (bool sent, ) = receiver.call{value: amount - penalty}(""); - require(sent, "Failed to send FTM"); - _burnFTM(penalty); - - emit Withdrawn(delegator, toValidatorID, wrID, amount); - } - - function withdraw(uint256 toValidatorID, uint256 wrID) public { - _withdraw(msg.sender, toValidatorID, wrID, _receiverOf(msg.sender)); - } - - function deactivateValidator(uint256 validatorID, uint256 status) external onlyDriver { - require(status != OK_STATUS, "wrong status"); - - _setValidatorDeactivated(validatorID, status); - _syncValidator(validatorID, false); - address validatorAddr = getValidator[validatorID].auth; - _recountVotes(validatorAddr, validatorAddr, false); - } - - function _highestPayableEpoch(uint256 validatorID) internal view returns (uint256) { - if (getValidator[validatorID].deactivatedEpoch != 0) { - if (currentSealedEpoch < getValidator[validatorID].deactivatedEpoch) { - return currentSealedEpoch; - } - return getValidator[validatorID].deactivatedEpoch; - } - return currentSealedEpoch; - } - - // find highest epoch such that _isLockedUpAtEpoch returns true (using binary search) - function _highestLockupEpoch(address delegator, uint256 validatorID) internal view returns (uint256) { - uint256 l = getLockupInfo[delegator][validatorID].fromEpoch; - uint256 r = currentSealedEpoch; - if (_isLockedUpAtEpoch(delegator, validatorID, r)) { - return r; - } - if (!_isLockedUpAtEpoch(delegator, validatorID, l)) { - return 0; - } - if (l > r) { - return 0; - } - while (l < r) { - uint256 m = (l + r) / 2; - if (_isLockedUpAtEpoch(delegator, validatorID, m)) { - l = m + 1; - } else { - r = m; - } - } - if (r == 0) { - return 0; - } - return r - 1; - } - - function _newRewards(address delegator, uint256 toValidatorID) internal view returns (Rewards memory) { - 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); - } - - function _newRewardsOf( - uint256 stakeAmount, - uint256 toValidatorID, - uint256 fromEpoch, - uint256 toEpoch - ) internal view returns (uint256) { - if (fromEpoch >= toEpoch) { - return 0; - } - uint256 stashedRate = getEpochSnapshot[fromEpoch].accumulatedRewardPerToken[toValidatorID]; - uint256 currentRate = getEpochSnapshot[toEpoch].accumulatedRewardPerToken[toValidatorID]; - 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; - } - - function stashRewards(address delegator, uint256 toValidatorID) external { - require(_stashRewards(delegator, toValidatorID), "nothing to stash"); - } - - function _stashRewards(address delegator, uint256 toValidatorID) internal returns (bool updated) { - Rewards memory 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; - } - - function _claimRewards(address delegator, uint256 toValidatorID) internal returns (Rewards memory rewards) { - _stashRewards(delegator, toValidatorID); - rewards = _rewardsStash[delegator][toValidatorID]; - uint256 totalReward = rewards.unlockedReward + rewards.lockupBaseReward + rewards.lockupExtraReward; - require(totalReward != 0, "zero rewards"); - delete _rewardsStash[delegator][toValidatorID]; - // It's important that we mint after erasing (protection against Re-Entrancy) - _mintNativeToken(totalReward); - return rewards; - } - - function claimRewards(uint256 toValidatorID) public { - address delegator = msg.sender; - Rewards memory 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 - }(""); - require(sent, "Failed to send FTM"); - - emit ClaimedRewards( - delegator, - toValidatorID, - rewards.lockupExtraReward, - rewards.lockupBaseReward, - rewards.unlockedReward - ); - } - - 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 - ); - } - - // burnFTM allows SFC to burn an arbitrary amount of FTM tokens - function burnFTM(uint256 amount) external onlyOwner { - _burnFTM(amount); - } - - function _burnFTM(uint256 amount) internal { - if (amount != 0) { - payable(address(0)).transfer(amount); - emit BurntFTM(amount); - } - } - - function epochEndTime(uint256 epoch) internal view returns (uint256) { - 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 { - require(!_redirected(delegator), "redirected"); - require(amount <= getUnlockedStake(delegator, toValidatorID), "not enough stake"); - require(getValidator[toValidatorID].status == OK_STATUS, "validator isn't active"); - - require( - lockupDuration >= c.minLockupDuration() && lockupDuration <= c.maxLockupDuration(), - "incorrect duration" - ); - uint256 endTime = _now() + lockupDuration; - address validatorAddr = getValidator[toValidatorID].auth; - if (delegator != validatorAddr) { - require( - getLockupInfo[validatorAddr][toValidatorID].endTime + 30 * 24 * 60 * 60 >= endTime, - "validator's lockup will end too early" - ); - } - - _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)); - require(penalties.length <= 30, "too many ongoing relocks"); - require( - amount > ld.lockedStake / 100 || penalties.length <= 3 || endTime >= ld.endTime + 14 * 24 * 60 * 60, - "too frequent relocks" - ); - } - } - - // check lockup duration after _stashRewards, which has erased previous lockup if it has unlocked already - require(lockupDuration >= ld.duration, "lockup duration cannot decrease"); - - 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; - require(amount > 0, "zero amount"); - require(!isLockedUp(delegator, toValidatorID), "already locked up"); - _lockStake(delegator, toValidatorID, lockupDuration, amount, false); - } - - function relockStake(uint256 toValidatorID, uint256 lockupDuration, uint256 amount) public { - address delegator = msg.sender; - require(isLockedUp(delegator, toValidatorID), "not locked up"); - _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]; - - require(amount > 0, "zero amount"); - require(isLockedUp(delegator, toValidatorID), "not locked up"); - require(amount <= ld.lockedStake, "not enough locked stake"); - require(!_redirected(delegator), "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}(""); - require(success, "Failed to send penalty"); - } - - emit UnlockedStake(delegator, toValidatorID, amount, penalty); - return penalty; - } - - function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio) external onlyOwner { - require(isSlashed(validatorID), "validator isn't slashed"); - require(refundRatio <= Decimal.unit(), "must be less than or equal to 1.0"); - slashingRefundRatio[validatorID] = refundRatio; - 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 redirectedAccs() private pure returns (address[] memory, address[] memory) { - // the addresses below were reported as stolen by their owners via the signatures below: - // I redirect SFC withdrawals to account 0x80f93310709624636852d0111fd6c4A6e02ED0aA due to a potential attacker gaining access to my account. - // { - // "address": "0x93419fcb5d9dc7989439f0512d4f737421ed48d9", - // "msg": "0x4920726564697265637420534643207769746864726177616c7320746f206163636f756e74203078383066393333313037303936323436333638353264303131316664366334413665303245443061412064756520746f206120706f74656e7469616c2061747461636b6572206761696e696e672061636365737320746f206d79206163636f756e742e", - // "sig": "1c4f3168e01d499a657f0d1cd453b26e5f69aaf14372983ff62e54a1d53959e55edb0746f4aea0959899b06bf31dc6a0160f6ac428cd75d4657184ab2337e46e1c", - // "version": "3", - // "signer": "MEW" - // } - // -- - // I redirect SFC withdrawals to account 0x91B20102Dfd2ff1b00D0915266584009d0b1Ae39 due to a potential attacker gaining access to my account. - // { - // "address": "0xfbcae1b28ca5039dafec4f10a89e022bc8118394", - // "msg": "0x4920726564697265637420534643207769746864726177616c7320746f206163636f756e74203078393142323031303244666432666631623030443039313532363635383430303964306231416533392064756520746f206120706f74656e7469616c2061747461636b6572206761696e696e672061636365737320746f206d79206163636f756e742e", - // "sig": "c98431cc1b6f26b8248ca83f860721f31ec79097831e69c28d352512182bbfa93911564ed46ba11547b544c4d65380781a4f3cc6afe9f075d43a24e0947853151c", - // "version": "3", - // "signer": "MEW" - // } - // -- - // I redirect SFC withdrawals to account 0xCA3C54c11172A7263300a801E9937780b5143c08 due to a potential attacker gaining access to my account. - // { - // "address": "0x15c2ec517905fb3282f26f3ac3e12889755a2ed7", - // "msg": "0x4920726564697265637420534643207769746864726177616c7320746f206163636f756e74203078434133433534633131313732413732363333303061383031453939333737383062353134336330382064756520746f206120706f74656e7469616c2061747461636b6572206761696e696e672061636365737320746f206d79206163636f756e742e", - // "sig": "8d933ea6b1dfaa70c92d7dd8f68e9c821934eabd9c454dc792a90c9c58d0c4ec5c60d7737e7b8ed38cfdfe3bd7fce9a2c38133b9a98d6699088d79edb09ec3c21b", - // "version": "3", - // "signer": "MEW" - // } - // -- - // I redirect SFC withdrawals to account 0x5A1CAd027EACE4C052f5DEE0f42Da6c62E39b779 due to a potential attacker gaining access to my account. - // { - // "address": "0xbdAaEC5f9317cC63D26FD7d79aD17372Ccd7d763", - // "msg": "0x4920726564697265637420534643207769746864726177616c7320746f206163636f756e74203078354131434164303237454143453443303532663544454530663432446136633632453339623737392064756520746f206120706f74656e7469616c2061747461636b6572206761696e696e672061636365737320746f206d79206163636f756e742e", - // "sig": "0e9b3ce37f665ab03bdfd3095671249e1b2842b1dd314fd4281bbed527ea69014ca510227e57f973b35ef175c1214fb1a842be70ff5a9290cb260799c544eed900", - // "version": "3", - // "signer": "MEW" - // } - // -- - // I redirect SFC withdrawals to account 0x4A15B527475977D9B0CB3fcfE825d6Aa7428fAFC due to a potential attacker gaining access to my account. - // { - // "address": "0xf72148504819A1D1B038694B02d299F65BfA312d", - // "msg": "0x4920726564697265637420534643207769746864726177616c7320746f206163636f756e74203078344131354235323734373539373744394230434233666366453832356436416137343238664146432064756520746f206120706f74656e7469616c2061747461636b6572206761696e696e672061636365737320746f206d79206163636f756e742e", - // "sig": "cf6386edbbee504c07ae95cb7c5ef06e7e0f57b34d51ab4e4047b5cb326af9bc236f544a3ced994cd20601047966e683aaaf329772fbb6bf37f0bd12200d1e6100", - // "version": "3", - // "signer": "MEW" - // } - // The contract does not lock these positions; instead, it restricts withdrawals exclusively to the account designated in the signature. - // This measure prevents an attacker from transferring FTM following a withdrawal. - - address[] memory froms = new address[](5); - address[] memory tos = new address[](5); - assert(froms.length == tos.length); - froms[0] = 0x93419FcB5d9DC7989439f0512d4F737421ed48D9; - tos[0] = 0x80f93310709624636852d0111fd6c4A6e02ED0aA; - froms[1] = 0xFbCAe1B28ca5039DAFec4f10A89e022Bc8118394; - tos[1] = 0x91B20102Dfd2ff1b00D0915266584009d0b1Ae39; - froms[2] = 0x15C2EC517905fB3282f26F3aC3e12889755a2ed7; - tos[2] = 0xCA3C54c11172A7263300a801E9937780b5143c08; - froms[3] = 0xbdAaEC5f9317cC63D26FD7d79aD17372Ccd7d763; - tos[3] = 0x5A1CAd027EACE4C052f5DEE0f42Da6c62E39b779; - froms[4] = 0xf72148504819A1D1B038694B02d299F65BfA312d; - tos[4] = 0x4A15B527475977D9B0CB3fcfE825d6Aa7428fAFC; - return (froms, tos); - } - - function _redirected(address addr) internal view returns (bool) { - (address[] memory froms, ) = redirectedAccs(); - for (uint256 i = 0; i < froms.length; i++) { - if (addr == froms[i]) { - return true; - } - } - return getRedirection[addr] != address(0); - } - - function _redirectedTo(address addr) internal view returns (address) { - (address[] memory froms, address[] memory tos) = redirectedAccs(); - for (uint256 i = 0; i < froms.length; i++) { - if (addr == froms[i]) { - return tos[i]; - } - } - return getRedirection[addr]; - } - - function _receiverOf(address addr) internal view returns (address payable) { - address to = _redirectedTo(addr); - if (to == address(0)) { - return payable(address(uint160(addr))); - } - 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 313dc39..34ea881 100644 --- a/contracts/sfc/SFCState.sol +++ b/contracts/sfc/SFCState.sol @@ -105,10 +105,4 @@ contract SFCState is Initializable, Ownable { mapping(uint256 => uint256) internal validatorPubkeyChanges; mapping(bytes32 => uint256) internal pubkeyHashToValidatorID; - - address public redirectionAuthorizer; - - mapping(address => address) public getRedirectionRequest; - - mapping(address => address) public getRedirection; } diff --git a/contracts/sfc/Updater.sol b/contracts/sfc/Updater.sol index 1d8140a..b68c2a4 100644 --- a/contracts/sfc/Updater.sol +++ b/contracts/sfc/Updater.sol @@ -18,7 +18,6 @@ interface GovVersion { contract Updater { address public sfcFrom; - address public sfcLib; address public sfcConsts; address public govTo; address public govFrom; @@ -27,7 +26,7 @@ contract Updater { constructor( address _sfcFrom, - address _sfcLib, + address _sfcLib, // to be removed address _sfcConsts, address _govTo, address _govFrom, @@ -35,7 +34,6 @@ contract Updater { address _owner ) { sfcFrom = _sfcFrom; - sfcLib = _sfcLib; sfcConsts = _sfcConsts; govTo = _govTo; govFrom = _govFrom; @@ -44,7 +42,6 @@ contract Updater { address sfcTo = address(0xFC00FACE00000000000000000000000000000000); require( sfcFrom != address(0) && - sfcLib != address(0) && sfcConsts != address(0) && govTo != address(0) && govFrom != address(0) && @@ -86,7 +83,6 @@ contract Updater { nodeAuth.upgradeCode(sfcTo, sfcFrom); SFCI(sfcTo).updateConstsAddress(sfcConsts); SFCI(sfcTo).updateVoteBookAddress(voteBook); - SFC(sfcTo).updateLibAddress(sfcLib); nodeAuth.upgradeCode(govTo, govFrom); GovI(govTo).upgrade(voteBook); diff --git a/contracts/test/UnitTestSFC.sol b/contracts/test/UnitTestSFC.sol index 263d8db..6de3092 100644 --- a/contracts/test/UnitTestSFC.sol +++ b/contracts/test/UnitTestSFC.sol @@ -3,74 +3,10 @@ pragma solidity ^0.8.9; import "../sfc/SFC.sol"; import "../sfc/SFCI.sol"; -import "../sfc/SFCLib.sol"; import "./UnitTestConstantsManager.sol"; -contract UnitTestSFCBase { - uint256 internal time; - bool public allowedNonNodeCalls; +contract UnitTestSFC is SFC { - function rebaseTime() external { - time = block.timestamp; - } - - function advanceTime(uint256 diff) external { - time += diff; - } - - function getTime() external view returns (uint256) { - return time; - } - - function getBlockTime() external view returns (uint256) { - return block.timestamp; - } - - function enableNonNodeCalls() external { - allowedNonNodeCalls = true; - } - - function disableNonNodeCalls() external { - allowedNonNodeCalls = false; - } -} - -contract UnitTestSFC is SFC, UnitTestSFCBase { - function _now() internal view override returns (uint256) { - return time; - } - - function isNode(address addr) internal view override returns (bool) { - if (allowedNonNodeCalls) { - return true; - } - return SFCBase.isNode(addr); - } -} - -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; - } - - function isNode(address addr) internal view override returns (bool) { - if (allowedNonNodeCalls) { - return true; - } - 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 {