-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathStakingVerifier.sol
More file actions
352 lines (300 loc) · 13.7 KB
/
Copy pathStakingVerifier.sol
File metadata and controls
352 lines (300 loc) · 13.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
// Staking instance interface
interface IStaking {
/// @dev Gets rewards per second.
/// @return Rewards per second.
function rewardsPerSecond() external view returns (uint256);
/// @dev Gets maximum number of services.
/// @return Maximum number of services.
function maxNumServices() external view returns (uint256);
/// @dev Gets time for emissions.
/// @return Time for emissions.
function timeForEmissions() external view returns (uint256);
/// @dev Gets emissions amount.
/// @return Emissions amount.
function emissionsAmount() external view returns (uint256);
/// @dev Gets service staking token.
/// @return Service staking token address.
function stakingToken() external view returns (address);
/// @dev Gets service registry address.
/// @return Service registry address.
function serviceRegistry() external view returns(address);
/// @dev Gets service registry token utility address.
/// @return Service registry token utility address.
function serviceRegistryTokenUtility() external view returns(address);
/// @dev Minimum service staking deposit value required for staking.
/// @return Minimum service staking deposit.
function minStakingDeposit() external view returns(uint256);
}
/// @dev Provided zero address.
error ZeroAddress();
/// @dev Provided zero value.
error ZeroValue();
/// @dev Wrong length of two arrays.
/// @param numValues1 Number of values in a first array.
/// @param numValues2 Number of values in a second array.
error WrongArrayLength(uint256 numValues1, uint256 numValues2);
/// @dev Only `owner` has a privilege, but the `sender` was provided.
/// @param sender Sender address.
/// @param owner Required sender address as an owner.
error OwnerOnly(address sender, address owner);
/// @dev The deployed implementation must be a contract.
/// @param implementation Implementation address.
error ContractOnly(address implementation);
/// @title StakingVerifier - Smart contract for service staking contracts verification
/// @author Aleksandr Kuperman - <aleksandr.kuperman@valory.xyz>
/// @author Andrey Lebedev - <andrey.lebedev@valory.xyz>
/// @author Mariapia Moscatiello - <mariapia.moscatiello@valory.xyz>
contract StakingVerifier {
event OwnerUpdated(address indexed owner);
event SetImplementationsCheck(bool setCheck);
event ImplementationsWhitelistUpdated(address[] implementations, bool[] statuses, bool setCheck);
event StakingLimitsUpdated(uint256 minStakingDepositLimit, uint256 timeForEmissionsLimit, uint256 numServicesLimit,
uint256 apyLimit);
// One year constant
uint256 public constant ONE_YEAR = 1 days * 365;
// OLAS token address
address public immutable olas;
// Service registry address
address public immutable serviceRegistry;
// Service registry token utility
address public immutable serviceRegistryTokenUtility;
// Minimum staking deposit limit
uint256 public minStakingDepositLimit;
// Time for emissions limit
uint256 public timeForEmissionsLimit;
// Limit for the number of services
uint256 public numServicesLimit;
// APY limit in 1e18 format
uint256 public apyLimit;
// Contract owner address
address public owner;
// Flag to check for the implementation address whitelisting status
bool public implementationsCheck;
// Mapping implementation address => whitelisting status
mapping(address => bool) public mapImplementations;
/// @dev StakingVerifier constructor.
/// @param _olas OLAS token address.
/// @param _serviceRegistry Service registry address.
/// @param _serviceRegistryTokenUtility Service registry token utility address.
/// @param _minStakingDepositLimit Minimum staking deposit limit.
/// @param _timeForEmissionsLimit Time for emissions limit.
/// @param _numServicesLimit Limit for the number of services.
/// @param _apyLimit APY limit in 1e18 format.
constructor(
address _olas,
address _serviceRegistry,
address _serviceRegistryTokenUtility,
uint256 _minStakingDepositLimit,
uint256 _timeForEmissionsLimit,
uint256 _numServicesLimit,
uint256 _apyLimit
) {
// Zero address check
if (_olas == address(0) || _serviceRegistry == address(0)) {
revert ZeroAddress();
}
// Zero values check
if (_minStakingDepositLimit == 0 || _timeForEmissionsLimit == 0 || _numServicesLimit == 0 || _apyLimit == 0) {
revert ZeroValue();
}
owner = msg.sender;
olas = _olas;
serviceRegistry = _serviceRegistry;
serviceRegistryTokenUtility = _serviceRegistryTokenUtility;
minStakingDepositLimit = _minStakingDepositLimit;
timeForEmissionsLimit = _timeForEmissionsLimit;
numServicesLimit = _numServicesLimit;
apyLimit = _apyLimit;
}
/// @dev Changes the owner address.
/// @param newOwner Address of a new owner.
function changeOwner(address newOwner) external {
// Check for the ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
// Check for the zero address
if (newOwner == address(0)) {
revert ZeroAddress();
}
owner = newOwner;
emit OwnerUpdated(newOwner);
}
/// @dev Controls the necessity of checking implementation whitelisting statuses.
/// @param setCheck True if the whitelisting check is needed, and false otherwise.
function setImplementationsCheck(bool setCheck) external {
// Check the contract ownership
if (owner != msg.sender) {
revert OwnerOnly(owner, msg.sender);
}
// Set the implementations check requirement
implementationsCheck = setCheck;
emit SetImplementationsCheck(setCheck);
}
/// @dev Controls implementations whitelisting statuses.
/// @notice Implementation is considered whitelisted if the global status is set to true.
/// @notice Implementations check could be set to false even though (some) implementations are set to true.
/// This is the owner responsibility how to manage the whitelisting logic.
/// @param implementations Set of implementation addresses.
/// @param statuses Set of whitelisting statuses.
/// @param setCheck True if the whitelisting check is needed, and false otherwise.
function setImplementationsStatuses(
address[] memory implementations,
bool[] memory statuses,
bool setCheck
) external {
// Check the contract ownership
if (owner != msg.sender) {
revert OwnerOnly(owner, msg.sender);
}
// Check for the array length and that they are not empty
if (implementations.length == 0 || implementations.length != statuses.length) {
revert WrongArrayLength(implementations.length, statuses.length);
}
// Set the implementations address check requirement
implementationsCheck = setCheck;
// Set implementations whitelisting status
for (uint256 i = 0; i < implementations.length; ++i) {
// Check for the zero address
if (implementations[i] == address(0)) {
revert ZeroAddress();
}
// Set the operator whitelisting status
mapImplementations[implementations[i]] = statuses[i];
}
emit ImplementationsWhitelistUpdated(implementations, statuses, setCheck);
}
/// @dev Verifies a service staking implementation contract.
/// @param implementation Service staking implementation contract address.
/// @return True, if verification is successful.
function verifyImplementation(address implementation) external view returns (bool){
// Check the operator whitelisting status, if the whitelisting check is set
if (implementationsCheck) {
return mapImplementations[implementation];
}
return true;
}
/// @dev Verifies a service staking proxy instance.
/// @param instance Service staking proxy instance.
/// @param implementation Service staking implementation.
/// @return True, if verification is successful.
function verifyInstance(address instance, address implementation) external view returns (bool) {
// If the implementations check is true, and the implementation is not whitelisted, the verification is failed
if (implementationsCheck && !mapImplementations[implementation]) {
return false;
}
// Check that instance is the contract when it is not checked against the implementation
if (instance.code.length == 0) {
return false;
}
// Check service registry
// This is a mandatory check since all the services were created by a service registry contract
bytes memory registryData = abi.encodeCall(IStaking.serviceRegistry, ());
(bool success, bytes memory returnData) = instance.staticcall(registryData);
// Check the returnData if the call was successful
// The returned size must be 32 to fit one address
if (success && returnData.length == 32) {
address registry = abi.decode(returnData, (address));
if (registry != serviceRegistry) {
return false;
}
} else {
return false;
}
// Check for minimum staking deposit
// This lets the verifier limit the max stake per slot for risk mitigation
uint256 minStakingDeposit = IStaking(instance).minStakingDeposit();
if (minStakingDeposit > minStakingDepositLimit) {
return false;
}
// Calculate rewards per year
uint256 rewardsPerYear = IStaking(instance).rewardsPerSecond() * ONE_YEAR;
// Calculate current APY in 1e18 format
uint256 apy = (rewardsPerYear * 1e18) / minStakingDeposit;
// Check for APY
// This lets the verifier limit the max APY a staking contract offers
// The DAO can this way express an upper bound on the APY staking contracts can offer
if (apy > apyLimit) {
return false;
}
// Check for time for emissions
// This lets the verifier enforce an upper bound on the emissions length for risk mitigation
uint256 timeForEmissions = IStaking(instance).timeForEmissions();
if (timeForEmissions > timeForEmissionsLimit) {
return false;
}
// Check for the number of services
// This is a must have parameter for all staking contracts
uint256 numServices = IStaking(instance).maxNumServices();
if (numServices > numServicesLimit) {
return false;
}
address token;
// Check staking token
// This is an optional check since there could be staking contracts with native tokens
bytes memory tokenData = abi.encodeCall(IStaking.stakingToken, ());
(success, returnData) = instance.staticcall(tokenData);
// Check the returnData is the call was successful
if (success) {
// The returned size must be 32 to fit one address
if (returnData.length == 32) {
token = abi.decode(returnData, (address));
if (token != olas) {
return false;
}
} else {
return false;
}
}
// Check service registry token utility if the staking token non zero
if (token != address(0) && serviceRegistryTokenUtility != address(0)) {
registryData = abi.encodeCall(IStaking.serviceRegistryTokenUtility, ());
(success, returnData) = instance.staticcall(registryData);
// Check the returnData if the call was successful
// The returned size must be 32 to fit one address
if (success && returnData.length == 32) {
address registry = abi.decode(returnData, (address));
if (registry != serviceRegistryTokenUtility) {
return false;
}
} else {
return false;
}
}
return true;
}
/// @dev Changes staking parameter limits.
/// @param _minStakingDepositLimit Minimum staking deposit limit.
/// @param _timeForEmissionsLimit Time for emissions limit.
/// @param _numServicesLimit Limit for the number of services.
/// @param _apyLimit APY limit in 1e18 format.
function changeStakingLimits(
uint256 _minStakingDepositLimit,
uint256 _timeForEmissionsLimit,
uint256 _numServicesLimit,
uint256 _apyLimit
) external {
// Check the contract ownership
if (owner != msg.sender) {
revert OwnerOnly(owner, msg.sender);
}
// Zero values check
if (_minStakingDepositLimit == 0 || _timeForEmissionsLimit == 0 || _numServicesLimit == 0 || _apyLimit == 0) {
revert ZeroValue();
}
minStakingDepositLimit = _minStakingDepositLimit;
timeForEmissionsLimit = _timeForEmissionsLimit;
numServicesLimit = _numServicesLimit;
apyLimit = _apyLimit;
emit StakingLimitsUpdated(_minStakingDepositLimit, _timeForEmissionsLimit, _numServicesLimit, _apyLimit);
}
/// @dev Gets emissions amount limit for a specific staking proxy instance.
/// @param instance Staking proxy instance.
/// @return amount Emissions amount limit.
function getEmissionsAmountLimit(address instance) external view returns (uint256 amount) {
// Get calculated emissions amount from the instance
amount = IStaking(instance).emissionsAmount();
}
}