-
Notifications
You must be signed in to change notification settings - Fork 17
/
StakingFactory.sol
314 lines (261 loc) · 11.6 KB
/
StakingFactory.sol
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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;
import {StakingProxy} from "./StakingProxy.sol";
// Staking interface
interface IStaking {
/// @dev Gets emissions amount.
/// @return Emissions amount.
function emissionsAmount() external view returns (uint256);
}
// Staking verifier interface
interface IStakingVerifier {
/// @dev Verifies a service staking implementation contract.
/// @param implementation Service staking implementation contract address.
/// @return success True, if verification is successful.
function verifyImplementation(address implementation) external view returns (bool);
/// @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);
/// @dev Gets emissions amount limit for a specific staking proxy instance.
/// @return Emissions amount limit.
function getEmissionsAmountLimit(address) external view returns (uint256);
}
/// @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 Provided zero address.
error ZeroAddress();
/// @dev Provided incorrect data length.
/// @param expected Expected minimum data length.
/// @param provided Provided data length.
error IncorrectDataLength(uint256 expected, uint256 provided);
/// @dev The deployed implementation must be a contract.
/// @param implementation Implementation address.
error ContractOnly(address implementation);
/// @dev Proxy creation failed.
/// @param implementation Implementation address.
error ProxyCreationFailed(address implementation);
/// @dev Proxy instance initialization failed
/// @param instance Proxy instance address.
error InitializationFailed(address instance);
/// @dev Implementation is not verified.
/// @param implementation Implementation address.
error UnverifiedImplementation(address implementation);
/// @dev Proxy instance is not verified.
/// @param instance Proxy instance address.
error UnverifiedProxy(address instance);
/// @dev Caught reentrancy violation.
error ReentrancyGuard();
// Instance params struct
struct InstanceParams {
// Implementation of a created proxy instance
address implementation;
// Instance deployer
address deployer;
// Instance status flag
bool isEnabled;
}
/// @title StakingFactory - Smart contract for service staking factory
/// @author Aleksandr Kuperman - <[email protected]>
/// @author Andrey Lebedev - <[email protected]>
/// @author Mariapia Moscatiello - <[email protected]>
contract StakingFactory {
event OwnerUpdated(address indexed owner);
event VerifierUpdated(address indexed verifier);
event InstanceCreated(address indexed sender, address indexed instance, address indexed implementation);
event InstanceStatusChanged(address indexed instance, bool isEnabled);
// Minimum data length that contains at least a selector (4 bytes or 32 bits)
uint256 public constant SELECTOR_DATA_LENGTH = 4;
// Nonce
uint256 public nonce;
// Contract owner address
address public owner;
// Verifier address
address public verifier;
// Reentrancy lock
uint256 internal _locked = 1;
// Mapping of staking service proxy instances => InstanceParams struct
mapping(address => InstanceParams) public mapInstanceParams;
/// @dev StakingFactory constructor.
/// @param _verifier Verifier contract address (can be zero).
constructor(address _verifier) {
owner = msg.sender;
verifier = _verifier;
}
/// @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 Changes the verifier address.
/// @param newVerifier Address of a new verifier.
function changeVerifier(address newVerifier) external {
// Check for the ownership
if (msg.sender != owner) {
revert OwnerOnly(msg.sender, owner);
}
verifier = newVerifier;
emit VerifierUpdated(newVerifier);
}
/// @dev Calculates a new proxy address based on the deployment data and provided nonce.
/// @notice New address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(deploymentData)).
/// @param implementation Implementation contract address.
/// @param localNonce Nonce.
function getProxyAddressWithNonce(address implementation, uint256 localNonce) public view returns (address) {
// Get salt based on chain Id and nonce values
bytes32 salt = keccak256(abi.encodePacked(block.chainid, localNonce));
// Get the deployment data based on the proxy bytecode and the implementation address
bytes memory deploymentData = abi.encodePacked(type(StakingProxy).creationCode,
uint256(uint160(implementation)));
// Get the hash forming the contract address
bytes32 hash = keccak256(
abi.encodePacked(
bytes1(0xff), address(this), salt, keccak256(deploymentData)
)
);
return address(uint160(uint256(hash)));
}
/// @dev Calculates a new proxy address based on the deployment data and utilizing a current contract nonce.
/// @param implementation Implementation contract address.
function getProxyAddress(address implementation) external view returns (address) {
return getProxyAddressWithNonce(implementation, nonce);
}
/// @dev Creates a service staking contract instance.
/// @param implementation Service staking blanc implementation address.
/// @param initPayload Initialization payload.
function createStakingInstance(
address implementation,
bytes memory initPayload
) external returns (address payable instance) {
// Reentrancy guard
if (_locked > 1) {
revert ReentrancyGuard();
}
_locked = 2;
// Check for the zero implementation address
if (implementation == address(0)) {
revert ZeroAddress();
}
// Check that the implementation is the contract
if (implementation.code.length == 0) {
revert ContractOnly(implementation);
}
// The payload length must be at least of the a function selector size
if (initPayload.length < SELECTOR_DATA_LENGTH) {
revert IncorrectDataLength(initPayload.length, SELECTOR_DATA_LENGTH);
}
// Provide additional checks, if needed
address localVerifier = verifier;
if (localVerifier != address(0) && !IStakingVerifier(localVerifier).verifyImplementation(implementation)) {
revert UnverifiedImplementation(implementation);
}
uint256 localNonce = nonce;
// Get salt based on chain Id and nonce values
bytes32 salt = keccak256(abi.encodePacked(block.chainid, localNonce));
// Get the deployment data based on the proxy bytecode and the implementation address
bytes memory deploymentData = abi.encodePacked(type(StakingProxy).creationCode,
uint256(uint160(implementation)));
// solhint-disable-next-line no-inline-assembly
assembly {
instance := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
}
// Check that the proxy creation was successful
if (instance == address(0)) {
revert ProxyCreationFailed(implementation);
}
// Initialize the proxy instance
(bool success, bytes memory returnData) = instance.call(initPayload);
// Process unsuccessful call
if (!success) {
// Get the revert message bytes
if (returnData.length > 0) {
assembly {
let returnDataSize := mload(returnData)
revert(add(32, returnData), returnDataSize)
}
} else {
revert InitializationFailed(instance);
}
}
// Check that the created proxy instance does not violate defined limits
if (localVerifier != address(0) && !IStakingVerifier(localVerifier).verifyInstance(instance, implementation)) {
revert UnverifiedProxy(instance);
}
// Record instance params
InstanceParams memory instanceParams = InstanceParams(implementation, msg.sender, true);
mapInstanceParams[instance] = instanceParams;
// Increase the nonce
nonce = localNonce + 1;
emit InstanceCreated(msg.sender, instance, implementation);
_locked = 1;
}
/// @dev Sets the instance status flag.
/// @param instance Proxy instance address.
/// @param isEnabled Activity flag.
function setInstanceStatus(address instance, bool isEnabled) external {
// Get proxy instance params
InstanceParams storage instanceParams = mapInstanceParams[instance];
address deployer = instanceParams.deployer;
// Check for the instance deployer
if (msg.sender != deployer) {
revert OwnerOnly(msg.sender, deployer);
}
instanceParams.isEnabled = isEnabled;
emit InstanceStatusChanged(instance, isEnabled);
}
/// @dev Verifies a service staking contract instance.
/// @param instance Service staking proxy instance.
/// @return True, if verification is successful.
function verifyInstance(address instance) public view returns (bool) {
// Get proxy instance params
InstanceParams storage instanceParams = mapInstanceParams[instance];
address implementation = instanceParams.implementation;
// Check that the implementation corresponds to the proxy instance
if (implementation == address(0)) {
return false;
}
// Check for the instance being active
if (!instanceParams.isEnabled) {
return false;
}
// Provide additional checks, if needed
address localVerifier = verifier;
if (localVerifier != address(0)) {
return IStakingVerifier(localVerifier).verifyInstance(instance, implementation);
}
return true;
}
/// @dev Verifies staking proxy instance and gets emissions amount.
/// @param instance Staking proxy instance.
/// @return amount Emissions amount.
function verifyInstanceAndGetEmissionsAmount(address instance) external view returns (uint256 amount) {
// Verify the proxy instance
bool success = verifyInstance(instance);
if (success) {
// Get the proxy instance emissions amount
amount = IStaking(instance).emissionsAmount();
// If there is a verifier, adjust the amount
address localVerifier = verifier;
if (localVerifier != address(0)) {
// Get the max possible emissions amount
uint256 maxEmissions = IStakingVerifier(localVerifier).getEmissionsAmountLimit(instance);
// Limit excessive emissions amount
if (amount > maxEmissions) {
amount = maxEmissions;
}
}
}
}
}