Skip to content

Commit 30b0410

Browse files
committed
Allowlist for WalletRegistry
The allowlist contract replaces the Threshold `TokenStaking` contract and is as an outcome of TIP-092 and TIP-100 governance decisions. Staking tokens is no longer required to operate nodes. Beta stakers are selected by the DAO and operate the network based on the allowlist maintained by the DAO. The contract will be integrated with the `WalletRegistry` and replace calls to `TokenStaking`. I have been experimenting with various approaches, and the most extreme one was to remove most of the `EcdsaAuthorization` logic as well as all `TokenStaking.seize` calls. This would have cascading effects on tBTC Bridge contracts as they rely on `WalletRegistry.seize`. That would also require implementing weight decrease delays in the `Allowlist,` so essentially doing work that is already done in `WalletRegistry`. Considering the pros and cons, I decided on the least invasive option. The `WalletRegistry` still thinks in terms of stake authorization, but everything is based on the staking provider's weight as set in the `Allowlist`, and weight decrease delays are enforced by the existing mechanism in `EcdsaAuthorization`. The `seize` function does nothing except of emitting an event about detecting beta staker misbehavior. This code is still a draft. Has to be integrated and covered with proper tests!
1 parent 0998cb5 commit 30b0410

File tree

1 file changed

+206
-0
lines changed

1 file changed

+206
-0
lines changed
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
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

Comments
 (0)