diff --git a/contracts/sfc/SFC.sol b/contracts/sfc/SFC.sol index 0cce6b2..f40f0bd 100644 --- a/contracts/sfc/SFC.sol +++ b/contracts/sfc/SFC.sol @@ -170,12 +170,6 @@ contract SFC is Initializable, Ownable, Version { error TransfersNotAllowed(); error TransferFailed(); - // updater - error SFCAlreadyUpdated(); - error SFCWrongVersion(); - error SFCGovAlreadyUpdated(); - error SFCWrongGovVersion(); - // governance error GovVotesRecountFailed(); @@ -217,9 +211,8 @@ contract SFC is Initializable, Ownable, Version { _; } - /* - * Initializer - */ + /// Initialization is called only once, after the contract deployment. + /// Because the contract code is written directly into genesis, constructor cannot be used. function initialize( uint256 sealedEpoch, uint256 _totalSupply, @@ -236,10 +229,12 @@ contract SFC is Initializable, Ownable, Version { getEpochSnapshot[sealedEpoch].endTime = _now(); } + /// Receive fallback to revert transfers receive() external payable { revert TransfersNotAllowed(); } + /// Update validator's public key function updateValidatorPubkey(bytes calldata pubkey) external { if (pubkey.length != 66 || pubkey[0] != 0xc0) { revert MalformedPubkey(); @@ -264,6 +259,7 @@ contract SFC is Initializable, Ownable, Version { _syncValidator(validatorID, true); } + /// Set redirection authorizer for approving redirections function setRedirectionAuthorizer(address v) external onlyOwner { if (redirectionAuthorizer == v) { revert SameRedirectionAuthorizer(); @@ -271,10 +267,12 @@ contract SFC is Initializable, Ownable, Version { redirectionAuthorizer = v; } + // Announce redirection of address to another address function announceRedirection(address to) external { emit AnnouncedRedirection(msg.sender, to); } + // Initiate redirection request of address to another address function initiateRedirection(address from, address to) external { if (msg.sender != redirectionAuthorizer) { revert NotAuthorized(); @@ -288,6 +286,7 @@ contract SFC is Initializable, Ownable, Version { getRedirectionRequest[from] = to; } + // Finish redirection request and redirect address to another address function redirect(address to) external { address from = msg.sender; if (to == address(0)) { @@ -300,6 +299,8 @@ contract SFC is Initializable, Ownable, Version { getRedirectionRequest[from] = address(0); } + // Seal epoch and distribute rewards + // This method is called BEFORE the epoch sealing made by the client itself. function sealEpoch( uint256[] calldata offlineTime, uint256[] calldata offlineBlocks, @@ -328,6 +329,8 @@ contract SFC is Initializable, Ownable, Version { snapshot.totalSupply = totalSupply; } + // Seal epoch and set validators for the next epoch + // This method is called AFTER the epoch sealing made by the client itself. function sealEpochValidators(uint256[] calldata nextValidatorIDs) external onlyDriver { EpochSnapshot storage snapshot = getEpochSnapshot[currentEpoch()]; // fill data for the next snapshot @@ -341,6 +344,7 @@ contract SFC is Initializable, Ownable, Version { node.updateMinGasPrice(minGasPrice); } + // Set an initial validator. Called only as part of network initialization/genesis file generating. function setGenesisValidator( address auth, uint256 validatorID, @@ -362,11 +366,13 @@ contract SFC is Initializable, Ownable, Version { } } + // Set an initial delegation. Called only as part of network initialization/genesis file generating. function setGenesisDelegation(address delegator, uint256 toValidatorID, uint256 stake) external onlyDriver { _rawDelegate(delegator, toValidatorID, stake, false); _mintNativeToken(stake); } + // Create a validator with a given public key function createValidator(bytes calldata pubkey) external payable { if (msg.value < c.minSelfStake()) { revert InsufficientSelfStake(); @@ -381,6 +387,8 @@ contract SFC is Initializable, Ownable, Version { _delegate(msg.sender, lastValidatorID, msg.value); } + // Update slashing refund ratio for a validator + // The refund ratio is used to calculate the amount of stake that can be withdrawn after slashing function updateSlashingRefundRatio(uint256 validatorID, uint256 refundRatio) external onlyOwner { if (!isSlashed(validatorID)) { revert ValidatorNotSlashed(); @@ -392,6 +400,7 @@ contract SFC is Initializable, Ownable, Version { emit UpdatedSlashingRefundRatio(validatorID, refundRatio); } + // Recount votes for a delegator and a validator function recountVotes(address delegator, address validatorAuth, bool strict, uint256 gas) external { // solhint-disable-next-line avoid-low-level-calls (bool success, ) = voteBookAddress.call{gas: gas}( @@ -402,14 +411,18 @@ contract SFC is Initializable, Ownable, Version { } } + // Delegate stake to a validator function delegate(uint256 toValidatorID) external payable { _delegate(msg.sender, toValidatorID, msg.value); } + // Withdraw stake from a validator + // Un-delegated stake is locked for a certain period of time function withdraw(uint256 toValidatorID, uint256 wrID) public { _withdraw(msg.sender, toValidatorID, wrID, _receiverOf(msg.sender)); } + // Deactivate a validator function deactivateValidator(uint256 validatorID, uint256 status) external onlyDriver { if (status == OK_STATUS) { revert NotDeactivatedStatus(); @@ -421,6 +434,7 @@ contract SFC is Initializable, Ownable, Version { _recountVotes(validatorAddr, validatorAddr, false); } + // Stash rewards for a delegator function stashRewards(address delegator, uint256 toValidatorID) external { if (!_stashRewards(delegator, toValidatorID)) { revert NothingToStash(); @@ -432,22 +446,27 @@ contract SFC is Initializable, Ownable, Version { _burnFTM(amount); } + // Update treasury address function updateTreasuryAddress(address v) external onlyOwner { treasuryAddress = v; } + // Update consts address function updateConstsAddress(address v) external onlyOwner { c = ConstantsManager(v); } + // Update voteBook address function updateVoteBookAddress(address v) external onlyOwner { voteBookAddress = v; } + // Get consts address function constsAddress() external view returns (address) { return address(c); } + // Claim rewards for stake delegated to a validator function claimRewards(uint256 toValidatorID) public { address delegator = msg.sender; uint256 rewards = _claimRewards(delegator, toValidatorID); @@ -460,10 +479,12 @@ contract SFC is Initializable, Ownable, Version { emit ClaimedRewards(delegator, toValidatorID, rewards); } + // Get rewards stash for a delegator function rewardsStash(address delegator, uint256 validatorID) public view returns (uint256) { return _rewardsStash[delegator][validatorID]; } + // Un-delegate stake from a validator function undelegate(uint256 toValidatorID, uint256 wrID, uint256 amount) public { address delegator = msg.sender; @@ -488,6 +509,7 @@ contract SFC is Initializable, Ownable, Version { emit Undelegated(delegator, toValidatorID, wrID, amount); } + // Re-stake rewards for stake delegated to a validator function restakeRewards(uint256 toValidatorID) public { address delegator = msg.sender; uint256 rewards = _claimRewards(delegator, toValidatorID); @@ -496,65 +518,80 @@ contract SFC is Initializable, Ownable, Version { emit RestakedRewards(delegator, toValidatorID, rewards); } + // Get the current epoch number function currentEpoch() public view returns (uint256) { return currentSealedEpoch + 1; } + // Get self-stake of a validator function getSelfStake(uint256 validatorID) public view returns (uint256) { return getStake[getValidator[validatorID].auth][validatorID]; } + // Get validator IDs for given epoch function getEpochValidatorIDs(uint256 epoch) public view returns (uint256[] memory) { return getEpochSnapshot[epoch].validatorIDs; } + // Get received stake for a validator in a given epoch function getEpochReceivedStake(uint256 epoch, uint256 validatorID) public view returns (uint256) { return getEpochSnapshot[epoch].receivedStake[validatorID]; } + // Get accumulated reward per token for a validator in a given epoch function getEpochAccumulatedRewardPerToken(uint256 epoch, uint256 validatorID) public view returns (uint256) { return getEpochSnapshot[epoch].accumulatedRewardPerToken[validatorID]; } + // Get accumulated uptime for a validator in a given epoch function getEpochAccumulatedUptime(uint256 epoch, uint256 validatorID) public view returns (uint256) { return getEpochSnapshot[epoch].accumulatedUptime[validatorID]; } + // Get accumulated originated txs fee for a validator in a given epoch function getEpochAccumulatedOriginatedTxsFee(uint256 epoch, uint256 validatorID) public view returns (uint256) { return getEpochSnapshot[epoch].accumulatedOriginatedTxsFee[validatorID]; } + // Get offline time for a validator in a given epoch function getEpochOfflineTime(uint256 epoch, uint256 validatorID) public view returns (uint256) { return getEpochSnapshot[epoch].offlineTime[validatorID]; } + // Get offline blocks for a validator in a given epoch function getEpochOfflineBlocks(uint256 epoch, uint256 validatorID) public view returns (uint256) { return getEpochSnapshot[epoch].offlineBlocks[validatorID]; } + // Get end block for a given epoch function getEpochEndBlock(uint256 epoch) public view returns (uint256) { return getEpochSnapshot[epoch].endBlock; } + // Get slashed status for a validator function isSlashed(uint256 validatorID) public view returns (bool) { return getValidator[validatorID].status & CHEATER_MASK != 0; } + // Get pending rewards for given delegator and validator function pendingRewards(address delegator, uint256 toValidatorID) public view returns (uint256) { uint256 reward = _newRewards(delegator, toValidatorID); return _rewardsStash[delegator][toValidatorID] + reward; } + // Check delegation limit for a validator function _checkDelegatedStakeLimit(uint256 validatorID) internal view returns (bool) { return getValidator[validatorID].receivedStake <= (getSelfStake(validatorID) * c.maxDelegatedRatio()) / Decimal.unit(); } + // Check if an address is a node function isNode(address addr) internal view virtual returns (bool) { return addr == address(node); } + // Delegate stake to a validator function _delegate(address delegator, uint256 toValidatorID, uint256 amount) internal { if (!_validatorExists(toValidatorID)) { revert ValidatorNotExists(); @@ -568,6 +605,7 @@ contract SFC is Initializable, Ownable, Version { } } + // Delegate stake to a validator without checking delegation limit function _rawDelegate(address delegator, uint256 toValidatorID, uint256 amount, bool strict) internal { if (amount == 0) { revert ZeroAmount(); @@ -590,6 +628,7 @@ contract SFC is Initializable, Ownable, Version { _recountVotes(delegator, getValidator[toValidatorID].auth, strict); } + // Un-delegate stake from a validator function _rawUndelegate( address delegator, uint256 toValidatorID, @@ -624,6 +663,7 @@ contract SFC is Initializable, Ownable, Version { _recountVotes(delegator, getValidator[toValidatorID].auth, strict); } + // Get slashing penalty for a stake function getSlashingPenalty( uint256 amount, bool isCheater, @@ -640,6 +680,7 @@ contract SFC is Initializable, Ownable, Version { return penalty; } + // Withdraw stake from a validator function _withdraw(address delegator, uint256 toValidatorID, uint256 wrID, address payable receiver) private { WithdrawalRequest memory request = getWithdrawalRequest[delegator][toValidatorID][wrID]; if (request.epoch == 0) { @@ -682,6 +723,9 @@ contract SFC is Initializable, Ownable, Version { emit Withdrawn(delegator, toValidatorID, wrID, amount); } + // Get highest payable epoch for a validator + // If the validator is deactivated, the highest payable epoch is the deactivation epoch + // or the current epoch, whichever is lower function _highestPayableEpoch(uint256 validatorID) internal view returns (uint256) { if (getValidator[validatorID].deactivatedEpoch != 0) { if (currentSealedEpoch < getValidator[validatorID].deactivatedEpoch) { @@ -692,6 +736,8 @@ contract SFC is Initializable, Ownable, Version { return currentSealedEpoch; } + // Get new rewards for a delegator + // The rewards are calculated from the last stashed epoch until the highest payable epoch function _newRewards(address delegator, uint256 toValidatorID) internal view returns (uint256) { uint256 stashedUntil = stashedRewardsUntilEpoch[delegator][toValidatorID]; uint256 payableUntil = _highestPayableEpoch(toValidatorID); @@ -700,6 +746,7 @@ contract SFC is Initializable, Ownable, Version { return fullReward; } + // Get new rewards for a delegator for a given stake amount and epoch range function _newRewardsOf( uint256 stakeAmount, uint256 toValidatorID, @@ -714,6 +761,7 @@ contract SFC is Initializable, Ownable, Version { return ((currentRate - stashedRate) * stakeAmount) / Decimal.unit(); } + // Stash rewards for a delegator function _stashRewards(address delegator, uint256 toValidatorID) internal returns (bool updated) { uint256 nonStashedReward = _newRewards(delegator, toValidatorID); stashedRewardsUntilEpoch[delegator][toValidatorID] = _highestPayableEpoch(toValidatorID); @@ -721,6 +769,7 @@ contract SFC is Initializable, Ownable, Version { return nonStashedReward != 0; } + // Claim rewards for a delegator function _claimRewards(address delegator, uint256 toValidatorID) internal returns (uint256) { _stashRewards(delegator, toValidatorID); uint256 rewards = _rewardsStash[delegator][toValidatorID]; @@ -733,6 +782,8 @@ contract SFC is Initializable, Ownable, Version { return rewards; } + // Burn FTM tokens + // The tokens are sent to the zero address function _burnFTM(uint256 amount) internal { if (amount != 0) { payable(address(0)).transfer(amount); @@ -740,14 +791,17 @@ contract SFC is Initializable, Ownable, Version { } } + // Get epoch end time function epochEndTime(uint256 epoch) internal view returns (uint256) { return getEpochSnapshot[epoch].endTime; } + // Check if an address is redirected function _redirected(address addr) internal view returns (bool) { return getRedirection[addr] != address(0); } + // Get receiver address and check for redirection function _receiverOf(address addr) internal view returns (address payable) { address to = getRedirection[addr]; if (to == address(0)) { @@ -756,10 +810,7 @@ contract SFC is Initializable, Ownable, Version { return payable(address(uint160(to))); } - /* - Epoch callbacks - */ - + // Seal epoch - sync validators function _sealEpochOffline( EpochSnapshot storage snapshot, uint256[] memory validatorIDs, @@ -781,6 +832,7 @@ contract SFC is Initializable, Ownable, Version { } } + // Seal epoch - calculate rewards function _sealEpochRewards( uint256 epochDuration, EpochSnapshot storage snapshot, @@ -872,6 +924,7 @@ contract SFC is Initializable, Ownable, Version { } } + // Seal epoch - calculate min gas price for the next epoch function _sealEpochMinGasPrice(uint256 epochDuration, uint256 epochGas) internal { // change minGasPrice proportionally to the difference between target and received epochGas uint256 targetEpochGas = epochDuration * c.targetGasPowerPerSecond() + 1; @@ -892,11 +945,13 @@ contract SFC is Initializable, Ownable, Version { minGasPrice = newMinGasPrice; } + // Create a new validator function _createValidator(address auth, bytes memory pubkey) internal { uint256 validatorID = ++lastValidatorID; _rawCreateValidator(auth, validatorID, pubkey, OK_STATUS, currentEpoch(), _now(), 0, 0); } + // Create a new validator without incrementing lastValidatorID function _rawCreateValidator( address auth, uint256 validatorID, @@ -929,6 +984,7 @@ contract SFC is Initializable, Ownable, Version { } } + // Calculate raw validator epoch transaction reward function _calcRawValidatorEpochTxReward( uint256 epochFee, uint256 txRewardWeight, @@ -942,6 +998,7 @@ contract SFC is Initializable, Ownable, Version { return (txReward * (Decimal.unit() - c.burntFeeShare() - c.treasuryFeeShare())) / Decimal.unit(); } + // Calculate raw validator epoch base reward function _calcRawValidatorEpochBaseReward( uint256 epochDuration, uint256 _baseRewardPerSecond, @@ -955,12 +1012,14 @@ contract SFC is Initializable, Ownable, Version { return (totalReward * baseRewardWeight) / totalBaseRewardWeight; } + // Mint native token function _mintNativeToken(uint256 amount) internal { // balance will be increased after the transaction is processed node.incBalance(address(this), amount); totalSupply = totalSupply + amount; } + // Recount votes for a delegator and a validator function _recountVotes(address delegator, address validatorAuth, bool strict) internal { if (voteBookAddress != address(0)) { // Don't allow recountVotes to use up all the gas @@ -975,6 +1034,7 @@ contract SFC is Initializable, Ownable, Version { } } + // Set validator deactivated status function _setValidatorDeactivated(uint256 validatorID, uint256 status) internal { if (getValidator[validatorID].status == OK_STATUS && status != OK_STATUS) { totalActiveStake = totalActiveStake - getValidator[validatorID].receivedStake; @@ -995,6 +1055,7 @@ contract SFC is Initializable, Ownable, Version { } } + // Sync validator with node function _syncValidator(uint256 validatorID, bool syncPubkey) public { if (!_validatorExists(validatorID)) { revert ValidatorNotExists(); @@ -1010,14 +1071,17 @@ contract SFC is Initializable, Ownable, Version { } } + // Check if a validator exists function _validatorExists(uint256 validatorID) internal view returns (bool) { return getValidator[validatorID].createdTime != 0; } + // Calculate validator commission function _calcValidatorCommission(uint256 rawReward, uint256 commission) internal pure returns (uint256) { return (rawReward * commission) / Decimal.unit(); } + // Get current time function _now() internal view virtual returns (uint256) { return block.timestamp; }