-
Notifications
You must be signed in to change notification settings - Fork 250
Expand file tree
/
Copy pathRocketNodeDeposit.sol
More file actions
272 lines (246 loc) · 16.3 KB
/
RocketNodeDeposit.sol
File metadata and controls
272 lines (246 loc) · 16.3 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
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.7.6;
import "@openzeppelin/contracts/math/SafeMath.sol";
import "../RocketBase.sol";
import "../../interface/deposit/RocketDepositPoolInterface.sol";
import "../../interface/minipool/RocketMinipoolInterface.sol";
import "../../interface/minipool/RocketMinipoolManagerInterface.sol";
import "../../interface/minipool/RocketMinipoolQueueInterface.sol";
import "../../interface/network/RocketNetworkFeesInterface.sol";
import "../../interface/node/RocketNodeDepositInterface.sol";
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsDepositInterface.sol";
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsMinipoolInterface.sol";
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNodeInterface.sol";
import "../../interface/dao/protocol/settings/RocketDAOProtocolSettingsNetworkInterface.sol";
import "../../interface/dao/node/RocketDAONodeTrustedInterface.sol";
import "../../interface/dao/node/settings/RocketDAONodeTrustedSettingsMembersInterface.sol";
import "../../types/MinipoolDeposit.sol";
import "../../interface/node/RocketNodeManagerInterface.sol";
import "../../interface/RocketVaultInterface.sol";
import "../../interface/node/RocketNodeStakingInterface.sol";
import "../../interface/token/RocketTokenRETHInterface.sol";
/// @notice Handles node deposits and minipool creation
contract RocketNodeDeposit is RocketBase, RocketNodeDepositInterface {
// Libs
using SafeMath for uint;
// Events
event DepositReceived(address indexed from, uint256 amount, uint256 time);
constructor(RocketStorageInterface _rocketStorageAddress) RocketBase(_rocketStorageAddress) {
version = 3;
}
/// @dev Accept incoming ETH from the deposit pool
receive() external payable onlyLatestContract("rocketDepositPool", msg.sender) {}
/// @notice Returns a node operator's credit balance in wei
function getNodeDepositCredit(address _nodeOperator) override public view returns (uint256) {
return getUint(keccak256(abi.encodePacked("node.deposit.credit.balance", _nodeOperator)));
}
/// @dev Increases a node operators deposit credit balance
function increaseDepositCreditBalance(address _nodeOperator, uint256 _amount) override external onlyRegisteredMinipool(msg.sender) onlyLatestContract("rocketNodeDeposit", address(this)) {
addUint(keccak256(abi.encodePacked("node.deposit.credit.balance", _nodeOperator)), _amount);
}
/// @notice Accept a node deposit and create a new minipool under the node. Only accepts calls from registered nodes
/// @param _bondAmount The amount of capital the node operator wants to put up as his bond
/// @param _minimumNodeFee Transaction will revert if network commission rate drops below this amount
/// @param _validatorPubkey Pubkey of the validator the node operator wishes to migrate
/// @param _validatorSignature Signature from the validator over the deposit data
/// @param _depositDataRoot The hash tree root of the deposit data (passed onto the deposit contract on pre stake)
/// @param _salt Salt used to deterministically construct the minipool's address
/// @param _expectedMinipoolAddress The expected deterministic minipool address. Will revert if it doesn't match
function deposit(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) {
// Use credit balance
require(_useCredit(_bondAmount) == 0, "Invalid value");
// Process the deposit
_deposit(_bondAmount, _minimumNodeFee, _validatorPubkey, _validatorSignature, _depositDataRoot, _salt, _expectedMinipoolAddress);
}
function _useCredit(uint256 _bondAmount) private returns (uint256 _surplus) {
// Query node's deposit credit
uint256 credit = getNodeDepositCredit(msg.sender);
// Credit balance accounting
if (credit < _bondAmount) {
uint256 shortFall = _bondAmount.sub(credit);
require(msg.value >= shortFall, "Invalid value");
setUint(keccak256(abi.encodePacked("node.deposit.credit.balance", msg.sender)), 0);
return msg.value.sub(shortFall);
} else {
subUint(keccak256(abi.encodePacked("node.deposit.credit.balance", msg.sender)), _bondAmount);
return msg.value;
}
}
function depositAndMint(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) override external payable onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) {
// Use credit balance
uint256 surplus = _useCredit(_bondAmount);
// Deposit the minipool
_deposit(_bondAmount, _minimumNodeFee, _validatorPubkey, _validatorSignature, _depositDataRoot, _salt, _expectedMinipoolAddress);
// Mint rETH for the caller
_mint(surplus);
}
function _mint(uint256 _mintEthAmount) private {
// Deposit ETH to mint rETH
RocketDepositPoolInterface(getContractAddress("rocketDepositPool")).deposit{value: _mintEthAmount}();
// Transfer minted rETH back to the caller
RocketTokenRETHInterface rocketTokenRETH = RocketTokenRETHInterface(getContractAddress("rocketTokenRETH"));
rocketTokenRETH.transfer(msg.sender, rocketTokenRETH.balanceOf(address(this)));
}
/// @notice Returns true if the given amount is a valid deposit amount
function isValidDepositAmount(uint256 _amount) override public pure returns (bool) {
return _amount == 16 ether || _amount == 8 ether;
}
/// @notice Returns an array of valid deposit amounts
function getDepositAmounts() override external pure returns (uint256[] memory) {
uint256[] memory amounts = new uint256[](2);
amounts[0] = 16 ether;
amounts[1] = 8 ether;
return amounts;
}
/// @dev Internal logic to process a deposit
function _deposit(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, bytes calldata _validatorSignature, bytes32 _depositDataRoot, uint256 _salt, address _expectedMinipoolAddress) private {
RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool"));
// Check pre-conditions
checkDepositsEnabled();
checkDistributorInitialised();
checkNodeFee(_minimumNodeFee);
require(isValidDepositAmount(_bondAmount), "Invalid deposit amount");
// Emit deposit received event
emit DepositReceived(msg.sender, msg.value, block.timestamp);
// Increase ETH matched (used to calculate RPL collateral requirements)
uint256 launchAmount;
{
RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool"));
launchAmount = rocketDAOProtocolSettingsMinipool.getLaunchBalance();
}
increaseEthMatched(msg.sender, launchAmount.sub(_bondAmount));
// Create the minipool
RocketMinipoolInterface minipool = createMinipool(_salt, _expectedMinipoolAddress);
// Get the pre-launch value
uint256 preLaunchValue = getPreLaunchValue();
{
// Retrieve ETH from deposit pool if required
uint256 shortFall = 0;
if (msg.value < preLaunchValue) {
shortFall = preLaunchValue.sub(msg.value);
rocketDepositPool.nodeCreditWithdrawal(shortFall);
}
// Perform the pre-deposit
uint256 remaining = msg.value.add(shortFall).sub(preLaunchValue);
if (remaining > 0) {
// Deposit the left over value into the deposit pool
rocketDepositPool.nodeDeposit{value: remaining}();
}
}
minipool.preDeposit{value: preLaunchValue}(_bondAmount, _validatorPubkey, _validatorSignature, _depositDataRoot);
// Enqueue the minipool
enqueueMinipool(address(minipool));
// Assign deposits if enabled
assignDeposits();
}
/// @notice Creates a "vacant" minipool which a node operator can use to migrate a validator with a BLS withdrawal credential
/// @param _bondAmount The amount of capital the node operator wants to put up as his bond
/// @param _minimumNodeFee Transaction will revert if network commission rate drops below this amount
/// @param _validatorPubkey Pubkey of the validator the node operator wishes to migrate
/// @param _salt Salt used to deterministically construct the minipool's address
/// @param _expectedMinipoolAddress The expected deterministic minipool address. Will revert if it doesn't match
function createVacantMinipool(uint256 _bondAmount, uint256 _minimumNodeFee, bytes calldata _validatorPubkey, uint256 _salt, address _expectedMinipoolAddress) override external onlyLatestContract("rocketNodeDeposit", address(this)) onlyRegisteredNode(msg.sender) {
// Check pre-conditions
checkDepositsEnabled();
checkDistributorInitialised();
checkNodeFee(_minimumNodeFee);
require(isValidDepositAmount(_bondAmount), "Invalid deposit amount");
// Increase ETH matched (used to calculate RPL collateral requirements)
RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool"));
uint256 launchAmount = rocketDAOProtocolSettingsMinipool.getLaunchBalance();
increaseEthMatched(msg.sender, launchAmount.sub(_bondAmount));
// Create the minipool
_createVacantMinipool(_salt, _validatorPubkey, _bondAmount, _expectedMinipoolAddress);
}
/// @dev Increases the amount of ETH that has been matched against a node operators bond. Reverts if it exceeds the
/// collateralisation requirements of the network
function increaseEthMatched(address _nodeAddress, uint256 _amount) private {
// Check amount doesn't exceed limits
RocketNodeStakingInterface rocketNodeStaking = RocketNodeStakingInterface(getContractAddress("rocketNodeStaking"));
require(
rocketNodeStaking.getNodeETHMatched(_nodeAddress).add(_amount) <= rocketNodeStaking.getNodeETHMatchedLimit(_nodeAddress),
"ETH matched after deposit exceeds limit based on node RPL stake"
);
// Update ETH matched
uint256 ethMatched = getUint(keccak256(abi.encodePacked("eth.matched.node.amount", _nodeAddress)));
if (ethMatched == 0) {
RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager"));
// Migration from legacy minipools which all had 16 ETH matched
ethMatched = rocketMinipoolManager.getNodeActiveMinipoolCount(_nodeAddress).mul(16 ether);
}
ethMatched = ethMatched.add(_amount);
setUint(keccak256(abi.encodePacked("eth.matched.node.amount", _nodeAddress)), ethMatched);
}
/// @dev Adds a minipool to the queue
function enqueueMinipool(address _minipoolAddress) private {
// Add minipool to queue
RocketMinipoolQueueInterface(getContractAddress("rocketMinipoolQueue")).enqueueMinipool(_minipoolAddress);
}
/// @dev Returns the ETH amount used in a pre launch
function getPreLaunchValue() private view returns (uint256) {
RocketDAOProtocolSettingsMinipoolInterface rocketDAOProtocolSettingsMinipool = RocketDAOProtocolSettingsMinipoolInterface(getContractAddress("rocketDAOProtocolSettingsMinipool"));
return rocketDAOProtocolSettingsMinipool.getPreLaunchValue();
}
/// @dev Reverts if node operator has not initialised their fee distributor
function checkDistributorInitialised() private view {
// Check node has initialised their fee distributor
RocketNodeManagerInterface rocketNodeManager = RocketNodeManagerInterface(getContractAddress("rocketNodeManager"));
require(rocketNodeManager.getFeeDistributorInitialised(msg.sender), "Fee distributor not initialised");
}
/// @dev Creates a minipool and returns an instance of it
/// @param _salt The salt used to determine the minipools address
/// @param _expectedMinipoolAddress The expected minipool address. Reverts if not correct
function createMinipool(uint256 _salt, address _expectedMinipoolAddress) private returns (RocketMinipoolInterface) {
// Load contracts
RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager"));
// Check minipool doesn't exist or previously exist
require(!rocketMinipoolManager.getMinipoolExists(_expectedMinipoolAddress) && !rocketMinipoolManager.getMinipoolDestroyed(_expectedMinipoolAddress), "Minipool already exists or was previously destroyed");
// Create minipool
RocketMinipoolInterface minipool = rocketMinipoolManager.createMinipool(msg.sender, _salt);
// Ensure minipool address matches expected
require(address(minipool) == _expectedMinipoolAddress, "Unexpected minipool address");
// Return
return minipool;
}
/// @dev Creates a vacant minipool and returns an instance of it
/// @param _salt The salt used to determine the minipools address
/// @param _validatorPubkey Pubkey of the validator owning this minipool
/// @param _bondAmount ETH value the node operator is putting up as capital for this minipool
/// @param _expectedMinipoolAddress The expected minipool address. Reverts if not correct
function _createVacantMinipool(uint256 _salt, bytes calldata _validatorPubkey, uint256 _bondAmount, address _expectedMinipoolAddress) private returns (RocketMinipoolInterface) {
// Load contracts
RocketMinipoolManagerInterface rocketMinipoolManager = RocketMinipoolManagerInterface(getContractAddress("rocketMinipoolManager"));
// Check minipool doesn't exist or previously exist
require(!rocketMinipoolManager.getMinipoolExists(_expectedMinipoolAddress) && !rocketMinipoolManager.getMinipoolDestroyed(_expectedMinipoolAddress), "Minipool already exists or was previously destroyed");
// Create minipool
RocketMinipoolInterface minipool = rocketMinipoolManager.createVacantMinipool(msg.sender, _salt, _validatorPubkey, _bondAmount);
// Ensure minipool address matches expected
require(address(minipool) == _expectedMinipoolAddress, "Unexpected minipool address");
// Return
return minipool;
}
/// @dev Reverts if network node fee is below a minimum
/// @param _minimumNodeFee The minimum node fee required to not revert
function checkNodeFee(uint256 _minimumNodeFee) private view {
// Load contracts
RocketNetworkFeesInterface rocketNetworkFees = RocketNetworkFeesInterface(getContractAddress("rocketNetworkFees"));
// Check current node fee
uint256 nodeFee = rocketNetworkFees.getNodeFee();
require(nodeFee >= _minimumNodeFee, "Minimum node fee exceeds current network node fee");
}
/// @dev Reverts if deposits are not enabled
function checkDepositsEnabled() private view {
// Get contracts
RocketDAOProtocolSettingsNodeInterface rocketDAOProtocolSettingsNode = RocketDAOProtocolSettingsNodeInterface(getContractAddress("rocketDAOProtocolSettingsNode"));
// Check node settings
require(rocketDAOProtocolSettingsNode.getDepositEnabled(), "Node deposits are currently disabled");
}
/// @dev Executes an assignDeposits call on the deposit pool
function assignDeposits() private {
RocketDAOProtocolSettingsDepositInterface rocketDAOProtocolSettingsDeposit = RocketDAOProtocolSettingsDepositInterface(getContractAddress("rocketDAOProtocolSettingsDeposit"));
if (rocketDAOProtocolSettingsDeposit.getAssignDepositsEnabled()) {
RocketDepositPoolInterface rocketDepositPool = RocketDepositPoolInterface(getContractAddress("rocketDepositPool"));
rocketDepositPool.assignDeposits();
}
}
}