Skip to content

Commit 816bf1d

Browse files
authored
Merge pull request #367 from lidofinance/develop
Update the `master` branch
2 parents 36d0505 + 9d9c720 commit 816bf1d

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+3056
-303
lines changed

.solcover.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
module.exports = {
22
skipFiles: ['template', 'test_helpers', 'oracle/test_helpers', 'nos/test_helpers', 'mocks'],
3-
providerOptions: {
4-
default_balance_ether: 10000,
5-
gasPrice: '0x1',
6-
},
3+
mocha: {
4+
enableTimeouts: false
5+
}
76
}

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ The contract also works as a wrapper that accepts stETH tokens and mints wstETH
5757
* Node Operators registry: [`0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5`](https://etherscan.io/address/0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5) (proxy)
5858
* Oracle: [`0x442af784A788A5bd6F42A01Ebe9F287a871243fb`](https://etherscan.io/address/0x442af784A788A5bd6F42A01Ebe9F287a871243fb) (proxy)
5959
* WstETH token: [`0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0`](https://etherscan.io/token/0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0)
60+
* Deposit Security Module: [`0xDb149235B6F40dC08810AA69869783Be101790e7`](https://etherscan.io/address/0xDb149235B6F40dC08810AA69869783Be101790e7)
6061
* Aragon Voting: [`0x2e59A20f205bB85a89C53f1936454680651E618e`](https://etherscan.io/address/0x2e59A20f205bB85a89C53f1936454680651E618e) (proxy)
6162
* Aragon Token Manager: [`0xf73a1260d222f447210581DDf212D915c09a3249`](https://etherscan.io/address/0xf73a1260d222f447210581DDf212D915c09a3249) (proxy)
6263
* Aragon Finance: [`0xB9E5CBB9CA5b0d659238807E84D0176930753d86`](https://etherscan.io/address/0xB9E5CBB9CA5b0d659238807E84D0176930753d86) (proxy)
@@ -70,6 +71,7 @@ The contract also works as a wrapper that accepts stETH tokens and mints wstETH
7071
* Node Operators registry: [`0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320`](https://goerli.etherscan.io/address/0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320) (proxy)
7172
* Oracle: [`0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB`](https://goerli.etherscan.io/address/0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB) (proxy)
7273
* WstETH token: [`0x1643e812ae58766192cf7d2cf9567df2c37e9b7f`](https://goerli.etherscan.io/address/0x1643e812ae58766192cf7d2cf9567df2c37e9b7f)
74+
* Deposit Security Module: [`0xEd23AD3EA5Fb9d10e7371Caef1b141AD1C23A80c`](https://goerli.etherscan.io/address/0xEd23AD3EA5Fb9d10e7371Caef1b141AD1C23A80c)
7375
* Aragon Voting: [`0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db`](https://goerli.etherscan.io/address/0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db) (proxy)
7476
* Aragon Token Manager: [`0xDfe76d11b365f5e0023343A367f0b311701B3bc1`](https://goerli.etherscan.io/address/0xDfe76d11b365f5e0023343A367f0b311701B3bc1) (proxy)
7577
* Aragon Finance: [`0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179`](https://goerli.etherscan.io/address/0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179) (proxy)

apps/node-operators-registry/app/src/App.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,7 @@ function App() {
5050
[]
5151
)
5252
const addNodeOperatorApi = useCallback(
53-
(name, address, limit) =>
54-
api.addNodeOperator(name, address, limit).toPromise(),
53+
(name, address) => api.addNodeOperator(name, address).toPromise(),
5554
[api]
5655
)
5756

apps/node-operators-registry/app/src/components/AddNodeOperatorSidePanel.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ const validationSchema = yup.object().shape({
1818

1919
function PanelContent({ addNodeOperatorApi, onClose }) {
2020
const onSubmit = useCallback(
21-
({ name, address, limit }) => {
22-
addNodeOperatorApi(name, address, limit)
21+
({ name, address }) => {
22+
addNodeOperatorApi(name, address)
2323
.catch(console.error)
2424
.finally(() => {
2525
onClose()

contracts/0.4.24/Lido.sol

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp {
4646
bytes32 constant public BURN_ROLE = keccak256("BURN_ROLE");
4747
bytes32 constant public SET_TREASURY = keccak256("SET_TREASURY");
4848
bytes32 constant public SET_INSURANCE_FUND = keccak256("SET_INSURANCE_FUND");
49+
bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE");
4950

5051
uint256 constant public PUBKEY_LENGTH = 48;
5152
uint256 constant public WITHDRAWAL_CREDENTIALS_LENGTH = 32;
@@ -56,7 +57,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp {
5657
uint256 internal constant DEPOSIT_AMOUNT_UNIT = 1000000000 wei;
5758

5859
/// @dev default value for maximum number of Ethereum 2.0 validators registered in a single depositBufferedEther call
59-
uint256 internal constant DEFAULT_MAX_DEPOSITS_PER_CALL = 16;
60+
uint256 internal constant DEFAULT_MAX_DEPOSITS_PER_CALL = 150;
6061

6162
bytes32 internal constant FEE_POSITION = keccak256("lido.Lido.fee");
6263
bytes32 internal constant TREASURY_FEE_POSITION = keccak256("lido.Lido.treasuryFee");
@@ -131,15 +132,15 @@ contract Lido is ILido, IsContract, StETH, AragonApp {
131132
* @notice Deposits buffered ethers to the official DepositContract.
132133
* @dev This function is separated from submit() to reduce the cost of sending funds.
133134
*/
134-
function depositBufferedEther() external {
135+
function depositBufferedEther() external auth(DEPOSIT_ROLE) {
135136
return _depositBufferedEther(DEFAULT_MAX_DEPOSITS_PER_CALL);
136137
}
137138

138139
/**
139140
* @notice Deposits buffered ethers to the official DepositContract, making no more than `_maxDeposits` deposit calls.
140141
* @dev This function is separated from submit() to reduce the cost of sending funds.
141142
*/
142-
function depositBufferedEther(uint256 _maxDeposits) external {
143+
function depositBufferedEther(uint256 _maxDeposits) external auth(DEPOSIT_ROLE) {
143144
return _depositBufferedEther(_maxDeposits);
144145
}
145146

@@ -297,7 +298,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp {
297298
uint256 balance;
298299
if (_token == ETH) {
299300
balance = _getUnaccountedEther();
300-
vault.transfer(balance);
301+
// Transfer replaced by call to prevent transfer gas amount issue
302+
require(vault.call.value(balance)(), "RECOVER_TRANSFER_FAILED");
301303
} else {
302304
ERC20 token = ERC20(_token);
303305
balance = token.staticBalanceOf(this);

contracts/0.4.24/interfaces/INodeOperatorsRegistry.sol

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,12 @@ pragma solidity 0.4.24;
1313
*/
1414
interface INodeOperatorsRegistry {
1515
/**
16-
* @notice Add node operator named `name` with reward address `rewardAddress` and staking limit `stakingLimit` validators
16+
* @notice Add node operator named `name` with reward address `rewardAddress` and staking limit = 0 validators
1717
* @param _name Human-readable name
1818
* @param _rewardAddress Ethereum 1 address which receives stETH rewards for this operator
19-
* @param _stakingLimit the maximum number of validators to stake for this operator
2019
* @return a unique key of the added operator
2120
*/
22-
function addNodeOperator(string _name, address _rewardAddress, uint64 _stakingLimit) external returns (uint256 id);
21+
function addNodeOperator(string _name, address _rewardAddress) external returns (uint256 id);
2322

2423
/**
2524
* @notice `_active ? 'Enable' : 'Disable'` the node operator #`_id`
@@ -91,6 +90,7 @@ interface INodeOperatorsRegistry {
9190
event NodeOperatorRewardAddressSet(uint256 indexed id, address rewardAddress);
9291
event NodeOperatorStakingLimitSet(uint256 indexed id, uint64 stakingLimit);
9392
event NodeOperatorTotalStoppedValidatorsReported(uint256 indexed id, uint64 totalStopped);
93+
event NodeOperatorTotalKeysTrimmed(uint256 indexed id, uint64 totalKeysTrimmed);
9494

9595
/**
9696
* @notice Selects and returns at most `_numKeys` signing keys (as well as the corresponding
@@ -115,13 +115,49 @@ interface INodeOperatorsRegistry {
115115
*/
116116
function addSigningKeys(uint256 _operator_id, uint256 _quantity, bytes _pubkeys, bytes _signatures) external;
117117

118+
/**
119+
* @notice Add `_quantity` validator signing keys of operator #`_id` to the set of usable keys. Concatenated keys are: `_pubkeys`. Can be done by node operator in question by using the designated rewards address.
120+
* @dev Along with each key the DAO has to provide a signatures for the
121+
* (pubkey, withdrawal_credentials, 32000000000) message.
122+
* Given that information, the contract'll be able to call
123+
* deposit_contract.deposit on-chain.
124+
* @param _operator_id Node Operator id
125+
* @param _quantity Number of signing keys provided
126+
* @param _pubkeys Several concatenated validator signing keys
127+
* @param _signatures Several concatenated signatures for (pubkey, withdrawal_credentials, 32000000000) messages
128+
*/
129+
function addSigningKeysOperatorBH(uint256 _operator_id, uint256 _quantity, bytes _pubkeys, bytes _signatures) external;
130+
118131
/**
119132
* @notice Removes a validator signing key #`_index` from the keys of the node operator #`_operator_id`
120133
* @param _operator_id Node Operator id
121134
* @param _index Index of the key, starting with 0
122135
*/
123136
function removeSigningKey(uint256 _operator_id, uint256 _index) external;
124137

138+
/**
139+
* @notice Removes a validator signing key #`_index` of operator #`_id` from the set of usable keys. Executed on behalf of Node Operator.
140+
* @param _operator_id Node Operator id
141+
* @param _index Index of the key, starting with 0
142+
*/
143+
function removeSigningKeyOperatorBH(uint256 _operator_id, uint256 _index) external;
144+
145+
/**
146+
* @notice Removes an #`_amount` of validator signing keys starting from #`_index` of operator #`_id` usable keys. Executed on behalf of DAO.
147+
* @param _operator_id Node Operator id
148+
* @param _index Index of the key, starting with 0
149+
* @param _amount Number of keys to remove
150+
*/
151+
function removeSigningKeys(uint256 _operator_id, uint256 _index, uint256 _amount) external;
152+
153+
/**
154+
* @notice Removes an #`_amount` of validator signing keys starting from #`_index` of operator #`_id` usable keys. Executed on behalf of Node Operator.
155+
* @param _operator_id Node Operator id
156+
* @param _index Index of the key, starting with 0
157+
* @param _amount Number of keys to remove
158+
*/
159+
function removeSigningKeysOperatorBH(uint256 _operator_id, uint256 _index, uint256 _amount) external;
160+
125161
/**
126162
* @notice Returns total number of signing keys of the node operator #`_operator_id`
127163
*/
@@ -143,6 +179,18 @@ interface INodeOperatorsRegistry {
143179
function getSigningKey(uint256 _operator_id, uint256 _index) external view returns
144180
(bytes key, bytes depositSignature, bool used);
145181

182+
183+
/**
184+
* @notice Returns a monotonically increasing counter that gets incremented when any of the following happens:
185+
* 1. a node operator's key(s) is added;
186+
* 2. a node operator's key(s) is removed;
187+
* 3. a node operator's approved keys limit is changed.
188+
* 4. a node operator was activated/deactivated. Activation or deactivation of node operator
189+
* might lead to usage of unvalidated keys in the assignNextSigningKeys method.
190+
*/
191+
function getKeysOpIndex() external view returns (uint256);
192+
146193
event SigningKeyAdded(uint256 indexed operatorId, bytes pubkey);
147194
event SigningKeyRemoved(uint256 indexed operatorId, bytes pubkey);
195+
event KeysOpIndexSet(uint256 keysOpIndex);
148196
}

contracts/0.4.24/nos/NodeOperatorsRegistry.sol

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
7979
/// @dev link to the Lido contract
8080
bytes32 internal constant LIDO_POSITION = keccak256("lido.NodeOperatorsRegistry.lido");
8181

82+
/// @dev link to the index of operations with keys
83+
bytes32 internal constant KEYS_OP_INDEX_POSITION = keccak256("lido.NodeOperatorsRegistry.keysOpIndex");
84+
8285

8386
modifier onlyLido() {
8487
require(msg.sender == LIDO_POSITION.getStorageAddress(), "APP_AUTH_FAILED");
@@ -98,18 +101,18 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
98101
function initialize(address _lido) public onlyInit {
99102
TOTAL_OPERATORS_COUNT_POSITION.setStorageUint256(0);
100103
ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(0);
104+
KEYS_OP_INDEX_POSITION.setStorageUint256(0);
101105
LIDO_POSITION.setStorageAddress(_lido);
102106
initialized();
103107
}
104108

105109
/**
106-
* @notice Add node operator named `_name` with reward address `_rewardAddress` and staking limit `_stakingLimit`
110+
* @notice Add node operator named `_name` with reward address `_rewardAddress` and staking limit = 0
107111
* @param _name Human-readable name
108112
* @param _rewardAddress Ethereum 1 address which receives stETH rewards for this operator
109-
* @param _stakingLimit the maximum number of validators to stake for this operator
110113
* @return a unique key of the added operator
111114
*/
112-
function addNodeOperator(string _name, address _rewardAddress, uint64 _stakingLimit) external
115+
function addNodeOperator(string _name, address _rewardAddress) external
113116
auth(ADD_NODE_OPERATOR_ROLE)
114117
validAddress(_rewardAddress)
115118
returns (uint256 id)
@@ -125,9 +128,9 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
125128
operator.active = true;
126129
operator.name = _name;
127130
operator.rewardAddress = _rewardAddress;
128-
operator.stakingLimit = _stakingLimit;
131+
operator.stakingLimit = 0;
129132

130-
emit NodeOperatorAdded(id, _name, _rewardAddress, _stakingLimit);
133+
emit NodeOperatorAdded(id, _name, _rewardAddress, 0);
131134

132135
return id;
133136
}
@@ -139,6 +142,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
139142
authP(SET_NODE_OPERATOR_ACTIVE_ROLE, arr(_id, _active ? uint256(1) : uint256(0)))
140143
operatorExists(_id)
141144
{
145+
_increaseKeysOpIndex();
142146
if (operators[_id].active != _active) {
143147
uint256 activeOperatorsCount = getActiveNodeOperatorsCount();
144148
if (_active)
@@ -182,6 +186,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
182186
authP(SET_NODE_OPERATOR_LIMIT_ROLE, arr(_id, uint256(_stakingLimit)))
183187
operatorExists(_id)
184188
{
189+
_increaseKeysOpIndex();
185190
operators[_id].stakingLimit = _stakingLimit;
186191
emit NodeOperatorStakingLimitSet(_id, _stakingLimit);
187192
}
@@ -207,8 +212,12 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
207212
function trimUnusedKeys() external onlyLido {
208213
uint256 length = getNodeOperatorsCount();
209214
for (uint256 operatorId = 0; operatorId < length; ++operatorId) {
210-
if (operators[operatorId].totalSigningKeys != operators[operatorId].usedSigningKeys) // write only if update is needed
211-
operators[operatorId].totalSigningKeys = operators[operatorId].usedSigningKeys; // discard unused keys
215+
uint64 totalSigningKeys = operators[operatorId].totalSigningKeys;
216+
uint64 usedSigningKeys = operators[operatorId].usedSigningKeys;
217+
if (totalSigningKeys != usedSigningKeys) { // write only if update is needed
218+
operators[operatorId].totalSigningKeys = usedSigningKeys; // discard unused keys
219+
emit NodeOperatorTotalKeysTrimmed(operatorId, totalSigningKeys - usedSigningKeys);
220+
}
212221
}
213222
}
214223

@@ -264,6 +273,22 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
264273
_removeSigningKey(_operator_id, _index);
265274
}
266275

276+
/**
277+
* @notice Removes an #`_amount` of validator signing keys starting from #`_index` of operator #`_id` usable keys. Executed on behalf of DAO.
278+
* @param _operator_id Node Operator id
279+
* @param _index Index of the key, starting with 0
280+
* @param _amount Number of keys to remove
281+
*/
282+
function removeSigningKeys(uint256 _operator_id, uint256 _index, uint256 _amount)
283+
external
284+
authP(MANAGE_SIGNING_KEYS, arr(_operator_id))
285+
{
286+
// removing from the last index to the highest one, so we won't get outside the array
287+
for (uint256 i = _index + _amount; i > _index ; --i) {
288+
_removeSigningKey(_operator_id, i - 1);
289+
}
290+
}
291+
267292
/**
268293
* @notice Removes a validator signing key #`_index` of operator #`_id` from the set of usable keys. Executed on behalf of Node Operator.
269294
* @param _operator_id Node Operator id
@@ -274,6 +299,20 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
274299
_removeSigningKey(_operator_id, _index);
275300
}
276301

302+
/**
303+
* @notice Removes an #`_amount` of validator signing keys starting from #`_index` of operator #`_id` usable keys. Executed on behalf of Node Operator.
304+
* @param _operator_id Node Operator id
305+
* @param _index Index of the key, starting with 0
306+
* @param _amount Number of keys to remove
307+
*/
308+
function removeSigningKeysOperatorBH(uint256 _operator_id, uint256 _index, uint256 _amount) external {
309+
require(msg.sender == operators[_operator_id].rewardAddress, "APP_AUTH_FAILED");
310+
// removing from the last index to the highest one, so we won't get outside the array
311+
for (uint256 i = _index + _amount; i > _index ; --i) {
312+
_removeSigningKey(_operator_id, i - 1);
313+
}
314+
}
315+
277316
/**
278317
* @notice Selects and returns at most `_numKeys` signing keys (as well as the corresponding
279318
* signatures) from the set of active keys and marks the selected keys as used.
@@ -484,6 +523,18 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
484523
return TOTAL_OPERATORS_COUNT_POSITION.getStorageUint256();
485524
}
486525

526+
/**
527+
* @notice Returns a monotonically increasing counter that gets incremented when any of the following happens:
528+
* 1. a node operator's key(s) is added;
529+
* 2. a node operator's key(s) is removed;
530+
* 3. a node operator's approved keys limit is changed.
531+
* 4. a node operator was activated/deactivated. Activation or deactivation of node operator
532+
* might lead to usage of unvalidated keys in the assignNextSigningKeys method.
533+
*/
534+
function getKeysOpIndex() public view returns (uint256) {
535+
return KEYS_OP_INDEX_POSITION.getStorageUint256();
536+
}
537+
487538
function _isEmptySigningKey(bytes memory _key) internal pure returns (bool) {
488539
assert(_key.length == PUBKEY_LENGTH);
489540
// algorithm applicability constraint
@@ -540,6 +591,8 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
540591
require(_pubkeys.length == _quantity.mul(PUBKEY_LENGTH), "INVALID_LENGTH");
541592
require(_signatures.length == _quantity.mul(SIGNATURE_LENGTH), "INVALID_LENGTH");
542593

594+
_increaseKeysOpIndex();
595+
543596
for (uint256 i = 0; i < _quantity; ++i) {
544597
bytes memory key = BytesLib.slice(_pubkeys, i * PUBKEY_LENGTH, PUBKEY_LENGTH);
545598
require(!_isEmptySigningKey(key), "EMPTY_KEY");
@@ -558,6 +611,8 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
558611
require(_index < operators[_operator_id].totalSigningKeys, "KEY_NOT_FOUND");
559612
require(_index >= operators[_operator_id].usedSigningKeys, "KEY_WAS_USED");
560613

614+
_increaseKeysOpIndex();
615+
561616
(bytes memory removedKey, ) = _loadSigningKey(_operator_id, _index);
562617

563618
uint256 lastIndex = operators[_operator_id].totalSigningKeys.sub(1);
@@ -569,6 +624,11 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
569624
_deleteSigningKey(_operator_id, lastIndex);
570625
operators[_operator_id].totalSigningKeys = operators[_operator_id].totalSigningKeys.sub(1);
571626

627+
if (_index < operators[_operator_id].stakingLimit) {
628+
// decreasing the staking limit so the key at _index can't be used anymore
629+
operators[_operator_id].stakingLimit = uint64(_index);
630+
}
631+
572632
emit SigningKeyRemoved(_operator_id, removedKey);
573633
}
574634

@@ -634,4 +694,10 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp
634694

635695
return cache;
636696
}
697+
698+
function _increaseKeysOpIndex() internal {
699+
uint256 keysOpIndex = getKeysOpIndex();
700+
KEYS_OP_INDEX_POSITION.setStorageUint256(keysOpIndex + 1);
701+
emit KeysOpIndexSet(keysOpIndex + 1);
702+
}
637703
}

contracts/0.4.24/test_helpers/DepositContractMock.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ contract DepositContractMock is IDepositContract {
2020
}
2121

2222
Call[] public calls;
23+
bytes32 internal depositRoot;
2324

2425
function deposit(
2526
bytes /* 48 */ pubkey,
@@ -40,4 +41,12 @@ contract DepositContractMock is IDepositContract {
4041
function reset() external {
4142
calls.length = 0;
4243
}
44+
45+
function get_deposit_root() external view returns (bytes32) {
46+
return depositRoot;
47+
}
48+
49+
function set_deposit_root(bytes32 _newRoot) external {
50+
depositRoot = _newRoot;
51+
}
4352
}

0 commit comments

Comments
 (0)