|
| 1 | +// SPDX-License-Identifier: GPL-3.0-only |
| 2 | +// |
| 3 | +// ▓▓▌ ▓▓ ▐▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▄ |
| 4 | +// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▌▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ |
| 5 | +// ▓▓▓▓▓▓ ▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓ ▐▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓ |
| 6 | +// ▓▓▓▓▓▓▄▄▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▄▄▄▄ ▓▓▓▓▓▓▄▄▄▄ ▐▓▓▓▓▓▌ ▐▓▓▓▓▓▓ |
| 7 | +// ▓▓▓▓▓▓▓▓▓▓▓▓▓▀ ▐▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ |
| 8 | +// ▓▓▓▓▓▓▀▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓▀▀▀▀ ▓▓▓▓▓▓▀▀▀▀ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▀ |
| 9 | +// ▓▓▓▓▓▓ ▀▓▓▓▓▓▓▄ ▐▓▓▓▓▓▓ ▓▓▓▓▓ ▓▓▓▓▓▓ ▓▓▓▓▓ ▐▓▓▓▓▓▌ |
| 10 | +// ▓▓▓▓▓▓▓▓▓▓ █▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ |
| 11 | +// ▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ ▐▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓ ▓▓▓▓▓▓▓▓▓▓ |
| 12 | +// |
| 13 | +// Trust math, not hardware. |
| 14 | + |
| 15 | +pragma solidity 0.8.17; |
| 16 | + |
| 17 | +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; |
| 18 | +import "@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol"; |
| 19 | +import "./WalletRegistry.sol"; |
| 20 | + |
| 21 | +/// @title Allowlist |
| 22 | +/// @notice The allowlist contract replaces the Threshold TokenStaking contract |
| 23 | +/// and is as an outcome of TIP-092 and TIP-100 governance decisions. |
| 24 | +/// Staking tokens is no longer required to operate nodes. Beta stakers |
| 25 | +/// are selected by the DAO and operate the network based on the |
| 26 | +/// allowlist maintained by the DAO. |
| 27 | +/// @dev The allowlist contract maintains the maximum possible compatibility |
| 28 | +/// with the old TokenStaking contract interface, as utilized by the |
| 29 | +/// WalletRegistry contract. |
| 30 | +contract Allowlist is Ownable2StepUpgradeable { |
| 31 | + struct StakingProviderInfo { |
| 32 | + uint96 weight; |
| 33 | + uint96 pendingNewWeight; |
| 34 | + } |
| 35 | + |
| 36 | + /// @notice Mapping between the staking provider address and a struct |
| 37 | + /// maintaining weight settings for that staking provider. |
| 38 | + mapping(address => StakingProviderInfo) public stakingProviders; |
| 39 | + |
| 40 | + WalletRegistry public walletRegistry; |
| 41 | + |
| 42 | + event StakingProviderAdded(address indexed stakingProvider, uint96 weight); |
| 43 | + event WeightDecreaseRequested( |
| 44 | + address indexed stakingProvider, |
| 45 | + uint96 oldWeight, |
| 46 | + uint96 newWeight |
| 47 | + ); |
| 48 | + event WeightDecreaseFinalized( |
| 49 | + address indexed stakingProvider, |
| 50 | + uint96 oldWeight, |
| 51 | + uint96 newWeight |
| 52 | + ); |
| 53 | + event MaliciousBehaviorIdentified( |
| 54 | + address notifier, |
| 55 | + address[] stakingProviders |
| 56 | + ); |
| 57 | + |
| 58 | + error StakingProviderAlreadyAdded(); |
| 59 | + error StakingProviderUnknown(); |
| 60 | + error RequestedWeightNotBelowCurrentWeight(); |
| 61 | + error NotWalletRegistry(); |
| 62 | + |
| 63 | + /// @custom:oz-upgrades-unsafe-allow constructor |
| 64 | + constructor() { |
| 65 | + _disableInitializers(); |
| 66 | + } |
| 67 | + |
| 68 | + function initialize(address _walletRegistry) external initializer { |
| 69 | + __Ownable2Step_init(); |
| 70 | + |
| 71 | + walletRegistry = WalletRegistry(_walletRegistry); |
| 72 | + } |
| 73 | + |
| 74 | + /// @notice Allows the governance to add a new staking provider with the |
| 75 | + /// provided weight. If the staking provider address already has |
| 76 | + /// a non-zero weight, the function reverts. |
| 77 | + /// @param stakingProvider The staking provider's address |
| 78 | + /// @param weight The weight of the new staking provider |
| 79 | + function addStakingProvider(address stakingProvider, uint96 weight) |
| 80 | + external |
| 81 | + onlyOwner |
| 82 | + { |
| 83 | + StakingProviderInfo storage info = stakingProviders[stakingProvider]; |
| 84 | + |
| 85 | + if (info.weight != 0) { |
| 86 | + revert StakingProviderAlreadyAdded(); |
| 87 | + } |
| 88 | + |
| 89 | + emit StakingProviderAdded(stakingProvider, weight); |
| 90 | + |
| 91 | + info.weight = weight; |
| 92 | + walletRegistry.authorizationIncreased(stakingProvider, 0, weight); |
| 93 | + } |
| 94 | + |
| 95 | + /// @notice Allows the governance to request weight decrease for the given |
| 96 | + /// staking provider. The change does not take the effect immediately |
| 97 | + /// as it has to be approved by the WalletRegistry contract based on |
| 98 | + /// decrease delays required. Overwrites pending weight decrease |
| 99 | + /// request for the given staking provider. Reverts if the staking |
| 100 | + /// provider is now known or if the proposed new weight is higher |
| 101 | + /// or equal the current weight. |
| 102 | + /// |
| 103 | + /// BE EXTREMELY CAREFUL MAKING CHANGES TO THE BETA STAKER SET! |
| 104 | + /// ENSURE WALLET LIVENESS IS NOT AT RISK AND FAILED HEARTBEATS |
| 105 | + /// ARE NOT GOING TO TRIGGER CASCADING MOVING FUNDS OPERATIONS! |
| 106 | + /// |
| 107 | + /// @param stakingProvider The staking provider's address |
| 108 | + /// @param newWeight The new requested weight of this staking provider |
| 109 | + function requestWeightDecrease(address stakingProvider, uint96 newWeight) |
| 110 | + external |
| 111 | + onlyOwner |
| 112 | + { |
| 113 | + StakingProviderInfo storage info = stakingProviders[stakingProvider]; |
| 114 | + uint96 currentWeight = info.weight; |
| 115 | + |
| 116 | + if (currentWeight == 0) { |
| 117 | + revert StakingProviderUnknown(); |
| 118 | + } |
| 119 | + |
| 120 | + if (newWeight >= currentWeight) { |
| 121 | + revert RequestedWeightNotBelowCurrentWeight(); |
| 122 | + } |
| 123 | + |
| 124 | + emit WeightDecreaseRequested(stakingProvider, currentWeight, newWeight); |
| 125 | + |
| 126 | + info.pendingNewWeight = newWeight; |
| 127 | + walletRegistry.authorizationDecreaseRequested( |
| 128 | + stakingProvider, |
| 129 | + currentWeight, |
| 130 | + newWeight |
| 131 | + ); |
| 132 | + } |
| 133 | + |
| 134 | + /// @notice Called by WalletRegistry contract to approve the previously |
| 135 | + /// requested weight decrease for the given staking provider. |
| 136 | + /// @param stakingProvider The staking provider's address |
| 137 | + /// @return The new weight of the staking provider |
| 138 | + function approveAuthorizationDecrease(address stakingProvider) |
| 139 | + external |
| 140 | + returns (uint96) |
| 141 | + { |
| 142 | + if (msg.sender != address(walletRegistry)) { |
| 143 | + revert NotWalletRegistry(); |
| 144 | + } |
| 145 | + |
| 146 | + StakingProviderInfo storage info = stakingProviders[stakingProvider]; |
| 147 | + uint96 currentWeight = info.weight; |
| 148 | + uint96 newWeight = info.pendingNewWeight; |
| 149 | + |
| 150 | + if (currentWeight == 0) { |
| 151 | + revert StakingProviderUnknown(); |
| 152 | + } |
| 153 | + |
| 154 | + emit WeightDecreaseFinalized(stakingProvider, currentWeight, newWeight); |
| 155 | + |
| 156 | + info.weight = newWeight; |
| 157 | + info.pendingNewWeight = 0; |
| 158 | + return newWeight; |
| 159 | + } |
| 160 | + |
| 161 | + /// @notice Returns the current weight of the staking provider. |
| 162 | + /// @dev The function signature maintains compatibility with Threshold |
| 163 | + /// TokenStaking contract to minimize the TIP-092 impact on the |
| 164 | + /// WalletRegistry contract. |
| 165 | + function authorizedStake(address stakingProvider, address) |
| 166 | + external |
| 167 | + view |
| 168 | + returns (uint96) |
| 169 | + { |
| 170 | + return stakingProviders[stakingProvider].weight; |
| 171 | + } |
| 172 | + |
| 173 | + /// @notice No-op stake seize operation. After TIP-092 tokens are not staked |
| 174 | + /// so there is nothing to seize from. |
| 175 | + /// @dev The function signature maintains compatibility with Threshold |
| 176 | + /// TokenStaking contract to minimize the TIP-092 impact on the |
| 177 | + /// WalletRegistry contract. |
| 178 | + function seize( |
| 179 | + uint96, |
| 180 | + uint256, |
| 181 | + address notifier, |
| 182 | + address[] memory _stakingProviders |
| 183 | + ) external { |
| 184 | + emit MaliciousBehaviorIdentified(notifier, _stakingProviders); |
| 185 | + } |
| 186 | + |
| 187 | + /// @notice Returns the stake owner, beneficiary, and authorizer roles for |
| 188 | + /// the given staking provider. After TIP-092 those roles are no |
| 189 | + /// longer relevant as no tokens are staked. The owner is set to the |
| 190 | + /// allowlist owner, the beneficiary is the staking provider itself |
| 191 | + /// and the authorizer is the zero address. |
| 192 | + /// @dev The function signature maintains compatibility with Threshold |
| 193 | + /// TokenStaking contract to minimize the TIP-092 impact on the |
| 194 | + /// WalletRegistry contract. |
| 195 | + function rolesOf(address stakingProvider) |
| 196 | + external |
| 197 | + view |
| 198 | + returns ( |
| 199 | + address stakeOwner, |
| 200 | + address payable beneficiary, |
| 201 | + address authorizer |
| 202 | + ) |
| 203 | + { |
| 204 | + return (owner(), payable(stakingProvider), address(0)); |
| 205 | + } |
| 206 | +} |
0 commit comments