// SPDX-License-Identifier: Apache-2.0 // https://docs.soliditylang.org/en/v0.8.10/style-guide.html pragma solidity ^0.8.10; import "./SpiralsStaking.sol"; import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol"; import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; /// @title SpiralsStakingV2 /// @author @douglasqian /// @notice This is a 2nd iteration of SpiralsStaking that inherits some /// basic OpenZeppelin upgradeable contracts to enhance security on the /// core staking logic. /// /// @dev We inherit "SpiralsStaking" first so that existing state variables /// preserve their slot assignments. New state variables introduced on this /// smart contract occupy slots after all inherited contract variables /// are assigned. /// contract SpiralsStakingV2 is SpiralsStaking, OwnableUpgradeable, PausableUpgradeable, ReentrancyGuardUpgradeable { uint256 public totalStaked; uint256 public numStakers; /// @notice Initializes the Spirals staking smart contract. Should only be /// called once (stored in state variable). function initialize(address _validatorGroup) public override initializer { __Ownable_init(); __Pausable_init(); __ReentrancyGuard_init(); _setValidatorGroup(validatorGroup); if (!getAccounts().isAccount(address(this))) { // Ensures "initialize" is idempotent. require(getAccounts().createAccount(), "CREATE_ACCOUNT_FAILED"); } } /// @notice Pauses the smart contract. This is useful if a critical /// issue or vulnerability is discovered that requires patching or if /// the contract function stop() external onlyOwner { _pause(); } /// @notice Resumes the smart contract. function resume() external onlyOwner { _unpause(); } /// @notice See "stake" in SpiralsStaking.sol /// @dev Same implementation as original with 2 new modifiers. function stake() external payable override nonReentrant whenNotPaused { require(msg.value > 0, "STAKING_ZERO"); lock(msg.value); vote(msg.value); if (stakers[msg.sender].stakedValue == 0) { // msg.sender is a new staker numStakers++; } stakers[msg.sender].stakedValue += msg.value; totalStaked += msg.value; emit UserCeloStaked(msg.sender, validatorGroup, msg.value); } /// @notice See "unstake" in SpiralsStaking.sol /// @dev Same implementation as original with 2 new modifiers. function unstake(uint256 _value) public override nonReentrant whenNotPaused { require( stakers[msg.sender].stakedValue >= _value, "EXCEEDS_USER_STAKE" ); uint256 activeVotes = getActiveVotes(); (uint256 pendingVotes, ) = getPendingVotes(); require(activeVotes + pendingVotes >= _value, "EXCEEDS_PROTOCOL_STAKE"); // Can only support 1 outstanding unstake request at a time (without // rebuilding all of how Celo unstaking works) require( stakers[msg.sender].withdrawalValue == 0, "OUTSTANDING_PENDING_WITHDRAWAL" ); if (activeVotes >= _value) { revokeActive(_value); } else { revokePending(_value); } unlock(_value); StakerInfo memory newStaker = stakers[msg.sender]; newStaker.stakedValue -= _value; newStaker.withdrawalValue = _value; newStaker.withdrawalTimestamp = block.timestamp + getLockedGold().unlockingPeriod(); totalPendingWithdrawal += _value; totalStaked -= _value; if (newStaker.stakedValue == 0) { // msg.sender unstaked all CELO numStakers--; } stakers[msg.sender] = newStaker; emit UserCeloUnstaked(msg.sender, validatorGroup, _value); } /// @notice See "withdraw" in SpiralsStaking.sol /// @dev Same implementation as original with 2 new modifiers. function withdraw() public override nonReentrant whenNotPaused { StakerInfo memory s = stakers[msg.sender]; require(s.withdrawalValue > 0, "NO_PENDING_WITHDRAWALS"); require(userCanWithdraw(msg.sender), "WITHDRAWAL_NOT_READY"); payable(msg.sender).transfer(s.withdrawalValue); // should fail if protocol doesn't have enough emit UserCeloWithdrawn(msg.sender, s.withdrawalValue); totalPendingWithdrawal -= s.withdrawalValue; s.withdrawalValue = 0; s.withdrawalTimestamp = 0; stakers[msg.sender] = s; } /// @notice See "setValidatorGroup" in SpiralsStaking.sol /// @dev Same implementation as original with 2 new modifiers. function setValidatorGroup(address _newValidatorGroup) external override onlyOwner whenNotPaused { _setValidatorGroup(_newValidatorGroup); } /// @notice Implementation of "setValidatorGroup" function _setValidatorGroup(address _newValidatorGroup) internal onlyOwner whenNotPaused { require( getValidators().isValidatorGroup(_newValidatorGroup), "NOT_VALIDATOR_GROUP" ); require( getElection().getGroupEligibility(_newValidatorGroup), "NOT_ELIGIBLE_VALIDATOR_GROUP" ); validatorGroup = _newValidatorGroup; } }