Created
May 31, 2022 09:02
-
-
Save dome/31c46b52cb0c330b8d9f3c0213edbfaf to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Dependency file: contracts/interfaces/IChainConfig.sol | |
| // SPDX-License-Identifier: GPL-3.0-only | |
| // pragma solidity ^0.8.0; | |
| interface IChainConfig { | |
| function getActiveValidatorsLength() external view returns (uint32); | |
| function setActiveValidatorsLength(uint32 newValue) external; | |
| function getEpochBlockInterval() external view returns (uint32); | |
| function setEpochBlockInterval(uint32 newValue) external; | |
| function getMisdemeanorThreshold() external view returns (uint32); | |
| function setMisdemeanorThreshold(uint32 newValue) external; | |
| function getFelonyThreshold() external view returns (uint32); | |
| function setFelonyThreshold(uint32 newValue) external; | |
| function getValidatorJailEpochLength() external view returns (uint32); | |
| function setValidatorJailEpochLength(uint32 newValue) external; | |
| function getUndelegatePeriod() external view returns (uint32); | |
| function setUndelegatePeriod(uint32 newValue) external; | |
| function getMinValidatorStakeAmount() external view returns (uint256); | |
| function setMinValidatorStakeAmount(uint256 newValue) external; | |
| function getMinStakingAmount() external view returns (uint256); | |
| function setMinStakingAmount(uint256 newValue) external; | |
| } | |
| // Dependency file: contracts/interfaces/IGovernance.sol | |
| // pragma solidity ^0.8.0; | |
| interface IGovernance { | |
| function getVotingSupply() external view returns (uint256); | |
| function getVotingPower(address validator) external view returns (uint256); | |
| } | |
| // Dependency file: contracts/interfaces/ISlashingIndicator.sol | |
| // pragma solidity ^0.8.0; | |
| interface ISlashingIndicator { | |
| function slash(address validator) external; | |
| } | |
| // Dependency file: contracts/interfaces/ISystemReward.sol | |
| // pragma solidity ^0.8.0; | |
| interface ISystemReward { | |
| function updateDistributionShare(address[] calldata accounts, uint16[] calldata shares) external; | |
| function getSystemFee() external view returns (uint256); | |
| function claimSystemFee() external; | |
| } | |
| // Dependency file: contracts/interfaces/IRuntimeUpgradeEvmHook.sol | |
| // pragma solidity ^0.8.0; | |
| interface IRuntimeUpgradeEvmHook { | |
| function upgradeTo(address contractAddress, bytes calldata byteCode) external; | |
| function deployTo(address contractAddress, bytes calldata byteCode) external; | |
| } | |
| // Dependency file: contracts/interfaces/IValidatorSet.sol | |
| // pragma solidity ^0.8.0; | |
| interface IValidatorSet { | |
| function getValidators() external view returns (address[] memory); | |
| function deposit(address validator) external payable; | |
| } | |
| // Dependency file: contracts/interfaces/IStaking.sol | |
| // pragma solidity ^0.8.0; | |
| // import "contracts/interfaces/IValidatorSet.sol"; | |
| interface IStaking is IValidatorSet { | |
| function currentEpoch() external view returns (uint64); | |
| function nextEpoch() external view returns (uint64); | |
| function isValidatorActive(address validator) external view returns (bool); | |
| function isValidator(address validator) external view returns (bool); | |
| function getValidatorStatus(address validator) external view returns ( | |
| address ownerAddress, | |
| uint8 status, | |
| uint256 totalDelegated, | |
| uint32 slashesCount, | |
| uint64 changedAt, | |
| uint64 jailedBefore, | |
| uint64 claimedAt, | |
| uint16 commissionRate, | |
| uint96 totalRewards | |
| ); | |
| function getValidatorStatusAtEpoch(address validator, uint64 epoch) external view returns ( | |
| address ownerAddress, | |
| uint8 status, | |
| uint256 totalDelegated, | |
| uint32 slashesCount, | |
| uint64 changedAt, | |
| uint64 jailedBefore, | |
| uint64 claimedAt, | |
| uint16 commissionRate, | |
| uint96 totalRewards | |
| ); | |
| function getValidatorByOwner(address owner) external view returns (address); | |
| function registerValidator(address validator, uint16 commissionRate) payable external; | |
| function addValidator(address validator) external; | |
| function removeValidator(address validator) external; | |
| function activateValidator(address validator) external; | |
| function disableValidator(address validator) external; | |
| function releaseValidatorFromJail(address validator) external; | |
| function changeValidatorCommissionRate(address validator, uint16 commissionRate) external; | |
| function changeValidatorOwner(address validator, address newOwner) external; | |
| function getValidatorDelegation(address validator, address delegator) external view returns ( | |
| uint256 delegatedAmount, | |
| uint64 atEpoch | |
| ); | |
| function delegate(address validator) payable external; | |
| function undelegate(address validator, uint256 amount) external; | |
| function getValidatorFee(address validator) external view returns (uint256); | |
| function getPendingValidatorFee(address validator) external view returns (uint256); | |
| function claimValidatorFee(address validator) external; | |
| function claimValidatorFeeAtEpoch(address validator, uint64 beforeEpoch) external; | |
| function getDelegatorFee(address validator, address delegator) external view returns (uint256); | |
| function getPendingDelegatorFee(address validator, address delegator) external view returns (uint256); | |
| function claimDelegatorFee(address validator) external; | |
| function calcAvailableForRedelegateAmount(address validator, address delegator) external view returns (uint256 amountToStake, uint256 rewardsDust); | |
| function redelegateDelegatorFee(address validator) external; | |
| function claimDelegatorFeeAtEpoch(address validator, uint64 beforeEpoch) external; | |
| function slash(address validator) external; | |
| } | |
| // Dependency file: contracts/interfaces/IRuntimeUpgrade.sol | |
| // pragma solidity ^0.8.0; | |
| interface IRuntimeUpgrade { | |
| function getEvmHookAddress() external view returns (address); | |
| function upgradeSystemSmartContract(address systemContractAddress, bytes calldata newByteCode, bytes calldata applyFunction) external; | |
| function deploySystemSmartContract(address systemContractAddress, bytes calldata newByteCode, bytes calldata applyFunction) external; | |
| function getSystemContracts() external view returns (address[] memory); | |
| } | |
| // Dependency file: contracts/interfaces/IStakingPool.sol | |
| // pragma solidity ^0.8.0; | |
| interface IStakingPool { | |
| function getStakedAmount(address validator, address staker) external view returns (uint256); | |
| function stake(address validator) external payable; | |
| function unstake(address validator, uint256 amount) external; | |
| function claimableRewards(address validator, address staker) external view returns (uint256); | |
| function claim(address validator) external; | |
| } | |
| // Dependency file: contracts/interfaces/IDeployerProxy.sol | |
| // pragma solidity ^0.8.0; | |
| interface IDeployerProxy { | |
| function registerDeployedContract(address account, address impl) external; | |
| function checkContractActive(address impl) external; | |
| function isDeployer(address account) external view returns (bool); | |
| function getContractState(address contractAddress) external view returns (uint8 state, address impl, address deployer); | |
| function isBanned(address account) external view returns (bool); | |
| function addDeployer(address account) external; | |
| function banDeployer(address account) external; | |
| function unbanDeployer(address account) external; | |
| function removeDeployer(address account) external; | |
| function disableContract(address contractAddress) external; | |
| function enableContract(address contractAddress) external; | |
| } | |
| // Dependency file: contracts/interfaces/IInjector.sol | |
| // pragma solidity ^0.8.0; | |
| // import "contracts/interfaces/ISlashingIndicator.sol"; | |
| // import "contracts/interfaces/ISystemReward.sol"; | |
| // import "contracts/interfaces/IGovernance.sol"; | |
| // import "contracts/interfaces/IStaking.sol"; | |
| // import "contracts/interfaces/IDeployerProxy.sol"; | |
| // import "contracts/interfaces/IStakingPool.sol"; | |
| // import "contracts/interfaces/IChainConfig.sol"; | |
| interface IInjector { | |
| function init() external; | |
| function isInitialized() external view returns (bool); | |
| function getStaking() external view returns (IStaking); | |
| function getSlashingIndicator() external view returns (ISlashingIndicator); | |
| function getSystemReward() external view returns (ISystemReward); | |
| function getStakingPool() external view returns (IStakingPool); | |
| function getGovernance() external view returns (IGovernance); | |
| function getChainConfig() external view returns (IChainConfig); | |
| } | |
| // Dependency file: contracts/Injector.sol | |
| // pragma solidity ^0.8.0; | |
| // import "contracts/interfaces/IChainConfig.sol"; | |
| // import "contracts/interfaces/IGovernance.sol"; | |
| // import "contracts/interfaces/ISlashingIndicator.sol"; | |
| // import "contracts/interfaces/ISystemReward.sol"; | |
| // import "contracts/interfaces/IRuntimeUpgradeEvmHook.sol"; | |
| // import "contracts/interfaces/IValidatorSet.sol"; | |
| // import "contracts/interfaces/IStaking.sol"; | |
| // import "contracts/interfaces/IRuntimeUpgrade.sol"; | |
| // import "contracts/interfaces/IStakingPool.sol"; | |
| // import "contracts/interfaces/IInjector.sol"; | |
| // import "contracts/interfaces/IDeployerProxy.sol"; | |
| abstract contract AlreadyInit { | |
| // flag indicating is smart contract initialized already | |
| bool internal _init; | |
| modifier initializer() { | |
| require(!_init, "Injector: already initialized"); | |
| _; | |
| _init = true; | |
| } | |
| modifier whenNotInitialized() { | |
| require(!_init, "Injector: already initialized"); | |
| _; | |
| } | |
| modifier whenInitialized() { | |
| require(_init, "Injector: not initialized yet"); | |
| _; | |
| } | |
| } | |
| abstract contract InjectorContextHolder is AlreadyInit, IInjector { | |
| // system smart contract constructor | |
| bytes internal _ctor; | |
| // BSC compatible contracts | |
| IStaking internal _stakingContract; | |
| ISlashingIndicator internal _slashingIndicatorContract; | |
| ISystemReward internal _systemRewardContract; | |
| // BAS defined contracts | |
| IStakingPool internal _stakingPoolContract; | |
| IGovernance internal _governanceContract; | |
| IChainConfig internal _chainConfigContract; | |
| IRuntimeUpgrade internal _runtimeUpgradeContract; | |
| IDeployerProxy internal _deployerProxyContract; | |
| // already init (1) + ctor(1) + injector (8) = 10 | |
| uint256[100 - 10] private __reserved; | |
| constructor(bytes memory constructorParams) { | |
| // save constructor params to use them in the init function | |
| _ctor = constructorParams; | |
| } | |
| function init() external initializer { | |
| // BSC compatible addresses | |
| _stakingContract = IStaking(0x0000000000000000000000000000000000001000); | |
| _slashingIndicatorContract = ISlashingIndicator(0x0000000000000000000000000000000000001001); | |
| _systemRewardContract = ISystemReward(0x0000000000000000000000000000000000001002); | |
| // BAS defined addresses | |
| _stakingPoolContract = IStakingPool(0x0000000000000000000000000000000000007001); | |
| _governanceContract = IGovernance(0x0000000000000000000000000000000000007002); | |
| _chainConfigContract = IChainConfig(0x0000000000000000000000000000000000007003); | |
| _runtimeUpgradeContract = IRuntimeUpgrade(0x0000000000000000000000000000000000007004); | |
| _deployerProxyContract = IDeployerProxy(0x0000000000000000000000000000000000007005); | |
| // invoke constructor | |
| _invokeContractConstructor(); | |
| } | |
| function initManually( | |
| IStaking stakingContract, | |
| ISlashingIndicator slashingIndicatorContract, | |
| ISystemReward systemRewardContract, | |
| IStakingPool stakingPoolContract, | |
| IGovernance governanceContract, | |
| IChainConfig chainConfigContract, | |
| IRuntimeUpgrade runtimeUpgradeContract, | |
| IDeployerProxy deployerProxyContract | |
| ) public initializer { | |
| // BSC-compatible | |
| _stakingContract = stakingContract; | |
| _slashingIndicatorContract = slashingIndicatorContract; | |
| _systemRewardContract = systemRewardContract; | |
| // BAS-defined | |
| _stakingPoolContract = stakingPoolContract; | |
| _governanceContract = governanceContract; | |
| _chainConfigContract = chainConfigContract; | |
| _runtimeUpgradeContract = runtimeUpgradeContract; | |
| _deployerProxyContract = deployerProxyContract; | |
| // invoke constructor | |
| _invokeContractConstructor(); | |
| } | |
| function _invokeContractConstructor() internal { | |
| if (_ctor.length == 0) { | |
| return; | |
| } | |
| (bool success, bytes memory returnData) = address(this).call(_ctor); | |
| // if everything is success then just exit w/o revert | |
| if (success) { | |
| return; | |
| } | |
| if (returnData.length == 0) { | |
| revert("Injector: construction failed w/ unknown error"); | |
| } | |
| assembly { | |
| let returnDataSize := mload(returnData) | |
| revert(add(32, returnData), returnDataSize) | |
| } | |
| } | |
| function isInitialized() external view returns (bool) { | |
| return _init; | |
| } | |
| modifier onlyFromCoinbase() { | |
| require(msg.sender == block.coinbase, "InjectorContextHolder: only coinbase"); | |
| _; | |
| } | |
| modifier onlyFromSlashingIndicator() { | |
| require(msg.sender == address(_slashingIndicatorContract), "InjectorContextHolder: only slashing indicator"); | |
| _; | |
| } | |
| modifier onlyFromGovernance() { | |
| require(IGovernance(msg.sender) == _governanceContract, "InjectorContextHolder: only governance"); | |
| _; | |
| } | |
| modifier onlyFromRuntimeUpgrade() { | |
| require(IRuntimeUpgrade(msg.sender) == _runtimeUpgradeContract, "InjectorContextHolder: only runtime upgrade"); | |
| _; | |
| } | |
| modifier onlyZeroGasPrice() { | |
| require(tx.gasprice == 0, "InjectorContextHolder: only zero gas price"); | |
| _; | |
| } | |
| function getStaking() public view returns (IStaking) { | |
| return _stakingContract; | |
| } | |
| function getSlashingIndicator() public view returns (ISlashingIndicator) { | |
| return _slashingIndicatorContract; | |
| } | |
| function getSystemReward() public view returns (ISystemReward) { | |
| return _systemRewardContract; | |
| } | |
| function getStakingPool() public view returns (IStakingPool) { | |
| return _stakingPoolContract; | |
| } | |
| function getGovernance() public view returns (IGovernance) { | |
| return _governanceContract; | |
| } | |
| function getChainConfig() public view returns (IChainConfig) { | |
| return _chainConfigContract; | |
| } | |
| } | |
| // Root file: contracts/Staking.sol | |
| pragma solidity ^0.8.0; | |
| // import "contracts/Injector.sol"; | |
| contract Staking is IStaking, InjectorContextHolder { | |
| /** | |
| * This constant indicates precision of storing compact balances in the storage or floating point. Since default | |
| * balance precision is 256 bits it might gain some overhead on the storage because we don't need to store such huge | |
| * amount range. That is why we compact balances in uint112 values instead of uint256. By managing this value | |
| * you can set the precision of your balances, aka min and max possible staking amount. This value depends | |
| * mostly on your asset price in USD, for example ETH costs 4000$ then if we use 1 ether precision it takes 4000$ | |
| * as min amount that might be problematic for users to do the stake. We can set 1 gwei precision and in this case | |
| * we increase min staking amount in 1e9 times, but also decreases max staking amount or total amount of staked assets. | |
| * | |
| * Here is an universal formula, if your asset is cheap in USD equivalent, like ~1$, then use 1 ether precision, | |
| * otherwise it might be better to use 1 gwei precision or any other amount that your want. | |
| * | |
| * Also be careful with setting `minValidatorStakeAmount` and `minStakingAmount`, because these values has | |
| * the same precision as specified here. It means that if you set precision 1 ether, then min staking amount of 10 | |
| * tokens should have 10 raw value. For 1 gwei precision 10 tokens min amount should be stored as 10000000000. | |
| * | |
| * For the 112 bits we have ~32 decimals lg(2**112)=33.71 (lets round to 32 for simplicity). We split this amount | |
| * into integer (24) and for fractional (8) parts. It means that we can have only 8 decimals after zero. | |
| * | |
| * Based in current params we have next min/max values: | |
| * - min staking amount: 0.00000001 or 1e-8 | |
| * - max staking amount: 1000000000000000000000000 or 1e+24 | |
| * | |
| * WARNING: precision must be a 1eN format (A=1, N>0) | |
| */ | |
| uint256 internal constant BALANCE_COMPACT_PRECISION = 1e10; | |
| /** | |
| * Here is min/max commission rates. Lets don't allow to set more than 30% of validator commission, because it's | |
| * too big commission for validator. Commission rate is a percents divided by 100 stored with 0 decimals as percents*100 (=pc/1e2*1e4) | |
| * | |
| * Here is some examples: | |
| * + 0.3% => 0.3*100=30 | |
| * + 3% => 3*100=300 | |
| * + 30% => 30*100=3000 | |
| */ | |
| uint16 internal constant COMMISSION_RATE_MIN_VALUE = 0; // 0% | |
| uint16 internal constant COMMISSION_RATE_MAX_VALUE = 3000; // 30% | |
| /** | |
| * This gas limit is used for internal transfers, BSC doesn't support berlin and it | |
| * might cause problems with smart contracts who used to stake transparent proxies or | |
| * beacon proxies that have a lot of expensive SLOAD instructions. | |
| */ | |
| uint64 internal constant TRANSFER_GAS_LIMIT = 30000; | |
| // validator events | |
| event ValidatorAdded(address indexed validator, address owner, uint8 status, uint16 commissionRate); | |
| event ValidatorModified(address indexed validator, address owner, uint8 status, uint16 commissionRate); | |
| event ValidatorRemoved(address indexed validator); | |
| event ValidatorOwnerClaimed(address indexed validator, uint256 amount, uint64 epoch); | |
| event ValidatorSlashed(address indexed validator, uint32 slashes, uint64 epoch); | |
| event ValidatorJailed(address indexed validator, uint64 epoch); | |
| event ValidatorDeposited(address indexed validator, uint256 amount, uint64 epoch); | |
| event ValidatorReleased(address indexed validator, uint64 epoch); | |
| // staker events | |
| event Delegated(address indexed validator, address indexed staker, uint256 amount, uint64 epoch); | |
| event Undelegated(address indexed validator, address indexed staker, uint256 amount, uint64 epoch); | |
| event Claimed(address indexed validator, address indexed staker, uint256 amount, uint64 epoch); | |
| event Redelegated(address indexed validator, address indexed staker, uint256 amount, uint256 dust, uint64 epoch); | |
| enum ValidatorStatus { | |
| NotFound, | |
| Active, | |
| Pending, | |
| Jail | |
| } | |
| struct ValidatorSnapshot { | |
| uint96 totalRewards; | |
| uint112 totalDelegated; | |
| uint32 slashesCount; | |
| uint16 commissionRate; | |
| } | |
| struct Validator { | |
| address validatorAddress; | |
| address ownerAddress; | |
| ValidatorStatus status; | |
| uint64 changedAt; | |
| uint64 jailedBefore; | |
| uint64 claimedAt; | |
| } | |
| struct DelegationOpDelegate { | |
| uint112 amount; | |
| uint64 epoch; | |
| } | |
| struct DelegationOpUndelegate { | |
| uint112 amount; | |
| uint64 epoch; | |
| } | |
| struct ValidatorDelegation { | |
| DelegationOpDelegate[] delegateQueue; | |
| uint64 delegateGap; | |
| DelegationOpUndelegate[] undelegateQueue; | |
| uint64 undelegateGap; | |
| } | |
| // mapping from validator address to validator | |
| mapping(address => Validator) internal _validatorsMap; | |
| // mapping from validator owner to validator address | |
| mapping(address => address) internal _validatorOwners; | |
| // list of all validators that are in validators mapping | |
| address[] internal _activeValidatorsList; | |
| // mapping with stakers to validators at epoch (validator -> delegator -> delegation) | |
| mapping(address => mapping(address => ValidatorDelegation)) internal _validatorDelegations; | |
| // mapping with validator snapshots per each epoch (validator -> epoch -> snapshot) | |
| mapping(address => mapping(uint64 => ValidatorSnapshot)) internal _validatorSnapshots; | |
| constructor(bytes memory constructorParams) InjectorContextHolder(constructorParams) { | |
| } | |
| function ctor(address[] calldata validators, uint256[] calldata initialStakes, uint16 commissionRate) external whenNotInitialized { | |
| require(initialStakes.length == validators.length); | |
| uint256 totalStakes = 0; | |
| for (uint256 i = 0; i < validators.length; i++) { | |
| _addValidator(validators[i], validators[i], ValidatorStatus.Active, commissionRate, initialStakes[i], 0); | |
| totalStakes += initialStakes[i]; | |
| } | |
| require(address(this).balance == totalStakes, "Staking: initial stake balance mismatch"); | |
| } | |
| function getValidatorDelegation(address validatorAddress, address delegator) external view override returns ( | |
| uint256 delegatedAmount, | |
| uint64 atEpoch | |
| ) { | |
| ValidatorDelegation memory delegation = _validatorDelegations[validatorAddress][delegator]; | |
| if (delegation.delegateQueue.length == 0) { | |
| return (delegatedAmount = 0, atEpoch = 0); | |
| } | |
| DelegationOpDelegate memory snapshot = delegation.delegateQueue[delegation.delegateQueue.length - 1]; | |
| return (delegatedAmount = uint256(snapshot.amount) * BALANCE_COMPACT_PRECISION, atEpoch = snapshot.epoch); | |
| } | |
| function getValidatorStatus(address validatorAddress) external view override returns ( | |
| address ownerAddress, | |
| uint8 status, | |
| uint256 totalDelegated, | |
| uint32 slashesCount, | |
| uint64 changedAt, | |
| uint64 jailedBefore, | |
| uint64 claimedAt, | |
| uint16 commissionRate, | |
| uint96 totalRewards | |
| ) { | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| ValidatorSnapshot memory snapshot = _validatorSnapshots[validator.validatorAddress][validator.changedAt]; | |
| return ( | |
| ownerAddress = validator.ownerAddress, | |
| status = uint8(validator.status), | |
| totalDelegated = uint256(snapshot.totalDelegated) * BALANCE_COMPACT_PRECISION, | |
| slashesCount = snapshot.slashesCount, | |
| changedAt = validator.changedAt, | |
| jailedBefore = validator.jailedBefore, | |
| claimedAt = validator.claimedAt, | |
| commissionRate = snapshot.commissionRate, | |
| totalRewards = snapshot.totalRewards | |
| ); | |
| } | |
| function getValidatorStatusAtEpoch(address validatorAddress, uint64 epoch) external view returns ( | |
| address ownerAddress, | |
| uint8 status, | |
| uint256 totalDelegated, | |
| uint32 slashesCount, | |
| uint64 changedAt, | |
| uint64 jailedBefore, | |
| uint64 claimedAt, | |
| uint16 commissionRate, | |
| uint96 totalRewards | |
| ) { | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| ValidatorSnapshot memory snapshot = _touchValidatorSnapshotImmutable(validator, epoch); | |
| return ( | |
| ownerAddress = validator.ownerAddress, | |
| status = uint8(validator.status), | |
| totalDelegated = uint256(snapshot.totalDelegated) * BALANCE_COMPACT_PRECISION, | |
| slashesCount = snapshot.slashesCount, | |
| changedAt = validator.changedAt, | |
| jailedBefore = validator.jailedBefore, | |
| claimedAt = validator.claimedAt, | |
| commissionRate = snapshot.commissionRate, | |
| totalRewards = snapshot.totalRewards | |
| ); | |
| } | |
| function getValidatorByOwner(address owner) external view override returns (address) { | |
| return _validatorOwners[owner]; | |
| } | |
| function releaseValidatorFromJail(address validatorAddress) external { | |
| // make sure validator is in jail | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| require(validator.status == ValidatorStatus.Jail, "Staking: validator not in jail"); | |
| // only validator owner | |
| require(msg.sender == validator.ownerAddress, "Staking: only validator owner"); | |
| require(_currentEpoch() >= validator.jailedBefore, "Staking: still in jail"); | |
| // update validator status | |
| validator.status = ValidatorStatus.Active; | |
| _validatorsMap[validatorAddress] = validator; | |
| _activeValidatorsList.push(validatorAddress); | |
| // emit event | |
| emit ValidatorReleased(validatorAddress, _currentEpoch()); | |
| } | |
| function _totalDelegatedToValidator(Validator memory validator) internal view returns (uint256) { | |
| ValidatorSnapshot memory snapshot = _validatorSnapshots[validator.validatorAddress][validator.changedAt]; | |
| return uint256(snapshot.totalDelegated) * BALANCE_COMPACT_PRECISION; | |
| } | |
| function delegate(address validatorAddress) payable external override { | |
| _delegateTo(msg.sender, validatorAddress, msg.value); | |
| } | |
| function undelegate(address validatorAddress, uint256 amount) external override { | |
| _undelegateFrom(msg.sender, validatorAddress, amount); | |
| } | |
| function currentEpoch() external view returns (uint64) { | |
| return _currentEpoch(); | |
| } | |
| function nextEpoch() external view returns (uint64) { | |
| return _nextEpoch(); | |
| } | |
| function _currentEpoch() internal view returns (uint64) { | |
| return uint64(block.number / _chainConfigContract.getEpochBlockInterval() + 0); | |
| } | |
| function _nextEpoch() internal view returns (uint64) { | |
| return _currentEpoch() + 1; | |
| } | |
| function _touchValidatorSnapshot(Validator memory validator, uint64 epoch) internal returns (ValidatorSnapshot storage) { | |
| ValidatorSnapshot storage snapshot = _validatorSnapshots[validator.validatorAddress][epoch]; | |
| // if snapshot is already initialized then just return it | |
| if (snapshot.totalDelegated > 0) { | |
| return snapshot; | |
| } | |
| // find previous snapshot to copy parameters from it | |
| ValidatorSnapshot memory lastModifiedSnapshot = _validatorSnapshots[validator.validatorAddress][validator.changedAt]; | |
| // last modified snapshot might store zero value, for first delegation it might happen and its not critical | |
| snapshot.totalDelegated = lastModifiedSnapshot.totalDelegated; | |
| snapshot.commissionRate = lastModifiedSnapshot.commissionRate; | |
| // we must save last affected epoch for this validator to be able to restore total delegated | |
| // amount in the future (check condition upper) | |
| if (epoch > validator.changedAt) { | |
| validator.changedAt = epoch; | |
| } | |
| return snapshot; | |
| } | |
| function _touchValidatorSnapshotImmutable(Validator memory validator, uint64 epoch) internal view returns (ValidatorSnapshot memory) { | |
| ValidatorSnapshot memory snapshot = _validatorSnapshots[validator.validatorAddress][epoch]; | |
| // if snapshot is already initialized then just return it | |
| if (snapshot.totalDelegated > 0) { | |
| return snapshot; | |
| } | |
| // find previous snapshot to copy parameters from it | |
| ValidatorSnapshot memory lastModifiedSnapshot = _validatorSnapshots[validator.validatorAddress][validator.changedAt]; | |
| // last modified snapshot might store zero value, for first delegation it might happen and its not critical | |
| snapshot.totalDelegated = lastModifiedSnapshot.totalDelegated; | |
| snapshot.commissionRate = lastModifiedSnapshot.commissionRate; | |
| // return existing or new snapshot | |
| return snapshot; | |
| } | |
| function _delegateTo(address fromDelegator, address toValidator, uint256 amount) internal { | |
| // check is minimum delegate amount | |
| require(amount >= _chainConfigContract.getMinStakingAmount() && amount != 0, "Staking: amount is too low"); | |
| require(amount % BALANCE_COMPACT_PRECISION == 0, "Staking: amount have a remainder"); | |
| // make sure amount is greater than min staking amount | |
| // make sure validator exists at least | |
| Validator memory validator = _validatorsMap[toValidator]; | |
| require(validator.status != ValidatorStatus.NotFound, "Staking: validator not found"); | |
| uint64 atEpoch = _nextEpoch(); | |
| // Lets upgrade next snapshot parameters: | |
| // + find snapshot for the next epoch after current block | |
| // + increase total delegated amount in the next epoch for this validator | |
| // + re-save validator because last affected epoch might change | |
| ValidatorSnapshot storage validatorSnapshot = _touchValidatorSnapshot(validator, atEpoch); | |
| validatorSnapshot.totalDelegated += uint112(amount / BALANCE_COMPACT_PRECISION); | |
| _validatorsMap[toValidator] = validator; | |
| // if last pending delegate has the same next epoch then its safe to just increase total | |
| // staked amount because it can't affect current validator set, but otherwise we must create | |
| // new record in delegation queue with the last epoch (delegations are ordered by epoch) | |
| ValidatorDelegation storage delegation = _validatorDelegations[toValidator][fromDelegator]; | |
| if (delegation.delegateQueue.length > 0) { | |
| DelegationOpDelegate storage recentDelegateOp = delegation.delegateQueue[delegation.delegateQueue.length - 1]; | |
| // if we already have pending snapshot for the next epoch then just increase new amount, | |
| // otherwise create next pending snapshot. (tbh it can't be greater, but what we can do here instead?) | |
| if (recentDelegateOp.epoch >= atEpoch) { | |
| recentDelegateOp.amount += uint112(amount / BALANCE_COMPACT_PRECISION); | |
| } else { | |
| delegation.delegateQueue.push(DelegationOpDelegate({epoch : atEpoch, amount : recentDelegateOp.amount + uint112(amount / BALANCE_COMPACT_PRECISION)})); | |
| } | |
| } else { | |
| // there is no any delegations at al, lets create the first one | |
| delegation.delegateQueue.push(DelegationOpDelegate({epoch : atEpoch, amount : uint112(amount / BALANCE_COMPACT_PRECISION)})); | |
| } | |
| // emit event with the next epoch | |
| emit Delegated(toValidator, fromDelegator, amount, atEpoch); | |
| } | |
| function _undelegateFrom(address toDelegator, address fromValidator, uint256 amount) internal { | |
| // check minimum delegate amount | |
| require(amount >= _chainConfigContract.getMinStakingAmount() && amount != 0, "Staking: amount is too low"); | |
| require(amount % BALANCE_COMPACT_PRECISION == 0, "Staking: amount have a remainder"); | |
| // make sure validator exists at least | |
| Validator memory validator = _validatorsMap[fromValidator]; | |
| uint64 beforeEpoch = _nextEpoch(); | |
| // Lets upgrade next snapshot parameters: | |
| // + find snapshot for the next epoch after current block | |
| // + increase total delegated amount in the next epoch for this validator | |
| // + re-save validator because last affected epoch might change | |
| ValidatorSnapshot storage validatorSnapshot = _touchValidatorSnapshot(validator, beforeEpoch); | |
| require(validatorSnapshot.totalDelegated >= uint112(amount / BALANCE_COMPACT_PRECISION), "Staking: insufficient balance"); | |
| validatorSnapshot.totalDelegated -= uint112(amount / BALANCE_COMPACT_PRECISION); | |
| _validatorsMap[fromValidator] = validator; | |
| // if last pending delegate has the same next epoch then its safe to just increase total | |
| // staked amount because it can't affect current validator set, but otherwise we must create | |
| // new record in delegation queue with the last epoch (delegations are ordered by epoch) | |
| ValidatorDelegation storage delegation = _validatorDelegations[fromValidator][toDelegator]; | |
| require(delegation.delegateQueue.length > 0, "Staking: delegation queue is empty"); | |
| DelegationOpDelegate storage recentDelegateOp = delegation.delegateQueue[delegation.delegateQueue.length - 1]; | |
| require(recentDelegateOp.amount >= uint64(amount / BALANCE_COMPACT_PRECISION), "Staking: insufficient balance"); | |
| uint112 nextDelegatedAmount = recentDelegateOp.amount - uint112(amount / BALANCE_COMPACT_PRECISION); | |
| if (recentDelegateOp.epoch >= beforeEpoch) { | |
| // decrease total delegated amount for the next epoch | |
| recentDelegateOp.amount = nextDelegatedAmount; | |
| } else { | |
| // there is no pending delegations, so lets create the new one with the new amount | |
| delegation.delegateQueue.push(DelegationOpDelegate({epoch : beforeEpoch, amount : nextDelegatedAmount})); | |
| } | |
| // create new undelegate queue operation with soft lock | |
| delegation.undelegateQueue.push(DelegationOpUndelegate({amount : uint112(amount / BALANCE_COMPACT_PRECISION), epoch : beforeEpoch + _chainConfigContract.getUndelegatePeriod()})); | |
| // emit event with the next epoch number | |
| emit Undelegated(fromValidator, toDelegator, amount, beforeEpoch); | |
| } | |
| enum ClaimMode { | |
| Transfer, | |
| Redelegate | |
| } | |
| function _claimDelegatorRewardsAndPendingUndelegates(address validator, address delegator, uint64 beforeEpochExclude, ClaimMode claimMode) internal { | |
| ValidatorDelegation storage delegation = _validatorDelegations[validator][delegator]; | |
| uint256 availableFunds = 0; | |
| // process delegate queue to calculate staking rewards | |
| uint64 delegateGap = delegation.delegateGap; | |
| for (uint256 queueLength = delegation.delegateQueue.length; delegateGap < queueLength;) { | |
| DelegationOpDelegate memory delegateOp = delegation.delegateQueue[delegateGap]; | |
| if (delegateOp.epoch >= beforeEpochExclude) { | |
| break; | |
| } | |
| uint256 voteChangedAtEpoch = 0; | |
| if (delegateGap < queueLength - 1) { | |
| voteChangedAtEpoch = delegation.delegateQueue[delegateGap + 1].epoch; | |
| } | |
| for (; delegateOp.epoch < beforeEpochExclude && (voteChangedAtEpoch == 0 || delegateOp.epoch < voteChangedAtEpoch); delegateOp.epoch++) { | |
| ValidatorSnapshot memory validatorSnapshot = _validatorSnapshots[validator][delegateOp.epoch]; | |
| if (validatorSnapshot.totalDelegated == 0) { | |
| continue; | |
| } | |
| (uint256 delegatorFee, /*uint256 ownerFee*/, /*uint256 systemFee*/) = _calcValidatorSnapshotEpochPayout(validatorSnapshot); | |
| availableFunds += delegatorFee * delegateOp.amount / validatorSnapshot.totalDelegated; | |
| } | |
| // if we have reached end of the delegation list then lets stay on the last item, but with updated latest processed epoch | |
| if (delegateGap >= queueLength - 1) { | |
| delegation.delegateQueue[delegateGap] = delegateOp; | |
| break; | |
| } | |
| delete delegation.delegateQueue[delegateGap]; | |
| ++delegateGap; | |
| } | |
| delegation.delegateGap = delegateGap; | |
| // process all items from undelegate queue | |
| uint64 undelegateGap = delegation.undelegateGap; | |
| for (uint256 queueLength = delegation.undelegateQueue.length; undelegateGap < queueLength;) { | |
| DelegationOpUndelegate memory undelegateOp = delegation.undelegateQueue[undelegateGap]; | |
| if (undelegateOp.epoch > beforeEpochExclude) { | |
| break; | |
| } | |
| availableFunds += uint256(undelegateOp.amount) * BALANCE_COMPACT_PRECISION; | |
| delete delegation.undelegateQueue[undelegateGap]; | |
| ++undelegateGap; | |
| } | |
| delegation.undelegateGap = undelegateGap; | |
| // send available for claim funds to delegator | |
| if (claimMode == ClaimMode.Transfer) { | |
| // for transfer claim mode just all rewards to the user | |
| _safeTransferWithGasLimit(payable(delegator), availableFunds); | |
| // emit event | |
| emit Claimed(validator, delegator, availableFunds, beforeEpochExclude); | |
| } else if (claimMode == ClaimMode.Redelegate) { | |
| (uint256 amountToStake, uint256 rewardsDust) = _calcAvailableForRedelegateAmount(availableFunds); | |
| // if we have something to re-stake then delegate it to the validator | |
| if (amountToStake > 0) { | |
| _delegateTo(delegator, validator, amountToStake); | |
| } | |
| // if we have dust from staking then send it to user | |
| if (rewardsDust > 0) { | |
| _safeTransferWithGasLimit(payable(delegator), rewardsDust); | |
| } | |
| // emit event | |
| emit Redelegated(validator, delegator, amountToStake, rewardsDust, beforeEpochExclude); | |
| } else { | |
| // this case is not possible, no error for less bytecode | |
| revert(); | |
| } | |
| } | |
| function _calcDelegatorRewardsAndPendingUndelegates(address validator, address delegator, uint64 beforeEpoch) internal view returns (uint256) { | |
| ValidatorDelegation memory delegation = _validatorDelegations[validator][delegator]; | |
| uint256 availableFunds = 0; | |
| // process delegate queue to calculate staking rewards | |
| while (delegation.delegateGap < delegation.delegateQueue.length) { | |
| DelegationOpDelegate memory delegateOp = delegation.delegateQueue[delegation.delegateGap]; | |
| if (delegateOp.epoch >= beforeEpoch) { | |
| break; | |
| } | |
| uint256 voteChangedAtEpoch = 0; | |
| if (delegation.delegateGap < delegation.delegateQueue.length - 1) { | |
| voteChangedAtEpoch = delegation.delegateQueue[delegation.delegateGap + 1].epoch; | |
| } | |
| for (; delegateOp.epoch < beforeEpoch && (voteChangedAtEpoch == 0 || delegateOp.epoch < voteChangedAtEpoch); delegateOp.epoch++) { | |
| ValidatorSnapshot memory validatorSnapshot = _validatorSnapshots[validator][delegateOp.epoch]; | |
| if (validatorSnapshot.totalDelegated == 0) { | |
| continue; | |
| } | |
| (uint256 delegatorFee, /*uint256 ownerFee*/, /*uint256 systemFee*/) = _calcValidatorSnapshotEpochPayout(validatorSnapshot); | |
| availableFunds += delegatorFee * delegateOp.amount / validatorSnapshot.totalDelegated; | |
| } | |
| ++delegation.delegateGap; | |
| } | |
| // process all items from undelegate queue | |
| while (delegation.undelegateGap < delegation.undelegateQueue.length) { | |
| DelegationOpUndelegate memory undelegateOp = delegation.undelegateQueue[delegation.undelegateGap]; | |
| if (undelegateOp.epoch > beforeEpoch) { | |
| break; | |
| } | |
| availableFunds += uint256(undelegateOp.amount) * BALANCE_COMPACT_PRECISION; | |
| ++delegation.undelegateGap; | |
| } | |
| // return available for claim funds | |
| return availableFunds; | |
| } | |
| function _claimValidatorOwnerRewards(Validator storage validator, uint64 beforeEpoch) internal { | |
| uint256 availableFunds = 0; | |
| uint256 systemFee = 0; | |
| uint64 claimAt = validator.claimedAt; | |
| for (; claimAt < beforeEpoch; claimAt++) { | |
| ValidatorSnapshot memory validatorSnapshot = _validatorSnapshots[validator.validatorAddress][claimAt]; | |
| (/*uint256 delegatorFee*/, uint256 ownerFee, uint256 slashingFee) = _calcValidatorSnapshotEpochPayout(validatorSnapshot); | |
| availableFunds += ownerFee; | |
| systemFee += slashingFee; | |
| } | |
| validator.claimedAt = claimAt; | |
| _safeTransferWithGasLimit(payable(validator.ownerAddress), availableFunds); | |
| // if we have system fee then pay it to treasury account | |
| if (systemFee > 0) { | |
| _unsafeTransfer(payable(address(_systemRewardContract)), systemFee); | |
| } | |
| emit ValidatorOwnerClaimed(validator.validatorAddress, availableFunds, beforeEpoch); | |
| } | |
| function _calcValidatorOwnerRewards(Validator memory validator, uint64 beforeEpoch) internal view returns (uint256) { | |
| uint256 availableFunds = 0; | |
| for (; validator.claimedAt < beforeEpoch; validator.claimedAt++) { | |
| ValidatorSnapshot memory validatorSnapshot = _validatorSnapshots[validator.validatorAddress][validator.claimedAt]; | |
| (/*uint256 delegatorFee*/, uint256 ownerFee, /*uint256 systemFee*/) = _calcValidatorSnapshotEpochPayout(validatorSnapshot); | |
| availableFunds += ownerFee; | |
| } | |
| return availableFunds; | |
| } | |
| function _calcValidatorSnapshotEpochPayout(ValidatorSnapshot memory validatorSnapshot) internal view returns (uint256 delegatorFee, uint256 ownerFee, uint256 systemFee) { | |
| // detect validator slashing to transfer all rewards to treasury | |
| if (validatorSnapshot.slashesCount >= _chainConfigContract.getMisdemeanorThreshold()) { | |
| return (delegatorFee = 0, ownerFee = 0, systemFee = validatorSnapshot.totalRewards); | |
| } else if (validatorSnapshot.totalDelegated == 0) { | |
| return (delegatorFee = 0, ownerFee = validatorSnapshot.totalRewards, systemFee = 0); | |
| } | |
| // ownerFee_(18+4-4=18) = totalRewards_18 * commissionRate_4 / 1e4 | |
| ownerFee = uint256(validatorSnapshot.totalRewards) * validatorSnapshot.commissionRate / 1e4; | |
| // delegatorRewards = totalRewards - ownerFee | |
| delegatorFee = validatorSnapshot.totalRewards - ownerFee; | |
| // default system fee is zero for epoch | |
| systemFee = 0; | |
| } | |
| function registerValidator(address validatorAddress, uint16 commissionRate) payable external override { | |
| uint256 initialStake = msg.value; | |
| // // initial stake amount should be greater than minimum validator staking amount | |
| require(initialStake >= _chainConfigContract.getMinValidatorStakeAmount(), "Staking: initial stake is too low"); | |
| require(initialStake % BALANCE_COMPACT_PRECISION == 0, "Staking: amount have a remainder"); | |
| // add new validator as pending | |
| _addValidator(validatorAddress, msg.sender, ValidatorStatus.Pending, commissionRate, initialStake, _nextEpoch()); | |
| } | |
| function addValidator(address account) external onlyFromGovernance virtual override { | |
| _addValidator(account, account, ValidatorStatus.Active, 0, 0, _nextEpoch()); | |
| } | |
| function _addValidator(address validatorAddress, address validatorOwner, ValidatorStatus status, uint16 commissionRate, uint256 initialStake, uint64 sinceEpoch) internal { | |
| // validator commission rate | |
| require(commissionRate >= COMMISSION_RATE_MIN_VALUE && commissionRate <= COMMISSION_RATE_MAX_VALUE, "Staking: bad commission rate"); | |
| // init validator default params | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| require(_validatorsMap[validatorAddress].status == ValidatorStatus.NotFound, "Staking: validator already exist"); | |
| validator.validatorAddress = validatorAddress; | |
| validator.ownerAddress = validatorOwner; | |
| validator.status = status; | |
| validator.changedAt = sinceEpoch; | |
| _validatorsMap[validatorAddress] = validator; | |
| // save validator owner | |
| require(_validatorOwners[validatorOwner] == address(0x00), "Staking: owner already in use"); | |
| _validatorOwners[validatorOwner] = validatorAddress; | |
| // add new validator to array | |
| if (status == ValidatorStatus.Active) { | |
| _activeValidatorsList.push(validatorAddress); | |
| } | |
| // push initial validator snapshot at zero epoch with default params | |
| _validatorSnapshots[validatorAddress][sinceEpoch] = ValidatorSnapshot(0, uint112(initialStake / BALANCE_COMPACT_PRECISION), 0, commissionRate); | |
| // delegate initial stake to validator owner | |
| ValidatorDelegation storage delegation = _validatorDelegations[validatorAddress][validatorOwner]; | |
| require(delegation.delegateQueue.length == 0, "Staking: delegation queue is not empty"); | |
| delegation.delegateQueue.push(DelegationOpDelegate(uint112(initialStake / BALANCE_COMPACT_PRECISION), sinceEpoch)); | |
| // emit event | |
| emit ValidatorAdded(validatorAddress, validatorOwner, uint8(status), commissionRate); | |
| } | |
| function removeValidator(address account) external onlyFromGovernance virtual override { | |
| _removeValidator(account); | |
| } | |
| function _removeValidatorFromActiveList(address validatorAddress) internal { | |
| // find index of validator in validator set | |
| int256 indexOf = - 1; | |
| for (uint256 i = 0; i < _activeValidatorsList.length; i++) { | |
| if (_activeValidatorsList[i] != validatorAddress) continue; | |
| indexOf = int256(i); | |
| break; | |
| } | |
| // remove validator from array (since we remove only active it might not exist in the list) | |
| if (indexOf >= 0) { | |
| if (_activeValidatorsList.length > 1 && uint256(indexOf) != _activeValidatorsList.length - 1) { | |
| _activeValidatorsList[uint256(indexOf)] = _activeValidatorsList[_activeValidatorsList.length - 1]; | |
| } | |
| _activeValidatorsList.pop(); | |
| } | |
| } | |
| function _removeValidator(address account) internal { | |
| Validator memory validator = _validatorsMap[account]; | |
| require(validator.status != ValidatorStatus.NotFound, "Staking: validator not found"); | |
| // remove validator from active list if exists | |
| _removeValidatorFromActiveList(account); | |
| // remove from validators map | |
| delete _validatorOwners[validator.ownerAddress]; | |
| delete _validatorsMap[account]; | |
| // emit event about it | |
| emit ValidatorRemoved(account); | |
| } | |
| function activateValidator(address validator) external onlyFromGovernance virtual override { | |
| _activateValidator(validator); | |
| } | |
| function _activateValidator(address validatorAddress) internal { | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| require(_validatorsMap[validatorAddress].status == ValidatorStatus.Pending, "Staking: not pending validator"); | |
| _activeValidatorsList.push(validatorAddress); | |
| validator.status = ValidatorStatus.Active; | |
| _validatorsMap[validatorAddress] = validator; | |
| ValidatorSnapshot storage snapshot = _touchValidatorSnapshot(validator, _nextEpoch()); | |
| emit ValidatorModified(validatorAddress, validator.ownerAddress, uint8(validator.status), snapshot.commissionRate); | |
| } | |
| function disableValidator(address validator) external onlyFromGovernance virtual override { | |
| _disableValidator(validator); | |
| } | |
| function _disableValidator(address validatorAddress) internal { | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| require(_validatorsMap[validatorAddress].status == ValidatorStatus.Active, "Staking: not active validator"); | |
| _removeValidatorFromActiveList(validatorAddress); | |
| validator.status = ValidatorStatus.Pending; | |
| _validatorsMap[validatorAddress] = validator; | |
| ValidatorSnapshot storage snapshot = _touchValidatorSnapshot(validator, _nextEpoch()); | |
| emit ValidatorModified(validatorAddress, validator.ownerAddress, uint8(validator.status), snapshot.commissionRate); | |
| } | |
| function changeValidatorCommissionRate(address validatorAddress, uint16 commissionRate) external { | |
| require(commissionRate >= COMMISSION_RATE_MIN_VALUE && commissionRate <= COMMISSION_RATE_MAX_VALUE, "Staking: bad commission rate"); | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| require(validator.status != ValidatorStatus.NotFound, "Staking: validator not found"); | |
| require(validator.ownerAddress == msg.sender, "Staking: only validator owner"); | |
| ValidatorSnapshot storage snapshot = _touchValidatorSnapshot(validator, _nextEpoch()); | |
| snapshot.commissionRate = commissionRate; | |
| _validatorsMap[validatorAddress] = validator; | |
| emit ValidatorModified(validator.validatorAddress, validator.ownerAddress, uint8(validator.status), commissionRate); | |
| } | |
| function changeValidatorOwner(address validatorAddress, address newOwner) external override { | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| require(validator.ownerAddress == msg.sender, "Staking: only validator owner"); | |
| require(_validatorOwners[newOwner] == address(0x00), "Staking: owner already in use"); | |
| delete _validatorOwners[validator.ownerAddress]; | |
| validator.ownerAddress = newOwner; | |
| _validatorOwners[newOwner] = validatorAddress; | |
| _validatorsMap[validatorAddress] = validator; | |
| ValidatorSnapshot storage snapshot = _touchValidatorSnapshot(validator, _nextEpoch()); | |
| emit ValidatorModified(validator.validatorAddress, validator.ownerAddress, uint8(validator.status), snapshot.commissionRate); | |
| } | |
| function isValidatorActive(address account) external override view returns (bool) { | |
| if (_validatorsMap[account].status != ValidatorStatus.Active) { | |
| return false; | |
| } | |
| address[] memory topValidators = _getValidators(); | |
| for (uint256 i = 0; i < topValidators.length; i++) { | |
| if (topValidators[i] == account) return true; | |
| } | |
| return false; | |
| } | |
| function isValidator(address account) external override view returns (bool) { | |
| return _validatorsMap[account].status != ValidatorStatus.NotFound; | |
| } | |
| function _getValidators() internal view returns (address[] memory) { | |
| uint256 n = _activeValidatorsList.length; | |
| address[] memory orderedValidators = new address[](n); | |
| for (uint256 i = 0; i < n; i++) { | |
| orderedValidators[i] = _activeValidatorsList[i]; | |
| } | |
| // we need to select k top validators out of n | |
| uint256 k = _chainConfigContract.getActiveValidatorsLength(); | |
| if (k > n) { | |
| k = n; | |
| } | |
| for (uint256 i = 0; i < k; i++) { | |
| uint256 nextValidator = i; | |
| Validator memory currentMax = _validatorsMap[orderedValidators[nextValidator]]; | |
| for (uint256 j = i + 1; j < n; j++) { | |
| Validator memory current = _validatorsMap[orderedValidators[j]]; | |
| if (_totalDelegatedToValidator(currentMax) < _totalDelegatedToValidator(current)) { | |
| nextValidator = j; | |
| currentMax = current; | |
| } | |
| } | |
| address backup = orderedValidators[i]; | |
| orderedValidators[i] = orderedValidators[nextValidator]; | |
| orderedValidators[nextValidator] = backup; | |
| } | |
| // this is to cut array to first k elements without copying | |
| assembly { | |
| mstore(orderedValidators, k) | |
| } | |
| return orderedValidators; | |
| } | |
| function getValidators() external view override returns (address[] memory) { | |
| return _getValidators(); | |
| } | |
| function deposit(address validatorAddress) external payable onlyFromCoinbase onlyZeroGasPrice virtual override { | |
| _depositFee(validatorAddress); | |
| } | |
| function _depositFee(address validatorAddress) internal { | |
| require(msg.value > 0, "Staking: deposit is zero"); | |
| // make sure validator is active | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| require(validator.status != ValidatorStatus.NotFound, "Staking: validator not found"); | |
| uint64 epoch = _currentEpoch(); | |
| // increase total pending rewards for validator for current epoch | |
| ValidatorSnapshot storage currentSnapshot = _touchValidatorSnapshot(validator, epoch); | |
| currentSnapshot.totalRewards += uint96(msg.value); | |
| // emit event | |
| emit ValidatorDeposited(validatorAddress, msg.value, epoch); | |
| } | |
| function getValidatorFee(address validatorAddress) external override view returns (uint256) { | |
| // make sure validator exists at least | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| if (validator.status == ValidatorStatus.NotFound) { | |
| return 0; | |
| } | |
| // calc validator rewards | |
| return _calcValidatorOwnerRewards(validator, _currentEpoch()); | |
| } | |
| function getPendingValidatorFee(address validatorAddress) external override view returns (uint256) { | |
| // make sure validator exists at least | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| if (validator.status == ValidatorStatus.NotFound) { | |
| return 0; | |
| } | |
| // calc validator rewards | |
| return _calcValidatorOwnerRewards(validator, _nextEpoch()); | |
| } | |
| function claimValidatorFee(address validatorAddress) external override { | |
| // make sure validator exists at least | |
| Validator storage validator = _validatorsMap[validatorAddress]; | |
| // only validator owner can claim deposit fee | |
| require(msg.sender == validator.ownerAddress, "Staking: only validator owner"); | |
| // claim all validator fees | |
| _claimValidatorOwnerRewards(validator, _currentEpoch()); | |
| } | |
| function claimValidatorFeeAtEpoch(address validatorAddress, uint64 beforeEpoch) external override { | |
| // make sure validator exists at least | |
| Validator storage validator = _validatorsMap[validatorAddress]; | |
| // only validator owner can claim deposit fee | |
| require(msg.sender == validator.ownerAddress, "Staking: only validator owner"); | |
| // we disallow to claim rewards from future epochs | |
| require(beforeEpoch <= _currentEpoch()); | |
| // claim all validator fees | |
| _claimValidatorOwnerRewards(validator, beforeEpoch); | |
| } | |
| function getDelegatorFee(address validatorAddress, address delegatorAddress) external override view returns (uint256) { | |
| return _calcDelegatorRewardsAndPendingUndelegates(validatorAddress, delegatorAddress, _currentEpoch()); | |
| } | |
| function getPendingDelegatorFee(address validatorAddress, address delegatorAddress) external override view returns (uint256) { | |
| return _calcDelegatorRewardsAndPendingUndelegates(validatorAddress, delegatorAddress, _nextEpoch()); | |
| } | |
| function claimDelegatorFee(address validatorAddress) external override { | |
| // claim all confirmed delegator fees including undelegates | |
| _claimDelegatorRewardsAndPendingUndelegates(validatorAddress, msg.sender, _currentEpoch(), ClaimMode.Transfer); | |
| } | |
| function _calcAvailableForRedelegateAmount(uint256 claimableRewards) internal view returns (uint256 amountToStake, uint256 rewardsDust) { | |
| // for redelegate we must split amount into stake-able and dust | |
| amountToStake = (claimableRewards / BALANCE_COMPACT_PRECISION) * BALANCE_COMPACT_PRECISION; | |
| if (amountToStake < _chainConfigContract.getMinStakingAmount()) { | |
| return (0, claimableRewards); | |
| } | |
| // if we have dust remaining after re-stake then send it to user (we can't keep it in the contract) | |
| return (amountToStake, claimableRewards - amountToStake); | |
| } | |
| function calcAvailableForRedelegateAmount(address validator, address delegator) external view override returns (uint256 amountToStake, uint256 rewardsDust) { | |
| uint256 claimableRewards = _calcDelegatorRewardsAndPendingUndelegates(validator, delegator, _currentEpoch()); | |
| return _calcAvailableForRedelegateAmount(claimableRewards); | |
| } | |
| function redelegateDelegatorFee(address validator) external override { | |
| // claim rewards in the redelegate mode (check function code for more info) | |
| _claimDelegatorRewardsAndPendingUndelegates(validator, msg.sender, _currentEpoch(), ClaimMode.Redelegate); | |
| } | |
| function claimDelegatorFeeAtEpoch(address validatorAddress, uint64 beforeEpoch) external override { | |
| // make sure delegator can't claim future epochs | |
| require(beforeEpoch <= _currentEpoch()); | |
| // claim all confirmed delegator fees including undelegates | |
| _claimDelegatorRewardsAndPendingUndelegates(validatorAddress, msg.sender, beforeEpoch, ClaimMode.Transfer); | |
| } | |
| function _safeTransferWithGasLimit(address payable recipient, uint256 amount) internal { | |
| (bool success,) = recipient.call{value : amount, gas : TRANSFER_GAS_LIMIT}(""); | |
| require(success, "Staking: failed to safe transfer"); | |
| } | |
| function _unsafeTransfer(address payable recipient, uint256 amount) internal { | |
| (bool success,) = payable(address(recipient)).call{value : amount}(""); | |
| require(success, "Staking: failed to unsafe transfer"); | |
| } | |
| function slash(address validatorAddress) external onlyFromSlashingIndicator virtual override { | |
| _slashValidator(validatorAddress); | |
| } | |
| function _slashValidator(address validatorAddress) internal { | |
| // make sure validator exists | |
| Validator memory validator = _validatorsMap[validatorAddress]; | |
| require(validator.status != ValidatorStatus.NotFound, "Staking: validator not found"); | |
| uint64 epoch = _currentEpoch(); | |
| // increase slashes for current epoch | |
| ValidatorSnapshot storage currentSnapshot = _touchValidatorSnapshot(validator, epoch); | |
| uint32 slashesCount = currentSnapshot.slashesCount + 1; | |
| currentSnapshot.slashesCount = slashesCount; | |
| // validator state might change, lets update it | |
| _validatorsMap[validatorAddress] = validator; | |
| // if validator has a lot of misses then put it in jail for 1 week (if epoch is 1 day) | |
| if (slashesCount == _chainConfigContract.getFelonyThreshold()) { | |
| validator.jailedBefore = _currentEpoch() + _chainConfigContract.getValidatorJailEpochLength(); | |
| validator.status = ValidatorStatus.Jail; | |
| _removeValidatorFromActiveList(validatorAddress); | |
| _validatorsMap[validatorAddress] = validator; | |
| emit ValidatorJailed(validatorAddress, epoch); | |
| } | |
| // emit event | |
| emit ValidatorSlashed(validatorAddress, slashesCount, epoch); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment