|
| 1 | +// Copyright (c) Immutable Pty Ltd 2018 - 2025 |
| 2 | +// SPDX-License-Identifier: Apache 2 |
| 3 | +pragma solidity >=0.8.19 <0.8.29; |
| 4 | + |
| 5 | +import {StakeHolderBase} from "./StakeHolderBase.sol"; |
| 6 | +import {IStakeHolderV2, IStakeHolder} from "./IStakeHolderV2.sol"; |
| 7 | + |
| 8 | +/** |
| 9 | + * @title StakeHolderBase: allows anyone to stake any amount of an ERC20 token and to then remove all or part of that stake. |
| 10 | + * @dev This contract is designed to be upgradeable. |
| 11 | + */ |
| 12 | +abstract contract StakeHolderBaseV2 is IStakeHolderV2, StakeHolderBase { |
| 13 | + /// @notice Version 2 version number |
| 14 | + uint256 internal constant _VERSION2 = 2; |
| 15 | + |
| 16 | + /** |
| 17 | + * @notice Initialises the upgradeable contract, setting up admin accounts. |
| 18 | + * @param _roleAdmin the address to grant `DEFAULT_ADMIN_ROLE` to |
| 19 | + * @param _upgradeAdmin the address to grant `UPGRADE_ROLE` to |
| 20 | + * @param _distributeAdmin the address to grant `DISTRIBUTE_ROLE` to |
| 21 | + */ |
| 22 | + function __StakeHolderBase_init( |
| 23 | + address _roleAdmin, |
| 24 | + address _upgradeAdmin, |
| 25 | + address _distributeAdmin |
| 26 | + ) internal virtual override { |
| 27 | + // NOTE: onlyInitializing is called in super. |
| 28 | + super.__StakeHolderBase_init(_roleAdmin, _upgradeAdmin, _distributeAdmin); |
| 29 | + version = _VERSION2; |
| 30 | + } |
| 31 | + |
| 32 | + /** |
| 33 | + * @notice Function to be called when upgrading this contract. |
| 34 | + * @dev Call this function as part of upgradeToAndCall(). |
| 35 | + * This initial version of this function reverts. There is no situation |
| 36 | + * in which it makes sense to upgrade to the V0 storage layout. |
| 37 | + * Note that this function is permissionless. Future versions must |
| 38 | + * compare the code version and the storage version and upgrade |
| 39 | + * appropriately. As such, the code will revert if an attacker calls |
| 40 | + * this function attempting a malicious upgrade. |
| 41 | + * @ param _data ABI encoded data to be used as part of the contract storage upgrade. |
| 42 | + */ |
| 43 | + function upgradeStorage(bytes memory /* _data */) external virtual override { |
| 44 | + if (version == _VERSION0) { |
| 45 | + // Upgrading from version 0 to 2 involves only code changes and |
| 46 | + // changing the storage version number. |
| 47 | + version = _VERSION2; |
| 48 | + } else { |
| 49 | + // Don't allow downgrade or re-initialising. |
| 50 | + revert CanNotUpgradeToLowerOrSameVersion(version); |
| 51 | + } |
| 52 | + } |
| 53 | + |
| 54 | + /** |
| 55 | + * @inheritdoc IStakeHolder |
| 56 | + */ |
| 57 | + function distributeRewards( |
| 58 | + AccountAmount[] calldata _recipientsAndAmounts |
| 59 | + ) external payable override(IStakeHolder, StakeHolderBase) nonReentrant onlyRole(DISTRIBUTE_ROLE) { |
| 60 | + uint256 total = _distributeRewards(_recipientsAndAmounts, true); |
| 61 | + uint256 len = _recipientsAndAmounts.length; |
| 62 | + emit Distributed(msg.sender, total, len); |
| 63 | + } |
| 64 | + |
| 65 | + /** |
| 66 | + * @inheritdoc IStakeHolderV2 |
| 67 | + */ |
| 68 | + function stakeFor( |
| 69 | + AccountAmount[] calldata _recipientsAndAmounts |
| 70 | + ) external payable nonReentrant onlyRole(DISTRIBUTE_ROLE) { |
| 71 | + uint256 total = _distributeRewards(_recipientsAndAmounts, false); |
| 72 | + uint256 len = _recipientsAndAmounts.length; |
| 73 | + emit StakedFor(msg.sender, total, len); |
| 74 | + } |
| 75 | + |
| 76 | + /** |
| 77 | + * @notice Distribute tokens to a set of accounts. |
| 78 | + * @param _recipientsAndAmounts An array of recipients to distribute value to and |
| 79 | + * amounts to be distributed to each recipient. |
| 80 | + * @param _existingAccountsOnly If true, revert if the account has never been used. |
| 81 | + * @return _total Value distributed. |
| 82 | + */ |
| 83 | + function _distributeRewards( |
| 84 | + AccountAmount[] calldata _recipientsAndAmounts, |
| 85 | + bool _existingAccountsOnly |
| 86 | + ) private returns (uint256 _total) { |
| 87 | + // Distribute the value. |
| 88 | + _total = 0; |
| 89 | + uint256 len = _recipientsAndAmounts.length; |
| 90 | + for (uint256 i = 0; i < len; i++) { |
| 91 | + AccountAmount calldata accountAmount = _recipientsAndAmounts[i]; |
| 92 | + uint256 amount = accountAmount.amount; |
| 93 | + _addStake(accountAmount.account, amount, _existingAccountsOnly); |
| 94 | + _total += amount; |
| 95 | + } |
| 96 | + if (_total == 0) { |
| 97 | + revert MustDistributeMoreThanZero(); |
| 98 | + } |
| 99 | + _checksAndTransfer(_total); |
| 100 | + } |
| 101 | +} |
0 commit comments