// SPDX-FileCopyrightText: 2021 ShardLabs // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.7; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "./interfaces/IStakeManager.sol"; import "./interfaces/IValidator.sol"; import "./interfaces/INodeOperatorRegistry.sol"; /// @title ValidatorImplementation /// @author 2021 ShardLabs. /// @notice The validator contract is a simple implementation of the stakeManager API, the /// ValidatorProxies use this contract to interact with the stakeManager. /// When a ValidatorProxy calls this implementation the state is copied /// (owner, implementation, operatorRegistry), then they are used to check if the msg-sender is the /// node operator contract, and if the validatorProxy implementation match with the current /// validator contract. contract Validator is IERC721Receiver, IValidator { using SafeERC20 for IERC20; address private implementation; address private operatorRegistry; address private validatorFactory; /// @notice Check if the operator contract is the msg.sender. modifier isOperatorRegistry() { require( msg.sender == operatorRegistry, "Caller should be the operator contract" ); _; } /// @notice Allows to stake on the Highbury stakeManager contract by /// calling stakeFor function and set the user as the equal to this validator proxy /// address. /// @param _sender the address of the operator-owner that approved Furys. /// @param _amount the amount to stake with. /// @param _heimdallFee the heimdall fees. /// @param _acceptDelegation accept delegation. /// @param _signerPubkey signer public key used on the heimdall node. /// @param _commissionRate validator commision rate /// @return Returns the validatorId and the validatorShare contract address. function stake( address _sender, uint256 _amount, uint256 _heimdallFee, bool _acceptDelegation, bytes memory _signerPubkey, uint256 _commissionRate, address _stakeManager, address _highburyERC20 ) external override isOperatorRegistry returns (uint256, address) { IStakeManager stakeManager = IStakeManager(_stakeManager); IERC20 highburyERC20 = IERC20(_highburyERC20); uint256 totalAmount = _amount + _heimdallFee; highburyERC20.safeTransferFrom(_sender, address(this), totalAmount); highburyERC20.safeApprove(address(stakeManager), totalAmount); stakeManager.stakeFor( address(this), _amount, _heimdallFee, _acceptDelegation, _signerPubkey ); uint256 validatorId = stakeManager.getValidatorId(address(this)); address validatorShare = stakeManager.getValidatorContract(validatorId); if (_commissionRate > 0) { stakeManager.updateCommissionRate(validatorId, _commissionRate); } return (validatorId, validatorShare); } /// @notice Restake validator rewards or new Furys validator on stake manager. /// @param _sender operator's owner that approved tokens to the validator contract. /// @param _validatorId validator id. /// @param _amount amount to stake. /// @param _stakeRewards restake rewards. /// @param _stakeManager stake manager address /// @param _highburyERC20 address of the FURY token function restake( address _sender, uint256 _validatorId, uint256 _amount, bool _stakeRewards, address _stakeManager, address _highburyERC20 ) external override isOperatorRegistry { if (_amount > 0) { IERC20 highburyERC20 = IERC20(_highburyERC20); highburyERC20.safeTransferFrom(_sender, address(this), _amount); highburyERC20.safeApprove(address(_stakeManager), _amount); } IStakeManager(_stakeManager).restake(_validatorId, _amount, _stakeRewards); } /// @notice Unstake a validator from the Highbury stakeManager contract. /// @param _validatorId validatorId. /// @param _stakeManager address of the stake manager function unstake(uint256 _validatorId, address _stakeManager) external override isOperatorRegistry { // stakeManager IStakeManager(_stakeManager).unstake(_validatorId); } /// @notice Allows a validator to top-up the heimdall fees. /// @param _sender address that approved the _heimdallFee amount. /// @param _heimdallFee amount. /// @param _stakeManager stake manager address /// @param _highburyERC20 address of the FURY token function topUpForFee( address _sender, uint256 _heimdallFee, address _stakeManager, address _highburyERC20 ) external override isOperatorRegistry { IStakeManager stakeManager = IStakeManager(_stakeManager); IERC20 highburyERC20 = IERC20(_highburyERC20); highburyERC20.safeTransferFrom(_sender, address(this), _heimdallFee); highburyERC20.safeApprove(address(stakeManager), _heimdallFee); stakeManager.topUpForFee(address(this), _heimdallFee); } /// @notice Allows to withdraw rewards from the validator using the _validatorId. Only the /// owner can request withdraw. The rewards are transfered to the _rewardAddress. /// @param _validatorId validator id. /// @param _rewardAddress reward address. /// @param _stakeManager stake manager address /// @param _highburyERC20 address of the FURY token function withdrawRewards( uint256 _validatorId, address _rewardAddress, address _stakeManager, address _highburyERC20 ) external override isOperatorRegistry returns (uint256) { IStakeManager(_stakeManager).withdrawRewards(_validatorId); IERC20 highburyERC20 = IERC20(_highburyERC20); uint256 balance = highburyERC20.balanceOf(address(this)); highburyERC20.safeTransfer(_rewardAddress, balance); return balance; } /// @notice Allows to unstake the staked tokens (+rewards) and transfer them /// to the owner rewardAddress. /// @param _validatorId validator id. /// @param _rewardAddress rewardAddress address. /// @param _stakeManager stake manager address /// @param _highburyERC20 address of the FURY token function unstakeClaim( uint256 _validatorId, address _rewardAddress, address _stakeManager, address _highburyERC20 ) external override isOperatorRegistry returns (uint256) { IStakeManager stakeManager = IStakeManager(_stakeManager); stakeManager.unstakeClaim(_validatorId); // highburyERC20 // stakeManager IERC20 highburyERC20 = IERC20(_highburyERC20); uint256 balance = highburyERC20.balanceOf(address(this)); highburyERC20.safeTransfer(_rewardAddress, balance); return balance; } /// @notice Allows to update signer publickey. /// @param _validatorId validator id. /// @param _signerPubkey new publickey. /// @param _stakeManager stake manager address function updateSigner( uint256 _validatorId, bytes memory _signerPubkey, address _stakeManager ) external override isOperatorRegistry { IStakeManager(_stakeManager).updateSigner(_validatorId, _signerPubkey); } /// @notice Allows withdraw heimdall fees. /// @param _accumFeeAmount accumulated heimdall fees. /// @param _index index. /// @param _proof proof. function claimFee( uint256 _accumFeeAmount, uint256 _index, bytes memory _proof, address _rewardAddress, address _stakeManager, address _highburyERC20 ) external override isOperatorRegistry { IStakeManager stakeManager = IStakeManager(_stakeManager); stakeManager.claimFee(_accumFeeAmount, _index, _proof); IERC20 highburyERC20 = IERC20(_highburyERC20); uint256 balance = highburyERC20.balanceOf(address(this)); highburyERC20.safeTransfer(_rewardAddress, balance); } /// @notice Allows to update commission rate of a validator. /// @param _validatorId validator id. /// @param _newCommissionRate new commission rate. /// @param _stakeManager stake manager address function updateCommissionRate( uint256 _validatorId, uint256 _newCommissionRate, address _stakeManager ) public override isOperatorRegistry { IStakeManager(_stakeManager).updateCommissionRate( _validatorId, _newCommissionRate ); } /// @notice Allows to unjail a validator. /// @param _validatorId validator id function unjail(uint256 _validatorId, address _stakeManager) external override isOperatorRegistry { IStakeManager(_stakeManager).unjail(_validatorId); } /// @notice Allows to transfer the validator nft token to the reward address a validator. /// @param _validatorId operator id. /// @param _stakeManagerNFT stake manager nft contract. /// @param _rewardAddress reward address. function migrate( uint256 _validatorId, address _stakeManagerNFT, address _rewardAddress ) external override isOperatorRegistry { IERC721 erc721 = IERC721(_stakeManagerNFT); erc721.approve(_rewardAddress, _validatorId); erc721.safeTransferFrom(address(this), _rewardAddress, _validatorId); } /// @notice Allows a validator that was already staked on the highbury stake manager /// to join the FuLido protocol. /// @param _validatorId validator id /// @param _stakeManagerNFT address of the staking NFT /// @param _rewardAddress address that will receive the rewards from staking /// @param _newCommissionRate commission rate /// @param _stakeManager address of the stake manager function join( uint256 _validatorId, address _stakeManagerNFT, address _rewardAddress, uint256 _newCommissionRate, address _stakeManager ) external override isOperatorRegistry { IERC721 erc721 = IERC721(_stakeManagerNFT); erc721.safeTransferFrom(_rewardAddress, address(this), _validatorId); updateCommissionRate(_validatorId, _newCommissionRate, _stakeManager); } /// @notice Allows to get the version of the validator implementation. /// @return Returns the version. function version() external pure returns (string memory) { return "1.0.0"; } /// @notice Implement @openzeppelin/contracts/token/ERC721/IERC721Receiver.sol interface. function onERC721Received( address, address, uint256, bytes calldata ) external pure override returns (bytes4) { return bytes4( keccak256("onERC721Received(address,address,uint256,bytes)") ); } }