From e0872045b7ec75cadae548b8967059fcea5e1483 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sun, 19 Dec 2021 00:44:54 +0300 Subject: [PATCH 001/159] fix: add `StETHBurnt` event for the `StETH` token The new event logs account addr, amount and shares amount. --- contracts/0.4.24/StETH.sol | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index c510cf959..9a8876b2f 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -81,6 +81,22 @@ contract StETH is IERC20, Pausable { */ bytes32 internal constant TOTAL_SHARES_POSITION = keccak256("lido.StETH.totalShares"); + /** + * @notice An executed stETH burn event + * + * @dev Reports simultaneously stETH amount and shares amount. + * The stETH amount is calculated before the burning incurred rebase. + * + * @param account holder of the burnt stETH + * @param amount amount of burnt stETH + * @param sharesAmount amount of burnt shares + */ + event StETHBurnt( + address indexed account, + uint256 amount, + uint256 sharesAmount + ); + /** * @return the name of the token. */ @@ -403,6 +419,9 @@ contract StETH is IERC20, Pausable { uint256 accountShares = shares[_account]; require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE"); + uint256 amount = getPooledEthByShares(_sharesAmount); + emit StETHBurnt(_account, amount, _sharesAmount); + newTotalShares = _getTotalShares().sub(_sharesAmount); TOTAL_SHARES_POSITION.setStorageUint256(newTotalShares); @@ -413,5 +432,7 @@ contract StETH is IERC20, Pausable { // all other token holders. The total supply of the token doesn't change as the result. // This is equivalent to performing a send from `address` to each other token holder address, // but we cannot reflect this as it would require sending an unbounded number of events. + + // We're emitting StETHBurnt event to provide an explicit rebase log record nonetheless. } } From c359fd1ac55743fc4ebdfe1276ad98910148cacb Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sun, 19 Dec 2021 01:02:15 +0300 Subject: [PATCH 002/159] test: update tests on the `StETHBurnEvent` event --- test/0.4.24/lido.test.js | 12 ++++++++++-- test/0.4.24/steth.test.js | 14 +++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index ec52c1efe..6d701518d 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -1061,8 +1061,16 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.burnShares(user1, ETH(1), { from: nobody }), 'APP_AUTH_FAILED') // voting can burn shares of any user - await app.burnShares(user1, ETH(0.5), { from: voting }) - await app.burnShares(user1, ETH(0.5), { from: voting }) + const expectedAmount = await app.getPooledEthByShares(ETH(0.5)) + let receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) + assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: ETH(0.5) } }) + + const expectedDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) + receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) + assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: expectedDoubledAmount, sharesAmount: ETH(0.5) } }) + + assertBn(expectedAmount.mul(bn(2)), expectedDoubledAmount) + assertBn(tokens(0), await app.getPooledEthByShares(ETH(0.5))) // user1 has zero shares afteralls assertBn(await app.sharesOf(user1), tokens(0)) diff --git a/test/0.4.24/steth.test.js b/test/0.4.24/steth.test.js index 7c36f6ac5..416044ddd 100644 --- a/test/0.4.24/steth.test.js +++ b/test/0.4.24/steth.test.js @@ -356,7 +356,9 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) it('burning zero value works', async () => { - await stEth.burnShares(user1, tokens(0)) + const receipt = await stEth.burnShares(user1, tokens(0)) + assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: tokens(0), sharesAmount: tokens(0) } }) + assertBn(await stEth.totalSupply(), tokens(300)) assertBn(await stEth.balanceOf(user1), tokens(100)) assertBn(await stEth.balanceOf(user2), tokens(100)) @@ -377,7 +379,10 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { totalSupply.mul(totalShares.sub(user1Shares)).div(totalSupply.sub(user1Balance).add(bn(tokens(10)))) ) - await stEth.burnShares(user1, sharesToBurn) + const expectedAmount = await stEth.getPooledEthByShares(sharesToBurn) + const receipt = await stEth.burnShares(user1, sharesToBurn) + assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) + assertBn(await stEth.totalSupply(), tokens(300)) assertBn(await stEth.balanceOf(user1), bn(tokens(90)).subn(1)) // expected round error assertBn(await stEth.balanceOf(user2), tokens(105)) @@ -400,7 +405,10 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { totalSupply.mul(totalShares.sub(user1Shares)).div(totalSupply.sub(user1Balance).add(bn(tokens(50)))) ) - await stEth.burnShares(user1, sharesToBurn) + const expectedAmount = await stEth.getPooledEthByShares(sharesToBurn) + const receipt = await stEth.burnShares(user1, sharesToBurn) + assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) + assertBn(await stEth.balanceOf(user1), tokens(50)) assertBn(await stEth.allowance(user1, user2), tokens(75)) From 08969d2b8447b59355336ca80d768a0e532f1476 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 30 Dec 2021 12:56:17 +0300 Subject: [PATCH 003/159] feat: add `transferShares` function Sometimes we should user shares transfers instead of token transfers to avoid rounding issues. Introduce function and new `TransferShares` event. Note: also emits conventional `Transfer` event for the compatibility with existing indexing off-chain solutions subscribed to transfers. --- contracts/0.4.24/StETH.sol | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index c510cf959..333810244 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -81,6 +81,15 @@ contract StETH is IERC20, Pausable { */ bytes32 internal constant TOTAL_SHARES_POSITION = keccak256("lido.StETH.totalShares"); + /** + * @notice An executed shares transfer from `sender` to `recipient` + */ + event TransferShares( + address indexed from, + address indexed to, + uint256 sharesValue + ); + /** * @return the name of the token. */ @@ -291,6 +300,29 @@ contract StETH is IERC20, Pausable { } } + /** + * @notice Moves `_sharesAmount` token shares from the caller's account to the `_recipient` account. + * + * @return a boolean value indicating whether the operation succeeded. + * Emits a `TransferShares` event. + * Emits a `Transfer` event. + * + * Requirements: + * + * - `_recipient` cannot be the zero address. + * - the caller must have at least `_sharesAmount` shares. + * - the contract must not be paused. + * + * @dev The `_sharesAmount` argument is the amount of shares, not tokens. + */ + function transferShares(address _recipient, uint256 _sharesAmount) public returns (bool) { + _transferShares(msg.sender, _recipient, _sharesAmount); + emit TransferShares(msg.sender, _recipient, _sharesAmount); + uint256 tokensAmount = getPooledEthByShares(_sharesAmount); + emit Transfer(msg.sender, _recipient, tokensAmount); + return true; + } + /** * @return the total amount (in wei) of Ether controlled by the protocol. * @dev This is used for calaulating tokens from shares and vice versa. From 5e07507c1411fe0a240200b7df0d33c864891ad3 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 30 Dec 2021 12:58:56 +0300 Subject: [PATCH 004/159] test: add unit tests for the `transferShares` func Add tests on transfer itself and on the logging `Transfer` and `TransferShares` events. --- test/0.4.24/steth.test.js | 50 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/test/0.4.24/steth.test.js b/test/0.4.24/steth.test.js index 7c36f6ac5..d957f12aa 100644 --- a/test/0.4.24/steth.test.js +++ b/test/0.4.24/steth.test.js @@ -1,5 +1,5 @@ const { assert } = require('chai') -const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') +const { assertBn, assertRevert, assertEvent, assertAmountOfEvents } = require('@aragon/contract-helpers-test/src/asserts') const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') const StETH = artifacts.require('StETHMock') @@ -412,7 +412,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { }) }) - context('share-related getters', async () => { + context('share-related getters and transfers', async () => { context('with zero totalPooledEther (supply)', async () => { it('getTotalSupply', async () => { assertBn(await stEth.totalSupply({ from: nobody }), tokens(0)) @@ -445,6 +445,16 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertBn(await stEth.getSharesByPooledEth(tokens(0)), tokens(0)) assertBn(await stEth.getSharesByPooledEth(tokens(100)), tokens(0)) }) + + it('transferShares', async () => { + assertBn(await stEth.balanceOf(nobody), tokens(0)) + + const receipt = await stEth.transferShares(user1, tokens(0), { from: nobody }) + assertEvent(receipt, 'Transfer', { expectedArgs: { from: nobody, to: user1, value: tokens(0) } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: nobody, to: user1, sharesValue: tokens(0) } }) + + assertBn(await stEth.balanceOf(nobody), tokens(0)) + }) }) context('with non-zero totalPooledEther (supply)', async () => { @@ -485,6 +495,42 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assertBn(await stEth.getSharesByPooledEth(tokens(1)), tokens(1)) assertBn(await stEth.getSharesByPooledEth(tokens(100)), tokens(100)) }) + + it('transferShares', async () => { + assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(nobody), tokens(0)) + + let receipt = await stEth.transferShares(nobody, tokens(0), { from: user1 }) + assertAmountOfEvents(receipt, 'Transfer', { expectedAmount: 1 }) + assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) + assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(0) } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(0) } }) + + assertBn(await stEth.balanceOf(user1), tokens(100)) + assertBn(await stEth.balanceOf(nobody), tokens(0)) + + receipt = await stEth.transferShares(nobody, tokens(30), { from: user1 }) + assertAmountOfEvents(receipt, 'Transfer', { expectedAmount: 1 }) + assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) + assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(30) } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(30) } }) + + assertBn(await stEth.balanceOf(user1), tokens(70)) + assertBn(await stEth.balanceOf(nobody), tokens(30)) + + assertRevert(stEth.transferShares(nobody, tokens(75), { from: user1 }), 'TRANSFER_AMOUNT_EXCEEDS_BALANCE') + + await stEth.setTotalPooledEther(tokens(120)) + + receipt = await stEth.transferShares(nobody, tokens(70), { from: user1 }) + assertAmountOfEvents(receipt, 'Transfer', { expectedAmount: 1 }) + assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) + assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: nobody, value: tokens(84) } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: nobody, sharesValue: tokens(70) } }) + + assertBn(await stEth.balanceOf(user1), tokens(0)) + assertBn(await stEth.balanceOf(nobody), tokens(120)) + }) }) }) }) From 0bb9f552693fdda3409f28b38e0218f18659c7a0 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 9 Jan 2022 18:44:41 +0300 Subject: [PATCH 005/159] increase `keysOpIndex` in `assignNextSigningKeys` to make less difficult to track used keys --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 2 ++ .../0.4.24/nos/test_helpers/PoolMock.sol | 1 + test/0.4.24/node-operators-registry.test.js | 23 ++++++++++++++++--- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index bc12ca570..9b1b535cb 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -386,6 +386,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp for (uint256 keyIndex = entry.initialUsedSigningKeys; keyIndex < entry.usedSigningKeys; ++keyIndex) { (bytes memory pubkey, bytes memory signature) = _loadSigningKey(entry.id, keyIndex); if (numAssignedKeys == 1) { + _increaseKeysOpIndex(); return (pubkey, signature); } else { MemUtils.copyBytes(pubkey, pubkeys, numLoadedKeys * PUBKEY_LENGTH); @@ -399,6 +400,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp } } + _increaseKeysOpIndex(); // numAssignedKeys is guaranteed to be > 0 here assert(numLoadedKeys == numAssignedKeys); return (pubkeys, signatures); } diff --git a/contracts/0.4.24/nos/test_helpers/PoolMock.sol b/contracts/0.4.24/nos/test_helpers/PoolMock.sol index 7828c4fed..0045bbe95 100644 --- a/contracts/0.4.24/nos/test_helpers/PoolMock.sol +++ b/contracts/0.4.24/nos/test_helpers/PoolMock.sol @@ -8,6 +8,7 @@ import "../../interfaces/INodeOperatorsRegistry.sol"; */ contract PoolMock { event KeysAssigned(bytes pubkeys, bytes signatures); + event KeysOpIndexSet(uint256 keysOpIndex); INodeOperatorsRegistry private operators; diff --git a/test/0.4.24/node-operators-registry.test.js b/test/0.4.24/node-operators-registry.test.js index bb60450ba..e87595057 100644 --- a/test/0.4.24/node-operators-registry.test.js +++ b/test/0.4.24/node-operators-registry.test.js @@ -1,8 +1,8 @@ const { assert } = require('chai') -const { hexSplit } = require('../helpers/utils') +const { hexSplit, toBN } = require('../helpers/utils') const { newDao, newApp } = require('./helpers/dao') const { ZERO_ADDRESS, getEventAt, getEventArgument } = require('@aragon/contract-helpers-test') -const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') +const { assertBn, assertRevert, assertEvent, assertNoEvent } = require('@aragon/contract-helpers-test/src/asserts') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry.sol') const PoolMock = artifacts.require('PoolMock.sol') @@ -217,11 +217,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob }) it('assignNextSigningKeys works', async () => { + let keysOpIndex = await app.getKeysOpIndex() let result = await pool.assignNextSigningKeys(10) let keysAssignedEvt = getEventAt(result, 'KeysAssigned').args assert.equal(keysAssignedEvt.pubkeys, null, 'empty cache, no singing keys added: pubkeys') assert.equal(keysAssignedEvt.signatures, null, 'empty cache, no singing keys added: signatures') + assertBn(await app.getKeysOpIndex(), keysOpIndex, 'keysOpIndex must not increase if no keys were assigned') + assertNoEvent(result, 'KeysOpIndexSet') await app.addNodeOperator('fo o', ADDRESS_1, { from: voting }) await app.addNodeOperator(' bar', ADDRESS_2, { from: voting }) @@ -229,11 +232,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.setNodeOperatorStakingLimit(0, 10, { from: voting }) await app.setNodeOperatorStakingLimit(1, 10, { from: voting }) + keysOpIndex = await app.getKeysOpIndex() result = await pool.assignNextSigningKeys(10) keysAssignedEvt = getEventAt(result, 'KeysAssigned').args assert.equal(keysAssignedEvt.pubkeys, null, 'no singing keys added: pubkeys') assert.equal(keysAssignedEvt.signatures, null, 'no singing keys added: signatures') + assertBn(await app.getKeysOpIndex(), keysOpIndex, 'keysOpIndex must not increase if no keys were assigned') + assertNoEvent(result, 'KeysOpIndexSet') const op0 = { keys: [pad('0xaa0101', 48), pad('0xaa0202', 48), pad('0xaa0303', 48)], @@ -248,18 +254,25 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await app.addSigningKeys(0, 3, hexConcat(...op0.keys), hexConcat(...op0.sigs), { from: voting }) await app.addSigningKeys(1, 3, hexConcat(...op1.keys), hexConcat(...op1.sigs), { from: voting }) + keysOpIndex = await app.getKeysOpIndex() result = await pool.assignNextSigningKeys(1) keysAssignedEvt = getEventAt(result, 'KeysAssigned').args assert.equal(keysAssignedEvt.pubkeys, op0.keys[0], 'assignment 1: pubkeys') assert.equal(keysAssignedEvt.signatures, op0.sigs[0], 'assignment 1: signatures') + assertBn(await app.getKeysOpIndex(), keysOpIndex.add(toBN(1)), 'keysOpIndex must increase if any keys were assigned') + assertEvent(result, 'KeysOpIndexSet') + keysOpIndex = await app.getKeysOpIndex() result = await pool.assignNextSigningKeys(2) keysAssignedEvt = getEventAt(result, 'KeysAssigned').args assert.sameMembers(hexSplit(keysAssignedEvt.pubkeys, PUBKEY_LENGTH_BYTES), [op0.keys[1], op1.keys[0]], 'assignment 2: pubkeys') assert.sameMembers(hexSplit(keysAssignedEvt.signatures, SIGNATURE_LENGTH_BYTES), [op0.sigs[1], op1.sigs[0]], 'assignment 2: signatures') + assertBn(await app.getKeysOpIndex(), keysOpIndex.add(toBN(1)), 'keysOpIndex must increase if any keys were assigned') + assertEvent(result, 'KeysOpIndexSet') + keysOpIndex = await app.getKeysOpIndex() result = await pool.assignNextSigningKeys(10) keysAssignedEvt = getEventAt(result, 'KeysAssigned').args @@ -268,18 +281,22 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob [op0.keys[2], op1.keys[1], op1.keys[2]], 'assignment 2: pubkeys' ) - assert.sameMembers( hexSplit(keysAssignedEvt.signatures, SIGNATURE_LENGTH_BYTES), [op0.sigs[2], op1.sigs[1], op1.sigs[2]], 'assignment 2: signatures' ) + assertBn(await app.getKeysOpIndex(), keysOpIndex.add(toBN(1)), 'keysOpIndex must increase if any keys were assigned') + assertEvent(result, 'KeysOpIndexSet') + keysOpIndex = await app.getKeysOpIndex() result = await pool.assignNextSigningKeys(10) keysAssignedEvt = getEventAt(result, 'KeysAssigned').args assert.equal(keysAssignedEvt.pubkeys, null, 'no singing keys left: pubkeys') assert.equal(keysAssignedEvt.signatures, null, 'no singing keys left: signatures') + assertBn(await app.getKeysOpIndex(), keysOpIndex, 'keysOpIndex must not increase if no keys were assigned') + assertNoEvent(result, 'KeysOpIndexSet') }) it('assignNextSigningKeys skips stopped operators', async () => { From 938d0aa8fa5be401a4e97387c270b353a6c1ebc1 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 9 Jan 2022 21:12:19 +0300 Subject: [PATCH 006/159] node-operators-registry.test: fix incorrect import --- test/0.4.24/node-operators-registry.test.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/0.4.24/node-operators-registry.test.js b/test/0.4.24/node-operators-registry.test.js index e87595057..57794c280 100644 --- a/test/0.4.24/node-operators-registry.test.js +++ b/test/0.4.24/node-operators-registry.test.js @@ -2,7 +2,7 @@ const { assert } = require('chai') const { hexSplit, toBN } = require('../helpers/utils') const { newDao, newApp } = require('./helpers/dao') const { ZERO_ADDRESS, getEventAt, getEventArgument } = require('@aragon/contract-helpers-test') -const { assertBn, assertRevert, assertEvent, assertNoEvent } = require('@aragon/contract-helpers-test/src/asserts') +const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry.sol') const PoolMock = artifacts.require('PoolMock.sol') @@ -31,6 +31,11 @@ const hexConcat = (first, ...rest) => { return result } +const assertNoEvent = (receipt, eventName, msg) => { + const event = getEventAt(receipt, eventName) + assert.equal(event, undefined, msg) +} + const ETH = (value) => web3.utils.toWei(value + '', 'ether') const tokens = ETH From 596875d357ced2c0a1e176caf6c22a1eb13f5231 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 10 Jan 2022 12:33:10 +0300 Subject: [PATCH 007/159] fix: add `transferShares` into `IStETH` interface --- contracts/0.4.24/interfaces/ISTETH.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/0.4.24/interfaces/ISTETH.sol b/contracts/0.4.24/interfaces/ISTETH.sol index 6222d559e..35ec2e56d 100644 --- a/contracts/0.4.24/interfaces/ISTETH.sol +++ b/contracts/0.4.24/interfaces/ISTETH.sol @@ -64,6 +64,7 @@ interface ISTETH /* is IERC20 */ { function balanceOf(address owner) external view returns (uint256); function transfer(address to, uint256 value) external returns (bool); + function transferShares(address to, uint256 sharesValue) external returns (uint256); function getTotalShares() external view returns (uint256); From 95ac796164e2f961bf05dfb506497cc9e042cdc2 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 10 Jan 2022 12:34:19 +0300 Subject: [PATCH 008/159] fix: ask `Transfer` events explicitly for scentest --- test/scenario/lido_rewards_distribution_math.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scenario/lido_rewards_distribution_math.js b/test/scenario/lido_rewards_distribution_math.js index 232cd6c60..85e1e2772 100644 --- a/test/scenario/lido_rewards_distribution_math.js +++ b/test/scenario/lido_rewards_distribution_math.js @@ -554,7 +554,7 @@ contract('Lido: rewards distribution math', (addresses) => { } async function readLastPoolEventLog() { - const events = await pool.getPastEvents() + const events = await pool.getPastEvents('Transfer') let reportedMintAmount = new BN(0) const tos = [] const values = [] From b40f7edaa1ed2cee85179e8d2f7fd70eecc43a8d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 10 Jan 2022 12:38:36 +0300 Subject: [PATCH 009/159] fix: emit `TransferShares` from transfer calls too `transfer` and `transferShares` methods should emit same events sequence: - Transfer - TransferShares. --- contracts/0.4.24/Lido.sol | 3 ++- contracts/0.4.24/StETH.sol | 10 +++++++--- contracts/0.4.24/test_helpers/StETHMock.sol | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 60b8d7cd1..8339eb90c 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -458,10 +458,11 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @dev Emits an {Transfer} event where from is 0 address. Indicates mint events. + * @dev Emits {Transfer} and {TransferShares} events where `from` is 0 address. Indicates mint events. */ function _emitTransferAfterMintingShares(address _to, uint256 _sharesAmount) internal { emit Transfer(address(0), _to, getPooledEthByShares(_sharesAmount)); + emit TransferShares(address(0), _to, _sharesAmount); } /** diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 333810244..d17a55eff 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -146,6 +146,7 @@ contract StETH is IERC20, Pausable { * * @return a boolean value indicating whether the operation succeeded. * Emits a `Transfer` event. + * Emits a `TransferShares` event. * * Requirements: * @@ -196,6 +197,7 @@ contract StETH is IERC20, Pausable { * @return a boolean value indicating whether the operation succeeded. * * Emits a `Transfer` event. + * Emits a `TransferShares` event. * Emits an `Approval` event indicating the updated allowance. * * Requirements: @@ -303,7 +305,7 @@ contract StETH is IERC20, Pausable { /** * @notice Moves `_sharesAmount` token shares from the caller's account to the `_recipient` account. * - * @return a boolean value indicating whether the operation succeeded. + * @return amount of transferred tokens. * Emits a `TransferShares` event. * Emits a `Transfer` event. * @@ -315,12 +317,12 @@ contract StETH is IERC20, Pausable { * * @dev The `_sharesAmount` argument is the amount of shares, not tokens. */ - function transferShares(address _recipient, uint256 _sharesAmount) public returns (bool) { + function transferShares(address _recipient, uint256 _sharesAmount) public returns (uint256) { _transferShares(msg.sender, _recipient, _sharesAmount); emit TransferShares(msg.sender, _recipient, _sharesAmount); uint256 tokensAmount = getPooledEthByShares(_sharesAmount); emit Transfer(msg.sender, _recipient, tokensAmount); - return true; + return tokensAmount; } /** @@ -333,11 +335,13 @@ contract StETH is IERC20, Pausable { /** * @notice Moves `_amount` tokens from `_sender` to `_recipient`. * Emits a `Transfer` event. + * Emits a `TransferShares` event. */ function _transfer(address _sender, address _recipient, uint256 _amount) internal { uint256 _sharesToTransfer = getSharesByPooledEth(_amount); _transferShares(_sender, _recipient, _sharesToTransfer); emit Transfer(_sender, _recipient, _amount); + emit TransferShares(_sender, _recipient, _sharesToTransfer); } /** diff --git a/contracts/0.4.24/test_helpers/StETHMock.sol b/contracts/0.4.24/test_helpers/StETHMock.sol index 5d5f4c81e..f93b7d8e2 100644 --- a/contracts/0.4.24/test_helpers/StETHMock.sol +++ b/contracts/0.4.24/test_helpers/StETHMock.sol @@ -52,5 +52,6 @@ contract StETHMock is StETH { internal { emit Transfer(address(0), _to, getPooledEthByShares(_sharesAmount)); + emit TransferShares(address(0), _to, _sharesAmount); } } From 541232fd637f239099e562216ccf471becec61be Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 10 Jan 2022 12:40:59 +0300 Subject: [PATCH 010/159] test: add assertions for `TransferShares` events --- test/0.4.24/steth.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/0.4.24/steth.test.js b/test/0.4.24/steth.test.js index d957f12aa..4f53ec067 100644 --- a/test/0.4.24/steth.test.js +++ b/test/0.4.24/steth.test.js @@ -99,15 +99,23 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { it('transfer all balance works and emits event', async () => { const amount = await stEth.balanceOf(user1) const receipt = await stEth.transfer(user2, amount, { from: user1 }) + const sharesAmount = await stEth.getSharesByPooledEth(amount) + assertAmountOfEvents(receipt, 'Transfer', { expectedAmount: 1 }) + assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: user2, value: amount } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: user2, sharesValue: sharesAmount } }) assertBn(await stEth.balanceOf(user1), tokens(0)) assertBn(await stEth.balanceOf(user2), tokens(100)) }) it('transfer zero tokens works and emits event', async () => { const amount = bn('0') + const sharesAmount = bn('0') const receipt = await stEth.transfer(user2, amount, { from: user1 }) + assertAmountOfEvents(receipt, 'Transfer', { expectedAmount: 1 }) + assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: user2, value: amount } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: user2, sharesValue: sharesAmount } }) assertBn(await stEth.balanceOf(user1), tokens(100)) assertBn(await stEth.balanceOf(user2), tokens(0)) }) @@ -168,9 +176,14 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { it('transferFrom works and emits events', async () => { const amount = tokens(50) + const sharesAmount = await stEth.getSharesByPooledEth(amount) const receipt = await stEth.transferFrom(user1, user3, amount, { from: user2 }) + assertAmountOfEvents(receipt, 'Transfer', { expectedAmount: 1 }) + assertAmountOfEvents(receipt, 'TransferShares', { expectedAmount: 1 }) + assertAmountOfEvents(receipt, 'Approval', { expectedAmount: 1 }) assertEvent(receipt, 'Approval', { expectedArgs: { owner: user1, spender: user2, value: bn(0) } }) assertEvent(receipt, 'Transfer', { expectedArgs: { from: user1, to: user3, value: amount } }) + assertEvent(receipt, 'TransferShares', { expectedArgs: { from: user1, to: user3, sharesValue: sharesAmount } }) assertBn(await stEth.allowance(user2, user1), bn(0)) assertBn(await stEth.balanceOf(user1), tokens(50)) assertBn(await stEth.balanceOf(user3), tokens(50)) From 205830a49649e00b1fa9b9b23d5e4bacab0466f2 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 12 Jan 2022 15:14:28 +0300 Subject: [PATCH 011/159] fix: move `TransferShares` event to ISTETH Interface file should contain all events definitions. --- contracts/0.4.24/StETH.sol | 9 --------- contracts/0.4.24/interfaces/ISTETH.sol | 11 +++++++++++ 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index d17a55eff..b74b39f13 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -81,15 +81,6 @@ contract StETH is IERC20, Pausable { */ bytes32 internal constant TOTAL_SHARES_POSITION = keccak256("lido.StETH.totalShares"); - /** - * @notice An executed shares transfer from `sender` to `recipient` - */ - event TransferShares( - address indexed from, - address indexed to, - uint256 sharesValue - ); - /** * @return the name of the token. */ diff --git a/contracts/0.4.24/interfaces/ISTETH.sol b/contracts/0.4.24/interfaces/ISTETH.sol index 35ec2e56d..ee6b35570 100644 --- a/contracts/0.4.24/interfaces/ISTETH.sol +++ b/contracts/0.4.24/interfaces/ISTETH.sol @@ -34,6 +34,17 @@ interface ISTETH /* is IERC20 */ { */ function isStopped() external view returns (bool); + /** + * @notice An executed shares transfer from `sender` to `recipient`. + * + * @dev emitted in pair with an ERC20-defined `Transfer` event. + */ + event TransferShares( + address indexed from, + address indexed to, + uint256 sharesValue + ); + event Stopped(); event Resumed(); From 82191417649dedceb1e3e2c2b5dd85dce0266d03 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 12 Jan 2022 16:19:13 +0300 Subject: [PATCH 012/159] fix: make stETH is IStETH It is a controversary decision. Friday devteam sync planned for the resolution. --- contracts/0.4.24/StETH.sol | 3 ++- contracts/0.4.24/interfaces/ISTETH.sol | 15 --------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index b74b39f13..895cf1b2e 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -9,6 +9,7 @@ import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "@aragon/os/contracts/common/UnstructuredStorage.sol"; import "@aragon/os/contracts/lib/math/SafeMath.sol"; import "./lib/Pausable.sol"; +import "./interfaces/ISTETH.sol"; /** * @title Interest-bearing ERC20-like token for Lido Liquid Stacking protocol. @@ -47,7 +48,7 @@ import "./lib/Pausable.sol"; * DAO. This is useful for emergency scenarios, e.g. a protocol bug, where one might want * to freeze all token transfers and approvals until the emergency is resolved. */ -contract StETH is IERC20, Pausable { +contract StETH is IERC20, ISTETH, Pausable { using SafeMath for uint256; using UnstructuredStorage for bytes32; diff --git a/contracts/0.4.24/interfaces/ISTETH.sol b/contracts/0.4.24/interfaces/ISTETH.sol index ee6b35570..ae627d858 100644 --- a/contracts/0.4.24/interfaces/ISTETH.sol +++ b/contracts/0.4.24/interfaces/ISTETH.sol @@ -48,21 +48,6 @@ interface ISTETH /* is IERC20 */ { event Stopped(); event Resumed(); - /** - * @notice Increases shares of a given address by the specified amount. Called by Lido - * contract in two cases: 1) when a user submits an ETH1.0 deposit; 2) when - * ETH2.0 rewards are reported by the oracle. Upon user deposit, Lido contract - * mints the amount of shares that corresponds to the submitted Ether, so - * token balances of other token holders don't change. Upon rewards report, - * Lido contract mints new shares to distribute fee, effectively diluting the - * amount of Ether that would otherwise correspond to each share. - * - * @param _to Receiver of new shares - * @param _sharesAmount Amount of shares to mint - * @return The total amount of all holders' shares after new shares are minted - */ - function mintShares(address _to, uint256 _sharesAmount) external returns (uint256); - /** * @notice Burn is called by Lido contract when a user withdraws their Ether. * @param _account Account which tokens are to be burnt From 071851dd277308f5565b7b549881373ece98ee2f Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 18 Jan 2022 11:42:35 +0300 Subject: [PATCH 013/159] DepositSecurityModule, _addGuardian: add check for zero address --- contracts/0.8.9/DepositSecurityModule.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index dc6ad889f..b29489c93 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -280,6 +280,7 @@ contract DepositSecurityModule { } function _addGuardian(address addr) internal { + require(addr != address(0), "guardian zero address"); require(!_isGuardian(addr), "duplicate address"); guardians.push(addr); guardianIndicesOneBased[addr] = guardians.length; From 6dea01fd491c574a930b6ae22e94be63e5b26c74 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 18 Jan 2022 12:04:31 +0300 Subject: [PATCH 014/159] extract hardcoded `10000` into TOTAL_BASIS_POINTS internal constant --- contracts/0.4.24/Lido.sol | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 60b8d7cd1..23e9215a4 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -55,6 +55,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { uint256 constant public DEPOSIT_SIZE = 32 ether; uint256 internal constant DEPOSIT_AMOUNT_UNIT = 1000000000 wei; + uint256 internal constant TOTAL_BASIS_POINTS = 10000; /// @dev default value for maximum number of Ethereum 2.0 validators registered in a single depositBufferedEther call uint256 internal constant DEFAULT_MAX_DEPOSITS_PER_CALL = 150; @@ -186,7 +187,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { external auth(MANAGE_FEE) { require( - 10000 == uint256(_treasuryFeeBasisPoints) + TOTAL_BASIS_POINTS == uint256(_treasuryFeeBasisPoints) .add(uint256(_insuranceFeeBasisPoints)) .add(uint256(_operatorsFeeBasisPoints)), "FEES_DONT_ADD_UP" @@ -554,7 +555,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { // We need to take a defined percentage of the reported reward as a fee, and we do // this by minting new token shares and assigning them to the fee recipients (see // StETH docs for the explanation of the shares mechanics). The staking rewards fee - // is defined in basis points (1 basis point is equal to 0.01%, 10000 is 100%). + // is defined in basis points (1 basis point is equal to 0.01%, 10000 (TOTAL_BASIS_POINTS) is 100%). // // Since we've increased totalPooledEther by _totalRewards (which is already // performed by the time this function is called), the combined cost of all holders' @@ -564,14 +565,14 @@ contract Lido is ILido, IsContract, StETH, AragonApp { // Now we want to mint new shares to the fee recipient, so that the total cost of the // newly-minted shares exactly corresponds to the fee taken: // - // shares2mint * newShareCost = (_totalRewards * feeBasis) / 10000 + // shares2mint * newShareCost = (_totalRewards * feeBasis) / TOTAL_BASIS_POINTS // newShareCost = newTotalPooledEther / (prevTotalShares + shares2mint) // // which follows to: // // _totalRewards * feeBasis * prevTotalShares // shares2mint = -------------------------------------------------------------- - // (newTotalPooledEther * 10000) - (feeBasis * _totalRewards) + // (newTotalPooledEther * TOTAL_BASIS_POINTS) - (feeBasis * _totalRewards) // // The effect is that the given percentage of the reward goes to the fee recipient, and // the rest of the reward is distributed between token holders proportionally to their @@ -580,7 +581,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { uint256 shares2mint = ( _totalRewards.mul(feeBasis).mul(_getTotalShares()) .div( - _getTotalPooledEther().mul(10000) + _getTotalPooledEther().mul(TOTAL_BASIS_POINTS) .sub(feeBasis.mul(_totalRewards)) ) ); @@ -591,13 +592,13 @@ contract Lido is ILido, IsContract, StETH, AragonApp { (,uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints) = _getFeeDistribution(); - uint256 toInsuranceFund = shares2mint.mul(insuranceFeeBasisPoints).div(10000); + uint256 toInsuranceFund = shares2mint.mul(insuranceFeeBasisPoints).div(TOTAL_BASIS_POINTS); address insuranceFund = getInsuranceFund(); _transferShares(address(this), insuranceFund, toInsuranceFund); _emitTransferAfterMintingShares(insuranceFund, toInsuranceFund); uint256 distributedToOperatorsShares = _distributeNodeOperatorsReward( - shares2mint.mul(operatorsFeeBasisPoints).div(10000) + shares2mint.mul(operatorsFeeBasisPoints).div(TOTAL_BASIS_POINTS) ); // Transfer the rest of the fee to treasury @@ -652,7 +653,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * @dev Write a value nominated in basis points */ function _setBPValue(bytes32 _slot, uint16 _value) internal { - require(_value <= 10000, "VALUE_OVER_100_PERCENT"); + require(_value <= TOTAL_BASIS_POINTS, "VALUE_OVER_100_PERCENT"); _slot.setStorageUint256(uint256(_value)); } @@ -679,7 +680,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { */ function _readBPValue(bytes32 _slot) internal view returns (uint16) { uint256 v = _slot.getStorageUint256(); - assert(v <= 10000); + assert(v <= TOTAL_BASIS_POINTS); return uint16(v); } From 3b11426286aa3605cb26acca1cb6e26e0f7e8fa6 Mon Sep 17 00:00:00 2001 From: Alexey Potapkin Date: Tue, 18 Jan 2022 19:11:03 +0300 Subject: [PATCH 015/159] Use UINT64_MAX constant. Fix #382 --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index bc12ca570..a26755d82 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -551,7 +551,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp } function to64(uint256 v) internal pure returns (uint64) { - assert(v <= uint256(uint64(-1))); + assert(v <= UINT64_MAX); return uint64(v); } From a6d264aef7e2ee22441ec3ef0afb378babecfbf7 Mon Sep 17 00:00:00 2001 From: Alexey Potapkin Date: Wed, 19 Jan 2022 09:55:56 +0300 Subject: [PATCH 016/159] Remove extra defensive aserts for constants. Fix #383 --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 9 --------- 1 file changed, 9 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index a26755d82..f7b2fdd6e 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -537,8 +537,6 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp function _isEmptySigningKey(bytes memory _key) internal pure returns (bool) { assert(_key.length == PUBKEY_LENGTH); - // algorithm applicability constraint - assert(PUBKEY_LENGTH >= 32 && PUBKEY_LENGTH <= 64); uint256 k1; uint256 k2; @@ -562,9 +560,6 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp function _storeSigningKey(uint256 _operator_id, uint256 _keyIndex, bytes memory _key, bytes memory _signature) internal { assert(_key.length == PUBKEY_LENGTH); assert(_signature.length == SIGNATURE_LENGTH); - // algorithm applicability constraints - assert(PUBKEY_LENGTH >= 32 && PUBKEY_LENGTH <= 64); - assert(0 == SIGNATURE_LENGTH % 32); // key uint256 offset = _signingKeyOffset(_operator_id, _keyIndex); @@ -642,10 +637,6 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp } function _loadSigningKey(uint256 _operator_id, uint256 _keyIndex) internal view returns (bytes memory key, bytes memory signature) { - // algorithm applicability constraints - assert(PUBKEY_LENGTH >= 32 && PUBKEY_LENGTH <= 64); - assert(0 == SIGNATURE_LENGTH % 32); - uint256 offset = _signingKeyOffset(_operator_id, _keyIndex); // key From 9372b0dd1c558d33c0e7c50450a691751defe14f Mon Sep 17 00:00:00 2001 From: Alexey Potapkin Date: Wed, 19 Jan 2022 10:03:14 +0300 Subject: [PATCH 017/159] Move storage assigning under if-branch. Fix #390 --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index f7b2fdd6e..ee5eca780 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -149,9 +149,9 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(activeOperatorsCount.add(1)); else ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(activeOperatorsCount.sub(1)); - } - operators[_id].active = _active; + operators[_id].active = _active; + } emit NodeOperatorActiveSet(_id, _active); } From f03fce328d92810027486c1c73797a22b8f932b0 Mon Sep 17 00:00:00 2001 From: Alexey Potapkin Date: Wed, 19 Jan 2022 10:06:40 +0300 Subject: [PATCH 018/159] Rename a variable. Fix #387 --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index ee5eca780..1e9dbf1da 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -438,10 +438,10 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp if (effectiveStakeTotal == 0) return (recipients, shares); - uint256 perValidatorReward = _totalRewardShares.div(effectiveStakeTotal); + uint256 perStakeReward = _totalRewardShares.div(effectiveStakeTotal); for (idx = 0; idx < activeCount; ++idx) { - shares[idx] = shares[idx].mul(perValidatorReward); + shares[idx] = shares[idx].mul(perStakeReward); } return (recipients, shares); From d17b86d118206687bcf2a701ebc5a9f2ee85121e Mon Sep 17 00:00:00 2001 From: Alexey Potapkin Date: Wed, 19 Jan 2022 11:03:34 +0300 Subject: [PATCH 019/159] Add comments to internal functions. Fix #388 --- contracts/0.4.24/Lido.sol | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 23e9215a4..a036225f8 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -299,7 +299,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { uint256 balance; if (_token == ETH) { balance = _getUnaccountedEther(); - // Transfer replaced by call to prevent transfer gas amount issue + // Transfer replaced by call to prevent transfer gas amount issue require(vault.call.value(balance)(), "RECOVER_TRANSFER_FAILED"); } else { ERC20 token = ERC20(_token); @@ -425,11 +425,19 @@ contract Lido is ILido, IsContract, StETH, AragonApp { NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(_r); } + /** + * @dev Internal function to set treasury address + * @param _treasury treasury address + */ function _setTreasury(address _treasury) internal { require(_treasury != address(0), "SET_TREASURY_ZERO_ADDRESS"); TREASURY_POSITION.setStorageAddress(_treasury); } + /** + * @dev Internal function to set insurance fund address + * @param _insuranceFund insurance fund address + */ function _setInsuranceFund(address _insuranceFund) internal { require(_insuranceFund != address(0), "SET_INSURANCE_FUND_ZERO_ADDRESS"); INSURANCE_FUND_POSITION.setStorageAddress(_insuranceFund); @@ -609,6 +617,11 @@ contract Lido is ILido, IsContract, StETH, AragonApp { _emitTransferAfterMintingShares(treasury, toTreasury); } + /** + * @dev Internal function to distribute reward to node operators + * @param _sharesToDistribute amount of shares to distribute + * @return actual amount of shares that was transferred to node operators as a reward + */ function _distributeNodeOperatorsReward(uint256 _sharesToDistribute) internal returns (uint256 distributed) { (address[] memory recipients, uint256[] memory shares) = getOperators().getRewardsDistribution(_sharesToDistribute); From 517d379ebbe08d31ae8decbde5751390e957d8a0 Mon Sep 17 00:00:00 2001 From: Alexey Potapkin Date: Wed, 19 Jan 2022 11:51:44 +0300 Subject: [PATCH 020/159] Remove explicit variables initialization. Fix #384 --- contracts/0.8.9/DepositSecurityModule.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index b29489c93..25b49a492 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -94,12 +94,8 @@ contract DepositSecurityModule { _setMaxDeposits(_maxDepositsPerBlock); _setMinDepositBlockDistance(_minDepositBlockDistance); _setPauseIntentValidityPeriodBlocks(_pauseIntentValidityPeriodBlocks); - - paused = false; - lastDepositBlock = 0; } - /** * Returns the owner address. */ From 638a9c20b63ff8dddead2e882bf4909b38708596 Mon Sep 17 00:00:00 2001 From: Alexey Potapkin Date: Wed, 19 Jan 2022 16:21:58 +0300 Subject: [PATCH 021/159] Fix comments not to mention non-existent constants. Fix #385 --- contracts/0.8.9/DepositSecurityModule.sol | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 25b49a492..a8572fceb 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -143,14 +143,14 @@ contract DepositSecurityModule { /** - * Returns `PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS` (see `pauseDeposits`). + * Returns current `pauseIntentValidityPeriodBlocks` contract parameter (see `pauseDeposits`). */ function getPauseIntentValidityPeriodBlocks() external view returns (uint256) { return pauseIntentValidityPeriodBlocks; } /** - * Sets `PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS`. Only callable by the owner. + * Sets `pauseIntentValidityPeriodBlocks`. Only callable by the owner. */ function setPauseIntentValidityPeriodBlocks(uint256 newValue) external onlyOwner { _setPauseIntentValidityPeriodBlocks(newValue); @@ -164,14 +164,14 @@ contract DepositSecurityModule { /** - * Returns `MAX_DEPOSITS_PER_BLOCK` (see `depositBufferedEther`). + * Returns `maxDepositsPerBlock` (see `depositBufferedEther`). */ function getMaxDeposits() external view returns (uint256) { return maxDepositsPerBlock; } /** - * Sets `MAX_DEPOSITS_PER_BLOCK`. Only callable by the owner. + * Sets `maxDepositsPerBlock`. Only callable by the owner. */ function setMaxDeposits(uint256 newValue) external onlyOwner { _setMaxDeposits(newValue); @@ -184,14 +184,14 @@ contract DepositSecurityModule { /** - * Returns `MIN_DEPOSIT_BLOCK_DISTANCE` (see `depositBufferedEther`). + * Returns `minDepositBlockDistance` (see `depositBufferedEther`). */ function getMinDepositBlockDistance() external view returns (uint256) { return minDepositBlockDistance; } /** - * Sets `MIN_DEPOSIT_BLOCK_DISTANCE`. Only callable by the owner. + * Sets `minDepositBlockDistance`. Only callable by the owner. */ function setMinDepositBlockDistance(uint256 newValue) external onlyOwner { _setMinDepositBlockDistance(newValue); @@ -324,7 +324,7 @@ contract DepositSecurityModule { * is a valid signature by the guardian with index guardianIndex of the data * defined below. * - * 2. block.number - blockNumber <= PAUSE_INTENT_VALIDITY_PERIOD_BLOCKS + * 2. block.number - blockNumber <= pauseIntentValidityPeriodBlocks * * The signature, if present, must be produced for keccak256 hash of the following * message (each component taking 32 bytes): @@ -387,14 +387,14 @@ contract DepositSecurityModule { /** - * Calls Lido.depositBufferedEther(MAX_DEPOSITS_PER_BLOCK). + * Calls Lido.depositBufferedEther(maxDepositsPerBlock). * * Reverts if any of the following is true: * 1. IDepositContract.get_deposit_root() != depositRoot. * 2. INodeOperatorsRegistry.getKeysOpIndex() != keysOpIndex. * 3. The number of guardian signatures is less than getGuardianQuorum(). * 4. An invalid or non-guardian signature received. - * 5. block.number - getLastDepositBlock() < MIN_DEPOSIT_BLOCK_DISTANCE. + * 5. block.number - getLastDepositBlock() < minDepositBlockDistance. * 6. blockhash(blockNumber) != blockHash. * * Signatures must be sorted in ascending order by index of the guardian. Each signature must From b9bbc48702cf24475c3f1e8cb8259e7175e86783 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 19 Jan 2022 21:56:26 +0300 Subject: [PATCH 022/159] NodeOperatorsRegistry: add requires to 4 set functions --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 18 +++++++++++------- test/0.4.24/node-operators-registry.test.js | 9 +++++++-- test/scenario/lido_penalties_slashing.js | 5 ++++- .../scenario/lido_rewards_distribution_math.js | 7 +++++-- 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index bc12ca570..dca71a7e0 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -142,14 +142,15 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp authP(SET_NODE_OPERATOR_ACTIVE_ROLE, arr(_id, _active ? uint256(1) : uint256(0))) operatorExists(_id) { + require(operators[_id].active != _active, "NODE_OPERATOR_ACTIVITY_ALREADY_SET"); + _increaseKeysOpIndex(); - if (operators[_id].active != _active) { - uint256 activeOperatorsCount = getActiveNodeOperatorsCount(); - if (_active) - ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(activeOperatorsCount.add(1)); - else - ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(activeOperatorsCount.sub(1)); - } + + uint256 activeOperatorsCount = getActiveNodeOperatorsCount(); + if (_active) + ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(activeOperatorsCount.add(1)); + else + ACTIVE_OPERATORS_COUNT_POSITION.setStorageUint256(activeOperatorsCount.sub(1)); operators[_id].active = _active; @@ -163,6 +164,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp authP(SET_NODE_OPERATOR_NAME_ROLE, arr(_id)) operatorExists(_id) { + require(keccak256(operators[_id].name) != keccak256(_name), "NODE_OPERATOR_NAME_IS_THE_SAME"); operators[_id].name = _name; emit NodeOperatorNameSet(_id, _name); } @@ -175,6 +177,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp operatorExists(_id) validAddress(_rewardAddress) { + require(operators[_id].rewardAddress != _rewardAddress, "NODE_OPERATOR_ADDRESS_IS_THE_SAME"); operators[_id].rewardAddress = _rewardAddress; emit NodeOperatorRewardAddressSet(_id, _rewardAddress); } @@ -186,6 +189,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp authP(SET_NODE_OPERATOR_LIMIT_ROLE, arr(_id, uint256(_stakingLimit))) operatorExists(_id) { + require(operators[_id].stakingLimit != _stakingLimit, "NODE_OPERATOR_STAKING_LIMIT_IS_THE_SAME"); _increaseKeysOpIndex(); operators[_id].stakingLimit = _stakingLimit; emit NodeOperatorStakingLimitSet(_id, _stakingLimit); diff --git a/test/0.4.24/node-operators-registry.test.js b/test/0.4.24/node-operators-registry.test.js index bb60450ba..05ddb4028 100644 --- a/test/0.4.24/node-operators-registry.test.js +++ b/test/0.4.24/node-operators-registry.test.js @@ -138,7 +138,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assertBn(await app.getNodeOperatorsCount({ from: nobody }), 2) assertBn(await app.getActiveNodeOperatorsCount({ from: nobody }), 1) - await app.setNodeOperatorActive(0, false, { from: voting }) + await assertRevert(app.setNodeOperatorActive(0, false, { from: voting }), 'NODE_OPERATOR_ACTIVITY_ALREADY_SET') assert.equal((await app.getNodeOperator(0, false)).active, false) assertBn(await app.getActiveNodeOperatorsCount({ from: nobody }), 1) @@ -156,7 +156,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assertBn(await app.getNodeOperatorsCount({ from: nobody }), 2) assertBn(await app.getActiveNodeOperatorsCount({ from: nobody }), 1) - await app.setNodeOperatorActive(0, true, { from: voting }) + await assertRevert(app.setNodeOperatorActive(0, true, { from: voting }), 'NODE_OPERATOR_ACTIVITY_ALREADY_SET') assert.equal((await app.getNodeOperator(0, false)).active, true) assertBn(await app.getActiveNodeOperatorsCount({ from: nobody }), 1) @@ -174,6 +174,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert.equal((await app.getNodeOperator(1, true)).name, ' bar') await app.setNodeOperatorName(0, 'zzz', { from: voting }) + await assertRevert(app.setNodeOperatorName(0, 'zzz', { from: voting }), 'NODE_OPERATOR_NAME_IS_THE_SAME') + assert.equal((await app.getNodeOperator(0, true)).name, 'zzz') assert.equal((await app.getNodeOperator(1, true)).name, ' bar') @@ -191,6 +193,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert.equal((await app.getNodeOperator(1, false)).rewardAddress, ADDRESS_2) await app.setNodeOperatorRewardAddress(0, ADDRESS_4, { from: voting }) + await assertRevert(app.setNodeOperatorRewardAddress(0, ADDRESS_4, { from: voting }), 'NODE_OPERATOR_ADDRESS_IS_THE_SAME') assert.equal((await app.getNodeOperator(0, false)).rewardAddress, ADDRESS_4) assert.equal((await app.getNodeOperator(1, false)).rewardAddress, ADDRESS_2) @@ -209,6 +212,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assertBn((await app.getNodeOperator(1, false)).stakingLimit, 0) await app.setNodeOperatorStakingLimit(0, 40, { from: voting }) + await assertRevert(app.setNodeOperatorStakingLimit(0, 40, { from: voting }), 'NODE_OPERATOR_STAKING_LIMIT_IS_THE_SAME') assertBn((await app.getNodeOperator(0, false)).stakingLimit, 40) assertBn((await app.getNodeOperator(1, false)).stakingLimit, 0) @@ -310,6 +314,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob assert.sameMembers(hexSplit(keysAssignedEvt.signatures, SIGNATURE_LENGTH_BYTES), [op0.sigs[0], op1.sigs[0]], 'assignment 1: signatures') await app.setNodeOperatorActive(0, false, { from: voting }) + await assertRevert(app.setNodeOperatorActive(0, false, { from: voting }), 'NODE_OPERATOR_ACTIVITY_ALREADY_SET') result = await pool.assignNextSigningKeys(2) keysAssignedEvt = getEventAt(result, 'KeysAssigned').args diff --git a/test/scenario/lido_penalties_slashing.js b/test/scenario/lido_penalties_slashing.js index d21b2539c..79c651542 100644 --- a/test/scenario/lido_penalties_slashing.js +++ b/test/scenario/lido_penalties_slashing.js @@ -117,7 +117,10 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const validatorsLimit = 0 const txn = await nodeOperatorRegistry.addNodeOperator(nodeOperator1.name, nodeOperator1.address, { from: voting }) - await nodeOperatorRegistry.setNodeOperatorStakingLimit(0, validatorsLimit, { from: voting }) + await assertRevert( + nodeOperatorRegistry.setNodeOperatorStakingLimit(0, validatorsLimit, { from: voting }), + 'NODE_OPERATOR_STAKING_LIMIT_IS_THE_SAME' + ) // Some Truffle versions fail to decode logs here, so we're decoding them explicitly using a helper nodeOperator1.id = getEventArgument(txn, 'NodeOperatorAdded', 'id', { decodeForAbi: NodeOperatorsRegistry._json.abi }) diff --git a/test/scenario/lido_rewards_distribution_math.js b/test/scenario/lido_rewards_distribution_math.js index 232cd6c60..c8e65be97 100644 --- a/test/scenario/lido_rewards_distribution_math.js +++ b/test/scenario/lido_rewards_distribution_math.js @@ -1,6 +1,6 @@ const { assert } = require('chai') const { BN } = require('bn.js') -const { assertBn, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') +const { assertBn, assertEvent, assertRevert } = require('@aragon/contract-helpers-test/src/asserts') const { getEventArgument, ZERO_ADDRESS } = require('@aragon/contract-helpers-test') const { pad, ETH } = require('../helpers/utils') @@ -120,7 +120,10 @@ contract('Lido: rewards distribution math', (addresses) => { const validatorsLimit = 0 const txn = await nodeOperatorRegistry.addNodeOperator(nodeOperator1.name, nodeOperator1.address, { from: voting }) - await nodeOperatorRegistry.setNodeOperatorStakingLimit(0, validatorsLimit, { from: voting }) + await assertRevert( + nodeOperatorRegistry.setNodeOperatorStakingLimit(0, validatorsLimit, { from: voting }), + 'NODE_OPERATOR_STAKING_LIMIT_IS_THE_SAME' + ) // Some Truffle versions fail to decode logs here, so we're decoding them explicitly using a helper nodeOperator1.id = getEventArgument(txn, 'NodeOperatorAdded', 'id', { decodeForAbi: NodeOperatorsRegistry._json.abi }) From 0b1e52e15e33fce914c2cd0e235da5a77dde4743 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 19 Jan 2022 22:36:11 +0300 Subject: [PATCH 023/159] dep-sec-module: add 2 more usages of SafeMath: _index.add(_amount) --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index dca71a7e0..1425f683c 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -288,7 +288,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp authP(MANAGE_SIGNING_KEYS, arr(_operator_id)) { // removing from the last index to the highest one, so we won't get outside the array - for (uint256 i = _index + _amount; i > _index ; --i) { + for (uint256 i = _index.add(_amount); i > _index; --i) { _removeSigningKey(_operator_id, i - 1); } } @@ -312,7 +312,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp function removeSigningKeysOperatorBH(uint256 _operator_id, uint256 _index, uint256 _amount) external { require(msg.sender == operators[_operator_id].rewardAddress, "APP_AUTH_FAILED"); // removing from the last index to the highest one, so we won't get outside the array - for (uint256 i = _index + _amount; i > _index ; --i) { + for (uint256 i = _index.add(_amount); i > _index; --i) { _removeSigningKey(_operator_id, i - 1); } } From bf3c2611f086c891ad8b02dcc21670abb2021533 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 19 Jan 2022 22:40:55 +0300 Subject: [PATCH 024/159] dep-sec-module: add checks for zero addresses in constructor --- contracts/0.8.9/DepositSecurityModule.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index b29489c93..f64898876 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -74,6 +74,8 @@ contract DepositSecurityModule { uint256 _minDepositBlockDistance, uint256 _pauseIntentValidityPeriodBlocks ) { + require(_lido != address(0x0), "LIDO_ZERO_ADDRESS"); + require(_depositContract != address(0x0), "DEPOSIT_CONTRACT_ZERO_ADDRESS"); LIDO = _lido; DEPOSIT_CONTRACT = _depositContract; From 0ce2dc2c8e9f7aea92edb6db8a5dccabbba4fb9b Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 21 Jan 2022 10:31:34 +0300 Subject: [PATCH 025/159] wrn-13: fix omitted events --- contracts/0.4.24/Lido.sol | 35 ++++++++-------------- contracts/0.4.24/interfaces/ILido.sol | 29 ++++++++++++++++++ contracts/0.4.24/test_helpers/LidoMock.sol | 20 ++----------- test/0.4.24/lido.test.js | 21 +++++++------ 4 files changed, 53 insertions(+), 52 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index a036225f8..f2361f711 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -85,12 +85,12 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @dev As AragonApp, Lido contract must be initialized with following variables: - * @param depositContract official ETH2 Deposit contract + * @param _depositContract official ETH2 Deposit contract * @param _oracle oracle contract * @param _operators instance of Node Operators Registry */ function initialize( - IDepositContract depositContract, + IDepositContract _depositContract, address _oracle, INodeOperatorsRegistry _operators, address _treasury, @@ -98,9 +98,13 @@ contract Lido is ILido, IsContract, StETH, AragonApp { ) public onlyInit { - _setDepositContract(depositContract); + require(isContract(address(_operators)), "NOT_A_CONTRACT"); + require(isContract(address(_depositContract)), "NOT_A_CONTRACT"); + + NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(_operators); + DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_depositContract)); + _setOracle(_oracle); - _setOperators(_operators); _setTreasury(_treasury); _setInsuranceFund(_insuranceFund); @@ -208,6 +212,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { */ function setOracle(address _oracle) external auth(SET_ORACLE) { _setOracle(_oracle); + emit OracleSet(_oracle); } /** @@ -217,6 +222,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { */ function setTreasury(address _treasury) external auth(SET_TREASURY) { _setTreasury(_treasury); + emit TreasurySet(_treasury); } /** @@ -226,6 +232,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { */ function setInsuranceFund(address _insuranceFund) external auth(SET_INSURANCE_FUND) { _setInsuranceFund(_insuranceFund); + emit InsuranceFundSet(_insuranceFund); } /** @@ -398,15 +405,6 @@ contract Lido is ILido, IsContract, StETH, AragonApp { beaconBalance = BEACON_BALANCE_POSITION.getStorageUint256(); } - /** - * @dev Sets the address of Deposit contract - * @param _contract the address of Deposit contract - */ - function _setDepositContract(IDepositContract _contract) internal { - require(isContract(address(_contract)), "NOT_A_CONTRACT"); - DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_contract)); - } - /** * @dev Internal function to set authorized oracle address * @param _oracle oracle contract @@ -416,15 +414,6 @@ contract Lido is ILido, IsContract, StETH, AragonApp { ORACLE_POSITION.setStorageAddress(_oracle); } - /** - * @dev Internal function to set node operator registry address - * @param _r registry of node operators - */ - function _setOperators(INodeOperatorsRegistry _r) internal { - require(isContract(_r), "NOT_A_CONTRACT"); - NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(_r); - } - /** * @dev Internal function to set treasury address * @param _treasury treasury address @@ -557,7 +546,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @dev Distributes rewards by minting and distributing corresponding amount of liquid tokens. - * @param _totalRewards Total rewards accrued on the Ethereum 2.0 side in wei + * @param _totalRewards Total rewards accured on the Ethereum 2.0 side in wei */ function distributeRewards(uint256 _totalRewards) internal { // We need to take a defined percentage of the reported reward as a fee, and we do diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 647fa3cb1..9c2224de2 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -35,6 +35,35 @@ interface ILido { event Resumed(); + /** + * @notice Set authorized oracle contract address to `_oracle` + * @dev Contract specified here is allowed to make periodical updates of beacon states + * by calling pushBeacon. + * @param _oracle oracle contract + */ + function setOracle(address _oracle) external; + + event OracleSet(address oracle); + + /** + * @notice Set treasury contract address to `_treasury` + * @dev Contract specified here is used to accumulate the protocol treasury fee. + * @param _treasury contract which accumulates treasury fee. + */ + function setTreasury(address _treasury) external; + + event TreasurySet(address treasury); + + /** + * @notice Set insuranceFund contract address to `_insuranceFund` + * @dev Contract specified here is used to accumulate the protocol insurance fee. + * @param _insuranceFund contract which accumulates insurance fee. + */ + function setInsuranceFund(address _insuranceFund) external; + + event InsuranceFundSet(address insuranceFund); + + /** * @notice Set fee rate to `_feeBasisPoints` basis points. The fees are accrued when oracles report staking results * @param _feeBasisPoints Fee rate, in basis points diff --git a/contracts/0.4.24/test_helpers/LidoMock.sol b/contracts/0.4.24/test_helpers/LidoMock.sol index 8ae94289a..8aa191157 100644 --- a/contracts/0.4.24/test_helpers/LidoMock.sol +++ b/contracts/0.4.24/test_helpers/LidoMock.sol @@ -13,14 +13,14 @@ import "./VaultMock.sol"; */ contract LidoMock is Lido { function initialize( - IDepositContract depositContract, + IDepositContract _depositContract, address _oracle, INodeOperatorsRegistry _operators ) public { super.initialize( - depositContract, + _depositContract, _oracle, _operators, new VaultMock(), @@ -53,22 +53,6 @@ contract LidoMock is Lido { return _toLittleEndian64(_value); } - /** - * @dev Public wrapper of internal fun. Internal function sets the address of Deposit contract - * @param _contract the address of Deposit contract - */ - function setDepositContract(IDepositContract _contract) public { - _setDepositContract(_contract); - } - - /** - * @dev Public wrapper of internal fun. Internal function sets node operator registry address - * @param _r registry of node operators - */ - function setOperators(INodeOperatorsRegistry _r) public { - _setOperators(_r); - } - /** * @dev Only for testing recovery vault */ diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index ec52c1efe..b71b9fe7f 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -92,7 +92,10 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(depositor, app.address, await app.DEPOSIT_ROLE(), appManager, { from: appManager }) // Initialize the app's proxy. + await assertRevert(app.initialize(user1, oracle.address, operators.address), 'NOT_A_CONTRACT') + await assertRevert(app.initialize(depositContract.address, oracle.address, user1), 'NOT_A_CONTRACT') await app.initialize(depositContract.address, oracle.address, operators.address) + treasuryAddr = await app.getTreasury() insuranceAddr = await app.getInsuranceFund() @@ -157,15 +160,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('setOracle works', async () => { await assertRevert(app.setOracle(user1, { from: voting }), 'NOT_A_CONTRACT') - await app.setOracle(yetAnotherOracle.address, { from: voting }) - }) - - it('_setDepositContract reverts with invalid arg', async () => { - await assertRevert(app.setDepositContract(user1, { from: voting }), 'NOT_A_CONTRACT') - }) - - it('_setOperators reverts with invalid arg', async () => { - await assertRevert(app.setOperators(user1, { from: voting }), 'NOT_A_CONTRACT') + const receipt = await app.setOracle(yetAnotherOracle.address, { from: voting }) + assertEvent(receipt, 'OracleSet', { expectedArgs: { oracle: yetAnotherOracle.address } }) + assert.equal(await app.getOracle(), yetAnotherOracle.address) }) it('setWithdrawalCredentials resets unused keys', async () => { @@ -1082,7 +1079,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('voting can set treasury', async () => { - await app.setTreasury(user1, { from: voting }) + const receipt = await app.setTreasury(user1, { from: voting }) + assertEvent(receipt, 'TreasurySet', { expectedArgs: { treasury: user1 } }) assert.equal(await app.getTreasury(), user1) }) @@ -1102,7 +1100,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('voting can set insurance fund', async () => { - await app.setInsuranceFund(user1, { from: voting }) + const receipt = await app.setInsuranceFund(user1, { from: voting }) + assertEvent(receipt, 'InsuranceFundSet', { expectedArgs: { insuranceFund: user1 } }) assert.equal(await app.getInsuranceFund(), user1) }) From 7dcf3b4358818f6e64d51c1e49fd3e887f04f064 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 21 Jan 2022 10:32:51 +0300 Subject: [PATCH 026/159] wrn-5: add clarifying comment to `if (paused)` in `pauseDeposits` --- contracts/0.8.9/DepositSecurityModule.sol | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 0906bc71f..724fc4e73 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -334,6 +334,10 @@ contract DepositSecurityModule { * | PAUSE_MESSAGE_PREFIX | blockNumber */ function pauseDeposits(uint256 blockNumber, Signature memory sig) external { + // In case of an emergency function `pauseDeposits` is supposed to be called + // by all guardians. Thus only the first call will do the actual change. But + // the other calls would be OK operations from the point of view of protocol’s logic. + // Thus we prefer not to use “error” semantics which is implied by `require`. if (paused) { return; } From efe8172afe083947ef8ca6f58cfd4be2deaaf5b5 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 21 Jan 2022 16:51:17 +0300 Subject: [PATCH 027/159] improve LidoOracle comments related to versioning --- contracts/0.4.24/oracle/LidoOracle.sol | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index a12bf01e9..a414ecf66 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -74,7 +74,11 @@ contract LidoOracle is ILidoOracle, AragonApp { 0x805e82d53a51be3dfde7cfed901f1f96f5dad18e874708b082adb8841e8ca909; // keccak256("lido.LidoOracle.beaconSpec") /// Version of the initialized contract data - /// Verstion at state right after deployment when no initializer is invoked yet is 0 + /// NB: Contract versioning starts from 1. + /// This version sotred in CONTRACT_VERSION_POSITION equals to + /// - 0 right after deployment when no initializer is invoked yet + /// - N after calling initialize() during deployment from scratch, where N is the current contract version + /// - N after upgrading contract from the previous version (after calling finalize_vN()) bytes32 internal constant CONTRACT_VERSION_POSITION = 0x75be19a3f314d89bd1f84d30a6c84e2f1cd7afc7b6ca21876564c265113bb7e4; // keccak256("lido.LidoOracle.contractVersion") @@ -355,7 +359,9 @@ contract LidoOracle is ILidoOracle, AragonApp { { assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert - // Initializations for v0 --> v1 + // We consider storage state right after deployment (no initialize() called yet) as version 0 + + // Initializations for v0 --> v1 (considering version semantically) require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "BASE_VERSION_MUST_BE_ZERO"); _setBeaconSpec( @@ -371,7 +377,7 @@ contract LidoOracle is ILidoOracle, AragonApp { emit QuorumChanged(1); - // Initializations for v1 --> v2 + // Initializations for v1 --> v2 (considering version semantically) ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION .setStorageUint256(_allowedBeaconBalanceAnnualRelativeIncrease); emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_allowedBeaconBalanceAnnualRelativeIncrease); @@ -386,17 +392,17 @@ contract LidoOracle is ILidoOracle, AragonApp { EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); emit ExpectedEpochIdUpdated(expectedEpoch); - // Initializations for v2 --> v3 + // Initializations for v2 --> v3 (considering version semantically) _initialize_v3(); // Need this despite contract version check because Aragon requires it to handle auth() modificators properly initialized(); } - /** * @notice A function to finalize upgrade to v3 (from v1). Can be called only once - * @dev For more details see _initialize_v3() + * @dev Value 2 in CONTRACT_VERSION_POSITION is skipped due to change in numbering + * . For more details see LIP-??? _initialize_v3() */ function finalizeUpgrade_v3() external { require(CONTRACT_VERSION_POSITION.getStorageUint256() == 1, "WRONG_BASE_VERSION"); @@ -405,9 +411,9 @@ contract LidoOracle is ILidoOracle, AragonApp { } /** - * @notice A dummy incremental v1/v2 --> v3 initialize function. Just corrects version number - * @dev This function is introduced just for the sake of clarity and correspondence between number - * of version in initialize function name and number is CONTRACT_VERSION_POSITION. + * @notice A dummy incremental v1/v2 --> v3 initialize function. Just corrects version number in storage + * @dev This function is introduced just to set in correspondence version number in storage, + * semantic version of the contract and number N used in naming of _initialize_nN/finalizeUpgrade_vN. * NB, that thus version 2 is skipped */ function _initialize_v3() { From bcae4249a0d9cf1e92858f92cf9fe5154dff8c21 Mon Sep 17 00:00:00 2001 From: Aleksei Potapkin Date: Sat, 22 Jan 2022 23:16:19 +0300 Subject: [PATCH 028/159] Fix naming in getRewardsDistribution (#395) --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 8af7a550a..e87898775 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -424,28 +424,28 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp shares = new uint256[](activeCount); uint256 idx = 0; - uint256 effectiveStakeTotal = 0; + uint256 activeValidatorsTotal = 0; for (uint256 operatorId = 0; operatorId < nodeOperatorCount; ++operatorId) { NodeOperator storage operator = operators[operatorId]; if (!operator.active) continue; - uint256 effectiveStake = operator.usedSigningKeys.sub(operator.stoppedValidators); - effectiveStakeTotal = effectiveStakeTotal.add(effectiveStake); + uint256 activeValidators = operator.usedSigningKeys.sub(operator.stoppedValidators); + activeValidatorsTotal = activeValidatorsTotal.add(activeValidators); recipients[idx] = operator.rewardAddress; - shares[idx] = effectiveStake; + shares[idx] = activeValidators; ++idx; } - if (effectiveStakeTotal == 0) + if (activeValidatorsTotal == 0) return (recipients, shares); - uint256 perStakeReward = _totalRewardShares.div(effectiveStakeTotal); + uint256 perValidatorReward = _totalRewardShares.div(activeValidatorsTotal); for (idx = 0; idx < activeCount; ++idx) { - shares[idx] = shares[idx].mul(perStakeReward); + shares[idx] = shares[idx].mul(perValidatorReward); } return (recipients, shares); From 08aa865f783c52741d70b3b76b84deaf478c5769 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 26 Jan 2022 12:42:13 +0300 Subject: [PATCH 029/159] code review fixes - make MevTxFeeVault payable - refactor a few names - add more clarifying comments --- contracts/0.4.24/Lido.sol | 48 ++++++++++++++++----------- contracts/0.4.24/interfaces/ILido.sol | 9 +++++ contracts/0.8.9/LidoMevTxFeeVault.sol | 16 ++++++--- test/0.4.24/lido.test.js | 19 +++++++++-- 4 files changed, 65 insertions(+), 27 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index e032d5d84..ee8212b83 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -81,8 +81,10 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant BEACON_BALANCE_POSITION = keccak256("lido.Lido.beaconBalance"); /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); - /// @dev total amount of Ether MEV and transaction rewards received by Lido contract - bytes32 internal constant MEV_TX_FEE_ETHER_POSITION = keccak256("lido.Lido.mevTxFeeEther"); + + /// @dev Just a counter of total amount of MEV and transaction rewards received by Lido contract + /// Not used in the logic + bytes32 internal constant TOTAL_MEV_TX_FEE_COLLECTED_POSITION = keccak256("lido.Lido.totalMevTxFeeCollected"); /// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.Lido.withdrawalCredentials"); @@ -124,16 +126,6 @@ contract Lido is ILido, IsContract, StETH, AragonApp { _submit(0); } - /** - * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract - * @dev We need a separate function because funds received by default payable function - * will go through entire deposit algorithm - */ - function mevTxFeeReceiver() external payable { - require(msg.sender == MEV_TX_FEE_VAULT_POSITION.getStorageAddress()); - emit MevTxFeeReceived(msg.value); - } - /** * @notice Send funds to the pool with optional _referral parameter * @dev This function is alternative way to submit funds. Supports optional referral address. @@ -143,6 +135,20 @@ contract Lido is ILido, IsContract, StETH, AragonApp { return _submit(_referral); } + /** + * @notice A payable function for Mev Tx Fee rewards. Can be funded only by LidoMevTxFeeVault contract + * @dev We need a separate payable function because funds received by default payable function + * are considered as funds submitted for minting stETH + */ + function receiveMevTxFee() external payable { + require(msg.sender == MEV_TX_FEE_VAULT_POSITION.getStorageAddress()); + + TOTAL_MEV_TX_FEE_COLLECTED_POSITION.setStorageUint256( + TOTAL_MEV_TX_FEE_COLLECTED_POSITION.getStorageUint256().add(msg.value)); + + emit MevTxFeeReceived(msg.value); + } + /** * @notice Deposits buffered ethers to the official DepositContract. * @dev This function is separated from submit() to reduce the cost of sending funds. @@ -304,17 +310,19 @@ contract Lido is ILido, IsContract, StETH, AragonApp { BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); - // If LidoMevTxFeeVault's address is not set just do as if there were no mevTxFee rewards at all - // Otherwise withdraw all rewards and put them to the buffer for further staking - // and increase counter of total mevTxFee rewards collected by Lido account + // If LidoMevTxFeeVault address is not set just do as if there were no mevTxFee rewards at all + // Otherwise withdraw all rewards and put them to the buffer + // Thus, MEV tx fees are handled the same way as beacon rewards uint256 mevRewards = 0; address mevVaultAddress = getMevTxFeeVault(); - if (mevVaultAddress != address(0x0)) { + if (mevVaultAddress != address(0)) { mevRewards = ILidoMevTxFeeVault(mevVaultAddress).withdrawRewards(); BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevRewards)); - MEV_TX_FEE_ETHER_POSITION.setStorageUint256(MEV_TX_FEE_ETHER_POSITION.getStorageUint256().add(mevRewards)); } + // Don’t mint/distribute any protocol fee on the non-profitable Lido oracle report + // (when beacon chain balance delta is zero or negative). + // See ADR #3 for details: https://hackmd.io/Jyvwq8DKSAGjIlCk80YJ7w or https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); distributeRewards(rewards.add(mevRewards)); @@ -385,13 +393,13 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Get total amount of MEV and transaction fees Ether buffered on this contract's balance + * @notice Get total amount of MEV and transaction fees collected to Lido contract * @dev Ether got through LidoMevTxFeeVault is kept on this contract's balance the same way * as other buffered Ether is kept (until it gets deposited) * @return uint256 of funds received as MEV and Transaction fees in wei */ - function getMevTxFeeEther() external view returns (uint256) { - return MEV_TX_FEE_ETHER_POSITION.getStorageUint256(); + function getTotalMevTxFeeCollected() external view returns (uint256) { + return TOTAL_MEV_TX_FEE_COLLECTED_POSITION.getStorageUint256(); } /** diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 4d07f1929..544931ede 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -65,6 +65,15 @@ interface ILido { event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints); + /** + * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract + * @dev We need a separate function because funds received by default payable function + * are considered as funds submitted by a user for staking + */ + function receiveMevTxFee() external payable; + + + // The amount of ETH withdrawn from LidoMevTxFeeVault contract to Lido contract event MevTxFeeReceived(uint256 amount); diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 3e526ec96..b07468038 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -12,7 +12,7 @@ interface ILido { * @dev We need a separate function because funds received by default payable function * will go through entire deposit algorithm */ - function mevTxFeeReceiver() external payable; + function receiveMevTxFee() external payable; } @@ -29,21 +29,29 @@ contract LidoMevTxFeeVault { address public immutable LIDO; constructor(address _lidoAddress) { - require(_lidoAddress != address(0x0), "LIDO_ZERO_ADDRESS"); + require(_lidoAddress != address(0), "LIDO_ZERO_ADDRESS"); LIDO = _lidoAddress; } + /** + * @notice Allows the contract to receive ETH + * @dev MEV rewards may be sent as plain ETH transfers + */ + receive() external payable { + } + /** * @notice Withdraw all accumulated rewards to Lido contract + * @dev Can be called only by the Lido contract * @return amount uint256 of funds received as MEV and transaction fees in wei */ function withdrawRewards() external returns (uint256 amount) { - require(msg.sender == LIDO, "Nobody except Lido contract can withdraw"); + require(msg.sender == LIDO, "ONLY_LIDO_CAN_WITHDRAW"); amount = address(this).balance; if (amount > 0) { - ILido(LIDO).mevTxFeeReceiver{value: amount}(); + ILido(LIDO).receiveMevTxFee{value: amount}(); } return amount; } diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 6d6f0552f..88fe628f0 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -202,9 +202,22 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) } it('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { - await assertRevert(mevVault.withdrawRewards({ from: user1 }), 'Nobody except Lido contract can withdraw') - await assertRevert(mevVault.withdrawRewards({ from: voting }), 'Nobody except Lido contract can withdraw') - await assertRevert(mevVault.withdrawRewards({ from: appManager }), 'Nobody except Lido contract can withdraw') + await assertRevert(mevVault.withdrawRewards({ from: user1 }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(mevVault.withdrawRewards({ from: voting }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(mevVault.withdrawRewards({ from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') + }) + + it('MEV Tx Fee vault can receive Ether by plain transfers (no call data)', async () => { + const before = +(await web3.eth.getBalance(mevVault.address)).toString() + const amount = 0.02 + await web3.eth.sendTransaction({ to: mevVault.address, from: user2, value: ETH(amount) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(before + amount)) + }) + + it('MEV Tx Fee vault refuses to receive Ether by transfers with call data', async () => { + const before = +(await web3.eth.getBalance(mevVault.address)).toString() + const amount = 0.02 + await assertRevert(web3.eth.sendTransaction({ to: mevVault.address, from: user2, value: ETH(amount), data: '0x12345678' })) }) it('MEV distribution works when zero rewards reported', async () => { From 4070eb5ac2e6319598b39a15a140b61855a73633 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 12 Dec 2021 20:48:29 +0300 Subject: [PATCH 030/159] initial dirty version of MevTipsVault and Lido modifications --- contracts/0.4.24/Lido.sol | 31 +++- .../0.4.24/interfaces/ILidoMevTipsVault.sol | 14 ++ contracts/0.8.9/LidoMevTipsVault.sol | 46 ++++++ test/0.4.24/lido.test.js | 150 ++++++++++++++++++ 4 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 contracts/0.4.24/interfaces/ILidoMevTipsVault.sol create mode 100644 contracts/0.8.9/LidoMevTipsVault.sol diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 60b8d7cd1..4b27088d3 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -14,6 +14,7 @@ import "solidity-bytes-utils/contracts/BytesLib.sol"; import "./interfaces/ILido.sol"; import "./interfaces/INodeOperatorsRegistry.sol"; import "./interfaces/IDepositContract.sol"; +import "./interfaces/ILidoMevTipsVault.sol"; import "./StETH.sol"; @@ -69,6 +70,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant NODE_OPERATORS_REGISTRY_POSITION = keccak256("lido.Lido.nodeOperatorsRegistry"); bytes32 internal constant TREASURY_POSITION = keccak256("lido.Lido.treasury"); bytes32 internal constant INSURANCE_FUND_POSITION = keccak256("lido.Lido.insuranceFund"); + bytes32 internal constant MEV_VAULT_POSITION = keccak256("lido.Lido.mevVault"); /// @dev amount of Ether (on the current Ethereum side) buffered on this smart contract balance bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther"); @@ -119,6 +121,10 @@ contract Lido is ILido, IsContract, StETH, AragonApp { _submit(0); } + function mevReceiver() external payable { + // TODO: emit some event + } + /** * @notice Send funds to the pool with optional _referral parameter * @dev This function is alternative way to submit funds. Supports optional referral address. @@ -280,9 +286,16 @@ contract Lido is ILido, IsContract, StETH, AragonApp { BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); + ILidoMevTipsVault mevVault = getMevVault(); + uint256 mevFeesRewards = mevVault.withdrawAllFunds(); + + BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevFeesRewards)); + _depositBufferedEther(DEFAULT_MAX_DEPOSITS_PER_CALL); + if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); - distributeRewards(rewards); + // distributeRewards(rewards); + distributeRewards(rewards.add(mevFeesRewards)); } } @@ -415,6 +428,19 @@ contract Lido is ILido, IsContract, StETH, AragonApp { ORACLE_POSITION.setStorageAddress(_oracle); } + /** + * @dev TODO + * @param _mevVault MEV and Tx Fees Vault contract + */ + function setMevVault(address _mevVault) public { + require(isContract(_mevVault), "NOT_A_CONTRACT"); + MEV_VAULT_POSITION.setStorageAddress(_mevVault); + } + + function getMevVault() public view returns (ILidoMevTipsVault) { + return ILidoMevTipsVault(MEV_VAULT_POSITION.getStorageAddress()); + } + /** * @dev Internal function to set node operator registry address * @param _r registry of node operators @@ -688,7 +714,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { */ function _getBufferedEther() internal view returns (uint256) { uint256 buffered = BUFFERED_ETHER_POSITION.getStorageUint256(); - assert(address(this).balance >= buffered); + // assert(address(this).balance >= buffered); + // require(address(this).balance >= buffered, "address(this).balance >= buffered"); return buffered; } diff --git a/contracts/0.4.24/interfaces/ILidoMevTipsVault.sol b/contracts/0.4.24/interfaces/ILidoMevTipsVault.sol new file mode 100644 index 000000000..0bbffea3a --- /dev/null +++ b/contracts/0.4.24/interfaces/ILidoMevTipsVault.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.4.24; + + +/** + * @title TODO + */ +interface ILidoMevTipsVault { + /** + * @notice TODO + */ + function withdrawAllFunds() external returns (uint256); +} diff --git a/contracts/0.8.9/LidoMevTipsVault.sol b/contracts/0.8.9/LidoMevTipsVault.sol new file mode 100644 index 000000000..a28fa5333 --- /dev/null +++ b/contracts/0.8.9/LidoMevTipsVault.sol @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: 2020 Lido + +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.9; + +interface ILido { + function mevReceiver() external payable; +} + +contract LidoMevTipsVault { + // Just for debug as in production it would happen too often + event LidoTxFeeMevReceived(uint256 amount); + + address immutable public lidoAddress; + + constructor(address payable _lidoAddress) { + // TODO: We don't need proxy, do we? + lidoAddress = _lidoAddress; + } + + receive() external payable { + // TODO: remove + // This is for debug purposes, as in production fees + // and it seems we don't need any kind of handling of accidentally sent funds + emit LidoTxFeeMevReceived(msg.value); + } + + function withdrawAllFunds() external returns (uint256) { + require(msg.sender == lidoAddress, "Nobody except Lido contract can withdraw"); + // TODO: Reentrance guard? Seems no need as nobody except Lido can withdraw at all + + uint256 balance = address(this).balance; + if (balance > 0) { + // Note: How to send money to Lido? + // 1) Cannot send it like this because _submit() on Lido would be called and the funds get deposited as by a user + // (bool sent, ) = lidoAddress.call{value: balance }(""); + // require(sent, "sent"); + // 2) Via a separate payable function on Lido side + // 3) By using something alike "approve" for ERC-20 + + ILido(lidoAddress).mevReceiver{value: balance}(); + return balance; + } + } +} \ No newline at end of file diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index ec52c1efe..31871d585 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -5,10 +5,13 @@ const { getInstalledApp } = require('@aragon/contract-helpers-test/src/aragon-os const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') const { BN } = require('bn.js') +const { ethers } = require('ethers') +const { formatEther } = require('ethers/lib/utils') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const Lido = artifacts.require('LidoMock.sol') +const MevVault = artifacts.require('LidoMevTipsVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') @@ -40,12 +43,14 @@ const hexConcat = (first, ...rest) => { const div15 = (bn) => bn.div(new BN('1000000000000000')) const ETH = (value) => web3.utils.toWei(value + '', 'ether') +const STETH = ETH const tokens = ETH contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) => { let appBase, nodeOperatorsRegistryBase, app, oracle, depositContract, operators let treasuryAddr, insuranceAddr let dao, acl + let mevVault before('deploy base app', async () => { // Deploy the app's base contract. @@ -64,6 +69,14 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let proxyAddress = await newApp(dao, 'lido', appBase.address, appManager) app = await Lido.at(proxyAddress) + mevVault = await MevVault.new(app.address) + await app.setMevVault(mevVault.address) + + console.log(`LIDO ADDRESS: ${app.address}`) + console.log(`MEV_VAULT ADDRESS: ${mevVault.address}`) + console.log(`ORACLE ADDRESS: ${oracle.address}`) + console.log() + // NodeOperatorsRegistry proxyAddress = await newApp(dao, 'node-operators-registry', nodeOperatorsRegistryBase.address, appManager) operators = await NodeOperatorsRegistry.at(proxyAddress) @@ -124,6 +137,143 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(div15(operators_b.add(a1).add(a2).add(a3).add(a4)), operator, 'node operators token balance check') } + function formatWei(weiString) { + return ethers.utils.formatEther(ethers.utils.parseUnits(weiString, 'wei'), { commify: true }) + ' ETH' + } + + function formatBN(bn) { + return formatWei(bn.toString()) + } + + async function getEthBalance(address) { + return formatWei(await web3.eth.getBalance(address)) + } + + function formamtStEth(bn) { + return ethers.utils.formatEther(ethers.utils.parseUnits(bn.toString(), 'wei'), { commify: true }) + ' stETH' + } + + async function getStEthBalance(address) { + return formamtStEth(await app.balanceOf(address)) + } + + const setupNodeOperatorsForMevVaultTests = async (userAddress, initialDepositAmount) => { + await app.setFee(1000, { from: voting }) // 10% + + await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) + await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) + + await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) + await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) + + await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) + await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) + await operators.addSigningKeys( + 0, + 3, + hexConcat(pad('0x010204', 48), pad('0x010205', 48), pad('0x010206', 48)), + hexConcat(pad('0x01', 96), pad('0x01', 96), pad('0x01', 96)), + { from: voting } + ) + + await web3.eth.sendTransaction({ to: app.address, from: userAddress, value: initialDepositAmount }) + await app.methods['depositBufferedEther()']({ from: depositor }) + // await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) + } + + const logLidoState = async () => { + const mevVaultBalance = await getEthBalance(mevVault.address) + const lidoBalance = await getEthBalance(app.address) + const lidoTotalSupply = formatBN(await app.totalSupply()) + const lidoTotalPooledEther = formatBN(await app.getTotalPooledEther()) + const lidoBufferedEther = formatBN(await app.getBufferedEther()) + const lidoTotalShares = formatBN(await app.getTotalShares()) + const beaconStat = await app.getBeaconStat() + const depositedValidators = beaconStat.depositedValidators.toString() + const beaconValidators = beaconStat.beaconValidators.toString() + const beaconBalance = formatEther(beaconStat.beaconBalance) + + console.log({ + mevVaultBalance, + lidoBalance, + lidoTotalSupply, + lidoTotalPooledEther, + lidoBufferedEther, + lidoTotalShares, + depositedValidators, + beaconValidators, + beaconBalance + }) + } + + const logBalances = async () => { + const user2stEthBalance = await getStEthBalance(user2) + const treasuryStEthBalance = await getStEthBalance(treasuryAddr) + const insuranceStEthBalance = await getStEthBalance(insuranceAddr) + console.log({ user2stEthBalance, treasuryStEthBalance, insuranceStEthBalance }) + } + + const logAll = async () => { + await logLidoState() + await logBalances() + console.log() + } + + it.only('MEV distribution works when zero rewards reported', async () => { + const depositAmount = 32 + const mevAmount = 10 + const beaconRewards = 0 + + await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) + await oracle.reportBeacon(100, 1, ETH(depositAmount)) + await logAll() + + await web3.eth.sendTransaction({ to: mevVault.address, from: user1, value: ETH(mevAmount) }) + await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) + await logAll() + + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(mevAmount)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + mevAmount)) + }) + + it.only('MEV distribution works when negative rewards reported', async () => { + const depositAmount = 32 + const mevAmount = 12 + const beaconRewards = -2 + + await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) + await oracle.reportBeacon(100, 1, ETH(depositAmount)) + await logAll() + + await web3.eth.sendTransaction({ to: mevVault.address, from: user1, value: ETH(mevAmount) }) + await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) + await logAll() + + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(mevAmount)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + mevAmount + beaconRewards)) + }) + + it.only('MEV distribution works when positive rewards reported', async () => { + const depositAmount = 32 + const mevAmount = 7 + const beaconRewards = 3 + + await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) + await oracle.reportBeacon(100, 1, ETH(depositAmount)) + await logAll() + + await web3.eth.sendTransaction({ to: mevVault.address, from: user1, value: ETH(mevAmount) }) + await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) + await logAll() + + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(mevAmount)) + assertBn(await app.balanceOf(user2), STETH(41)) + // TODO: assertBn(await app.balanceOf(treasuryAddr), STETH(1)) + }) + it('setFee works', async () => { await app.setFee(110, { from: voting }) await assertRevert(app.setFee(110, { from: user1 }), 'APP_AUTH_FAILED') From 5d29bfed147b7c63ef03ea62581d2cb12ff5b7f4 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 13 Dec 2021 14:25:50 +0300 Subject: [PATCH 031/159] further edits on init version: don't deposit, auth etc --- contracts/0.4.24/Lido.sol | 51 +++++++++++++++--------- test/0.4.24/lido.test.js | 82 ++++++++++++++++----------------------- test/helpers/utils.js | 21 +++++++++- 3 files changed, 86 insertions(+), 68 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 4b27088d3..205076d4a 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -80,6 +80,9 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant BEACON_BALANCE_POSITION = keccak256("lido.Lido.beaconBalance"); /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); + + // TODO: It seems it's good to have one more storage variable to track amount of mev-tips, as otherwise + // we won't be able to keep track of them /// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.Lido.withdrawalCredentials"); @@ -121,8 +124,12 @@ contract Lido is ILido, IsContract, StETH, AragonApp { _submit(0); } + /** + * @notice A payable function supposed to be funded by LidoMevTipsVault contract + */ function mevReceiver() external payable { // TODO: emit some event + // TODO: do we need to forbit payments from anyone except LidoMevTipsVault? } /** @@ -246,6 +253,16 @@ contract Lido is ILido, IsContract, StETH, AragonApp { emit WithdrawalCredentialsSet(_withdrawalCredentials); } + /** + * @dev Sets given address as an address of LidoMevTipsVault contract + * @param _mevVault MEV and Tx Fees Vault contract address + */ + function setMevVault(address _mevVault) external auth(MANAGE_FEE) { + // TODO: What role is best to use? Is new role required? + require(isContract(_mevVault), "NOT_A_CONTRACT"); + MEV_VAULT_POSITION.setStorageAddress(_mevVault); + } + /** * @notice Issues withdrawal request. Not implemented. * @param _amount Amount of StETH to withdraw @@ -286,15 +303,18 @@ contract Lido is ILido, IsContract, StETH, AragonApp { BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); - ILidoMevTipsVault mevVault = getMevVault(); - uint256 mevFeesRewards = mevVault.withdrawAllFunds(); + ILidoMevTipsVault mevVault = ILidoMevTipsVault(getMevVault()); + uint256 mevFeesRewards = ILidoMevTipsVault(getMevVault()).withdrawAllFunds(); BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevFeesRewards)); - _depositBufferedEther(DEFAULT_MAX_DEPOSITS_PER_CALL); + + // TODO: We don't need to deposit automatically, do we? + // 1) bacause the ether will be deposited soon by general mechanism + // 2) because it might (potentially?) lead to another front running vulnerability exploit + // _depositBufferedEther(DEFAULT_MAX_DEPOSITS_PER_CALL); if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); - // distributeRewards(rewards); distributeRewards(rewards.add(mevFeesRewards)); } } @@ -410,6 +430,13 @@ contract Lido is ILido, IsContract, StETH, AragonApp { beaconBalance = BEACON_BALANCE_POSITION.getStorageUint256(); } + /** + * @notice Returns address of a contract set as LidoMevTipsVault + */ + function getMevVault() public view returns (address) { + return MEV_VAULT_POSITION.getStorageAddress(); + } + /** * @dev Sets the address of Deposit contract * @param _contract the address of Deposit contract @@ -428,19 +455,6 @@ contract Lido is ILido, IsContract, StETH, AragonApp { ORACLE_POSITION.setStorageAddress(_oracle); } - /** - * @dev TODO - * @param _mevVault MEV and Tx Fees Vault contract - */ - function setMevVault(address _mevVault) public { - require(isContract(_mevVault), "NOT_A_CONTRACT"); - MEV_VAULT_POSITION.setStorageAddress(_mevVault); - } - - function getMevVault() public view returns (ILidoMevTipsVault) { - return ILidoMevTipsVault(MEV_VAULT_POSITION.getStorageAddress()); - } - /** * @dev Internal function to set node operator registry address * @param _r registry of node operators @@ -714,8 +728,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { */ function _getBufferedEther() internal view returns (uint256) { uint256 buffered = BUFFERED_ETHER_POSITION.getStorageUint256(); - // assert(address(this).balance >= buffered); - // require(address(this).balance >= buffered, "address(this).balance >= buffered"); + assert(address(this).balance >= buffered); return buffered; } diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 31871d585..c64bbd845 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -7,6 +7,7 @@ const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') const { BN } = require('bn.js') const { ethers } = require('ethers') const { formatEther } = require('ethers/lib/utils') +const { getEthBalance, formatStEth: formamtStEth, formatBN } = require('../helpers/utils') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') @@ -39,7 +40,6 @@ const hexConcat = (first, ...rest) => { } // Divides a BN by 1e15 - const div15 = (bn) => bn.div(new BN('1000000000000000')) const ETH = (value) => web3.utils.toWei(value + '', 'ether') @@ -70,12 +70,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) app = await Lido.at(proxyAddress) mevVault = await MevVault.new(app.address) - await app.setMevVault(mevVault.address) - - console.log(`LIDO ADDRESS: ${app.address}`) - console.log(`MEV_VAULT ADDRESS: ${mevVault.address}`) - console.log(`ORACLE ADDRESS: ${oracle.address}`) - console.log() // NodeOperatorsRegistry proxyAddress = await newApp(dao, 'node-operators-registry', nodeOperatorsRegistryBase.address, appManager) @@ -111,6 +105,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.setPool(app.address) await depositContract.reset() + + await app.setMevVault(mevVault.address, { from: voting }) }) const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { @@ -137,50 +133,10 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(div15(operators_b.add(a1).add(a2).add(a3).add(a4)), operator, 'node operators token balance check') } - function formatWei(weiString) { - return ethers.utils.formatEther(ethers.utils.parseUnits(weiString, 'wei'), { commify: true }) + ' ETH' - } - - function formatBN(bn) { - return formatWei(bn.toString()) - } - - async function getEthBalance(address) { - return formatWei(await web3.eth.getBalance(address)) - } - - function formamtStEth(bn) { - return ethers.utils.formatEther(ethers.utils.parseUnits(bn.toString(), 'wei'), { commify: true }) + ' stETH' - } - async function getStEthBalance(address) { return formamtStEth(await app.balanceOf(address)) } - const setupNodeOperatorsForMevVaultTests = async (userAddress, initialDepositAmount) => { - await app.setFee(1000, { from: voting }) // 10% - - await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) - await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) - - await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) - await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) - await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) - await operators.addSigningKeys( - 0, - 3, - hexConcat(pad('0x010204', 48), pad('0x010205', 48), pad('0x010206', 48)), - hexConcat(pad('0x01', 96), pad('0x01', 96), pad('0x01', 96)), - { from: voting } - ) - - await web3.eth.sendTransaction({ to: app.address, from: userAddress, value: initialDepositAmount }) - await app.methods['depositBufferedEther()']({ from: depositor }) - // await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - } - const logLidoState = async () => { const mevVaultBalance = await getEthBalance(mevVault.address) const lidoBalance = await getEthBalance(app.address) @@ -219,6 +175,36 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) console.log() } + const setupNodeOperatorsForMevVaultTests = async (userAddress, initialDepositAmount) => { + await app.setFee(1000, { from: voting }) // 10% + + await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) + await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) + + await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) + await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) + + await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) + await operators.addSigningKeys(0, 1, pad('0x010203', 48), pad('0x01', 96), { from: voting }) + await operators.addSigningKeys( + 0, + 3, + hexConcat(pad('0x010204', 48), pad('0x010205', 48), pad('0x010206', 48)), + hexConcat(pad('0x01', 96), pad('0x01', 96), pad('0x01', 96)), + { from: voting } + ) + + await web3.eth.sendTransaction({ to: app.address, from: userAddress, value: initialDepositAmount }) + await app.methods['depositBufferedEther()']({ from: depositor }) + // await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) + } + + it.only('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { + await assertRevert(mevVault.withdrawAllFunds({ from: user1 }), 'Nobody except Lido contract can withdraw') + await assertRevert(mevVault.withdrawAllFunds({ from: voting }), 'Nobody except Lido contract can withdraw') + await assertRevert(mevVault.withdrawAllFunds({ from: appManager }), 'Nobody except Lido contract can withdraw') + }) + it.only('MEV distribution works when zero rewards reported', async () => { const depositAmount = 32 const mevAmount = 10 @@ -271,7 +257,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(mevAmount)) assertBn(await app.balanceOf(user2), STETH(41)) - // TODO: assertBn(await app.balanceOf(treasuryAddr), STETH(1)) + // TODO: assertBn(await app.balanceOf(treasuryAddr), STETH(1) - 1 wei) }) it('setFee works', async () => { diff --git a/test/helpers/utils.js b/test/helpers/utils.js index a0b409009..bfb93399b 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -43,6 +43,22 @@ const div15 = (bn) => bn.div(new BN(1000000)).div(new BN(1000000)).div(new BN(10 const ETH = (value) => web3.utils.toWei(value + '', 'ether') const tokens = ETH +function formatWei(weiString) { + return ethers.utils.formatEther(ethers.utils.parseUnits(weiString, 'wei'), { commify: true }) + ' ETH' +} + +function formatBN(bn) { + return formatWei(bn.toString()) +} + +async function getEthBalance(address) { + return formatWei(await web3.eth.getBalance(address)) +} + +function formatStEth(bn) { + return ethers.utils.formatEther(ethers.utils.parseUnits(bn.toString(), 'wei'), { commify: true }) + ' stETH' +} + module.exports = { pad, hexConcat, @@ -50,5 +66,8 @@ module.exports = { toBN, div15, ETH, - tokens + tokens, + getEthBalance, + formatBN, + formatStEth: formatStEth } From da95529e18474148c9c7baad84f970578b948ec3 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 30 Jan 2022 20:38:59 +0300 Subject: [PATCH 032/159] test: add reward emulator mock --- contracts/0.8.9/LidoMevTipsVault.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/LidoMevTipsVault.sol b/contracts/0.8.9/LidoMevTipsVault.sol index a28fa5333..6c644430f 100644 --- a/contracts/0.8.9/LidoMevTipsVault.sol +++ b/contracts/0.8.9/LidoMevTipsVault.sol @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2020 Lido +// SPDX-FileCopyrightText: 2021 Lido // SPDX-License-Identifier: MIT From 9c067942d97b8437b57d2c5590cb09a9a16c2556 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 13 Dec 2021 14:51:54 +0300 Subject: [PATCH 033/159] test: add scenario test on mev after the merge --- test/scenario/mev_tx_fee_after_the_merge.js | 460 ++++++++++++++++++++ 1 file changed, 460 insertions(+) create mode 100644 test/scenario/mev_tx_fee_after_the_merge.js diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js new file mode 100644 index 000000000..8fa0b8488 --- /dev/null +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -0,0 +1,460 @@ +const { assert } = require('chai') +const { BN } = require('bn.js') +const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') +const { getEventArgument } = require('@aragon/contract-helpers-test') + +const { pad, toBN, ETH, tokens, hexConcat } = require('../helpers/utils') +const { deployDaoAndPool } = require('./helpers/deploy') + +const { signDepositData } = require('../0.8.9/helpers/signatures') +const { waitBlocks } = require('../helpers/blockchain') +const addresses = require('@aragon/contract-helpers-test/src/addresses') + +const LidoMevTipsVault = artifacts.require('LidoMevTipsVault.sol') +const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') + +const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') + +contract.only('Lido: merge acceptance', (addresses) => { + const [ + // the root account which deployed the DAO + appManager, + // the address which we use to simulate the voting DAO application + voting, + // node operators + operator_1, + operator_2, + operator_3, + // users who deposit Ether to the pool + user1, + user2, + user3, + // unrelated address + nobody, + // mev source + userMev + ] = addresses + + let pool, nodeOperatorRegistry, token + let oracleMock, depositContractMock + let treasuryAddr, insuranceAddr, guardians + let depositSecurityModule, depositRoot + let rewarder, mevVault + + it('DAO, node operators registry, token, pool and deposit security module are deployed and initialized', async () => { + const deployed = await deployDaoAndPool(appManager, voting) + + // contracts/StETH.sol + token = deployed.pool + + // contracts/Lido.sol + pool = deployed.pool + + // contracts/nos/NodeOperatorsRegistry.sol + nodeOperatorRegistry = deployed.nodeOperatorRegistry + + // mocks + oracleMock = deployed.oracleMock + depositContractMock = deployed.depositContractMock + + // addresses + treasuryAddr = deployed.treasuryAddr + insuranceAddr = deployed.insuranceAddr + depositSecurityModule = deployed.depositSecurityModule + guardians = deployed.guardians + + depositRoot = await depositContractMock.get_deposit_root() + }) + + it('Mev and tx tips vault deployed and initialized', async () => { + mevVault = await LidoMevTipsVault.new(pool.address) + await pool.setMevVault(mevVault.address) + }) + + it('Rewards emulator deployed and initialized', async () => { + rewarder = await RewardEmulatorMock.new(mevVault.address) + + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'rewarder balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + }) + + // Fee and its distribution are in basis points, 10000 corresponding to 100% + + // Total fee is 1% + const totalFeePoints = 0.01 * 10000 + + // Of this 1%, 30% goes to the treasury + const treasuryFeePoints = 0.3 * 10000 + // 20% goes to the insurance fund + const insuranceFeePoints = 0.2 * 10000 + // 50% goes to node operators + const nodeOperatorsFeePoints = 0.5 * 10000 + + it('voting sets fee and its distribution', async () => { + await pool.setFee(totalFeePoints, { from: voting }) + await pool.setFeeDistribution(treasuryFeePoints, insuranceFeePoints, nodeOperatorsFeePoints, { from: voting }) + + // Fee and distribution were set + + assertBn(await pool.getFee({ from: nobody }), totalFeePoints, 'total fee') + + const distribution = await pool.getFeeDistribution({ from: nobody }) + assertBn(distribution.treasuryFeeBasisPoints, treasuryFeePoints, 'treasury fee') + assertBn(distribution.insuranceFeeBasisPoints, insuranceFeePoints, 'insurance fee') + assertBn(distribution.operatorsFeeBasisPoints, nodeOperatorsFeePoints, 'node operators fee') + }) + + const withdrawalCredentials = pad('0x0202', 32) + + it('voting sets withdrawal credentials', async () => { + await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) + + // Withdrawal credentials were set + + assert.equal(await pool.getWithdrawalCredentials({ from: nobody }), withdrawalCredentials, 'withdrawal credentials') + }) + + // Each node operator has its Ethereum 1 address, a name and a set of registered + // validators, each of them defined as a (public key, signature) pair + const nodeOperator1 = { + name: 'operator_1', + address: operator_1, + validators: [ + { + key: pad('0x010101', 48), + sig: pad('0x01', 96) + } + ] + } + + it('voting adds the first node operator', async () => { + // How many validators can this node operator register + const validatorsLimit = 1000000000 + + const txn = await nodeOperatorRegistry.addNodeOperator(nodeOperator1.name, nodeOperator1.address, { from: voting }) + await nodeOperatorRegistry.setNodeOperatorStakingLimit(0, validatorsLimit, { from: voting }) + + // Some Truffle versions fail to decode logs here, so we're decoding them explicitly using a helper + nodeOperator1.id = getEventArgument(txn, 'NodeOperatorAdded', 'id', { decodeForAbi: NodeOperatorsRegistry._json.abi }) + assertBn(nodeOperator1.id, 0, 'operator id') + + assertBn(await nodeOperatorRegistry.getNodeOperatorsCount(), 1, 'total node operators') + }) + + it('the first node operator registers one validator', async () => { + const numKeys = 1 + + await nodeOperatorRegistry.addSigningKeysOperatorBH( + nodeOperator1.id, + numKeys, + nodeOperator1.validators[0].key, + nodeOperator1.validators[0].sig, + { + from: nodeOperator1.address + } + ) + + // The key was added + + const totalKeys = await nodeOperatorRegistry.getTotalSigningKeyCount(nodeOperator1.id, { from: nobody }) + assertBn(totalKeys, 1, 'total signing keys') + + // The key was not used yet + + const unusedKeys = await nodeOperatorRegistry.getUnusedSigningKeyCount(nodeOperator1.id, { from: nobody }) + assertBn(unusedKeys, 1, 'unused signing keys') + }) + + it('the first user deposits 3 ETH to the pool', async () => { + await web3.eth.sendTransaction({ to: pool.address, from: user1, value: ETH(3) }) + const block = await web3.eth.getBlock('latest') + const keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex() + const signatures = [ + signDepositData( + await depositSecurityModule.ATTEST_MESSAGE_PREFIX(), + depositRoot, + keysOpIndex, + block.number, + block.hash, + guardians.privateKeys[guardians.addresses[0]] + ), + signDepositData( + await depositSecurityModule.ATTEST_MESSAGE_PREFIX(), + depositRoot, + keysOpIndex, + block.number, + block.hash, + guardians.privateKeys[guardians.addresses[1]] + ) + ] + await depositSecurityModule.depositBufferedEther(depositRoot, keysOpIndex, block.number, block.hash, signatures) + + // No Ether was deposited yet to the validator contract + + assertBn(await depositContractMock.totalCalls(), 0) + + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 0, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, 0, 'remote ether2') + + // All Ether was buffered within the pool contract atm + + assertBn(await pool.getBufferedEther(), ETH(3), 'buffered ether') + assertBn(await pool.getTotalPooledEther(), ETH(3), 'total pooled ether') + + // The amount of tokens corresponding to the deposited ETH value was minted to the user + + assertBn(await token.balanceOf(user1), tokens(3), 'user1 tokens') + + assertBn(await token.totalSupply(), tokens(3), 'token total supply') + }) + + it('the second user deposits 30 ETH to the pool', async () => { + await web3.eth.sendTransaction({ to: pool.address, from: user2, value: ETH(30) }) + const block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance()) + const keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex() + const signatures = [ + signDepositData( + await depositSecurityModule.ATTEST_MESSAGE_PREFIX(), + depositRoot, + keysOpIndex, + block.number, + block.hash, + guardians.privateKeys[guardians.addresses[0]] + ), + signDepositData( + await depositSecurityModule.ATTEST_MESSAGE_PREFIX(), + depositRoot, + keysOpIndex, + block.number, + block.hash, + guardians.privateKeys[guardians.addresses[1]] + ) + ] + await depositSecurityModule.depositBufferedEther(depositRoot, keysOpIndex, block.number, block.hash, signatures) + + // The first 32 ETH chunk was deposited to the deposit contract, + // using public key and signature of the only validator of the first operator + + assertBn(await depositContractMock.totalCalls(), 1) + + const regCall = await depositContractMock.calls.call(0) + assert.equal(regCall.pubkey, nodeOperator1.validators[0].key) + assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) + assert.equal(regCall.signature, nodeOperator1.validators[0].sig) + assertBn(regCall.value, ETH(32)) + + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 1, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, 0, 'remote ether2') + + // Some Ether remained buffered within the pool contract + + assertBn(await pool.getBufferedEther(), ETH(1), 'buffered ether') + assertBn(await pool.getTotalPooledEther(), ETH(1 + 32), 'total pooled ether') + + // The amount of tokens corresponding to the deposited ETH value was minted to the users + + assertBn(await token.balanceOf(user1), tokens(3), 'user1 tokens') + assertBn(await token.balanceOf(user2), tokens(30), 'user2 tokens') + + assertBn(await token.totalSupply(), tokens(3 + 30), 'token total supply') + }) + + it('at this point, the pool has ran out of signing keys', async () => { + const unusedKeys = await nodeOperatorRegistry.getUnusedSigningKeyCount(nodeOperator1.id, { from: nobody }) + assertBn(unusedKeys, 0, 'unused signing keys') + }) + + const nodeOperator2 = { + name: 'operator_2', + address: operator_2, + validators: [ + { + key: pad('0x020202', 48), + sig: pad('0x02', 96) + } + ] + } + + it('voting adds the second node operator who registers one validator', async () => { + // TODO: we have to submit operators with 0 validators allowed only + const validatorsLimit = 1000000000 + + const txn = await nodeOperatorRegistry.addNodeOperator(nodeOperator2.name, nodeOperator2.address, { from: voting }) + await nodeOperatorRegistry.setNodeOperatorStakingLimit(1, validatorsLimit, { from: voting }) + + // Some Truffle versions fail to decode logs here, so we're decoding them explicitly using a helper + nodeOperator2.id = getEventArgument(txn, 'NodeOperatorAdded', 'id', { decodeForAbi: NodeOperatorsRegistry._json.abi }) + assertBn(nodeOperator2.id, 1, 'operator id') + + assertBn(await nodeOperatorRegistry.getNodeOperatorsCount(), 2, 'total node operators') + + const numKeys = 1 + + await nodeOperatorRegistry.addSigningKeysOperatorBH( + nodeOperator2.id, + numKeys, + nodeOperator2.validators[0].key, + nodeOperator2.validators[0].sig, + { + from: nodeOperator2.address + } + ) + + // The key was added + + const totalKeys = await nodeOperatorRegistry.getTotalSigningKeyCount(nodeOperator2.id, { from: nobody }) + assertBn(totalKeys, 1, 'total signing keys') + + // The key was not used yet + + const unusedKeys = await nodeOperatorRegistry.getUnusedSigningKeyCount(nodeOperator2.id, { from: nobody }) + assertBn(unusedKeys, 1, 'unused signing keys') + }) + + it('the third user deposits 64 ETH to the pool', async () => { + await web3.eth.sendTransaction({ to: pool.address, from: user3, value: ETH(64) }) + + const block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance()) + const keysOpIndex = await nodeOperatorRegistry.getKeysOpIndex() + const signatures = [ + signDepositData( + await depositSecurityModule.ATTEST_MESSAGE_PREFIX(), + depositRoot, + keysOpIndex, + block.number, + block.hash, + guardians.privateKeys[guardians.addresses[0]] + ), + signDepositData( + await depositSecurityModule.ATTEST_MESSAGE_PREFIX(), + depositRoot, + keysOpIndex, + block.number, + block.hash, + guardians.privateKeys[guardians.addresses[1]] + ) + ] + await depositSecurityModule.depositBufferedEther(depositRoot, keysOpIndex, block.number, block.hash, signatures) + + // The first 32 ETH chunk was deposited to the deposit contract, + // using public key and signature of the only validator of the second operator + + assertBn(await depositContractMock.totalCalls(), 2) + + const regCall = await depositContractMock.calls.call(1) + assert.equal(regCall.pubkey, nodeOperator2.validators[0].key) + assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) + assert.equal(regCall.signature, nodeOperator2.validators[0].sig) + assertBn(regCall.value, ETH(32)) + + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, 0, 'remote ether2') + + // The pool ran out of validator keys, so the remaining 32 ETH were added to the + // pool buffer + + assertBn(await pool.getBufferedEther(), ETH(1 + 32), 'buffered ether') + assertBn(await pool.getTotalPooledEther(), ETH(33 + 64), 'total pooled ether') + + // The amount of tokens corresponding to the deposited ETH value was minted to the users + + assertBn(await token.balanceOf(user1), tokens(3), 'user1 tokens') + assertBn(await token.balanceOf(user2), tokens(30), 'user2 tokens') + assertBn(await token.balanceOf(user3), tokens(64), 'user3 tokens') + + assertBn(await token.totalSupply(), tokens(3 + 30 + 64), 'token total supply') + }) + + it('collect 9 ETH mev and tx rewards on a vault', async () => { + await rewarder.reward({ from: userMev, value: ETH(9) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(9), 'mev vault balance') + }) + + it('the oracle reports balance increase on Ethereum2 side and claims collected mev and tx fee rewards', async () => { + const epoch = 100 + + // Total shares are equal to deposited eth before ratio change and fee mint + + const oldTotalShares = await token.getTotalShares() + assertBn(oldTotalShares, ETH(97), 'total shares') + + // Old total pooled Ether + + const oldTotalPooledEther = await pool.getTotalPooledEther() + assertBn(oldTotalPooledEther, ETH(33 + 64), 'total pooled ether') + + // Reporting 1.5-fold balance increase (64 => 96) + + await oracleMock.reportBeacon(epoch, 2, ETH(96)) + + // Mev and tx rewards just claimed + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + + // Total shares increased because fee minted (fee shares added) + // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) + + const newTotalShares = await token.getTotalShares() + + assertBn(newTotalShares, new BN('97289047169125663202'), 'total shares') + + // Total pooled Ether increased + + const newTotalPooledEther = await pool.getTotalPooledEther() + assertBn(newTotalPooledEther, ETH(33 + 96 + 9), 'total pooled ether') + + // Ether2 stat reported by the pool changed correspondingly + + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, ETH(96), 'remote ether2') + + // Buffered Ether amount didn't change + + assertBn(await pool.getBufferedEther(), ETH(33 + 9), 'buffered ether') + + // New tokens was minted to distribute fee + assertBn(await token.totalSupply(), tokens(129 + 9), 'token total supply') + + const reward = toBN(ETH(96 - 64 + 9)) + const mintedAmount = new BN(totalFeePoints).mul(reward).divn(10000) + + // Token user balances increased + assertBn(await token.balanceOf(user1), new BN('4255360824742268041'), 'user1 tokens') + assertBn(await token.balanceOf(user2), new BN('42553608247422680412'), 'user2 tokens') + assertBn(await token.balanceOf(user3), new BN('90781030927835051546'), 'user3 tokens') + + // Fee, in the form of minted tokens, was distributed between treasury, insurance fund + // and node operators + // treasuryTokenBalance ~= mintedAmount * treasuryFeePoints / 10000 + // insuranceTokenBalance ~= mintedAmount * insuranceFeePoints / 10000 + assertBn(await token.balanceOf(treasuryAddr), new BN('123000000000000001'), 'treasury tokens') + assertBn(await token.balanceOf(insuranceAddr), new BN('81999999999999999'), 'insurance tokens') + + // The node operators' fee is distributed between all active node operators, + // proprotional to their effective stake (the amount of Ether staked by the operator's + // used and non-stopped validators). + // + // In our case, both node operators received the same fee since they have the same + // effective stake (one signing key used from each operator, staking 32 ETH) + + assertBn(await token.balanceOf(nodeOperator1.address), new BN('102499999999999999'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('102499999999999999'), 'operator_2 tokens') + + // Real minted amount should be a bit less than calculated caused by round errors on mint and transfer operations + assert( + mintedAmount + .sub( + new BN(0) + .add(await token.balanceOf(treasuryAddr)) + .add(await token.balanceOf(insuranceAddr)) + .add(await token.balanceOf(nodeOperator1.address)) + .add(await token.balanceOf(nodeOperator2.address)) + .add(await token.balanceOf(nodeOperatorRegistry.address)) + ) + .lt(mintedAmount.divn(100)) + ) + }) +}) From 2a318111a20765b61e662fe26e9ff365b2eeb41a Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 13 Dec 2021 15:06:12 +0300 Subject: [PATCH 034/159] fix: sync merge test scenario with the latest impl --- test/scenario/mev_tx_fee_after_the_merge.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index 8fa0b8488..5af16d356 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -68,7 +68,7 @@ contract.only('Lido: merge acceptance', (addresses) => { it('Mev and tx tips vault deployed and initialized', async () => { mevVault = await LidoMevTipsVault.new(pool.address) - await pool.setMevVault(mevVault.address) + await pool.setMevVault(mevVault.address, { from: voting }) }) it('Rewards emulator deployed and initialized', async () => { From 2c6aa74d671a842f2f980f4ae648079c92b65464 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 14 Dec 2021 18:50:08 +0300 Subject: [PATCH 035/159] test: Add scenario test on penalties/slashing Scenario covers various mev/tx rewards and beacon chain rewards/losses. --- test/scenario/mev_tx_fee_after_the_merge.js | 479 +++++++++++++++----- 1 file changed, 377 insertions(+), 102 deletions(-) diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index 5af16d356..10493c421 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -24,7 +24,6 @@ contract.only('Lido: merge acceptance', (addresses) => { // node operators operator_1, operator_2, - operator_3, // users who deposit Ether to the pool user1, user2, @@ -41,7 +40,44 @@ contract.only('Lido: merge acceptance', (addresses) => { let depositSecurityModule, depositRoot let rewarder, mevVault - it('DAO, node operators registry, token, pool and deposit security module are deployed and initialized', async () => { + // Total fee is 1% + const totalFeePoints = 0.01 * 10000 + // Of this 1%, 30% goes to the treasury + const treasuryFeePoints = 0.3 * 10000 + // 20% goes to the insurance fund + const insuranceFeePoints = 0.2 * 10000 + // 50% goes to node operators + const nodeOperatorsFeePoints = 0.5 * 10000 + + const withdrawalCredentials = pad('0x0202', 32) + + // Each node operator has its Ethereum 1 address, a name and a set of registered + // validators, each of them defined as a (public key, signature) pair + // NO with 1 validator + const nodeOperator1 = { + name: 'operator_1', + address: operator_1, + validators: [ + { + key: pad('0x010101', 48), + sig: pad('0x01', 96) + } + ] + } + + // NO with 1 validator + const nodeOperator2 = { + name: 'operator_2', + address: operator_2, + validators: [ + { + key: pad('0x020202', 48), + sig: pad('0x02', 96) + } + ] + } + + before('deploy base stuff', async () => { const deployed = await deployDaoAndPool(appManager, voting) // contracts/StETH.sol @@ -64,33 +100,17 @@ contract.only('Lido: merge acceptance', (addresses) => { guardians = deployed.guardians depositRoot = await depositContractMock.get_deposit_root() - }) - it('Mev and tx tips vault deployed and initialized', async () => { mevVault = await LidoMevTipsVault.new(pool.address) await pool.setMevVault(mevVault.address, { from: voting }) - }) - it('Rewards emulator deployed and initialized', async () => { rewarder = await RewardEmulatorMock.new(mevVault.address) assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'rewarder balance') assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') - }) - // Fee and its distribution are in basis points, 10000 corresponding to 100% + // Fee and its distribution are in basis points, 10000 corresponding to 100% - // Total fee is 1% - const totalFeePoints = 0.01 * 10000 - - // Of this 1%, 30% goes to the treasury - const treasuryFeePoints = 0.3 * 10000 - // 20% goes to the insurance fund - const insuranceFeePoints = 0.2 * 10000 - // 50% goes to node operators - const nodeOperatorsFeePoints = 0.5 * 10000 - - it('voting sets fee and its distribution', async () => { await pool.setFee(totalFeePoints, { from: voting }) await pool.setFeeDistribution(treasuryFeePoints, insuranceFeePoints, nodeOperatorsFeePoints, { from: voting }) @@ -102,36 +122,15 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(distribution.treasuryFeeBasisPoints, treasuryFeePoints, 'treasury fee') assertBn(distribution.insuranceFeeBasisPoints, insuranceFeePoints, 'insurance fee') assertBn(distribution.operatorsFeeBasisPoints, nodeOperatorsFeePoints, 'node operators fee') - }) - - const withdrawalCredentials = pad('0x0202', 32) - it('voting sets withdrawal credentials', async () => { await pool.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) // Withdrawal credentials were set - assert.equal(await pool.getWithdrawalCredentials({ from: nobody }), withdrawalCredentials, 'withdrawal credentials') - }) - - // Each node operator has its Ethereum 1 address, a name and a set of registered - // validators, each of them defined as a (public key, signature) pair - const nodeOperator1 = { - name: 'operator_1', - address: operator_1, - validators: [ - { - key: pad('0x010101', 48), - sig: pad('0x01', 96) - } - ] - } - it('voting adds the first node operator', async () => { // How many validators can this node operator register - const validatorsLimit = 1000000000 - - const txn = await nodeOperatorRegistry.addNodeOperator(nodeOperator1.name, nodeOperator1.address, { from: voting }) + const validatorsLimit = 100000000 + let txn = await nodeOperatorRegistry.addNodeOperator(nodeOperator1.name, nodeOperator1.address, { from: voting }) await nodeOperatorRegistry.setNodeOperatorStakingLimit(0, validatorsLimit, { from: voting }) // Some Truffle versions fail to decode logs here, so we're decoding them explicitly using a helper @@ -139,9 +138,7 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(nodeOperator1.id, 0, 'operator id') assertBn(await nodeOperatorRegistry.getNodeOperatorsCount(), 1, 'total node operators') - }) - it('the first node operator registers one validator', async () => { const numKeys = 1 await nodeOperatorRegistry.addSigningKeysOperatorBH( @@ -156,12 +153,40 @@ contract.only('Lido: merge acceptance', (addresses) => { // The key was added - const totalKeys = await nodeOperatorRegistry.getTotalSigningKeyCount(nodeOperator1.id, { from: nobody }) + let totalKeys = await nodeOperatorRegistry.getTotalSigningKeyCount(nodeOperator1.id, { from: nobody }) assertBn(totalKeys, 1, 'total signing keys') // The key was not used yet - const unusedKeys = await nodeOperatorRegistry.getUnusedSigningKeyCount(nodeOperator1.id, { from: nobody }) + let unusedKeys = await nodeOperatorRegistry.getUnusedSigningKeyCount(nodeOperator1.id, { from: nobody }) + assertBn(unusedKeys, 1, 'unused signing keys') + + txn = await nodeOperatorRegistry.addNodeOperator(nodeOperator2.name, nodeOperator2.address, { from: voting }) + await nodeOperatorRegistry.setNodeOperatorStakingLimit(1, validatorsLimit, { from: voting }) + + // Some Truffle versions fail to decode logs here, so we're decoding them explicitly using a helper + nodeOperator2.id = getEventArgument(txn, 'NodeOperatorAdded', 'id', { decodeForAbi: NodeOperatorsRegistry._json.abi }) + assertBn(nodeOperator2.id, 1, 'operator id') + + assertBn(await nodeOperatorRegistry.getNodeOperatorsCount(), 2, 'total node operators') + + await nodeOperatorRegistry.addSigningKeysOperatorBH( + nodeOperator2.id, + numKeys, + nodeOperator2.validators[0].key, + nodeOperator2.validators[0].sig, + { + from: nodeOperator2.address + } + ) + + // The key was added + + totalKeys = await nodeOperatorRegistry.getTotalSigningKeyCount(nodeOperator2.id, { from: nobody }) + assertBn(totalKeys, 1, 'total signing keys') + + // The key was not used yet + unusedKeys = await nodeOperatorRegistry.getUnusedSigningKeyCount(nodeOperator2.id, { from: nobody }) assertBn(unusedKeys, 1, 'unused signing keys') }) @@ -261,58 +286,6 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(await token.totalSupply(), tokens(3 + 30), 'token total supply') }) - it('at this point, the pool has ran out of signing keys', async () => { - const unusedKeys = await nodeOperatorRegistry.getUnusedSigningKeyCount(nodeOperator1.id, { from: nobody }) - assertBn(unusedKeys, 0, 'unused signing keys') - }) - - const nodeOperator2 = { - name: 'operator_2', - address: operator_2, - validators: [ - { - key: pad('0x020202', 48), - sig: pad('0x02', 96) - } - ] - } - - it('voting adds the second node operator who registers one validator', async () => { - // TODO: we have to submit operators with 0 validators allowed only - const validatorsLimit = 1000000000 - - const txn = await nodeOperatorRegistry.addNodeOperator(nodeOperator2.name, nodeOperator2.address, { from: voting }) - await nodeOperatorRegistry.setNodeOperatorStakingLimit(1, validatorsLimit, { from: voting }) - - // Some Truffle versions fail to decode logs here, so we're decoding them explicitly using a helper - nodeOperator2.id = getEventArgument(txn, 'NodeOperatorAdded', 'id', { decodeForAbi: NodeOperatorsRegistry._json.abi }) - assertBn(nodeOperator2.id, 1, 'operator id') - - assertBn(await nodeOperatorRegistry.getNodeOperatorsCount(), 2, 'total node operators') - - const numKeys = 1 - - await nodeOperatorRegistry.addSigningKeysOperatorBH( - nodeOperator2.id, - numKeys, - nodeOperator2.validators[0].key, - nodeOperator2.validators[0].sig, - { - from: nodeOperator2.address - } - ) - - // The key was added - - const totalKeys = await nodeOperatorRegistry.getTotalSigningKeyCount(nodeOperator2.id, { from: nobody }) - assertBn(totalKeys, 1, 'total signing keys') - - // The key was not used yet - - const unusedKeys = await nodeOperatorRegistry.getUnusedSigningKeyCount(nodeOperator2.id, { from: nobody }) - assertBn(unusedKeys, 1, 'unused signing keys') - }) - it('the third user deposits 64 ETH to the pool', async () => { await web3.eth.sendTransaction({ to: pool.address, from: user3, value: ETH(64) }) @@ -368,12 +341,12 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(await token.totalSupply(), tokens(3 + 30 + 64), 'token total supply') }) - it('collect 9 ETH mev and tx rewards on a vault', async () => { + it('collect 9 ETH mev and tx rewards to the vault', async () => { await rewarder.reward({ from: userMev, value: ETH(9) }) assertBn(await web3.eth.getBalance(mevVault.address), ETH(9), 'mev vault balance') }) - it('the oracle reports balance increase on Ethereum2 side and claims collected mev and tx fee rewards', async () => { + it('the oracle reports balance increase on Ethereum2 side (+32ETH) and claims collected mev and tx fee rewards (+9ETH)', async () => { const epoch = 100 // Total shares are equal to deposited eth before ratio change and fee mint @@ -411,8 +384,7 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') assertBn(ether2Stat.beaconBalance, ETH(96), 'remote ether2') - // Buffered Ether amount didn't change - + // Buffered Ether amount changed on MEV/tx rewards assertBn(await pool.getBufferedEther(), ETH(33 + 9), 'buffered ether') // New tokens was minted to distribute fee @@ -457,4 +429,307 @@ contract.only('Lido: merge acceptance', (addresses) => { .lt(mintedAmount.divn(100)) ) }) + + it('collect another 7ETH mev and tx rewards to the vault', async () => { + await rewarder.reward({ from: userMev, value: ETH(2) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(2), 'mev vault balance') + + await rewarder.reward({ from: userMev, value: ETH(5) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(7), 'mev vault balance') + }) + + it('the oracle reports balance is the same on Ethereum2 side (+0ETH) and claims collected mev and tx fee rewards (+7ETH)', async () => { + const epoch = 101 + + // Total shares are equal to deposited eth before ratio change and fee mint + const oldTotalShares = await token.getTotalShares() + assertBn(oldTotalShares, new BN('97289047169125663202'), 'total shares') + + // Old total pooled Ether + + const oldTotalPooledEther = await pool.getTotalPooledEther() + assertBn(oldTotalPooledEther, ETH(138), 'total pooled ether') + + // Reporting the same balance as it was before (96ETH => 96ETH) + await oracleMock.reportBeacon(epoch, 2, ETH(96)) + + // Mev and tx rewards just claimed + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + + // Total shares preserved because fee shares NOT minted + // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) + + const newTotalShares = await token.getTotalShares() + assertBn(newTotalShares, oldTotalShares, 'total shares') + + // Total pooled Ether increased + + const newTotalPooledEther = await pool.getTotalPooledEther() + assertBn(newTotalPooledEther, ETH(138 + 7), 'total pooled ether') + + // Ether2 stat reported by the pool changed correspondingly + + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, ETH(96), 'remote ether2') + + // Buffered Ether amount changed on MEV/tx rewards + assertBn(await pool.getBufferedEther(), ETH(42 + 7), 'buffered ether') + + assertBn(await token.totalSupply(), tokens(145), 'token total supply') + + const reward = toBN(0) + const mintedAmount = new BN(0) + + // All of the balances should be increased with proportion of newTotalPooledEther/oldTotalPooledEther (which is >1) + // cause shares per user and overall shares number are preserved + + assertBn(await token.balanceOf(user1), new BN('4471212460779919318'), 'user1 tokens') + assertBn(await token.balanceOf(user2), new BN('44712124607799193187'), 'user2 tokens') + assertBn(await token.balanceOf(user3), new BN('95385865829971612132'), 'user3 tokens') + + assertBn(await token.balanceOf(treasuryAddr), new BN('129239130434782610'), 'treasury tokens') + assertBn(await token.balanceOf(insuranceAddr), new BN('86159420289855071'), 'insurance tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('107699275362318839'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('107699275362318839'), 'operator_2 tokens') + }) + + it('collect another 5ETH mev and tx rewards to the vault', async () => { + await rewarder.reward({ from: userMev, value: ETH(5) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(5), 'mev vault balance') + }) + + it('the oracle reports loss on Ethereum2 side (-2ETH) and claims collected mev and tx fee rewards (+5ETH)', async () => { + const epoch = 102 + + // Total shares are equal to deposited eth before ratio change and fee mint + const oldTotalShares = await token.getTotalShares() + assertBn(oldTotalShares, new BN('97289047169125663202'), 'total shares') + + // Old total pooled Ether + + const oldTotalPooledEther = await pool.getTotalPooledEther() + assertBn(oldTotalPooledEther, ETH(145), 'total pooled ether') + + // Reporting balance decrease (96ETH => 94ETH) + await oracleMock.reportBeacon(epoch, 2, ETH(94)) + + // Mev and tx rewards just claimed + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + + // Total shares preserved because fee shares NOT minted + // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) + const newTotalShares = await token.getTotalShares() + assertBn(newTotalShares, oldTotalShares, 'total shares') + + // Total pooled Ether increased by 5ETH - 2ETH + const newTotalPooledEther = await pool.getTotalPooledEther() + assertBn(newTotalPooledEther, ETH(145 + 3), 'total pooled ether') + + // Ether2 stat reported by the pool changed correspondingly + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, ETH(94), 'remote ether2') + + // Buffered Ether amount changed on MEV/tx rewards + assertBn(await pool.getBufferedEther(), ETH(49 + 5), 'buffered ether') + + assertBn(await token.totalSupply(), tokens(145 + 3), 'token total supply') + + const reward = toBN(0) + const mintedAmount = new BN(0) + + // All of the balances should be increased with proportion of newTotalPooledEther/oldTotalPooledEther (which is >1) + // cause shares per user and overall shares number are preserved + + assertBn(await token.balanceOf(user1), new BN('4563720304796055580'), 'user1 tokens') + assertBn(await token.balanceOf(user2), new BN('45637203047960555804'), 'user2 tokens') + assertBn(await token.balanceOf(user3), new BN('97359366502315852383'), 'user3 tokens') + + assertBn(await token.balanceOf(treasuryAddr), new BN('131913043478260871'), 'treasury tokens') + assertBn(await token.balanceOf(insuranceAddr), new BN('87942028985507245'), 'insurance tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('109927536231884057'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('109927536231884057'), 'operator_2 tokens') + }) + + it('collect another 3ETH mev and tx rewards to the vault', async () => { + await rewarder.reward({ from: userMev, value: ETH(3) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(3), 'mev vault balance') + }) + + it('the oracle reports loss on Ethereum2 side (-3ETH) and claims collected mev and tx fee rewards (+3ETH)', async () => { + const epoch = 103 + + // Total shares are equal to deposited eth before ratio change and fee mint + const oldTotalShares = await token.getTotalShares() + assertBn(oldTotalShares, new BN('97289047169125663202'), 'total shares') + + // Old total pooled Ether + + const oldTotalPooledEther = await pool.getTotalPooledEther() + assertBn(oldTotalPooledEther, ETH(148), 'total pooled ether') + + // Reporting balance decrease (94ETH => 91ETH) + await oracleMock.reportBeacon(epoch, 2, ETH(91)) + + // Mev and tx rewards just claimed + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + + // Total shares preserved because fee shares NOT minted + // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) + const newTotalShares = await token.getTotalShares() + assertBn(newTotalShares, oldTotalShares, 'total shares') + + // Total pooled Ether increased by 5ETH - 2ETH + const newTotalPooledEther = await pool.getTotalPooledEther() + assertBn(newTotalPooledEther, oldTotalPooledEther, 'total pooled ether') + + // Ether2 stat reported by the pool changed correspondingly + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, ETH(91), 'remote ether2') + + // Buffered Ether amount changed on MEV/tx rewards + assertBn(await pool.getBufferedEther(), ETH(54 + 3), 'buffered ether') + + assertBn(await token.totalSupply(), tokens(148), 'token total supply') + + const reward = toBN(0) + const mintedAmount = new BN(0) + + // All of the balances should be the same as before cause overall changes sums to zero + assertBn(await token.balanceOf(user1), new BN('4563720304796055580'), 'user1 tokens') + assertBn(await token.balanceOf(user2), new BN('45637203047960555804'), 'user2 tokens') + assertBn(await token.balanceOf(user3), new BN('97359366502315852383'), 'user3 tokens') + + assertBn(await token.balanceOf(treasuryAddr), new BN('131913043478260871'), 'treasury tokens') + assertBn(await token.balanceOf(insuranceAddr), new BN('87942028985507245'), 'insurance tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('109927536231884057'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('109927536231884057'), 'operator_2 tokens') + }) + + it('collect another 2ETH mev and tx rewards to the vault', async () => { + await rewarder.reward({ from: userMev, value: ETH(2) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(2), 'mev vault balance') + }) + + it('the oracle reports loss on Ethereum2 side (-8ETH) and claims collected mev and tx fee rewards (+2ETH)', async () => { + const epoch = 104 + + // Total shares are equal to deposited eth before ratio change and fee mint + const oldTotalShares = await token.getTotalShares() + assertBn(oldTotalShares, new BN('97289047169125663202'), 'total shares') + + // Old total pooled Ether + + const oldTotalPooledEther = await pool.getTotalPooledEther() + assertBn(oldTotalPooledEther, ETH(148), 'total pooled ether') + + // Reporting balance decrease (91ETH => 83ETH) + await oracleMock.reportBeacon(epoch, 2, ETH(83)) + + // Mev and tx rewards just claimed + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + + // Total shares preserved because fee shares NOT minted + // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) + const newTotalShares = await token.getTotalShares() + assertBn(newTotalShares, oldTotalShares, 'total shares') + + // Total pooled Ether decreased by 8ETH-2ETH + const newTotalPooledEther = await pool.getTotalPooledEther() + assertBn(newTotalPooledEther, ETH(142), 'total pooled ether') + + // Ether2 stat reported by the pool changed correspondingly + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, ETH(83), 'remote ether2') + + // Buffered Ether amount changed on MEV/tx rewards + assertBn(await pool.getBufferedEther(), ETH(57 + 2), 'buffered ether') + + assertBn(await token.totalSupply(), tokens(142), 'token total supply') + + const reward = toBN(0) + const mintedAmount = new BN(0) + + // All of the balances should be decreased with proportion of newTotalPooledEther/oldTotalPooledEther (which is <1) + // cause shares per user and overall shares number are preserved + assertBn(await token.balanceOf(user1), new BN('4378704616763783056'), 'user1 tokens') + assertBn(await token.balanceOf(user2), new BN('43787046167637830569'), 'user2 tokens') + assertBn(await token.balanceOf(user3), new BN('93412365157627371881'), 'user3 tokens') + + assertBn(await token.balanceOf(treasuryAddr), new BN('126565217391304349'), 'treasury tokens') + assertBn(await token.balanceOf(insuranceAddr), new BN('84376811594202897'), 'insurance tokens') + assertBn(await token.balanceOf(nodeOperator1.address), new BN('105471014492753622'), 'operator_1 tokens') + assertBn(await token.balanceOf(nodeOperator2.address), new BN('105471014492753622'), 'operator_2 tokens') + }) + + it('collect another 3ETH mev and tx rewards to the vault', async () => { + await rewarder.reward({ from: userMev, value: ETH(3) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(3), 'mev vault balance') + }) + + it('the oracle reports balance increase on Ethereum2 side (+2ETH) and claims collected mev and tx fee rewards (+3ETH)', async () => { + const epoch = 105 + + // Total shares are equal to deposited eth before ratio change and fee mint + const oldTotalShares = await token.getTotalShares() + assertBn(oldTotalShares, new BN('97289047169125663202'), 'total shares') + + // Old total pooled Ether + + const oldTotalPooledEther = await pool.getTotalPooledEther() + assertBn(oldTotalPooledEther, ETH(142), 'total pooled ether') + + // Reporting balance increase (83ETH => 85ETH) + await oracleMock.reportBeacon(epoch, 2, ETH(85)) + + // Mev and tx rewards just claimed + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + + // Total shares increased because fee minted (fee shares added) + // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) + + const newTotalShares = await token.getTotalShares() + assertBn(newTotalShares, new BN('97322149941214511675'), 'total shares') + + // Total pooled Ether increased by 2ETH+3ETH + const newTotalPooledEther = await pool.getTotalPooledEther() + assertBn(newTotalPooledEther, ETH(142 + 5), 'total pooled ether') + + // Ether2 stat reported by the pool changed correspondingly + const ether2Stat = await pool.getBeaconStat() + assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') + assertBn(ether2Stat.beaconBalance, ETH(85), 'remote ether2') + + // Buffered Ether amount changed on MEV/tx rewards + assertBn(await pool.getBufferedEther(), ETH(59 + 3), 'buffered ether') + + assertBn(await token.totalSupply(), tokens(142 + 5), 'token total supply') + + const reward = toBN(ETH(5)) + const mintedAmount = new BN(totalFeePoints).mul(reward).divn(10000) + + // Token user balances increased + assertBn(await token.balanceOf(user1), new BN('4531342559390407888'), 'user1 tokens') + assertBn(await token.balanceOf(user2), new BN('45313425593904078888'), 'user2 tokens') + assertBn(await token.balanceOf(user3), new BN('96668641266995368295'), 'user3 tokens') + + // Fee, in the form of minted tokens, was distributed between treasury, insurance fund + // and node operators + // treasuryTokenBalance = (oldTreasuryShares + mintedRewardShares * treasuryFeePoints / 10000) * sharePrice + assertBn((await token.balanceOf(treasuryAddr)).divn(10), new BN('14597717391304348'), 'treasury tokens') + // should preserver treasuryFeePoints/insuranceFeePoints ratio + assertBn((await token.balanceOf(insuranceAddr)).divn(10), new BN('9731811594202898'), 'insurance tokens') + + // The node operators' fee is distributed between all active node operators, + // proprotional to their effective stake (the amount of Ether staked by the operator's + // used and non-stopped validators). + // + // In our case, both node operators received the same fee since they have the same + // effective stake (one signing key used from each operator, staking 32 ETH) + assertBn((await token.balanceOf(nodeOperator1.address)).divn(10), new BN('12164764492753623'), 'operator_1 tokens') + assertBn((await token.balanceOf(nodeOperator2.address)).divn(10), new BN('12164764492753623'), 'operator_2 tokens') + }) }) From 498e11a691e176585381d36ba9ffc40129d402bb Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 15 Dec 2021 07:29:46 +0300 Subject: [PATCH 036/159] fix: unify MEVÐ output format for scenario test --- test/scenario/mev_tx_fee_after_the_merge.js | 54 ++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index 10493c421..01c21c91b 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -30,7 +30,7 @@ contract.only('Lido: merge acceptance', (addresses) => { user3, // unrelated address nobody, - // mev source + // MEV source userMev ] = addresses @@ -107,7 +107,7 @@ contract.only('Lido: merge acceptance', (addresses) => { rewarder = await RewardEmulatorMock.new(mevVault.address) assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'rewarder balance') - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') // Fee and its distribution are in basis points, 10000 corresponding to 100% @@ -341,12 +341,12 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(await token.totalSupply(), tokens(3 + 30 + 64), 'token total supply') }) - it('collect 9 ETH mev and tx rewards to the vault', async () => { + it('collect 9 ETH MEV and tx rewards to the vault', async () => { await rewarder.reward({ from: userMev, value: ETH(9) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(9), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(9), 'MEV vault balance') }) - it('the oracle reports balance increase on Ethereum2 side (+32ETH) and claims collected mev and tx fee rewards (+9ETH)', async () => { + it('the oracle reports balance increase on Ethereum2 side (+32 ETH) and claims collected MEV and tx fee rewards (+9 ETH)', async () => { const epoch = 100 // Total shares are equal to deposited eth before ratio change and fee mint @@ -364,7 +364,7 @@ contract.only('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(96)) // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') // Total shares increased because fee minted (fee shares added) // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -430,15 +430,15 @@ contract.only('Lido: merge acceptance', (addresses) => { ) }) - it('collect another 7ETH mev and tx rewards to the vault', async () => { + it('collect another 7 ETH MEV and tx rewards to the vault', async () => { await rewarder.reward({ from: userMev, value: ETH(2) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(2), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(2), 'MEV vault balance') await rewarder.reward({ from: userMev, value: ETH(5) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(7), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(7), 'MEV vault balance') }) - it('the oracle reports balance is the same on Ethereum2 side (+0ETH) and claims collected mev and tx fee rewards (+7ETH)', async () => { + it('the oracle reports same balance on Ethereum2 side (+0 ETH) and claims collected MEV and tx fee rewards (+7 ETH)', async () => { const epoch = 101 // Total shares are equal to deposited eth before ratio change and fee mint @@ -454,7 +454,7 @@ contract.only('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(96)) // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -494,12 +494,12 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(nodeOperator2.address), new BN('107699275362318839'), 'operator_2 tokens') }) - it('collect another 5ETH mev and tx rewards to the vault', async () => { + it('collect another 5 ETH MEV and tx rewards to the vault', async () => { await rewarder.reward({ from: userMev, value: ETH(5) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(5), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(5), 'MEV vault balance') }) - it('the oracle reports loss on Ethereum2 side (-2ETH) and claims collected mev and tx fee rewards (+5ETH)', async () => { + it('the oracle reports loss on Ethereum2 side (-2 ETH) and claims collected MEV and tx fee rewards (+5 ETH)', async () => { const epoch = 102 // Total shares are equal to deposited eth before ratio change and fee mint @@ -515,7 +515,7 @@ contract.only('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(94)) // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -552,12 +552,12 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(nodeOperator2.address), new BN('109927536231884057'), 'operator_2 tokens') }) - it('collect another 3ETH mev and tx rewards to the vault', async () => { + it('collect another 3 ETH MEV and tx rewards to the vault', async () => { await rewarder.reward({ from: userMev, value: ETH(3) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(3), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(3), 'MEV vault balance') }) - it('the oracle reports loss on Ethereum2 side (-3ETH) and claims collected mev and tx fee rewards (+3ETH)', async () => { + it('the oracle reports loss on Ethereum2 side (-3 ETH) and claims collected MEV and tx fee rewards (+3 ETH)', async () => { const epoch = 103 // Total shares are equal to deposited eth before ratio change and fee mint @@ -573,7 +573,7 @@ contract.only('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(91)) // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -608,12 +608,12 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(nodeOperator2.address), new BN('109927536231884057'), 'operator_2 tokens') }) - it('collect another 2ETH mev and tx rewards to the vault', async () => { + it('collect another 2 ETH MEV and tx rewards to the vault', async () => { await rewarder.reward({ from: userMev, value: ETH(2) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(2), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(2), 'MEV vault balance') }) - it('the oracle reports loss on Ethereum2 side (-8ETH) and claims collected mev and tx fee rewards (+2ETH)', async () => { + it('the oracle reports loss on Ethereum2 side (-8 ETH) and claims collected MEV and tx fee rewards (+2 ETH)', async () => { const epoch = 104 // Total shares are equal to deposited eth before ratio change and fee mint @@ -629,7 +629,7 @@ contract.only('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(83)) // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -665,12 +665,12 @@ contract.only('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(nodeOperator2.address), new BN('105471014492753622'), 'operator_2 tokens') }) - it('collect another 3ETH mev and tx rewards to the vault', async () => { + it('collect another 3 ETH MEV and tx rewards to the vault', async () => { await rewarder.reward({ from: userMev, value: ETH(3) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(3), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(3), 'MEV vault balance') }) - it('the oracle reports balance increase on Ethereum2 side (+2ETH) and claims collected mev and tx fee rewards (+3ETH)', async () => { + it('the oracle reports balance increase on Ethereum2 side (+2 ETH) and claims collected MEV and tx fee rewards (+3 ETH)', async () => { const epoch = 105 // Total shares are equal to deposited eth before ratio change and fee mint @@ -686,7 +686,7 @@ contract.only('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(85)) // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'mev vault balance') + assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') // Total shares increased because fee minted (fee shares added) // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) From a090370d856c90518cdf76bb423205cf76398b29 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 17 Dec 2021 10:08:12 +0300 Subject: [PATCH 037/159] more work on MevTxFeeVault and Lido contract - refactor - add separate role for setting vault's address to Lido - use Rewards Emulator in all tests --- contracts/0.4.24/Lido.sol | 42 +++++++++-------- contracts/0.4.24/interfaces/ILido.sol | 2 + ...evTipsVault.sol => ILidoMevTxFeeVault.sol} | 4 +- contracts/0.8.9/LidoMevTipsVault.sol | 46 ------------------- contracts/0.8.9/LidoMevTxFeeVault.sol | 32 +++++++++++++ test/0.4.24/lido.test.js | 19 ++++---- test/scenario/helpers/deploy.js | 3 ++ test/scenario/mev_tx_fee_after_the_merge.js | 6 +-- 8 files changed, 77 insertions(+), 77 deletions(-) rename contracts/0.4.24/interfaces/{ILidoMevTipsVault.sol => ILidoMevTxFeeVault.sol} (58%) delete mode 100644 contracts/0.8.9/LidoMevTipsVault.sol create mode 100644 contracts/0.8.9/LidoMevTxFeeVault.sol diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 205076d4a..b532c5812 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -14,7 +14,7 @@ import "solidity-bytes-utils/contracts/BytesLib.sol"; import "./interfaces/ILido.sol"; import "./interfaces/INodeOperatorsRegistry.sol"; import "./interfaces/IDepositContract.sol"; -import "./interfaces/ILidoMevTipsVault.sol"; +import "./interfaces/ILidoMevTxFeeVault.sol"; import "./StETH.sol"; @@ -48,6 +48,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 constant public SET_TREASURY = keccak256("SET_TREASURY"); bytes32 constant public SET_INSURANCE_FUND = keccak256("SET_INSURANCE_FUND"); bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE"); + bytes32 constant public SET_MEV_TX_FEE_VAULT_ROLE = keccak256("SET_MEV_TX_FEE_VAULT_ROLE"); uint256 constant public PUBKEY_LENGTH = 48; uint256 constant public WITHDRAWAL_CREDENTIALS_LENGTH = 32; @@ -80,9 +81,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant BEACON_BALANCE_POSITION = keccak256("lido.Lido.beaconBalance"); /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); - - // TODO: It seems it's good to have one more storage variable to track amount of mev-tips, as otherwise - // we won't be able to keep track of them + /// @dev amount of Ether received by the contract as MEV and transaction Fees + bytes32 internal constant MEV_TX_FEE_ETHER_POSITION = keccak256("lido.Lido.mevTxFeeEther"); /// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.Lido.withdrawalCredentials"); @@ -125,11 +125,13 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice A payable function supposed to be funded by LidoMevTipsVault contract + * @notice A payable function supposed to be funded by LidoMevTxFeeVault contract + * @dev We need a separate function because funds received by default payable function + * go through deposit algorithm */ function mevReceiver() external payable { - // TODO: emit some event - // TODO: do we need to forbit payments from anyone except LidoMevTipsVault? + require(msg.sender == MEV_VAULT_POSITION.getStorageAddress()); + emit MevTxFeeReceived(msg.value); } /** @@ -254,11 +256,10 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @dev Sets given address as an address of LidoMevTipsVault contract + * @dev Sets given address as an address of LidoMevTxFeeVault contract * @param _mevVault MEV and Tx Fees Vault contract address */ - function setMevVault(address _mevVault) external auth(MANAGE_FEE) { - // TODO: What role is best to use? Is new role required? + function setMevVault(address _mevVault) external auth(SET_MEV_TX_FEE_VAULT_ROLE) { require(isContract(_mevVault), "NOT_A_CONTRACT"); MEV_VAULT_POSITION.setStorageAddress(_mevVault); } @@ -303,15 +304,10 @@ contract Lido is ILido, IsContract, StETH, AragonApp { BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); - ILidoMevTipsVault mevVault = ILidoMevTipsVault(getMevVault()); - uint256 mevFeesRewards = ILidoMevTipsVault(getMevVault()).withdrawAllFunds(); + uint256 mevFeesRewards = ILidoMevTxFeeVault(getMevVault()).withdrawRewards(); BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevFeesRewards)); - - // TODO: We don't need to deposit automatically, do we? - // 1) bacause the ether will be deposited soon by general mechanism - // 2) because it might (potentially?) lead to another front running vulnerability exploit - // _depositBufferedEther(DEFAULT_MAX_DEPOSITS_PER_CALL); + MEV_TX_FEE_ETHER_POSITION.setStorageUint256(MEV_TX_FEE_ETHER_POSITION.getStorageUint256().add(mevFeesRewards)); if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); @@ -382,6 +378,16 @@ contract Lido is ILido, IsContract, StETH, AragonApp { return _getBufferedEther(); } + /** + * @notice Get the total amount of MEV and transaction fees Ether buffered on this contract balance + * @dev Ether got through MevTxFeeVault is kept on this contract's balance the same way as buffered + * Ether is kept till being deposited + * @return uint256 of funds received as MEV and Transaction fees in wei + */ + function getMevTxFeeEther() external view returns (uint256) { + return MEV_TX_FEE_ETHER_POSITION.getStorageUint256(); + } + /** * @notice Gets deposit contract handle */ @@ -431,7 +437,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Returns address of a contract set as LidoMevTipsVault + * @notice Returns address of a contract set as LidoMevTxFeeVault */ function getMevVault() public view returns (address) { return MEV_VAULT_POSITION.getStorageAddress(); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 647fa3cb1..04d2b8bda 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -65,6 +65,8 @@ interface ILido { event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints); + event MevTxFeeReceived(uint256 amount); + /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` diff --git a/contracts/0.4.24/interfaces/ILidoMevTipsVault.sol b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol similarity index 58% rename from contracts/0.4.24/interfaces/ILidoMevTipsVault.sol rename to contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol index 0bbffea3a..aff49ce3c 100644 --- a/contracts/0.4.24/interfaces/ILidoMevTipsVault.sol +++ b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol @@ -6,9 +6,9 @@ pragma solidity 0.4.24; /** * @title TODO */ -interface ILidoMevTipsVault { +interface ILidoMevTxFeeVault { /** * @notice TODO */ - function withdrawAllFunds() external returns (uint256); + function withdrawRewards() external returns (uint256); } diff --git a/contracts/0.8.9/LidoMevTipsVault.sol b/contracts/0.8.9/LidoMevTipsVault.sol deleted file mode 100644 index 6c644430f..000000000 --- a/contracts/0.8.9/LidoMevTipsVault.sol +++ /dev/null @@ -1,46 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Lido - -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.9; - -interface ILido { - function mevReceiver() external payable; -} - -contract LidoMevTipsVault { - // Just for debug as in production it would happen too often - event LidoTxFeeMevReceived(uint256 amount); - - address immutable public lidoAddress; - - constructor(address payable _lidoAddress) { - // TODO: We don't need proxy, do we? - lidoAddress = _lidoAddress; - } - - receive() external payable { - // TODO: remove - // This is for debug purposes, as in production fees - // and it seems we don't need any kind of handling of accidentally sent funds - emit LidoTxFeeMevReceived(msg.value); - } - - function withdrawAllFunds() external returns (uint256) { - require(msg.sender == lidoAddress, "Nobody except Lido contract can withdraw"); - // TODO: Reentrance guard? Seems no need as nobody except Lido can withdraw at all - - uint256 balance = address(this).balance; - if (balance > 0) { - // Note: How to send money to Lido? - // 1) Cannot send it like this because _submit() on Lido would be called and the funds get deposited as by a user - // (bool sent, ) = lidoAddress.call{value: balance }(""); - // require(sent, "sent"); - // 2) Via a separate payable function on Lido side - // 3) By using something alike "approve" for ERC-20 - - ILido(lidoAddress).mevReceiver{value: balance}(); - return balance; - } - } -} \ No newline at end of file diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol new file mode 100644 index 000000000..ccf14f0e1 --- /dev/null +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -0,0 +1,32 @@ +// SPDX-FileCopyrightText: 2021 Lido + +// SPDX-License-Identifier: MIT + + +pragma solidity 0.8.9; + +interface ILido { + function mevReceiver() external payable; +} + +contract LidoMevTxFeeVault { + address public immutable lidoAddress; + + constructor(address _lidoAddress) { + lidoAddress = _lidoAddress; + } + + /** + * @notice Withdraw all accumulated rewards to Lido contract + * @return balance uint256 of funds received as MEV and Transaction fees in wei + */ + function withdrawRewards() external returns (uint256 balance) { + require(msg.sender == lidoAddress, "Nobody except Lido contract can withdraw"); + + balance = address(this).balance; + if (balance > 0) { + ILido(lidoAddress).mevReceiver{value: balance}(); + } + return balance; + } +} \ No newline at end of file diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index c64bbd845..454561dea 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -12,11 +12,12 @@ const { getEthBalance, formatStEth: formamtStEth, formatBN } = require('../helpe const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const Lido = artifacts.require('LidoMock.sol') -const MevVault = artifacts.require('LidoMevTipsVault.sol') +const MevVault = artifacts.require('LidoMevTxFeeVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') const VaultMock = artifacts.require('AragonVaultMock.sol') +const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') const ADDRESS_1 = '0x0000000000000000000000000000000000000001' const ADDRESS_2 = '0x0000000000000000000000000000000000000002' @@ -50,7 +51,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let appBase, nodeOperatorsRegistryBase, app, oracle, depositContract, operators let treasuryAddr, insuranceAddr let dao, acl - let mevVault + let mevVault, rewarder before('deploy base app', async () => { // Deploy the app's base contract. @@ -70,6 +71,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) app = await Lido.at(proxyAddress) mevVault = await MevVault.new(app.address) + rewarder = await RewardEmulatorMock.new(mevVault.address) // NodeOperatorsRegistry proxyAddress = await newApp(dao, 'node-operators-registry', nodeOperatorsRegistryBase.address, appManager) @@ -84,6 +86,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.SET_TREASURY(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_ORACLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_INSURANCE_FUND(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) @@ -200,9 +203,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) } it.only('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { - await assertRevert(mevVault.withdrawAllFunds({ from: user1 }), 'Nobody except Lido contract can withdraw') - await assertRevert(mevVault.withdrawAllFunds({ from: voting }), 'Nobody except Lido contract can withdraw') - await assertRevert(mevVault.withdrawAllFunds({ from: appManager }), 'Nobody except Lido contract can withdraw') + await assertRevert(mevVault.withdrawRewards({ from: user1 }), 'Nobody except Lido contract can withdraw') + await assertRevert(mevVault.withdrawRewards({ from: voting }), 'Nobody except Lido contract can withdraw') + await assertRevert(mevVault.withdrawRewards({ from: appManager }), 'Nobody except Lido contract can withdraw') }) it.only('MEV distribution works when zero rewards reported', async () => { @@ -214,7 +217,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.reportBeacon(100, 1, ETH(depositAmount)) await logAll() - await web3.eth.sendTransaction({ to: mevVault.address, from: user1, value: ETH(mevAmount) }) + await rewarder.reward({ from: user1, value: ETH(mevAmount) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) await logAll() @@ -232,7 +235,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.reportBeacon(100, 1, ETH(depositAmount)) await logAll() - await web3.eth.sendTransaction({ to: mevVault.address, from: user1, value: ETH(mevAmount) }) + await rewarder.reward({ from: user1, value: ETH(mevAmount) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) await logAll() @@ -250,7 +253,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.reportBeacon(100, 1, ETH(depositAmount)) await logAll() - await web3.eth.sendTransaction({ to: mevVault.address, from: user1, value: ETH(mevAmount) }) + await rewarder.reward({ from: user1, value: ETH(mevAmount) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) await logAll() diff --git a/test/scenario/helpers/deploy.js b/test/scenario/helpers/deploy.js index 0a7d02baa..9a3f93cd1 100644 --- a/test/scenario/helpers/deploy.js +++ b/test/scenario/helpers/deploy.js @@ -71,6 +71,7 @@ async function deployDaoAndPool(appManager, voting) { POOL_MANAGE_WITHDRAWAL_KEY, POOL_BURN_ROLE, DEPOSIT_ROLE, + SET_MEV_TX_FEE_VAULT_ROLE, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, NODE_OPERATOR_REGISTRY_ADD_NODE_OPERATOR_ROLE, NODE_OPERATOR_REGISTRY_SET_NODE_OPERATOR_ACTIVE_ROLE, @@ -84,6 +85,7 @@ async function deployDaoAndPool(appManager, voting) { pool.MANAGE_WITHDRAWAL_KEY(), pool.BURN_ROLE(), pool.DEPOSIT_ROLE(), + pool.SET_MEV_TX_FEE_VAULT_ROLE(), nodeOperatorRegistry.MANAGE_SIGNING_KEYS(), nodeOperatorRegistry.ADD_NODE_OPERATOR_ROLE(), nodeOperatorRegistry.SET_NODE_OPERATOR_ACTIVE_ROLE(), @@ -99,6 +101,7 @@ async function deployDaoAndPool(appManager, voting) { acl.createPermission(voting, pool.address, POOL_MANAGE_FEE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, POOL_MANAGE_WITHDRAWAL_KEY, appManager, { from: appManager }), acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, SET_MEV_TX_FEE_VAULT_ROLE, appManager, { from: appManager }), // Allow depositor to deposit buffered ether acl.createPermission(depositSecurityModule.address, pool.address, DEPOSIT_ROLE, appManager, { from: appManager }), diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index 01c21c91b..9b7e555d1 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -10,7 +10,7 @@ const { signDepositData } = require('../0.8.9/helpers/signatures') const { waitBlocks } = require('../helpers/blockchain') const addresses = require('@aragon/contract-helpers-test/src/addresses') -const LidoMevTipsVault = artifacts.require('LidoMevTipsVault.sol') +const LidoMevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') @@ -101,12 +101,12 @@ contract.only('Lido: merge acceptance', (addresses) => { depositRoot = await depositContractMock.get_deposit_root() - mevVault = await LidoMevTipsVault.new(pool.address) + mevVault = await LidoMevTxFeeVault.new(pool.address) await pool.setMevVault(mevVault.address, { from: voting }) rewarder = await RewardEmulatorMock.new(mevVault.address) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'rewarder balance') + assertBn(await web3.eth.getBalance(rewarder.address), ETH(0), 'rewarder balance') assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') // Fee and its distribution are in basis points, 10000 corresponding to 100% From 09367f2ec80ac9cf0f32a19709000646ea1c984b Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 20 Dec 2021 05:35:02 +0300 Subject: [PATCH 038/159] add mev upgrade deploy scripts --- scripts/multisig/21-deploy-mev-vault.js | 35 +++++ .../multisig/22-obtain-deployed-mev-vault.js | 41 +++++ scripts/multisig/23-vote-mev-lido-upgrade.js | 146 ++++++++++++++++++ 3 files changed, 222 insertions(+) create mode 100644 scripts/multisig/21-deploy-mev-vault.js create mode 100644 scripts/multisig/22-obtain-deployed-mev-vault.js create mode 100644 scripts/multisig/23-vote-mev-lido-upgrade.js diff --git a/scripts/multisig/21-deploy-mev-vault.js b/scripts/multisig/21-deploy-mev-vault.js new file mode 100644 index 000000000..c64c36174 --- /dev/null +++ b/scripts/multisig/21-deploy-mev-vault.js @@ -0,0 +1,35 @@ +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, logSplitter, logWideSplitter, yl, gr } = require('../helpers/log') +const { saveDeployTx } = require('../helpers/deploy') +const { readNetworkState, assertRequiredNetworkState, persistNetworkState } = require('../helpers/persisted-network-state') + +const { APP_NAMES } = require('./constants') + +const DEPLOYER = process.env.DEPLOYER || '' +const REQUIRED_NET_STATE = ['daoInitialSettings', 'depositorParams', `app:${APP_NAMES.LIDO}`, `app:${APP_NAMES.NODE_OPERATORS_REGISTRY}`] + +const SET_MEV_TX_FEE_VAULT_ROLE = web3.utils.soliditySha3("SET_MEV_TX_FEE_VAULT_ROLE"); + +async function upgradeApp({ web3, artifacts }) { + const appArtifact = 'LidoMevTxFeeVault' + const netId = await web3.eth.net.getId() + + logWideSplitter() + log(`Network ID:`, yl(netId)) + + const state = readNetworkState(network.name, netId) + assertRequiredNetworkState(state, REQUIRED_NET_STATE) + const lidoAddress = state[`app:${APP_NAMES.LIDO}`].proxyAddress + log(`Using Lido address:`, yl(lidoAddress)) + logSplitter() + + const args = [ + lidoAddress, + ] + await saveDeployTx(appArtifact, `tx-21-deploy-mev-vault.json`, { + arguments: args, + from: DEPLOYER || state.multisigAddress + }) +} + +module.exports = runOrWrapScript(upgradeApp, module) diff --git a/scripts/multisig/22-obtain-deployed-mev-vault.js b/scripts/multisig/22-obtain-deployed-mev-vault.js new file mode 100644 index 000000000..b614a7906 --- /dev/null +++ b/scripts/multisig/22-obtain-deployed-mev-vault.js @@ -0,0 +1,41 @@ +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, logSplitter, logWideSplitter, logHeader, yl } = require('../helpers/log') +const { useOrGetDeployed, assertDeployedBytecode } = require('../helpers/deploy') +const { assert } = require('../helpers/assert') +const { readNetworkState, persistNetworkState, assertRequiredNetworkState } = require('../helpers/persisted-network-state') + +const { APP_NAMES, APP_ARTIFACTS } = require('./constants') + +const REQUIRED_NET_STATE = [ + 'mevTxFeeVaultDeployTx', + `app:${APP_NAMES.LIDO}`, +] + +async function obtainInstance({ web3, artifacts }) { + // convert dash-ed appName to camel case-d + const appArtifact = 'LidoMevTxFeeVault' + const netId = await web3.eth.net.getId() + + logWideSplitter() + log(`Network ID:`, yl(netId)) + + const state = readNetworkState(network.name, netId) + assertRequiredNetworkState(state, REQUIRED_NET_STATE) + + logHeader(`${appArtifact} app base`) + const vault = await useOrGetDeployed(appArtifact, null, state.mevTxFeeVaultDeployTx) + log(`Checking...`) + const lidoAddress = state[`app:${APP_NAMES.LIDO}`].proxyAddress + await assertAddresses({ lidoAddress }, vault, appArtifact) + persistNetworkState(network.name, netId, state, { + mevTxFeeVaultAddress: vault.address + }) +} + + +async function assertAddresses({ lidoAddress }, instance, desc) { + assert.equal(await instance.lidoAddress(), lidoAddress, `${desc}: wrong lido`) + log.success(`Lido address is correct`) +} + +module.exports = runOrWrapScript(obtainInstance, module) diff --git a/scripts/multisig/23-vote-mev-lido-upgrade.js b/scripts/multisig/23-vote-mev-lido-upgrade.js new file mode 100644 index 000000000..7fad9a54c --- /dev/null +++ b/scripts/multisig/23-vote-mev-lido-upgrade.js @@ -0,0 +1,146 @@ +const { hash: namehash } = require('eth-ens-namehash') +const { encodeCallScript } = require('@aragon/contract-helpers-test/src/aragon-os') + +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, logSplitter, logWideSplitter, yl, gr } = require('../helpers/log') +const { saveCallTxData } = require('../helpers/tx-data') +const { readNetworkState, assertRequiredNetworkState, persistNetworkState } = require('../helpers/persisted-network-state') +const { resolveEnsAddress } = require('../components/ens') + +const { APP_NAMES } = require('./constants') + +const DEPLOYER = process.env.DEPLOYER || '' +const REQUIRED_NET_STATE = [ + 'lidoApmEnsName', + 'ensAddress', + 'daoAddress', + `app:${APP_NAMES.ARAGON_VOTING}`, + `app:${APP_NAMES.ARAGON_TOKEN_MANAGER}` +] + +async function upgradeAppImpl({ web3, artifacts }) { + const netId = await web3.eth.net.getId() + + logWideSplitter() + log(`Network ID:`, yl(netId)) + + const state = readNetworkState(network.name, netId) + // TODO: update assertRequiredNetworkState: remove nos, add mev, require for rewards emulator + assertRequiredNetworkState(state, REQUIRED_NET_STATE.concat(['app:lido', 'app:node-operators-registry'])) + + logSplitter() + + const ens = await artifacts.require('ENS').at(state.ensAddress) + const votingAddress = state[`app:${APP_NAMES.ARAGON_VOTING}`].proxyAddress + const tokenManagerAddress = state[`app:${APP_NAMES.ARAGON_TOKEN_MANAGER}`].proxyAddress + const voting = await artifacts.require('Voting').at(votingAddress) + const tokenManager = await artifacts.require('TokenManager').at(tokenManagerAddress) + const kernel = await artifacts.require('Kernel').at(state.daoAddress) + const aclAddress = await kernel.acl() + const acl = await artifacts.require('ACL').at(aclAddress) + const lidoAddress = state[`app:lido`].proxyAddress + const lido = await artifacts.require('Lido').at(lidoAddress) + const mevTxFeeVaultAddress = state.mevTxFeeVaultAddress + + log(`Using ENS:`, yl(state.ensAddress)) + log(`TokenManager address:`, yl(tokenManagerAddress)) + log(`Voting address:`, yl(votingAddress)) + log(`Kernel`, yl(kernel.address)) + log(`ACL`, yl(acl.address)) + + log.splitter() + + const lidoUpgradeCallData = await buildUpgradeTransaction('lido', state, ens, kernel) + + const grantRoleCallData = { + to: aclAddress, + calldata: await acl.contract.methods + .createPermission( + votingAddress, + state[`app:lido`].proxyAddress, + web3.utils.soliditySha3('SET_MEV_TX_FEE_VAULT_ROLE'), + votingAddress + ) + .encodeABI() + } + + const setMevTxFeeVaultToLidoCallData = { + to: lidoAddress, + calldata: await lido.contract.methods.setMevVault(mevTxFeeVaultAddress).encodeABI() + } + + const encodedUpgradeCallData = encodeCallScript([ + ...lidoUpgradeCallData, + grantRoleCallData, + setMevTxFeeVaultToLidoCallData, + ]) + + log(`encodedUpgradeCallData:`, yl(encodedUpgradeCallData)) + const votingCallData = encodeCallScript([ + { + to: votingAddress, + calldata: await voting.contract.methods.forward(encodedUpgradeCallData).encodeABI() + } + ]) + + const txName = `tx-23-deploy-mev-lido-upgrade.json` + const votingDesc = `1) Publishing new implementation in lido app APM repo +2) Updating implementaion of lido app with new one +3) Grant role SENT_MEV_TX_FEE_VAULT_ROLE to voting +4) Set deployed MevTxFeeVault to Lido contract + ` + + await saveCallTxData(votingDesc, tokenManager, 'forward', txName, { + arguments: [votingCallData], + from: DEPLOYER || state.multisigAddress + }) + + log.splitter() + log(gr(`Before continuing the deployment, please send all transactions listed above.`)) + log(gr(`You must complete it positively and execute before continuing with the deployment!`)) + log.splitter() +} + +async function buildUpgradeTransaction(appName, state, ens, kernel) { + const appId = namehash(`${appName}.${state.lidoApmEnsName}`) + const repoAddress = await resolveEnsAddress(artifacts, ens, appId) + const newContractAddress = state[`app:${appName}`].baseAddress + const newContentURI = state[`app:${appName}`].contentURI + const repo = await artifacts.require('Repo').at(repoAddress) + const APP_BASES_NAMESPACE = await kernel.APP_BASES_NAMESPACE() + + const { semanticVersion: currentVersion, contractAddress: currentContractAddress, contentURI: currentContentURI } = await repo.getLatest() + + const versionFrom = currentVersion.map((n) => n.toNumber()) + currentVersion[0] = currentVersion[0].addn(1) + const versionTo = currentVersion.map((n) => n.toNumber()) + + log.splitter() + + log(`Upgrading app:`, yl(appName)) + log(`App ID:`, yl(appId)) + log(`Contract implementation:`, yl(currentContractAddress), `->`, yl(newContractAddress)) + log(`Content URI:`, yl(currentContentURI), `->`, yl(newContentURI)) + log(`Bump version:`, yl(versionFrom.join('.')), `->`, yl(versionTo.join('.'))) + log(`Repo:`, yl(repoAddress)) + log(`APP_BASES_NAMESPACE`, yl(APP_BASES_NAMESPACE)) + + log.splitter() + const upgradeCallData = [ + { + // repo.newVersion(versionTo, contractAddress, contentURI) + to: repoAddress, + calldata: await repo.contract.methods.newVersion(versionTo, newContractAddress, newContentURI).encodeABI() + }, + + { + // kernel.setApp(APP_BASES_NAMESPACE, appId, oracle) + to: state.daoAddress, + calldata: await kernel.contract.methods.setApp(APP_BASES_NAMESPACE, appId, newContractAddress).encodeABI() + } + ] + + return upgradeCallData +} + +module.exports = runOrWrapScript(upgradeAppImpl, module) From 6c13602ef3c95d34baaeeb154e6b744560afdc56 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 21 Dec 2021 14:21:36 +0300 Subject: [PATCH 039/159] rename Lido::pushBeacon --> pushRewards --- contracts/0.4.24/Lido.sol | 4 ++-- contracts/0.4.24/interfaces/ILido.sol | 2 +- contracts/0.4.24/oracle/LidoOracle.sol | 2 +- .../0.4.24/test_helpers/LidoMockForOracle.sol | 2 +- contracts/0.4.24/test_helpers/LidoPushableMock.sol | 2 +- contracts/0.4.24/test_helpers/OracleMock.sol | 2 +- docs/protocol-levers.md | 2 +- scripts/multisig/21-deploy-mev-vault.js | 1 - test/0.4.24/lido.test.js | 14 +++++++------- ...oPushBeacon.test.js => lidoPushRewards.test.js} | 2 +- test/scenario/mev_tx_fee_after_the_merge.js | 2 +- 11 files changed, 17 insertions(+), 18 deletions(-) rename test/0.4.24/{lidoPushBeacon.test.js => lidoPushRewards.test.js} (99%) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index b532c5812..09b938391 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -217,7 +217,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @notice Set authorized oracle contract address to `_oracle` * @dev Contract specified here is allowed to make periodical updates of beacon states - * by calling pushBeacon. + * by calling pushRewards. * @param _oracle oracle contract */ function setOracle(address _oracle) external auth(SET_ORACLE) { @@ -281,7 +281,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * @param _beaconValidators number of Lido's keys in the beacon state * @param _beaconBalance simmarized balance of Lido-controlled keys in wei */ - function pushBeacon(uint256 _beaconValidators, uint256 _beaconBalance) external whenNotStopped { + function pushRewards(uint256 _beaconValidators, uint256 _beaconBalance) external whenNotStopped { require(msg.sender == getOracle(), "APP_AUTH_FAILED"); uint256 depositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256(); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 04d2b8bda..b7f0591d9 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -90,7 +90,7 @@ interface ILido { * @param _epoch Epoch id * @param _eth2balance Balance in wei on the ETH 2.0 side */ - function pushBeacon(uint256 _epoch, uint256 _eth2balance) external; + function pushRewards(uint256 _epoch, uint256 _eth2balance) external; // User functions diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index d0159f525..99982900c 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -567,7 +567,7 @@ contract LidoOracle is ILidoOracle, AragonApp { // report to the Lido and collect stats ILido lido = getLido(); uint256 prevTotalPooledEther = lido.totalSupply(); - lido.pushBeacon(_beaconValidators, _beaconBalanceEth1); + lido.pushRewards(_beaconValidators, _beaconBalanceEth1); uint256 postTotalPooledEther = lido.totalSupply(); PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.setStorageUint256(prevTotalPooledEther); diff --git a/contracts/0.4.24/test_helpers/LidoMockForOracle.sol b/contracts/0.4.24/test_helpers/LidoMockForOracle.sol index 695d22f81..15792b08f 100644 --- a/contracts/0.4.24/test_helpers/LidoMockForOracle.sol +++ b/contracts/0.4.24/test_helpers/LidoMockForOracle.sol @@ -15,7 +15,7 @@ contract LidoMockForOracle { return totalPooledEther; } - function pushBeacon(uint256 /*_beaconValidators*/, uint256 _beaconBalance) external { + function pushRewards(uint256 /*_beaconValidators*/, uint256 _beaconBalance) external { totalPooledEther = _beaconBalance; } diff --git a/contracts/0.4.24/test_helpers/LidoPushableMock.sol b/contracts/0.4.24/test_helpers/LidoPushableMock.sol index b5e29823b..2918fa1b6 100644 --- a/contracts/0.4.24/test_helpers/LidoPushableMock.sol +++ b/contracts/0.4.24/test_helpers/LidoPushableMock.sol @@ -9,7 +9,7 @@ import "./VaultMock.sol"; /** - * @dev Mock for unit-testing pushBeacon and how reward get calculated + * @dev Mock for unit-testing pushRewards and how reward get calculated */ contract LidoPushableMock is Lido { diff --git a/contracts/0.4.24/test_helpers/OracleMock.sol b/contracts/0.4.24/test_helpers/OracleMock.sol index bcbcab128..f59811679 100644 --- a/contracts/0.4.24/test_helpers/OracleMock.sol +++ b/contracts/0.4.24/test_helpers/OracleMock.sol @@ -19,7 +19,7 @@ contract OracleMock { } function reportBeacon(uint256 _epochId, uint128 _beaconValidators, uint128 _beaconBalance) external { - pool.pushBeacon(_beaconValidators, _beaconBalance); + pool.pushRewards(_beaconValidators, _beaconBalance); } function setBeaconReportReceiver(address _receiver) { diff --git a/docs/protocol-levers.md b/docs/protocol-levers.md index f8f5e152a..c8d1763b6 100644 --- a/docs/protocol-levers.md +++ b/docs/protocol-levers.md @@ -112,7 +112,7 @@ allowances) are allowed. The following transactions revert: * calls to `submit(address)`; * calls to `depositBufferedEther(uint256)`; * calls to `withdraw(uint256, bytes32)` (withdrawals are not implemented yet). -* calls to `pushBeacon(uint256, uint256)`; +* calls to `pushRewards(uint256, uint256)`; * calls to `burnShares(address, uint256)` * calls to `transfer(address, uint256)` * calls to `transferFrom(address, address, uint256)` diff --git a/scripts/multisig/21-deploy-mev-vault.js b/scripts/multisig/21-deploy-mev-vault.js index c64c36174..bffd71ac5 100644 --- a/scripts/multisig/21-deploy-mev-vault.js +++ b/scripts/multisig/21-deploy-mev-vault.js @@ -8,7 +8,6 @@ const { APP_NAMES } = require('./constants') const DEPLOYER = process.env.DEPLOYER || '' const REQUIRED_NET_STATE = ['daoInitialSettings', 'depositorParams', `app:${APP_NAMES.LIDO}`, `app:${APP_NAMES.NODE_OPERATORS_REGISTRY}`] -const SET_MEV_TX_FEE_VAULT_ROLE = web3.utils.soliditySha3("SET_MEV_TX_FEE_VAULT_ROLE"); async function upgradeApp({ web3, artifacts }) { const appArtifact = 'LidoMevTxFeeVault' diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 454561dea..e8b21ac3a 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -202,13 +202,13 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) // await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) } - it.only('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { + it('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { await assertRevert(mevVault.withdrawRewards({ from: user1 }), 'Nobody except Lido contract can withdraw') await assertRevert(mevVault.withdrawRewards({ from: voting }), 'Nobody except Lido contract can withdraw') await assertRevert(mevVault.withdrawRewards({ from: appManager }), 'Nobody except Lido contract can withdraw') }) - it.only('MEV distribution works when zero rewards reported', async () => { + it('MEV distribution works when zero rewards reported', async () => { const depositAmount = 32 const mevAmount = 10 const beaconRewards = 0 @@ -226,7 +226,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.balanceOf(user2), STETH(depositAmount + mevAmount)) }) - it.only('MEV distribution works when negative rewards reported', async () => { + it('MEV distribution works when negative rewards reported', async () => { const depositAmount = 32 const mevAmount = 12 const beaconRewards = -2 @@ -244,7 +244,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.balanceOf(user2), STETH(depositAmount + mevAmount + beaconRewards)) }) - it.only('MEV distribution works when positive rewards reported', async () => { + it('MEV distribution works when positive rewards reported', async () => { const depositAmount = 32 const mevAmount = 7 const beaconRewards = 3 @@ -648,7 +648,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.withdraw(tokens(1), pad('0x1000', 32), { from: user1 }), 'NOT_IMPLEMENTED_YET') }) - it('pushBeacon works', async () => { + it('pushRewards works', async () => { await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) @@ -669,12 +669,12 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await app.methods['depositBufferedEther()']({ from: depositor }) await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - await assertRevert(app.pushBeacon(1, ETH(30), { from: appManager }), 'APP_AUTH_FAILED') + await assertRevert(app.pushRewards(1, ETH(30), { from: appManager }), 'APP_AUTH_FAILED') await oracle.reportBeacon(100, 1, ETH(30)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(30) }) - await assertRevert(app.pushBeacon(1, ETH(29), { from: nobody }), 'APP_AUTH_FAILED') + await assertRevert(app.pushRewards(1, ETH(29), { from: nobody }), 'APP_AUTH_FAILED') await oracle.reportBeacon(50, 1, ETH(100)) // stale data await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(100) }) diff --git a/test/0.4.24/lidoPushBeacon.test.js b/test/0.4.24/lidoPushRewards.test.js similarity index 99% rename from test/0.4.24/lidoPushBeacon.test.js rename to test/0.4.24/lidoPushRewards.test.js index b42db18bd..cb9c6eb91 100644 --- a/test/0.4.24/lidoPushBeacon.test.js +++ b/test/0.4.24/lidoPushRewards.test.js @@ -7,7 +7,7 @@ const OracleMock = artifacts.require('OracleMock.sol') const ETH = (value) => web3.utils.toWei(value + '', 'ether') -contract('Lido pushBeacon', ([appManager, voting, user1, user2, user3, nobody]) => { +contract('Lido pushRewards', ([appManager, voting, user1, user2, user3, nobody]) => { let appBase, app, oracle before('deploy base app', async () => { diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index 9b7e555d1..b40c2b7f6 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -15,7 +15,7 @@ const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') -contract.only('Lido: merge acceptance', (addresses) => { +contract('Lido: merge acceptance', (addresses) => { const [ // the root account which deployed the DAO appManager, From 31f6774b9f3160f812b1835370673dc3807be6a6 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 21 Dec 2021 19:37:05 +0300 Subject: [PATCH 040/159] handle case when no mevTxFeeVault is set to Lido contract --- contracts/0.4.24/Lido.sol | 16 +++++++++++----- test/0.4.24/lido.test.js | 8 -------- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 09b938391..3b59bcfc9 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -81,7 +81,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant BEACON_BALANCE_POSITION = keccak256("lido.Lido.beaconBalance"); /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); - /// @dev amount of Ether received by the contract as MEV and transaction Fees + /// @dev total amount of Ether MEV and transaction rewards received by Lido contract bytes32 internal constant MEV_TX_FEE_ETHER_POSITION = keccak256("lido.Lido.mevTxFeeEther"); /// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side @@ -304,10 +304,16 @@ contract Lido is ILido, IsContract, StETH, AragonApp { BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); - uint256 mevFeesRewards = ILidoMevTxFeeVault(getMevVault()).withdrawRewards(); - - BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevFeesRewards)); - MEV_TX_FEE_ETHER_POSITION.setStorageUint256(MEV_TX_FEE_ETHER_POSITION.getStorageUint256().add(mevFeesRewards)); + // If LidoMevTxFeeVault is not connected just do as if there are no mevTxFee rewards + // If the vault is connected withdraw all rewards and put them to the buffer for further staking + // Also increase counter of total mevTxFee rewards collected by Lido account + uint256 mevFeesRewards = 0; + address mevVaultAddress = getMevVault(); + if (mevVaultAddress != address(0x0)) { + mevFeesRewards = ILidoMevTxFeeVault(getMevVault()).withdrawRewards(); + BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevFeesRewards)); + MEV_TX_FEE_ETHER_POSITION.setStorageUint256(MEV_TX_FEE_ETHER_POSITION.getStorageUint256().add(mevFeesRewards)); + } if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index e8b21ac3a..29fc9417d 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -199,7 +199,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await web3.eth.sendTransaction({ to: app.address, from: userAddress, value: initialDepositAmount }) await app.methods['depositBufferedEther()']({ from: depositor }) - // await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) } it('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { @@ -215,11 +214,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await logAll() await rewarder.reward({ from: user1, value: ETH(mevAmount) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - await logAll() assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(mevAmount)) @@ -233,11 +230,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await logAll() await rewarder.reward({ from: user1, value: ETH(mevAmount) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - await logAll() assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(mevAmount)) @@ -251,16 +246,13 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await logAll() await rewarder.reward({ from: user1, value: ETH(mevAmount) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - await logAll() assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(mevAmount)) assertBn(await app.balanceOf(user2), STETH(41)) - // TODO: assertBn(await app.balanceOf(treasuryAddr), STETH(1) - 1 wei) }) it('setFee works', async () => { From 748ae19d190d3d4b48033d28d5831a2aff8d7c44 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 22 Dec 2021 15:19:02 +0300 Subject: [PATCH 041/159] refactor and improve documentation --- contracts/0.4.24/Lido.sol | 28 +++++++++---------- .../0.4.24/interfaces/ILidoMevTxFeeVault.sol | 11 ++++---- contracts/0.8.9/LidoMevTxFeeVault.sol | 22 +++++++++++++-- scripts/multisig/23-vote-mev-lido-upgrade.js | 2 +- test/0.4.24/lido.test.js | 14 +++++----- test/scenario/mev_tx_fee_after_the_merge.js | 2 +- 6 files changed, 48 insertions(+), 31 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 3b59bcfc9..204e7b623 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -71,7 +71,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant NODE_OPERATORS_REGISTRY_POSITION = keccak256("lido.Lido.nodeOperatorsRegistry"); bytes32 internal constant TREASURY_POSITION = keccak256("lido.Lido.treasury"); bytes32 internal constant INSURANCE_FUND_POSITION = keccak256("lido.Lido.insuranceFund"); - bytes32 internal constant MEV_VAULT_POSITION = keccak256("lido.Lido.mevVault"); + bytes32 internal constant MEV_TX_FEE_VAULT_POSITION = keccak256("lido.Lido.mevTxFeeVault"); /// @dev amount of Ether (on the current Ethereum side) buffered on this smart contract balance bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther"); @@ -125,12 +125,12 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice A payable function supposed to be funded by LidoMevTxFeeVault contract + * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract * @dev We need a separate function because funds received by default payable function - * go through deposit algorithm + * will go through entire deposit algorithm */ - function mevReceiver() external payable { - require(msg.sender == MEV_VAULT_POSITION.getStorageAddress()); + function mevTxFeeReceiver() external payable { + require(msg.sender == MEV_TX_FEE_VAULT_POSITION.getStorageAddress()); emit MevTxFeeReceived(msg.value); } @@ -257,11 +257,11 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @dev Sets given address as an address of LidoMevTxFeeVault contract - * @param _mevVault MEV and Tx Fees Vault contract address + * @param _mevTxFeeVault MEV and Tx Fees Vault contract address */ - function setMevVault(address _mevVault) external auth(SET_MEV_TX_FEE_VAULT_ROLE) { - require(isContract(_mevVault), "NOT_A_CONTRACT"); - MEV_VAULT_POSITION.setStorageAddress(_mevVault); + function setMevTxFeeVault(address _mevTxFeeVault) external auth(SET_MEV_TX_FEE_VAULT_ROLE) { + require(isContract(_mevTxFeeVault), "NOT_A_CONTRACT"); + MEV_TX_FEE_VAULT_POSITION.setStorageAddress(_mevTxFeeVault); } /** @@ -308,9 +308,9 @@ contract Lido is ILido, IsContract, StETH, AragonApp { // If the vault is connected withdraw all rewards and put them to the buffer for further staking // Also increase counter of total mevTxFee rewards collected by Lido account uint256 mevFeesRewards = 0; - address mevVaultAddress = getMevVault(); + address mevVaultAddress = getMevTxFeeVault(); if (mevVaultAddress != address(0x0)) { - mevFeesRewards = ILidoMevTxFeeVault(getMevVault()).withdrawRewards(); + mevFeesRewards = ILidoMevTxFeeVault(getMevTxFeeVault()).withdrawRewards(); BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevFeesRewards)); MEV_TX_FEE_ETHER_POSITION.setStorageUint256(MEV_TX_FEE_ETHER_POSITION.getStorageUint256().add(mevFeesRewards)); } @@ -387,7 +387,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @notice Get the total amount of MEV and transaction fees Ether buffered on this contract balance * @dev Ether got through MevTxFeeVault is kept on this contract's balance the same way as buffered - * Ether is kept till being deposited + * Ether is kept until it gets deposited * @return uint256 of funds received as MEV and Transaction fees in wei */ function getMevTxFeeEther() external view returns (uint256) { @@ -445,8 +445,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @notice Returns address of a contract set as LidoMevTxFeeVault */ - function getMevVault() public view returns (address) { - return MEV_VAULT_POSITION.getStorageAddress(); + function getMevTxFeeVault() public view returns (address) { + return MEV_TX_FEE_VAULT_POSITION.getStorageAddress(); } /** diff --git a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol index aff49ce3c..513e28695 100644 --- a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol +++ b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol @@ -1,14 +1,15 @@ // SPDX-License-Identifier: GPL-3.0 +// SPDX-License-Identifier: MIT + pragma solidity 0.4.24; -/** - * @title TODO - */ interface ILidoMevTxFeeVault { + /** - * @notice TODO - */ + * @notice Withdraw all accumulated rewards to Lido contract + * @return balance uint256 of funds received as MEV and transaction fees in wei + */ function withdrawRewards() external returns (uint256); } diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index ccf14f0e1..1e1b3b295 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -6,9 +6,25 @@ pragma solidity 0.8.9; interface ILido { - function mevReceiver() external payable; + + /** + * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract + * @dev We need a separate function because funds received by default payable function + * will go through entire deposit algorithm + */ + function mevTxFeeReceiver() external payable; } + +/** +* @title A vault for temporary storage of MEV and transaction fees +* +* This contract has no payable functions because it's balance is supposed to be +* increased directly by ethereum protocol when transaction priority fees and extracted MEV +* rewards are earned by a validator. +* These vault replenishments happen continuously throught a day, while withdrawals +* happen much less often, only on LidoOracle beacon balance reports +*/ contract LidoMevTxFeeVault { address public immutable lidoAddress; @@ -18,14 +34,14 @@ contract LidoMevTxFeeVault { /** * @notice Withdraw all accumulated rewards to Lido contract - * @return balance uint256 of funds received as MEV and Transaction fees in wei + * @return balance uint256 of funds received as MEV and transaction fees in wei */ function withdrawRewards() external returns (uint256 balance) { require(msg.sender == lidoAddress, "Nobody except Lido contract can withdraw"); balance = address(this).balance; if (balance > 0) { - ILido(lidoAddress).mevReceiver{value: balance}(); + ILido(lidoAddress).mevTxFeeReceiver{value: balance}(); } return balance; } diff --git a/scripts/multisig/23-vote-mev-lido-upgrade.js b/scripts/multisig/23-vote-mev-lido-upgrade.js index 7fad9a54c..194e80623 100644 --- a/scripts/multisig/23-vote-mev-lido-upgrade.js +++ b/scripts/multisig/23-vote-mev-lido-upgrade.js @@ -66,7 +66,7 @@ async function upgradeAppImpl({ web3, artifacts }) { const setMevTxFeeVaultToLidoCallData = { to: lidoAddress, - calldata: await lido.contract.methods.setMevVault(mevTxFeeVaultAddress).encodeABI() + calldata: await lido.contract.methods.setMevTxFeeVault(mevTxFeeVaultAddress).encodeABI() } const encodedUpgradeCallData = encodeCallScript([ diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 29fc9417d..c46ce0bc5 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -12,7 +12,7 @@ const { getEthBalance, formatStEth: formamtStEth, formatBN } = require('../helpe const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const Lido = artifacts.require('LidoMock.sol') -const MevVault = artifacts.require('LidoMevTxFeeVault.sol') +const MevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') @@ -70,7 +70,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let proxyAddress = await newApp(dao, 'lido', appBase.address, appManager) app = await Lido.at(proxyAddress) - mevVault = await MevVault.new(app.address) + mevVault = await MevTxFeeVault.new(app.address) rewarder = await RewardEmulatorMock.new(mevVault.address) // NodeOperatorsRegistry @@ -109,7 +109,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.setPool(app.address) await depositContract.reset() - await app.setMevVault(mevVault.address, { from: voting }) + await app.setMevTxFeeVault(mevVault.address, { from: voting }) }) const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { @@ -178,7 +178,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) console.log() } - const setupNodeOperatorsForMevVaultTests = async (userAddress, initialDepositAmount) => { + const setupNodeOperatorsForMevTxFeeVaultTests = async (userAddress, initialDepositAmount) => { await app.setFee(1000, { from: voting }) // 10% await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) @@ -212,7 +212,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const mevAmount = 10 const beaconRewards = 0 - await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) await rewarder.reward({ from: user1, value: ETH(mevAmount) }) @@ -228,7 +228,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const mevAmount = 12 const beaconRewards = -2 - await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) await rewarder.reward({ from: user1, value: ETH(mevAmount) }) @@ -244,7 +244,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const mevAmount = 7 const beaconRewards = 3 - await setupNodeOperatorsForMevVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) await rewarder.reward({ from: user1, value: ETH(mevAmount) }) diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index b40c2b7f6..a1c11a6e4 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -102,7 +102,7 @@ contract('Lido: merge acceptance', (addresses) => { depositRoot = await depositContractMock.get_deposit_root() mevVault = await LidoMevTxFeeVault.new(pool.address) - await pool.setMevVault(mevVault.address, { from: voting }) + await pool.setMevTxFeeVault(mevVault.address, { from: voting }) rewarder = await RewardEmulatorMock.new(mevVault.address) From 17d133c620daf1df4ca59ddf91cc5b54608b49d1 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 22 Dec 2021 15:40:22 +0300 Subject: [PATCH 042/159] a bit more of refactoring --- contracts/0.4.24/Lido.sol | 26 +++++++++---------- .../0.4.24/interfaces/ILidoMevTxFeeVault.sol | 4 +-- contracts/0.8.9/LidoMevTxFeeVault.sol | 12 ++++----- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 204e7b623..1a8ba3be4 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -256,7 +256,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @dev Sets given address as an address of LidoMevTxFeeVault contract + * @dev Sets given address as the address of LidoMevTxFeeVault contract * @param _mevTxFeeVault MEV and Tx Fees Vault contract address */ function setMevTxFeeVault(address _mevTxFeeVault) external auth(SET_MEV_TX_FEE_VAULT_ROLE) { @@ -304,20 +304,20 @@ contract Lido is ILido, IsContract, StETH, AragonApp { BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); - // If LidoMevTxFeeVault is not connected just do as if there are no mevTxFee rewards - // If the vault is connected withdraw all rewards and put them to the buffer for further staking - // Also increase counter of total mevTxFee rewards collected by Lido account - uint256 mevFeesRewards = 0; + // If LidoMevTxFeeVault's address is not set just do as if there were no mevTxFee rewards at all + // Otherwise withdraw all rewards and put them to the buffer for further staking + // and increase counter of total mevTxFee rewards collected by Lido account + uint256 mevRewards = 0; address mevVaultAddress = getMevTxFeeVault(); if (mevVaultAddress != address(0x0)) { - mevFeesRewards = ILidoMevTxFeeVault(getMevTxFeeVault()).withdrawRewards(); - BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevFeesRewards)); - MEV_TX_FEE_ETHER_POSITION.setStorageUint256(MEV_TX_FEE_ETHER_POSITION.getStorageUint256().add(mevFeesRewards)); + mevRewards = ILidoMevTxFeeVault(mevVaultAddress).withdrawRewards(); + BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevRewards)); + MEV_TX_FEE_ETHER_POSITION.setStorageUint256(MEV_TX_FEE_ETHER_POSITION.getStorageUint256().add(mevRewards)); } if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); - distributeRewards(rewards.add(mevFeesRewards)); + distributeRewards(rewards.add(mevRewards)); } } @@ -385,9 +385,9 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Get the total amount of MEV and transaction fees Ether buffered on this contract balance - * @dev Ether got through MevTxFeeVault is kept on this contract's balance the same way as buffered - * Ether is kept until it gets deposited + * @notice Get total amount of MEV and transaction fees Ether buffered on this contract's balance + * @dev Ether got through LidoMevTxFeeVault is kept on this contract's balance the same way + * as other buffered Ether is kept (until it gets deposited) * @return uint256 of funds received as MEV and Transaction fees in wei */ function getMevTxFeeEther() external view returns (uint256) { @@ -443,7 +443,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Returns address of a contract set as LidoMevTxFeeVault + * @notice Returns address of the contract set as LidoMevTxFeeVault */ function getMevTxFeeVault() public view returns (address) { return MEV_TX_FEE_VAULT_POSITION.getStorageAddress(); diff --git a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol index 513e28695..b8ff2697b 100644 --- a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol +++ b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol @@ -9,7 +9,7 @@ interface ILidoMevTxFeeVault { /** * @notice Withdraw all accumulated rewards to Lido contract - * @return balance uint256 of funds received as MEV and transaction fees in wei + * @return amount uint256 of funds received as MEV and transaction fees in wei */ - function withdrawRewards() external returns (uint256); + function withdrawRewards() external returns (uint256 amount); } diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 1e1b3b295..1509a1e00 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -34,15 +34,15 @@ contract LidoMevTxFeeVault { /** * @notice Withdraw all accumulated rewards to Lido contract - * @return balance uint256 of funds received as MEV and transaction fees in wei + * @return amount uint256 of funds received as MEV and transaction fees in wei */ - function withdrawRewards() external returns (uint256 balance) { + function withdrawRewards() external returns (uint256 amount) { require(msg.sender == lidoAddress, "Nobody except Lido contract can withdraw"); - balance = address(this).balance; - if (balance > 0) { - ILido(lidoAddress).mevTxFeeReceiver{value: balance}(); + amount = address(this).balance; + if (amount > 0) { + ILido(lidoAddress).mevTxFeeReceiver{value: amount}(); } - return balance; + return amount; } } \ No newline at end of file From 6e3b8b01ca11a3ad0ef5d0f2ad1ccdae38a9d5a0 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 30 Jan 2022 20:42:40 +0300 Subject: [PATCH 043/159] fix licenses in MEV-related contracts: MIT --> GPL-3.0 --- contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol | 4 ++-- contracts/0.8.9/LidoMevTxFeeVault.sol | 2 +- contracts/0.8.9/test_helpers/ETHForwarderMock.sol | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol index b8ff2697b..d596c1fd0 100644 --- a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol +++ b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol @@ -1,6 +1,6 @@ -// SPDX-License-Identifier: GPL-3.0 +// SPDX-FileCopyrightText: 2021 Lido -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.4.24; diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 1509a1e00..f23a7624b 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -1,6 +1,6 @@ // SPDX-FileCopyrightText: 2021 Lido -// SPDX-License-Identifier: MIT +// SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.9; diff --git a/contracts/0.8.9/test_helpers/ETHForwarderMock.sol b/contracts/0.8.9/test_helpers/ETHForwarderMock.sol index a83b791c5..7d00cedfe 100644 --- a/contracts/0.8.9/test_helpers/ETHForwarderMock.sol +++ b/contracts/0.8.9/test_helpers/ETHForwarderMock.sol @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: 2021 Lido -// SPDX-License-Identifier: GPL-3.0 +//SPDX-License-Identifier: GPL-3.0 + pragma solidity 0.8.9; contract ETHForwarderMock { From 42c0b85d7f3c61e87519011a85fce0e389d14323 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Thu, 23 Dec 2021 12:39:40 +0300 Subject: [PATCH 044/159] refactor LidoMevTxFeeVault a bit --- contracts/0.8.9/LidoMevTxFeeVault.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index f23a7624b..3e526ec96 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -26,10 +26,12 @@ interface ILido { * happen much less often, only on LidoOracle beacon balance reports */ contract LidoMevTxFeeVault { - address public immutable lidoAddress; + address public immutable LIDO; constructor(address _lidoAddress) { - lidoAddress = _lidoAddress; + require(_lidoAddress != address(0x0), "LIDO_ZERO_ADDRESS"); + + LIDO = _lidoAddress; } /** @@ -37,11 +39,11 @@ contract LidoMevTxFeeVault { * @return amount uint256 of funds received as MEV and transaction fees in wei */ function withdrawRewards() external returns (uint256 amount) { - require(msg.sender == lidoAddress, "Nobody except Lido contract can withdraw"); + require(msg.sender == LIDO, "Nobody except Lido contract can withdraw"); amount = address(this).balance; if (amount > 0) { - ILido(lidoAddress).mevTxFeeReceiver{value: amount}(); + ILido(LIDO).mevTxFeeReceiver{value: amount}(); } return amount; } From c32c3ad2abcb6dd17e37744ca6f4fc915b5784e8 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 30 Jan 2022 20:44:57 +0300 Subject: [PATCH 045/159] update deployed-goerli.json after mev upgrade deployment --- deployed-goerli.json | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/deployed-goerli.json b/deployed-goerli.json index d9bd5bc50..44e4a8604 100644 --- a/deployed-goerli.json +++ b/deployed-goerli.json @@ -16,14 +16,14 @@ "0x70B3284fD016a570146cE48fC54D7A81bCd94BBa" ], "daoTemplateDeployTx": "0xda8746bca23c7a4f7c58e1a3370cb1ffa1e250ace90fd4684dbb1842ee6ac921", - "lidoBaseDeployTx": "0xedff0d82dd32fdafa30a62388898e35b8c98759c15aa92ab1ba87b779d86c0bb", + "lidoBaseDeployTx": "0x4553e47f6030b9b28a17e92b0e7cc9ac64964e48e3fe2c2077aec486b44508b1", "oracleBaseDeployTx": "0x4ff91791cc0f969cf9fe0cc9a6241593d26b20ee27f785a2eb002258e25f4e43", "nodeOperatorsRegistryBaseDeployTx": "0x25cf4db41a4159e94826fd0495bafe02e4a015a9bcf0ffbc0e684a5cafe6eefc", "compositePostRebaseBeaconReceiverDeployTx": "0xf0f2d207ac3693abb8aa58e885aeff12371bb033d64376a409a8dd43d28d85ea", "selfOwnedStETHBurnerDeployTx": "0xa6267ebc56579f0c9e3f3b55908b851db971a83d5f97d4d2df9984396ae03b2f", "daoTemplateAddress": "0x9A4a36B5fe517f98BD6529d4317B0b723F600AaD", "app:lido": { - "baseAddress": "0x25C29642Aa0263B8Bf0A621a264Be6b6238B90E6", + "baseAddress": "0x5D3891E3015AED193eC4502D98524c4a94CfA6B5", "ipfsCid": "QmbmPW5r9HMdyUARNJjjE7MNqBUGrXashwoWvqRZhc1t5b", "contentURI": "0x697066733a516d626d5057357239484d64795541524e4a6a6a45374d4e714255477258617368776f577671525a686331743562", "name": "lido", @@ -184,5 +184,8 @@ 0, 4 ], - "selfOwnedStETHBurnerAddress": "0xf6a64DcB06Ef7eB1ee94aDfD7D10ACB44D9A9888" + "selfOwnedStETHBurnerAddress": "0xf6a64DcB06Ef7eB1ee94aDfD7D10ACB44D9A9888", + "mevTxFeeVaultDeployTx": "0xa93e29b3fc45eef335fcbe84c3587e93313281e2ff31a3df091400079a6b564c", + "mevTxFeeVaultAddress": "0x44D92F61E2236A7d0e9879aB87CF34622E85124e", + "mevTxFeeRewardsEmulator": "0x636ebc9dd60ef85bbaf2abb0bad789676aac12d0" } From 51886be8c1dfe79f6302d2e1bb6119eadc93c855 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 30 Jan 2022 20:55:27 +0300 Subject: [PATCH 046/159] Rename pushRewards and update deploy scripts - rename `pushRewards` to `handleOracleReport` - 23-vote-mev-upgrade.js: fixes and Oracle deployment - update addresses of deployed contracts --- README.md | 1 + contracts/0.4.24/Lido.sol | 8 ++--- contracts/0.4.24/interfaces/ILido.sol | 2 +- contracts/0.4.24/oracle/LidoOracle.sol | 2 +- .../0.4.24/test_helpers/LidoMockForOracle.sol | 2 +- .../0.4.24/test_helpers/LidoPushableMock.sol | 2 +- contracts/0.4.24/test_helpers/OracleMock.sol | 2 +- deployed-goerli.json | 12 +++---- docs/protocol-levers.md | 2 +- lib/abi/ILido.json | 2 +- lib/abi/Lido.json | 2 +- lib/abi/LidoMevTxFeeVault.json | 1 + ...oy-mev-vault.js => 26-deploy-mev-vault.js} | 0 ...ult.js => 27-obtain-deployed-mev-vault.js} | 2 +- ...lido-upgrade.js => 28-vote-mev-upgrade.js} | 35 ++++++++++++++----- test/0.4.24/lido.test.js | 6 ++-- ...test.js => lidoHandleOracleReport.test.js} | 2 +- 17 files changed, 51 insertions(+), 32 deletions(-) create mode 100644 lib/abi/LidoMevTxFeeVault.json rename scripts/multisig/{21-deploy-mev-vault.js => 26-deploy-mev-vault.js} (100%) rename scripts/multisig/{22-obtain-deployed-mev-vault.js => 27-obtain-deployed-mev-vault.js} (94%) rename scripts/multisig/{23-vote-mev-lido-upgrade.js => 28-vote-mev-upgrade.js} (80%) rename test/0.4.24/{lidoPushRewards.test.js => lidoHandleOracleReport.test.js} (99%) diff --git a/README.md b/README.md index 175dcc4b3..83c1e5e69 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ The contract also works as a wrapper that accepts stETH tokens and mints wstETH * Oracle: [`0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB`](https://goerli.etherscan.io/address/0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB) (proxy) * WstETH token: [`0x1643e812ae58766192cf7d2cf9567df2c37e9b7f`](https://goerli.etherscan.io/address/0x1643e812ae58766192cf7d2cf9567df2c37e9b7f) * Deposit Security Module: [`0xEd23AD3EA5Fb9d10e7371Caef1b141AD1C23A80c`](https://goerli.etherscan.io/address/0xEd23AD3EA5Fb9d10e7371Caef1b141AD1C23A80c) +* MEV Transaction Fee Vault [`0xece7301B3aeEC2b2B6C41a55dE831D47c205AaCC`](https://goerli.etherscan.io/address/0xece7301B3aeEC2b2B6C41a55dE831D47c205AaCC) * Aragon Voting: [`0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db`](https://goerli.etherscan.io/address/0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db) (proxy) * Aragon Token Manager: [`0xDfe76d11b365f5e0023343A367f0b311701B3bc1`](https://goerli.etherscan.io/address/0xDfe76d11b365f5e0023343A367f0b311701B3bc1) (proxy) * Aragon Finance: [`0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179`](https://goerli.etherscan.io/address/0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179) (proxy) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 1a8ba3be4..e032d5d84 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -217,7 +217,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @notice Set authorized oracle contract address to `_oracle` * @dev Contract specified here is allowed to make periodical updates of beacon states - * by calling pushRewards. + * by calling handleOracleReport. * @param _oracle oracle contract */ function setOracle(address _oracle) external auth(SET_ORACLE) { @@ -276,12 +276,12 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Updates the number of Lido-controlled keys in the beacon validators set and their total balance. + * @notice Updates beacon states, collects rewards from MevTxFeeVault and distributes all rewards if beacon balance increased * @dev periodically called by the Oracle contract * @param _beaconValidators number of Lido's keys in the beacon state - * @param _beaconBalance simmarized balance of Lido-controlled keys in wei + * @param _beaconBalance summarized balance of Lido-controlled keys in wei */ - function pushRewards(uint256 _beaconValidators, uint256 _beaconBalance) external whenNotStopped { + function handleOracleReport(uint256 _beaconValidators, uint256 _beaconBalance) external whenNotStopped { require(msg.sender == getOracle(), "APP_AUTH_FAILED"); uint256 depositedValidators = DEPOSITED_VALIDATORS_POSITION.getStorageUint256(); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index b7f0591d9..4d07f1929 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -90,7 +90,7 @@ interface ILido { * @param _epoch Epoch id * @param _eth2balance Balance in wei on the ETH 2.0 side */ - function pushRewards(uint256 _epoch, uint256 _eth2balance) external; + function handleOracleReport(uint256 _epoch, uint256 _eth2balance) external; // User functions diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 99982900c..339e76a7c 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -567,7 +567,7 @@ contract LidoOracle is ILidoOracle, AragonApp { // report to the Lido and collect stats ILido lido = getLido(); uint256 prevTotalPooledEther = lido.totalSupply(); - lido.pushRewards(_beaconValidators, _beaconBalanceEth1); + lido.handleOracleReport(_beaconValidators, _beaconBalanceEth1); uint256 postTotalPooledEther = lido.totalSupply(); PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.setStorageUint256(prevTotalPooledEther); diff --git a/contracts/0.4.24/test_helpers/LidoMockForOracle.sol b/contracts/0.4.24/test_helpers/LidoMockForOracle.sol index 15792b08f..c9b1299e9 100644 --- a/contracts/0.4.24/test_helpers/LidoMockForOracle.sol +++ b/contracts/0.4.24/test_helpers/LidoMockForOracle.sol @@ -15,7 +15,7 @@ contract LidoMockForOracle { return totalPooledEther; } - function pushRewards(uint256 /*_beaconValidators*/, uint256 _beaconBalance) external { + function handleOracleReport(uint256 /*_beaconValidators*/, uint256 _beaconBalance) external { totalPooledEther = _beaconBalance; } diff --git a/contracts/0.4.24/test_helpers/LidoPushableMock.sol b/contracts/0.4.24/test_helpers/LidoPushableMock.sol index 2918fa1b6..8b768c4a9 100644 --- a/contracts/0.4.24/test_helpers/LidoPushableMock.sol +++ b/contracts/0.4.24/test_helpers/LidoPushableMock.sol @@ -9,7 +9,7 @@ import "./VaultMock.sol"; /** - * @dev Mock for unit-testing pushRewards and how reward get calculated + * @dev Mock for unit-testing handleOracleReport and how reward get calculated */ contract LidoPushableMock is Lido { diff --git a/contracts/0.4.24/test_helpers/OracleMock.sol b/contracts/0.4.24/test_helpers/OracleMock.sol index f59811679..4293d430f 100644 --- a/contracts/0.4.24/test_helpers/OracleMock.sol +++ b/contracts/0.4.24/test_helpers/OracleMock.sol @@ -19,7 +19,7 @@ contract OracleMock { } function reportBeacon(uint256 _epochId, uint128 _beaconValidators, uint128 _beaconBalance) external { - pool.pushRewards(_beaconValidators, _beaconBalance); + pool.handleOracleReport(_beaconValidators, _beaconBalance); } function setBeaconReportReceiver(address _receiver) { diff --git a/deployed-goerli.json b/deployed-goerli.json index 44e4a8604..ccb649c10 100644 --- a/deployed-goerli.json +++ b/deployed-goerli.json @@ -16,14 +16,14 @@ "0x70B3284fD016a570146cE48fC54D7A81bCd94BBa" ], "daoTemplateDeployTx": "0xda8746bca23c7a4f7c58e1a3370cb1ffa1e250ace90fd4684dbb1842ee6ac921", - "lidoBaseDeployTx": "0x4553e47f6030b9b28a17e92b0e7cc9ac64964e48e3fe2c2077aec486b44508b1", - "oracleBaseDeployTx": "0x4ff91791cc0f969cf9fe0cc9a6241593d26b20ee27f785a2eb002258e25f4e43", + "lidoBaseDeployTx": "0x754584f974624d32310d68e6bda260b7200c31d4086b1416d421ad7b31f4ad0c", + "oracleBaseDeployTx": "0x171d09bc9bea4421db6c614c994c218c894dc028d807f8e7edd759a4ba148bbf", "nodeOperatorsRegistryBaseDeployTx": "0x25cf4db41a4159e94826fd0495bafe02e4a015a9bcf0ffbc0e684a5cafe6eefc", "compositePostRebaseBeaconReceiverDeployTx": "0xf0f2d207ac3693abb8aa58e885aeff12371bb033d64376a409a8dd43d28d85ea", "selfOwnedStETHBurnerDeployTx": "0xa6267ebc56579f0c9e3f3b55908b851db971a83d5f97d4d2df9984396ae03b2f", "daoTemplateAddress": "0x9A4a36B5fe517f98BD6529d4317B0b723F600AaD", "app:lido": { - "baseAddress": "0x5D3891E3015AED193eC4502D98524c4a94CfA6B5", + "baseAddress": "0xd2deB2210C1C4BcA5cb20081B7383278d84E009B", "ipfsCid": "QmbmPW5r9HMdyUARNJjjE7MNqBUGrXashwoWvqRZhc1t5b", "contentURI": "0x697066733a516d626d5057357239484d64795541524e4a6a6a45374d4e714255477258617368776f577671525a686331743562", "name": "lido", @@ -32,7 +32,7 @@ "proxyAddress": "0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F" }, "app:oracle": { - "baseAddress": "0x869E3cB508200D3bE0e946613a8986E8eb3E64d7", + "baseAddress": "0x0fCdBaaC5dfEC479c8A9dEEaFe0B1BBc42E04706", "ipfsCid": "QmfACH9oSHFWgV81Dh8RSVcgaVBdhkZuHhZY2iZv5syBJK", "contentURI": "0x697066733a516d66414348396f5348465767563831446838525356636761564264686b5a7548685a5932695a76357379424a4b", "name": "oracle", @@ -185,7 +185,7 @@ 4 ], "selfOwnedStETHBurnerAddress": "0xf6a64DcB06Ef7eB1ee94aDfD7D10ACB44D9A9888", - "mevTxFeeVaultDeployTx": "0xa93e29b3fc45eef335fcbe84c3587e93313281e2ff31a3df091400079a6b564c", - "mevTxFeeVaultAddress": "0x44D92F61E2236A7d0e9879aB87CF34622E85124e", + "mevTxFeeVaultDeployTx": "0x0cff10cc3830ff645032875eb895d4db007ce7cad9cfcd2668015f905ceb338f", + "mevTxFeeVaultAddress": "0xece7301B3aeEC2b2B6C41a55dE831D47c205AaCC", "mevTxFeeRewardsEmulator": "0x636ebc9dd60ef85bbaf2abb0bad789676aac12d0" } diff --git a/docs/protocol-levers.md b/docs/protocol-levers.md index c8d1763b6..e53648e7d 100644 --- a/docs/protocol-levers.md +++ b/docs/protocol-levers.md @@ -112,7 +112,7 @@ allowances) are allowed. The following transactions revert: * calls to `submit(address)`; * calls to `depositBufferedEther(uint256)`; * calls to `withdraw(uint256, bytes32)` (withdrawals are not implemented yet). -* calls to `pushRewards(uint256, uint256)`; +* calls to `handleOracleReport(uint256, uint256)`; * calls to `burnShares(address, uint256)` * calls to `transfer(address, uint256)` * calls to `transferFrom(address, address, uint256)` diff --git a/lib/abi/ILido.json b/lib/abi/ILido.json index 6f7d89520..aead76592 100644 --- a/lib/abi/ILido.json +++ b/lib/abi/ILido.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"uint256","name":"maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"mevTxFeeReceiver","outputs":[],"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/Lido.json b/lib/abi/Lido.json index b5c287d9f..12b38705d 100644 --- a/lib/abi/Lido.json +++ b/lib/abi/Lido.json @@ -1 +1 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"pushBeacon","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"pushBeacon","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"}] diff --git a/lib/abi/LidoMevTxFeeVault.json b/lib/abi/LidoMevTxFeeVault.json new file mode 100644 index 000000000..637d75a9e --- /dev/null +++ b/lib/abi/LidoMevTxFeeVault.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"address","name":"_lidoAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawRewards","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/scripts/multisig/21-deploy-mev-vault.js b/scripts/multisig/26-deploy-mev-vault.js similarity index 100% rename from scripts/multisig/21-deploy-mev-vault.js rename to scripts/multisig/26-deploy-mev-vault.js diff --git a/scripts/multisig/22-obtain-deployed-mev-vault.js b/scripts/multisig/27-obtain-deployed-mev-vault.js similarity index 94% rename from scripts/multisig/22-obtain-deployed-mev-vault.js rename to scripts/multisig/27-obtain-deployed-mev-vault.js index b614a7906..7a8281aad 100644 --- a/scripts/multisig/22-obtain-deployed-mev-vault.js +++ b/scripts/multisig/27-obtain-deployed-mev-vault.js @@ -34,7 +34,7 @@ async function obtainInstance({ web3, artifacts }) { async function assertAddresses({ lidoAddress }, instance, desc) { - assert.equal(await instance.lidoAddress(), lidoAddress, `${desc}: wrong lido`) + assert.equal(await instance.LIDO(), lidoAddress, `${desc}: wrong lido`) log.success(`Lido address is correct`) } diff --git a/scripts/multisig/23-vote-mev-lido-upgrade.js b/scripts/multisig/28-vote-mev-upgrade.js similarity index 80% rename from scripts/multisig/23-vote-mev-lido-upgrade.js rename to scripts/multisig/28-vote-mev-upgrade.js index 194e80623..b35ea36e7 100644 --- a/scripts/multisig/23-vote-mev-lido-upgrade.js +++ b/scripts/multisig/28-vote-mev-upgrade.js @@ -1,5 +1,6 @@ const { hash: namehash } = require('eth-ens-namehash') const { encodeCallScript } = require('@aragon/contract-helpers-test/src/aragon-os') +const { BN } = require('bn.js') const runOrWrapScript = require('../helpers/run-or-wrap-script') const { log, logSplitter, logWideSplitter, yl, gr } = require('../helpers/log') @@ -18,15 +19,15 @@ const REQUIRED_NET_STATE = [ `app:${APP_NAMES.ARAGON_TOKEN_MANAGER}` ] -async function upgradeAppImpl({ web3, artifacts }) { +async function createVoting({ web3, artifacts }) { const netId = await web3.eth.net.getId() logWideSplitter() log(`Network ID:`, yl(netId)) const state = readNetworkState(network.name, netId) - // TODO: update assertRequiredNetworkState: remove nos, add mev, require for rewards emulator - assertRequiredNetworkState(state, REQUIRED_NET_STATE.concat(['app:lido', 'app:node-operators-registry'])) + assertRequiredNetworkState(state, REQUIRED_NET_STATE.concat([ + 'app:lido', 'app:node-operators-registry', 'app:oracle', 'mevTxFeeVaultAddress'])) logSplitter() @@ -39,7 +40,9 @@ async function upgradeAppImpl({ web3, artifacts }) { const aclAddress = await kernel.acl() const acl = await artifacts.require('ACL').at(aclAddress) const lidoAddress = state[`app:lido`].proxyAddress + const oracleAddress = state[`app:oracle`].proxyAddress const lido = await artifacts.require('Lido').at(lidoAddress) + const oracle = await artifacts.require('LidoOracle').at(oracleAddress) const mevTxFeeVaultAddress = state.mevTxFeeVaultAddress log(`Using ENS:`, yl(state.ensAddress)) @@ -50,7 +53,9 @@ async function upgradeAppImpl({ web3, artifacts }) { log.splitter() - const lidoUpgradeCallData = await buildUpgradeTransaction('lido', state, ens, kernel) + const lidoUpgradeCallData = await buildUpgradeTransaction('lido', state, ens, kernel, 0) + + const oracleUpgradeCallData = await buildUpgradeTransaction('oracle', state, ens, kernel, 2) const grantRoleCallData = { to: aclAddress, @@ -69,8 +74,15 @@ async function upgradeAppImpl({ web3, artifacts }) { calldata: await lido.contract.methods.setMevTxFeeVault(mevTxFeeVaultAddress).encodeABI() } + const updateOracleVersionToV3CallData = { + to: oracleAddress, + calldata: await oracle.contract.methods.finalizeUpgrade_v3().encodeABI() + } + const encodedUpgradeCallData = encodeCallScript([ ...lidoUpgradeCallData, + ...oracleUpgradeCallData, + updateOracleVersionToV3CallData, grantRoleCallData, setMevTxFeeVaultToLidoCallData, ]) @@ -83,12 +95,14 @@ async function upgradeAppImpl({ web3, artifacts }) { } ]) - const txName = `tx-23-deploy-mev-lido-upgrade.json` + const txName = `tx-23-deploy-mev-upgrade.json` const votingDesc = `1) Publishing new implementation in lido app APM repo 2) Updating implementaion of lido app with new one -3) Grant role SENT_MEV_TX_FEE_VAULT_ROLE to voting -4) Set deployed MevTxFeeVault to Lido contract - ` +3) Publishing new implementation in oracle app APM repo +4) Updating implementaion of oracle app with new one +5) Call Oracle's finalizeUpgrade_v3() to update internal version counter +5) Grant role SENT_MEV_TX_FEE_VAULT_ROLE to voting +6) Set deployed MevTxFeeVault to Lido contract` await saveCallTxData(votingDesc, tokenManager, 'forward', txName, { arguments: [votingCallData], @@ -112,7 +126,10 @@ async function buildUpgradeTransaction(appName, state, ens, kernel) { const { semanticVersion: currentVersion, contractAddress: currentContractAddress, contentURI: currentContentURI } = await repo.getLatest() const versionFrom = currentVersion.map((n) => n.toNumber()) + console.log(currentVersion) currentVersion[0] = currentVersion[0].addn(1) + currentVersion[1] = new BN(0) + currentVersion[2] = new BN(0) const versionTo = currentVersion.map((n) => n.toNumber()) log.splitter() @@ -143,4 +160,4 @@ async function buildUpgradeTransaction(appName, state, ens, kernel) { return upgradeCallData } -module.exports = runOrWrapScript(upgradeAppImpl, module) +module.exports = runOrWrapScript(createVoting, module) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index c46ce0bc5..6d6f0552f 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -640,7 +640,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.withdraw(tokens(1), pad('0x1000', 32), { from: user1 }), 'NOT_IMPLEMENTED_YET') }) - it('pushRewards works', async () => { + it('handleOracleReport works', async () => { await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) @@ -661,12 +661,12 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await app.methods['depositBufferedEther()']({ from: depositor }) await checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) - await assertRevert(app.pushRewards(1, ETH(30), { from: appManager }), 'APP_AUTH_FAILED') + await assertRevert(app.handleOracleReport(1, ETH(30), { from: appManager }), 'APP_AUTH_FAILED') await oracle.reportBeacon(100, 1, ETH(30)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(30) }) - await assertRevert(app.pushRewards(1, ETH(29), { from: nobody }), 'APP_AUTH_FAILED') + await assertRevert(app.handleOracleReport(1, ETH(29), { from: nobody }), 'APP_AUTH_FAILED') await oracle.reportBeacon(50, 1, ETH(100)) // stale data await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(100) }) diff --git a/test/0.4.24/lidoPushRewards.test.js b/test/0.4.24/lidoHandleOracleReport.test.js similarity index 99% rename from test/0.4.24/lidoPushRewards.test.js rename to test/0.4.24/lidoHandleOracleReport.test.js index cb9c6eb91..c8de0f421 100644 --- a/test/0.4.24/lidoPushRewards.test.js +++ b/test/0.4.24/lidoHandleOracleReport.test.js @@ -7,7 +7,7 @@ const OracleMock = artifacts.require('OracleMock.sol') const ETH = (value) => web3.utils.toWei(value + '', 'ether') -contract('Lido pushRewards', ([appManager, voting, user1, user2, user3, nobody]) => { +contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, nobody]) => { let appBase, app, oracle before('deploy base app', async () => { From 05103ee1b0e6f7d5c2697672456707be92e57ea2 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 22 Dec 2021 10:39:39 +0300 Subject: [PATCH 047/159] Initial version of Oracle initialization according to new LIP - restore initialize() function which performs up-to-date initialization from scratch - keep the latest increment initialize function: namely dummy upgrade from v1 to v3 to correct version number - still a few TODOs left --- contracts/0.4.24/oracle/LidoOracle.sol | 63 ++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 10 deletions(-) diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index d0159f525..1d4aeca48 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -73,7 +73,8 @@ contract LidoOracle is ILidoOracle, AragonApp { bytes32 internal constant BEACON_SPEC_POSITION = 0x805e82d53a51be3dfde7cfed901f1f96f5dad18e874708b082adb8841e8ca909; // keccak256("lido.LidoOracle.beaconSpec") - /// Version of the initialized contract data, v1 is 0 + /// Version of the initialized contract data + /// Verstion at state right after deployment when no initializer is invoked yet is 0 bytes32 internal constant CONTRACT_VERSION_POSITION = 0x75be19a3f314d89bd1f84d30a6c84e2f1cd7afc7b6ca21876564c265113bb7e4; // keccak256("lido.LidoOracle.contractVersion") @@ -112,7 +113,8 @@ contract LidoOracle is ILidoOracle, AragonApp { bytes32 internal constant ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION = 0x92ba7776ed6c5d13cf023555a94e70b823a4aebd56ed522a77345ff5cd8a9109; // keccak256("lido.LidoOracle.allowedBeaconBalanceDecrease") - /// This variable is from v1: the last reported epoch, used only in the initializer + /// This is a dead variable: it was used only in v1 and in upgrade v1 --> v2 + /// Just keep in mind that storage at this position is occupied but with no actual usage bytes32 internal constant V1_LAST_REPORTED_EPOCH_ID_POSITION = 0xfe0250ed0c5d8af6526c6d133fccb8e5a55dd6b1aa6696ed0c327f8e517b5a94; // keccak256("lido.LidoOracle.lastReportedEpochId") @@ -328,22 +330,46 @@ contract LidoOracle is ILidoOracle, AragonApp { preTotalPooledEther = PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.getStorageUint256(); timeElapsed = TIME_ELAPSED_POSITION.getStorageUint256(); } - /** - * @notice Initialize the contract v2 data, with sanity check bounds - * (`_allowedBeaconBalanceAnnualRelativeIncrease`, `_allowedBeaconBalanceRelativeDecrease`) - * @dev Original initialize function removed from v2 because it is invoked only once + * @notice Initialize the contract (version 3 for now) from scratch + * @dev TODO: Add link to the related LIP + * @param _lido Address of Lido contract + * @param _epochsPerFrame Number of epochs per frame + * @param _slotsPerEpoch Number of slots per epoch + * @param _secondsPerSlot Number of seconds per slot + * @param _genesisTime Genesis time + * @param _allowedBeaconBalanceAnnualRelativeIncrease + * @param _allowedBeaconBalanceRelativeDecrease */ - function initialize_v2( + function initialize( + address _lido, + uint64 _epochsPerFrame, + uint64 _slotsPerEpoch, + uint64 _secondsPerSlot, + uint64 _genesisTime, uint256 _allowedBeaconBalanceAnnualRelativeIncrease, uint256 _allowedBeaconBalanceRelativeDecrease ) - external { + assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert + + // Initializations for v0 --> v1 require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "ALREADY_INITIALIZED"); - CONTRACT_VERSION_POSITION.setStorageUint256(1); - emit ContractVersionSet(1); + _setBeaconSpec( + _epochsPerFrame, + _slotsPerEpoch, + _secondsPerSlot, + _genesisTime + ); + + LIDO_POSITION.setStorageAddress(_lido); + + QUORUM_POSITION.setStorageUint256(1); + emit QuorumChanged(1); + + + // Initializations for v1 --> v2 ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION .setStorageUint256(_allowedBeaconBalanceAnnualRelativeIncrease); emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_allowedBeaconBalanceAnnualRelativeIncrease); @@ -354,6 +380,7 @@ contract LidoOracle is ILidoOracle, AragonApp { // set last completed epoch as V1's contract last reported epoch, in the vast majority of // cases this is true, in others the error is within a frame + // TODO: restore value of V1_LAST_REPORTED_EPOCH_ID_POSITION uint256 lastReportedEpoch = V1_LAST_REPORTED_EPOCH_ID_POSITION.getStorageUint256(); LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(lastReportedEpoch); @@ -362,6 +389,22 @@ contract LidoOracle is ILidoOracle, AragonApp { uint256 expectedEpoch = _getFrameFirstEpochId(lastReportedEpoch, beaconSpec) + beaconSpec.epochsPerFrame; EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); emit ExpectedEpochIdUpdated(expectedEpoch); + + + // Initializations for v2 --> v3 + initialize_v3(); + } + + /** + * @notice A dummy incremental v2 --> v3 initialize function. Just corrects version number + * @dev This function is introduced just for the sake of clarity and correspondence between number + * of version in initialize function name and number is CONTRACT_VERSION_POSITION. + * NB, that thus version 2 is skipped + */ + function initialize_v3() { + require(CONTRACT_VERSION_POSITION.getStorageUint256() == 1, "ALREADY_INITIALIZED"); + CONTRACT_VERSION_POSITION.setStorageUint256(3); + emit ContractVersionSet(3); } /** From 4f586fbfb0e686216ad11f748fab3fe62262fd11 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Thu, 23 Dec 2021 13:43:05 +0300 Subject: [PATCH 048/159] updates to LidoOracle initialization function - make separate private _initialize_v3 - rename public initialize_v3 to finalizeUpgrade_v3 - fix: make initialize() external --- contracts/0.4.24/oracle/LidoOracle.sol | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 1d4aeca48..83132d309 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -350,6 +350,7 @@ contract LidoOracle is ILidoOracle, AragonApp { uint256 _allowedBeaconBalanceAnnualRelativeIncrease, uint256 _allowedBeaconBalanceRelativeDecrease ) + external { assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert @@ -392,17 +393,27 @@ contract LidoOracle is ILidoOracle, AragonApp { // Initializations for v2 --> v3 - initialize_v3(); + _initialize_v3(); + } + + + /** + * @notice A function to finalize upgrade to v3 (from v1). Can be called only once + * @dev For more details see _initialize_v3() + */ + function finalizeUpgrade_v3() external { + require(CONTRACT_VERSION_POSITION.getStorageUint256() == 1, "WRONG_BASE_VERSION"); + + _initialize_v3(); } /** - * @notice A dummy incremental v2 --> v3 initialize function. Just corrects version number + * @notice A dummy incremental v1/v2 --> v3 initialize function. Just corrects version number * @dev This function is introduced just for the sake of clarity and correspondence between number * of version in initialize function name and number is CONTRACT_VERSION_POSITION. * NB, that thus version 2 is skipped */ - function initialize_v3() { - require(CONTRACT_VERSION_POSITION.getStorageUint256() == 1, "ALREADY_INITIALIZED"); + function _initialize_v3() { CONTRACT_VERSION_POSITION.setStorageUint256(3); emit ContractVersionSet(3); } From aacc1119313748e1c7539adbd51a2602c11cd99e Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Thu, 23 Dec 2021 15:26:16 +0300 Subject: [PATCH 049/159] fixes to LidoOracle initialization related files --- contracts/0.4.24/interfaces/ILidoOracle.sol | 31 ++++++++++++++----- contracts/0.4.24/oracle/LidoOracle.sol | 26 ++++++++-------- test/0.4.24/lidooracle.test.js | 5 +-- .../scenario/changing_oracles_during_epoch.js | 3 +- 4 files changed, 40 insertions(+), 25 deletions(-) diff --git a/contracts/0.4.24/interfaces/ILidoOracle.sol b/contracts/0.4.24/interfaces/ILidoOracle.sol index 11864bbeb..4342454fc 100644 --- a/contracts/0.4.24/interfaces/ILidoOracle.sol +++ b/contracts/0.4.24/interfaces/ILidoOracle.sol @@ -184,15 +184,30 @@ interface ILidoOracle { ); /** - * @notice Initialize the contract v2 data, with sanity check bounds - * (`_allowedBeaconBalanceAnnualRelativeIncrease`, `_allowedBeaconBalanceRelativeDecrease`) - * @dev Original initialize function removed from v2 because it is invoked only once - */ - function initialize_v2( + * @notice Initialize the contract (version 3 for now) from scratch + * @dev TODO: Add link to the related LIP + * @param _lido Address of Lido contract + * @param _epochsPerFrame Number of epochs per frame + * @param _slotsPerEpoch Number of slots per epoch + * @param _secondsPerSlot Number of seconds per slot + * @param _genesisTime Genesis time + * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) + * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) + */ + function initialize( + address _lido, + uint64 _epochsPerFrame, + uint64 _slotsPerEpoch, + uint64 _secondsPerSlot, + uint64 _genesisTime, uint256 _allowedBeaconBalanceAnnualRelativeIncrease, - uint256 _allowedBeaconBalanceRelativeDecrease - ) - external; + uint256 _allowedBeaconBalanceRelativeDecrease) external; + + /** + * @notice A function to finalize upgrade to v3 (from v1). Can be called only once + * @dev For more details see _initialize_v3() + */ + function finalizeUpgrade_v3() external; /** * @notice Add `_member` to the oracle member committee list diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 83132d309..2e0fb3661 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -338,8 +338,8 @@ contract LidoOracle is ILidoOracle, AragonApp { * @param _slotsPerEpoch Number of slots per epoch * @param _secondsPerSlot Number of seconds per slot * @param _genesisTime Genesis time - * @param _allowedBeaconBalanceAnnualRelativeIncrease - * @param _allowedBeaconBalanceRelativeDecrease + * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) + * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) */ function initialize( address _lido, @@ -379,17 +379,17 @@ contract LidoOracle is ILidoOracle, AragonApp { .setStorageUint256(_allowedBeaconBalanceRelativeDecrease); emit AllowedBeaconBalanceRelativeDecreaseSet(_allowedBeaconBalanceRelativeDecrease); - // set last completed epoch as V1's contract last reported epoch, in the vast majority of - // cases this is true, in others the error is within a frame - // TODO: restore value of V1_LAST_REPORTED_EPOCH_ID_POSITION - uint256 lastReportedEpoch = V1_LAST_REPORTED_EPOCH_ID_POSITION.getStorageUint256(); - LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(lastReportedEpoch); - - // set expected epoch to the first epoch for the next frame - BeaconSpec memory beaconSpec = _getBeaconSpec(); - uint256 expectedEpoch = _getFrameFirstEpochId(lastReportedEpoch, beaconSpec) + beaconSpec.epochsPerFrame; - EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); - emit ExpectedEpochIdUpdated(expectedEpoch); + // // set last completed epoch as V1's contract last reported epoch, in the vast majority of + // // cases this is true, in others the error is within a frame + // // TODO: restore value of V1_LAST_REPORTED_EPOCH_ID_POSITION + // uint256 lastReportedEpoch = V1_LAST_REPORTED_EPOCH_ID_POSITION.getStorageUint256(); + // LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(lastReportedEpoch); + + // // set expected epoch to the first epoch for the next frame + // BeaconSpec memory beaconSpec = _getBeaconSpec(); + // uint256 expectedEpoch = _getFrameFirstEpochId(lastReportedEpoch, beaconSpec) + beaconSpec.epochsPerFrame; + // EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); + // emit ExpectedEpochIdUpdated(expectedEpoch); // Initializations for v2 --> v3 diff --git a/test/0.4.24/lidooracle.test.js b/test/0.4.24/lidooracle.test.js index 6d1de7f55..1baaec3a0 100644 --- a/test/0.4.24/lidooracle.test.js +++ b/test/0.4.24/lidooracle.test.js @@ -46,8 +46,9 @@ contract('LidoOracle', ([appManager, voting, user1, user2, user3, user4, user5, // Initialize the app's proxy. await app.setTime(GENESIS_TIME) - await app.initialize(appLido.address, 1, 32, 12, GENESIS_TIME) - await app.initialize_v2(1000, 500) // initialize the second version: 10% yearly increase, 5% moment decrease + + // 1000 and 500 stand for 10% yearly increase, 5% moment decrease + await app.initialize(appLido.address, 1, 32, 12, GENESIS_TIME, 1000, 500) }) it('beaconSpec is correct', async () => { diff --git a/test/scenario/changing_oracles_during_epoch.js b/test/scenario/changing_oracles_during_epoch.js index dadde46fa..e905b9351 100644 --- a/test/scenario/changing_oracles_during_epoch.js +++ b/test/scenario/changing_oracles_during_epoch.js @@ -35,10 +35,9 @@ contract('LidoOracle', ([appManager, voting, malicious1, malicious2, user1, user // Initialize the app's proxy. await app.setTime(GENESIS_TIME + 225 * EPOCH_LENGTH) - await app.initialize(appLido.address, 225, 32, 12, GENESIS_TIME) + await app.initialize(appLido.address, 225, 32, 12, GENESIS_TIME, 1000, 500) await app.setV1LastReportedEpochForTest(123) // pretend we had epoch 123 completed in v1 - await app.initialize_v2(1000, 500) // initialize the second version: 10% yearly increase, 5% moment decrease // Initialize the oracle time, quorum and basic oracles await app.setQuorum(4, { from: voting }) From 9095cab1b544889c7a8a47df16a15ef55654b1bb Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Thu, 23 Dec 2021 16:28:23 +0300 Subject: [PATCH 050/159] fix LidoOracle initialize() to fix local tests --- contracts/0.4.24/interfaces/ILidoOracle.sol | 6 +++- contracts/0.4.24/oracle/LidoOracle.sol | 29 +++++++++---------- .../0.4.24/test_helpers/LidoOracleMock.sol | 23 --------------- test/0.4.24/lidooracle.test.js | 2 +- .../scenario/changing_oracles_during_epoch.js | 4 +-- 5 files changed, 21 insertions(+), 43 deletions(-) diff --git a/contracts/0.4.24/interfaces/ILidoOracle.sol b/contracts/0.4.24/interfaces/ILidoOracle.sol index 4342454fc..69e2cce74 100644 --- a/contracts/0.4.24/interfaces/ILidoOracle.sol +++ b/contracts/0.4.24/interfaces/ILidoOracle.sol @@ -183,6 +183,7 @@ interface ILidoOracle { uint256 timeElapsed ); + /** * @notice Initialize the contract (version 3 for now) from scratch * @dev TODO: Add link to the related LIP @@ -193,6 +194,7 @@ interface ILidoOracle { * @param _genesisTime Genesis time * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) + * @param _lastCompletedEpochId Id of the last completed epoch TODO can it always be zero? */ function initialize( address _lido, @@ -201,7 +203,9 @@ interface ILidoOracle { uint64 _secondsPerSlot, uint64 _genesisTime, uint256 _allowedBeaconBalanceAnnualRelativeIncrease, - uint256 _allowedBeaconBalanceRelativeDecrease) external; + uint256 _allowedBeaconBalanceRelativeDecrease, + uint256 _lastCompletedEpochId + ) external; /** * @notice A function to finalize upgrade to v3 (from v1). Can be called only once diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 2e0fb3661..06127933f 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -340,6 +340,7 @@ contract LidoOracle is ILidoOracle, AragonApp { * @param _genesisTime Genesis time * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) + * @param _lastCompletedEpochId Id of the last completed epoch TODO can it always be zero? */ function initialize( address _lido, @@ -348,14 +349,15 @@ contract LidoOracle is ILidoOracle, AragonApp { uint64 _secondsPerSlot, uint64 _genesisTime, uint256 _allowedBeaconBalanceAnnualRelativeIncrease, - uint256 _allowedBeaconBalanceRelativeDecrease + uint256 _allowedBeaconBalanceRelativeDecrease, + uint256 _lastCompletedEpochId ) - external + external onlyInit { assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert // Initializations for v0 --> v1 - require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "ALREADY_INITIALIZED"); + require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "BASE_VERSION_MUST_BE_ZERO"); _setBeaconSpec( _epochsPerFrame, @@ -379,21 +381,18 @@ contract LidoOracle is ILidoOracle, AragonApp { .setStorageUint256(_allowedBeaconBalanceRelativeDecrease); emit AllowedBeaconBalanceRelativeDecreaseSet(_allowedBeaconBalanceRelativeDecrease); - // // set last completed epoch as V1's contract last reported epoch, in the vast majority of - // // cases this is true, in others the error is within a frame - // // TODO: restore value of V1_LAST_REPORTED_EPOCH_ID_POSITION - // uint256 lastReportedEpoch = V1_LAST_REPORTED_EPOCH_ID_POSITION.getStorageUint256(); - // LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(lastReportedEpoch); - - // // set expected epoch to the first epoch for the next frame - // BeaconSpec memory beaconSpec = _getBeaconSpec(); - // uint256 expectedEpoch = _getFrameFirstEpochId(lastReportedEpoch, beaconSpec) + beaconSpec.epochsPerFrame; - // EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); - // emit ExpectedEpochIdUpdated(expectedEpoch); - + LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(_lastCompletedEpochId); + // set expected epoch to the first epoch for the next frame + BeaconSpec memory beaconSpec = _getBeaconSpec(); + uint256 expectedEpoch = _getFrameFirstEpochId(_lastCompletedEpochId, beaconSpec) + beaconSpec.epochsPerFrame; + EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); + emit ExpectedEpochIdUpdated(expectedEpoch); // Initializations for v2 --> v3 _initialize_v3(); + + // Need this despite contract version check as Aragon requires it to handle auth() modificators properly + initialized(); } diff --git a/contracts/0.4.24/test_helpers/LidoOracleMock.sol b/contracts/0.4.24/test_helpers/LidoOracleMock.sol index 90adbe2c4..6b10eb231 100644 --- a/contracts/0.4.24/test_helpers/LidoOracleMock.sol +++ b/contracts/0.4.24/test_helpers/LidoOracleMock.sol @@ -13,29 +13,6 @@ import "../oracle/LidoOracle.sol"; contract LidoOracleMock is LidoOracle { uint256 private time; - // Original initialize function from v1 - function initialize( - address _lido, - uint64 _epochsPerFrame, - uint64 _slotsPerEpoch, - uint64 _secondsPerSlot, - uint64 _genesisTime - ) - public onlyInit - { - assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert - _setBeaconSpec( - _epochsPerFrame, - _slotsPerEpoch, - _secondsPerSlot, - _genesisTime - ); - LIDO_POSITION.setStorageAddress(_lido); - QUORUM_POSITION.setStorageUint256(1); - emit QuorumChanged(1); - initialized(); - } - function setV1LastReportedEpochForTest(uint256 _epoch) public { V1_LAST_REPORTED_EPOCH_ID_POSITION.setStorageUint256(_epoch); } diff --git a/test/0.4.24/lidooracle.test.js b/test/0.4.24/lidooracle.test.js index 1baaec3a0..98b78d463 100644 --- a/test/0.4.24/lidooracle.test.js +++ b/test/0.4.24/lidooracle.test.js @@ -48,7 +48,7 @@ contract('LidoOracle', ([appManager, voting, user1, user2, user3, user4, user5, await app.setTime(GENESIS_TIME) // 1000 and 500 stand for 10% yearly increase, 5% moment decrease - await app.initialize(appLido.address, 1, 32, 12, GENESIS_TIME, 1000, 500) + await app.initialize(appLido.address, 1, 32, 12, GENESIS_TIME, 1000, 500, 0) }) it('beaconSpec is correct', async () => { diff --git a/test/scenario/changing_oracles_during_epoch.js b/test/scenario/changing_oracles_during_epoch.js index e905b9351..555758004 100644 --- a/test/scenario/changing_oracles_during_epoch.js +++ b/test/scenario/changing_oracles_during_epoch.js @@ -35,9 +35,7 @@ contract('LidoOracle', ([appManager, voting, malicious1, malicious2, user1, user // Initialize the app's proxy. await app.setTime(GENESIS_TIME + 225 * EPOCH_LENGTH) - await app.initialize(appLido.address, 225, 32, 12, GENESIS_TIME, 1000, 500) - - await app.setV1LastReportedEpochForTest(123) // pretend we had epoch 123 completed in v1 + await app.initialize(appLido.address, 225, 32, 12, GENESIS_TIME, 1000, 500, 123) // pretend we had epoch 123 completed previously // Initialize the oracle time, quorum and basic oracles await app.setQuorum(4, { from: voting }) From 12eb9d7672018de7364696dd4c9a8c91df65365b Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Thu, 23 Dec 2021 17:47:24 +0300 Subject: [PATCH 051/159] leave last completed epoch be 0 on initialize() --- contracts/0.4.24/interfaces/ILidoOracle.sol | 4 +--- contracts/0.4.24/oracle/LidoOracle.sol | 12 +++++------- test/0.4.24/lidooracle.test.js | 2 +- test/scenario/changing_oracles_during_epoch.js | 2 +- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/contracts/0.4.24/interfaces/ILidoOracle.sol b/contracts/0.4.24/interfaces/ILidoOracle.sol index 69e2cce74..5b284d1c0 100644 --- a/contracts/0.4.24/interfaces/ILidoOracle.sol +++ b/contracts/0.4.24/interfaces/ILidoOracle.sol @@ -194,7 +194,6 @@ interface ILidoOracle { * @param _genesisTime Genesis time * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) - * @param _lastCompletedEpochId Id of the last completed epoch TODO can it always be zero? */ function initialize( address _lido, @@ -203,8 +202,7 @@ interface ILidoOracle { uint64 _secondsPerSlot, uint64 _genesisTime, uint256 _allowedBeaconBalanceAnnualRelativeIncrease, - uint256 _allowedBeaconBalanceRelativeDecrease, - uint256 _lastCompletedEpochId + uint256 _allowedBeaconBalanceRelativeDecrease ) external; /** diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 06127933f..a12bf01e9 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -330,6 +330,7 @@ contract LidoOracle is ILidoOracle, AragonApp { preTotalPooledEther = PRE_COMPLETED_TOTAL_POOLED_ETHER_POSITION.getStorageUint256(); timeElapsed = TIME_ELAPSED_POSITION.getStorageUint256(); } + /** * @notice Initialize the contract (version 3 for now) from scratch * @dev TODO: Add link to the related LIP @@ -340,7 +341,6 @@ contract LidoOracle is ILidoOracle, AragonApp { * @param _genesisTime Genesis time * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) - * @param _lastCompletedEpochId Id of the last completed epoch TODO can it always be zero? */ function initialize( address _lido, @@ -349,8 +349,7 @@ contract LidoOracle is ILidoOracle, AragonApp { uint64 _secondsPerSlot, uint64 _genesisTime, uint256 _allowedBeaconBalanceAnnualRelativeIncrease, - uint256 _allowedBeaconBalanceRelativeDecrease, - uint256 _lastCompletedEpochId + uint256 _allowedBeaconBalanceRelativeDecrease ) external onlyInit { @@ -381,17 +380,16 @@ contract LidoOracle is ILidoOracle, AragonApp { .setStorageUint256(_allowedBeaconBalanceRelativeDecrease); emit AllowedBeaconBalanceRelativeDecreaseSet(_allowedBeaconBalanceRelativeDecrease); - LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(_lastCompletedEpochId); - // set expected epoch to the first epoch for the next frame + // // set expected epoch to the first epoch for the next frame BeaconSpec memory beaconSpec = _getBeaconSpec(); - uint256 expectedEpoch = _getFrameFirstEpochId(_lastCompletedEpochId, beaconSpec) + beaconSpec.epochsPerFrame; + uint256 expectedEpoch = _getFrameFirstEpochId(0, beaconSpec) + beaconSpec.epochsPerFrame; EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); emit ExpectedEpochIdUpdated(expectedEpoch); // Initializations for v2 --> v3 _initialize_v3(); - // Need this despite contract version check as Aragon requires it to handle auth() modificators properly + // Need this despite contract version check because Aragon requires it to handle auth() modificators properly initialized(); } diff --git a/test/0.4.24/lidooracle.test.js b/test/0.4.24/lidooracle.test.js index 98b78d463..1baaec3a0 100644 --- a/test/0.4.24/lidooracle.test.js +++ b/test/0.4.24/lidooracle.test.js @@ -48,7 +48,7 @@ contract('LidoOracle', ([appManager, voting, user1, user2, user3, user4, user5, await app.setTime(GENESIS_TIME) // 1000 and 500 stand for 10% yearly increase, 5% moment decrease - await app.initialize(appLido.address, 1, 32, 12, GENESIS_TIME, 1000, 500, 0) + await app.initialize(appLido.address, 1, 32, 12, GENESIS_TIME, 1000, 500) }) it('beaconSpec is correct', async () => { diff --git a/test/scenario/changing_oracles_during_epoch.js b/test/scenario/changing_oracles_during_epoch.js index 555758004..cbc02a9e1 100644 --- a/test/scenario/changing_oracles_during_epoch.js +++ b/test/scenario/changing_oracles_during_epoch.js @@ -35,7 +35,7 @@ contract('LidoOracle', ([appManager, voting, malicious1, malicious2, user1, user // Initialize the app's proxy. await app.setTime(GENESIS_TIME + 225 * EPOCH_LENGTH) - await app.initialize(appLido.address, 225, 32, 12, GENESIS_TIME, 1000, 500, 123) // pretend we had epoch 123 completed previously + await app.initialize(appLido.address, 225, 32, 12, GENESIS_TIME, 1000, 500) // Initialize the oracle time, quorum and basic oracles await app.setQuorum(4, { from: voting }) From bbcd89378633c34bb7670a6d62de6b6affbc52eb Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 21 Jan 2022 16:51:17 +0300 Subject: [PATCH 052/159] improve LidoOracle comments related to versioning --- contracts/0.4.24/oracle/LidoOracle.sol | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index a12bf01e9..a414ecf66 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -74,7 +74,11 @@ contract LidoOracle is ILidoOracle, AragonApp { 0x805e82d53a51be3dfde7cfed901f1f96f5dad18e874708b082adb8841e8ca909; // keccak256("lido.LidoOracle.beaconSpec") /// Version of the initialized contract data - /// Verstion at state right after deployment when no initializer is invoked yet is 0 + /// NB: Contract versioning starts from 1. + /// This version sotred in CONTRACT_VERSION_POSITION equals to + /// - 0 right after deployment when no initializer is invoked yet + /// - N after calling initialize() during deployment from scratch, where N is the current contract version + /// - N after upgrading contract from the previous version (after calling finalize_vN()) bytes32 internal constant CONTRACT_VERSION_POSITION = 0x75be19a3f314d89bd1f84d30a6c84e2f1cd7afc7b6ca21876564c265113bb7e4; // keccak256("lido.LidoOracle.contractVersion") @@ -355,7 +359,9 @@ contract LidoOracle is ILidoOracle, AragonApp { { assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert - // Initializations for v0 --> v1 + // We consider storage state right after deployment (no initialize() called yet) as version 0 + + // Initializations for v0 --> v1 (considering version semantically) require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "BASE_VERSION_MUST_BE_ZERO"); _setBeaconSpec( @@ -371,7 +377,7 @@ contract LidoOracle is ILidoOracle, AragonApp { emit QuorumChanged(1); - // Initializations for v1 --> v2 + // Initializations for v1 --> v2 (considering version semantically) ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION .setStorageUint256(_allowedBeaconBalanceAnnualRelativeIncrease); emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_allowedBeaconBalanceAnnualRelativeIncrease); @@ -386,17 +392,17 @@ contract LidoOracle is ILidoOracle, AragonApp { EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); emit ExpectedEpochIdUpdated(expectedEpoch); - // Initializations for v2 --> v3 + // Initializations for v2 --> v3 (considering version semantically) _initialize_v3(); // Need this despite contract version check because Aragon requires it to handle auth() modificators properly initialized(); } - /** * @notice A function to finalize upgrade to v3 (from v1). Can be called only once - * @dev For more details see _initialize_v3() + * @dev Value 2 in CONTRACT_VERSION_POSITION is skipped due to change in numbering + * . For more details see LIP-??? _initialize_v3() */ function finalizeUpgrade_v3() external { require(CONTRACT_VERSION_POSITION.getStorageUint256() == 1, "WRONG_BASE_VERSION"); @@ -405,9 +411,9 @@ contract LidoOracle is ILidoOracle, AragonApp { } /** - * @notice A dummy incremental v1/v2 --> v3 initialize function. Just corrects version number - * @dev This function is introduced just for the sake of clarity and correspondence between number - * of version in initialize function name and number is CONTRACT_VERSION_POSITION. + * @notice A dummy incremental v1/v2 --> v3 initialize function. Just corrects version number in storage + * @dev This function is introduced just to set in correspondence version number in storage, + * semantic version of the contract and number N used in naming of _initialize_nN/finalizeUpgrade_vN. * NB, that thus version 2 is skipped */ function _initialize_v3() { From 1abd3da0b5ebb139daaa7a087fb7f678fb574b4f Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 30 Jan 2022 20:23:23 +0300 Subject: [PATCH 053/159] Revert "Merge branch 'redo-oracle-initialization' into audit/mev" This reverts commit 68800ffeb43d032d5cd9d8fe3039686c4136f769. --- contracts/0.4.24/oracle/LidoOracle.sol | 91 +++++--------------------- 1 file changed, 17 insertions(+), 74 deletions(-) diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 0016aa35a..339e76a7c 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -73,12 +73,7 @@ contract LidoOracle is ILidoOracle, AragonApp { bytes32 internal constant BEACON_SPEC_POSITION = 0x805e82d53a51be3dfde7cfed901f1f96f5dad18e874708b082adb8841e8ca909; // keccak256("lido.LidoOracle.beaconSpec") - /// Version of the initialized contract data - /// NB: Contract versioning starts from 1. - /// This version sotred in CONTRACT_VERSION_POSITION equals to - /// - 0 right after deployment when no initializer is invoked yet - /// - N after calling initialize() during deployment from scratch, where N is the current contract version - /// - N after upgrading contract from the previous version (after calling finalize_vN()) + /// Version of the initialized contract data, v1 is 0 bytes32 internal constant CONTRACT_VERSION_POSITION = 0x75be19a3f314d89bd1f84d30a6c84e2f1cd7afc7b6ca21876564c265113bb7e4; // keccak256("lido.LidoOracle.contractVersion") @@ -117,8 +112,7 @@ contract LidoOracle is ILidoOracle, AragonApp { bytes32 internal constant ALLOWED_BEACON_BALANCE_RELATIVE_DECREASE_POSITION = 0x92ba7776ed6c5d13cf023555a94e70b823a4aebd56ed522a77345ff5cd8a9109; // keccak256("lido.LidoOracle.allowedBeaconBalanceDecrease") - /// This is a dead variable: it was used only in v1 and in upgrade v1 --> v2 - /// Just keep in mind that storage at this position is occupied but with no actual usage + /// This variable is from v1: the last reported epoch, used only in the initializer bytes32 internal constant V1_LAST_REPORTED_EPOCH_ID_POSITION = 0xfe0250ed0c5d8af6526c6d133fccb8e5a55dd6b1aa6696ed0c327f8e517b5a94; // keccak256("lido.LidoOracle.lastReportedEpochId") @@ -336,48 +330,20 @@ contract LidoOracle is ILidoOracle, AragonApp { } /** - * @notice Initialize the contract (version 3 for now) from scratch - * @dev TODO: Add link to the related LIP - * @param _lido Address of Lido contract - * @param _epochsPerFrame Number of epochs per frame - * @param _slotsPerEpoch Number of slots per epoch - * @param _secondsPerSlot Number of seconds per slot - * @param _genesisTime Genesis time - * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) - * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) + * @notice Initialize the contract v2 data, with sanity check bounds + * (`_allowedBeaconBalanceAnnualRelativeIncrease`, `_allowedBeaconBalanceRelativeDecrease`) + * @dev Original initialize function removed from v2 because it is invoked only once */ - function initialize( - address _lido, - uint64 _epochsPerFrame, - uint64 _slotsPerEpoch, - uint64 _secondsPerSlot, - uint64 _genesisTime, + function initialize_v2( uint256 _allowedBeaconBalanceAnnualRelativeIncrease, uint256 _allowedBeaconBalanceRelativeDecrease ) - external onlyInit + external { - assert(1 == ((1 << (MAX_MEMBERS - 1)) >> (MAX_MEMBERS - 1))); // static assert - - // We consider storage state right after deployment (no initialize() called yet) as version 0 - - // Initializations for v0 --> v1 (considering version semantically) - require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "BASE_VERSION_MUST_BE_ZERO"); - - _setBeaconSpec( - _epochsPerFrame, - _slotsPerEpoch, - _secondsPerSlot, - _genesisTime - ); - - LIDO_POSITION.setStorageAddress(_lido); + require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "ALREADY_INITIALIZED"); + CONTRACT_VERSION_POSITION.setStorageUint256(1); + emit ContractVersionSet(1); - QUORUM_POSITION.setStorageUint256(1); - emit QuorumChanged(1); - - - // Initializations for v1 --> v2 (considering version semantically) ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION .setStorageUint256(_allowedBeaconBalanceAnnualRelativeIncrease); emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_allowedBeaconBalanceAnnualRelativeIncrease); @@ -386,39 +352,16 @@ contract LidoOracle is ILidoOracle, AragonApp { .setStorageUint256(_allowedBeaconBalanceRelativeDecrease); emit AllowedBeaconBalanceRelativeDecreaseSet(_allowedBeaconBalanceRelativeDecrease); - // // set expected epoch to the first epoch for the next frame + // set last completed epoch as V1's contract last reported epoch, in the vast majority of + // cases this is true, in others the error is within a frame + uint256 lastReportedEpoch = V1_LAST_REPORTED_EPOCH_ID_POSITION.getStorageUint256(); + LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(lastReportedEpoch); + + // set expected epoch to the first epoch for the next frame BeaconSpec memory beaconSpec = _getBeaconSpec(); - uint256 expectedEpoch = _getFrameFirstEpochId(0, beaconSpec) + beaconSpec.epochsPerFrame; + uint256 expectedEpoch = _getFrameFirstEpochId(lastReportedEpoch, beaconSpec) + beaconSpec.epochsPerFrame; EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); emit ExpectedEpochIdUpdated(expectedEpoch); - - // Initializations for v2 --> v3 (considering version semantically) - _initialize_v3(); - - // Need this despite contract version check because Aragon requires it to handle auth() modificators properly - initialized(); - } - - /** - * @notice A function to finalize upgrade to v3 (from v1). Can be called only once - * @dev Value 2 in CONTRACT_VERSION_POSITION is skipped due to change in numbering - * . For more details see LIP-??? _initialize_v3() - */ - function finalizeUpgrade_v3() external { - require(CONTRACT_VERSION_POSITION.getStorageUint256() == 1, "WRONG_BASE_VERSION"); - - _initialize_v3(); - } - - /** - * @notice A dummy incremental v1/v2 --> v3 initialize function. Just corrects version number in storage - * @dev This function is introduced just to set in correspondence version number in storage, - * semantic version of the contract and number N used in naming of _initialize_nN/finalizeUpgrade_vN. - * NB, that thus version 2 is skipped - */ - function _initialize_v3() { - CONTRACT_VERSION_POSITION.setStorageUint256(3); - emit ContractVersionSet(3); } /** From 3154a143ce3db5ebb90f87a1aede3674abb059c9 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Sun, 30 Jan 2022 23:27:58 +0300 Subject: [PATCH 054/159] cosmetic and docs fixes after review --- SCRATH_DEPLOY.md => SCRATCH_DEPLOY.md | 0 contracts/0.4.24/Lido.sol | 2 +- contracts/0.4.24/interfaces/ILidoOracle.sol | 2 +- contracts/0.4.24/oracle/LidoOracle.sol | 4 ++-- contracts/0.8.9/DepositSecurityModule.sol | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) rename SCRATH_DEPLOY.md => SCRATCH_DEPLOY.md (100%) diff --git a/SCRATH_DEPLOY.md b/SCRATCH_DEPLOY.md similarity index 100% rename from SCRATH_DEPLOY.md rename to SCRATCH_DEPLOY.md diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 3dd0925a1..8ef614744 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -342,7 +342,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { // Don’t mint/distribute any protocol fee on the non-profitable Lido oracle report // (when beacon chain balance delta is zero or negative). - // See ADR #3 for details: https://hackmd.io/Jyvwq8DKSAGjIlCk80YJ7w or https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 + // See ADR #3 for details: https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); distributeRewards(rewards.add(mevRewards)); diff --git a/contracts/0.4.24/interfaces/ILidoOracle.sol b/contracts/0.4.24/interfaces/ILidoOracle.sol index 5b284d1c0..8847dd08d 100644 --- a/contracts/0.4.24/interfaces/ILidoOracle.sol +++ b/contracts/0.4.24/interfaces/ILidoOracle.sol @@ -186,7 +186,7 @@ interface ILidoOracle { /** * @notice Initialize the contract (version 3 for now) from scratch - * @dev TODO: Add link to the related LIP + * @dev For details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md * @param _lido Address of Lido contract * @param _epochsPerFrame Number of epochs per frame * @param _slotsPerEpoch Number of slots per epoch diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 0016aa35a..42399a9af 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -337,7 +337,7 @@ contract LidoOracle is ILidoOracle, AragonApp { /** * @notice Initialize the contract (version 3 for now) from scratch - * @dev TODO: Add link to the related LIP + * @dev For details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md * @param _lido Address of Lido contract * @param _epochsPerFrame Number of epochs per frame * @param _slotsPerEpoch Number of slots per epoch @@ -416,7 +416,7 @@ contract LidoOracle is ILidoOracle, AragonApp { * semantic version of the contract and number N used in naming of _initialize_nN/finalizeUpgrade_vN. * NB, that thus version 2 is skipped */ - function _initialize_v3() { + function _initialize_v3() internal { CONTRACT_VERSION_POSITION.setStorageUint256(3); emit ContractVersionSet(3); } diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 724fc4e73..3cf0098d1 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -74,8 +74,8 @@ contract DepositSecurityModule { uint256 _minDepositBlockDistance, uint256 _pauseIntentValidityPeriodBlocks ) { - require(_lido != address(0x0), "LIDO_ZERO_ADDRESS"); - require(_depositContract != address(0x0), "DEPOSIT_CONTRACT_ZERO_ADDRESS"); + require(_lido != address(0), "LIDO_ZERO_ADDRESS"); + require(_depositContract != address(0), "DEPOSIT_CONTRACT_ZERO_ADDRESS"); LIDO = _lido; DEPOSIT_CONTRACT = _depositContract; From 33d10e716d0657b0d86aa2bf45e47d9436584013 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 31 Jan 2022 00:06:58 +0300 Subject: [PATCH 055/159] MevTxFeeVault: make ERC-20 and ERC-721 recoverable --- contracts/0.8.9/LidoMevTxFeeVault.sol | 69 ++++++++- test/0.4.24/lido.test.js | 19 --- test/0.8.9/lido-mev-tx-fee-vault.js | 202 ++++++++++++++++++++++++++ 3 files changed, 266 insertions(+), 24 deletions(-) create mode 100644 test/0.8.9/lido-mev-tx-fee-vault.js diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index c37cc3051..7f8451c9e 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -2,11 +2,14 @@ // SPDX-License-Identifier: GPL-3.0 - +/* See contracts/COMPILERS.md */ pragma solidity 0.8.9; -interface ILido { +import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC721/IERC721.sol"; + +interface ILido { /** * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract * @dev We need a separate function because funds received by default payable function @@ -27,11 +30,39 @@ interface ILido { */ contract LidoMevTxFeeVault { address public immutable LIDO; + address public immutable TREASURY; - constructor(address _lidoAddress) { - require(_lidoAddress != address(0), "LIDO_ZERO_ADDRESS"); + /** + * Emitted when the ERC20 `token` recovered (e.g. transferred) + * to the Lido treasure address by `requestedBy` sender. + */ + event ERC20Recovered( + address indexed requestedBy, + address indexed token, + uint256 amount + ); + + /** + * Emitted when the ERC721-compatible `token` (NFT) recovered (e.g. transferred) + * to the Lido treasure address by `requestedBy` sender. + */ + event ERC721Recovered( + address indexed requestedBy, + address indexed token, + uint256 tokenId + ); + + /** + * Ctor + * + * @param _lido the Lido token (stETH) address + * @param _treasury the Lido treasury address (see ERC20/ERC721-recovery interfaces) + */ + constructor(address _lido, address _treasury) { + require(_lido != address(0), "LIDO_ZERO_ADDRESS"); - LIDO = _lidoAddress; + LIDO = _lido; + TREASURY = _treasury; } /** @@ -55,4 +86,32 @@ contract LidoMevTxFeeVault { } return amount; } + + /** + * Transfers a given `_amount` of an ERC20-token (defined by the `_token` contract address) + * currently belonging to the burner contract address to the Lido treasury address. + * + * @param _token an ERC20-compatible token + * @param _amount token amount + */ + function recoverERC20(address _token, uint256 _amount) external { + require(_amount > 0, "ZERO_RECOVERY_AMOUNT"); + + emit ERC20Recovered(msg.sender, _token, _amount); + + require(IERC20(_token).transfer(TREASURY, _amount)); + } + + /** + * Transfers a given token_id of an ERC721-compatible NFT (defined by the token contract address) + * currently belonging to the burner contract address to the Lido treasury address. + * + * @param _token an ERC721-compatible token + * @param _tokenId minted token id + */ + function recoverERC721(address _token, uint256 _tokenId) external { + emit ERC721Recovered(msg.sender, _token, _tokenId); + + IERC721(_token).transferFrom(address(this), TREASURY, _tokenId); + } } diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 60f39ab60..579f2c58b 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -204,25 +204,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await app.methods['depositBufferedEther()']({ from: depositor }) } - it('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { - await assertRevert(mevVault.withdrawRewards({ from: user1 }), 'ONLY_LIDO_CAN_WITHDRAW') - await assertRevert(mevVault.withdrawRewards({ from: voting }), 'ONLY_LIDO_CAN_WITHDRAW') - await assertRevert(mevVault.withdrawRewards({ from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') - }) - - it('MEV Tx Fee vault can receive Ether by plain transfers (no call data)', async () => { - const before = +(await web3.eth.getBalance(mevVault.address)).toString() - const amount = 0.02 - await web3.eth.sendTransaction({ to: mevVault.address, from: user2, value: ETH(amount) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(before + amount)) - }) - - it('MEV Tx Fee vault refuses to receive Ether by transfers with call data', async () => { - const before = +(await web3.eth.getBalance(mevVault.address)).toString() - const amount = 0.02 - await assertRevert(web3.eth.sendTransaction({ to: mevVault.address, from: user2, value: ETH(amount), data: '0x12345678' })) - }) - it('MEV distribution works when zero rewards reported', async () => { const depositAmount = 32 const mevAmount = 10 diff --git a/test/0.8.9/lido-mev-tx-fee-vault.js b/test/0.8.9/lido-mev-tx-fee-vault.js new file mode 100644 index 000000000..23504edeb --- /dev/null +++ b/test/0.8.9/lido-mev-tx-fee-vault.js @@ -0,0 +1,202 @@ +const { assertBn, assertRevert, assertEvent, assertAmountOfEvents } = require('@aragon/contract-helpers-test/src/asserts') +const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') +const { newDao, newApp } = require('../0.4.24/helpers/dao') + +const { assert } = require('chai') + +const LidoMevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') + +const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') + +const LidoMock = artifacts.require('LidoMock.sol') +const LidoOracleMock = artifacts.require('OracleMock.sol') +const DepositContractMock = artifacts.require('DepositContractMock.sol') + +const ERC20OZMock = artifacts.require('ERC20OZMock.sol') +const ERC721OZMock = artifacts.require('ERC721OZMock.sol') + +const ETH = (value) => web3.utils.toWei(value + '', 'ether') +// semantic aliases +const stETH = ETH +const stETHShares = ETH + +contract.only('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { + let oracle, lido, mevVault + let treasuryAddr + let dao, acl, operators + + beforeEach('deploy lido with dao', async () => { + const lidoBase = await LidoMock.new({ from: deployer }) + oracle = await LidoOracleMock.new({ from: deployer }) + const depositContract = await DepositContractMock.new({ from: deployer }) + const nodeOperatorsRegistryBase = await NodeOperatorsRegistry.new({ from: deployer }) + + const daoAclObj = await newDao(appManager) + dao = daoAclObj.dao + acl = daoAclObj.acl + + // Instantiate a proxy for the app, using the base contract as its logic implementation. + let proxyAddress = await newApp(dao, 'lido', lidoBase.address, appManager) + lido = await LidoMock.at(proxyAddress) + + // NodeOperatorsRegistry + proxyAddress = await newApp(dao, 'node-operators-registry', nodeOperatorsRegistryBase.address, appManager) + operators = await NodeOperatorsRegistry.at(proxyAddress) + await operators.initialize(lido.address) + + // Init the BURN_ROLE role and assign in to voting + await acl.createPermission(voting, lido.address, await lido.BURN_ROLE(), appManager, { from: appManager }) + + // Initialize the app's proxy. + await lido.initialize(depositContract.address, oracle.address, operators.address) + treasuryAddr = await lido.getInsuranceFund() + + await oracle.setPool(lido.address) + await depositContract.reset() + + mevVault = await LidoMevTxFeeVault.new(lido.address, treasuryAddr, { from: deployer }) + }) + + it('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { + await assertRevert(mevVault.withdrawRewards({ from: anotherAccount }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(mevVault.withdrawRewards({ from: deployer }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(mevVault.withdrawRewards({ from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') + }) + + it('MEV Tx Fee vault can receive Ether by plain transfers (no call data)', async () => { + const before = +(await web3.eth.getBalance(mevVault.address)).toString() + const amount = 0.02 + await web3.eth.sendTransaction({ to: mevVault.address, from: anotherAccount, value: ETH(amount) }) + assertBn(await web3.eth.getBalance(mevVault.address), ETH(before + amount)) + }) + + it('MEV Tx Fee Vault refuses to receive Ether by transfers with call data', async () => { + const before = +(await web3.eth.getBalance(mevVault.address)).toString() + const amount = 0.02 + await assertRevert(web3.eth.sendTransaction({ to: mevVault.address, from: anotherAccount, value: ETH(amount), data: '0x12345678' })) + }) + + describe('Recover ERC20 / ERC721', () => { + let mockERC20Token, mockNFT + let nft1, nft2 + let totalERC20Supply + + beforeEach(async () => { + // setup ERC20 token with total supply 100,000 units + // mint two NFTs + // the deployer solely holds newly created ERC20 and ERC721 items on setup + + nft1 = bn(666) + nft2 = bn(777) + totalERC20Supply = bn(1000000) + + mockERC20Token = await ERC20OZMock.new(totalERC20Supply, { from: deployer }) + + assertBn(await mockERC20Token.totalSupply(), totalERC20Supply) + assertBn(await mockERC20Token.balanceOf(deployer), totalERC20Supply) + + await mockERC20Token.balanceOf(deployer) + + mockNFT = await ERC721OZMock.new({ from: deployer }) + + await mockNFT.mintToken(nft1, { from: deployer }) + await mockNFT.mintToken(nft2, { from: deployer }) + + assertBn(await mockNFT.balanceOf(deployer), bn(2)) + assert.equal(await mockNFT.ownerOf(nft1), deployer) + assert.equal(await mockNFT.ownerOf(nft2), deployer) + }) + + it(`can't recover zero ERC20 amount`, async () => { + assertRevert(mevVault.recoverERC20(mockERC20Token.address, bn(0)), `ZERO_RECOVERY_AMOUNT`) + }) + + it(`can't recover zero-address ERC20`, async () => { + assertRevert(mevVault.recoverERC20(ZERO_ADDRESS, bn(10))) + }) + + it(`can't recover stETH by recoverERC20`, async () => { + // initial stETH balance is zero + assertBn(await lido.balanceOf(anotherAccount), stETH(0)) + // submit 10 ETH to mint 10 stETH + await web3.eth.sendTransaction({ from: anotherAccount, to: lido.address, value: ETH(10) }) + // check 10 stETH minted on balance + assertBn(await lido.balanceOf(anotherAccount), stETH(10)) + // transfer 5 stETH to the mevVault account + await lido.transfer(mevVault.address, stETH(5), { from: anotherAccount }) + + assertBn(await lido.balanceOf(anotherAccount), stETH(5)) + assertBn(await lido.balanceOf(mevVault.address), stETH(5)) + }) + + it(`recover some accidentally sent ERC20`, async () => { + // distribute deployer's balance among anotherAccount and mevVault + await mockERC20Token.transfer(anotherAccount, bn(400000), { from: deployer }) + await mockERC20Token.transfer(mevVault.address, bn(600000), { from: deployer }) + + // check the resulted state + assertBn(await mockERC20Token.balanceOf(deployer), bn(0)) + assertBn(await mockERC20Token.balanceOf(anotherAccount), bn(400000)) + assertBn(await mockERC20Token.balanceOf(mevVault.address), bn(600000)) + + // recover ERC20 + const firstReceipt = await mevVault.recoverERC20(mockERC20Token.address, bn(100000), { from: deployer }) + assertEvent(firstReceipt, `ERC20Recovered`, { + expectedArgs: { requestedBy: deployer, token: mockERC20Token.address, amount: bn(100000) } + }) + + const secondReceipt = await mevVault.recoverERC20(mockERC20Token.address, bn(400000), { from: anotherAccount }) + assertEvent(secondReceipt, `ERC20Recovered`, { + expectedArgs: { requestedBy: anotherAccount, token: mockERC20Token.address, amount: bn(400000) } + }) + + // check balances again + assertBn(await mockERC20Token.balanceOf(mevVault.address), bn(100000)) + assertBn(await mockERC20Token.balanceOf(treasuryAddr), bn(500000)) + assertBn(await mockERC20Token.balanceOf(deployer), bn(0)) + assertBn(await mockERC20Token.balanceOf(anotherAccount), bn(400000)) + + // recover last portion + const lastReceipt = await mevVault.recoverERC20(mockERC20Token.address, bn(100000), { from: anotherAccount }) + assertEvent(lastReceipt, `ERC20Recovered`, { + expectedArgs: { requestedBy: anotherAccount, token: mockERC20Token.address, amount: bn(100000) } + }) + + // balance is zero already, have to be reverted + assertRevert(mevVault.recoverERC20(mockERC20Token.address, bn(1), { from: deployer }), `ERC20: transfer amount exceeds balance`) + }) + + it(`can't recover zero-address ERC721(NFT)`, async () => { + assertRevert(mevVault.recoverERC721(ZERO_ADDRESS, 0)) + }) + + it(`recover some accidentally sent NFTs`, async () => { + // send nft1 to anotherAccount and nft2 to the mevVault address + await mockNFT.transferFrom(deployer, anotherAccount, nft1, { from: deployer }) + await mockNFT.transferFrom(deployer, mevVault.address, nft2, { from: deployer }) + + // check the new holders' rights + assertBn(await mockNFT.balanceOf(deployer), bn(0)) + assertBn(await mockNFT.balanceOf(anotherAccount), bn(1)) + assertBn(await mockNFT.balanceOf(mevVault.address), bn(1)) + + // recover nft2 should work + const receiptNfc2 = await mevVault.recoverERC721(mockNFT.address, nft2, { from: anotherAccount }) + assertEvent(receiptNfc2, `ERC721Recovered`, { expectedArgs: { requestedBy: anotherAccount, token: mockNFT.address, tokenId: nft2 } }) + + // but nft1 recovery should revert + assertRevert(mevVault.recoverERC721(mockNFT.address, nft1), `ERC721: transfer caller is not owner nor approved`) + + // send nft1 to mevVault and recover it + await mockNFT.transferFrom(anotherAccount, mevVault.address, nft1, { from: anotherAccount }) + const receiptNft1 = await mevVault.recoverERC721(mockNFT.address, nft1, { from: deployer }) + + assertEvent(receiptNft1, `ERC721Recovered`, { expectedArgs: { requestedBy: deployer, token: mockNFT.address, tokenId: nft1 } }) + + // check final NFT ownership state + assertBn(await mockNFT.balanceOf(treasuryAddr), bn(2)) + assertBn(await mockNFT.ownerOf(nft1), treasuryAddr) + assertBn(await mockNFT.ownerOf(nft2), treasuryAddr) + }) + }) +}) From d3e5e637619531ce4defee65c67521ad0bf0267d Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 31 Jan 2022 00:37:52 +0300 Subject: [PATCH 056/159] MevTxFeeVault: add counter for rewards via transactions --- contracts/0.8.9/LidoMevTxFeeVault.sol | 8 ++++++++ test/0.8.9/lido-mev-tx-fee-vault.js | 9 ++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 7f8451c9e..2722714b9 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -32,6 +32,13 @@ contract LidoMevTxFeeVault { address public immutable LIDO; address public immutable TREASURY; + /** + * Total amount of rewards received via transactions + * Rewards received on this contract set as coinbase (fee receipient) + * are not counted + */ + uint256 public totalRewardsReceivedViaTransactions; + /** * Emitted when the ERC20 `token` recovered (e.g. transferred) * to the Lido treasure address by `requestedBy` sender. @@ -70,6 +77,7 @@ contract LidoMevTxFeeVault { * @dev MEV rewards may be sent as plain ETH transfers */ receive() external payable { + totalRewardsReceivedViaTransactions = totalRewardsReceivedViaTransactions + msg.value; } /** diff --git a/test/0.8.9/lido-mev-tx-fee-vault.js b/test/0.8.9/lido-mev-tx-fee-vault.js index 23504edeb..74f477478 100644 --- a/test/0.8.9/lido-mev-tx-fee-vault.js +++ b/test/0.8.9/lido-mev-tx-fee-vault.js @@ -63,7 +63,14 @@ contract.only('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, an await assertRevert(mevVault.withdrawRewards({ from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') }) - it('MEV Tx Fee vault can receive Ether by plain transfers (no call data)', async () => { + it('MEV Tx Fee Vault totalRewardsReceivedViaTransactions counter', async () => { + const counterBefore = +(await mevVault.totalRewardsReceivedViaTransactions()).toString() + await web3.eth.sendTransaction({ to: mevVault.address, from: anotherAccount, value: ETH(0.05) }) + console.log({ counterBefore }) + assertBn(await mevVault.totalRewardsReceivedViaTransactions(), ETH(counterBefore + 0.05)) + }) + + it('MEV Tx Fee Vault can receive Ether by plain transfers (no call data)', async () => { const before = +(await web3.eth.getBalance(mevVault.address)).toString() const amount = 0.02 await web3.eth.sendTransaction({ to: mevVault.address, from: anotherAccount, value: ETH(amount) }) From 5a7acd0d96fb30b55f0c543c10a94660a5e05fae Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 31 Jan 2022 00:51:33 +0300 Subject: [PATCH 057/159] fix tests --- test/0.4.24/lido.test.js | 5 ++--- test/0.8.9/lido-mev-tx-fee-vault.js | 2 +- test/scenario/mev_tx_fee_after_the_merge.js | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 579f2c58b..6bf6e831a 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -70,9 +70,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let proxyAddress = await newApp(dao, 'lido', appBase.address, appManager) app = await Lido.at(proxyAddress) - mevVault = await MevTxFeeVault.new(app.address) - rewarder = await RewardEmulatorMock.new(mevVault.address) - // NodeOperatorsRegistry proxyAddress = await newApp(dao, 'node-operators-registry', nodeOperatorsRegistryBase.address, appManager) operators = await NodeOperatorsRegistry.at(proxyAddress) @@ -112,6 +109,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.setPool(app.address) await depositContract.reset() + mevVault = await MevTxFeeVault.new(app.address, treasuryAddr) + rewarder = await RewardEmulatorMock.new(mevVault.address) await app.setMevTxFeeVault(mevVault.address, { from: voting }) }) diff --git a/test/0.8.9/lido-mev-tx-fee-vault.js b/test/0.8.9/lido-mev-tx-fee-vault.js index 74f477478..b785f8d1f 100644 --- a/test/0.8.9/lido-mev-tx-fee-vault.js +++ b/test/0.8.9/lido-mev-tx-fee-vault.js @@ -20,7 +20,7 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') const stETH = ETH const stETHShares = ETH -contract.only('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { +contract('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { let oracle, lido, mevVault let treasuryAddr let dao, acl, operators diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index a1c11a6e4..dc762b2b9 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -101,7 +101,7 @@ contract('Lido: merge acceptance', (addresses) => { depositRoot = await depositContractMock.get_deposit_root() - mevVault = await LidoMevTxFeeVault.new(pool.address) + mevVault = await LidoMevTxFeeVault.new(pool.address, treasuryAddr) await pool.setMevTxFeeVault(mevVault.address, { from: voting }) rewarder = await RewardEmulatorMock.new(mevVault.address) From d6f388793cfb76d79679f7935e7d24f7a23c8915 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 31 Jan 2022 01:17:19 +0300 Subject: [PATCH 058/159] LidoOracle, docs: update link to LIP-10 --- contracts/0.4.24/oracle/LidoOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 42399a9af..ba56e38f8 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -402,7 +402,7 @@ contract LidoOracle is ILidoOracle, AragonApp { /** * @notice A function to finalize upgrade to v3 (from v1). Can be called only once * @dev Value 2 in CONTRACT_VERSION_POSITION is skipped due to change in numbering - * . For more details see LIP-??? _initialize_v3() + * For more details see https://github.com/lidofinance/lido-improvement-proposals/blob/develop/LIPS/lip-10.md */ function finalizeUpgrade_v3() external { require(CONTRACT_VERSION_POSITION.getStorageUint256() == 1, "WRONG_BASE_VERSION"); From fa2e978df57ed30105e5adbca6aa0b2b5ce8732a Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 31 Jan 2022 01:27:09 +0300 Subject: [PATCH 059/159] get rid of useless ISTETH.sol interface --- contracts/0.4.24/StETH.sol | 14 +++++- contracts/0.4.24/interfaces/ILido.sol | 3 -- contracts/0.4.24/interfaces/ISTETH.sol | 69 -------------------------- lib/abi/Lido.json | 2 +- lib/abi/LidoMevTxFeeVault.json | 2 +- lib/abi/LidoOracle.json | 2 +- lib/abi/StETH.json | 2 +- 7 files changed, 16 insertions(+), 78 deletions(-) delete mode 100644 contracts/0.4.24/interfaces/ISTETH.sol diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index e14801e6a..8251034de 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -9,7 +9,6 @@ import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; import "@aragon/os/contracts/common/UnstructuredStorage.sol"; import "@aragon/os/contracts/lib/math/SafeMath.sol"; import "./lib/Pausable.sol"; -import "./interfaces/ISTETH.sol"; /** * @title Interest-bearing ERC20-like token for Lido Liquid Stacking protocol. @@ -48,7 +47,7 @@ import "./interfaces/ISTETH.sol"; * DAO. This is useful for emergency scenarios, e.g. a protocol bug, where one might want * to freeze all token transfers and approvals until the emergency is resolved. */ -contract StETH is IERC20, ISTETH, Pausable { +contract StETH is IERC20, Pausable { using SafeMath for uint256; using UnstructuredStorage for bytes32; @@ -82,6 +81,17 @@ contract StETH is IERC20, ISTETH, Pausable { */ bytes32 internal constant TOTAL_SHARES_POSITION = keccak256("lido.StETH.totalShares"); + /** + * @notice An executed shares transfer from `sender` to `recipient`. + * + * @dev emitted in pair with an ERC20-defined `Transfer` event. + */ + event TransferShares( + address indexed from, + address indexed to, + uint256 sharesValue + ); + /** * @notice An executed stETH burn event * diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index b632a6121..95f5f709b 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -15,9 +15,6 @@ pragma solidity 0.4.24; * It also mints new tokens for rewards generated at the ETH 2.0 side. */ interface ILido { - /** - * @dev From ISTETH interface, because "Interfaces cannot inherit". - */ function totalSupply() external view returns (uint256); function getTotalShares() external view returns (uint256); diff --git a/contracts/0.4.24/interfaces/ISTETH.sol b/contracts/0.4.24/interfaces/ISTETH.sol deleted file mode 100644 index ae627d858..000000000 --- a/contracts/0.4.24/interfaces/ISTETH.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-FileCopyrightText: 2020 Lido - -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.4.24; - - -/** - * @title A liquid version of ETH 2.0 native token - * - * ERC20 token which supports stop/resume mechanics. The token is operated by `ILido`. - * - * Since balances of all token holders change when the amount of total controlled Ether - * changes, this token cannot fully implement ERC20 standard: it only emits `Transfer` - * events upon explicit transfer between holders. In contrast, when Lido oracle reports - * rewards, no Transfer events are generated: doing so would require emitting an event - * for each token holder and thus running an unbounded loop. - */ -interface ISTETH /* is IERC20 */ { - function totalSupply() external view returns (uint256); - - /** - * @notice Stop transfers - */ - function stop() external; - - /** - * @notice Resume transfers - */ - function resume() external; - - /** - * @notice Returns true if the token is stopped - */ - function isStopped() external view returns (bool); - - /** - * @notice An executed shares transfer from `sender` to `recipient`. - * - * @dev emitted in pair with an ERC20-defined `Transfer` event. - */ - event TransferShares( - address indexed from, - address indexed to, - uint256 sharesValue - ); - - event Stopped(); - event Resumed(); - - /** - * @notice Burn is called by Lido contract when a user withdraws their Ether. - * @param _account Account which tokens are to be burnt - * @param _sharesAmount Amount of shares to burn - * @return The total amount of all holders' shares after the shares are burned - */ - function burnShares(address _account, uint256 _sharesAmount) external returns (uint256); - - - function balanceOf(address owner) external view returns (uint256); - - function transfer(address to, uint256 value) external returns (bool); - function transferShares(address to, uint256 sharesValue) external returns (uint256); - - function getTotalShares() external view returns (uint256); - - function getPooledEthByShares(uint256 _sharesAmount) external view returns (uint256); - function getSharesByPooledEth(uint256 _pooledEthAmount) external view returns (uint256); -} diff --git a/lib/abi/Lido.json b/lib/abi/Lido.json index dfda51a97..4b40cf44b 100644 --- a/lib/abi/Lido.json +++ b/lib/abi/Lido.json @@ -1 +1 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveMevTxFee","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalMevTxFeeCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mevTxFeeVault","type":"address"}],"name":"setMevTxFeeVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"mevTxFeeReceiver","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasury","type":"address"}],"name":"TreasurySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"InsuranceFundSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"MevTxFeeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveMevTxFee","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalMevTxFeeCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mevTxFeeVault","type":"address"}],"name":"setMevTxFeeVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"mevTxFeeReceiver","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasury","type":"address"}],"name":"TreasurySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"InsuranceFundSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"MevTxFeeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"}] \ No newline at end of file diff --git a/lib/abi/LidoMevTxFeeVault.json b/lib/abi/LidoMevTxFeeVault.json index 02148657b..f8d1606cc 100644 --- a/lib/abi/LidoMevTxFeeVault.json +++ b/lib/abi/LidoMevTxFeeVault.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_lidoAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawRewards","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_lido","type":"address"},{"internalType":"address","name":"_treasury","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalRewardsReceivedViaTransactions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawRewards","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/lib/abi/LidoOracle.json b/lib/abi/LidoOracle.json index ff643ffd8..d875e1943 100644 --- a/lib/abi/LidoOracle.json +++ b/lib/abi/LidoOracle.json @@ -1 +1 @@ -[{"constant":true,"inputs":[],"name":"getCurrentOraclesReportStatus","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"setAllowedBeaconBalanceAnnualRelativeIncrease","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_QUORUM","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_epochId","type":"uint256"},{"name":"_beaconBalance","type":"uint64"},{"name":"_beaconValidators","type":"uint32"}],"name":"reportBeacon","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllowedBeaconBalanceAnnualRelativeIncrease","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllowedBeaconBalanceRelativeDecrease","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"_initialize_v3","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getExpectedEpochId","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLastCompletedReportDelta","outputs":[{"name":"postTotalPooledEther","type":"uint256"},{"name":"preTotalPooledEther","type":"uint256"},{"name":"timeElapsed","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lido","type":"address"},{"name":"_epochsPerFrame","type":"uint64"},{"name":"_slotsPerEpoch","type":"uint64"},{"name":"_secondsPerSlot","type":"uint64"},{"name":"_genesisTime","type":"uint64"},{"name":"_allowedBeaconBalanceAnnualRelativeIncrease","type":"uint256"},{"name":"_allowedBeaconBalanceRelativeDecrease","type":"uint256"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getLido","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_BEACON_REPORT_RECEIVER","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"finalizeUpgrade_v3","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_MEMBERS","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentFrame","outputs":[{"name":"frameEpochId","type":"uint256"},{"name":"frameStartTime","type":"uint256"},{"name":"frameEndTime","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"getCurrentReportVariant","outputs":[{"name":"beaconBalance","type":"uint64"},{"name":"beaconValidators","type":"uint32"},{"name":"count","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLastCompletedEpochId","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_allowedBeaconBalanceAnnualRelativeIncrease","type":"uint256"},{"name":"_allowedBeaconBalanceRelativeDecrease","type":"uint256"}],"name":"initialize_v2","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"setBeaconReportReceiver","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_BEACON_SPEC","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentEpochId","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_member","type":"address"}],"name":"addOracleMember","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconReportReceiver","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_REPORT_BOUNDARIES","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_quorum","type":"uint256"}],"name":"setQuorum","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getQuorum","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracleMembers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"setAllowedBeaconBalanceRelativeDecrease","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconSpec","outputs":[{"name":"epochsPerFrame","type":"uint64"},{"name":"slotsPerEpoch","type":"uint64"},{"name":"secondsPerSlot","type":"uint64"},{"name":"genesisTime","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_epochsPerFrame","type":"uint64"},{"name":"_slotsPerEpoch","type":"uint64"},{"name":"_secondsPerSlot","type":"uint64"},{"name":"_genesisTime","type":"uint64"}],"name":"setBeaconSpec","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_MEMBERS","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentReportVariantsSize","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_member","type":"address"}],"name":"removeOracleMember","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"AllowedBeaconBalanceAnnualRelativeIncreaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"AllowedBeaconBalanceRelativeDecreaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"callback","type":"address"}],"name":"BeaconReportReceiverSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"member","type":"address"}],"name":"MemberAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"member","type":"address"}],"name":"MemberRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"quorum","type":"uint256"}],"name":"QuorumChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"epochId","type":"uint256"}],"name":"ExpectedEpochIdUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"epochsPerFrame","type":"uint64"},{"indexed":false,"name":"slotsPerEpoch","type":"uint64"},{"indexed":false,"name":"secondsPerSlot","type":"uint64"},{"indexed":false,"name":"genesisTime","type":"uint64"}],"name":"BeaconSpecSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"epochId","type":"uint256"},{"indexed":false,"name":"beaconBalance","type":"uint128"},{"indexed":false,"name":"beaconValidators","type":"uint128"},{"indexed":false,"name":"caller","type":"address"}],"name":"BeaconReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"epochId","type":"uint256"},{"indexed":false,"name":"beaconBalance","type":"uint128"},{"indexed":false,"name":"beaconValidators","type":"uint128"}],"name":"Completed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"postTotalPooledEther","type":"uint256"},{"indexed":false,"name":"preTotalPooledEther","type":"uint256"},{"indexed":false,"name":"timeElapsed","type":"uint256"},{"indexed":false,"name":"totalShares","type":"uint256"}],"name":"PostTotalShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"}] \ No newline at end of file +[{"constant":true,"inputs":[],"name":"getCurrentOraclesReportStatus","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"setAllowedBeaconBalanceAnnualRelativeIncrease","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getVersion","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_QUORUM","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_epochId","type":"uint256"},{"name":"_beaconBalance","type":"uint64"},{"name":"_beaconValidators","type":"uint32"}],"name":"reportBeacon","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllowedBeaconBalanceAnnualRelativeIncrease","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getAllowedBeaconBalanceRelativeDecrease","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getExpectedEpochId","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLastCompletedReportDelta","outputs":[{"name":"postTotalPooledEther","type":"uint256"},{"name":"preTotalPooledEther","type":"uint256"},{"name":"timeElapsed","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_lido","type":"address"},{"name":"_epochsPerFrame","type":"uint64"},{"name":"_slotsPerEpoch","type":"uint64"},{"name":"_secondsPerSlot","type":"uint64"},{"name":"_genesisTime","type":"uint64"},{"name":"_allowedBeaconBalanceAnnualRelativeIncrease","type":"uint256"},{"name":"_allowedBeaconBalanceRelativeDecrease","type":"uint256"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getLido","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_BEACON_REPORT_RECEIVER","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"finalizeUpgrade_v3","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_MEMBERS","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentFrame","outputs":[{"name":"frameEpochId","type":"uint256"},{"name":"frameStartTime","type":"uint256"},{"name":"frameEndTime","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_index","type":"uint256"}],"name":"getCurrentReportVariant","outputs":[{"name":"beaconBalance","type":"uint64"},{"name":"beaconValidators","type":"uint32"},{"name":"count","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getLastCompletedEpochId","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_addr","type":"address"}],"name":"setBeaconReportReceiver","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_BEACON_SPEC","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentEpochId","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_member","type":"address"}],"name":"addOracleMember","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconReportReceiver","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_REPORT_BOUNDARIES","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_quorum","type":"uint256"}],"name":"setQuorum","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getQuorum","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracleMembers","outputs":[{"name":"","type":"address[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_value","type":"uint256"}],"name":"setAllowedBeaconBalanceRelativeDecrease","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconSpec","outputs":[{"name":"epochsPerFrame","type":"uint64"},{"name":"slotsPerEpoch","type":"uint64"},{"name":"secondsPerSlot","type":"uint64"},{"name":"genesisTime","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_epochsPerFrame","type":"uint64"},{"name":"_slotsPerEpoch","type":"uint64"},{"name":"_secondsPerSlot","type":"uint64"},{"name":"_genesisTime","type":"uint64"}],"name":"setBeaconSpec","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MAX_MEMBERS","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getCurrentReportVariantsSize","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_member","type":"address"}],"name":"removeOracleMember","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"AllowedBeaconBalanceAnnualRelativeIncreaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"value","type":"uint256"}],"name":"AllowedBeaconBalanceRelativeDecreaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"callback","type":"address"}],"name":"BeaconReportReceiverSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"member","type":"address"}],"name":"MemberAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"member","type":"address"}],"name":"MemberRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"quorum","type":"uint256"}],"name":"QuorumChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"epochId","type":"uint256"}],"name":"ExpectedEpochIdUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"epochsPerFrame","type":"uint64"},{"indexed":false,"name":"slotsPerEpoch","type":"uint64"},{"indexed":false,"name":"secondsPerSlot","type":"uint64"},{"indexed":false,"name":"genesisTime","type":"uint64"}],"name":"BeaconSpecSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"epochId","type":"uint256"},{"indexed":false,"name":"beaconBalance","type":"uint128"},{"indexed":false,"name":"beaconValidators","type":"uint128"},{"indexed":false,"name":"caller","type":"address"}],"name":"BeaconReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"epochId","type":"uint256"},{"indexed":false,"name":"beaconBalance","type":"uint128"},{"indexed":false,"name":"beaconValidators","type":"uint128"}],"name":"Completed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"postTotalPooledEther","type":"uint256"},{"indexed":false,"name":"preTotalPooledEther","type":"uint256"},{"indexed":false,"name":"timeElapsed","type":"uint256"},{"indexed":false,"name":"totalShares","type":"uint256"}],"name":"PostTotalShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"}] \ No newline at end of file diff --git a/lib/abi/StETH.json b/lib/abi/StETH.json index 84dc022c2..9179ad43d 100644 --- a/lib/abi/StETH.json +++ b/lib/abi/StETH.json @@ -1 +1 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file +[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file From 0b983b9115b342c3a91160bfefcb8933e83ffad5 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 31 Jan 2022 10:43:05 +0300 Subject: [PATCH 060/159] update LidoTemplate.sol after merging initialize branch --- contracts/0.4.24/template/LidoTemplate.sol | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 64d9e5f4b..93160b89b 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -359,16 +359,15 @@ contract LidoTemplate is IsContract { noInit )); - // TODO: uncomment on merge of branch 'redo-oracle-initialization' - // state.oracle.initialize( - // state.lido, - // _beaconSpec[0], // epochsPerFrame - // _beaconSpec[1], // slotsPerEpoch - // _beaconSpec[2], // secondsPerSlot - // _beaconSpec[3], // genesisTime - // 100000, - // 50000 - // ); + state.oracle.initialize( + state.lido, + _beaconSpec[0], // epochsPerFrame + _beaconSpec[1], // slotsPerEpoch + _beaconSpec[2], // secondsPerSlot + _beaconSpec[3], // genesisTime + 100000, + 50000 + ); state.operators.initialize(state.lido); From 0c8b75b7f8707216e2466a4b76a9746b43bd3bb2 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 31 Jan 2022 13:26:05 +0300 Subject: [PATCH 061/159] Lido.sol: remove an obsolete line --- contracts/0.4.24/Lido.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 8ef614744..9de5b964a 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -82,8 +82,6 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant BEACON_BALANCE_POSITION = keccak256("lido.Lido.beaconBalance"); /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); - /// @dev total amount of Ether MEV and transaction rewards received by Lido contract - bytes32 internal constant MEV_TX_FEE_ETHER_POSITION = keccak256("lido.Lido.mevTxFeeEther"); /// @dev Just a counter of total amount of MEV and transaction rewards received by Lido contract /// Not used in the logic From 801d3e854efb33ff33a59fe51187e187047a6be2 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 31 Jan 2022 13:39:27 +0300 Subject: [PATCH 062/159] ILido.sol: add event `LidoMevTxFeeVaultSet` --- contracts/0.4.24/Lido.sol | 2 ++ contracts/0.4.24/interfaces/ILido.sol | 10 +++++++--- test/0.4.24/lido.test.js | 3 ++- 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 9de5b964a..7fa1b47d5 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -286,6 +286,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { function setMevTxFeeVault(address _mevTxFeeVault) external auth(SET_MEV_TX_FEE_VAULT_ROLE) { require(isContract(_mevTxFeeVault), "NOT_A_CONTRACT"); MEV_TX_FEE_VAULT_POSITION.setStorageAddress(_mevTxFeeVault); + + emit LidoMevTxFeeVaultSet(_mevTxFeeVault); } /** diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 95f5f709b..a8132d079 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -98,11 +98,9 @@ interface ILido { */ function receiveMevTxFee() external payable; - // The amount of ETH withdrawn from LidoMevTxFeeVault contract to Lido contract event MevTxFeeReceived(uint256 amount); - /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. @@ -116,9 +114,15 @@ interface ILido { */ function getWithdrawalCredentials() external view returns (bytes); - event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); + /** + * @dev Sets given address as the address of LidoMevTxFeeVault contract + * @param _mevTxFeeVault MEV and Tx Fees Vault contract address + */ + function setMevTxFeeVault(address _mevTxFeeVault) external; + + event LidoMevTxFeeVaultSet(address mevTxFeeVault); /** * @notice Ether on the ETH 2.0 side reported by the oracle diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 6bf6e831a..ae5b6918a 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -111,7 +111,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) mevVault = await MevTxFeeVault.new(app.address, treasuryAddr) rewarder = await RewardEmulatorMock.new(mevVault.address) - await app.setMevTxFeeVault(mevVault.address, { from: voting }) + const receipt = await app.setMevTxFeeVault(mevVault.address, { from: voting }) + assertEvent(receipt, 'LidoMevTxFeeVaultSet', { expectedArgs: { mevTxFeeVault: mevVault.address } }) }) const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { From a70083eeaf7fdc6a1b52f9121a3c9cb280e8d5ea Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 2 Feb 2022 21:39:15 +0300 Subject: [PATCH 063/159] Lido app: add line address of MevTxFeeVault --- apps/lido/app/src/App.js | 5 +++++ apps/lido/app/src/index.js | 1 + apps/lido/app/src/script.js | 5 +++++ 3 files changed, 11 insertions(+) diff --git a/apps/lido/app/src/App.js b/apps/lido/app/src/App.js index 04203f3ac..7433d99f3 100644 --- a/apps/lido/app/src/App.js +++ b/apps/lido/app/src/App.js @@ -85,6 +85,7 @@ export default function App() { nodeOperatorsRegistry, depositContract, oracle, + mevTxFeeVault, // operators, // treasury, // insuranceFund, @@ -249,6 +250,10 @@ export default function App() { label: 'Oracle', content: , }, + { + label: 'MEV and Transaction Fees Vault', + content: , + }, ] }, [ appState, diff --git a/apps/lido/app/src/index.js b/apps/lido/app/src/index.js index 036e81d37..79147276e 100644 --- a/apps/lido/app/src/index.js +++ b/apps/lido/app/src/index.js @@ -22,6 +22,7 @@ const defaultState = { nodeOperatorsRegistry: defaultValue, depositContract: defaultValue, oracle: defaultValue, + mevTxFeeVault: defaultValue, operators: defaultValue, treasury: defaultValue, insuranceFund: defaultValue, diff --git a/apps/lido/app/src/script.js b/apps/lido/app/src/script.js index 437e61a7c..3b1f725e5 100644 --- a/apps/lido/app/src/script.js +++ b/apps/lido/app/src/script.js @@ -62,6 +62,7 @@ function initializeState() { nodeOperatorsRegistry: await getNodeOperatorsRegistry(), depositContract: await getDepositContract(), oracle: await getOracle(), + mevTxFeeVault: await getMevTxFeeVault(), // operators: await getOperators(), // treasury: await getTreasury(), // insuranceFund: await getInsuranceFund(), @@ -107,6 +108,10 @@ function getOracle() { return app.call('getOracle').toPromise() } +function getMevTxFeeVault() { + return app.call('getMevTxFeeVault').toPromise() +} + // async function getOperators() { // return await app.call('getOperators').toPromise() // } From 66850e2f7cdbba155ef4ae2a1e86e7121f93291b Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 21 Feb 2022 14:33:30 +0300 Subject: [PATCH 064/159] fixes after review by skozin --- contracts/0.4.24/oracle/LidoOracle.sol | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index ba56e38f8..1f9481d0a 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -14,6 +14,7 @@ import "../interfaces/ILidoOracle.sol"; import "./ReportUtils.sol"; + /** * @title Implementation of an ETH 2.0 -> ETH oracle * @@ -75,9 +76,9 @@ contract LidoOracle is ILidoOracle, AragonApp { /// Version of the initialized contract data /// NB: Contract versioning starts from 1. - /// This version sotred in CONTRACT_VERSION_POSITION equals to + /// The version stored in CONTRACT_VERSION_POSITION equals to /// - 0 right after deployment when no initializer is invoked yet - /// - N after calling initialize() during deployment from scratch, where N is the current contract version + /// - N after calling initialize() during deployment from scratch, where N is the current contract version /// - N after upgrading contract from the previous version (after calling finalize_vN()) bytes32 internal constant CONTRACT_VERSION_POSITION = 0x75be19a3f314d89bd1f84d30a6c84e2f1cd7afc7b6ca21876564c265113bb7e4; // keccak256("lido.LidoOracle.contractVersion") @@ -343,8 +344,8 @@ contract LidoOracle is ILidoOracle, AragonApp { * @param _slotsPerEpoch Number of slots per epoch * @param _secondsPerSlot Number of seconds per slot * @param _genesisTime Genesis time - * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% yearly increase) - * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance moment descreat (e.g. 500 means 5% moment decrease) + * @param _allowedBeaconBalanceAnnualRelativeIncrease Allowed beacon balance annual relative increase (e.g. 1000 means 10% increase) + * @param _allowedBeaconBalanceRelativeDecrease Allowed beacon balance instantaneous decrease (e.g. 500 means 5% decrease) */ function initialize( address _lido, @@ -361,7 +362,7 @@ contract LidoOracle is ILidoOracle, AragonApp { // We consider storage state right after deployment (no initialize() called yet) as version 0 - // Initializations for v0 --> v1 (considering version semantically) + // Initializations for v0 --> v1 require(CONTRACT_VERSION_POSITION.getStorageUint256() == 0, "BASE_VERSION_MUST_BE_ZERO"); _setBeaconSpec( @@ -376,8 +377,7 @@ contract LidoOracle is ILidoOracle, AragonApp { QUORUM_POSITION.setStorageUint256(1); emit QuorumChanged(1); - - // Initializations for v1 --> v2 (considering version semantically) + // Initializations for v1 --> v2 ALLOWED_BEACON_BALANCE_ANNUAL_RELATIVE_INCREASE_POSITION .setStorageUint256(_allowedBeaconBalanceAnnualRelativeIncrease); emit AllowedBeaconBalanceAnnualRelativeIncreaseSet(_allowedBeaconBalanceAnnualRelativeIncrease); @@ -386,16 +386,16 @@ contract LidoOracle is ILidoOracle, AragonApp { .setStorageUint256(_allowedBeaconBalanceRelativeDecrease); emit AllowedBeaconBalanceRelativeDecreaseSet(_allowedBeaconBalanceRelativeDecrease); - // // set expected epoch to the first epoch for the next frame + // set expected epoch to the first epoch for the next frame BeaconSpec memory beaconSpec = _getBeaconSpec(); uint256 expectedEpoch = _getFrameFirstEpochId(0, beaconSpec) + beaconSpec.epochsPerFrame; EXPECTED_EPOCH_ID_POSITION.setStorageUint256(expectedEpoch); emit ExpectedEpochIdUpdated(expectedEpoch); - // Initializations for v2 --> v3 (considering version semantically) + // Initializations for v2 --> v3 _initialize_v3(); - // Need this despite contract version check because Aragon requires it to handle auth() modificators properly + // Needed to finish the Aragon part of initialization (otherwise auth() modifiers will fail) initialized(); } From 2f49e4b16eb5610f1397b6901dea15aa49e3fa86 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 21 Feb 2022 14:40:31 +0300 Subject: [PATCH 065/159] audit: fix MEDIUM-1 --- contracts/0.4.24/Lido.sol | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 7fa1b47d5..480ef0f20 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -131,16 +131,6 @@ contract Lido is ILido, IsContract, StETH, AragonApp { _submit(0); } - /** - * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract - * @dev We need a separate function because funds received by default payable function - * will go through entire deposit algorithm - */ - function mevTxFeeReceiver() external payable { - require(msg.sender == MEV_TX_FEE_VAULT_POSITION.getStorageAddress()); - emit MevTxFeeReceived(msg.value); - } - /** * @notice Send funds to the pool with optional _referral parameter * @dev This function is alternative way to submit funds. Supports optional referral address. From c8b065c1b8e8aa39164bd6edb594254d5ffa3a12 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 21 Feb 2022 14:46:52 +0300 Subject: [PATCH 066/159] audit: fix MEDIUM-2 --- contracts/0.8.9/LidoMevTxFeeVault.sol | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 2722714b9..5d78dc328 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -25,7 +25,7 @@ interface ILido { * This contract has no payable functions because it's balance is supposed to be * increased directly by ethereum protocol when transaction priority fees and extracted MEV * rewards are earned by a validator. -* These vault replenishments happen continuously throught a day, while withdrawals +* These vault replenishments happen continuously through a day, while withdrawals * happen much less often, only on LidoOracle beacon balance reports */ contract LidoMevTxFeeVault { @@ -34,7 +34,7 @@ contract LidoMevTxFeeVault { /** * Total amount of rewards received via transactions - * Rewards received on this contract set as coinbase (fee receipient) + * Rewards received on this contract set as coinbase (fee recipient) * are not counted */ uint256 public totalRewardsReceivedViaTransactions; @@ -67,6 +67,7 @@ contract LidoMevTxFeeVault { */ constructor(address _lido, address _treasury) { require(_lido != address(0), "LIDO_ZERO_ADDRESS"); + require(_treasury != address(0), "TREASURY_ZERO_ADDRESS"); LIDO = _lido; TREASURY = _treasury; From 9985ffbf1199311295d6ac2555e9c23a9ca2971e Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 21 Feb 2022 14:56:24 +0300 Subject: [PATCH 067/159] audit: fix LOW-1 --- contracts/0.4.24/Lido.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 480ef0f20..87d24be7c 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -327,7 +327,9 @@ contract Lido is ILido, IsContract, StETH, AragonApp { address mevVaultAddress = getMevTxFeeVault(); if (mevVaultAddress != address(0)) { mevRewards = ILidoMevTxFeeVault(mevVaultAddress).withdrawRewards(); - BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevRewards)); + if (mevRewards != 0) { + BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevRewards)); + } } // Don’t mint/distribute any protocol fee on the non-profitable Lido oracle report From fdd6b7a78dedd1312c734c434f9df8d97b1dabe6 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 21 Feb 2022 14:57:24 +0300 Subject: [PATCH 068/159] audit: fix LOW-2 --- contracts/0.8.9/DepositSecurityModule.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 3cf0098d1..c289bef66 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -201,8 +201,10 @@ contract DepositSecurityModule { function _setMinDepositBlockDistance(uint256 newValue) internal { require(newValue > 0, "invalid value for minDepositBlockDistance: must be greater then 0"); - minDepositBlockDistance = newValue; - emit MinDepositBlockDistanceChanged(newValue); + if (newValue != minDepositBlockDistance) { + minDepositBlockDistance = newValue; + emit MinDepositBlockDistanceChanged(newValue); + } } From 6adfa826f00a5f09b48bf0353ccf441edab3a62f Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 21 Feb 2022 15:04:48 +0300 Subject: [PATCH 069/159] audit: fix LOW-4 --- contracts/0.4.24/Lido.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 87d24be7c..3e856c408 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -95,6 +95,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * @param _depositContract official ETH2 Deposit contract * @param _oracle oracle contract * @param _operators instance of Node Operators Registry + * @param _treasury contract which accumulates treasury fee + * @param _insuranceFund contract which accumulates insurance fee */ function initialize( IDepositContract _depositContract, From c0ad70b3acb9d1d971cb6e7d911607d387252a8e Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 21 Feb 2022 15:09:20 +0300 Subject: [PATCH 070/159] audit: fix LOW-5 --- contracts/0.4.24/Lido.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 3e856c408..5d9fdf78e 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -110,7 +110,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { require(isContract(address(_operators)), "NOT_A_CONTRACT"); require(isContract(address(_depositContract)), "NOT_A_CONTRACT"); - NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(_operators); + NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(address(_operators)); DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_depositContract)); _setOracle(_oracle); From 2c592fe8a370db48f264c0dcca0aeace549905ef Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 21 Feb 2022 15:51:27 +0000 Subject: [PATCH 071/159] audit: fix MEDIUM-3 Utilize MAX_MEMBERS constant properly with the `LidoOracle.addOracleMember` code. --- contracts/0.4.24/oracle/LidoOracle.sol | 5 +++-- test/0.4.24/lidooracle.test.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index 1f9481d0a..bbddfc91c 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -414,7 +414,7 @@ contract LidoOracle is ILidoOracle, AragonApp { * @notice A dummy incremental v1/v2 --> v3 initialize function. Just corrects version number in storage * @dev This function is introduced just to set in correspondence version number in storage, * semantic version of the contract and number N used in naming of _initialize_nN/finalizeUpgrade_vN. - * NB, that thus version 2 is skipped + * NB, that thus version 2 is skipped */ function _initialize_v3() internal { CONTRACT_VERSION_POSITION.setStorageUint256(3); @@ -427,9 +427,10 @@ contract LidoOracle is ILidoOracle, AragonApp { function addOracleMember(address _member) external auth(MANAGE_MEMBERS) { require(address(0) != _member, "BAD_ARGUMENT"); require(MEMBER_NOT_FOUND == _getMemberId(_member), "MEMBER_EXISTS"); + require(members.length < MAX_MEMBERS, "TOO_MANY_MEMBERS"); members.push(_member); - require(members.length < MAX_MEMBERS, "TOO_MANY_MEMBERS"); + emit MemberAdded(_member); } diff --git a/test/0.4.24/lidooracle.test.js b/test/0.4.24/lidooracle.test.js index 1baaec3a0..ce54d1535 100644 --- a/test/0.4.24/lidooracle.test.js +++ b/test/0.4.24/lidooracle.test.js @@ -2,6 +2,7 @@ const { assert } = require('chai') const { newDao, newApp } = require('./helpers/dao') const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') const { toBN } = require('../helpers/utils') +const keccak256 = require('js-sha3').keccak_256 const LidoOracle = artifacts.require('LidoOracleMock.sol') const Lido = artifacts.require('LidoMockForOracle.sol') @@ -81,6 +82,8 @@ contract('LidoOracle', ([appManager, voting, user1, user2, user3, user4, user5, assertBn(beaconSpec.genesisTime, 1) }) describe('Test utility functions:', function () { + this.timeout(60000) // addOracleMember edge-case is heavy on execution time + beforeEach(async () => { await app.setTime(GENESIS_TIME) }) @@ -100,6 +103,18 @@ contract('LidoOracle', ([appManager, voting, user1, user2, user3, user4, user5, await assertRevert(app.addOracleMember(user2, { from: voting }), 'MEMBER_EXISTS') }) + it('addOracleMember edge-case', async () => { + const promises = [] + const maxMembersCount = await app.MAX_MEMBERS() + for (let i = 0; i < maxMembersCount; ++i) { + const addr = '0x' + keccak256('member' + i).substring(0, 40) + promises.push(app.addOracleMember(addr, { from: voting })) + } + await Promise.all(promises) + + assertRevert(app.addOracleMember(user4, { from: voting }), 'TOO_MANY_MEMBERS') + }) + it('removeOracleMember works', async () => { await app.addOracleMember(user1, { from: voting }) From ea5e232231483f8b0cfa630036f05d347680402c Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 21 Feb 2022 18:24:56 +0000 Subject: [PATCH 072/159] audit: fix MEDIUM-4 Add transferERC721ToVault function for the `Lido` contract. Introduce the `RecoverERC721ToVault` event. Add tests to cover new code. --- contracts/0.4.24/Lido.sol | 26 +++++++++++++++++++ contracts/0.4.24/interfaces/ILido.sol | 3 +++ .../0.4.24/nos/test_helpers/ERC721Mock.sol | 15 +++++++++++ test/0.4.24/lido.test.js | 18 +++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 contracts/0.4.24/nos/test_helpers/ERC721Mock.sol diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 5d9fdf78e..e6442fd00 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -19,6 +19,15 @@ import "./interfaces/ILidoMevTxFeeVault.sol"; import "./StETH.sol"; +interface IERC721 { + /// @notice Transfer ownership of an NFT + /// @param _from The current owner of the NFT + /// @param _to The new owner + /// @param _tokenId The NFT to transfer + function transferFrom(address _from, address _to, uint256 _tokenId) external payable; +} + + /** * @title Liquid staking pool implementation * @@ -367,6 +376,23 @@ contract Lido is ILido, IsContract, StETH, AragonApp { emit RecoverToVault(vault, _token, balance); } + /** + * @notice Send NTFs to recovery Vault. + * @param _token Token to be sent to recovery vault. + * @param _tokenId Token Id + */ + function transferERC721ToVault(address _token, uint256 _tokenId) external { + require(_token != address(0), "ZERO_ADDRESS"); + require(allowRecoverability(_token), "RECOVER_DISALLOWED"); + + address vault = getRecoveryVault(); + require(isContract(vault), "RECOVER_VAULT_NOT_CONTRACT"); + + IERC721(_token).transferFrom(address(this), vault, _tokenId); + + emit RecoverERC721ToVault(vault, _token, _tokenId); + } + /** * @notice Returns staking rewards fee rate */ diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index a8132d079..d8ce77700 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -178,4 +178,7 @@ interface ILido { * @return beaconBalance - total amount of Beacon-side Ether (sum of all the balances of Lido validators) */ function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); + + // Requested ERC721 recovery from the `Lido` to the designated `recoveryVault` vault. + event RecoverERC721ToVault(address indexed vault, address indexed token, uint256 tokenId); } diff --git a/contracts/0.4.24/nos/test_helpers/ERC721Mock.sol b/contracts/0.4.24/nos/test_helpers/ERC721Mock.sol new file mode 100644 index 000000000..e3f85f44a --- /dev/null +++ b/contracts/0.4.24/nos/test_helpers/ERC721Mock.sol @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.4.24; + +import "openzeppelin-solidity/contracts/token/ERC721/ERC721.sol"; + +contract ERC721Mock is ERC721 { + constructor() ERC721() {} + + function mint(address _account, uint256 _tokenId) public { + _mint(_account, _tokenId); + } +} diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index ae5b6918a..e41a1eeda 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -16,6 +16,7 @@ const MevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') +const ERC721Mock = artifacts.require('ERC721Mock.sol') const VaultMock = artifacts.require('AragonVaultMock.sol') const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') @@ -1244,12 +1245,18 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) context('recovery vault', () => { + let nftToken + beforeEach(async () => { await anyToken.mint(app.address, 100) + + nftToken = await ERC721Mock.new() + await nftToken.mint(app.address, 777) }) it('reverts when vault is not set', async () => { await assertRevert(app.transferToVault(anyToken.address, { from: nobody }), 'RECOVER_VAULT_NOT_CONTRACT') + await assertRevert(app.transferERC721ToVault(nftToken.address, 777, { from: nobody }), 'RECOVER_VAULT_NOT_CONTRACT') }) context('recovery works with vault mock deployed', () => { @@ -1272,6 +1279,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'RecoverToVault', { expectedArgs: { vault: vault.address, token: anyToken.address, amount: 100 } }) }) + it('recovery with nft tokens works and emits event', async () => { + await assertRevert(app.transferERC721ToVault(ZERO_ADDRESS, 777, { from: nobody })) + + assert.equal(await nftToken.ownerOf(777), app.address) + + const receipt = await app.transferERC721ToVault(nftToken.address, 777, { from: nobody }) + assertEvent(receipt, 'RecoverERC721ToVault', { expectedArgs: { vault: vault.address, token: nftToken.address, tokenId: 777 } }) + + assert.equal(await nftToken.ownerOf(777), vault.address) + }) + it('recovery with unaccounted ether works and emits event', async () => { await app.makeUnaccountedEther({ from: user1, value: ETH(10) }) const receipt = await app.transferToVault(ZERO_ADDRESS, { from: nobody }) From f8b4b96e734ef392945bf41a4f657df227828085 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 21 Feb 2022 21:13:35 +0000 Subject: [PATCH 073/159] audit: fix MEDIUM-5 Disambiguate burn event name (`StETHBurnt` -> `SharesBurnt`). Update tests. --- contracts/0.4.24/StETH.sol | 17 +++++++++-------- test/0.4.24/lido.test.js | 4 ++-- test/0.4.24/steth.test.js | 6 +++--- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 8251034de..689e1347e 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -93,16 +93,17 @@ contract StETH is IERC20, Pausable { ); /** - * @notice An executed stETH burn event + * @notice An executed `burnShares` request * - * @dev Reports simultaneously stETH amount and shares amount. - * The stETH amount is calculated before the burning incurred rebase. + * @dev Reports simultaneously burnt shares amount + * and corresponding stETH amount. + * The stETH amount is calculated just before the burning incurred rebase. * - * @param account holder of the burnt stETH - * @param amount amount of burnt stETH - * @param sharesAmount amount of burnt shares + * @param account holder of the burnt shares + * @param amount amount of burnt shares (expressed in stETH just before burning invocation) + * @param sharesAmount amount of burnt shares (expressed in shares themselves) */ - event StETHBurnt( + event SharesBurnt( address indexed account, uint256 amount, uint256 sharesAmount @@ -458,7 +459,7 @@ contract StETH is IERC20, Pausable { require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE"); uint256 amount = getPooledEthByShares(_sharesAmount); - emit StETHBurnt(_account, amount, _sharesAmount); + emit SharesBurnt(_account, amount, _sharesAmount); newTotalShares = _getTotalShares().sub(_sharesAmount); TOTAL_SHARES_POSITION.setStorageUint256(newTotalShares); diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index e41a1eeda..ff99e8025 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -1186,11 +1186,11 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) // voting can burn shares of any user const expectedAmount = await app.getPooledEthByShares(ETH(0.5)) let receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) - assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: ETH(0.5) } }) + assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: ETH(0.5) } }) const expectedDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) - assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: expectedDoubledAmount, sharesAmount: ETH(0.5) } }) + assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedDoubledAmount, sharesAmount: ETH(0.5) } }) assertBn(expectedAmount.mul(bn(2)), expectedDoubledAmount) assertBn(tokens(0), await app.getPooledEthByShares(ETH(0.5))) diff --git a/test/0.4.24/steth.test.js b/test/0.4.24/steth.test.js index 9893f33f6..4ccf0455e 100644 --- a/test/0.4.24/steth.test.js +++ b/test/0.4.24/steth.test.js @@ -370,7 +370,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { it('burning zero value works', async () => { const receipt = await stEth.burnShares(user1, tokens(0)) - assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: tokens(0), sharesAmount: tokens(0) } }) + assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: tokens(0), sharesAmount: tokens(0) } }) assertBn(await stEth.totalSupply(), tokens(300)) assertBn(await stEth.balanceOf(user1), tokens(100)) @@ -394,7 +394,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { const expectedAmount = await stEth.getPooledEthByShares(sharesToBurn) const receipt = await stEth.burnShares(user1, sharesToBurn) - assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) + assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) assertBn(await stEth.totalSupply(), tokens(300)) assertBn(await stEth.balanceOf(user1), bn(tokens(90)).subn(1)) // expected round error @@ -420,7 +420,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { const expectedAmount = await stEth.getPooledEthByShares(sharesToBurn) const receipt = await stEth.burnShares(user1, sharesToBurn) - assertEvent(receipt, 'StETHBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) + assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) assertBn(await stEth.balanceOf(user1), tokens(50)) From fb8bafe3ef9ae94b79c46162982aa35927aed81d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 21 Feb 2022 22:27:19 +0000 Subject: [PATCH 074/159] audit: fix LOW-3 issue Enforce node operators limit programatically. Add tests. --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 6 ++++-- test/0.4.24/node-operators-registry.test.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 5b304435f..12295964c 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -20,7 +20,7 @@ import "../lib/MemUtils.sol"; * * See the comment of `INodeOperatorsRegistry`. * - * NOTE: the code below assumes moderate amount of node operators, e.g. up to 50. + * NOTE: the code below assumes moderate amount of node operators, i.e. up to 50 (MAX_NODE_OPERATORS_COUNT). */ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp { using SafeMath for uint256; @@ -38,12 +38,12 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp uint256 constant public PUBKEY_LENGTH = 48; uint256 constant public SIGNATURE_LENGTH = 96; + uint256 constant public MAX_NODE_OPERATORS_COUNT = 50; uint256 internal constant UINT64_MAX = uint256(uint64(-1)); bytes32 internal constant SIGNING_KEYS_MAPPING_NAME = keccak256("lido.NodeOperatorsRegistry.signingKeysMappingName"); - /// @dev Node Operator parameters and internal state struct NodeOperator { bool active; // a flag indicating if the operator can participate in further staking and reward distribution @@ -118,6 +118,8 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp returns (uint256 id) { id = getNodeOperatorsCount(); + require(id < MAX_NODE_OPERATORS_COUNT, "MAX_NODE_OPERATORS_COUNT_EXCEEDED"); + TOTAL_OPERATORS_COUNT_POSITION.setStorageUint256(id.add(1)); NodeOperator storage operator = operators[id]; diff --git a/test/0.4.24/node-operators-registry.test.js b/test/0.4.24/node-operators-registry.test.js index 2273990ff..3941afd88 100644 --- a/test/0.4.24/node-operators-registry.test.js +++ b/test/0.4.24/node-operators-registry.test.js @@ -3,6 +3,7 @@ const { hexSplit, toBN } = require('../helpers/utils') const { newDao, newApp } = require('./helpers/dao') const { ZERO_ADDRESS, getEventAt, getEventArgument } = require('@aragon/contract-helpers-test') const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') +const keccak256 = require('js-sha3').keccak_256 const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry.sol') const PoolMock = artifacts.require('PoolMock.sol') @@ -87,6 +88,20 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, nob await assertRevert(app.addNodeOperator('1', ADDRESS_3, { from: nobody }), 'APP_AUTH_FAILED') }) + it('addNodeOperator limit works', async () => { + const maxOperatorsCount = await app.MAX_NODE_OPERATORS_COUNT() + const currentOperatorsCount = await app.getNodeOperatorsCount() + + for (let opIndex = currentOperatorsCount; opIndex < maxOperatorsCount; opIndex++) { + const name = keccak256('op' + opIndex) + const addr = '0x' + name.substr(0, 40) + + await app.addNodeOperator(name, addr, { from: voting }) + } + + await assertRevert(app.addNodeOperator('L', ADDRESS_4, { from: voting }), 'MAX_NODE_OPERATORS_COUNT_EXCEEDED') + }) + it('getNodeOperator works', async () => { await app.addNodeOperator('fo o', ADDRESS_1, { from: voting }) await app.addNodeOperator(' bar', ADDRESS_2, { from: voting }) From 9babc852be219b711a8ff73b11cf6dcb7a9d6261 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 22 Feb 2022 14:47:22 +0000 Subject: [PATCH 075/159] audit: fix MEDIUM-7 Stich beacon report callbacks with ERC165 checks. --- contracts/0.4.24/oracle/LidoOracle.sol | 10 ++++++++++ .../test_helpers/BeaconReportReceiverMock.sol | 18 +++++++++++++++-- .../CompositePostRebaseBeaconReceiver.sol | 14 ++++++++++--- contracts/0.8.9/OrderedCallbacksArray.sol | 11 +++++++++- contracts/0.8.9/SelfOwnedStETHBurner.sol | 18 +++++++++++++---- .../interfaces/ISelfOwnedStETHBurner.sol | 20 +++++++++++++++++++ .../0.8.9/test_helpers/BeaconReceiverMock.sol | 18 +++++++++++++++-- test/0.4.24/lidooracle.test.js | 6 +++++- ...posite-post-rebase-beacon-receiver.test.js | 6 +++++- 9 files changed, 107 insertions(+), 14 deletions(-) create mode 100644 contracts/0.8.9/interfaces/ISelfOwnedStETHBurner.sol diff --git a/contracts/0.4.24/oracle/LidoOracle.sol b/contracts/0.4.24/oracle/LidoOracle.sol index bbddfc91c..46bccbd40 100644 --- a/contracts/0.4.24/oracle/LidoOracle.sol +++ b/contracts/0.4.24/oracle/LidoOracle.sol @@ -7,6 +7,7 @@ pragma solidity 0.4.24; import "@aragon/os/contracts/apps/AragonApp.sol"; import "@aragon/os/contracts/lib/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/introspection/ERC165Checker.sol"; import "../interfaces/IBeaconReportReceiver.sol"; import "../interfaces/ILido.sol"; @@ -32,6 +33,7 @@ import "./ReportUtils.sol"; contract LidoOracle is ILidoOracle, AragonApp { using SafeMath for uint256; using ReportUtils for uint256; + using ERC165Checker for address; struct BeaconSpec { uint64 epochsPerFrame; @@ -184,6 +186,14 @@ contract LidoOracle is ILidoOracle, AragonApp { * @dev Specify 0 to disable this functionality */ function setBeaconReportReceiver(address _addr) external auth(SET_BEACON_REPORT_RECEIVER) { + if(_addr != address(0)) { + IBeaconReportReceiver iBeacon; + require( + _addr._supportsInterface(iBeacon.processLidoOracleReport.selector), + "BAD_BEACON_REPORT_RECEIVER" + ); + } + BEACON_REPORT_RECEIVER_POSITION.setStorageUint256(uint256(_addr)); emit BeaconReportReceiverSet(_addr); } diff --git a/contracts/0.4.24/test_helpers/BeaconReportReceiverMock.sol b/contracts/0.4.24/test_helpers/BeaconReportReceiverMock.sol index dd560c0e9..4a5305f5f 100644 --- a/contracts/0.4.24/test_helpers/BeaconReportReceiverMock.sol +++ b/contracts/0.4.24/test_helpers/BeaconReportReceiverMock.sol @@ -4,15 +4,22 @@ pragma solidity 0.4.24; +import "openzeppelin-solidity/contracts/introspection/ERC165.sol"; + import "../interfaces/IBeaconReportReceiver.sol"; -contract BeaconReportReceiverMock is IBeaconReportReceiver { +contract BeaconReportReceiverMock is IBeaconReportReceiver, ERC165 { uint256 public postTotalPooledEther; uint256 public preTotalPooledEther; uint256 public timeElapsed; uint256 public gas; - + + constructor() { + IBeaconReportReceiver iBeacon; + _registerInterface(iBeacon.processLidoOracleReport.selector); + } + function processLidoOracleReport(uint256 _postTotalPooledEther, uint256 _preTotalPooledEther, uint256 _timeElapsed) external { @@ -22,3 +29,10 @@ contract BeaconReportReceiverMock is IBeaconReportReceiver { timeElapsed = _timeElapsed; } } + +contract BeaconReportReceiverMockWithoutERC165 is IBeaconReportReceiver { + function processLidoOracleReport(uint256 _postTotalPooledEther, + uint256 _preTotalPooledEther, + uint256 _timeElapsed) external { + } +} diff --git a/contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol b/contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol index 231791e99..f96264499 100644 --- a/contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol +++ b/contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol @@ -5,6 +5,7 @@ /* See contracts/COMPILERS.md */ pragma solidity 0.8.9; +import "@openzeppelin/contracts-v4.4/utils/introspection/ERC165.sol"; import "./OrderedCallbacksArray.sol"; import "./interfaces/IBeaconReportReceiver.sol"; @@ -14,7 +15,7 @@ import "./interfaces/IBeaconReportReceiver.sol"; * Contract adds permission modifiers. * Only the `ORACLE` address can invoke `processLidoOracleReport` function. */ -contract CompositePostRebaseBeaconReceiver is OrderedCallbacksArray, IBeaconReportReceiver { +contract CompositePostRebaseBeaconReceiver is OrderedCallbacksArray, IBeaconReportReceiver, ERC165 { address public immutable ORACLE; modifier onlyOracle() { @@ -25,7 +26,7 @@ contract CompositePostRebaseBeaconReceiver is OrderedCallbacksArray, IBeaconRepo constructor( address _voting, address _oracle - ) OrderedCallbacksArray(_voting) { + ) OrderedCallbacksArray(_voting, type(IBeaconReportReceiver).interfaceId) { require(_oracle != address(0), "ORACLE_ZERO_ADDRESS"); ORACLE = _oracle; @@ -35,7 +36,7 @@ contract CompositePostRebaseBeaconReceiver is OrderedCallbacksArray, IBeaconRepo uint256 _postTotalPooledEther, uint256 _preTotalPooledEther, uint256 _timeElapsed - ) external override onlyOracle { + ) external virtual override onlyOracle { uint256 callbacksLen = callbacksLength(); for (uint256 brIndex = 0; brIndex < callbacksLen; brIndex++) { @@ -47,4 +48,11 @@ contract CompositePostRebaseBeaconReceiver is OrderedCallbacksArray, IBeaconRepo ); } } + + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return ( + interfaceId == type(IBeaconReportReceiver).interfaceId + || super.supportsInterface(interfaceId) + ); + } } diff --git a/contracts/0.8.9/OrderedCallbacksArray.sol b/contracts/0.8.9/OrderedCallbacksArray.sol index c13e8e36b..aefecc6aa 100644 --- a/contracts/0.8.9/OrderedCallbacksArray.sol +++ b/contracts/0.8.9/OrderedCallbacksArray.sol @@ -5,6 +5,8 @@ /* See contracts/COMPILERS.md */ pragma solidity 0.8.9; +import "@openzeppelin/contracts-v4.4/utils/introspection/ERC165Checker.sol"; + import "./interfaces/IOrderedCallbacksArray.sol"; /** @@ -14,9 +16,13 @@ import "./interfaces/IOrderedCallbacksArray.sol"; * Only the `VOTING` address can invoke storage mutating (add/insert/remove) functions. */ contract OrderedCallbacksArray is IOrderedCallbacksArray { + using ERC165Checker for address; + uint256 public constant MAX_CALLBACKS_COUNT = 16; + bytes4 constant INVALID_INTERFACE_ID = 0xffffffff; address public immutable VOTING; + bytes4 public immutable REQUIRED_INTERFACE; address[] public callbacks; @@ -25,10 +31,12 @@ contract OrderedCallbacksArray is IOrderedCallbacksArray { _; } - constructor(address _voting) { + constructor(address _voting, bytes4 _requiredIface) { + require(_requiredIface != INVALID_INTERFACE_ID, "INVALID_IFACE"); require(_voting != address(0), "VOTING_ZERO_ADDRESS"); VOTING = _voting; + REQUIRED_INTERFACE = _requiredIface; } function callbacksLength() public view override returns (uint256) { @@ -58,6 +66,7 @@ contract OrderedCallbacksArray is IOrderedCallbacksArray { function _insertCallback(address _callback, uint256 _atIndex) private { require(_callback != address(0), "CALLBACK_ZERO_ADDRESS"); + require(_callback.supportsInterface(REQUIRED_INTERFACE), "BAD_CALLBACK_INTERFACE"); uint256 oldCArrayLength = callbacks.length; require(_atIndex <= oldCArrayLength, "INDEX_IS_OUT_OF_RANGE"); diff --git a/contracts/0.8.9/SelfOwnedStETHBurner.sol b/contracts/0.8.9/SelfOwnedStETHBurner.sol index 8a26a5bd6..dfc8ada9b 100644 --- a/contracts/0.8.9/SelfOwnedStETHBurner.sol +++ b/contracts/0.8.9/SelfOwnedStETHBurner.sol @@ -7,8 +7,10 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-v4.4/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts-v4.4/utils/introspection/ERC165.sol"; import "@openzeppelin/contracts-v4.4/utils/math/Math.sol"; import "./interfaces/IBeaconReportReceiver.sol"; +import "./interfaces/ISelfOwnedStETHBurner.sol"; /** * @title Interface defining a Lido liquid staking pool @@ -73,7 +75,7 @@ interface IOracle { * * @dev Burning stETH means 'decrease total underlying shares amount to perform stETH token rebase' */ -contract SelfOwnedStETHBurner is IBeaconReportReceiver { +contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, ERC165 { uint256 private constant MAX_BASIS_POINTS = 10000; uint256 private coverSharesBurnRequested; @@ -287,7 +289,7 @@ contract SelfOwnedStETHBurner is IBeaconReportReceiver { * Resets `coverSharesBurnRequested` and `nonCoverSharesBurnRequested` counters to zero. * Does nothing if there are no pending burning requests. */ - function processLidoOracleReport(uint256, uint256, uint256) external override { + function processLidoOracleReport(uint256, uint256, uint256) external virtual override { uint256 memCoverSharesBurnRequested = coverSharesBurnRequested; uint256 memNonCoverSharesBurnRequested = nonCoverSharesBurnRequested; @@ -346,14 +348,14 @@ contract SelfOwnedStETHBurner is IBeaconReportReceiver { /** * Returns the total cover shares ever burnt. */ - function getCoverSharesBurnt() external view returns (uint256) { + function getCoverSharesBurnt() external view virtual override returns (uint256) { return totalCoverSharesBurnt; } /** * Returns the total non-cover shares ever burnt. */ - function getNonCoverSharesBurnt() external view returns (uint256) { + function getNonCoverSharesBurnt() external view virtual override returns (uint256) { return totalNonCoverSharesBurnt; } @@ -379,6 +381,14 @@ contract SelfOwnedStETHBurner is IBeaconReportReceiver { return ILido(LIDO).getPooledEthByShares(totalShares - sharesBurnRequested); } + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return ( + interfaceId == type(IBeaconReportReceiver).interfaceId + || interfaceId == type(ISelfOwnedStETHBurner).interfaceId + || super.supportsInterface(interfaceId) + ); + } + function _requestBurnMyStETH(uint256 _stETH2Burn, bool _isCover) private { require(_stETH2Burn > 0, "ZERO_BURN_AMOUNT"); require(msg.sender == VOTING, "MSG_SENDER_MUST_BE_VOTING"); diff --git a/contracts/0.8.9/interfaces/ISelfOwnedStETHBurner.sol b/contracts/0.8.9/interfaces/ISelfOwnedStETHBurner.sol new file mode 100644 index 000000000..6c66044c4 --- /dev/null +++ b/contracts/0.8.9/interfaces/ISelfOwnedStETHBurner.sol @@ -0,0 +1,20 @@ +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + +/** + * @title Interface defining a "client-side" of the `SelfOwnedStETHBurner` contract. + */ +interface ISelfOwnedStETHBurner { + /** + * Returns the total cover shares ever burnt. + */ + function getCoverSharesBurnt() external view returns (uint256); + + /** + * Returns the total non-cover shares ever burnt. + */ + function getNonCoverSharesBurnt() external view returns (uint256); +} diff --git a/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol b/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol index 99397b58c..219fda27c 100644 --- a/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol +++ b/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol @@ -5,6 +5,7 @@ /* See contracts/COMPILERS.md */ pragma solidity 0.8.9; +import "@openzeppelin/contracts-v4.4/utils/introspection/ERC165.sol"; import "../interfaces/IBeaconReportReceiver.sol"; /** @@ -12,7 +13,7 @@ import "../interfaces/IBeaconReportReceiver.sol"; * * @dev DON'T USE THIS CODE IN A PRODUCTION */ -contract BeaconReceiverMock is IBeaconReportReceiver { +contract BeaconReceiverMock is IBeaconReportReceiver, ERC165 { uint256 public immutable id; uint256 public processedCounter; @@ -20,7 +21,20 @@ contract BeaconReceiverMock is IBeaconReportReceiver { id = _id; } - function processLidoOracleReport(uint256, uint256, uint256) external override { + function processLidoOracleReport(uint256, uint256, uint256) external virtual override { processedCounter++; } + + function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + return ( + interfaceId == type(IBeaconReportReceiver).interfaceId + || super.supportsInterface(interfaceId) + ); + } +} + +contract BeaconReceiverMockWithoutERC165 is IBeaconReportReceiver { + function processLidoOracleReport(uint256, uint256, uint256) external virtual override { + + } } diff --git a/test/0.4.24/lidooracle.test.js b/test/0.4.24/lidooracle.test.js index ce54d1535..06af6e61b 100644 --- a/test/0.4.24/lidooracle.test.js +++ b/test/0.4.24/lidooracle.test.js @@ -6,7 +6,8 @@ const keccak256 = require('js-sha3').keccak_256 const LidoOracle = artifacts.require('LidoOracleMock.sol') const Lido = artifacts.require('LidoMockForOracle.sol') -const BeaconReportReceiver = artifacts.require('BeaconReportReceiverMock.sol') +const BeaconReportReceiver = artifacts.require('BeaconReportReceiverMock') +const BeaconReportReceiverWithoutERC165 = artifacts.require('BeaconReportReceiverMockWithoutERC165') const GENESIS_TIME = 1606824000 const EPOCH_LENGTH = 32 * 12 @@ -497,6 +498,9 @@ contract('LidoOracle', ([appManager, voting, user1, user2, user3, user4, user5, }) it('quorum receiver called with same arguments as getLastCompletedReportDelta', async () => { + const badMock = await BeaconReportReceiverWithoutERC165.new() + await assertRevert(app.setBeaconReportReceiver(badMock.address, { from: voting }), 'BAD_BEACON_REPORT_RECEIVER') + const mock = await BeaconReportReceiver.new() let receipt = await app.setBeaconReportReceiver(mock.address, { from: voting }) assertEvent(receipt, 'BeaconReportReceiverSet', { expectedArgs: { callback: mock.address } }) diff --git a/test/0.8.9/composite-post-rebase-beacon-receiver.test.js b/test/0.8.9/composite-post-rebase-beacon-receiver.test.js index 592b02e56..9481e8813 100644 --- a/test/0.8.9/composite-post-rebase-beacon-receiver.test.js +++ b/test/0.8.9/composite-post-rebase-beacon-receiver.test.js @@ -4,7 +4,8 @@ const { assertBn, assertRevert, assertEvent, assertAmountOfEvents } = require('@ const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') const CompositePostRebaseBeaconReceiver = artifacts.require('CompositePostRebaseBeaconReceiver.sol') -const BeaconReceiverMock = artifacts.require('BeaconReceiverMock.sol') +const BeaconReceiverMock = artifacts.require('BeaconReceiverMock') +const BeaconReceiverMockWithoutERC165 = artifacts.require('BeaconReceiverMockWithoutERC165') const deployedCallbackCount = 8 @@ -32,6 +33,9 @@ contract('CompositePostRebaseBeaconReceiver', ([deployer, voting, oracle, anothe }) it(`add a single callback works`, async () => { + const invalidCallback = await BeaconReceiverMockWithoutERC165.new() + assertRevert(compositeReceiver.addCallback(invalidCallback.address, { from: voting }), `BAD_CALLBACK_INTERFACE`) + const receipt = await compositeReceiver.addCallback(callbackMocks[0], { from: voting }) assertBn(await compositeReceiver.callbacksLength(), bn(1)) From d198ed95065b8a4f9d42554456d69b74213cea18 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 23 Feb 2022 15:11:06 +0300 Subject: [PATCH 076/159] small typos and coding style fixes --- contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol | 6 +++--- contracts/0.8.9/OrderedCallbacksArray.sol | 2 +- contracts/0.8.9/SelfOwnedStETHBurner.sol | 8 ++++---- contracts/0.8.9/test_helpers/BeaconReceiverMock.sol | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol b/contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol index f96264499..bd332d752 100644 --- a/contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol +++ b/contracts/0.8.9/CompositePostRebaseBeaconReceiver.sol @@ -49,10 +49,10 @@ contract CompositePostRebaseBeaconReceiver is OrderedCallbacksArray, IBeaconRepo } } - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { return ( - interfaceId == type(IBeaconReportReceiver).interfaceId - || super.supportsInterface(interfaceId) + _interfaceId == type(IBeaconReportReceiver).interfaceId + || super.supportsInterface(_interfaceId) ); } } diff --git a/contracts/0.8.9/OrderedCallbacksArray.sol b/contracts/0.8.9/OrderedCallbacksArray.sol index aefecc6aa..be684e120 100644 --- a/contracts/0.8.9/OrderedCallbacksArray.sol +++ b/contracts/0.8.9/OrderedCallbacksArray.sol @@ -12,7 +12,7 @@ import "./interfaces/IOrderedCallbacksArray.sol"; /** * @title Contract defining an ordered callbacks array supporting add/insert/remove ops * - * Contract adds permission modifiers ontop of `IOderedCallbacksArray` interface functions. + * Contract adds permission modifiers atop of `IOrderedCallbacksArray` interface functions. * Only the `VOTING` address can invoke storage mutating (add/insert/remove) functions. */ contract OrderedCallbacksArray is IOrderedCallbacksArray { diff --git a/contracts/0.8.9/SelfOwnedStETHBurner.sol b/contracts/0.8.9/SelfOwnedStETHBurner.sol index dfc8ada9b..328c559a8 100644 --- a/contracts/0.8.9/SelfOwnedStETHBurner.sol +++ b/contracts/0.8.9/SelfOwnedStETHBurner.sol @@ -381,11 +381,11 @@ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, E return ILido(LIDO).getPooledEthByShares(totalShares - sharesBurnRequested); } - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { return ( - interfaceId == type(IBeaconReportReceiver).interfaceId - || interfaceId == type(ISelfOwnedStETHBurner).interfaceId - || super.supportsInterface(interfaceId) + _interfaceId == type(IBeaconReportReceiver).interfaceId + || _interfaceId == type(ISelfOwnedStETHBurner).interfaceId + || super.supportsInterface(_interfaceId) ); } diff --git a/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol b/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol index 219fda27c..9d369de77 100644 --- a/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol +++ b/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol @@ -25,10 +25,10 @@ contract BeaconReceiverMock is IBeaconReportReceiver, ERC165 { processedCounter++; } - function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) { + function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { return ( - interfaceId == type(IBeaconReportReceiver).interfaceId - || super.supportsInterface(interfaceId) + _interfaceId == type(IBeaconReportReceiver)._interfaceId + || super.supportsInterface(_interfaceId) ); } } From 6d289eafa83974fb548866566f8f419eebdd1e29 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 23 Feb 2022 15:15:28 +0300 Subject: [PATCH 077/159] tiny coding style and type fixes --- contracts/0.4.24/Lido.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index e6442fd00..6aac8f4ca 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -353,8 +353,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Send funds to recovery Vault. Overrides default AragonApp behaviour. - * @param _token Token to be sent to recovery vault. + * @notice Send funds to recovery Vault. Overrides default AragonApp behaviour + * @param _token Token to be sent to recovery vault */ function transferToVault(address _token) external { require(allowRecoverability(_token), "RECOVER_DISALLOWED"); @@ -369,7 +369,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } else { ERC20 token = ERC20(_token); balance = token.staticBalanceOf(this); - // safeTransfer comes from overriden default implementation + // safeTransfer comes from overridden default implementation require(token.safeTransfer(vault, balance), "RECOVER_TOKEN_TRANSFER_FAILED"); } @@ -377,8 +377,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Send NTFs to recovery Vault. - * @param _token Token to be sent to recovery vault. + * @notice Send NTFs to recovery Vault + * @param _token Token to be sent to recovery vault * @param _tokenId Token Id */ function transferERC721ToVault(address _token, uint256 _tokenId) external { @@ -734,7 +734,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @dev Records a deposit to the deposit_contract.deposit function. + * @dev Records a deposit to the deposit_contract.deposit function * @param _amount Total amount deposited to the ETH 2.0 side */ function _markAsUnbuffered(uint256 _amount) internal { From 381393273a654f122dee51cc5884a0e54f323141 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 23 Feb 2022 16:16:16 +0300 Subject: [PATCH 078/159] fix compilation error --- contracts/0.8.9/test_helpers/BeaconReceiverMock.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol b/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol index 9d369de77..1aaeaed0d 100644 --- a/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol +++ b/contracts/0.8.9/test_helpers/BeaconReceiverMock.sol @@ -27,7 +27,7 @@ contract BeaconReceiverMock is IBeaconReportReceiver, ERC165 { function supportsInterface(bytes4 _interfaceId) public view virtual override returns (bool) { return ( - _interfaceId == type(IBeaconReportReceiver)._interfaceId + _interfaceId == type(IBeaconReportReceiver).interfaceId || super.supportsInterface(_interfaceId) ); } From e3b84476d20c51d2ce95dfd9f289d34bd902c0f7 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Wed, 23 Feb 2022 18:39:10 +0300 Subject: [PATCH 079/159] add limit to mev rewards withdrawal per LidoOracle report (no tests yet) --- contracts/0.4.24/Lido.sol | 34 ++++++++++++++++--- contracts/0.4.24/interfaces/ILido.sol | 3 ++ .../0.4.24/interfaces/ILidoMevTxFeeVault.sol | 3 +- contracts/0.8.9/LidoMevTxFeeVault.sol | 6 ++-- 4 files changed, 39 insertions(+), 7 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 6aac8f4ca..14976eb86 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -58,6 +58,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 constant public SET_INSURANCE_FUND = keccak256("SET_INSURANCE_FUND"); bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE"); bytes32 constant public SET_MEV_TX_FEE_VAULT_ROLE = keccak256("SET_MEV_TX_FEE_VAULT_ROLE"); + bytes32 constant public SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE = keccak256("SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE"); uint256 constant public PUBKEY_LENGTH = 48; uint256 constant public WITHDRAWAL_CREDENTIALS_LENGTH = 32; @@ -92,6 +93,9 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); + /// @dev percent in points of total pooled ether allowed to withdraw from MevTxFeeVault per LidoOracle report + bytes32 internal constant MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS = keccak256("lido.Lido.mevTxFeeWithdrawalLimitPoints"); + /// @dev Just a counter of total amount of MEV and transaction rewards received by Lido contract /// Not used in the logic bytes32 internal constant TOTAL_MEV_TX_FEE_COLLECTED_POSITION = keccak256("lido.Lido.totalMevTxFeeCollected"); @@ -291,6 +295,16 @@ contract Lido is ILido, IsContract, StETH, AragonApp { emit LidoMevTxFeeVaultSet(_mevTxFeeVault); } + /** + * @dev Sets limit to amount of ETH to withdraw per LidoOracle report + * @param _limitPoints limit in points to amount of ETH to withdraw per LidoOracle report + */ + function setMevTxFeeWithdrawalLimit(uint256 _limitPoints) external auth(SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE) { + require(_limitPoints <= TOTAL_BASIS_POINTS); + MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.setStorageUint256(_limitPoints); + emit MevTxFeeWithdrawalLimitSet(_limitPoints); + } + /** * @notice Issues withdrawal request. Not implemented. * @param _amount Amount of StETH to withdraw @@ -327,17 +341,21 @@ contract Lido is ILido, IsContract, StETH, AragonApp { uint256 rewardBase = (appearedValidators.mul(DEPOSIT_SIZE)).add(BEACON_BALANCE_POSITION.getStorageUint256()); // Save the current beacon balance and validators to - // calcuate rewards on the next push + // calculate rewards on the next push BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); // If LidoMevTxFeeVault address is not set just do as if there were no mevTxFee rewards at all // Otherwise withdraw all rewards and put them to the buffer // Thus, MEV tx fees are handled the same way as beacon rewards - uint256 mevRewards = 0; + + // Calc max amount for this withdrawal + uint256 mevRewards = (_getTotalPooledEther() * MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256()) + / TOTAL_BASIS_POINTS; + address mevVaultAddress = getMevTxFeeVault(); if (mevVaultAddress != address(0)) { - mevRewards = ILidoMevTxFeeVault(mevVaultAddress).withdrawRewards(); + mevRewards = ILidoMevTxFeeVault(mevVaultAddress).withdrawRewards(mevRewards); if (mevRewards != 0) { BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevRewards)); } @@ -442,6 +460,14 @@ contract Lido is ILido, IsContract, StETH, AragonApp { return TOTAL_MEV_TX_FEE_COLLECTED_POSITION.getStorageUint256(); } + /** + * @notice Get limit in points to amount of ETH to withdraw per LidoOracle report + * @return uint256 limit in points to amount of ETH to withdraw per LidoOracle report + */ + function getMevTxFeeWithdrawalLimitPoints() external view returns (uint256) { + return MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256(); + } + /** * @notice Gets deposit contract handle */ @@ -639,7 +665,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @dev Distributes rewards by minting and distributing corresponding amount of liquid tokens. - * @param _totalRewards Total rewards accured on the Ethereum 2.0 side in wei + * @param _totalRewards Total rewards occurred on the Ethereum 2.0 side in wei */ function distributeRewards(uint256 _totalRewards) internal { // We need to take a defined percentage of the reported reward as a fee, and we do diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index d8ce77700..1555f064f 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -101,6 +101,9 @@ interface ILido { // The amount of ETH withdrawn from LidoMevTxFeeVault contract to Lido contract event MevTxFeeReceived(uint256 amount); + // Percent in points of total pooled ether allowed to withdraw from MevTxFeeVault per LidoOracle report + event MevTxFeeWithdrawalLimitSet(uint256 limitPoints); + /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. diff --git a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol index d596c1fd0..c33ad2aee 100644 --- a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol +++ b/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol @@ -9,7 +9,8 @@ interface ILidoMevTxFeeVault { /** * @notice Withdraw all accumulated rewards to Lido contract + * @param _maxAmount Max amount of ETH to withdraw * @return amount uint256 of funds received as MEV and transaction fees in wei */ - function withdrawRewards() external returns (uint256 amount); + function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount); } diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 5d78dc328..53dd0c16c 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -84,12 +84,14 @@ contract LidoMevTxFeeVault { /** * @notice Withdraw all accumulated rewards to Lido contract * @dev Can be called only by the Lido contract + * @param _maxAmount Max amount of ETH to withdraw * @return amount uint256 of funds received as MEV and transaction fees in wei */ - function withdrawRewards() external returns (uint256 amount) { + function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount) { require(msg.sender == LIDO, "ONLY_LIDO_CAN_WITHDRAW"); - amount = address(this).balance; + uint256 balance = address(this).balance; + amount = (balance > _maxAmount) ? _maxAmount : balance; if (amount > 0) { ILido(LIDO).receiveMevTxFee{value: amount}(); } From a5af16ebc9156827be45fa05a38273eb5a465488 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 28 Feb 2022 16:17:28 +0300 Subject: [PATCH 080/159] fix existing unit tests --- hardhat.config.js | 1 + test/0.4.24/lido.test.js | 18 +++++++++++++----- test/0.8.9/lido-mev-tx-fee-vault.js | 6 +++--- test/scenario/helpers/deploy.js | 3 +++ test/scenario/mev_tx_fee_after_the_merge.js | 5 ++++- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index 26381f4cb..599adddfe 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -68,6 +68,7 @@ const getNetConfig = (networkName, ethAccountName) => { blockGasLimit: 20000000, gasPrice: 0, initialBaseFeePerGas: 0, + allowUnlimitedContractSize: true, accounts: { mnemonic: 'hardhat', count: 20, diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index ff99e8025..b976e8f58 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -26,6 +26,7 @@ const ADDRESS_3 = '0x0000000000000000000000000000000000000003' const ADDRESS_4 = '0x0000000000000000000000000000000000000004' const UNLIMITED = 1000000000 +const TOTAL_BASIS_POINTS = 10000 const pad = (hex, bytesLength) => { const absentZeroes = bytesLength * 2 + 2 - hex.length @@ -85,6 +86,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.SET_ORACLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_INSURANCE_FUND(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) @@ -112,8 +114,12 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) mevVault = await MevTxFeeVault.new(app.address, treasuryAddr) rewarder = await RewardEmulatorMock.new(mevVault.address) - const receipt = await app.setMevTxFeeVault(mevVault.address, { from: voting }) + let receipt = await app.setMevTxFeeVault(mevVault.address, { from: voting }) assertEvent(receipt, 'LidoMevTxFeeVaultSet', { expectedArgs: { mevTxFeeVault: mevVault.address } }) + + const mevTxFeeWithdrawalLimitPoints = 3 + receipt = await app.setMevTxFeeWithdrawalLimit(mevTxFeeWithdrawalLimitPoints, { from: voting }) + assertEvent(receipt, 'MevTxFeeWithdrawalLimitSet', { expectedArgs: { limitPoints: mevTxFeeWithdrawalLimitPoints } }) }) const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { @@ -207,7 +213,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('MEV distribution works when zero rewards reported', async () => { const depositAmount = 32 - const mevAmount = 10 + const mevAmount = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = 0 await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) @@ -223,7 +229,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('MEV distribution works when negative rewards reported', async () => { const depositAmount = 32 - const mevAmount = 12 + const mevAmount = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = -2 await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) @@ -239,7 +245,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('MEV distribution works when positive rewards reported', async () => { const depositAmount = 32 - const mevAmount = 7 + const mevAmount = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = 3 await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) @@ -248,9 +254,11 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await rewarder.reward({ from: user1, value: ETH(mevAmount) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) + const protocolFeePoints = await app.getFee() + const shareOfRewardsForStakers = (TOTAL_BASIS_POINTS - protocolFeePoints) / TOTAL_BASIS_POINTS assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(mevAmount)) - assertBn(await app.balanceOf(user2), STETH(41)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + shareOfRewardsForStakers * (mevAmount + beaconRewards))) }) it('setFee works', async () => { diff --git a/test/0.8.9/lido-mev-tx-fee-vault.js b/test/0.8.9/lido-mev-tx-fee-vault.js index b785f8d1f..084889209 100644 --- a/test/0.8.9/lido-mev-tx-fee-vault.js +++ b/test/0.8.9/lido-mev-tx-fee-vault.js @@ -58,9 +58,9 @@ contract('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, another }) it('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { - await assertRevert(mevVault.withdrawRewards({ from: anotherAccount }), 'ONLY_LIDO_CAN_WITHDRAW') - await assertRevert(mevVault.withdrawRewards({ from: deployer }), 'ONLY_LIDO_CAN_WITHDRAW') - await assertRevert(mevVault.withdrawRewards({ from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(mevVault.withdrawRewards(12345, { from: anotherAccount }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(mevVault.withdrawRewards(12345, { from: deployer }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(mevVault.withdrawRewards(12345, { from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') }) it('MEV Tx Fee Vault totalRewardsReceivedViaTransactions counter', async () => { diff --git a/test/scenario/helpers/deploy.js b/test/scenario/helpers/deploy.js index 9a3f93cd1..6f255a119 100644 --- a/test/scenario/helpers/deploy.js +++ b/test/scenario/helpers/deploy.js @@ -72,6 +72,7 @@ async function deployDaoAndPool(appManager, voting) { POOL_BURN_ROLE, DEPOSIT_ROLE, SET_MEV_TX_FEE_VAULT_ROLE, + SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, NODE_OPERATOR_REGISTRY_ADD_NODE_OPERATOR_ROLE, NODE_OPERATOR_REGISTRY_SET_NODE_OPERATOR_ACTIVE_ROLE, @@ -86,6 +87,7 @@ async function deployDaoAndPool(appManager, voting) { pool.BURN_ROLE(), pool.DEPOSIT_ROLE(), pool.SET_MEV_TX_FEE_VAULT_ROLE(), + pool.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), nodeOperatorRegistry.MANAGE_SIGNING_KEYS(), nodeOperatorRegistry.ADD_NODE_OPERATOR_ROLE(), nodeOperatorRegistry.SET_NODE_OPERATOR_ACTIVE_ROLE(), @@ -102,6 +104,7 @@ async function deployDaoAndPool(appManager, voting) { acl.createPermission(voting, pool.address, POOL_MANAGE_WITHDRAWAL_KEY, appManager, { from: appManager }), acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, SET_MEV_TX_FEE_VAULT_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }), // Allow depositor to deposit buffered ether acl.createPermission(depositSecurityModule.address, pool.address, DEPOSIT_ROLE, appManager, { from: appManager }), diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index dc762b2b9..4a92374f1 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -104,6 +104,9 @@ contract('Lido: merge acceptance', (addresses) => { mevVault = await LidoMevTxFeeVault.new(pool.address, treasuryAddr) await pool.setMevTxFeeVault(mevVault.address, { from: voting }) + // At first go through tests assuming there is no withdrawal limit + await pool.setMevTxFeeWithdrawalLimit(TOTAL_BASIS_POINTS, { from: voting }) + rewarder = await RewardEmulatorMock.new(mevVault.address) assertBn(await web3.eth.getBalance(rewarder.address), ETH(0), 'rewarder balance') @@ -406,7 +409,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(insuranceAddr), new BN('81999999999999999'), 'insurance tokens') // The node operators' fee is distributed between all active node operators, - // proprotional to their effective stake (the amount of Ether staked by the operator's + // proportional to their effective stake (the amount of Ether staked by the operator's // used and non-stopped validators). // // In our case, both node operators received the same fee since they have the same From 365c45b56843823f854ff187fabe97cc5f43a732 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 28 Feb 2022 16:20:11 +0300 Subject: [PATCH 081/159] bump hardhat, hardhat-gas-reporter and add hardhat-contract-sizer --- package.json | 5 +- yarn.lock | 180 +++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 142 insertions(+), 43 deletions(-) diff --git a/package.json b/package.json index 902a74bc6..b2d024d47 100644 --- a/package.json +++ b/package.json @@ -102,8 +102,9 @@ "eth-ens-namehash": "^2.0.8", "eth-gas-reporter": "latest", "ethereumjs-testrpc-sc": "^6.5.1-sc.1", - "hardhat": "^2.7.0", - "hardhat-gas-reporter": "1.0.7", + "hardhat": "^2.8.0", + "hardhat-contract-sizer": "^2.5.0", + "hardhat-gas-reporter": "1.0.8", "husky": "^4.3.0", "ipfs-http-client": "^55.0.0", "lerna": "^3.22.1", diff --git a/yarn.lock b/yarn.lock index d1db316f1..8553f3589 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1937,7 +1937,7 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/block@npm:^3.4.0, @ethereumjs/block@npm:^3.5.0, @ethereumjs/block@npm:^3.6.0": +"@ethereumjs/block@npm:^3.5.0, @ethereumjs/block@npm:^3.6.0": version: 3.6.0 resolution: "@ethereumjs/block@npm:3.6.0" dependencies: @@ -1949,7 +1949,19 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/blockchain@npm:^5.4.0, @ethereumjs/blockchain@npm:^5.5.0": +"@ethereumjs/block@npm:^3.6.1": + version: 3.6.1 + resolution: "@ethereumjs/block@npm:3.6.1" + dependencies: + "@ethereumjs/common": ^2.6.1 + "@ethereumjs/tx": ^3.5.0 + ethereumjs-util: ^7.1.4 + merkle-patricia-tree: ^4.2.3 + checksum: e61e50e6a550720b7ea614108d9e503aecef8beb2a56948db532b8b76c07a87ef1ea8ccc3232fc2cb3ba3f24f882ae1656a8be5192bf3318ef84c7d520b63650 + languageName: node + linkType: hard + +"@ethereumjs/blockchain@npm:^5.5.0, @ethereumjs/blockchain@npm:^5.5.1": version: 5.5.1 resolution: "@ethereumjs/blockchain@npm:5.5.1" dependencies: @@ -1965,7 +1977,7 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/common@npm:^2.4.0, @ethereumjs/common@npm:^2.6.0": +"@ethereumjs/common@npm:^2.6.0": version: 2.6.0 resolution: "@ethereumjs/common@npm:2.6.0" dependencies: @@ -1975,6 +1987,16 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/common@npm:^2.6.1, @ethereumjs/common@npm:^2.6.2": + version: 2.6.2 + resolution: "@ethereumjs/common@npm:2.6.2" + dependencies: + crc-32: ^1.2.0 + ethereumjs-util: ^7.1.4 + checksum: 5f9447ab56d8917ea5f1d7efe772f8315997568efa5f37ee174a9199c5ccb695b3405949ffec62df3168c3daa93f45358454f3601ac055540eadbfa147feb3e5 + languageName: node + linkType: hard + "@ethereumjs/ethash@npm:^1.1.0": version: 1.1.0 resolution: "@ethereumjs/ethash@npm:1.1.0" @@ -1988,7 +2010,7 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/tx@npm:^3.3.0, @ethereumjs/tx@npm:^3.4.0": +"@ethereumjs/tx@npm:^3.4.0": version: 3.4.0 resolution: "@ethereumjs/tx@npm:3.4.0" dependencies: @@ -1998,23 +2020,33 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/vm@npm:^5.5.2": - version: 5.6.0 - resolution: "@ethereumjs/vm@npm:5.6.0" +"@ethereumjs/tx@npm:^3.5.0": + version: 3.5.0 + resolution: "@ethereumjs/tx@npm:3.5.0" dependencies: - "@ethereumjs/block": ^3.6.0 - "@ethereumjs/blockchain": ^5.5.0 - "@ethereumjs/common": ^2.6.0 - "@ethereumjs/tx": ^3.4.0 + "@ethereumjs/common": ^2.6.1 + ethereumjs-util: ^7.1.4 + checksum: 97db5545803cecd721f2fe45c95f5892b7594afb4721566cc0a0516ddbcf385dbec7e850425106c624fa19289987fd67007c2fe14f1cc4c5325866c3356f7103 + languageName: node + linkType: hard + +"@ethereumjs/vm@npm:^5.6.0": + version: 5.7.1 + resolution: "@ethereumjs/vm@npm:5.7.1" + dependencies: + "@ethereumjs/block": ^3.6.1 + "@ethereumjs/blockchain": ^5.5.1 + "@ethereumjs/common": ^2.6.2 + "@ethereumjs/tx": ^3.5.0 async-eventemitter: ^0.2.4 core-js-pure: ^3.0.1 - debug: ^2.2.0 - ethereumjs-util: ^7.1.3 + debug: ^4.3.3 + ethereumjs-util: ^7.1.4 functional-red-black-tree: ^1.0.1 mcl-wasm: ^0.7.1 - merkle-patricia-tree: ^4.2.2 + merkle-patricia-tree: ^4.2.3 rustbn.js: ~0.2.0 - checksum: 275c1f8804e37e3df6d51c75c5c396dcf3ead0b2bb159970971d592d734e146c5284efcc02fcfbb8f4332e36cd8d126c1cb73ea6e4620bcfd329d544358f86ba + checksum: 890c145f788307d51ccf12832cac8c0c31a8048b4590edc16ecdbead0714045caa56ee6bc3c9ce236e39a57d2670e5be685057ae3c031c2003721680268fddf4 languageName: node linkType: hard @@ -3674,8 +3706,9 @@ __metadata: ethereumjs-testrpc-sc: ^6.5.1-sc.1 ethereumjs-util: ^7.0.8 ethers: ^5.0.19 - hardhat: ^2.7.0 - hardhat-gas-reporter: 1.0.7 + hardhat: ^2.8.0 + hardhat-contract-sizer: ^2.5.0 + hardhat-gas-reporter: 1.0.8 husky: ^4.3.0 ipfs-http-client: ^55.0.0 lerna: ^3.22.1 @@ -3707,6 +3740,19 @@ __metadata: languageName: unknown linkType: soft +"@metamask/eth-sig-util@npm:^4.0.0": + version: 4.0.0 + resolution: "@metamask/eth-sig-util@npm:4.0.0" + dependencies: + ethereumjs-abi: ^0.6.8 + ethereumjs-util: ^6.2.1 + ethjs-util: ^0.1.6 + tweetnacl: ^1.0.3 + tweetnacl-util: ^0.15.1 + checksum: ab2b1ddb3db3fdf64ba89f22d93a0c584d2870c83f01b2ad2c46a8b1fcd45398b24ed9b427dbdcc34f9afeb66341536dbc1e6094e15f5c91972baa2fed4a87bf + languageName: node + linkType: hard + "@mrmlnc/readdir-enhanced@npm:^2.2.1": version: 2.2.1 resolution: "@mrmlnc/readdir-enhanced@npm:2.2.1" @@ -8449,6 +8495,19 @@ __metadata: languageName: node linkType: hard +"cli-table3@npm:^0.6.0": + version: 0.6.1 + resolution: "cli-table3@npm:0.6.1" + dependencies: + colors: 1.4.0 + string-width: ^4.2.0 + dependenciesMeta: + colors: + optional: true + checksum: b8104fe8ed7608ce7da368ac7626c744ea695cdef81c757bb7e4f122e156cc6cac5c6ba50da6a8ef0ae3af90c64763cbf557f31cd8d2b1b3977ac8768a0fa0f9 + languageName: node + linkType: hard + "cli-truncate@npm:^2.1.0": version: 2.1.0 resolution: "cli-truncate@npm:2.1.0" @@ -9773,7 +9832,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:^4.3.1": +"debug@npm:^4.3.1, debug@npm:^4.3.3": version: 4.3.3 resolution: "debug@npm:4.3.3" dependencies: @@ -11926,7 +11985,7 @@ __metadata: languageName: node linkType: hard -"ethereumjs-util@npm:6.2.1, ethereumjs-util@npm:^6.0.0, ethereumjs-util@npm:^6.1.0, ethereumjs-util@npm:^6.2.0": +"ethereumjs-util@npm:6.2.1, ethereumjs-util@npm:^6.0.0, ethereumjs-util@npm:^6.1.0, ethereumjs-util@npm:^6.2.0, ethereumjs-util@npm:^6.2.1": version: 6.2.1 resolution: "ethereumjs-util@npm:6.2.1" dependencies: @@ -12024,6 +12083,19 @@ __metadata: languageName: node linkType: hard +"ethereumjs-util@npm:^7.1.4": + version: 7.1.4 + resolution: "ethereumjs-util@npm:7.1.4" + dependencies: + "@types/bn.js": ^5.1.0 + bn.js: ^5.1.2 + create-hash: ^1.1.2 + ethereum-cryptography: ^0.1.3 + rlp: ^2.2.4 + checksum: 735c642f90cb4154b0d21fa495cc30a393c87d9424dafecb301f2b2d3618f3e9722322f8558bdd3be86baf52fa0ceb6db9cb348d4ee679e06991ef312240491b + languageName: node + linkType: hard + "ethereumjs-vm@npm:4.2.0": version: 4.2.0 resolution: "ethereumjs-vm@npm:4.2.0" @@ -12267,7 +12339,7 @@ __metadata: languageName: node linkType: hard -"ethjs-util@npm:0.1.6, ethjs-util@npm:^0.1.3": +"ethjs-util@npm:0.1.6, ethjs-util@npm:^0.1.3, ethjs-util@npm:^0.1.6": version: 0.1.6 resolution: "ethjs-util@npm:0.1.6" dependencies: @@ -14233,29 +14305,42 @@ __metadata: languageName: node linkType: hard -"hardhat-gas-reporter@npm:1.0.7": - version: 1.0.7 - resolution: "hardhat-gas-reporter@npm:1.0.7" +"hardhat-contract-sizer@npm:^2.5.0": + version: 2.5.0 + resolution: "hardhat-contract-sizer@npm:2.5.0" + dependencies: + chalk: ^4.0.0 + cli-table3: ^0.6.0 + peerDependencies: + hardhat: ^2.0.0 + checksum: 35c01af2cec385bbe1d51e5fdf5fb8e3fe2a6066f48c3b0800bf55710f89c01c52a0b43ce4e1f4218baf67a22b2b3f33132c20d994063a7aae0baa3697e35eab + languageName: node + linkType: hard + +"hardhat-gas-reporter@npm:1.0.8": + version: 1.0.8 + resolution: "hardhat-gas-reporter@npm:1.0.8" dependencies: array-uniq: 1.0.3 eth-gas-reporter: ^0.2.24 sha1: ^1.1.1 peerDependencies: hardhat: ^2.0.2 - checksum: 8751df1eb1ad56e851a07f4b72ecf4ca9f7c827d78d8ca65ce19e5266828bc64597c84055893258caaa45d32d006bad7d152c82df9de0cb56c072282082ee36d + checksum: cbee5088e797a149360a2e38ef40edb7234de272993c3ef426b4635991cb176964fd816caa8f40ec3931025b2164d46b7e3f6b24e9a14d110ac26d1f3ca65a25 languageName: node linkType: hard -"hardhat@npm:^2.7.0": - version: 2.7.0 - resolution: "hardhat@npm:2.7.0" +"hardhat@npm:^2.8.0": + version: 2.8.4 + resolution: "hardhat@npm:2.8.4" dependencies: - "@ethereumjs/block": ^3.4.0 - "@ethereumjs/blockchain": ^5.4.0 - "@ethereumjs/common": ^2.4.0 - "@ethereumjs/tx": ^3.3.0 - "@ethereumjs/vm": ^5.5.2 + "@ethereumjs/block": ^3.6.0 + "@ethereumjs/blockchain": ^5.5.0 + "@ethereumjs/common": ^2.6.0 + "@ethereumjs/tx": ^3.4.0 + "@ethereumjs/vm": ^5.6.0 "@ethersproject/abi": ^5.1.2 + "@metamask/eth-sig-util": ^4.0.0 "@sentry/node": ^5.18.1 "@solidity-parser/parser": ^0.14.0 "@types/bn.js": ^5.1.0 @@ -14269,10 +14354,9 @@ __metadata: debug: ^4.1.1 enquirer: ^2.3.0 env-paths: ^2.2.0 - eth-sig-util: ^2.5.2 ethereum-cryptography: ^0.1.2 ethereumjs-abi: ^0.6.8 - ethereumjs-util: ^7.1.0 + ethereumjs-util: ^7.1.3 find-up: ^2.1.0 fp-ts: 1.19.3 fs-extra: ^7.0.1 @@ -14281,9 +14365,9 @@ __metadata: immutable: ^4.0.0-rc.12 io-ts: 1.10.4 lodash: ^4.17.11 - merkle-patricia-tree: ^4.2.0 + merkle-patricia-tree: ^4.2.2 mnemonist: ^0.38.0 - mocha: ^7.1.2 + mocha: ^7.2.0 node-fetch: ^2.6.0 qs: ^6.7.0 raw-body: ^2.4.1 @@ -14299,7 +14383,7 @@ __metadata: ws: ^7.4.6 bin: hardhat: internal/cli/cli.js - checksum: 883032c7f6025c263166dc69bc9695c35723ce672895236455912886ecc9a186e3b218c402db87e7d514da1d45aa806a848cf2512d61a570629e91fd20ba9828 + checksum: 32319b9e97272778c2112e29c3a5c7231779c68fa5894a5474bfe3c27be6f4ddb0eef5f7973a1dd8e572e7b4c9fe7cd9865cfca2bcdf17ab5a8aea239a10c58b languageName: node linkType: hard @@ -18993,7 +19077,7 @@ __metadata: languageName: node linkType: hard -"merkle-patricia-tree@npm:^4.2.0, merkle-patricia-tree@npm:^4.2.2": +"merkle-patricia-tree@npm:^4.2.2": version: 4.2.2 resolution: "merkle-patricia-tree@npm:4.2.2" dependencies: @@ -19008,6 +19092,20 @@ __metadata: languageName: node linkType: hard +"merkle-patricia-tree@npm:^4.2.3": + version: 4.2.3 + resolution: "merkle-patricia-tree@npm:4.2.3" + dependencies: + "@types/levelup": ^4.3.0 + ethereumjs-util: ^7.1.4 + level-mem: ^5.0.1 + level-ws: ^2.0.0 + readable-stream: ^3.6.0 + semaphore-async-await: ^1.5.1 + checksum: cab8b2d4415be6ad513fd095d8b4b4b29c96bac6ff79092b21349cb52b2c03c00ad812f3e0b490a4818e5e71f10a997674731c8f30be30fd328636b79a4a8fc4 + languageName: node + linkType: hard + "methods@npm:~1.1.2": version: 1.1.2 resolution: "methods@npm:1.1.2" @@ -19445,7 +19543,7 @@ __metadata: languageName: node linkType: hard -"mocha@npm:^7.1.1, mocha@npm:^7.1.2": +"mocha@npm:^7.1.1, mocha@npm:^7.1.2, mocha@npm:^7.2.0": version: 7.2.0 resolution: "mocha@npm:7.2.0" dependencies: @@ -26729,7 +26827,7 @@ resolve@1.1.x: languageName: node linkType: hard -"tweetnacl-util@npm:^0.15.0": +"tweetnacl-util@npm:^0.15.0, tweetnacl-util@npm:^0.15.1": version: 0.15.1 resolution: "tweetnacl-util@npm:0.15.1" checksum: d4d40600eca66561c6cb73fdce5f2927876526e8cae4dea12b4b6f19a32f8e9e66f308d24cd131e850c83e00e1be5cdb0c65660806d9bf0d2d80ffb4c7cb0c55 @@ -26743,7 +26841,7 @@ resolve@1.1.x: languageName: node linkType: hard -"tweetnacl@npm:^1.0.0": +"tweetnacl@npm:^1.0.0, tweetnacl@npm:^1.0.3": version: 1.0.3 resolution: "tweetnacl@npm:1.0.3" checksum: 1188f3ef85db04d6dba632d211e481ae0a5805974491633ff60bd56ae3362312dfea1515e0d200685867b69ff212e8778e26923f8203e3c335064b07f620a6c7 From bf53258c1a2df207d2ceb04306d73b9afb820c55 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 28 Feb 2022 21:53:45 +0300 Subject: [PATCH 082/159] add mevTxFee withdrawal limit scenario test --- test/scenario/mev_tx_fee_after_the_merge.js | 87 +++++++++++++++++---- 1 file changed, 71 insertions(+), 16 deletions(-) diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index 4a92374f1..989e6d5c3 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -15,6 +15,8 @@ const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') +const TOTAL_BASIS_POINTS = 10000 + contract('Lido: merge acceptance', (addresses) => { const [ // the root account which deployed the DAO @@ -41,13 +43,13 @@ contract('Lido: merge acceptance', (addresses) => { let rewarder, mevVault // Total fee is 1% - const totalFeePoints = 0.01 * 10000 + const totalFeePoints = 0.01 * TOTAL_BASIS_POINTS // Of this 1%, 30% goes to the treasury - const treasuryFeePoints = 0.3 * 10000 + const treasuryFeePoints = 0.3 * TOTAL_BASIS_POINTS // 20% goes to the insurance fund - const insuranceFeePoints = 0.2 * 10000 + const insuranceFeePoints = 0.2 * TOTAL_BASIS_POINTS // 50% goes to node operators - const nodeOperatorsFeePoints = 0.5 * 10000 + const nodeOperatorsFeePoints = 0.5 * TOTAL_BASIS_POINTS const withdrawalCredentials = pad('0x0202', 32) @@ -376,10 +378,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(newTotalShares, new BN('97289047169125663202'), 'total shares') + const mevAmount = 9 + // Total pooled Ether increased const newTotalPooledEther = await pool.getTotalPooledEther() - assertBn(newTotalPooledEther, ETH(33 + 96 + 9), 'total pooled ether') + assertBn(newTotalPooledEther, ETH(33 + 96 + mevAmount), 'total pooled ether') // Ether2 stat reported by the pool changed correspondingly @@ -388,13 +392,13 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.beaconBalance, ETH(96), 'remote ether2') // Buffered Ether amount changed on MEV/tx rewards - assertBn(await pool.getBufferedEther(), ETH(33 + 9), 'buffered ether') + assertBn(await pool.getBufferedEther(), ETH(33 + mevAmount), 'buffered ether') // New tokens was minted to distribute fee - assertBn(await token.totalSupply(), tokens(129 + 9), 'token total supply') + assertBn(await token.totalSupply(), tokens(129 + mevAmount), 'token total supply') - const reward = toBN(ETH(96 - 64 + 9)) - const mintedAmount = new BN(totalFeePoints).mul(reward).divn(10000) + const reward = toBN(ETH(96 - 64 + mevAmount)) + const mintedAmount = new BN(totalFeePoints).mul(reward).divn(TOTAL_BASIS_POINTS) // Token user balances increased assertBn(await token.balanceOf(user1), new BN('4255360824742268041'), 'user1 tokens') @@ -653,9 +657,6 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.totalSupply(), tokens(142), 'token total supply') - const reward = toBN(0) - const mintedAmount = new BN(0) - // All of the balances should be decreased with proportion of newTotalPooledEther/oldTotalPooledEther (which is <1) // cause shares per user and overall shares number are preserved assertBn(await token.balanceOf(user1), new BN('4378704616763783056'), 'user1 tokens') @@ -711,9 +712,6 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.totalSupply(), tokens(142 + 5), 'token total supply') - const reward = toBN(ETH(5)) - const mintedAmount = new BN(totalFeePoints).mul(reward).divn(10000) - // Token user balances increased assertBn(await token.balanceOf(user1), new BN('4531342559390407888'), 'user1 tokens') assertBn(await token.balanceOf(user2), new BN('45313425593904078888'), 'user2 tokens') @@ -727,7 +725,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn((await token.balanceOf(insuranceAddr)).divn(10), new BN('9731811594202898'), 'insurance tokens') // The node operators' fee is distributed between all active node operators, - // proprotional to their effective stake (the amount of Ether staked by the operator's + // proportional to their effective stake (the amount of Ether staked by the operator's // used and non-stopped validators). // // In our case, both node operators received the same fee since they have the same @@ -735,4 +733,61 @@ contract('Lido: merge acceptance', (addresses) => { assertBn((await token.balanceOf(nodeOperator1.address)).divn(10), new BN('12164764492753623'), 'operator_1 tokens') assertBn((await token.balanceOf(nodeOperator2.address)).divn(10), new BN('12164764492753623'), 'operator_2 tokens') }) + + it('collect 0.1 ETH mevTxFee rewards to mevVault and withdraw it entirely by means of multiple oracle reports (+1 ETH)', async () => { + const toNum = (bn) => { + return +bn.toString() + } + const toE18 = (x) => { + return x * 1e18 + } + const fromNum = (x) => { + return new BN(String(x)) + } + + const mevAmount = toE18(0.1) + await rewarder.reward({ from: userMev, value: fromNum(mevAmount) }) + assertBn(await web3.eth.getBalance(mevVault.address), fromNum(mevAmount), 'MEV vault balance') + + await pool.setMevTxFeeWithdrawalLimit(3, { from: voting }) + + const mevWithdrawalLimitPoints = toNum(await pool.getMevTxFeeWithdrawalLimitPoints()) + + let lastBeaconBalance = toE18(85) + let mevVaultBalance = toNum(await web3.eth.getBalance(mevVault.address)) + let totalPooledEther = toNum(await pool.getTotalPooledEther()) + let bufferedEther = toNum(await pool.getBufferedEther()) + let totalSupply = toNum(await pool.totalSupply()) + const beaconBalanceInc = toE18(1) + let epoch = 106 + let mevWithdrawn = 0 + + // Do multiple oracle reports to withdraw all ETH from MEV Tx Fee Vault + while (mevVaultBalance > 0) { + const maxMevAmountPerWithdrawal = Math.floor(((totalPooledEther + beaconBalanceInc) * mevWithdrawalLimitPoints) / TOTAL_BASIS_POINTS) + const mevToWithdraw = Math.min(maxMevAmountPerWithdrawal, mevVaultBalance) + + // Reporting balance increase + await oracleMock.reportBeacon(epoch, 2, fromNum(lastBeaconBalance + beaconBalanceInc)) + + assertBn(await web3.eth.getBalance(mevVault.address), mevVaultBalance - mevToWithdraw, 'MEV vault balance') + + assertBn(await pool.getTotalPooledEther(), totalPooledEther + beaconBalanceInc + mevToWithdraw, 'total pooled ether') + + assertBn(await pool.totalSupply(), totalSupply + beaconBalanceInc + mevToWithdraw, 'token total supply') + + assertBn(await pool.getBufferedEther(), bufferedEther + mevToWithdraw, 'buffered ether') + + mevVaultBalance = toNum(await web3.eth.getBalance(mevVault.address)) + totalPooledEther = toNum(await pool.getTotalPooledEther()) + bufferedEther = toNum(await pool.getBufferedEther()) + totalSupply = toNum(await pool.totalSupply()) + + lastBeaconBalance += beaconBalanceInc + epoch += 1 + mevWithdrawn += mevToWithdraw + } + + assert.equal(mevWithdrawn, mevAmount) + }) }) From 126d8097af5fd98120948afa55b609117b286a0c Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 28 Feb 2022 23:01:57 +0300 Subject: [PATCH 083/159] update mev deploy scripts --- scripts/multisig/26-deploy-mev-vault.js | 2 +- scripts/multisig/28-vote-mev-upgrade.js | 46 +++++++++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/scripts/multisig/26-deploy-mev-vault.js b/scripts/multisig/26-deploy-mev-vault.js index bffd71ac5..e7e962f5a 100644 --- a/scripts/multisig/26-deploy-mev-vault.js +++ b/scripts/multisig/26-deploy-mev-vault.js @@ -25,7 +25,7 @@ async function upgradeApp({ web3, artifacts }) { const args = [ lidoAddress, ] - await saveDeployTx(appArtifact, `tx-21-deploy-mev-vault.json`, { + await saveDeployTx(appArtifact, `tx-26-deploy-mev-vault.json`, { arguments: args, from: DEPLOYER || state.multisigAddress }) diff --git a/scripts/multisig/28-vote-mev-upgrade.js b/scripts/multisig/28-vote-mev-upgrade.js index b35ea36e7..c61e222f1 100644 --- a/scripts/multisig/28-vote-mev-upgrade.js +++ b/scripts/multisig/28-vote-mev-upgrade.js @@ -45,11 +45,14 @@ async function createVoting({ web3, artifacts }) { const oracle = await artifacts.require('LidoOracle').at(oracleAddress) const mevTxFeeVaultAddress = state.mevTxFeeVaultAddress + const mevTxFeeWithdrawalLimitPoints = 3 + log(`Using ENS:`, yl(state.ensAddress)) log(`TokenManager address:`, yl(tokenManagerAddress)) log(`Voting address:`, yl(votingAddress)) - log(`Kernel`, yl(kernel.address)) - log(`ACL`, yl(acl.address)) + log(`Kernel:`, yl(kernel.address)) + log(`ACL:`, yl(acl.address)) + log(`mevTxFeeWithdrawalLimitPoints: `, yl(mevTxFeeWithdrawalLimitPoints)) log.splitter() @@ -57,7 +60,7 @@ async function createVoting({ web3, artifacts }) { const oracleUpgradeCallData = await buildUpgradeTransaction('oracle', state, ens, kernel, 2) - const grantRoleCallData = { + const grantSetMevVaultRoleCallData = { to: aclAddress, calldata: await acl.contract.methods .createPermission( @@ -69,11 +72,28 @@ async function createVoting({ web3, artifacts }) { .encodeABI() } - const setMevTxFeeVaultToLidoCallData = { + const grantSetMevWithdrawalLimitRoleCallData = { + to: aclAddress, + calldata: await acl.contract.methods + .createPermission( + votingAddress, + state[`app:lido`].proxyAddress, + web3.utils.soliditySha3('SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE'), + votingAddress + ) + .encodeABI() + } + + const setMevTxFeeVaultCallData = { to: lidoAddress, calldata: await lido.contract.methods.setMevTxFeeVault(mevTxFeeVaultAddress).encodeABI() } + const setMevWithdrawalLimitCallData = { + to: lidoAddress, + calldata: await lido.contract.methods.setMevTxFeeWithdrawalLimit(mevTxFeeWithdrawalLimitPoints).encodeABI() + } + const updateOracleVersionToV3CallData = { to: oracleAddress, calldata: await oracle.contract.methods.finalizeUpgrade_v3().encodeABI() @@ -83,8 +103,10 @@ async function createVoting({ web3, artifacts }) { ...lidoUpgradeCallData, ...oracleUpgradeCallData, updateOracleVersionToV3CallData, - grantRoleCallData, - setMevTxFeeVaultToLidoCallData, + grantSetMevVaultRoleCallData, + grantSetMevWithdrawalLimitRoleCallData, + setMevTxFeeVaultCallData, + setMevWithdrawalLimitCallData, ]) log(`encodedUpgradeCallData:`, yl(encodedUpgradeCallData)) @@ -95,14 +117,16 @@ async function createVoting({ web3, artifacts }) { } ]) - const txName = `tx-23-deploy-mev-upgrade.json` + const txName = `tx-28-deploy-mev-upgrade.json` const votingDesc = `1) Publishing new implementation in lido app APM repo -2) Updating implementaion of lido app with new one +2) Updating implementation of lido app with new one 3) Publishing new implementation in oracle app APM repo -4) Updating implementaion of oracle app with new one +4) Updating implementation of oracle app with new one 5) Call Oracle's finalizeUpgrade_v3() to update internal version counter -5) Grant role SENT_MEV_TX_FEE_VAULT_ROLE to voting -6) Set deployed MevTxFeeVault to Lido contract` +5) Grant role SET_MEV_TX_FEE_VAULT_ROLE to voting +6) Grant role SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE to voting +7) Set deployed MevTxFeeVault to Lido contract +8) Set MevTxFee Withdrawal Limit to ${mevTxFeeWithdrawalLimitPoints} basis points` await saveCallTxData(votingDesc, tokenManager, 'forward', txName, { arguments: [votingCallData], From 1427cb028dfefff05835dfc2294be499faaa4a1f Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 28 Feb 2022 23:04:07 +0300 Subject: [PATCH 084/159] Lido.sol: make setMevTxFeeWithdrawalLimit a no-op if value is the same --- contracts/0.4.24/Lido.sol | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 14976eb86..775e64858 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -93,7 +93,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); - /// @dev percent in points of total pooled ether allowed to withdraw from MevTxFeeVault per LidoOracle report + /// @dev percent in basis points of total pooled ether allowed to withdraw from MevTxFeeVault per LidoOracle report bytes32 internal constant MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS = keccak256("lido.Lido.mevTxFeeWithdrawalLimitPoints"); /// @dev Just a counter of total amount of MEV and transaction rewards received by Lido contract @@ -297,12 +297,15 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @dev Sets limit to amount of ETH to withdraw per LidoOracle report - * @param _limitPoints limit in points to amount of ETH to withdraw per LidoOracle report + * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ function setMevTxFeeWithdrawalLimit(uint256 _limitPoints) external auth(SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE) { - require(_limitPoints <= TOTAL_BASIS_POINTS); - MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.setStorageUint256(_limitPoints); - emit MevTxFeeWithdrawalLimitSet(_limitPoints); + require(_limitPoints <= TOTAL_BASIS_POINTS, "INVALID_POINTS_AMOUNT"); + + if (_limitPoints != MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256()) { + MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.setStorageUint256(_limitPoints); + emit MevTxFeeWithdrawalLimitSet(_limitPoints); + } } /** @@ -461,8 +464,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Get limit in points to amount of ETH to withdraw per LidoOracle report - * @return uint256 limit in points to amount of ETH to withdraw per LidoOracle report + * @notice Get limit in basis points to amount of ETH to withdraw per LidoOracle report + * @return uint256 limit in basis points to amount of ETH to withdraw per LidoOracle report */ function getMevTxFeeWithdrawalLimitPoints() external view returns (uint256) { return MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256(); From b1fd2dc424567f206ea690e1a708f4584fe5231f Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 28 Feb 2022 23:07:00 +0300 Subject: [PATCH 085/159] improve scenario test for mev withdrawal limit --- contracts/0.4.24/interfaces/ILido.sol | 2 +- hardhat.config.js | 1 + test/0.4.24/lido.test.js | 36 ++++++++++++++++----- test/scenario/mev_tx_fee_after_the_merge.js | 24 +++++++++++--- 4 files changed, 49 insertions(+), 14 deletions(-) diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 1555f064f..ea1d1a90f 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -101,7 +101,7 @@ interface ILido { // The amount of ETH withdrawn from LidoMevTxFeeVault contract to Lido contract event MevTxFeeReceived(uint256 amount); - // Percent in points of total pooled ether allowed to withdraw from MevTxFeeVault per LidoOracle report + // Percent in basis points of total pooled ether allowed to withdraw from MevTxFeeVault per LidoOracle report event MevTxFeeWithdrawalLimitSet(uint256 limitPoints); /** diff --git a/hardhat.config.js b/hardhat.config.js index 599adddfe..c2bd495aa 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -9,6 +9,7 @@ require('@nomiclabs/hardhat-ganache') require('@nomiclabs/hardhat-etherscan') require('hardhat-gas-reporter') require('solidity-coverage') +require('hardhat-contract-sizer') const NETWORK_NAME = getNetworkName() const ETH_ACCOUNT_NAME = process.env.ETH_ACCOUNT_NAME diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index b976e8f58..02a6c17d5 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -3,7 +3,7 @@ const { assert } = require('chai') const { newDao, newApp } = require('./helpers/dao') const { getInstalledApp } = require('@aragon/contract-helpers-test/src/aragon-os') const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') -const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') +const { ZERO_ADDRESS, bn, getEventAt } = require('@aragon/contract-helpers-test') const { BN } = require('bn.js') const { ethers } = require('ethers') const { formatEther } = require('ethers/lib/utils') @@ -42,6 +42,11 @@ const hexConcat = (first, ...rest) => { return result } +const assertNoEvent = (receipt, eventName, msg) => { + const event = getEventAt(receipt, eventName) + assert.equal(event, undefined, msg) +} + // Divides a BN by 1e15 const div15 = (bn) => bn.div(new BN('1000000000000000')) @@ -261,6 +266,21 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.balanceOf(user2), STETH(depositAmount + shareOfRewardsForStakers * (mevAmount + beaconRewards))) }) + it('Attempt to set invalid MEV Tx Fee withdrawal limit', async () => { + const initialValue = await app.getMevTxFeeWithdrawalLimitPoints() + + assertEvent(await app.setMevTxFeeWithdrawalLimit(1, { from: voting }), 'MevTxFeeWithdrawalLimitSet', { + expectedArgs: { limitPoints: 1 } + }) + + await assertNoEvent(app.setMevTxFeeWithdrawalLimit(1, { from: voting }), 'MevTxFeeWithdrawalLimitSet') + + await app.setMevTxFeeWithdrawalLimit(10000, { from: voting }) + await assertRevert(app.setMevTxFeeWithdrawalLimit(10001, { from: voting }), 'INVALID_POINTS_AMOUNT') + + await app.setMevTxFeeWithdrawalLimit(initialValue, { from: voting }) + }) + it('setFee works', async () => { await app.setFee(110, { from: voting }) await assertRevert(app.setFee(110, { from: user1 }), 'APP_AUTH_FAILED') @@ -605,7 +625,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.getBufferedEther(), ETH(5)) }) - it('withrawal method reverts', async () => { + it('withdrawal method reverts', async () => { await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) @@ -835,7 +855,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(34) }) await app.methods['depositBufferedEther()']({ from: depositor }) - // some slashing occured + // some slashing occurred await oracle.reportBeacon(100, 1, ETH(30)) await checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(30) }) @@ -1188,7 +1208,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('burnShares works', async () => { await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(1) }) - // not permited from arbitary address + // not permitted from arbitrary address await assertRevert(app.burnShares(user1, ETH(1), { from: nobody }), 'APP_AUTH_FAILED') // voting can burn shares of any user @@ -1211,11 +1231,11 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) context('treasury', () => { - it('treasury adddress has been set after init', async () => { + it('treasury address has been set after init', async () => { assert.notEqual(await app.getTreasury(), ZERO_ADDRESS) }) - it(`treasury can't be set by an arbitary address`, async () => { + it(`treasury can't be set by an arbitrary address`, async () => { await assertRevert(app.setTreasury(user1, { from: nobody })) await assertRevert(app.setTreasury(user1, { from: user1 })) }) @@ -1232,11 +1252,11 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) context('insurance fund', () => { - it('insurance fund adddress has been set after init', async () => { + it('insurance fund address has been set after init', async () => { assert.notEqual(await app.getInsuranceFund(), ZERO_ADDRESS) }) - it(`insurance fund can't be set by an arbitary address`, async () => { + it(`insurance fund can't be set by an arbitrary address`, async () => { await assertRevert(app.setInsuranceFund(user1, { from: nobody })) await assertRevert(app.setInsuranceFund(user1, { from: user1 })) }) diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/mev_tx_fee_after_the_merge.js index 989e6d5c3..ef60fcbe2 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/mev_tx_fee_after_the_merge.js @@ -745,25 +745,39 @@ contract('Lido: merge acceptance', (addresses) => { return new BN(String(x)) } + // Specify different withdrawal limits for a few epochs to test different values + const getMevWithdrawalLimitFromEpoch = (_epoch) => { + if (_epoch === 106) { + return 2 + } else if (_epoch === 107) { + return 0 + } else { + return 3 + } + } + const mevAmount = toE18(0.1) await rewarder.reward({ from: userMev, value: fromNum(mevAmount) }) assertBn(await web3.eth.getBalance(mevVault.address), fromNum(mevAmount), 'MEV vault balance') - await pool.setMevTxFeeWithdrawalLimit(3, { from: voting }) - - const mevWithdrawalLimitPoints = toNum(await pool.getMevTxFeeWithdrawalLimitPoints()) - + let epoch = 106 let lastBeaconBalance = toE18(85) + await pool.setMevTxFeeWithdrawalLimit(getMevWithdrawalLimitFromEpoch(epoch), { from: voting }) + + let mevWithdrawalLimitPoints = toNum(await pool.getMevTxFeeWithdrawalLimitPoints()) let mevVaultBalance = toNum(await web3.eth.getBalance(mevVault.address)) let totalPooledEther = toNum(await pool.getTotalPooledEther()) let bufferedEther = toNum(await pool.getBufferedEther()) let totalSupply = toNum(await pool.totalSupply()) const beaconBalanceInc = toE18(1) - let epoch = 106 let mevWithdrawn = 0 // Do multiple oracle reports to withdraw all ETH from MEV Tx Fee Vault while (mevVaultBalance > 0) { + const mevWithdrawalLimit = getMevWithdrawalLimitFromEpoch(epoch) + await pool.setMevTxFeeWithdrawalLimit(mevWithdrawalLimit, { from: voting }) + mevWithdrawalLimitPoints = toNum(await pool.getMevTxFeeWithdrawalLimitPoints()) + const maxMevAmountPerWithdrawal = Math.floor(((totalPooledEther + beaconBalanceInc) * mevWithdrawalLimitPoints) / TOTAL_BASIS_POINTS) const mevToWithdraw = Math.min(maxMevAmountPerWithdrawal, mevVaultBalance) From 0d0b2c7e5a0dc6ff1921607b4d460263c4768ac8 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Thu, 31 Mar 2022 15:14:50 +0400 Subject: [PATCH 086/159] feat: deploy kiln --- arapp.json | 5 + deployed-kiln.json | 213 +++++++++++++++++++ docker-compose.kiln.yml | 14 ++ hardhat.config.js | 8 + package.json | 6 + scripts/multisig/04-publish-app-frontends.js | 2 +- 6 files changed, 247 insertions(+), 1 deletion(-) create mode 100644 deployed-kiln.json create mode 100644 docker-compose.kiln.yml diff --git a/arapp.json b/arapp.json index 8dc75a4ce..cc55079a1 100644 --- a/arapp.json +++ b/arapp.json @@ -19,6 +19,11 @@ "registry": "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", "appName": "dao.lido.eth", "network": "mainnet" + }, + "kiln": { + "registry": "0xD3A23B83902066baC61e82bCe449fE1d3154Ab5D", + "appName": "dao.lido.eth", + "network": "kiln" } }, "appName": "dao.lido.eth" diff --git a/deployed-kiln.json b/deployed-kiln.json new file mode 100644 index 000000000..d78a1020b --- /dev/null +++ b/deployed-kiln.json @@ -0,0 +1,213 @@ +{ + "networkId": 1337802, + "ipfsAPI": "https://ipfs.infura.io:5001/api/v0", + "multisigAddress": "0x8c84909DE05702e1bA637fe05fddebA0FB3e3b21", + "lidoApmEnsName": "lidopm.eth", + "lidoApmEnsRegDurationSec": 94608000, + "daoAragonId": "lido-dao", + "daoInitialSettings": { + "voting": { + "minSupportRequired": "500000000000000000", + "minAcceptanceQuorum": "50000000000000000", + "voteDuration": 14400 + }, + "beaconSpec": { + "depositContractAddress": "0x4242424242424242424242424242424242424242", + "epochsPerFrame": 225, + "slotsPerEpoch": 32, + "secondsPerSlot": 12, + "genesisTime": 1647007200 + }, + "fee": { + "totalPercent": 10, + "treasuryPercent": 0, + "insurancePercent": 50, + "nodeOperatorsPercent": 50 + }, + "token": { + "name": "TEST Lido DAO Token", + "symbol": "TLDO" + } + }, + "vestingParams": { + "unvestedTokensAmount": "230000000000000000000000", + "holders": { + "0xa5F1d7D49F581136Cf6e58B32cBE9a2039C48bA1": "530000000000000000000000", + "0x00444797Ba158A7BdB8302e72da98dCbCCef0Fbc": "15000000000000000000000", + "0x9F188809b2ba2e29653693C4069c212a95C1B8c4": "15000000000000000000000", + "0xeC2d2E81721A477798e938924266f92c4E5Fc54e": "15000000000000000000000", + "0x401FD888B5E41113B7c0C47725A742bbc3A083EF": "15000000000000000000000", + "0x36c648351274bb4455ba6aAabF3F976824a93aF4": "15000000000000000000000", + "0xB6d0A3120c0e13749c211F48fccD9458Dfc99BD0": "15000000000000000000000", + "0x75936b355942D89d9B59798dacD70d70Ded93B78": "15000000000000000000000", + "0x39ceC2b3ba293CC15f15a3876dB8D356a1670789": "15000000000000000000000", + "0xC37F899BB002D93fdcbD9D91d42889AC0e29B704": "15000000000000000000000", + "0xc69f1606e0615F788Cb99982848528f39f188486": "15000000000000000000000", + "0xd355521db7f30F74059dFa7aE0A5C6543dFA8215": "15000000000000000000000", + "0xAA566f0eB53F26db5E15aC5B42685361B4e0d9F4": "15000000000000000000000", + "0xCd3975FC68Aa71eDebfef7442cF8e91ce407DEEE": "15000000000000000000000", + "0xfd0dd5c3b72528e8f581a8544309706d1c2d9206": "15000000000000000000000", + "0x161EE6a03D52ee176a514C88Cf07dcAcF248155f": "15000000000000000000000", + "0x11D7D72876cfC3d7CFC3d1a2C9c35EA4591694a7": "15000000000000000000000" + }, + "start": 1648627400, + "cliff": 1648628400, + "end": 1648628400, + "revokable": false + }, + "owner": "0x8c84909DE05702e1bA637fe05fddebA0FB3e3b21", + "aragonEnsLabelName": "aragonpm", + "ensAddress": "0xD3A23B83902066baC61e82bCe449fE1d3154Ab5D", + "ensFactoryAddress": "0x66Ca44dDb0B9Ba49C7a153A4108F46C95E4693dD", + "ensFactoryConstructorArgs": [], + "kernelBaseAddress": "0xf76235B924b6749cB62129b45c69771996D078B1", + "kernelBaseConstructorArgs": [ + true + ], + "aclBaseAddress": "0x31BbA6CCf72aA04f725602474614068fff2B3330", + "aclBaseConstructorArgs": [], + "evmScriptRegistryFactoryAddress": "0x19b424c26b36f2E7A9C6bA6d99243a2ECae7919F", + "evmScriptRegistryFactoryConstructorArgs": [], + "daoFactoryAddress": "0xF19b955B1aC41238eBc7411f3b75a157fe250df0", + "daoFactoryConstructorArgs": [ + "0xf76235B924b6749cB62129b45c69771996D078B1", + "0x31BbA6CCf72aA04f725602474614068fff2B3330", + "0x19b424c26b36f2E7A9C6bA6d99243a2ECae7919F" + ], + "apmRegistryBaseAddress": "0xfdac8a612d078c2F77b75992905275f2a06F0814", + "apmRepoBaseAddress": "0xD37598A90c531E5D35faa69D3706ECB6CE589E3A", + "ensSubdomainRegistrarBaseAddress": "0x79A5dcf2eC088639726aa3960B7Ea540466d4A42", + "apmRegistryFactoryAddress": "0xA160e5529Aa3cA0dE313B77C88671DD91D1C6B84", + "apmRegistryFactoryConstructorArgs": [ + "0xF19b955B1aC41238eBc7411f3b75a157fe250df0", + "0xfdac8a612d078c2F77b75992905275f2a06F0814", + "0xD37598A90c531E5D35faa69D3706ECB6CE589E3A", + "0x79A5dcf2eC088639726aa3960B7Ea540466d4A42", + "0xD3A23B83902066baC61e82bCe449fE1d3154Ab5D", + "0x0000000000000000000000000000000000000000" + ], + "aragonApmRegistryAddress": "0x1618d119c028f418Ab96D8096172aa8848DDBe36", + "aragonEnsNodeName": "aragonpm.eth", + "aragonEnsNode": "0x9065c3e7f7b7ef1ef4e53d2d0b8e0cef02874ab020c1ece79d5f0d3d0111c0ba", + "miniMeTokenFactoryAddress": "0x7DcaFCB80fDB46faEb501A5c5695771F938F47DC", + "miniMeTokenFactoryConstructorArgs": [], + "aragonIDAddress": "0x5B1510775E7186177b3f1fB3d36161276FE52C55", + "aragonIDConstructorArgs": [ + "0xD3A23B83902066baC61e82bCe449fE1d3154Ab5D", + "0xfa6574301a64d05838FE48A32bB415D5D35caF09", + "0x7e74a86b6e146964fb965db04dc2590516da77f720bb6759337bf5632415fd86" + ], + "aragonIDEnsNodeName": "aragonid.eth", + "aragonIDEnsNode": "0x7e74a86b6e146964fb965db04dc2590516da77f720bb6759337bf5632415fd86", + "app:agent": { + "fullName": "agent.aragonpm.eth", + "name": "agent", + "id": "0x9ac98dc5f995bf0211ed589ef022719d1487e5cb2bab505676f0d084c07cf89a", + "baseAddress": "0xa6b2449ef19191e06fde16bbd9a6A780820BBe01", + "ipfsCid": "Qmd32Lx4BbyfWKAvs9QprTDjWDnroWwwm9uRfRdwXTgS7T" + }, + "app:finance": { + "fullName": "finance.aragonpm.eth", + "name": "finance", + "id": "0xbf8491150dafc5dcaee5b861414dca922de09ccffa344964ae167212e8c673ae", + "baseAddress": "0x8E33218b90fD89c291882A3F26547Ecb76Fa6DFe", + "ipfsCid": "QmVWUzVwUV8gQFEdpw4Y5tAsN1zmPFqHtUUbNAD9upADsF" + }, + "app:token-manager": { + "fullName": "token-manager.aragonpm.eth", + "name": "token-manager", + "id": "0x6b20a3010614eeebf2138ccec99f028a61c811b3b1a3343b6ff635985c75c91f", + "baseAddress": "0x3F3A9f7E807C5CdFDeD57322907f8CD8D26C3Ab1", + "ipfsCid": "QmVA6TDcrEVHathVXLvYmRP944YLZpXsXUEoWqD9UwCVET" + }, + "app:vault": { + "fullName": "vault.aragonpm.eth", + "name": "vault", + "id": "0x7e852e0fcfce6551c13800f1e7476f982525c2b5277ba14b24339c68416336d1", + "baseAddress": "0x6b054aa056a381eFA094b4e96e39bb3F1856F108", + "ipfsCid": "QmQb95FxdzDZPX5rEnmdKzK2JLcntQWf2k7qvsPdmqr3Do" + }, + "app:voting": { + "fullName": "voting.aragonpm.eth", + "name": "voting", + "id": "0x9fa3927f639745e587912d4b0fea7ef9013bf93fb907d29faeab57417ba6e1d4", + "baseAddress": "0x48787380A54BDD6F058E4CFF3c35c1D546aa018B", + "ipfsCid": "QmZF7TCstYWRTr9QUV6CJ8bM2Zo5YMXqzagtf5VqY9teLe" + }, + "depositContractAddress": "0x4242424242424242424242424242424242424242", + "daoTemplateConstructorArgs": [ + "0x8c84909DE05702e1bA637fe05fddebA0FB3e3b21", + "0xF19b955B1aC41238eBc7411f3b75a157fe250df0", + "0xD3A23B83902066baC61e82bCe449fE1d3154Ab5D", + "0x7DcaFCB80fDB46faEb501A5c5695771F938F47DC", + "0x5B1510775E7186177b3f1fB3d36161276FE52C55", + "0xA160e5529Aa3cA0dE313B77C88671DD91D1C6B84" + ], + "daoTemplateDeployTx": "0x4437237c1b23538d48416ce38043e1030d71aa15a51e4e7af2205e7e08e64e33", + "lidoBaseDeployTx": "0xc1780accef35fab68aa7b62b09f8ae269690c8455d2704d1f2e5de367b89544e", + "oracleBaseDeployTx": "0x5680258689872a551dee4b67b117fff0bf75633e045b574c881d228357a5a190", + "nodeOperatorsRegistryBaseDeployTx": "0xe7449e5f98a6a20a44f5f70416a7a4191b559fcc5bc8ef7ab52cf178b0604dce", + "daoTemplateAddress": "0xaD07B585F279A6b4F693fB5c81be3920b76a45EC", + "daoTemplateDeployBlock": 161005, + "app:lido": { + "baseAddress": "0xD9f9Da698b1F09BcfC46Ec24A489ff0b1FCF74f0", + "fullName": "lido.lidopm.eth", + "name": "lido", + "id": "0x3ca7c3e38968823ccb4c78ea688df41356f182ae1d159e4ee608d30d68cef320", + "ipfsCid": "QmTtpuoZdv3i72DDfcHxoYywVNATVJEsBv6iipaEKDSi5d", + "contentURI": "0x697066733a516d547470756f5a6476336937324444666348786f597977564e4154564a457342763669697061454b4453693564", + "proxyAddress": "0x3E50180cf438e185ec52Ade55855718584541476" + }, + "app:oracle": { + "baseAddress": "0x92dfD7E1643A3Ab7eBBC68F399F4a62FabAaEa23", + "fullName": "oracle.lidopm.eth", + "name": "oracle", + "id": "0x8b47ba2a8454ec799cd91646e7ec47168e91fd139b23f017455f3e5898aaba93", + "ipfsCid": "QmYJVQW83CGmYpRtZNREz7mioXddzXxdd1z6g62oRtxWB8", + "contentURI": "0x697066733a516d594a565157383343476d597052745a4e52457a376d696f5864647a58786464317a366736326f527478574238", + "proxyAddress": "0x9633df55696561C4Cf8A260316A6e08113FA2164" + }, + "app:node-operators-registry": { + "baseAddress": "0xaF87d30A3463280b486B239C7DecC4390C84DCBC", + "fullName": "node-operators-registry.lidopm.eth", + "name": "node-operators-registry", + "id": "0x7071f283424072341f856ac9e947e7ec0eb68719f757a7e785979b6b8717579d", + "ipfsCid": "QmdS4HqNbRumzR1g5aebPzTf5K7A8MrA6Y4CV8ZuGfcUFq", + "contentURI": "0x697066733a516d64533448714e6252756d7a52316735616562507a5466354b3741384d72413659344356385a75476663554671", + "proxyAddress": "0xb849C5b35DC45277Bad5c6F2c6C77183d842367c" + }, + "lidoApmDeployArguments": [ + "0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae", + "0x90a9580abeb24937fc658e497221c81ce8553b560304f9525821f32b17dbdaec" + ], + "lidoApmDeployTx": "0x26af347abc33a16678a9f45b55cdfa8241f0d48de9862ece61da3e06158616c6", + "lidoApmAddress": "0xaC1719341a8b0AC8fbd058Ccf40e0e2eA20Cc06e", + "createAppReposTx": "0x056ea8c943ada1fbf8dd27f3322d5fc9dfa3b6e9de206f2128b58a1865712e9b", + "newDaoTx": "0x257d02e1cc52e4cf1d66da79ab23b0a256f3180950389174e5aa4c29dfc4f1af", + "daoAddress": "0x1c7E1226D5D7f276b80c1e42cf53F753a431ea9e", + "daoTokenAddress": "0xA463D98281b5126D501d22F0719a13BfE092688d", + "app:aragon-agent": { + "name": "aragon-agent", + "fullName": "aragon-agent.lidopm.eth", + "id": "0x701a4fd1f5174d12a0f1d9ad2c88d0ad11ab6aad0ac72b7d9ce621815f8016a9", + "proxyAddress": "0xb98B21f9725F1deF45500c5D2D4D48eC5DF4B6AF" + }, + "app:aragon-finance": { + "name": "aragon-finance", + "fullName": "aragon-finance.lidopm.eth", + "id": "0x5c9918c99c4081ca9459c178381be71d9da40e49e151687da55099c49a4237f1", + "proxyAddress": "0xA9024A812da051D4c947dcbBB745FD8051edF6e5" + }, + "app:aragon-token-manager": { + "name": "aragon-token-manager", + "fullName": "aragon-token-manager.lidopm.eth", + "id": "0xcd567bdf93dd0f6acc3bc7f2155f83244d56a65abbfbefb763e015420102c67b", + "proxyAddress": "0x6AB36E8B1c6Cb572F910A7e50204F41f98D5de74" + }, + "app:aragon-voting": { + "name": "aragon-voting", + "fullName": "aragon-voting.lidopm.eth", + "id": "0x0abcd104777321a82b010357f20887d61247493d89d2e987ff57bcecbde00e1e", + "proxyAddress": "0x84633bf8e3AE2508a81F09F46a47774F5aFD16B9" + } +} diff --git a/docker-compose.kiln.yml b/docker-compose.kiln.yml new file mode 100644 index 000000000..e6c181a49 --- /dev/null +++ b/docker-compose.kiln.yml @@ -0,0 +1,14 @@ +version: '3.7' +services: + aragon: + build: + context: ./docker/aragon/ + args: + - ARAGON_IPFS_GATEWAY=https://ipfs.infura.io/ipfs + - ARAGON_DEFAULT_ETH_NODE=ws://34.159.167.0:8546 + - ARAGON_APP_LOCATOR=ipfs + - ARAGON_ETH_NETWORK_TYPE=local + - ARAGON_ENS_REGISTRY_ADDRESS=0xD3A23B83902066baC61e82bCe449fE1d3154Ab5D + container_name: aragon + ports: + - 3000:8080 diff --git a/hardhat.config.js b/hardhat.config.js index c2bd495aa..a2999340e 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -56,6 +56,14 @@ const getNetConfig = (networkName, ethAccountName) => { // gas: 10000000, gasPrice: 2000000000 }, + kiln: { + ...base, + accounts: accounts.eth.kiln, + url: 'http://34.159.167.0:8545', + chainId: 1337802, + // gas: 10000000, + gasPrice: 2000000000 + }, // local local: { ...base, diff --git a/package.json b/package.json index b2d024d47..75f453740 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,12 @@ "deploy:kintsugi:apps": "NETWORK_NAME=kintsugi NETWORK_STATE_FILE=deployed-kintsugi.json IPFS_API_URL=https://ipfs.infura.io:5001/api/v0 IPFS_GATEWAY_URL=https://ipfs.io hardhat run --no-compile ./scripts/deploy-lido-apps.js", "deploy:kintsugi:perm": "NETWORK_NAME=kintsugi NETWORK_STATE_FILE=deployed-kintsugi.json hardhat run --no-compile ./scripts/deploy-repo-permissions.js", "deploy:kintsugi:dao": "NETWORK_NAME=kintsugi NETWORK_STATE_FILE=deployed-kintsugi.json hardhat run --no-compile ./scripts/deploy-lido-dao.js", + "deploy:kiln:aragon-env": "NETWORK_NAME=kiln NETWORK_STATE_FILE=deployed-kiln.json hardhat run --no-compile ./scripts/deploy-aragon-env.js", + "deploy:kiln:aragon-std-apps": "NETWORK_NAME=kiln NETWORK_STATE_FILE=deployed-kiln.json IPFS_API_URL=https://ipfs.infura.io:5001/api/v0 IPFS_GATEWAY_URL=https://ipfs.io RELEASE_TYPE=major hardhat run --no-compile ./scripts/deploy-aragon-std-apps.js --config ./hardhat.config.aragon-apps.js", + "deploy:kiln:apm-and-template": "NETWORK_NAME=kiln NETWORK_STATE_FILE=deployed-kiln.json hardhat run --no-compile ./scripts/deploy-lido-apm-and-template.js", + "deploy:kiln:apps": "NETWORK_NAME=kiln NETWORK_STATE_FILE=deployed-kiln.json IPFS_API_URL=https://ipfs.infura.io:5001/api/v0 IPFS_GATEWAY_URL=https://ipfs.io hardhat run --no-compile ./scripts/deploy-lido-apps.js", + "deploy:kiln:perm": "NETWORK_NAME=kiln NETWORK_STATE_FILE=deployed-kiln.json hardhat run --no-compile ./scripts/deploy-repo-permissions.js", + "deploy:kiln:dao": "NETWORK_NAME=kiln NETWORK_STATE_FILE=deployed-kiln.json hardhat run --no-compile ./scripts/deploy-lido-dao.js", "deploy:local:all": "yarn compile && yarn deploy:local:aragon-env && yarn deploy:local:aragon-std-apps && yarn deploy:local:apm-and-template && yarn deploy:local:apps && yarn deploy:local:dao", "deploy:local:aragon-env": "NETWORK_NAME=local NETWORK_STATE_FILE=deployed-local.json hardhat run --no-compile ./scripts/deploy-aragon-env.js", "deploy:local:aragon-std-apps": "NETWORK_NAME=local NETWORK_STATE_FILE=deployed-local.json IPFS_API_URL=http://127.0.0.1:5001/api/v0 IPFS_GATEWAY_URL=http://127.0.0.1:8080 RELEASE_TYPE=major hardhat run --no-compile ./scripts/deploy-aragon-std-apps.js --config ./hardhat.config.aragon-apps.js", diff --git a/scripts/multisig/04-publish-app-frontends.js b/scripts/multisig/04-publish-app-frontends.js index 0459c7b03..72814d586 100644 --- a/scripts/multisig/04-publish-app-frontends.js +++ b/scripts/multisig/04-publish-app-frontends.js @@ -17,7 +17,7 @@ const { readJSON } = require('../helpers/fs') require('@aragon/buidler-aragon/dist/bootstrap-paths') const { generateArtifacts } = require('@aragon/buidler-aragon/dist/src/utils/artifact/generateArtifacts') -const { uploadDirToIpfs } = require('../helpers/ipfs') +const { uploadDirToIpfs } = require('@aragon/buidler-aragon/dist/src/utils/ipfs') const { toContentUri } = require('@aragon/buidler-aragon/dist/src/utils/apm/utils') const { APP_NAMES } = require('./constants') From ffd27f95e6ff048a83b4cbbe2056923c6312ac12 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 4 Apr 2022 14:49:20 +0400 Subject: [PATCH 087/159] feat: mev vault deploy for kiln --- deployed-kiln.json | 28 +++++++++++++++++++++---- scripts/multisig/26-deploy-mev-vault.js | 8 +++---- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/deployed-kiln.json b/deployed-kiln.json index d78a1020b..5dafc1060 100644 --- a/deployed-kiln.json +++ b/deployed-kiln.json @@ -61,9 +61,7 @@ "ensFactoryAddress": "0x66Ca44dDb0B9Ba49C7a153A4108F46C95E4693dD", "ensFactoryConstructorArgs": [], "kernelBaseAddress": "0xf76235B924b6749cB62129b45c69771996D078B1", - "kernelBaseConstructorArgs": [ - true - ], + "kernelBaseConstructorArgs": [true], "aclBaseAddress": "0x31BbA6CCf72aA04f725602474614068fff2B3330", "aclBaseConstructorArgs": [], "evmScriptRegistryFactoryAddress": "0x19b424c26b36f2E7A9C6bA6d99243a2ECae7919F", @@ -134,6 +132,25 @@ "baseAddress": "0x48787380A54BDD6F058E4CFF3c35c1D546aa018B", "ipfsCid": "QmZF7TCstYWRTr9QUV6CJ8bM2Zo5YMXqzagtf5VqY9teLe" }, + "depositorParams": { + "maxDepositsPerBlock": 100, + "minDepositBlockDistance": 14, + "pauseIntentValidityPeriodBlocks": 10, + "guardians": [ + "0x3dc4cf780f2599b528f37dedb34449fb65ef7d4a", + "0x401fd888b5e41113b7c0c47725a742bbc3a083ef", + "0x3db460f33838fdc91e804d7fce2adf9e61e9d654", + "0x96fd3d127abd0d77724d49b7bddecdc89f684bb6", + "0xda1a296f9df18d04e0aefcff658b80b3ef824ec9", + "0xc34d33b95d7d6df2255d6051ddb12f8bd7aef64c", + "0xad5eecc863d88d5d848b89aa8517388a3ee432c8", + "0x6c83f70ed019fdcafe6c6f78cb33ce15943e4cb9", + "0x43464fe06c18848a2e2e913194d64c1970f4326a", + "0xf060ab3d5dcfdc6a0dfd5ca0645ac569b8f105ca", + "0x79a132be0c25ced09e745629d47cf05e531bb2bb" + ], + "quorum": 1 + }, "depositContractAddress": "0x4242424242424242424242424242424242424242", "daoTemplateConstructorArgs": [ "0x8c84909DE05702e1bA637fe05fddebA0FB3e3b21", @@ -209,5 +226,8 @@ "fullName": "aragon-voting.lidopm.eth", "id": "0x0abcd104777321a82b010357f20887d61247493d89d2e987ff57bcecbde00e1e", "proxyAddress": "0x84633bf8e3AE2508a81F09F46a47774F5aFD16B9" - } + }, + "mevTxFeeVaultDeployTx": "0x044a1aa15ed85bc58c9c27da391e74e3a21d0d5e47b9a657adb2b871627e88bd", + "mevTxFeeVaultAddress": "0xe3e01f9E940dDec242C3fdD7bbb855c3770bF999", + "mevTxFeeVaultUpgradeTx": "0xf6034ccfd75c67ce2392b6ddb1a7e6b2f3cced87cdf4747a86c295e857cd1c41" } diff --git a/scripts/multisig/26-deploy-mev-vault.js b/scripts/multisig/26-deploy-mev-vault.js index e7e962f5a..7c86376fd 100644 --- a/scripts/multisig/26-deploy-mev-vault.js +++ b/scripts/multisig/26-deploy-mev-vault.js @@ -8,7 +8,6 @@ const { APP_NAMES } = require('./constants') const DEPLOYER = process.env.DEPLOYER || '' const REQUIRED_NET_STATE = ['daoInitialSettings', 'depositorParams', `app:${APP_NAMES.LIDO}`, `app:${APP_NAMES.NODE_OPERATORS_REGISTRY}`] - async function upgradeApp({ web3, artifacts }) { const appArtifact = 'LidoMevTxFeeVault' const netId = await web3.eth.net.getId() @@ -22,9 +21,10 @@ async function upgradeApp({ web3, artifacts }) { log(`Using Lido address:`, yl(lidoAddress)) logSplitter() - const args = [ - lidoAddress, - ] + const lido = await artifacts.require('Lido').at(lidoAddress) + const treasuryAddr = await lido.getTreasury() + + const args = [lidoAddress, treasuryAddr] await saveDeployTx(appArtifact, `tx-26-deploy-mev-vault.json`, { arguments: args, from: DEPLOYER || state.multisigAddress From 9446913be0ba7279e7e073516b90434b575372ab Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 4 Apr 2022 15:05:11 +0400 Subject: [PATCH 088/159] fix: mev deploy script --- scripts/multisig/26-deploy-mev-vault.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/multisig/26-deploy-mev-vault.js b/scripts/multisig/26-deploy-mev-vault.js index e7e962f5a..7c86376fd 100644 --- a/scripts/multisig/26-deploy-mev-vault.js +++ b/scripts/multisig/26-deploy-mev-vault.js @@ -8,7 +8,6 @@ const { APP_NAMES } = require('./constants') const DEPLOYER = process.env.DEPLOYER || '' const REQUIRED_NET_STATE = ['daoInitialSettings', 'depositorParams', `app:${APP_NAMES.LIDO}`, `app:${APP_NAMES.NODE_OPERATORS_REGISTRY}`] - async function upgradeApp({ web3, artifacts }) { const appArtifact = 'LidoMevTxFeeVault' const netId = await web3.eth.net.getId() @@ -22,9 +21,10 @@ async function upgradeApp({ web3, artifacts }) { log(`Using Lido address:`, yl(lidoAddress)) logSplitter() - const args = [ - lidoAddress, - ] + const lido = await artifacts.require('Lido').at(lidoAddress) + const treasuryAddr = await lido.getTreasury() + + const args = [lidoAddress, treasuryAddr] await saveDeployTx(appArtifact, `tx-26-deploy-mev-vault.json`, { arguments: args, from: DEPLOYER || state.multisigAddress From d8de2eda62ad4f427f5e97d269025ac52731504c Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 13 Apr 2022 12:33:03 +0000 Subject: [PATCH 089/159] feat: add submits break Add submits break feature to temporary close staking for new customers in case of entry queue massive demands. --- contracts/0.4.24/Lido.sol | 34 +++++++++++++++++++++++++++ contracts/0.4.24/interfaces/ILido.sol | 15 ++++++++++++ lib/abi/Lido.json | 2 +- lib/abi/StETH.json | 2 +- test/0.4.24/lido.test.js | 26 ++++++++++++++++++++ 5 files changed, 77 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 775e64858..c83951104 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -50,6 +50,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /// ACL bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); + bytes32 constant public SUBMITS_BREAK_ROLE = keccak256("SUBMITS_BREAK_ROLE"); bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE"); bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY"); bytes32 constant public SET_ORACLE = keccak256("SET_ORACLE"); @@ -84,6 +85,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant INSURANCE_FUND_POSITION = keccak256("lido.Lido.insuranceFund"); bytes32 internal constant MEV_TX_FEE_VAULT_POSITION = keccak256("lido.Lido.mevTxFeeVault"); + /// @dev dedicated switch to cut-off staking new funds without pausing the whole protocol + bytes32 internal constant SUBMITS_BREAK_POSITION = keccak256("lido.Lido.submitsBreak"); /// @dev amount of Ether (on the current Ethereum side) buffered on this smart contract balance bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther"); /// @dev number of deposited validators (incrementing counter of deposit operations). @@ -133,6 +136,36 @@ contract Lido is ILido, IsContract, StETH, AragonApp { initialized(); } + /** + * @notice Cut-off new staking (every new submit transaction would revert if called) + * @dev Provides a way to stop staking without pushing PAUSE for the whole proto + * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue + */ + function stopSubmits() external auth(SUBMITS_BREAK_ROLE) { + require(!isSubmitsStopped(), "SUBMITS_ALREADY_STOPPED"); + SUBMITS_BREAK_POSITION.setStorageBool(true); + emit SubmitsStopped(); + } + + /** + * @notice Resume staking if `stopSubmits` was called previously (allow new submit transactions) + * See `stopSubmits` for the details. + */ + function resumeSubmits() external auth(SUBMITS_BREAK_ROLE) { + require(isSubmitsStopped(), "SUBMITS_ALREADY_RESUMED"); + SUBMITS_BREAK_POSITION.setStorageBool(false); + emit SubmitsResumed(); + } + + /** + * @notice check staking break state + * Returns true if new staking is cutted-off + * See `stopSubmits` for the details. + */ + function isSubmitsStopped() public view returns(bool) { + return SUBMITS_BREAK_POSITION.getStorageBool(); + } + /** * @notice Send funds to the pool * @dev Users are able to submit their funds by transacting to the fallback function. @@ -559,6 +592,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * @return amount of StETH shares generated */ function _submit(address _referral) internal whenNotStopped returns (uint256) { + require(!isSubmitsStopped(), "SUBMITS_STOPPED"); address sender = msg.sender; uint256 deposit = msg.value; require(deposit != 0, "ZERO_DEPOSIT"); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index ea1d1a90f..7d594e722 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -28,8 +28,23 @@ interface ILido { */ function resume() external; + /** + * @notice Cut-off new staking (every new submit transaction would revert if called) + * @dev Provides a way to stop staking without pushing PAUSE for the whole proto + * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue + */ + function stopSubmits() external; + + /** + * @notice Resume staking if `stopSubmits` was called previously (allow new submit transactions) + * See `stopSubmits` for the details. + */ + function resumeSubmits() external; + event Stopped(); event Resumed(); + event SubmitsStopped(); + event SubmitsResumed(); /** diff --git a/lib/abi/Lido.json b/lib/abi/Lido.json index 4b40cf44b..3b02bc6f3 100644 --- a/lib/abi/Lido.json +++ b/lib/abi/Lido.json @@ -1 +1 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveMevTxFee","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalMevTxFeeCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mevTxFeeVault","type":"address"}],"name":"setMevTxFeeVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"mevTxFeeReceiver","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasury","type":"address"}],"name":"TreasurySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"InsuranceFundSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"MevTxFeeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveMevTxFee","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalMevTxFeeCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SUBMITS_BREAK_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferERC721ToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_limitPoints","type":"uint256"}],"name":"setMevTxFeeWithdrawalLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"stopSubmits","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isSubmitsStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mevTxFeeVault","type":"address"}],"name":"setMevTxFeeVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"resumeSubmits","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeWithdrawalLimitPoints","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[],"name":"SubmitsStopped","type":"event"},{"anonymous":false,"inputs":[],"name":"SubmitsResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasury","type":"address"}],"name":"TreasurySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"InsuranceFundSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"MevTxFeeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"limitPoints","type":"uint256"}],"name":"MevTxFeeWithdrawalLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"mevTxFeeVault","type":"address"}],"name":"LidoMevTxFeeVaultSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"RecoverERC721ToVault","type":"event"}] \ No newline at end of file diff --git a/lib/abi/StETH.json b/lib/abi/StETH.json index 9179ad43d..9775eefc4 100644 --- a/lib/abi/StETH.json +++ b/lib/abi/StETH.json @@ -1 +1 @@ -[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file +[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 02a6c17d5..287007a93 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -92,6 +92,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.SET_INSURANCE_FUND(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SUBMITS_BREAK_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) @@ -555,6 +556,31 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) }) + it('submits break works', async () => { + let receipt + + receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) + assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) + + assertRevert(app.stopSubmits(), 'APP_AUTH_FAILED') + receipt = await app.stopSubmits({ from: voting }) + assertEvent(receipt, 'SubmitsStopped') + assertRevert(app.stopSubmits({ from: voting }), 'SUBMITS_ALREADY_STOPPED') + assert.equal(await app.isSubmitsStopped(), true) + assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `SUBMITS_STOPPED`) + assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `SUBMITS_STOPPED`) + + assertRevert(app.resumeSubmits(), 'APP_AUTH_FAILED') + receipt = await app.resumeSubmits({ from: voting }) + assertEvent(receipt, 'SubmitsResumed') + assertRevert(app.resumeSubmits({ from: voting }), 'SUBMITS_ALREADY_RESUMED') + assert.equal(await app.isSubmitsStopped(), false) + + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) + receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(1.4) }) + assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(1.4), referral: ZERO_ADDRESS } }) + }) + it('reverts when trying to call unknown function', async () => { const wrongMethodABI = '0x00' await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1), data: wrongMethodABI }), 'NON_EMPTY_DATA') From 4572109d2674dc7b10a3b4cd2dae0d5ccb576fb8 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 13 Apr 2022 17:31:28 +0000 Subject: [PATCH 090/159] fix: rename 'submits' to 'staking --- contracts/0.4.24/Lido.sol | 41 ++++++++++++++------------- contracts/0.4.24/interfaces/ILido.sol | 16 +++++------ lib/abi/Lido.json | 2 +- test/0.4.24/lido.test.js | 31 ++++++++++---------- 4 files changed, 46 insertions(+), 44 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index c83951104..e758720d0 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -50,7 +50,8 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /// ACL bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); - bytes32 constant public SUBMITS_BREAK_ROLE = keccak256("SUBMITS_BREAK_ROLE"); + bytes32 constant public STAKING_PAUSE_ROLE = keccak256("STAKING_PAUSE_ROLE"); + bytes32 constant public STAKING_RESUME_ROLE = keccak256("STAKING_RESUME_ROLE"); bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE"); bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY"); bytes32 constant public SET_ORACLE = keccak256("SET_ORACLE"); @@ -86,7 +87,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { bytes32 internal constant MEV_TX_FEE_VAULT_POSITION = keccak256("lido.Lido.mevTxFeeVault"); /// @dev dedicated switch to cut-off staking new funds without pausing the whole protocol - bytes32 internal constant SUBMITS_BREAK_POSITION = keccak256("lido.Lido.submitsBreak"); + bytes32 internal constant STAKING_PAUSED_POSITION = keccak256("lido.Lido.stakingPaused"); /// @dev amount of Ether (on the current Ethereum side) buffered on this smart contract balance bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther"); /// @dev number of deposited validators (incrementing counter of deposit operations). @@ -137,33 +138,33 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Cut-off new staking (every new submit transaction would revert if called) - * @dev Provides a way to stop staking without pushing PAUSE for the whole proto + * @notice Cut-off new staking (every new funds submit transaction would revert if called) + * @dev Provides a way to pause staking without pushing PAUSE for the whole proto * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue */ - function stopSubmits() external auth(SUBMITS_BREAK_ROLE) { - require(!isSubmitsStopped(), "SUBMITS_ALREADY_STOPPED"); - SUBMITS_BREAK_POSITION.setStorageBool(true); - emit SubmitsStopped(); + function pauseStaking() external auth(STAKING_PAUSE_ROLE) { + require(!isStakingPaused(), "STAKING_ALREADY_PAUSED"); + STAKING_PAUSED_POSITION.setStorageBool(true); + emit StakingPaused(); } /** - * @notice Resume staking if `stopSubmits` was called previously (allow new submit transactions) - * See `stopSubmits` for the details. + * @notice Resume staking if `pauseStaking` was called previously (allow new submit transactions) + * See `pauseStaking` for the details. */ - function resumeSubmits() external auth(SUBMITS_BREAK_ROLE) { - require(isSubmitsStopped(), "SUBMITS_ALREADY_RESUMED"); - SUBMITS_BREAK_POSITION.setStorageBool(false); - emit SubmitsResumed(); + function resumeStaking() external auth(STAKING_RESUME_ROLE) { + require(isStakingPaused(), "STAKING_ALREADY_RESUMED"); + STAKING_PAUSED_POSITION.setStorageBool(false); + emit StakingResumed(); } /** - * @notice check staking break state - * Returns true if new staking is cutted-off - * See `stopSubmits` for the details. + * @notice check staking pause state + * Returns true if staking is on pause currently + * See `pauseStaking` for the details. */ - function isSubmitsStopped() public view returns(bool) { - return SUBMITS_BREAK_POSITION.getStorageBool(); + function isStakingPaused() public view returns(bool) { + return STAKING_PAUSED_POSITION.getStorageBool(); } /** @@ -592,7 +593,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * @return amount of StETH shares generated */ function _submit(address _referral) internal whenNotStopped returns (uint256) { - require(!isSubmitsStopped(), "SUBMITS_STOPPED"); + require(!isStakingPaused(), "STAKING_PAUSED"); address sender = msg.sender; uint256 deposit = msg.value; require(deposit != 0, "ZERO_DEPOSIT"); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 7d594e722..f079bd162 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -29,22 +29,22 @@ interface ILido { function resume() external; /** - * @notice Cut-off new staking (every new submit transaction would revert if called) - * @dev Provides a way to stop staking without pushing PAUSE for the whole proto + * @notice Cut-off new staking (every new funds submit transaction would revert if called) + * @dev Provides a way to pause staking without pushing PAUSE for the whole proto * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue */ - function stopSubmits() external; + function pauseStaking() external; /** - * @notice Resume staking if `stopSubmits` was called previously (allow new submit transactions) - * See `stopSubmits` for the details. + * @notice Resume staking if `pauseStaking` was called previously (allow new funds submit transactions) + * See `pauseStaking` for the details. */ - function resumeSubmits() external; + function resumeStaking() external; event Stopped(); event Resumed(); - event SubmitsStopped(); - event SubmitsResumed(); + event StakingPaused(); + event StakingResumed(); /** diff --git a/lib/abi/Lido.json b/lib/abi/Lido.json index 3b02bc6f3..e5573a9e5 100644 --- a/lib/abi/Lido.json +++ b/lib/abi/Lido.json @@ -1 +1 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveMevTxFee","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalMevTxFeeCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SUBMITS_BREAK_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferERC721ToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_limitPoints","type":"uint256"}],"name":"setMevTxFeeWithdrawalLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"stopSubmits","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isSubmitsStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mevTxFeeVault","type":"address"}],"name":"setMevTxFeeVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"resumeSubmits","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeWithdrawalLimitPoints","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[],"name":"SubmitsStopped","type":"event"},{"anonymous":false,"inputs":[],"name":"SubmitsResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasury","type":"address"}],"name":"TreasurySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"InsuranceFundSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"MevTxFeeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"limitPoints","type":"uint256"}],"name":"MevTxFeeWithdrawalLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"mevTxFeeVault","type":"address"}],"name":"LidoMevTxFeeVaultSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"RecoverERC721ToVault","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveMevTxFee","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalMevTxFeeCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferERC721ToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_limitPoints","type":"uint256"}],"name":"setMevTxFeeWithdrawalLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mevTxFeeVault","type":"address"}],"name":"setMevTxFeeVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeWithdrawalLimitPoints","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasury","type":"address"}],"name":"TreasurySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"InsuranceFundSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"MevTxFeeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"limitPoints","type":"uint256"}],"name":"MevTxFeeWithdrawalLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"mevTxFeeVault","type":"address"}],"name":"LidoMevTxFeeVaultSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"RecoverERC721ToVault","type":"event"}] \ No newline at end of file diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 287007a93..45936525a 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -92,7 +92,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.SET_INSURANCE_FUND(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SUBMITS_BREAK_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) @@ -556,25 +557,25 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) }) - it('submits break works', async () => { + it('staking pause/resume works', async () => { let receipt receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - assertRevert(app.stopSubmits(), 'APP_AUTH_FAILED') - receipt = await app.stopSubmits({ from: voting }) - assertEvent(receipt, 'SubmitsStopped') - assertRevert(app.stopSubmits({ from: voting }), 'SUBMITS_ALREADY_STOPPED') - assert.equal(await app.isSubmitsStopped(), true) - assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `SUBMITS_STOPPED`) - assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `SUBMITS_STOPPED`) - - assertRevert(app.resumeSubmits(), 'APP_AUTH_FAILED') - receipt = await app.resumeSubmits({ from: voting }) - assertEvent(receipt, 'SubmitsResumed') - assertRevert(app.resumeSubmits({ from: voting }), 'SUBMITS_ALREADY_RESUMED') - assert.equal(await app.isSubmitsStopped(), false) + assertRevert(app.pauseStaking(), 'APP_AUTH_FAILED') + receipt = await app.pauseStaking({ from: voting }) + assertEvent(receipt, 'StakingPaused') + assertRevert(app.pauseStaking({ from: voting }), 'STAKING_ALREADY_PAUSED') + assert.equal(await app.isStakingPaused(), true) + assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) + assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) + + assertRevert(app.resumeStaking(), 'APP_AUTH_FAILED') + receipt = await app.resumeStaking({ from: voting }) + assertEvent(receipt, 'StakingResumed') + assertRevert(app.resumeStaking({ from: voting }), 'STAKING_ALREADY_RESUMED') + assert.equal(await app.isStakingPaused(), false) await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(1.4) }) From d130e8ec4997ecf7d2559c1817c11bc0bf73017b Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 13 Apr 2022 23:01:20 +0000 Subject: [PATCH 091/159] fix: reduce Lido contract size Limit the contact's interface. Remove ERC721 recovery. --- contracts/0.4.24/Lido.sol | 113 +++++------------- contracts/0.4.24/interfaces/ILido.sol | 51 ++++---- contracts/0.4.24/template/LidoTemplate.sol | 6 +- .../0.4.24/test_helpers/LidoPushableMock.sol | 2 +- test/0.4.24/lido.test.js | 60 ++++------ 5 files changed, 74 insertions(+), 158 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index e758720d0..381fc656e 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -43,21 +43,20 @@ interface IERC721 { * rewards, no Transfer events are generated: doing so would require emitting an event * for each token holder and thus running an unbounded loop. */ -contract Lido is ILido, IsContract, StETH, AragonApp { +contract Lido is ILido, StETH, AragonApp { using SafeMath for uint256; using SafeMath64 for uint64; using UnstructuredStorage for bytes32; /// ACL bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); + bytes32 constant public RESUME_ROLE = keccak256("RESUME_ROLE"); bytes32 constant public STAKING_PAUSE_ROLE = keccak256("STAKING_PAUSE_ROLE"); bytes32 constant public STAKING_RESUME_ROLE = keccak256("STAKING_RESUME_ROLE"); bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE"); bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY"); - bytes32 constant public SET_ORACLE = keccak256("SET_ORACLE"); + bytes32 constant public MANAGE_DAO_CONTRACTS_ROLE = keccak256("MANAGE_DAO_CONTRACTS_ROLE"); bytes32 constant public BURN_ROLE = keccak256("BURN_ROLE"); - bytes32 constant public SET_TREASURY = keccak256("SET_TREASURY"); - bytes32 constant public SET_INSURANCE_FUND = keccak256("SET_INSURANCE_FUND"); bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE"); bytes32 constant public SET_MEV_TX_FEE_VAULT_ROLE = keccak256("SET_MEV_TX_FEE_VAULT_ROLE"); bytes32 constant public SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE = keccak256("SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE"); @@ -124,15 +123,10 @@ contract Lido is ILido, IsContract, StETH, AragonApp { ) public onlyInit { - require(isContract(address(_operators)), "NOT_A_CONTRACT"); - require(isContract(address(_depositContract)), "NOT_A_CONTRACT"); - NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(address(_operators)); DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_depositContract)); - _setOracle(_oracle); - _setTreasury(_treasury); - _setInsuranceFund(_insuranceFund); + _setDAOContracts(_oracle, _treasury, _insuranceFund); initialized(); } @@ -237,12 +231,13 @@ contract Lido is ILido, IsContract, StETH, AragonApp { /** * @notice Resume pool routine operations */ - function resume() external auth(PAUSE_ROLE) { + function resume() external auth(RESUME_ROLE) { _resume(); } /** - * @notice Set fee rate to `_feeBasisPoints` basis points. The fees are accrued when oracles report staking results + * @notice Set fee rate to `_feeBasisPoints` basis points. + * The fees are accrued when oracles report staking results. * @param _feeBasisPoints Fee rate, in basis points */ function setFee(uint16 _feeBasisPoints) external auth(MANAGE_FEE) { @@ -251,10 +246,14 @@ contract Lido is ILido, IsContract, StETH, AragonApp { } /** - * @notice Set fee distribution: `_treasuryFeeBasisPoints` basis points go to the treasury, `_insuranceFeeBasisPoints` basis points go to the insurance fund, `_operatorsFeeBasisPoints` basis points go to node operators. The sum has to be 10 000. + * @notice Set fee distribution: + * `_treasuryFeeBasisPoints` basis points go to the treasury, + * `_insuranceFeeBasisPoints` basis points go to the insurance fund, + * `_operatorsFeeBasisPoints` basis points go to node operators. + * The sum has to be 10 000. */ function setFeeDistribution( - uint16 _treasuryFeeBasisPoints, + uint16 _treasuryFeeBasisPoints, uint16 _insuranceFeeBasisPoints, uint16 _operatorsFeeBasisPoints ) @@ -280,29 +279,12 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * by calling handleOracleReport. * @param _oracle oracle contract */ - function setOracle(address _oracle) external auth(SET_ORACLE) { - _setOracle(_oracle); - emit OracleSet(_oracle); - } - - /** - * @notice Set treasury contract address to `_treasury` - * @dev Contract specified here is used to accumulate the protocol treasury fee. - * @param _treasury contract which accumulates treasury fee. - */ - function setTreasury(address _treasury) external auth(SET_TREASURY) { - _setTreasury(_treasury); - emit TreasurySet(_treasury); - } - - /** - * @notice Set insuranceFund contract address to `_insuranceFund` - * @dev Contract specified here is used to accumulate the protocol insurance fee. - * @param _insuranceFund contract which accumulates insurance fee. - */ - function setInsuranceFund(address _insuranceFund) external auth(SET_INSURANCE_FUND) { - _setInsuranceFund(_insuranceFund); - emit InsuranceFundSet(_insuranceFund); + function setDAOContracts( + address _oracle, + address _treasury, + address _insuranceFund + ) external auth(MANAGE_DAO_CONTRACTS_ROLE) { + _setDAOContracts(_oracle, _treasury, _insuranceFund); } /** @@ -333,13 +315,9 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * @dev Sets limit to amount of ETH to withdraw per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function setMevTxFeeWithdrawalLimit(uint256 _limitPoints) external auth(SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE) { - require(_limitPoints <= TOTAL_BASIS_POINTS, "INVALID_POINTS_AMOUNT"); - - if (_limitPoints != MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256()) { - MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.setStorageUint256(_limitPoints); - emit MevTxFeeWithdrawalLimitSet(_limitPoints); - } + function setMevTxFeeWithdrawalLimit(uint16 _limitPoints) external auth(SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE) { + _setBPValue(MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS, _limitPoints); + emit MevTxFeeWithdrawalLimitSet(_limitPoints); } /** @@ -348,7 +326,7 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * @param _pubkeyHash Receiving address */ function withdraw(uint256 _amount, bytes32 _pubkeyHash) external whenNotStopped { /* solhint-disable-line no-unused-vars */ - //will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2021 or 2022). + //will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2022). //at the moment withdrawals are not possible in the beacon chain and there's no workaround revert("NOT_IMPLEMENTED_YET"); } @@ -431,23 +409,6 @@ contract Lido is ILido, IsContract, StETH, AragonApp { emit RecoverToVault(vault, _token, balance); } - /** - * @notice Send NTFs to recovery Vault - * @param _token Token to be sent to recovery vault - * @param _tokenId Token Id - */ - function transferERC721ToVault(address _token, uint256 _tokenId) external { - require(_token != address(0), "ZERO_ADDRESS"); - require(allowRecoverability(_token), "RECOVER_DISALLOWED"); - - address vault = getRecoveryVault(); - require(isContract(vault), "RECOVER_VAULT_NOT_CONTRACT"); - - IERC721(_token).transferFrom(address(this), vault, _tokenId); - - emit RecoverERC721ToVault(vault, _token, _tokenId); - } - /** * @notice Returns staking rewards fee rate */ @@ -564,27 +525,16 @@ contract Lido is ILido, IsContract, StETH, AragonApp { * @dev Internal function to set authorized oracle address * @param _oracle oracle contract */ - function _setOracle(address _oracle) internal { + function _setDAOContracts(address _oracle, address _treasury, address _insuranceFund) internal { require(isContract(_oracle), "NOT_A_CONTRACT"); - ORACLE_POSITION.setStorageAddress(_oracle); - } - - /** - * @dev Internal function to set treasury address - * @param _treasury treasury address - */ - function _setTreasury(address _treasury) internal { require(_treasury != address(0), "SET_TREASURY_ZERO_ADDRESS"); - TREASURY_POSITION.setStorageAddress(_treasury); - } - - /** - * @dev Internal function to set insurance fund address - * @param _insuranceFund insurance fund address - */ - function _setInsuranceFund(address _insuranceFund) internal { require(_insuranceFund != address(0), "SET_INSURANCE_FUND_ZERO_ADDRESS"); + + ORACLE_POSITION.setStorageAddress(_oracle); + TREASURY_POSITION.setStorageAddress(_treasury); INSURANCE_FUND_POSITION.setStorageAddress(_insuranceFund); + + emit DAOContactsSet(_oracle, _treasury, _insuranceFund); } /** @@ -918,9 +868,4 @@ contract Lido is ILido, IsContract, StETH, AragonApp { assert(0 == temp_value); // fully converted result <<= (24 * 8); } - - function to64(uint256 v) internal pure returns (uint64) { - assert(v <= uint256(uint64(-1))); - return uint64(v); - } } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index f079bd162..3a16073c0 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -46,50 +46,39 @@ interface ILido { event StakingPaused(); event StakingResumed(); - /** * @notice Set authorized oracle contract address to `_oracle` * @dev Contract specified here is allowed to make periodical updates of beacon states * by calling pushBeacon. * @param _oracle oracle contract */ - function setOracle(address _oracle) external; - - event OracleSet(address oracle); - - /** - * @notice Set treasury contract address to `_treasury` - * @dev Contract specified here is used to accumulate the protocol treasury fee. - * @param _treasury contract which accumulates treasury fee. - */ - function setTreasury(address _treasury) external; + function setDAOContracts( + address _oracle, + address _treasury, + address _insuranceFund + ) external; - event TreasurySet(address treasury); + event DAOContactsSet(address oracle, address treasury, address insuranceFund); /** - * @notice Set insuranceFund contract address to `_insuranceFund` - * @dev Contract specified here is used to accumulate the protocol insurance fee. - * @param _insuranceFund contract which accumulates insurance fee. - */ - function setInsuranceFund(address _insuranceFund) external; - - event InsuranceFundSet(address insuranceFund); - - - /** - * @notice Set fee rate to `_feeBasisPoints` basis points. The fees are accrued when oracles report staking results + * @notice Set fee rate to `_feeBasisPoints` basis points. + * The fees are accrued when oracles report staking results. * @param _feeBasisPoints Fee rate, in basis points */ function setFee(uint16 _feeBasisPoints) external; /** - * @notice Set fee distribution: `_treasuryFeeBasisPoints` basis points go to the treasury, `_insuranceFeeBasisPoints` basis points go to the insurance fund, `_operatorsFeeBasisPoints` basis points go to node operators. The sum has to be 10 000. + * @notice Set fee distribution: + * `_treasuryFeeBasisPoints` basis points go to the treasury, + * `_insuranceFeeBasisPoints` basis points go to the insurance fund, + * `_operatorsFeeBasisPoints` basis points go to node operators. + * The sum has to be 10 000. */ function setFeeDistribution( uint16 _treasuryFeeBasisPoints, uint16 _insuranceFeeBasisPoints, - uint16 _operatorsFeeBasisPoints) - external; + uint16 _operatorsFeeBasisPoints + ) external; /** * @notice Returns staking rewards fee rate @@ -99,8 +88,11 @@ interface ILido { /** * @notice Returns fee distribution proportion */ - function getFeeDistribution() external view returns (uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, - uint16 operatorsFeeBasisPoints); + function getFeeDistribution() external view returns ( + uint16 treasuryFeeBasisPoints, + uint16 insuranceFeeBasisPoints, + uint16 operatorsFeeBasisPoints + ); event FeeSet(uint16 feeBasisPoints); @@ -196,7 +188,4 @@ interface ILido { * @return beaconBalance - total amount of Beacon-side Ether (sum of all the balances of Lido validators) */ function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); - - // Requested ERC721 recovery from the `Lido` to the designated `recoveryVault` vault. - event RecoverERC721ToVault(address indexed vault, address indexed token, uint256 tokenId); } diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 93160b89b..43bfc32c1 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -652,12 +652,10 @@ contract LidoTemplate is IsContract { perms[0] = _state.lido.PAUSE_ROLE(); perms[1] = _state.lido.MANAGE_FEE(); perms[2] = _state.lido.MANAGE_WITHDRAWAL_KEY(); - perms[3] = _state.lido.SET_ORACLE(); + perms[3] = _state.lido.MANAGE_DAO_CONTRACTS_ROLE(); perms[4] = _state.lido.BURN_ROLE(); - perms[5] = _state.lido.SET_TREASURY(); - perms[6] = _state.lido.SET_INSURANCE_FUND(); - for (i = 0; i < 7; ++i) { + for (i = 0; i < 5; ++i) { _createPermissionForVoting(acl, _state.lido, perms[i], voting); } } diff --git a/contracts/0.4.24/test_helpers/LidoPushableMock.sol b/contracts/0.4.24/test_helpers/LidoPushableMock.sol index 8b768c4a9..a61590fa2 100644 --- a/contracts/0.4.24/test_helpers/LidoPushableMock.sol +++ b/contracts/0.4.24/test_helpers/LidoPushableMock.sol @@ -52,7 +52,7 @@ contract LidoPushableMock is Lido { } function initialize(address _oracle) public onlyInit { - _setOracle(_oracle); + _setDAOContracts(_oracle, _oracle, _oracle); _resume(); initialized(); } diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 45936525a..44dcd1c25 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -5,7 +5,6 @@ const { getInstalledApp } = require('@aragon/contract-helpers-test/src/aragon-os const { assertBn, assertRevert, assertEvent } = require('@aragon/contract-helpers-test/src/asserts') const { ZERO_ADDRESS, bn, getEventAt } = require('@aragon/contract-helpers-test') const { BN } = require('bn.js') -const { ethers } = require('ethers') const { formatEther } = require('ethers/lib/utils') const { getEthBalance, formatStEth: formamtStEth, formatBN } = require('../helpers/utils') @@ -16,7 +15,6 @@ const MevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') -const ERC721Mock = artifacts.require('ERC721Mock.sol') const VaultMock = artifacts.require('AragonVaultMock.sol') const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') @@ -84,12 +82,11 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) // Set up the app's permissions. await acl.createPermission(voting, app.address, await app.PAUSE_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.RESUME_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_FEE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.BURN_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_TREASURY(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_ORACLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_INSURANCE_FUND(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.MANAGE_DAO_CONTRACTS_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) @@ -109,8 +106,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(depositor, app.address, await app.DEPOSIT_ROLE(), appManager, { from: appManager }) // Initialize the app's proxy. - await assertRevert(app.initialize(user1, oracle.address, operators.address), 'NOT_A_CONTRACT') - await assertRevert(app.initialize(depositContract.address, oracle.address, user1), 'NOT_A_CONTRACT') await app.initialize(depositContract.address, oracle.address, operators.address) treasuryAddr = await app.getTreasury() @@ -278,7 +273,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertNoEvent(app.setMevTxFeeWithdrawalLimit(1, { from: voting }), 'MevTxFeeWithdrawalLimitSet') await app.setMevTxFeeWithdrawalLimit(10000, { from: voting }) - await assertRevert(app.setMevTxFeeWithdrawalLimit(10001, { from: voting }), 'INVALID_POINTS_AMOUNT') + await assertRevert(app.setMevTxFeeWithdrawalLimit(10001, { from: voting }), 'VALUE_OVER_100_PERCENT') await app.setMevTxFeeWithdrawalLimit(initialValue, { from: voting }) }) @@ -315,9 +310,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('setOracle works', async () => { - await assertRevert(app.setOracle(user1, { from: voting }), 'NOT_A_CONTRACT') - const receipt = await app.setOracle(yetAnotherOracle.address, { from: voting }) - assertEvent(receipt, 'OracleSet', { expectedArgs: { oracle: yetAnotherOracle.address } }) + await assertRevert(app.setDAOContracts(user1, user2, user3, { from: voting }), 'NOT_A_CONTRACT') + const receipt = await app.setDAOContracts(yetAnotherOracle.address, oracle.address, oracle.address, { from: voting }) + assertEvent(receipt, 'DAOContactsSet', { expectedArgs: { oracle: yetAnotherOracle.address } }) assert.equal(await app.getOracle(), yetAnotherOracle.address) }) @@ -1263,18 +1258,21 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it(`treasury can't be set by an arbitrary address`, async () => { - await assertRevert(app.setTreasury(user1, { from: nobody })) - await assertRevert(app.setTreasury(user1, { from: user1 })) + await assertRevert(app.setDAOContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: nobody })) + await assertRevert(app.setDAOContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: user1 })) }) it('voting can set treasury', async () => { - const receipt = await app.setTreasury(user1, { from: voting }) - assertEvent(receipt, 'TreasurySet', { expectedArgs: { treasury: user1 } }) + const receipt = await app.setDAOContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: voting }) + assertEvent(receipt, 'DAOContactsSet', { expectedArgs: { treasury: user1 } }) assert.equal(await app.getTreasury(), user1) }) it('reverts when treasury is zero address', async () => { - await assertRevert(app.setTreasury(ZERO_ADDRESS, { from: voting }), 'SET_TREASURY_ZERO_ADDRESS') + await assertRevert( + app.setDAOContracts(await app.getOracle(), ZERO_ADDRESS, await app.getInsuranceFund(), { from: voting }), + 'SET_TREASURY_ZERO_ADDRESS' + ) }) }) @@ -1284,34 +1282,31 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it(`insurance fund can't be set by an arbitrary address`, async () => { - await assertRevert(app.setInsuranceFund(user1, { from: nobody })) - await assertRevert(app.setInsuranceFund(user1, { from: user1 })) + await assertRevert(app.setDAOContracts(await app.getOracle(), await app.getTreasury(), user1, { from: nobody })) + await assertRevert(app.setDAOContracts(await app.getOracle(), await app.getTreasury(), user1, { from: user1 })) }) it('voting can set insurance fund', async () => { - const receipt = await app.setInsuranceFund(user1, { from: voting }) - assertEvent(receipt, 'InsuranceFundSet', { expectedArgs: { insuranceFund: user1 } }) + const receipt = await app.setDAOContracts(await app.getOracle(), await app.getTreasury(), user1, { from: voting }) + assertEvent(receipt, 'DAOContactsSet', { expectedArgs: { insuranceFund: user1 } }) assert.equal(await app.getInsuranceFund(), user1) }) it('reverts when insurance fund is zero address', async () => { - await assertRevert(app.setInsuranceFund(ZERO_ADDRESS, { from: voting }), 'SET_INSURANCE_FUND_ZERO_ADDRESS') + await assertRevert( + app.setDAOContracts(await app.getOracle(), await app.getTreasury(), ZERO_ADDRESS, { from: voting }), + 'SET_INSURANCE_FUND_ZERO_ADDRESS' + ) }) }) context('recovery vault', () => { - let nftToken - beforeEach(async () => { await anyToken.mint(app.address, 100) - - nftToken = await ERC721Mock.new() - await nftToken.mint(app.address, 777) }) it('reverts when vault is not set', async () => { await assertRevert(app.transferToVault(anyToken.address, { from: nobody }), 'RECOVER_VAULT_NOT_CONTRACT') - await assertRevert(app.transferERC721ToVault(nftToken.address, 777, { from: nobody }), 'RECOVER_VAULT_NOT_CONTRACT') }) context('recovery works with vault mock deployed', () => { @@ -1334,17 +1329,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'RecoverToVault', { expectedArgs: { vault: vault.address, token: anyToken.address, amount: 100 } }) }) - it('recovery with nft tokens works and emits event', async () => { - await assertRevert(app.transferERC721ToVault(ZERO_ADDRESS, 777, { from: nobody })) - - assert.equal(await nftToken.ownerOf(777), app.address) - - const receipt = await app.transferERC721ToVault(nftToken.address, 777, { from: nobody }) - assertEvent(receipt, 'RecoverERC721ToVault', { expectedArgs: { vault: vault.address, token: nftToken.address, tokenId: 777 } }) - - assert.equal(await nftToken.ownerOf(777), vault.address) - }) - it('recovery with unaccounted ether works and emits event', async () => { await app.makeUnaccountedEther({ from: user1, value: ETH(10) }) const receipt = await app.transferToVault(ZERO_ADDRESS, { from: nobody }) From b7db8d15982b3a3fdccfc8dc256270a6359a954d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 14 Apr 2022 07:17:09 +0000 Subject: [PATCH 092/159] doc: update docs --- contracts/0.4.24/Lido.sol | 27 ++++++++++++++++++--------- contracts/0.4.24/interfaces/ILido.sol | 19 ++++++++++++++----- 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 381fc656e..d7c2206ff 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -132,7 +132,9 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Cut-off new staking (every new funds submit transaction would revert if called) + * @notice Cut-off new staking (every new submit ETH funds transaction would revert + * if `pauseStaking` was called previously) + * * @dev Provides a way to pause staking without pushing PAUSE for the whole proto * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue */ @@ -143,7 +145,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Resume staking if `pauseStaking` was called previously (allow new submit transactions) + * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) * See `pauseStaking` for the details. */ function resumeStaking() external auth(STAKING_RESUME_ROLE) { @@ -253,7 +255,7 @@ contract Lido is ILido, StETH, AragonApp { * The sum has to be 10 000. */ function setFeeDistribution( - uint16 _treasuryFeeBasisPoints, + uint16 _treasuryFeeBasisPoints, uint16 _insuranceFeeBasisPoints, uint16 _operatorsFeeBasisPoints ) @@ -274,11 +276,18 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Set authorized oracle contract address to `_oracle` - * @dev Contract specified here is allowed to make periodical updates of beacon states - * by calling handleOracleReport. - * @param _oracle oracle contract - */ + * @notice Set Lido DAO contracts (oracle, treasury, insurance fund). + * + * @dev Oracle contract specified here is allowed to make + * periodical updates of beacon states + * by calling pushBeacon. Treasury contract specified here is used + * to accumulate the protocol treasury fee.Insurance fund contract + * specified here is used to accumulate the protocol insurance fee. + * + * @param _oracle oracle contract + * @param _treasury treasury contract which accumulates treasury fee + * @param _insuranceFund insurance fund contract which accumulates insurance fee + */ function setDAOContracts( address _oracle, address _treasury, @@ -326,7 +335,7 @@ contract Lido is ILido, StETH, AragonApp { * @param _pubkeyHash Receiving address */ function withdraw(uint256 _amount, bytes32 _pubkeyHash) external whenNotStopped { /* solhint-disable-line no-unused-vars */ - //will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2022). + //will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). //at the moment withdrawals are not possible in the beacon chain and there's no workaround revert("NOT_IMPLEMENTED_YET"); } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 3a16073c0..d887c8720 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -29,14 +29,16 @@ interface ILido { function resume() external; /** - * @notice Cut-off new staking (every new funds submit transaction would revert if called) + * @notice Cut-off new staking (every new submit ETH funds transaction would revert + * if `pauseStaking` was called previously) + * * @dev Provides a way to pause staking without pushing PAUSE for the whole proto * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue */ function pauseStaking() external; /** - * @notice Resume staking if `pauseStaking` was called previously (allow new funds submit transactions) + * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) * See `pauseStaking` for the details. */ function resumeStaking() external; @@ -47,10 +49,17 @@ interface ILido { event StakingResumed(); /** - * @notice Set authorized oracle contract address to `_oracle` - * @dev Contract specified here is allowed to make periodical updates of beacon states - * by calling pushBeacon. + * @notice Set Lido DAO contracts (oracle, treasury, insurance fund). + * + * @dev Oracle contract specified here is allowed to make + * periodical updates of beacon states + * by calling pushBeacon. Treasury contract specified here is used + * to accumulate the protocol treasury fee.Insurance fund contract + * specified here is used to accumulate the protocol insurance fee. + * * @param _oracle oracle contract + * @param _treasury treasury contract which accumulates treasury fee + * @param _insuranceFund insurance fund contract which accumulates insurance fee */ function setDAOContracts( address _oracle, From 406b327920d1cf3e87adc38e87d2d4fa6fa689d8 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 14 Apr 2022 07:20:39 +0000 Subject: [PATCH 093/159] chore: fix comments format --- contracts/0.4.24/interfaces/ILido.sol | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index d887c8720..21cc74fd5 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -52,10 +52,9 @@ interface ILido { * @notice Set Lido DAO contracts (oracle, treasury, insurance fund). * * @dev Oracle contract specified here is allowed to make - * periodical updates of beacon states - * by calling pushBeacon. Treasury contract specified here is used - * to accumulate the protocol treasury fee.Insurance fund contract - * specified here is used to accumulate the protocol insurance fee. + * periodical updates of beacon states by calling pushBeacon. + * Treasury contract specified here is used to accumulate the protocol treasury fee. + * Insurance fund contract specified here is used to accumulate the protocol insurance fee. * * @param _oracle oracle contract * @param _treasury treasury contract which accumulates treasury fee From 81d51d6a9b520279c6becf7959cee9360eceb512 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 14 Apr 2022 18:07:20 +0300 Subject: [PATCH 094/159] fix: rename DAO contracts -> protocol contracts Sounds more reasonable and consistent. --- contracts/0.4.24/Lido.sol | 16 ++++++++-------- contracts/0.4.24/interfaces/ILido.sol | 6 +++--- contracts/0.4.24/template/LidoTemplate.sol | 2 +- .../0.4.24/test_helpers/LidoPushableMock.sol | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index d7c2206ff..f9e184eda 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -55,7 +55,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 constant public STAKING_RESUME_ROLE = keccak256("STAKING_RESUME_ROLE"); bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE"); bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY"); - bytes32 constant public MANAGE_DAO_CONTRACTS_ROLE = keccak256("MANAGE_DAO_CONTRACTS_ROLE"); + bytes32 constant public MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE"); bytes32 constant public BURN_ROLE = keccak256("BURN_ROLE"); bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE"); bytes32 constant public SET_MEV_TX_FEE_VAULT_ROLE = keccak256("SET_MEV_TX_FEE_VAULT_ROLE"); @@ -126,7 +126,7 @@ contract Lido is ILido, StETH, AragonApp { NODE_OPERATORS_REGISTRY_POSITION.setStorageAddress(address(_operators)); DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_depositContract)); - _setDAOContracts(_oracle, _treasury, _insuranceFund); + _setProtocolContracts(_oracle, _treasury, _insuranceFund); initialized(); } @@ -276,7 +276,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Set Lido DAO contracts (oracle, treasury, insurance fund). + * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). * * @dev Oracle contract specified here is allowed to make * periodical updates of beacon states @@ -288,12 +288,12 @@ contract Lido is ILido, StETH, AragonApp { * @param _treasury treasury contract which accumulates treasury fee * @param _insuranceFund insurance fund contract which accumulates insurance fee */ - function setDAOContracts( + function setProtocolContracts( address _oracle, address _treasury, address _insuranceFund - ) external auth(MANAGE_DAO_CONTRACTS_ROLE) { - _setDAOContracts(_oracle, _treasury, _insuranceFund); + ) external auth(MANAGE_PROTOCOL_CONTRACTS_ROLE) { + _setProtocolContracts(_oracle, _treasury, _insuranceFund); } /** @@ -534,7 +534,7 @@ contract Lido is ILido, StETH, AragonApp { * @dev Internal function to set authorized oracle address * @param _oracle oracle contract */ - function _setDAOContracts(address _oracle, address _treasury, address _insuranceFund) internal { + function _setProtocolContracts(address _oracle, address _treasury, address _insuranceFund) internal { require(isContract(_oracle), "NOT_A_CONTRACT"); require(_treasury != address(0), "SET_TREASURY_ZERO_ADDRESS"); require(_insuranceFund != address(0), "SET_INSURANCE_FUND_ZERO_ADDRESS"); @@ -543,7 +543,7 @@ contract Lido is ILido, StETH, AragonApp { TREASURY_POSITION.setStorageAddress(_treasury); INSURANCE_FUND_POSITION.setStorageAddress(_insuranceFund); - emit DAOContactsSet(_oracle, _treasury, _insuranceFund); + emit ProtocolContactsSet(_oracle, _treasury, _insuranceFund); } /** diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 21cc74fd5..46165e1ce 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -49,7 +49,7 @@ interface ILido { event StakingResumed(); /** - * @notice Set Lido DAO contracts (oracle, treasury, insurance fund). + * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). * * @dev Oracle contract specified here is allowed to make * periodical updates of beacon states by calling pushBeacon. @@ -60,13 +60,13 @@ interface ILido { * @param _treasury treasury contract which accumulates treasury fee * @param _insuranceFund insurance fund contract which accumulates insurance fee */ - function setDAOContracts( + function setProtocolContracts( address _oracle, address _treasury, address _insuranceFund ) external; - event DAOContactsSet(address oracle, address treasury, address insuranceFund); + event ProtocolContactsSet(address oracle, address treasury, address insuranceFund); /** * @notice Set fee rate to `_feeBasisPoints` basis points. diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 43bfc32c1..7ae60e467 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -652,7 +652,7 @@ contract LidoTemplate is IsContract { perms[0] = _state.lido.PAUSE_ROLE(); perms[1] = _state.lido.MANAGE_FEE(); perms[2] = _state.lido.MANAGE_WITHDRAWAL_KEY(); - perms[3] = _state.lido.MANAGE_DAO_CONTRACTS_ROLE(); + perms[3] = _state.lido.MANAGE_PROTOCOL_CONTRACTS_ROLE(); perms[4] = _state.lido.BURN_ROLE(); for (i = 0; i < 5; ++i) { diff --git a/contracts/0.4.24/test_helpers/LidoPushableMock.sol b/contracts/0.4.24/test_helpers/LidoPushableMock.sol index a61590fa2..35c3d5c17 100644 --- a/contracts/0.4.24/test_helpers/LidoPushableMock.sol +++ b/contracts/0.4.24/test_helpers/LidoPushableMock.sol @@ -52,7 +52,7 @@ contract LidoPushableMock is Lido { } function initialize(address _oracle) public onlyInit { - _setDAOContracts(_oracle, _oracle, _oracle); + _setProtocolContracts(_oracle, _oracle, _oracle); _resume(); initialized(); } From 8bccd2cc10fb1e53ec258195b861488936adac66 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Fri, 15 Apr 2022 00:28:23 +0300 Subject: [PATCH 095/159] fix: update tests --- test/0.4.24/lido.test.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 44dcd1c25..452ccb0b3 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -86,7 +86,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.MANAGE_FEE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.BURN_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.MANAGE_DAO_CONTRACTS_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.MANAGE_PROTOCOL_CONTRACTS_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) @@ -310,9 +310,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('setOracle works', async () => { - await assertRevert(app.setDAOContracts(user1, user2, user3, { from: voting }), 'NOT_A_CONTRACT') - const receipt = await app.setDAOContracts(yetAnotherOracle.address, oracle.address, oracle.address, { from: voting }) - assertEvent(receipt, 'DAOContactsSet', { expectedArgs: { oracle: yetAnotherOracle.address } }) + await assertRevert(app.setProtocolContracts(user1, user2, user3, { from: voting }), 'NOT_A_CONTRACT') + const receipt = await app.setProtocolContracts(yetAnotherOracle.address, oracle.address, oracle.address, { from: voting }) + assertEvent(receipt, 'ProtocolContactsSet', { expectedArgs: { oracle: yetAnotherOracle.address } }) assert.equal(await app.getOracle(), yetAnotherOracle.address) }) @@ -1258,19 +1258,19 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it(`treasury can't be set by an arbitrary address`, async () => { - await assertRevert(app.setDAOContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: nobody })) - await assertRevert(app.setDAOContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: user1 })) + await assertRevert(app.setProtocolContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: nobody })) + await assertRevert(app.setProtocolContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: user1 })) }) it('voting can set treasury', async () => { - const receipt = await app.setDAOContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: voting }) - assertEvent(receipt, 'DAOContactsSet', { expectedArgs: { treasury: user1 } }) + const receipt = await app.setProtocolContracts(await app.getOracle(), user1, await app.getInsuranceFund(), { from: voting }) + assertEvent(receipt, 'ProtocolContactsSet', { expectedArgs: { treasury: user1 } }) assert.equal(await app.getTreasury(), user1) }) it('reverts when treasury is zero address', async () => { await assertRevert( - app.setDAOContracts(await app.getOracle(), ZERO_ADDRESS, await app.getInsuranceFund(), { from: voting }), + app.setProtocolContracts(await app.getOracle(), ZERO_ADDRESS, await app.getInsuranceFund(), { from: voting }), 'SET_TREASURY_ZERO_ADDRESS' ) }) @@ -1282,19 +1282,19 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it(`insurance fund can't be set by an arbitrary address`, async () => { - await assertRevert(app.setDAOContracts(await app.getOracle(), await app.getTreasury(), user1, { from: nobody })) - await assertRevert(app.setDAOContracts(await app.getOracle(), await app.getTreasury(), user1, { from: user1 })) + await assertRevert(app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), user1, { from: nobody })) + await assertRevert(app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), user1, { from: user1 })) }) it('voting can set insurance fund', async () => { - const receipt = await app.setDAOContracts(await app.getOracle(), await app.getTreasury(), user1, { from: voting }) - assertEvent(receipt, 'DAOContactsSet', { expectedArgs: { insuranceFund: user1 } }) + const receipt = await app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), user1, { from: voting }) + assertEvent(receipt, 'ProtocolContactsSet', { expectedArgs: { insuranceFund: user1 } }) assert.equal(await app.getInsuranceFund(), user1) }) it('reverts when insurance fund is zero address', async () => { await assertRevert( - app.setDAOContracts(await app.getOracle(), await app.getTreasury(), ZERO_ADDRESS, { from: voting }), + app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), ZERO_ADDRESS, { from: voting }), 'SET_INSURANCE_FUND_ZERO_ADDRESS' ) }) From 83b57397a7db83e468010e5c0c3ff9cf9ec9d636 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 16 Apr 2022 20:29:11 +0300 Subject: [PATCH 096/159] fix: handle edge-case when mev vault is zero Fix rewards distribution calculations if vault set to zero. --- contracts/0.4.24/Lido.sol | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index f9e184eda..a75179745 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -314,7 +314,6 @@ contract Lido is ILido, StETH, AragonApp { * @param _mevTxFeeVault MEV and Tx Fees Vault contract address */ function setMevTxFeeVault(address _mevTxFeeVault) external auth(SET_MEV_TX_FEE_VAULT_ROLE) { - require(isContract(_mevTxFeeVault), "NOT_A_CONTRACT"); MEV_TX_FEE_VAULT_POSITION.setStorageAddress(_mevTxFeeVault); emit LidoMevTxFeeVaultSet(_mevTxFeeVault); @@ -373,13 +372,14 @@ contract Lido is ILido, StETH, AragonApp { // Otherwise withdraw all rewards and put them to the buffer // Thus, MEV tx fees are handled the same way as beacon rewards - // Calc max amount for this withdrawal - uint256 mevRewards = (_getTotalPooledEther() * MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256()) - / TOTAL_BASIS_POINTS; - + uint256 mevRewards; address mevVaultAddress = getMevTxFeeVault(); + if (mevVaultAddress != address(0)) { - mevRewards = ILidoMevTxFeeVault(mevVaultAddress).withdrawRewards(mevRewards); + mevRewards = ILidoMevTxFeeVault(mevVaultAddress).withdrawRewards( + (_getTotalPooledEther() * MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256()) / TOTAL_BASIS_POINTS + ); + if (mevRewards != 0) { BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevRewards)); } From d18554979bddb92c287ad69b5754b80bd442dee9 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 16 Apr 2022 20:32:52 +0300 Subject: [PATCH 097/159] fix: contract size optimizations Remove extra vars and funcs to minimize Lido.sol contract's size --- contracts/0.4.24/Lido.sol | 51 +++++++++------------------------------ 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index a75179745..3ad496682 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -421,15 +421,15 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Returns staking rewards fee rate */ - function getFee() external view returns (uint16 feeBasisPoints) { - return _getFee(); + function getFee() public view returns (uint16 feeBasisPoints) { + return uint16(FEE_POSITION.getStorageUint256()); } /** * @notice Returns fee distribution proportion */ function getFeeDistribution() - external + public view returns ( uint16 treasuryFeeBasisPoints, @@ -437,7 +437,9 @@ contract Lido is ILido, StETH, AragonApp { uint16 operatorsFeeBasisPoints ) { - return _getFeeDistribution(); + treasuryFeeBasisPoints = uint16(TREASURY_FEE_POSITION.getStorageUint256()); + insuranceFeeBasisPoints = uint16(INSURANCE_FEE_POSITION.getStorageUint256()); + operatorsFeeBasisPoints = uint16(NODE_OPERATORS_FEE_POSITION.getStorageUint256()); } /** @@ -690,7 +692,7 @@ contract Lido is ILido, StETH, AragonApp { // The effect is that the given percentage of the reward goes to the fee recipient, and // the rest of the reward is distributed between token holders proportionally to their // token shares. - uint256 feeBasis = _getFee(); + uint256 feeBasis = getFee(); uint256 shares2mint = ( _totalRewards.mul(feeBasis).mul(_getTotalShares()) .div( @@ -703,7 +705,7 @@ contract Lido is ILido, StETH, AragonApp { // balances of the holders, as if the fee was taken in parts from each of them. _mintShares(address(this), shares2mint); - (,uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints) = _getFeeDistribution(); + (,uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints) = getFeeDistribution(); uint256 toInsuranceFund = shares2mint.mul(insuranceFeeBasisPoints).div(TOTAL_BASIS_POINTS); address insuranceFund = getInsuranceFund(); @@ -775,33 +777,6 @@ contract Lido is ILido, StETH, AragonApp { _slot.setStorageUint256(uint256(_value)); } - /** - * @dev Returns staking rewards fee rate - */ - function _getFee() internal view returns (uint16) { - return _readBPValue(FEE_POSITION); - } - - /** - * @dev Returns fee distribution proportion - */ - function _getFeeDistribution() internal view - returns (uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints) - { - treasuryFeeBasisPoints = _readBPValue(TREASURY_FEE_POSITION); - insuranceFeeBasisPoints = _readBPValue(INSURANCE_FEE_POSITION); - operatorsFeeBasisPoints = _readBPValue(NODE_OPERATORS_FEE_POSITION); - } - - /** - * @dev Read a value nominated in basis points - */ - function _readBPValue(bytes32 _slot) internal view returns (uint16) { - uint256 v = _slot.getStorageUint256(); - assert(v <= TOTAL_BASIS_POINTS); - return uint16(v); - } - /** * @dev Gets the amount of Ether temporary buffered on this contract balance */ @@ -829,8 +804,7 @@ contract Lido is ILido, StETH, AragonApp { uint256 beaconValidators = BEACON_VALIDATORS_POSITION.getStorageUint256(); // beaconValidators can never be less than deposited ones. assert(depositedValidators >= beaconValidators); - uint256 transientValidators = depositedValidators.sub(beaconValidators); - return transientValidators.mul(DEPOSIT_SIZE); + return depositedValidators.sub(beaconValidators).mul(DEPOSIT_SIZE); } /** @@ -838,10 +812,9 @@ contract Lido is ILido, StETH, AragonApp { * @return total balance in wei */ function _getTotalPooledEther() internal view returns (uint256) { - uint256 bufferedBalance = _getBufferedEther(); - uint256 beaconBalance = BEACON_BALANCE_POSITION.getStorageUint256(); - uint256 transientBalance = _getTransientBalance(); - return bufferedBalance.add(beaconBalance).add(transientBalance); + return _getBufferedEther().add( + BEACON_BALANCE_POSITION.getStorageUint256() + ).add(_getTransientBalance()); } /** From 5b6156e302ce4bfa7c8c4c6dd79fbdfa4a815a01 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 16 Apr 2022 20:37:45 +0300 Subject: [PATCH 098/159] feat: implement soft staking rate limit --- contracts/0.4.24/Lido.sol | 123 +++++++++++++++++++++----- contracts/0.4.24/interfaces/ILido.sol | 29 +++++- test/0.4.24/lido.test.js | 77 ++++++++++++++-- 3 files changed, 196 insertions(+), 33 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 3ad496682..c8ddee8a6 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -52,7 +52,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); bytes32 constant public RESUME_ROLE = keccak256("RESUME_ROLE"); bytes32 constant public STAKING_PAUSE_ROLE = keccak256("STAKING_PAUSE_ROLE"); - bytes32 constant public STAKING_RESUME_ROLE = keccak256("STAKING_RESUME_ROLE"); + bytes32 constant public STAKING_RATE_LIMIT_ROLE = keccak256("STAKING_RATE_LIMIT_ROLE"); bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE"); bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY"); bytes32 constant public MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE"); @@ -85,8 +85,8 @@ contract Lido is ILido, StETH, AragonApp { bytes32 internal constant INSURANCE_FUND_POSITION = keccak256("lido.Lido.insuranceFund"); bytes32 internal constant MEV_TX_FEE_VAULT_POSITION = keccak256("lido.Lido.mevTxFeeVault"); - /// @dev dedicated switch to cut-off staking new funds without pausing the whole protocol - bytes32 internal constant STAKING_PAUSED_POSITION = keccak256("lido.Lido.stakingPaused"); + /// @dev storage slot position of the staking rate limit structure + bytes32 internal constant STAKING_RATE_LIMIT_POSITION = keccak256("lido.Lido.stakingRateLimit"); /// @dev amount of Ether (on the current Ethereum side) buffered on this smart contract balance bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther"); /// @dev number of deposited validators (incrementing counter of deposit operations). @@ -127,6 +127,7 @@ contract Lido is ILido, StETH, AragonApp { DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_depositContract)); _setProtocolContracts(_oracle, _treasury, _insuranceFund); + _writeStakingRateLimit(uint64(-1), 0, 0, 0); initialized(); } @@ -139,28 +140,57 @@ contract Lido is ILido, StETH, AragonApp { * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue */ function pauseStaking() external auth(STAKING_PAUSE_ROLE) { - require(!isStakingPaused(), "STAKING_ALREADY_PAUSED"); - STAKING_PAUSED_POSITION.setStorageBool(true); + _writeStakingRateLimit(0, 0, 0, 0); + emit StakingPaused(); } /** * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) - * See `pauseStaking` for the details. + * or if new rate limit is desired. + * + * @dev Reverts if `_rateLimitAmount` is zero. + * To disable the rate limit, set `_rateLimitPeriodMinutes` to zero. + * + * @param _rateLimitAmount amount of ETH per period + * @param _rateLimitPeriodMinutes period duration + */ + function resumeStaking( + uint64 _rateLimitAmount, + uint64 _rateLimitPeriodMinutes + ) external auth(STAKING_RATE_LIMIT_ROLE) { + require(_rateLimitAmount != 0, "ZERO_AMOUNT"); + + _writeStakingRateLimit(_rateLimitAmount, 0, 0, _rateLimitPeriodMinutes); + + emit StakingResumed(_rateLimitAmount, _rateLimitPeriodMinutes); + } + + /** + * @dev Read storage slot containing rate limit params and state. */ - function resumeStaking() external auth(STAKING_RESUME_ROLE) { - require(isStakingPaused(), "STAKING_ALREADY_RESUMED"); - STAKING_PAUSED_POSITION.setStorageBool(false); - emit StakingResumed(); + function getStakingRateLimit() public view returns ( + uint64 amountETH, + uint64 spentETH, + uint64 lastStakeMinutes, + uint64 periodMinutes + ) { + uint256 packedRateLimit = STAKING_RATE_LIMIT_POSITION.getStorageUint256(); + + amountETH = uint64(packedRateLimit >> 192); + spentETH = uint64(packedRateLimit >> 128); + lastStakeMinutes = uint64(packedRateLimit >> 64); + periodMinutes = uint64(packedRateLimit); } /** * @notice check staking pause state * Returns true if staking is on pause currently - * See `pauseStaking` for the details. + * See `pauseStaking` and `resumeStaking` for the details. */ function isStakingPaused() public view returns(bool) { - return STAKING_PAUSED_POSITION.getStorageBool(); + (uint64 amountETH,,,) = getStakingRateLimit(); + return amountETH == 0; } /** @@ -554,21 +584,55 @@ contract Lido is ILido, StETH, AragonApp { * @return amount of StETH shares generated */ function _submit(address _referral) internal whenNotStopped returns (uint256) { - require(!isStakingPaused(), "STAKING_PAUSED"); - address sender = msg.sender; - uint256 deposit = msg.value; - require(deposit != 0, "ZERO_DEPOSIT"); + ( + uint64 amountETH, + uint64 spentETH, + uint64 lastStakeMinutes, + uint64 periodMinutes + ) = getStakingRateLimit(); + + require(amountETH > 0, "STAKING_PAUSED"); + require(msg.value != 0, "ZERO_DEPOSIT"); + + if (periodMinutes > 0) { + uint256 amountWei = uint256(amountETH) * 10**18; + uint256 spentWei = uint256(spentETH) * 10**18; + uint256 currentMinutes = block.timestamp / 60; + uint256 amountIncreaseWei = ( + amountWei * (currentMinutes - lastStakeMinutes) + ) / periodMinutes; + + uint256 nowAvailableAmountWei = amountIncreaseWei + (amountWei - spentWei); + if (nowAvailableAmountWei > amountWei) { + nowAvailableAmountWei = amountWei; + } + + require(msg.value <= nowAvailableAmountWei, "RATE_LIMIT_EXCESS"); - uint256 sharesAmount = getSharesByPooledEth(deposit); + if (amountIncreaseWei >= spentWei) { + spentWei = 0; + } else { + spentWei -= amountIncreaseWei; + } + + spentWei += msg.value; + if (spentWei >= amountWei) { + spentWei = amountWei; + } + + _writeStakingRateLimit(amountETH, uint64(spentWei / 10**18), uint64(currentMinutes), periodMinutes); + } + + uint256 sharesAmount = getSharesByPooledEth(msg.value); if (sharesAmount == 0) { // totalControlledEther is 0: either the first-ever deposit or complete slashing // assume that shares correspond to Ether 1-to-1 - sharesAmount = deposit; + sharesAmount = msg.value; } - _mintShares(sender, sharesAmount); - _submitted(sender, deposit, _referral); - _emitTransferAfterMintingShares(sender, sharesAmount); + _mintShares(msg.sender, sharesAmount); + _submitted(msg.sender, msg.value, _referral); + _emitTransferAfterMintingShares(msg.sender, sharesAmount); return sharesAmount; } @@ -850,4 +914,21 @@ contract Lido is ILido, StETH, AragonApp { assert(0 == temp_value); // fully converted result <<= (24 * 8); } + + /** + * @dev Write storage slot containing rate limit vars + */ + function _writeStakingRateLimit( + uint64 _amountETH, + uint64 _spentETH, + uint64 _lastStakeMinutes, + uint64 _periodMinutes + ) internal { + STAKING_RATE_LIMIT_POSITION.setStorageUint256( + (uint256(_amountETH) << 192) + | (uint256(_spentETH) << 128) + | (uint256(_lastStakeMinutes) << 64) + | (uint256(_periodMinutes)) + ); + } } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 46165e1ce..ab18977ba 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -39,14 +39,37 @@ interface ILido { /** * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) - * See `pauseStaking` for the details. + * or if new rate limit is required. + * + * @dev Reverts if `_rateLimitAmount` is zero. + * To disable the rate limit, set `_rateLimitPeriodMinutes` to zero. + * + * @param _rateLimitETHAmount amount of ETH per period + * @param _rateLimitPeriodMinutes period duration (in minutes) + */ + function resumeStaking(uint64 _rateLimitETHAmount, uint64 _rateLimitPeriodMinutes) external; + + /** + * @dev Read storage slot containing rate limit params and state. + */ + function getStakingRateLimit() public view returns ( + uint64 amountETH, + uint64 spentETH, + uint64 lastStakeMinutes, + uint64 periodMinutes + ); + + /** + * @notice check staking pause state + * Returns true if staking is on pause currently + * See `pauseStaking` and `resumeStaking` for the details. */ - function resumeStaking() external; + function isStakingPaused() public view returns(bool); event Stopped(); event Resumed(); event StakingPaused(); - event StakingResumed(); + event StakingResumed(uint64 rateLimitETHAmount, uint64 rateLimitPeriodMinutes); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 452ccb0b3..a785b927a 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -90,7 +90,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_RATE_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) @@ -552,24 +552,23 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) }) - it('staking pause/resume works', async () => { + it('staking pause/unlimited resume works', async () => { let receipt receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - assertRevert(app.pauseStaking(), 'APP_AUTH_FAILED') + await assertRevert(app.pauseStaking(), 'APP_AUTH_FAILED') receipt = await app.pauseStaking({ from: voting }) assertEvent(receipt, 'StakingPaused') - assertRevert(app.pauseStaking({ from: voting }), 'STAKING_ALREADY_PAUSED') assert.equal(await app.isStakingPaused(), true) - assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) - assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) + await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) + await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) - assertRevert(app.resumeStaking(), 'APP_AUTH_FAILED') - receipt = await app.resumeStaking({ from: voting }) + const disableRateLimit = 0 + await assertRevert(app.resumeStaking(1, disableRateLimit), 'APP_AUTH_FAILED') + receipt = await app.resumeStaking(1, disableRateLimit, { from: voting }) assertEvent(receipt, 'StakingResumed') - assertRevert(app.resumeStaking({ from: voting }), 'STAKING_ALREADY_RESUMED') assert.equal(await app.isStakingPaused(), false) await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) @@ -577,6 +576,66 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(1.4), referral: ZERO_ADDRESS } }) }) + const getTimestampMinutes = async () => { + const blockNum = await ethers.provider.getBlockNumber() + const block = await ethers.provider.getBlock(blockNum) + return Math.trunc(block.timestamp / 60) + } + + it('staking rate-limiting works', async () => { + let receipt + + const oneHour = 60 + const ethPerHour = 3 + + receipt = await app.resumeStaking(ethPerHour, oneHour, { from: voting }) + assertEvent(receipt, 'StakingResumed', { expectedArgs: { rateLimitETHAmount: ethPerHour, rateLimitPeriodMinutes: oneHour } }) + assert.equal(await app.isStakingPaused(), false) + ;({ amountETH, spentETH, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) + assertBn(amountETH, ethPerHour) + assertBn(spentETH, 0) + assertBn(lastStakeMinutes, 0) + assertBn(periodMinutes, oneHour) + + receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) + assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) + ;({ amountETH, spentETH, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) + assertBn(amountETH, ethPerHour) + assertBn(spentETH, 2) + assertBn(lastStakeMinutes, await getTimestampMinutes()) + assertBn(periodMinutes, oneHour) + + await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }), `RATE_LIMIT_EXCESS`) + + const halfHour = 30 * 60 + await ethers.provider.send('evm_increaseTime', [halfHour]) + await ethers.provider.send('evm_mine') + + await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.6) }), `RATE_LIMIT_EXCESS`) + + receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }) + assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2.5), referral: ZERO_ADDRESS } }) + ;({ amountETH, spentETH, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) + assertBn(amountETH, ethPerHour) + assertBn(spentETH, 3) + assertBn(lastStakeMinutes, await getTimestampMinutes()) + assertBn(periodMinutes, oneHour) + + await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `RATE_LIMIT_EXCESS`) + + const twentyMinutes = 20 * 60 + await ethers.provider.send('evm_increaseTime', [twentyMinutes]) + await ethers.provider.send('evm_mine') + + receipt = await app.submit(ZERO_ADDRESS, { from: user1, value: ETH(1) }) + assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user1, amount: ETH(1), referral: ZERO_ADDRESS } }) + ;({ amountETH, spentETH, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) + assertBn(amountETH, ethPerHour) + assertBn(spentETH, 3) + assertBn(lastStakeMinutes, await getTimestampMinutes()) + assertBn(periodMinutes, oneHour) + }) + it('reverts when trying to call unknown function', async () => { const wrongMethodABI = '0x00' await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1), data: wrongMethodABI }), 'NON_EMPTY_DATA') From b791efbd090b68bcd85b11db801e5dafe0623be7 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sun, 17 Apr 2022 14:23:10 +0300 Subject: [PATCH 099/159] doc: add notes about rate limit vars packing --- contracts/0.4.24/Lido.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index c8ddee8a6..382ddc48f 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -917,6 +917,10 @@ contract Lido is ILido, StETH, AragonApp { /** * @dev Write storage slot containing rate limit vars + * @param _amountETH amount of ETH per period + * @param _spentETH amount of already spent ETH + * @param _lastStakeMinutes timestamp (minutes) of the last stake + * @param _periodMinutes rate-limit period (minutes) */ function _writeStakingRateLimit( uint64 _amountETH, @@ -924,6 +928,18 @@ contract Lido is ILido, StETH, AragonApp { uint64 _lastStakeMinutes, uint64 _periodMinutes ) internal { + // We need to pack four variables into the same storage slot to lower the costs per each staking request. + // Since storage slot has 256bit size, we could proceed with 4 x 64bit vars. + // + // As a result, slot's memory aligned as follows: + // + // memory offset (bits) + // --------------------> + // 0________________64_________________128_________192__________256 + // |________________|___________________|___________|____________| + // | _periodMinutes | _lastStakeMinutes | _spentETH | _amountETH | + // + STAKING_RATE_LIMIT_POSITION.setStorageUint256( (uint256(_amountETH) << 192) | (uint256(_spentETH) << 128) From ef5ef14842b175df138a5f4baa8a4923c547fd34 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 18 Apr 2022 13:13:01 +0300 Subject: [PATCH 100/159] fix: handle preliminary review by Alexey P. Rename STAKING_RATE_LIMIT_ROLE to STAKING_RESUME_ROLE. Remove obsolete SafeMath for uint64. Use ether amount nominated in wei (change memory layour). --- contracts/0.4.24/Lido.sol | 97 +++++++++++++-------------- contracts/0.4.24/interfaces/ILido.sol | 19 ++++-- test/0.4.24/lido.test.js | 30 ++++----- 3 files changed, 74 insertions(+), 72 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 382ddc48f..c8fee4491 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -45,14 +45,13 @@ interface IERC721 { */ contract Lido is ILido, StETH, AragonApp { using SafeMath for uint256; - using SafeMath64 for uint64; using UnstructuredStorage for bytes32; /// ACL bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); bytes32 constant public RESUME_ROLE = keccak256("RESUME_ROLE"); bytes32 constant public STAKING_PAUSE_ROLE = keccak256("STAKING_PAUSE_ROLE"); - bytes32 constant public STAKING_RATE_LIMIT_ROLE = keccak256("STAKING_RATE_LIMIT_ROLE"); + bytes32 constant public STAKING_RESUME_ROLE = keccak256("STAKING_RESUME_ROLE"); bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE"); bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY"); bytes32 constant public MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE"); @@ -127,13 +126,13 @@ contract Lido is ILido, StETH, AragonApp { DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_depositContract)); _setProtocolContracts(_oracle, _treasury, _insuranceFund); - _writeStakingRateLimit(uint64(-1), 0, 0, 0); + _writeStakingRateLimit(uint96(-1), 0, 0, 0); initialized(); } /** - * @notice Cut-off new staking (every new submit ETH funds transaction would revert + * @notice Cut-off new staking (every new submit with ether funds transaction would revert * if `pauseStaking` was called previously) * * @dev Provides a way to pause staking without pushing PAUSE for the whole proto @@ -152,13 +151,13 @@ contract Lido is ILido, StETH, AragonApp { * @dev Reverts if `_rateLimitAmount` is zero. * To disable the rate limit, set `_rateLimitPeriodMinutes` to zero. * - * @param _rateLimitAmount amount of ETH per period + * @param _rateLimitAmount amount per period * @param _rateLimitPeriodMinutes period duration */ function resumeStaking( - uint64 _rateLimitAmount, - uint64 _rateLimitPeriodMinutes - ) external auth(STAKING_RATE_LIMIT_ROLE) { + uint96 _rateLimitAmount, + uint32 _rateLimitPeriodMinutes + ) external auth(STAKING_RESUME_ROLE) { require(_rateLimitAmount != 0, "ZERO_AMOUNT"); _writeStakingRateLimit(_rateLimitAmount, 0, 0, _rateLimitPeriodMinutes); @@ -170,17 +169,17 @@ contract Lido is ILido, StETH, AragonApp { * @dev Read storage slot containing rate limit params and state. */ function getStakingRateLimit() public view returns ( - uint64 amountETH, - uint64 spentETH, - uint64 lastStakeMinutes, - uint64 periodMinutes + uint96 amount, + uint96 spent, + uint32 lastStakeMinutes, + uint32 periodMinutes ) { uint256 packedRateLimit = STAKING_RATE_LIMIT_POSITION.getStorageUint256(); - amountETH = uint64(packedRateLimit >> 192); - spentETH = uint64(packedRateLimit >> 128); - lastStakeMinutes = uint64(packedRateLimit >> 64); - periodMinutes = uint64(packedRateLimit); + amount = uint96(packedRateLimit >> 160); + spent = uint96(packedRateLimit >> 64); + lastStakeMinutes = uint32(packedRateLimit >> 32); + periodMinutes = uint32(packedRateLimit); } /** @@ -189,8 +188,8 @@ contract Lido is ILido, StETH, AragonApp { * See `pauseStaking` and `resumeStaking` for the details. */ function isStakingPaused() public view returns(bool) { - (uint64 amountETH,,,) = getStakingRateLimit(); - return amountETH == 0; + (uint96 amount,,,) = getStakingRateLimit(); + return amount == 0; } /** @@ -585,42 +584,40 @@ contract Lido is ILido, StETH, AragonApp { */ function _submit(address _referral) internal whenNotStopped returns (uint256) { ( - uint64 amountETH, - uint64 spentETH, - uint64 lastStakeMinutes, - uint64 periodMinutes + uint256 amount, + uint256 spent, + uint256 lastStakeMinutes, + uint256 periodMinutes ) = getStakingRateLimit(); - require(amountETH > 0, "STAKING_PAUSED"); + require(amount > 0, "STAKING_PAUSED"); require(msg.value != 0, "ZERO_DEPOSIT"); if (periodMinutes > 0) { - uint256 amountWei = uint256(amountETH) * 10**18; - uint256 spentWei = uint256(spentETH) * 10**18; uint256 currentMinutes = block.timestamp / 60; - uint256 amountIncreaseWei = ( - amountWei * (currentMinutes - lastStakeMinutes) + uint256 amountIncrease = ( + amount * (currentMinutes - lastStakeMinutes) ) / periodMinutes; - uint256 nowAvailableAmountWei = amountIncreaseWei + (amountWei - spentWei); - if (nowAvailableAmountWei > amountWei) { - nowAvailableAmountWei = amountWei; + uint256 nowAvailableAmount = amountIncrease + (amount - spent); + if (nowAvailableAmount > amount) { + nowAvailableAmount = amount; } - require(msg.value <= nowAvailableAmountWei, "RATE_LIMIT_EXCESS"); + require(msg.value <= nowAvailableAmount, "RATE_LIMIT_EXCESS"); - if (amountIncreaseWei >= spentWei) { - spentWei = 0; + if (amountIncrease >= spent) { + spent = 0; } else { - spentWei -= amountIncreaseWei; + spent -= amountIncrease; } - spentWei += msg.value; - if (spentWei >= amountWei) { - spentWei = amountWei; + spent += msg.value; + if (spent >= amount) { + spent = amount; } - _writeStakingRateLimit(amountETH, uint64(spentWei / 10**18), uint64(currentMinutes), periodMinutes); + _writeStakingRateLimit(uint96(amount), uint96(spent), uint32(currentMinutes), uint32(periodMinutes)); } uint256 sharesAmount = getSharesByPooledEth(msg.value); @@ -917,16 +914,16 @@ contract Lido is ILido, StETH, AragonApp { /** * @dev Write storage slot containing rate limit vars - * @param _amountETH amount of ETH per period - * @param _spentETH amount of already spent ETH + * @param _amount amount per period + * @param _spent already spent amount * @param _lastStakeMinutes timestamp (minutes) of the last stake * @param _periodMinutes rate-limit period (minutes) */ function _writeStakingRateLimit( - uint64 _amountETH, - uint64 _spentETH, - uint64 _lastStakeMinutes, - uint64 _periodMinutes + uint96 _amount, + uint96 _spent, + uint32 _lastStakeMinutes, + uint32 _periodMinutes ) internal { // We need to pack four variables into the same storage slot to lower the costs per each staking request. // Since storage slot has 256bit size, we could proceed with 4 x 64bit vars. @@ -935,15 +932,15 @@ contract Lido is ILido, StETH, AragonApp { // // memory offset (bits) // --------------------> - // 0________________64_________________128_________192__________256 - // |________________|___________________|___________|____________| - // | _periodMinutes | _lastStakeMinutes | _spentETH | _amountETH | + // 0________________32_________________64_______160_______256 + // |________________|___________________|________|_________| + // | _periodMinutes | _lastStakeMinutes | _spent | _amount | // STAKING_RATE_LIMIT_POSITION.setStorageUint256( - (uint256(_amountETH) << 192) - | (uint256(_spentETH) << 128) - | (uint256(_lastStakeMinutes) << 64) + (uint256(_amount) << 160) + | (uint256(_spent) << 64) + | (uint256(_lastStakeMinutes) << 32) | (uint256(_periodMinutes)) ); } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index ab18977ba..3cb008669 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -44,19 +44,24 @@ interface ILido { * @dev Reverts if `_rateLimitAmount` is zero. * To disable the rate limit, set `_rateLimitPeriodMinutes` to zero. * - * @param _rateLimitETHAmount amount of ETH per period + * @param _rateLimitAmount amount per period * @param _rateLimitPeriodMinutes period duration (in minutes) */ - function resumeStaking(uint64 _rateLimitETHAmount, uint64 _rateLimitPeriodMinutes) external; + function resumeStaking(uint96 _rateLimitAmount, uint32 _rateLimitPeriodMinutes) external; /** * @dev Read storage slot containing rate limit params and state. + * @return + * `amount` amount per period + * `spend` spent up to now + * `lastStakeMinutes` last executed staking timestamp (minutes) + * `periodMinutes` rate limit period duration (minutes) */ function getStakingRateLimit() public view returns ( - uint64 amountETH, - uint64 spentETH, - uint64 lastStakeMinutes, - uint64 periodMinutes + uint96 amount, + uint96 spent, + uint32 lastStakeMinutes, + uint32 periodMinutes ); /** @@ -69,7 +74,7 @@ interface ILido { event Stopped(); event Resumed(); event StakingPaused(); - event StakingResumed(uint64 rateLimitETHAmount, uint64 rateLimitPeriodMinutes); + event StakingResumed(uint96 rateLimitAmount, uint96 rateLimitPeriodMinutes); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index a785b927a..b7248c15c 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -90,7 +90,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.STAKING_RATE_LIMIT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) @@ -586,22 +586,22 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let receipt const oneHour = 60 - const ethPerHour = 3 + const ethPerHour = ETH(3) receipt = await app.resumeStaking(ethPerHour, oneHour, { from: voting }) - assertEvent(receipt, 'StakingResumed', { expectedArgs: { rateLimitETHAmount: ethPerHour, rateLimitPeriodMinutes: oneHour } }) + assertEvent(receipt, 'StakingResumed', { expectedArgs: { rateLimitAmount: ethPerHour, rateLimitPeriodMinutes: oneHour } }) assert.equal(await app.isStakingPaused(), false) - ;({ amountETH, spentETH, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) - assertBn(amountETH, ethPerHour) - assertBn(spentETH, 0) + ;({ amount, spent, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) + assertBn(amount, ethPerHour) + assertBn(spent, 0) assertBn(lastStakeMinutes, 0) assertBn(periodMinutes, oneHour) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - ;({ amountETH, spentETH, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) - assertBn(amountETH, ethPerHour) - assertBn(spentETH, 2) + ;({ amount, spent, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) + assertBn(amount, ethPerHour) + assertBn(spent, ETH(2)) assertBn(lastStakeMinutes, await getTimestampMinutes()) assertBn(periodMinutes, oneHour) @@ -615,9 +615,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2.5), referral: ZERO_ADDRESS } }) - ;({ amountETH, spentETH, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) - assertBn(amountETH, ethPerHour) - assertBn(spentETH, 3) + ;({ amount, spent, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) + assertBn(amount, ethPerHour) + assertBn(spent, ETH(3)) assertBn(lastStakeMinutes, await getTimestampMinutes()) assertBn(periodMinutes, oneHour) @@ -629,9 +629,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) receipt = await app.submit(ZERO_ADDRESS, { from: user1, value: ETH(1) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user1, amount: ETH(1), referral: ZERO_ADDRESS } }) - ;({ amountETH, spentETH, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) - assertBn(amountETH, ethPerHour) - assertBn(spentETH, 3) + ;({ amount, spent, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) + assertBn(amount, ethPerHour) + assertBn(spent, ETH(3)) assertBn(lastStakeMinutes, await getTimestampMinutes()) assertBn(periodMinutes, oneHour) }) From 4749e5025d2908965f39c22df44d1c0918ee2681 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 19 Apr 2022 22:17:28 +0000 Subject: [PATCH 101/159] feat: change rate-limit design Use suggestions and insights by Sam. K, Eugene P. and Alexey P.: - pass limit increase per block instead of duration; - introduce bit twiddling library; - add external viewers to integrate with frontend. Tests are need to be updated. --- contracts/0.4.24/Lido.sol | 239 ++++++++++++----------- contracts/0.4.24/interfaces/ILido.sol | 73 ++++--- contracts/0.4.24/lib/StakeLimitUtils.sol | 82 ++++++++ test/0.4.24/lido.test.js | 96 ++++----- 4 files changed, 294 insertions(+), 196 deletions(-) create mode 100644 contracts/0.4.24/lib/StakeLimitUtils.sol diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index c8fee4491..688203b78 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -8,7 +8,6 @@ pragma solidity 0.4.24; import "@aragon/os/contracts/apps/AragonApp.sol"; import "@aragon/os/contracts/lib/math/SafeMath.sol"; import "@aragon/os/contracts/lib/math/SafeMath64.sol"; -import "@aragon/os/contracts/common/IsContract.sol"; import "solidity-bytes-utils/contracts/BytesLib.sol"; import "./interfaces/ILido.sol"; @@ -18,6 +17,8 @@ import "./interfaces/ILidoMevTxFeeVault.sol"; import "./StETH.sol"; +import "./lib/StakeLimitUtils.sol"; + interface IERC721 { /// @notice Transfer ownership of an NFT @@ -46,6 +47,7 @@ interface IERC721 { contract Lido is ILido, StETH, AragonApp { using SafeMath for uint256; using UnstructuredStorage for bytes32; + using StakeLimitUtils for uint256; /// ACL bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); @@ -85,7 +87,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 internal constant MEV_TX_FEE_VAULT_POSITION = keccak256("lido.Lido.mevTxFeeVault"); /// @dev storage slot position of the staking rate limit structure - bytes32 internal constant STAKING_RATE_LIMIT_POSITION = keccak256("lido.Lido.stakingRateLimit"); + bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("lido.Lido.stakeLimit"); /// @dev amount of Ether (on the current Ethereum side) buffered on this smart contract balance bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther"); /// @dev number of deposited validators (incrementing counter of deposit operations). @@ -126,70 +128,118 @@ contract Lido is ILido, StETH, AragonApp { DEPOSIT_CONTRACT_POSITION.setStorageAddress(address(_depositContract)); _setProtocolContracts(_oracle, _treasury, _insuranceFund); - _writeStakingRateLimit(uint96(-1), 0, 0, 0); + + STAKE_LIMIT_POSITION.setStorageUint256( + StakeLimitUtils.encodeStakeLimitSlot(0, 0, 0, uint32(block.number)) + ); initialized(); } /** - * @notice Cut-off new staking (every new submit with ether funds transaction would revert - * if `pauseStaking` was called previously) + * @notice Cut-off new stake (every new staking transaction submitting user-provided ETH + * would revert if `pauseStake` was called previously). * - * @dev Provides a way to pause staking without pushing PAUSE for the whole proto - * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue + * @dev A way to pause stake without pushing PAUSE for the whole proto. + * The main goal is to prevent huge APR losses for existing stakers due to high demands + * on post-Merge entry queue. + * + * Emits `StakingPaused` event. */ function pauseStaking() external auth(STAKING_PAUSE_ROLE) { - _writeStakingRateLimit(0, 0, 0, 0); - + STAKE_LIMIT_POSITION.setStorageUint256(0); emit StakingPaused(); } /** * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) - * or if new rate limit is desired. + * or if new rate-limit params are required. + * + * Staking could be rate-limited by imposing a limit on the stake amount + * at each moment in time. * - * @dev Reverts if `_rateLimitAmount` is zero. - * To disable the rate limit, set `_rateLimitPeriodMinutes` to zero. + * ▲ Stake limit + * │..... ..... ........ ... .... ... Stake limit = max + * │ . . . . . . . . . + * │ . . . . . . . . . + * │ . . . . . + * │──────────────────────────────────────────────────> Time + * │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events * - * @param _rateLimitAmount amount per period - * @param _rateLimitPeriodMinutes period duration + * If `maxStakeLimit` is set to zero then rate-limit is disabled. + * + * Emits `StakeResumed` event + * + * @param _maxStakeLimit max stake limit value + * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ function resumeStaking( - uint96 _rateLimitAmount, - uint32 _rateLimitPeriodMinutes + uint96 _maxStakeLimit, + uint96 _stakeLimitIncreasePerBlock ) external auth(STAKING_RESUME_ROLE) { - require(_rateLimitAmount != 0, "ZERO_AMOUNT"); + ( + uint96 maxStakeLimit,, + uint96 prevStakeLimit, + uint32 prevStakeBlockNumber + ) = STAKE_LIMIT_POSITION.getStorageUint256().decodeStakeLimitSlot(); + + // if staking was paused or unlimited previously, + // reset current limit to max and save current block + if (maxStakeLimit == 0) { + prevStakeLimit = _maxStakeLimit; + prevStakeBlockNumber = uint32(block.number); + } - _writeStakingRateLimit(_rateLimitAmount, 0, 0, _rateLimitPeriodMinutes); + STAKE_LIMIT_POSITION.setStorageUint256( + StakeLimitUtils.encodeStakeLimitSlot( + _maxStakeLimit, + _stakeLimitIncreasePerBlock, + prevStakeLimit, + prevStakeBlockNumber + ) + ); - emit StakingResumed(_rateLimitAmount, _rateLimitPeriodMinutes); + emit StakingResumed(_maxStakeLimit, _stakeLimitIncreasePerBlock); } /** - * @dev Read storage slot containing rate limit params and state. + * @notice Check staking state: whether it's paused or not */ - function getStakingRateLimit() public view returns ( - uint96 amount, - uint96 spent, - uint32 lastStakeMinutes, - uint32 periodMinutes - ) { - uint256 packedRateLimit = STAKING_RATE_LIMIT_POSITION.getStorageUint256(); - - amount = uint96(packedRateLimit >> 160); - spent = uint96(packedRateLimit >> 64); - lastStakeMinutes = uint32(packedRateLimit >> 32); - periodMinutes = uint32(packedRateLimit); + function isStakingPaused() external view returns (bool) { + return STAKE_LIMIT_POSITION.getStorageUint256() == 0; } /** - * @notice check staking pause state - * Returns true if staking is on pause currently - * See `pauseStaking` and `resumeStaking` for the details. + * @notice Get current stake limit value and main params. + * See `resumeStaking` for the details. + * + * @dev Reverts if staking is paused + * NB: returns zero `maxStakeLimit` if rate-limit is disabled */ - function isStakingPaused() public view returns(bool) { - (uint96 amount,,,) = getStakingRateLimit(); - return amount == 0; + function getCurrentStakeLimit() external view returns ( + uint256 currentStakeLimit, + uint256 maxStakeLimit, + uint256 stakeLimitIncreasePerBlock + ) { + uint256 slotValue = STAKE_LIMIT_POSITION.getStorageUint256(); + require(slotValue != 0); + + uint256 prevStakeLimit; + uint256 prevStakeBlockNumber; + + ( + maxStakeLimit, + stakeLimitIncreasePerBlock, + prevStakeLimit, + prevStakeBlockNumber + ) = slotValue.decodeStakeLimitSlot(); + + currentStakeLimit = StakeLimitUtils.getCurrentStakeLimit( + maxStakeLimit, + stakeLimitIncreasePerBlock, + prevStakeLimit, + prevStakeBlockNumber + ); } /** @@ -430,7 +480,7 @@ contract Lido is ILido, StETH, AragonApp { function transferToVault(address _token) external { require(allowRecoverability(_token), "RECOVER_DISALLOWED"); address vault = getRecoveryVault(); - require(isContract(vault), "RECOVER_VAULT_NOT_CONTRACT"); + require(vault != address(0)); uint256 balance; if (_token == ETH) { @@ -566,9 +616,9 @@ contract Lido is ILido, StETH, AragonApp { * @param _oracle oracle contract */ function _setProtocolContracts(address _oracle, address _treasury, address _insuranceFund) internal { - require(isContract(_oracle), "NOT_A_CONTRACT"); - require(_treasury != address(0), "SET_TREASURY_ZERO_ADDRESS"); - require(_insuranceFund != address(0), "SET_INSURANCE_FUND_ZERO_ADDRESS"); + require(_oracle != address(0), "ORACLE_ZERO_ADDRESS"); + require(_treasury != address(0), "TREASURY_ZERO_ADDRESS"); + require(_insuranceFund != address(0), "INSURANCE_FUND_ZERO_ADDRESS"); ORACLE_POSITION.setStorageAddress(_oracle); TREASURY_POSITION.setStorageAddress(_treasury); @@ -583,41 +633,36 @@ contract Lido is ILido, StETH, AragonApp { * @return amount of StETH shares generated */ function _submit(address _referral) internal whenNotStopped returns (uint256) { - ( - uint256 amount, - uint256 spent, - uint256 lastStakeMinutes, - uint256 periodMinutes - ) = getStakingRateLimit(); + uint256 stakeLimitSlotValue = STAKE_LIMIT_POSITION.getStorageUint256(); - require(amount > 0, "STAKING_PAUSED"); + require(stakeLimitSlotValue != 0, "STAKING_PAUSED"); require(msg.value != 0, "ZERO_DEPOSIT"); - if (periodMinutes > 0) { - uint256 currentMinutes = block.timestamp / 60; - uint256 amountIncrease = ( - amount * (currentMinutes - lastStakeMinutes) - ) / periodMinutes; - - uint256 nowAvailableAmount = amountIncrease + (amount - spent); - if (nowAvailableAmount > amount) { - nowAvailableAmount = amount; - } - - require(msg.value <= nowAvailableAmount, "RATE_LIMIT_EXCESS"); - - if (amountIncrease >= spent) { - spent = 0; - } else { - spent -= amountIncrease; - } + ( + uint96 maxStakeLimit, + uint96 stakeLimitIncPerBlock, + uint96 prevStakeLimit, + uint32 prevStakeBlockNumber + ) = stakeLimitSlotValue.decodeStakeLimitSlot(); + + if (maxStakeLimit != 0) { + uint256 currentStakeLimit = StakeLimitUtils.getCurrentStakeLimit( + maxStakeLimit, + stakeLimitIncPerBlock, + prevStakeLimit, + prevStakeBlockNumber + ); - spent += msg.value; - if (spent >= amount) { - spent = amount; - } + require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); - _writeStakingRateLimit(uint96(amount), uint96(spent), uint32(currentMinutes), uint32(periodMinutes)); + STAKE_LIMIT_POSITION.setStorageUint256( + StakeLimitUtils.encodeStakeLimitSlot( + maxStakeLimit, + stakeLimitIncPerBlock, + uint96(currentStakeLimit - msg.value), + uint32(block.number) + ) + ); } uint256 sharesAmount = getSharesByPooledEth(msg.value); @@ -628,7 +673,10 @@ contract Lido is ILido, StETH, AragonApp { } _mintShares(msg.sender, sharesAmount); - _submitted(msg.sender, msg.value, _referral); + + BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(msg.value)); + emit Submitted(msg.sender, msg.value, _referral); + _emitTransferAfterMintingShares(msg.sender, sharesAmount); return sharesAmount; } @@ -807,18 +855,6 @@ contract Lido is ILido, StETH, AragonApp { } } - /** - * @dev Records a deposit made by a user with optional referral - * @param _sender sender's address - * @param _value Deposit value in wei - * @param _referral address of the referral - */ - function _submitted(address _sender, uint256 _value, address _referral) internal { - BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(_value)); - - emit Submitted(_sender, _value, _referral); - } - /** * @dev Records a deposit to the deposit_contract.deposit function * @param _amount Total amount deposited to the ETH 2.0 side @@ -911,37 +947,4 @@ contract Lido is ILido, StETH, AragonApp { assert(0 == temp_value); // fully converted result <<= (24 * 8); } - - /** - * @dev Write storage slot containing rate limit vars - * @param _amount amount per period - * @param _spent already spent amount - * @param _lastStakeMinutes timestamp (minutes) of the last stake - * @param _periodMinutes rate-limit period (minutes) - */ - function _writeStakingRateLimit( - uint96 _amount, - uint96 _spent, - uint32 _lastStakeMinutes, - uint32 _periodMinutes - ) internal { - // We need to pack four variables into the same storage slot to lower the costs per each staking request. - // Since storage slot has 256bit size, we could proceed with 4 x 64bit vars. - // - // As a result, slot's memory aligned as follows: - // - // memory offset (bits) - // --------------------> - // 0________________32_________________64_______160_______256 - // |________________|___________________|________|_________| - // | _periodMinutes | _lastStakeMinutes | _spent | _amount | - // - - STAKING_RATE_LIMIT_POSITION.setStorageUint256( - (uint256(_amount) << 160) - | (uint256(_spent) << 64) - | (uint256(_lastStakeMinutes) << 32) - | (uint256(_periodMinutes)) - ); - } } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 3cb008669..ee998a5a2 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -29,52 +29,63 @@ interface ILido { function resume() external; /** - * @notice Cut-off new staking (every new submit ETH funds transaction would revert - * if `pauseStaking` was called previously) + * @notice Cut-off new stake (every new staking transaction submitting user-provided ETH + * would revert if `pauseStake` was called previously). * - * @dev Provides a way to pause staking without pushing PAUSE for the whole proto - * The main goal is to prevent huge APR losses for existing stakers due to high demands on entry queue + * @dev A way to pause stake without pushing PAUSE for the whole proto. + * The main goal is to prevent huge APR losses for existing stakers due to high demands + * on post-Merge entry queue. + * + * Emits `StakingPaused` event. */ function pauseStaking() external; /** * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) - * or if new rate limit is required. + * or if new rate-limit params are required. + * + * Staking could be rate-limited by imposing a limit on the stake amount + * at each moment in time. + * + * ▲ Stake limit + * │..... ..... ........ ... .... ... Stake limit = max + * │ . . . . . . . . . + * │ . . . . . . . . . + * │ . . . . . + * │──────────────────────────────────────────────────> Time + * │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events * - * @dev Reverts if `_rateLimitAmount` is zero. - * To disable the rate limit, set `_rateLimitPeriodMinutes` to zero. + * If `maxStakeLimit` is set to zero then rate-limit is disabled. * - * @param _rateLimitAmount amount per period - * @param _rateLimitPeriodMinutes period duration (in minutes) + * Emits `StakeResumed` event + * + * @param _maxStakeLimit max stake limit value + * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ - function resumeStaking(uint96 _rateLimitAmount, uint32 _rateLimitPeriodMinutes) external; + function resumeStaking(uint96 _maxStakeLimit, uint96 _stakeLimitIncreasePerBlock) external; /** - * @dev Read storage slot containing rate limit params and state. - * @return - * `amount` amount per period - * `spend` spent up to now - * `lastStakeMinutes` last executed staking timestamp (minutes) - * `periodMinutes` rate limit period duration (minutes) + * @notice Check staking state: whether it's paused or not */ - function getStakingRateLimit() public view returns ( - uint96 amount, - uint96 spent, - uint32 lastStakeMinutes, - uint32 periodMinutes - ); + function isStakingPaused() external view returns (bool); /** - * @notice check staking pause state - * Returns true if staking is on pause currently - * See `pauseStaking` and `resumeStaking` for the details. + * @notice Get current stake limit value and main params. + * See `resumeStaking` for the details. + * + * @dev Reverts if staking is paused + * NB: returns zero `maxStakeLimit` if rate-limit is disabled */ - function isStakingPaused() public view returns(bool); + function getCurrentStakeLimit() external view returns ( + uint256 currentStakeLimit, + uint256 maxStakeLimit, + uint256 stakeLimitIncreasePerBlock + ); event Stopped(); event Resumed(); event StakingPaused(); - event StakingResumed(uint96 rateLimitAmount, uint96 rateLimitPeriodMinutes); + event StakingResumed(uint96 maxStakeLimit, uint96 stakeLimitIncreasePerBlock); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). @@ -135,10 +146,10 @@ interface ILido { event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints); /** - * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract - * @dev We need a separate function because funds received by default payable function - * are considered as funds submitted by a user for staking - */ + * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract + * @dev We need a separate function because funds received by default payable function + * are considered as funds submitted by a user for staking + */ function receiveMevTxFee() external payable; // The amount of ETH withdrawn from LidoMevTxFeeVault contract to Lido contract diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol new file mode 100644 index 000000000..8f3b73bbd --- /dev/null +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -0,0 +1,82 @@ +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity 0.4.24; + +// +// We need to pack four variables into the same 256bit-wide storage slot +// to lower the costs per each staking request. +// +// As a result, slot's memory aligned as follows: +// +// LSB ------------------------------------------------------------------------------> MSB +// 0______________________32______________128_________________________160______________256 +// |______________________|________________|___________________________|________________| +// | prevStakeBlockNumber | prevStakeLimit | maxStakeLimitGrowthBlocks | maxStakeLimit | +// |<----- 32 bits ------>|<-- 96 bits --->|<---------- 32 bits ------>|<--- 96 bits -->| +// +// +// NB: we represent `maxStakeLimitGrowthPeriod` as follows: +// `maxStakeLimitGrowthPeriod` = `maxStakeLimit` / `stakeLimitIncPerBlock` +// 32 bits 96 bits 96 bits +// + +library StakeLimitUtils { + uint256 internal constant MAX_STAKE_LIMIT_OFFSET = 160; + uint256 internal constant MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET = 128; + uint256 internal constant PREV_STAKE_LIMIT_OFFSET = 32; + uint256 internal constant PREV_STAKE_BLOCK_NUMBER_OFFSET = 0; + + /** + * @notice Unpack the slot value into stake limit params and state. + */ + function decodeStakeLimitSlot(uint256 _slotValue) internal pure returns ( + uint96 maxStakeLimit, + uint96 stakeLimitIncPerBlock, + uint96 prevStakeLimit, + uint32 prevStakeBlockNumber + ) { + maxStakeLimit = uint96(_slotValue >> MAX_STAKE_LIMIT_OFFSET); + uint32 growthBlocks = uint32(_slotValue >> MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET); + if (growthBlocks > 0) { + stakeLimitIncPerBlock = maxStakeLimit / growthBlocks; + } + prevStakeLimit = uint96(_slotValue >> PREV_STAKE_LIMIT_OFFSET); + prevStakeBlockNumber = uint32(_slotValue >> PREV_STAKE_BLOCK_NUMBER_OFFSET); + } + + /** + * @notice Pack stake limit params and state into a slot. + */ + function encodeStakeLimitSlot( + uint96 maxStakeLimit, + uint96 stakeLimitIncPerBlock, + uint96 prevStakeLimit, + uint32 prevStakeBlockNumber + ) internal pure returns (uint256 ret) { + ret = uint256(maxStakeLimit) << MAX_STAKE_LIMIT_OFFSET + | uint256(prevStakeLimit) << PREV_STAKE_LIMIT_OFFSET + | uint256(prevStakeBlockNumber) << PREV_STAKE_BLOCK_NUMBER_OFFSET; + + if (stakeLimitIncPerBlock > 0) { + ret |= uint256(uint32(maxStakeLimit / stakeLimitIncPerBlock)) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; + } + } + + /** + * @notice Calculate stake limit for the current block. + */ + function getCurrentStakeLimit( + uint256 maxStakeLimit, + uint256 stakeLimitIncPerBlock, + uint256 prevStakeLimit, + uint256 prevBlockNumber + ) internal view returns(uint256 limit) { + limit = prevStakeLimit + ((block.number - prevBlockNumber) * stakeLimitIncPerBlock); + if (limit > maxStakeLimit) { + limit = maxStakeLimit; + } + } +} diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index b7248c15c..c879f4a23 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -310,7 +310,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('setOracle works', async () => { - await assertRevert(app.setProtocolContracts(user1, user2, user3, { from: voting }), 'NOT_A_CONTRACT') + await assertRevert(app.setProtocolContracts(ZERO_ADDRESS, user2, user3, { from: voting }), 'ORACLE_ZERO_ADDRESS') const receipt = await app.setProtocolContracts(yetAnotherOracle.address, oracle.address, oracle.address, { from: voting }) assertEvent(receipt, 'ProtocolContactsSet', { expectedArgs: { oracle: yetAnotherOracle.address } }) assert.equal(await app.getOracle(), yetAnotherOracle.address) @@ -552,7 +552,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) }) - it('staking pause/unlimited resume works', async () => { + it('stake pause/unlimited resume works', async () => { let receipt receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) @@ -561,13 +561,14 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.pauseStaking(), 'APP_AUTH_FAILED') receipt = await app.pauseStaking({ from: voting }) assertEvent(receipt, 'StakingPaused') + assert.equal(await app.isStakingPaused(), true) await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) - const disableRateLimit = 0 - await assertRevert(app.resumeStaking(1, disableRateLimit), 'APP_AUTH_FAILED') - receipt = await app.resumeStaking(1, disableRateLimit, { from: voting }) + const disableStakeLimit = 0 + await assertRevert(app.resumeStaking(disableStakeLimit, 0), 'APP_AUTH_FAILED') + receipt = await app.resumeStaking(disableStakeLimit, 0, { from: voting }) assertEvent(receipt, 'StakingResumed') assert.equal(await app.isStakingPaused(), false) @@ -576,64 +577,65 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(1.4), referral: ZERO_ADDRESS } }) }) - const getTimestampMinutes = async () => { - const blockNum = await ethers.provider.getBlockNumber() - const block = await ethers.provider.getBlock(blockNum) - return Math.trunc(block.timestamp / 60) + const getBlockNumber = async () => { + return await ethers.provider.getBlockNumber() + } + + const mineNBlocks = async (n) => { + for (let index = 0; index < n; index++) { + await ethers.provider.send('evm_mine') + } } - it('staking rate-limiting works', async () => { + it('stake rate-limiting works', async () => { let receipt - const oneHour = 60 - const ethPerHour = ETH(3) + const blocksToReachMaxStakeLimit = 250 + const expectedMaxStakeLimit = ETH(3) + const limitIncreasePerBlock = bn(expectedMaxStakeLimit).div(bn(blocksToReachMaxStakeLimit)) // 12 * 10**15 - receipt = await app.resumeStaking(ethPerHour, oneHour, { from: voting }) - assertEvent(receipt, 'StakingResumed', { expectedArgs: { rateLimitAmount: ethPerHour, rateLimitPeriodMinutes: oneHour } }) + receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingResumed', { + expectedArgs: { + maxStakeLimit: expectedMaxStakeLimit, + stakeLimitIncreasePerBlock: limitIncreasePerBlock + } + }) assert.equal(await app.isStakingPaused(), false) - ;({ amount, spent, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) - assertBn(amount, ethPerHour) - assertBn(spent, 0) - assertBn(lastStakeMinutes, 0) - assertBn(periodMinutes, oneHour) + ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) + assertBn(maxStakeLimit, expectedMaxStakeLimit) + assertBn(stakeLimitIncreasePerBlock, limitIncreasePerBlock) + assertBn(currentStakeLimit, expectedMaxStakeLimit) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - ;({ amount, spent, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) - assertBn(amount, ethPerHour) - assertBn(spent, ETH(2)) - assertBn(lastStakeMinutes, await getTimestampMinutes()) - assertBn(periodMinutes, oneHour) - - await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }), `RATE_LIMIT_EXCESS`) + ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) + assertBn(maxStakeLimit, expectedMaxStakeLimit) + assertBn(stakeLimitIncreasePerBlock, limitIncreasePerBlock) + assertBn(currentStakeLimit, ETH(1)) - const halfHour = 30 * 60 - await ethers.provider.send('evm_increaseTime', [halfHour]) - await ethers.provider.send('evm_mine') + await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }), `STAKE_LIMIT`) - await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.6) }), `RATE_LIMIT_EXCESS`) + await mineNBlocks(122) // why not 125??? + await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.6) }), `STAKE_LIMIT`) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2.5), referral: ZERO_ADDRESS } }) - ;({ amount, spent, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) - assertBn(amount, ethPerHour) - assertBn(spent, ETH(3)) - assertBn(lastStakeMinutes, await getTimestampMinutes()) - assertBn(periodMinutes, oneHour) + ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) + assertBn(maxStakeLimit, expectedMaxStakeLimit) + assertBn(stakeLimitIncreasePerBlock, limitIncreasePerBlock) + assertBn(currentStakeLimit, ETH(0)) - await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `RATE_LIMIT_EXCESS`) + await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) - const twentyMinutes = 20 * 60 - await ethers.provider.send('evm_increaseTime', [twentyMinutes]) - await ethers.provider.send('evm_mine') + await mineNBlocks(82) // why? receipt = await app.submit(ZERO_ADDRESS, { from: user1, value: ETH(1) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user1, amount: ETH(1), referral: ZERO_ADDRESS } }) - ;({ amount, spent, lastStakeMinutes, periodMinutes } = await app.getStakingRateLimit()) - assertBn(amount, ethPerHour) - assertBn(spent, ETH(3)) - assertBn(lastStakeMinutes, await getTimestampMinutes()) - assertBn(periodMinutes, oneHour) + ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) + assertBn(maxStakeLimit, expectedMaxStakeLimit) + assertBn(stakeLimitIncreasePerBlock, limitIncreasePerBlock) + // assertBn(currentStakeLimit, ETH(0)) }) it('reverts when trying to call unknown function', async () => { @@ -1330,7 +1332,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('reverts when treasury is zero address', async () => { await assertRevert( app.setProtocolContracts(await app.getOracle(), ZERO_ADDRESS, await app.getInsuranceFund(), { from: voting }), - 'SET_TREASURY_ZERO_ADDRESS' + 'TREASURY_ZERO_ADDRESS' ) }) }) @@ -1354,7 +1356,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('reverts when insurance fund is zero address', async () => { await assertRevert( app.setProtocolContracts(await app.getOracle(), await app.getTreasury(), ZERO_ADDRESS, { from: voting }), - 'SET_INSURANCE_FUND_ZERO_ADDRESS' + 'INSURANCE_FUND_ZERO_ADDRESS' ) }) }) @@ -1365,7 +1367,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('reverts when vault is not set', async () => { - await assertRevert(app.transferToVault(anyToken.address, { from: nobody }), 'RECOVER_VAULT_NOT_CONTRACT') + await assertRevert(app.transferToVault(anyToken.address, { from: nobody })) }) context('recovery works with vault mock deployed', () => { From 646e7e44d47597ffe36d29d4bad89f76ec67d794 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 20 Apr 2022 10:17:14 +0000 Subject: [PATCH 102/159] fix: use sentinel _auth to minimize bytecode bloat modifiers are too costly --- contracts/0.4.24/Lido.sol | 54 ++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 688203b78..051264b36 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -146,7 +146,9 @@ contract Lido is ILido, StETH, AragonApp { * * Emits `StakingPaused` event. */ - function pauseStaking() external auth(STAKING_PAUSE_ROLE) { + function pauseStaking() external { + _auth(STAKING_PAUSE_ROLE); + STAKE_LIMIT_POSITION.setStorageUint256(0); emit StakingPaused(); } @@ -167,6 +169,7 @@ contract Lido is ILido, StETH, AragonApp { * │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events * * If `maxStakeLimit` is set to zero then rate-limit is disabled. + * NB: Reverts if `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2**64 * * Emits `StakeResumed` event * @@ -176,7 +179,9 @@ contract Lido is ILido, StETH, AragonApp { function resumeStaking( uint96 _maxStakeLimit, uint96 _stakeLimitIncreasePerBlock - ) external auth(STAKING_RESUME_ROLE) { + ) external { + _auth(STAKING_RESUME_ROLE); + ( uint96 maxStakeLimit,, uint96 prevStakeLimit, @@ -282,7 +287,9 @@ contract Lido is ILido, StETH, AragonApp { * @notice Deposits buffered ethers to the official DepositContract. * @dev This function is separated from submit() to reduce the cost of sending funds. */ - function depositBufferedEther() external auth(DEPOSIT_ROLE) { + function depositBufferedEther() external { + _auth(DEPOSIT_ROLE); + return _depositBufferedEther(DEFAULT_MAX_DEPOSITS_PER_CALL); } @@ -290,7 +297,9 @@ contract Lido is ILido, StETH, AragonApp { * @notice Deposits buffered ethers to the official DepositContract, making no more than `_maxDeposits` deposit calls. * @dev This function is separated from submit() to reduce the cost of sending funds. */ - function depositBufferedEther(uint256 _maxDeposits) external auth(DEPOSIT_ROLE) { + function depositBufferedEther(uint256 _maxDeposits) external { + _auth(DEPOSIT_ROLE); + return _depositBufferedEther(_maxDeposits); } @@ -305,14 +314,18 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Stop pool routine operations */ - function stop() external auth(PAUSE_ROLE) { + function stop() external { + _auth(PAUSE_ROLE); + _stop(); } /** * @notice Resume pool routine operations */ - function resume() external auth(RESUME_ROLE) { + function resume() external { + _auth(RESUME_ROLE); + _resume(); } @@ -321,7 +334,9 @@ contract Lido is ILido, StETH, AragonApp { * The fees are accrued when oracles report staking results. * @param _feeBasisPoints Fee rate, in basis points */ - function setFee(uint16 _feeBasisPoints) external auth(MANAGE_FEE) { + function setFee(uint16 _feeBasisPoints) external { + _auth(MANAGE_FEE); + _setBPValue(FEE_POSITION, _feeBasisPoints); emit FeeSet(_feeBasisPoints); } @@ -338,8 +353,10 @@ contract Lido is ILido, StETH, AragonApp { uint16 _insuranceFeeBasisPoints, uint16 _operatorsFeeBasisPoints ) - external auth(MANAGE_FEE) + external { + _auth(MANAGE_FEE); + require( TOTAL_BASIS_POINTS == uint256(_treasuryFeeBasisPoints) .add(uint256(_insuranceFeeBasisPoints)) @@ -371,7 +388,9 @@ contract Lido is ILido, StETH, AragonApp { address _oracle, address _treasury, address _insuranceFund - ) external auth(MANAGE_PROTOCOL_CONTRACTS_ROLE) { + ) external { + _auth(MANAGE_PROTOCOL_CONTRACTS_ROLE); + _setProtocolContracts(_oracle, _treasury, _insuranceFund); } @@ -381,7 +400,9 @@ contract Lido is ILido, StETH, AragonApp { * @param _withdrawalCredentials hash of withdrawal multisignature key as accepted by * the deposit_contract.deposit function */ - function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external auth(MANAGE_WITHDRAWAL_KEY) { + function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external { + _auth(MANAGE_WITHDRAWAL_KEY); + WITHDRAWAL_CREDENTIALS_POSITION.setStorageBytes32(_withdrawalCredentials); getOperators().trimUnusedKeys(); @@ -392,7 +413,9 @@ contract Lido is ILido, StETH, AragonApp { * @dev Sets given address as the address of LidoMevTxFeeVault contract * @param _mevTxFeeVault MEV and Tx Fees Vault contract address */ - function setMevTxFeeVault(address _mevTxFeeVault) external auth(SET_MEV_TX_FEE_VAULT_ROLE) { + function setMevTxFeeVault(address _mevTxFeeVault) external { + _auth(SET_MEV_TX_FEE_VAULT_ROLE); + MEV_TX_FEE_VAULT_POSITION.setStorageAddress(_mevTxFeeVault); emit LidoMevTxFeeVaultSet(_mevTxFeeVault); @@ -402,7 +425,9 @@ contract Lido is ILido, StETH, AragonApp { * @dev Sets limit to amount of ETH to withdraw per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function setMevTxFeeWithdrawalLimit(uint16 _limitPoints) external auth(SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE) { + function setMevTxFeeWithdrawalLimit(uint16 _limitPoints) external { + _auth(SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE); + _setBPValue(MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS, _limitPoints); emit MevTxFeeWithdrawalLimitSet(_limitPoints); } @@ -947,4 +972,9 @@ contract Lido is ILido, StETH, AragonApp { assert(0 == temp_value); // fully converted result <<= (24 * 8); } + + // size-efficient analog of `auth(_role)` modifier + function _auth(bytes32 _role) internal view auth(_role) { + // no-op + } } From 4fca69e26eca3bc76b8e15d81b4e582fde1c9c58 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 20 Apr 2022 14:33:39 +0000 Subject: [PATCH 103/159] fix: refine implementation, test and docs More internal helpers inside StakeLimitUtils. Docs updated with actual behavior. Add some require statements. Improve test coverage. --- contracts/0.4.24/Lido.sol | 69 +++++++---------------- contracts/0.4.24/interfaces/ILido.sol | 33 +---------- contracts/0.4.24/lib/StakeLimitUtils.sol | 60 ++++++++++++++------ test/0.4.24/lido.test.js | 70 ++++++++++++++---------- 4 files changed, 106 insertions(+), 126 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 051264b36..1c1c1584c 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -139,11 +139,9 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Cut-off new stake (every new staking transaction submitting user-provided ETH * would revert if `pauseStake` was called previously). - * * @dev A way to pause stake without pushing PAUSE for the whole proto. * The main goal is to prevent huge APR losses for existing stakers due to high demands * on post-Merge entry queue. - * * Emits `StakingPaused` event. */ function pauseStaking() external { @@ -168,11 +166,11 @@ contract Lido is ILido, StETH, AragonApp { * │──────────────────────────────────────────────────> Time * │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events * - * If `maxStakeLimit` is set to zero then rate-limit is disabled. - * NB: Reverts if `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2**64 - * + * To disable rate-limit pass zero arg values. + * @dev Reverts if: + * - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` + * - `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2^32 (only if `_stakeLimitIncreasePerBlock` != 0) * Emits `StakeResumed` event - * * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ @@ -182,6 +180,13 @@ contract Lido is ILido, StETH, AragonApp { ) external { _auth(STAKING_RESUME_ROLE); + require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TO_LARGE_INCREESE"); + require( + (_stakeLimitIncreasePerBlock == 0) + || (_maxStakeLimit / _stakeLimitIncreasePerBlock <= uint32(-1)), + "TO_SMALL_INCREASE" + ); + ( uint96 maxStakeLimit,, uint96 prevStakeLimit, @@ -211,15 +216,14 @@ contract Lido is ILido, StETH, AragonApp { * @notice Check staking state: whether it's paused or not */ function isStakingPaused() external view returns (bool) { - return STAKE_LIMIT_POSITION.getStorageUint256() == 0; + return STAKE_LIMIT_POSITION.getStorageUint256().isStakingPaused(); } /** * @notice Get current stake limit value and main params. * See `resumeStaking` for the details. - * * @dev Reverts if staking is paused - * NB: returns zero `maxStakeLimit` if rate-limit is disabled + * NB: returns all zeros if staking rate-limit is disabled */ function getCurrentStakeLimit() external view returns ( uint256 currentStakeLimit, @@ -227,24 +231,10 @@ contract Lido is ILido, StETH, AragonApp { uint256 stakeLimitIncreasePerBlock ) { uint256 slotValue = STAKE_LIMIT_POSITION.getStorageUint256(); - require(slotValue != 0); - - uint256 prevStakeLimit; - uint256 prevStakeBlockNumber; + require(!slotValue.isStakingPaused(), "STAKING_PAUSED"); - ( - maxStakeLimit, - stakeLimitIncreasePerBlock, - prevStakeLimit, - prevStakeBlockNumber - ) = slotValue.decodeStakeLimitSlot(); - - currentStakeLimit = StakeLimitUtils.getCurrentStakeLimit( - maxStakeLimit, - stakeLimitIncreasePerBlock, - prevStakeLimit, - prevStakeBlockNumber - ); + (maxStakeLimit, stakeLimitIncreasePerBlock,,) = slotValue.decodeStakeLimitSlot(); + currentStakeLimit = slotValue.getCurrentStakeLimit(); } /** @@ -505,7 +495,7 @@ contract Lido is ILido, StETH, AragonApp { function transferToVault(address _token) external { require(allowRecoverability(_token), "RECOVER_DISALLOWED"); address vault = getRecoveryVault(); - require(vault != address(0)); + require(vault != address(0), "RECOVER_VAULT_ZERO"); uint256 balance; if (_token == ETH) { @@ -660,33 +650,16 @@ contract Lido is ILido, StETH, AragonApp { function _submit(address _referral) internal whenNotStopped returns (uint256) { uint256 stakeLimitSlotValue = STAKE_LIMIT_POSITION.getStorageUint256(); - require(stakeLimitSlotValue != 0, "STAKING_PAUSED"); + require(!stakeLimitSlotValue.isStakingPaused(), "STAKING_PAUSED"); require(msg.value != 0, "ZERO_DEPOSIT"); - ( - uint96 maxStakeLimit, - uint96 stakeLimitIncPerBlock, - uint96 prevStakeLimit, - uint32 prevStakeBlockNumber - ) = stakeLimitSlotValue.decodeStakeLimitSlot(); - - if (maxStakeLimit != 0) { - uint256 currentStakeLimit = StakeLimitUtils.getCurrentStakeLimit( - maxStakeLimit, - stakeLimitIncPerBlock, - prevStakeLimit, - prevStakeBlockNumber - ); + if (stakeLimitSlotValue.isStakingRateLimited()) { + uint256 currentStakeLimit = stakeLimitSlotValue.getCurrentStakeLimit(); require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); STAKE_LIMIT_POSITION.setStorageUint256( - StakeLimitUtils.encodeStakeLimitSlot( - maxStakeLimit, - stakeLimitIncPerBlock, - uint96(currentStakeLimit - msg.value), - uint32(block.number) - ) + stakeLimitSlotValue.updatePrevStakeLimit(currentStakeLimit - msg.value) ); } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index ee998a5a2..69ace8fb0 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -31,34 +31,13 @@ interface ILido { /** * @notice Cut-off new stake (every new staking transaction submitting user-provided ETH * would revert if `pauseStake` was called previously). - * - * @dev A way to pause stake without pushing PAUSE for the whole proto. - * The main goal is to prevent huge APR losses for existing stakers due to high demands - * on post-Merge entry queue. - * - * Emits `StakingPaused` event. */ function pauseStaking() external; /** * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) * or if new rate-limit params are required. - * - * Staking could be rate-limited by imposing a limit on the stake amount - * at each moment in time. - * - * ▲ Stake limit - * │..... ..... ........ ... .... ... Stake limit = max - * │ . . . . . . . . . - * │ . . . . . . . . . - * │ . . . . . - * │──────────────────────────────────────────────────> Time - * │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events - * - * If `maxStakeLimit` is set to zero then rate-limit is disabled. - * - * Emits `StakeResumed` event - * + * To disable rate-limit pass zero arg values. * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ @@ -71,10 +50,6 @@ interface ILido { /** * @notice Get current stake limit value and main params. - * See `resumeStaking` for the details. - * - * @dev Reverts if staking is paused - * NB: returns zero `maxStakeLimit` if rate-limit is disabled */ function getCurrentStakeLimit() external view returns ( uint256 currentStakeLimit, @@ -89,12 +64,6 @@ interface ILido { /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). - * - * @dev Oracle contract specified here is allowed to make - * periodical updates of beacon states by calling pushBeacon. - * Treasury contract specified here is used to accumulate the protocol treasury fee. - * Insurance fund contract specified here is used to accumulate the protocol insurance fee. - * * @param _oracle oracle contract * @param _treasury treasury contract which accumulates treasury fee * @param _insuranceFund insurance fund contract which accumulates insurance fee diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index 8f3b73bbd..d6bb728ed 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -28,6 +28,7 @@ library StakeLimitUtils { uint256 internal constant MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET = 128; uint256 internal constant PREV_STAKE_LIMIT_OFFSET = 32; uint256 internal constant PREV_STAKE_BLOCK_NUMBER_OFFSET = 0; + uint256 internal constant STAKE_LIMIT_PARAMS_MASK = uint256(-1) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; /** * @notice Unpack the slot value into stake limit params and state. @@ -51,32 +52,59 @@ library StakeLimitUtils { * @notice Pack stake limit params and state into a slot. */ function encodeStakeLimitSlot( - uint96 maxStakeLimit, - uint96 stakeLimitIncPerBlock, - uint96 prevStakeLimit, - uint32 prevStakeBlockNumber + uint96 _maxStakeLimit, + uint96 _stakeLimitIncPerBlock, + uint96 _prevStakeLimit, + uint32 _prevStakeBlockNumber ) internal pure returns (uint256 ret) { - ret = uint256(maxStakeLimit) << MAX_STAKE_LIMIT_OFFSET - | uint256(prevStakeLimit) << PREV_STAKE_LIMIT_OFFSET - | uint256(prevStakeBlockNumber) << PREV_STAKE_BLOCK_NUMBER_OFFSET; + ret = uint256(_maxStakeLimit) << MAX_STAKE_LIMIT_OFFSET + | uint256(_prevStakeLimit) << PREV_STAKE_LIMIT_OFFSET + | uint256(_prevStakeBlockNumber) << PREV_STAKE_BLOCK_NUMBER_OFFSET; - if (stakeLimitIncPerBlock > 0) { - ret |= uint256(uint32(maxStakeLimit / stakeLimitIncPerBlock)) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; + if (_stakeLimitIncPerBlock > 0) { + ret |= uint256(uint32(_maxStakeLimit / _stakeLimitIncPerBlock)) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; } } /** * @notice Calculate stake limit for the current block. */ - function getCurrentStakeLimit( - uint256 maxStakeLimit, - uint256 stakeLimitIncPerBlock, - uint256 prevStakeLimit, - uint256 prevBlockNumber - ) internal view returns(uint256 limit) { - limit = prevStakeLimit + ((block.number - prevBlockNumber) * stakeLimitIncPerBlock); + function getCurrentStakeLimit(uint256 _slotValue) internal view returns(uint256 limit) { + ( + uint96 maxStakeLimit, + uint96 stakeLimitIncPerBlock, + uint96 prevStakeLimit, + uint32 prevStakeBlockNumber + ) = decodeStakeLimitSlot(_slotValue); + + limit = prevStakeLimit + ((block.number - prevStakeBlockNumber) * stakeLimitIncPerBlock); if (limit > maxStakeLimit) { limit = maxStakeLimit; } } + + /** + * @notice Write new prev stake limit and current block number + */ + function updatePrevStakeLimit(uint256 _slotValue, uint256 _newPrevLimit) internal view returns(uint256) { + return ( + (_slotValue & STAKE_LIMIT_PARAMS_MASK) + | _newPrevLimit << PREV_STAKE_LIMIT_OFFSET + | block.number << PREV_STAKE_BLOCK_NUMBER_OFFSET + ); + } + + /** + * @notice check if staking is on pause (i.e. slot contains zero value) + */ + function isStakingPaused(uint256 _slotValue) internal pure returns(bool) { + return (_slotValue == 0); + } + + /** + * @notice check if rate limit is set (otherwise staking is unlimited) + */ + function isStakingRateLimited(uint256 _slotValue) internal pure returns(bool) { + return uint96(_slotValue >> MAX_STAKE_LIMIT_OFFSET) != 0; + } } diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index c879f4a23..6d63e495b 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -552,9 +552,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) }) + const verifyRateLimitStake = async (expectedMaxStakeLimit, expectedLimitIncrease, expectedCurrentStakeLimit) => { + ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) + assertBn(maxStakeLimit, expectedMaxStakeLimit) + assertBn(stakeLimitIncreasePerBlock, expectedLimitIncrease) + assertBn(currentStakeLimit, expectedCurrentStakeLimit) + } + it('stake pause/unlimited resume works', async () => { let receipt + await verifyRateLimitStake(bn(0), bn(0), bn(0)) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) @@ -563,24 +571,24 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'StakingPaused') assert.equal(await app.isStakingPaused(), true) + await assertRevert(app.getCurrentStakeLimit(), 'STAKING_PAUSED') + await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) const disableStakeLimit = 0 - await assertRevert(app.resumeStaking(disableStakeLimit, 0), 'APP_AUTH_FAILED') - receipt = await app.resumeStaking(disableStakeLimit, 0, { from: voting }) + await assertRevert(app.resumeStaking(disableStakeLimit, disableStakeLimit), 'APP_AUTH_FAILED') + receipt = await app.resumeStaking(disableStakeLimit, disableStakeLimit, { from: voting }) assertEvent(receipt, 'StakingResumed') assert.equal(await app.isStakingPaused(), false) + await verifyRateLimitStake(bn(0), bn(0), bn(0)) + await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(1.4) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(1.4), referral: ZERO_ADDRESS } }) }) - const getBlockNumber = async () => { - return await ethers.provider.getBlockNumber() - } - const mineNBlocks = async (n) => { for (let index = 0; index < n; index++) { await ethers.provider.send('evm_mine') @@ -590,9 +598,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('stake rate-limiting works', async () => { let receipt - const blocksToReachMaxStakeLimit = 250 + const blocksToReachMaxStakeLimit = 300 const expectedMaxStakeLimit = ETH(3) - const limitIncreasePerBlock = bn(expectedMaxStakeLimit).div(bn(blocksToReachMaxStakeLimit)) // 12 * 10**15 + const limitIncreasePerBlock = bn(expectedMaxStakeLimit).div(bn(blocksToReachMaxStakeLimit)) // 1 * 10**16 receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) assertEvent(receipt, 'StakingResumed', { @@ -601,41 +609,43 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) stakeLimitIncreasePerBlock: limitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) - ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) - assertBn(maxStakeLimit, expectedMaxStakeLimit) - assertBn(stakeLimitIncreasePerBlock, limitIncreasePerBlock) - assertBn(currentStakeLimit, expectedMaxStakeLimit) + assert.equal(await app.isStakingPaused(), false) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) - assertBn(maxStakeLimit, expectedMaxStakeLimit) - assertBn(stakeLimitIncreasePerBlock, limitIncreasePerBlock) - assertBn(currentStakeLimit, ETH(1)) - + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(1)) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }), `STAKE_LIMIT`) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, bn(ETH(1)).add(limitIncreasePerBlock)) - await mineNBlocks(122) // why not 125??? + // expect to grow for another 1.5 ETH since last submit + // every revert produces new block, so we need to account that block + await mineNBlocks(blocksToReachMaxStakeLimit / 2 - 1) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2.5)) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.6) }), `STAKE_LIMIT`) - receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2.5), referral: ZERO_ADDRESS } }) - ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) - assertBn(maxStakeLimit, expectedMaxStakeLimit) - assertBn(stakeLimitIncreasePerBlock, limitIncreasePerBlock) - assertBn(currentStakeLimit, ETH(0)) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(2)) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) - - await mineNBlocks(82) // why? + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(3)) + // once again, we are subtracting blocks number induced by revert checks + await mineNBlocks(blocksToReachMaxStakeLimit / 3 - 4) receipt = await app.submit(ZERO_ADDRESS, { from: user1, value: ETH(1) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user1, amount: ETH(1), referral: ZERO_ADDRESS } }) - ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) - assertBn(maxStakeLimit, expectedMaxStakeLimit) - assertBn(stakeLimitIncreasePerBlock, limitIncreasePerBlock) - // assertBn(currentStakeLimit, ETH(0)) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + + // check that limit is restored completely + await mineNBlocks(blocksToReachMaxStakeLimit) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + + // check that limit is capped by maxLimit value and doesn't grow infinitely + await mineNBlocks(10) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + + await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TO_LARGE_INCREESE`) + await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TO_SMALL_INCREASE`) }) it('reverts when trying to call unknown function', async () => { From 40b98f1a34da4ac3a1f3f98ff6c3578c46bca98c Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 20 Apr 2022 18:44:26 +0300 Subject: [PATCH 104/159] fix: resumeStaking edge cases --- contracts/0.4.24/Lido.sol | 9 +++-- test/0.4.24/lido.test.js | 74 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 5 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 1c1c1584c..a5f40158d 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -190,14 +190,13 @@ contract Lido is ILido, StETH, AragonApp { ( uint96 maxStakeLimit,, uint96 prevStakeLimit, - uint32 prevStakeBlockNumber ) = STAKE_LIMIT_POSITION.getStorageUint256().decodeStakeLimitSlot(); // if staking was paused or unlimited previously, - // reset current limit to max and save current block - if (maxStakeLimit == 0) { + // or new limit is lower than previous, then + // reset prev stake limit to max + if ((maxStakeLimit == 0) || (_maxStakeLimit < prevStakeLimit)) { prevStakeLimit = _maxStakeLimit; - prevStakeBlockNumber = uint32(block.number); } STAKE_LIMIT_POSITION.setStorageUint256( @@ -205,7 +204,7 @@ contract Lido is ILido, StETH, AragonApp { _maxStakeLimit, _stakeLimitIncreasePerBlock, prevStakeLimit, - prevStakeBlockNumber + uint32(block.number) ) ); diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 6d63e495b..56b7f78a7 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -648,6 +648,80 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TO_SMALL_INCREASE`) }) + it('one-shot rate-limiting works', async () => { + let receipt + + const expectedMaxStakeLimit = ETH(7) + const limitIncreasePerBlock = 0 + + receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingResumed', { + expectedArgs: { + maxStakeLimit: expectedMaxStakeLimit, + stakeLimitIncreasePerBlock: limitIncreasePerBlock + } + }) + + assert.equal(await app.isStakingPaused(), false) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(5) }) + assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2)) + receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) + assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await mineNBlocks(100) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + }) + + it('changing various rate-limits work', async () => { + let receipt + + const expectedMaxStakeLimit = ETH(9) + const limitIncreasePerBlock = bn(expectedMaxStakeLimit).divn(100) + + receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingResumed', { + expectedArgs: { + maxStakeLimit: expectedMaxStakeLimit, + stakeLimitIncreasePerBlock: limitIncreasePerBlock + } + }) + + assert.equal(await app.isStakingPaused(), false) + await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + + const smallerExpectedMaxStakeLimit = ETH(5) + const smallerLimitIncreasePerBlock = bn(smallerExpectedMaxStakeLimit).divn(200) + + receipt = await app.resumeStaking(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingResumed', { + expectedArgs: { + maxStakeLimit: smallerExpectedMaxStakeLimit, + stakeLimitIncreasePerBlock: smallerLimitIncreasePerBlock + } + }) + + assert.equal(await app.isStakingPaused(), false) + await verifyRateLimitStake(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) + + const largerExpectedMaxStakeLimit = ETH(10) + const largerLimitIncreasePerBlock = bn(largerExpectedMaxStakeLimit).divn(1000) + + receipt = await app.resumeStaking(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingResumed', { + expectedArgs: { + maxStakeLimit: largerExpectedMaxStakeLimit, + stakeLimitIncreasePerBlock: largerLimitIncreasePerBlock + } + }) + + assert.equal(await app.isStakingPaused(), false) + await verifyRateLimitStake(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) + }) + it('reverts when trying to call unknown function', async () => { const wrongMethodABI = '0x00' await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1), data: wrongMethodABI }), 'NON_EMPTY_DATA') From 7b668f7901532b51dcaa312890ec21c1e9989f26 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 20 Apr 2022 19:01:33 +0300 Subject: [PATCH 105/159] chore: improve mev/tx fee test coverage --- test/0.4.24/lido.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 56b7f78a7..4019a8f0e 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -227,6 +227,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(mevAmount)) assertBn(await app.balanceOf(user2), STETH(depositAmount + mevAmount)) + assertBn(await app.getTotalMevTxFeeCollected(), ETH(mevAmount)) }) it('MEV distribution works when negative rewards reported', async () => { @@ -243,6 +244,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(mevAmount)) assertBn(await app.balanceOf(user2), STETH(depositAmount + mevAmount + beaconRewards)) + assertBn(await app.getTotalMevTxFeeCollected(), ETH(mevAmount)) }) it('MEV distribution works when positive rewards reported', async () => { @@ -261,6 +263,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) assertBn(await app.getBufferedEther(), ETH(mevAmount)) assertBn(await app.balanceOf(user2), STETH(depositAmount + shareOfRewardsForStakers * (mevAmount + beaconRewards))) + assertBn(await app.getTotalMevTxFeeCollected(), ETH(mevAmount)) }) it('Attempt to set invalid MEV Tx Fee withdrawal limit', async () => { @@ -276,6 +279,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.setMevTxFeeWithdrawalLimit(10001, { from: voting }), 'VALUE_OVER_100_PERCENT') await app.setMevTxFeeWithdrawalLimit(initialValue, { from: voting }) + + // unable to receive mev tx fee from arbitrary account + assertRevert(app.receiveMevTxFee({ from: user1, value: ETH(1) })) }) it('setFee works', async () => { From 6be7fdf2ef9205452d638c7187ac59e41d880b41 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 21 Apr 2022 07:39:04 +0000 Subject: [PATCH 106/159] chore: fix grammar typos --- contracts/0.4.24/Lido.sol | 4 ++-- test/0.4.24/lido.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index a5f40158d..a9aee3d59 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -180,11 +180,11 @@ contract Lido is ILido, StETH, AragonApp { ) external { _auth(STAKING_RESUME_ROLE); - require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TO_LARGE_INCREESE"); + require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_INCREASE"); require( (_stakeLimitIncreasePerBlock == 0) || (_maxStakeLimit / _stakeLimitIncreasePerBlock <= uint32(-1)), - "TO_SMALL_INCREASE" + "TOO_SMALL_INCREASE" ); ( diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 4019a8f0e..0639b8114 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -650,8 +650,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await mineNBlocks(10) await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) - await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TO_LARGE_INCREESE`) - await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TO_SMALL_INCREASE`) + await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_INCREASE`) + await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TOO_SMALL_INCREASE`) }) it('one-shot rate-limiting works', async () => { From fc14365ea28eaecce05bf6646d7061ef4f1caf24 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 21 Apr 2022 08:00:29 +0000 Subject: [PATCH 107/159] feat: bring back NFT recovery Since we have adopted `_auth` function there are some more space to settle previously removed code features. --- contracts/0.4.24/Lido.sol | 19 ++++++++++++++++++- contracts/0.4.24/interfaces/ILido.sol | 3 +++ test/0.4.24/lido.test.js | 20 +++++++++++++++++++- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index a9aee3d59..28c3c89e9 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -511,6 +511,23 @@ contract Lido is ILido, StETH, AragonApp { emit RecoverToVault(vault, _token, balance); } + /** + * @notice Send NTFs to recovery Vault + * @param _token Token to be sent to recovery vault + * @param _tokenId Token Id + */ + function transferERC721ToVault(address _token, uint256 _tokenId) external { + require(_token != address(0), "ZERO_ADDRESS"); + require(allowRecoverability(_token), "RECOVER_DISALLOWED"); + + address vault = getRecoveryVault(); + require(vault != address(0), "RECOVER_VAULT_ZERO"); + + IERC721(_token).transferFrom(address(this), vault, _tokenId); + + emit RecoverERC721ToVault(vault, _token, _tokenId); + } + /** * @notice Returns staking rewards fee rate */ @@ -945,7 +962,7 @@ contract Lido is ILido, StETH, AragonApp { result <<= (24 * 8); } - // size-efficient analog of `auth(_role)` modifier + // size-efficient analog of the `auth(_role)` modifier function _auth(bytes32 _role) internal view auth(_role) { // no-op } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 69ace8fb0..d94d7633c 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -204,4 +204,7 @@ interface ILido { * @return beaconBalance - total amount of Beacon-side Ether (sum of all the balances of Lido validators) */ function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); + + // Requested ERC721 recovery from the `Lido` to the designated `recoveryVault` vault. + event RecoverERC721ToVault(address indexed vault, address indexed token, uint256 tokenId); } diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 0639b8114..a102a80ef 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -15,6 +15,7 @@ const MevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') +const ERC721Mock = artifacts.require('ERC721Mock.sol') const VaultMock = artifacts.require('AragonVaultMock.sol') const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') @@ -1452,12 +1453,18 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) context('recovery vault', () => { + let nftToken + beforeEach(async () => { await anyToken.mint(app.address, 100) + + nftToken = await ERC721Mock.new() + await nftToken.mint(app.address, 777) }) it('reverts when vault is not set', async () => { - await assertRevert(app.transferToVault(anyToken.address, { from: nobody })) + await assertRevert(app.transferToVault(anyToken.address, { from: nobody }), 'RECOVER_VAULT_ZERO') + await assertRevert(app.transferERC721ToVault(nftToken.address, 777, { from: nobody }), 'RECOVER_VAULT_ZERO') }) context('recovery works with vault mock deployed', () => { @@ -1480,6 +1487,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'RecoverToVault', { expectedArgs: { vault: vault.address, token: anyToken.address, amount: 100 } }) }) + it('recovery with nft tokens works and emits event', async () => { + await assertRevert(app.transferERC721ToVault(ZERO_ADDRESS, 777, { from: nobody })) + + assert.equal(await nftToken.ownerOf(777), app.address) + + const receipt = await app.transferERC721ToVault(nftToken.address, 777, { from: nobody }) + assertEvent(receipt, 'RecoverERC721ToVault', { expectedArgs: { vault: vault.address, token: nftToken.address, tokenId: 777 } }) + + assert.equal(await nftToken.ownerOf(777), vault.address) + }) + it('recovery with unaccounted ether works and emits event', async () => { await app.makeUnaccountedEther({ from: user1, value: ETH(10) }) const receipt = await app.transferToVault(ZERO_ADDRESS, { from: nobody }) From c3e5c121a2d74f7fbbb563db407541a311bcc7ce Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 21 Apr 2022 10:09:10 +0000 Subject: [PATCH 108/159] chore: small improvements by ArWer --- contracts/0.4.24/Lido.sol | 4 ++-- contracts/0.4.24/lib/StakeLimitUtils.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 28c3c89e9..f15a8b2a9 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -664,10 +664,10 @@ contract Lido is ILido, StETH, AragonApp { * @return amount of StETH shares generated */ function _submit(address _referral) internal whenNotStopped returns (uint256) { - uint256 stakeLimitSlotValue = STAKE_LIMIT_POSITION.getStorageUint256(); + require(msg.value != 0, "ZERO_DEPOSIT"); + uint256 stakeLimitSlotValue = STAKE_LIMIT_POSITION.getStorageUint256(); require(!stakeLimitSlotValue.isStakingPaused(), "STAKING_PAUSED"); - require(msg.value != 0, "ZERO_DEPOSIT"); if (stakeLimitSlotValue.isStakingRateLimited()) { uint256 currentStakeLimit = stakeLimitSlotValue.getCurrentStakeLimit(); diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index d6bb728ed..83372e5e0 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -18,8 +18,8 @@ pragma solidity 0.4.24; // |<----- 32 bits ------>|<-- 96 bits --->|<---------- 32 bits ------>|<--- 96 bits -->| // // -// NB: we represent `maxStakeLimitGrowthPeriod` as follows: -// `maxStakeLimitGrowthPeriod` = `maxStakeLimit` / `stakeLimitIncPerBlock` +// NB: we represent `maxStakeLimitGrowthBlocks` as follows: +// `maxStakeLimitGrowthBlocks` = `maxStakeLimit` / `stakeLimitIncreasePerBlock` // 32 bits 96 bits 96 bits // From 2c54f043c22cd8a23e8b8407ba7abff2c0f681ba Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 21 Apr 2022 12:59:01 +0000 Subject: [PATCH 109/159] chore: rename `getCurrentStakeLimit` --- contracts/0.4.24/Lido.sol | 8 ++++---- contracts/0.4.24/interfaces/ILido.sol | 4 ++-- contracts/0.4.24/lib/StakeLimitUtils.sol | 2 +- test/0.4.24/lido.test.js | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index f15a8b2a9..cd1de728b 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -219,12 +219,12 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Get current stake limit value and main params. + * @notice Calculate current stake limit value and return main params. * See `resumeStaking` for the details. * @dev Reverts if staking is paused * NB: returns all zeros if staking rate-limit is disabled */ - function getCurrentStakeLimit() external view returns ( + function calculateCurrentStakeLimit() external view returns ( uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock @@ -233,7 +233,7 @@ contract Lido is ILido, StETH, AragonApp { require(!slotValue.isStakingPaused(), "STAKING_PAUSED"); (maxStakeLimit, stakeLimitIncreasePerBlock,,) = slotValue.decodeStakeLimitSlot(); - currentStakeLimit = slotValue.getCurrentStakeLimit(); + currentStakeLimit = slotValue.calculateCurrentStakeLimit(); } /** @@ -670,7 +670,7 @@ contract Lido is ILido, StETH, AragonApp { require(!stakeLimitSlotValue.isStakingPaused(), "STAKING_PAUSED"); if (stakeLimitSlotValue.isStakingRateLimited()) { - uint256 currentStakeLimit = stakeLimitSlotValue.getCurrentStakeLimit(); + uint256 currentStakeLimit = stakeLimitSlotValue.calculateCurrentStakeLimit(); require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index d94d7633c..5fa6757fc 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -49,9 +49,9 @@ interface ILido { function isStakingPaused() external view returns (bool); /** - * @notice Get current stake limit value and main params. + * @notice Calculate current stake limit value and return main params. */ - function getCurrentStakeLimit() external view returns ( + function calculateCurrentStakeLimit() external view returns ( uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index 83372e5e0..ca703eee1 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -69,7 +69,7 @@ library StakeLimitUtils { /** * @notice Calculate stake limit for the current block. */ - function getCurrentStakeLimit(uint256 _slotValue) internal view returns(uint256 limit) { + function calculateCurrentStakeLimit(uint256 _slotValue) internal view returns(uint256 limit) { ( uint96 maxStakeLimit, uint96 stakeLimitIncPerBlock, diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index a102a80ef..4237e8eee 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -560,7 +560,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) const verifyRateLimitStake = async (expectedMaxStakeLimit, expectedLimitIncrease, expectedCurrentStakeLimit) => { - ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.getCurrentStakeLimit()) + ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.calculateCurrentStakeLimit()) assertBn(maxStakeLimit, expectedMaxStakeLimit) assertBn(stakeLimitIncreasePerBlock, expectedLimitIncrease) assertBn(currentStakeLimit, expectedCurrentStakeLimit) @@ -578,7 +578,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'StakingPaused') assert.equal(await app.isStakingPaused(), true) - await assertRevert(app.getCurrentStakeLimit(), 'STAKING_PAUSED') + await assertRevert(app.calculateCurrentStakeLimit(), 'STAKING_PAUSED') await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) From 8f6df45abbf7a5ba2bb93dfbcc94d8bfb479ad59 Mon Sep 17 00:00:00 2001 From: George Avsetsin Date: Mon, 25 Apr 2022 08:58:07 +0300 Subject: [PATCH 110/159] fix: replace uploadDirToIpfs util with the original --- scripts/deploy-aragon-std-apps.js | 2 +- scripts/helpers/ipfs.js | 23 ----------------------- 2 files changed, 1 insertion(+), 24 deletions(-) delete mode 100644 scripts/helpers/ipfs.js diff --git a/scripts/deploy-aragon-std-apps.js b/scripts/deploy-aragon-std-apps.js index 7032308bc..61a58326b 100644 --- a/scripts/deploy-aragon-std-apps.js +++ b/scripts/deploy-aragon-std-apps.js @@ -11,7 +11,7 @@ const { filterObject } = require('./helpers/collections') const { readAppName } = require('./helpers/aragon') const { gitCloneRepo } = require('./helpers/git') -const { uploadDirToIpfs } = require('./helpers/ipfs') +const { uploadDirToIpfs } = require('@aragon/buidler-aragon/dist/src/utils/ipfs') const { toContentUri } = require('@aragon/buidler-aragon/dist/src/utils/apm/utils') const APPS = ['agent', 'finance', 'token-manager', 'vault', 'voting'] diff --git a/scripts/helpers/ipfs.js b/scripts/helpers/ipfs.js deleted file mode 100644 index 5207f4128..000000000 --- a/scripts/helpers/ipfs.js +++ /dev/null @@ -1,23 +0,0 @@ -const { create, globSource } = require('ipfs-http-client') - -const globSourceOptions = { - recursive: true -} - -const addOptions = { - pin: true, - wrapWithDirectory: true, - timeout: 10000 -} - -async function uploadDirToIpfs({ apiUrl, dirPath }) { - const ipfs = await create(apiUrl) - - const results = [] - for await (const result of ipfs.addAll(globSource(dirPath, '*', globSourceOptions), addOptions)) { - results.push(result) - } - return results.find((r) => r.path === '').cid.toString() -} - -module.exports = { uploadDirToIpfs } From f2f19fcd4411dd9a5d5864686790c57056310d3f Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Fri, 13 May 2022 13:48:44 +0300 Subject: [PATCH 111/159] update mev vote script to merge-ready-first-pack vote script --- .../20-mitigating-deposit-front-running.js | 4 +- scripts/multisig/26-deploy-mev-vault.js | 10 +- ...28-vote-merge-ready-first-pack-upgrade.js} | 104 ++++++++++++------ 3 files changed, 78 insertions(+), 40 deletions(-) rename scripts/multisig/{28-vote-mev-upgrade.js => 28-vote-merge-ready-first-pack-upgrade.js} (64%) diff --git a/scripts/multisig/20-mitigating-deposit-front-running.js b/scripts/multisig/20-mitigating-deposit-front-running.js index 40cea0f49..08afed405 100644 --- a/scripts/multisig/20-mitigating-deposit-front-running.js +++ b/scripts/multisig/20-mitigating-deposit-front-running.js @@ -99,9 +99,9 @@ async function upgradeAppImpl({ web3, artifacts }) { const txName = `tx-20-mitigating-deposit-front-running.json` const votingDesc = `1) Publishing new implementation in lido app APM repo -2) Updating implementaion of lido app with new one +2) Updating implementation of lido app with new one 3) Publishing new implementation in node operators registry app APM repo -4) Updating implementaion of node operators registry app with new one +4) Updating implementation of node operators registry app with new one 5) Granting new permission DEPOSIT_ROLE for ${depositorAddress} ${nosIncreaseLimitsDesc.map((desc, index) => `${index + 6}) ${desc}`).join('\n')} ` diff --git a/scripts/multisig/26-deploy-mev-vault.js b/scripts/multisig/26-deploy-mev-vault.js index 7c86376fd..966b32dd8 100644 --- a/scripts/multisig/26-deploy-mev-vault.js +++ b/scripts/multisig/26-deploy-mev-vault.js @@ -8,7 +8,7 @@ const { APP_NAMES } = require('./constants') const DEPLOYER = process.env.DEPLOYER || '' const REQUIRED_NET_STATE = ['daoInitialSettings', 'depositorParams', `app:${APP_NAMES.LIDO}`, `app:${APP_NAMES.NODE_OPERATORS_REGISTRY}`] -async function upgradeApp({ web3, artifacts }) { +async function deployMevTxFeeVault({ web3, artifacts }) { const appArtifact = 'LidoMevTxFeeVault' const netId = await web3.eth.net.getId() @@ -18,12 +18,14 @@ async function upgradeApp({ web3, artifacts }) { const state = readNetworkState(network.name, netId) assertRequiredNetworkState(state, REQUIRED_NET_STATE) const lidoAddress = state[`app:${APP_NAMES.LIDO}`].proxyAddress - log(`Using Lido address:`, yl(lidoAddress)) - logSplitter() + log(`Using Lido contract address:`, yl(lidoAddress)) const lido = await artifacts.require('Lido').at(lidoAddress) const treasuryAddr = await lido.getTreasury() + log(`Using Lido Treasury contract address:`, yl(lidoAddress)) + logSplitter() + const args = [lidoAddress, treasuryAddr] await saveDeployTx(appArtifact, `tx-26-deploy-mev-vault.json`, { arguments: args, @@ -31,4 +33,4 @@ async function upgradeApp({ web3, artifacts }) { }) } -module.exports = runOrWrapScript(upgradeApp, module) +module.exports = runOrWrapScript(deployMevTxFeeVault, module) diff --git a/scripts/multisig/28-vote-mev-upgrade.js b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js similarity index 64% rename from scripts/multisig/28-vote-mev-upgrade.js rename to scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js index c61e222f1..576bafb69 100644 --- a/scripts/multisig/28-vote-mev-upgrade.js +++ b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js @@ -10,6 +10,8 @@ const { resolveEnsAddress } = require('../components/ens') const { APP_NAMES } = require('./constants') +const ETH = (value) => web3.utils.toWei(value + '', 'ether') + const DEPLOYER = process.env.DEPLOYER || '' const REQUIRED_NET_STATE = [ 'lidoApmEnsName', @@ -45,7 +47,24 @@ async function createVoting({ web3, artifacts }) { const oracle = await artifacts.require('LidoOracle').at(oracleAddress) const mevTxFeeVaultAddress = state.mevTxFeeVaultAddress - const mevTxFeeWithdrawalLimitPoints = 3 + const mevTxFeeWithdrawalLimitPoints = 2 // see https://github.com/lidofinance/lido-dao/issues/405 + const dailyStakingLimit = ETH(150000) + const stakeLimitIncreasePerBlock = calcStakeLimitIncreasePerBlock(dailyStakingLimit) + + async function createGrantRoleForLidoAppCallData(roleName) { + return { + to: aclAddress, + calldata: await acl.contract.methods + .createPermission( + votingAddress, + state['app:lido'].proxyAddress, + web3.utils.soliditySha3(roleName), + votingAddress + ) + .encodeABI() + } + } + log(`Using ENS:`, yl(state.ensAddress)) log(`TokenManager address:`, yl(tokenManagerAddress)) @@ -53,36 +72,23 @@ async function createVoting({ web3, artifacts }) { log(`Kernel:`, yl(kernel.address)) log(`ACL:`, yl(acl.address)) log(`mevTxFeeWithdrawalLimitPoints: `, yl(mevTxFeeWithdrawalLimitPoints)) + log(`dailyStakeLimit: `, yl(dailyStakingLimit)) + log(`stakeLimitIncreasePerBlock: `, yl(stakeLimitIncreasePerBlock)) log.splitter() - const lidoUpgradeCallData = await buildUpgradeTransaction('lido', state, ens, kernel, 0) + const lidoUpgradeCallData = await buildUpgradeTransaction('lido', state, ens, kernel) - const oracleUpgradeCallData = await buildUpgradeTransaction('oracle', state, ens, kernel, 2) + const nodeOperatorsRegistryUpgradeCallData = await buildUpgradeTransaction('node-operators-registry', state, ens, kernel) - const grantSetMevVaultRoleCallData = { - to: aclAddress, - calldata: await acl.contract.methods - .createPermission( - votingAddress, - state[`app:lido`].proxyAddress, - web3.utils.soliditySha3('SET_MEV_TX_FEE_VAULT_ROLE'), - votingAddress - ) - .encodeABI() - } + const oracleUpgradeCallData = await buildUpgradeTransaction('oracle', state, ens, kernel) - const grantSetMevWithdrawalLimitRoleCallData = { - to: aclAddress, - calldata: await acl.contract.methods - .createPermission( - votingAddress, - state[`app:lido`].proxyAddress, - web3.utils.soliditySha3('SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE'), - votingAddress - ) - .encodeABI() - } + const grantSetMevVaultRoleCallData = await createGrantRoleForLidoAppCallData('SET_MEV_TX_FEE_VAULT_ROLE') + const grantSetMevWithdrawalLimitRoleCallData = await createGrantRoleForLidoAppCallData('SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE') + const grantResumeRoleCallData = await createGrantRoleForLidoAppCallData('RESUME_ROLE') + const grantStakingPauseRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_PAUSE_ROLE') + const grantStakingResumeRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_RESUME_ROLE') + const grantManageProtocolContractsRoleCallData = await createGrantRoleForLidoAppCallData('MANAGE_PROTOCOL_CONTRACTS_ROLE') const setMevTxFeeVaultCallData = { to: lidoAddress, @@ -99,14 +105,26 @@ async function createVoting({ web3, artifacts }) { calldata: await oracle.contract.methods.finalizeUpgrade_v3().encodeABI() } + const unpauseStakingCallData = { + to: lidoAddress, + calldata: await lido.contract.methods.resumeStaking(dailyStakingLimit, stakeLimitIncreasePerBlock).encodeABI() + } + + const encodedUpgradeCallData = encodeCallScript([ ...lidoUpgradeCallData, + ...nodeOperatorsRegistryUpgradeCallData, ...oracleUpgradeCallData, updateOracleVersionToV3CallData, grantSetMevVaultRoleCallData, grantSetMevWithdrawalLimitRoleCallData, + grantResumeRoleCallData, + grantStakingPauseRoleCallData, + grantStakingResumeRoleCallData, + grantManageProtocolContractsRoleCallData, setMevTxFeeVaultCallData, setMevWithdrawalLimitCallData, + unpauseStakingCallData, ]) log(`encodedUpgradeCallData:`, yl(encodedUpgradeCallData)) @@ -117,16 +135,24 @@ async function createVoting({ web3, artifacts }) { } ]) - const txName = `tx-28-deploy-mev-upgrade.json` + // TODO: update the list + const txName = `tx-28-vote-merge-ready-first-pack-upgrade.json` const votingDesc = `1) Publishing new implementation in lido app APM repo 2) Updating implementation of lido app with new one -3) Publishing new implementation in oracle app APM repo -4) Updating implementation of oracle app with new one -5) Call Oracle's finalizeUpgrade_v3() to update internal version counter -5) Grant role SET_MEV_TX_FEE_VAULT_ROLE to voting -6) Grant role SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE to voting -7) Set deployed MevTxFeeVault to Lido contract -8) Set MevTxFee Withdrawal Limit to ${mevTxFeeWithdrawalLimitPoints} basis points` +3) Publishing new implementation in node-operators-registry app APM repo +4) Updating implementation of node-operators-registry app with new one +5) Publishing new implementation in oracle app APM repo +6) Updating implementation of oracle app with new one +7) Call Oracle's finalizeUpgrade_v3() to update internal version counter +8) Grant role SET_MEV_TX_FEE_VAULT_ROLE to voting +9) Grant role SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE to voting +10) Grant role RESUME_ROLE to voting +11) Grant role STAKING_PAUSE_ROLE to voting +12) Grant role STAKING_RESUME_ROLE to voting +13) Grant role MANAGE_PROTOCOL_CONTRACTS_ROLE to voting +14) Set deployed MevTxFeeVault to Lido contract +15) Set MevTxFee Withdrawal Limit to ${mevTxFeeWithdrawalLimitPoints} basis points +16) Unpause staking setting daily limit to ${fromE18ToString(dailyStakingLimit)}` await saveCallTxData(votingDesc, tokenManager, 'forward', txName, { arguments: [votingCallData], @@ -150,7 +176,6 @@ async function buildUpgradeTransaction(appName, state, ens, kernel) { const { semanticVersion: currentVersion, contractAddress: currentContractAddress, contentURI: currentContentURI } = await repo.getLatest() const versionFrom = currentVersion.map((n) => n.toNumber()) - console.log(currentVersion) currentVersion[0] = currentVersion[0].addn(1) currentVersion[1] = new BN(0) currentVersion[2] = new BN(0) @@ -184,4 +209,15 @@ async function buildUpgradeTransaction(appName, state, ens, kernel) { return upgradeCallData } +function calcStakeLimitIncreasePerBlock(dailyLimit) { + const secondsPerBlock = 12 + const secondsPerDay = 24 * 60 * 60 + const blocksPerDay = secondsPerDay / secondsPerBlock + return Math.floor(dailyLimit / blocksPerDay).toString() +} + +function fromE18ToString(x) { + return `${(x / 1e18).toFixed(3)} ETH (${x} wei)` +} + module.exports = runOrWrapScript(createVoting, module) From 092e694a115f726da2d0f0e2b5bca12b0ab9ced2 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Fri, 13 May 2022 12:28:04 +0000 Subject: [PATCH 112/159] fix: remove incoming eth counter from mev/tx vault --- contracts/0.8.9/LidoMevTxFeeVault.sol | 9 +-------- test/0.8.9/lido-mev-tx-fee-vault.js | 7 ------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 53dd0c16c..36af3d99a 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -32,13 +32,6 @@ contract LidoMevTxFeeVault { address public immutable LIDO; address public immutable TREASURY; - /** - * Total amount of rewards received via transactions - * Rewards received on this contract set as coinbase (fee recipient) - * are not counted - */ - uint256 public totalRewardsReceivedViaTransactions; - /** * Emitted when the ERC20 `token` recovered (e.g. transferred) * to the Lido treasure address by `requestedBy` sender. @@ -78,7 +71,7 @@ contract LidoMevTxFeeVault { * @dev MEV rewards may be sent as plain ETH transfers */ receive() external payable { - totalRewardsReceivedViaTransactions = totalRewardsReceivedViaTransactions + msg.value; + // no-op } /** diff --git a/test/0.8.9/lido-mev-tx-fee-vault.js b/test/0.8.9/lido-mev-tx-fee-vault.js index 084889209..486e45ceb 100644 --- a/test/0.8.9/lido-mev-tx-fee-vault.js +++ b/test/0.8.9/lido-mev-tx-fee-vault.js @@ -63,13 +63,6 @@ contract('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, another await assertRevert(mevVault.withdrawRewards(12345, { from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') }) - it('MEV Tx Fee Vault totalRewardsReceivedViaTransactions counter', async () => { - const counterBefore = +(await mevVault.totalRewardsReceivedViaTransactions()).toString() - await web3.eth.sendTransaction({ to: mevVault.address, from: anotherAccount, value: ETH(0.05) }) - console.log({ counterBefore }) - assertBn(await mevVault.totalRewardsReceivedViaTransactions(), ETH(counterBefore + 0.05)) - }) - it('MEV Tx Fee Vault can receive Ether by plain transfers (no call data)', async () => { const before = +(await web3.eth.getBalance(mevVault.address)).toString() const amount = 0.02 From 33356ec009519f9129bf4ce0c5ccd7e6535f0c0d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Fri, 13 May 2022 12:30:12 +0000 Subject: [PATCH 113/159] fix: increase max node operators count cap to 200 --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 12295964c..1611878f6 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -38,7 +38,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp uint256 constant public PUBKEY_LENGTH = 48; uint256 constant public SIGNATURE_LENGTH = 96; - uint256 constant public MAX_NODE_OPERATORS_COUNT = 50; + uint256 constant public MAX_NODE_OPERATORS_COUNT = 200; uint256 internal constant UINT64_MAX = uint256(uint64(-1)); From f3c12a05263ebb85f18686f4c6679a7b28f4c3f7 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Fri, 13 May 2022 12:36:55 +0000 Subject: [PATCH 114/159] abi: merge-ready protocol contracts ABI --- lib/abi/CompositePostRebaseBeaconReceiver.json | 2 +- lib/abi/IERC721.json | 1 + lib/abi/Lido.json | 2 +- lib/abi/LidoMevTxFeeVault.json | 2 +- lib/abi/NodeOperatorsRegistry.json | 2 +- lib/abi/OrderedCallbacksArray.json | 2 +- lib/abi/SelfOwnedStETHBurner.json | 2 +- 7 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 lib/abi/IERC721.json diff --git a/lib/abi/CompositePostRebaseBeaconReceiver.json b/lib/abi/CompositePostRebaseBeaconReceiver.json index c1bd83495..05fc8f647 100644 --- a/lib/abi/CompositePostRebaseBeaconReceiver.json +++ b/lib/abi/CompositePostRebaseBeaconReceiver.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_voting","type":"address"},{"internalType":"address","name":"_oracle","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"callback","type":"address"},{"indexed":false,"internalType":"uint256","name":"atIndex","type":"uint256"}],"name":"CallbackAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"callback","type":"address"},{"indexed":false,"internalType":"uint256","name":"atIndex","type":"uint256"}],"name":"CallbackRemoved","type":"event"},{"inputs":[],"name":"MAX_CALLBACKS_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ORACLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_callback","type":"address"}],"name":"addCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"callbacks","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"callbacksLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_callback","type":"address"},{"internalType":"uint256","name":"_atIndex","type":"uint256"}],"name":"insertCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_postTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_preTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_timeElapsed","type":"uint256"}],"name":"processLidoOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_atIndex","type":"uint256"}],"name":"removeCallback","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_voting","type":"address"},{"internalType":"address","name":"_oracle","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"callback","type":"address"},{"indexed":false,"internalType":"uint256","name":"atIndex","type":"uint256"}],"name":"CallbackAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"callback","type":"address"},{"indexed":false,"internalType":"uint256","name":"atIndex","type":"uint256"}],"name":"CallbackRemoved","type":"event"},{"inputs":[],"name":"MAX_CALLBACKS_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ORACLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUIRED_INTERFACE","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_callback","type":"address"}],"name":"addCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"callbacks","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"callbacksLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_callback","type":"address"},{"internalType":"uint256","name":"_atIndex","type":"uint256"}],"name":"insertCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_postTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_preTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_timeElapsed","type":"uint256"}],"name":"processLidoOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_atIndex","type":"uint256"}],"name":"removeCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/lib/abi/IERC721.json b/lib/abi/IERC721.json new file mode 100644 index 000000000..b777358c2 --- /dev/null +++ b/lib/abi/IERC721.json @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"payable":true,"stateMutability":"payable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/Lido.json b/lib/abi/Lido.json index e5573a9e5..d6fcd21bd 100644 --- a/lib/abi/Lido.json +++ b/lib/abi/Lido.json @@ -1 +1 @@ -[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveMevTxFee","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalMevTxFeeCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_ORACLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferERC721ToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"}],"name":"setOracle","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_limitPoints","type":"uint256"}],"name":"setMevTxFeeWithdrawalLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_TREASURY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mevTxFeeVault","type":"address"}],"name":"setMevTxFeeVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_insuranceFund","type":"address"}],"name":"setInsuranceFund","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_INSURANCE_FUND","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_treasury","type":"address"}],"name":"setTreasury","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeWithdrawalLimitPoints","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oracle","type":"address"}],"name":"OracleSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasury","type":"address"}],"name":"TreasurySet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"InsuranceFundSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"MevTxFeeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"limitPoints","type":"uint256"}],"name":"MevTxFeeWithdrawalLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"mevTxFeeVault","type":"address"}],"name":"LidoMevTxFeeVaultSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"RecoverERC721ToVault","type":"event"}] \ No newline at end of file +[{"constant":false,"inputs":[],"name":"resume","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[],"name":"stop","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"receiveMevTxFee","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":false,"inputs":[{"name":"_depositContract","type":"address"},{"name":"_oracle","type":"address"},{"name":"_operators","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInsuranceFund","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_ethAmount","type":"uint256"}],"name":"getSharesByPooledEth","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStakingPaused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getOperators","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalMevTxFeeCollected","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"DEPOSIT_SIZE","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalPooledEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getTreasury","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isStopped","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_WITHDRAWAL_KEY","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getBufferedEther","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"},{"name":"_tokenId","type":"uint256"}],"name":"transferERC721ToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_beaconValidators","type":"uint256"},{"name":"_beaconBalance","type":"uint256"}],"name":"handleOracleReport","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_VAULT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFeeDistribution","outputs":[{"name":"treasuryFeeBasisPoints","type":"uint16"},{"name":"insuranceFeeBasisPoints","type":"uint16"},{"name":"operatorsFeeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_maxStakeLimit","type":"uint96"},{"name":"_stakeLimitIncreasePerBlock","type":"uint96"}],"name":"resumeStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sharesAmount","type":"uint256"}],"name":"getPooledEthByShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_PROTOCOL_CONTRACTS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getOracle","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"calculateCurrentStakeLimit","outputs":[{"name":"currentStakeLimit","type":"uint256"},{"name":"maxStakeLimit","type":"uint256"},{"name":"stakeLimitIncreasePerBlock","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_treasuryFeeBasisPoints","type":"uint16"},{"name":"_insuranceFeeBasisPoints","type":"uint16"},{"name":"_operatorsFeeBasisPoints","type":"uint16"}],"name":"setFeeDistribution","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_feeBasisPoints","type":"uint16"}],"name":"setFee","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"transferShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_maxDeposits","type":"uint256"}],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_FEE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_referral","type":"address"}],"name":"submit","outputs":[{"name":"","type":"uint256"}],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[],"name":"WITHDRAWAL_CREDENTIALS_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_amount","type":"uint256"},{"name":"_pubkeyHash","type":"bytes32"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_recipient","type":"address"},{"name":"_amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getDepositContract","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_mevTxFeeVault","type":"address"}],"name":"setMevTxFeeVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getBeaconStat","outputs":[{"name":"depositedValidators","type":"uint256"},{"name":"beaconValidators","type":"uint256"},{"name":"beaconBalance","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"BURN_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getFee","outputs":[{"name":"feeBasisPoints","type":"uint16"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getTotalShares","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_oracle","type":"address"},{"name":"_treasury","type":"address"},{"name":"_insuranceFund","type":"address"}],"name":"setProtocolContracts","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_limitPoints","type":"uint16"}],"name":"setMevTxFeeWithdrawalLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_PAUSE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"depositBufferedEther","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_account","type":"address"},{"name":"_sharesAmount","type":"uint256"}],"name":"burnShares","outputs":[{"name":"newTotalShares","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_account","type":"address"}],"name":"sharesOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getMevTxFeeWithdrawalLimitPoints","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"STAKING_RESUME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"pauseStaking","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"sharesValue","type":"uint256"}],"name":"TransferShares","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"sharesAmount","type":"uint256"}],"name":"SharesBurnt","type":"event"},{"anonymous":false,"inputs":[],"name":"Stopped","type":"event"},{"anonymous":false,"inputs":[],"name":"Resumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[],"name":"StakingPaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"maxStakeLimit","type":"uint96"},{"indexed":false,"name":"stakeLimitIncreasePerBlock","type":"uint96"}],"name":"StakingResumed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"oracle","type":"address"},{"indexed":false,"name":"treasury","type":"address"},{"indexed":false,"name":"insuranceFund","type":"address"}],"name":"ProtocolContactsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"feeBasisPoints","type":"uint16"}],"name":"FeeSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"treasuryFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"insuranceFeeBasisPoints","type":"uint16"},{"indexed":false,"name":"operatorsFeeBasisPoints","type":"uint16"}],"name":"FeeDistributionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"MevTxFeeReceived","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"limitPoints","type":"uint256"}],"name":"MevTxFeeWithdrawalLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"withdrawalCredentials","type":"bytes32"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"mevTxFeeVault","type":"address"}],"name":"LidoMevTxFeeVaultSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"amount","type":"uint256"},{"indexed":false,"name":"referral","type":"address"}],"name":"Submitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Unbuffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"sender","type":"address"},{"indexed":false,"name":"tokenAmount","type":"uint256"},{"indexed":false,"name":"sentFromBuffer","type":"uint256"},{"indexed":true,"name":"pubkeyHash","type":"bytes32"},{"indexed":false,"name":"etherAmount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"tokenId","type":"uint256"}],"name":"RecoverERC721ToVault","type":"event"}] \ No newline at end of file diff --git a/lib/abi/LidoMevTxFeeVault.json b/lib/abi/LidoMevTxFeeVault.json index f8d1606cc..a337b331a 100644 --- a/lib/abi/LidoMevTxFeeVault.json +++ b/lib/abi/LidoMevTxFeeVault.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_lido","type":"address"},{"internalType":"address","name":"_treasury","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalRewardsReceivedViaTransactions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdrawRewards","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_lido","type":"address"},{"internalType":"address","name":"_treasury","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxAmount","type":"uint256"}],"name":"withdrawRewards","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/lib/abi/NodeOperatorsRegistry.json b/lib/abi/NodeOperatorsRegistry.json index a0356f8b7..0cc9f4db7 100644 --- a/lib/abi/NodeOperatorsRegistry.json +++ b/lib/abi/NodeOperatorsRegistry.json @@ -1 +1 @@ -[{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_quantity","type":"uint256"},{"name":"_pubkeys","type":"bytes"},{"name":"_signatures","type":"bytes"}],"name":"addSigningKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_numKeys","type":"uint256"}],"name":"assignNextSigningKeys","outputs":[{"name":"pubkeys","type":"bytes"},{"name":"signatures","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_ADDRESS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"},{"name":"_amount","type":"uint256"}],"name":"removeSigningKeysOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_name","type":"string"}],"name":"setNodeOperatorName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_totalRewardShares","type":"uint256"}],"name":"getRewardsDistribution","outputs":[{"name":"recipients","type":"address[]"},{"name":"shares","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_active","type":"bool"}],"name":"setNodeOperatorActive","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_NAME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"removeSigningKey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"},{"name":"_amount","type":"uint256"}],"name":"removeSigningKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ADD_NODE_OPERATOR_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_quantity","type":"uint256"},{"name":"_pubkeys","type":"bytes"},{"name":"_signatures","type":"bytes"}],"name":"addSigningKeysOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getActiveNodeOperatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_rewardAddress","type":"address"}],"name":"addNodeOperator","outputs":[{"name":"id","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_operator_id","type":"uint256"}],"name":"getUnusedSigningKeyCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_rewardAddress","type":"address"}],"name":"setNodeOperatorRewardAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_fullInfo","type":"bool"}],"name":"getNodeOperator","outputs":[{"name":"active","type":"bool"},{"name":"name","type":"string"},{"name":"rewardAddress","type":"address"},{"name":"stakingLimit","type":"uint64"},{"name":"stoppedValidators","type":"uint64"},{"name":"totalSigningKeys","type":"uint64"},{"name":"usedSigningKeys","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNodeOperatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_stakingLimit","type":"uint64"}],"name":"setNodeOperatorStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"getSigningKey","outputs":[{"name":"key","type":"bytes"},{"name":"depositSignature","type":"bytes"},{"name":"used","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_stoppedIncrement","type":"uint64"}],"name":"reportStoppedValidators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_lido","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"REPORT_STOPPED_VALIDATORS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getKeysOpIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_ACTIVE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_operator_id","type":"uint256"}],"name":"getTotalSigningKeyCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"removeSigningKeyOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_SIGNING_KEYS","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"trimUnusedKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"name","type":"string"},{"indexed":false,"name":"rewardAddress","type":"address"},{"indexed":false,"name":"stakingLimit","type":"uint64"}],"name":"NodeOperatorAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"active","type":"bool"}],"name":"NodeOperatorActiveSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"name","type":"string"}],"name":"NodeOperatorNameSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"rewardAddress","type":"address"}],"name":"NodeOperatorRewardAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"stakingLimit","type":"uint64"}],"name":"NodeOperatorStakingLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"totalStopped","type":"uint64"}],"name":"NodeOperatorTotalStoppedValidatorsReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"totalKeysTrimmed","type":"uint64"}],"name":"NodeOperatorTotalKeysTrimmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"operatorId","type":"uint256"},{"indexed":false,"name":"pubkey","type":"bytes"}],"name":"SigningKeyAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"operatorId","type":"uint256"},{"indexed":false,"name":"pubkey","type":"bytes"}],"name":"SigningKeyRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"keysOpIndex","type":"uint256"}],"name":"KeysOpIndexSet","type":"event"}] \ No newline at end of file +[{"constant":true,"inputs":[],"name":"hasInitialized","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_quantity","type":"uint256"},{"name":"_pubkeys","type":"bytes"},{"name":"_signatures","type":"bytes"}],"name":"addSigningKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_script","type":"bytes"}],"name":"getEVMScriptExecutor","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getRecoveryVault","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_numKeys","type":"uint256"}],"name":"assignNextSigningKeys","outputs":[{"name":"pubkeys","type":"bytes"},{"name":"signatures","type":"bytes"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SIGNATURE_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_ADDRESS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"},{"name":"_amount","type":"uint256"}],"name":"removeSigningKeysOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_name","type":"string"}],"name":"setNodeOperatorName","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_totalRewardShares","type":"uint256"}],"name":"getRewardsDistribution","outputs":[{"name":"recipients","type":"address[]"},{"name":"shares","type":"uint256[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_active","type":"bool"}],"name":"setNodeOperatorActive","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_NAME_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"removeSigningKey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"},{"name":"_amount","type":"uint256"}],"name":"removeSigningKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"ADD_NODE_OPERATOR_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"token","type":"address"}],"name":"allowRecoverability","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_quantity","type":"uint256"},{"name":"_pubkeys","type":"bytes"},{"name":"_signatures","type":"bytes"}],"name":"addSigningKeysOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"appId","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getActiveNodeOperatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_name","type":"string"},{"name":"_rewardAddress","type":"address"}],"name":"addNodeOperator","outputs":[{"name":"id","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"getInitializationBlock","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_operator_id","type":"uint256"}],"name":"getUnusedSigningKeyCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_rewardAddress","type":"address"}],"name":"setNodeOperatorRewardAddress","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_id","type":"uint256"},{"name":"_fullInfo","type":"bool"}],"name":"getNodeOperator","outputs":[{"name":"active","type":"bool"},{"name":"name","type":"string"},{"name":"rewardAddress","type":"address"},{"name":"stakingLimit","type":"uint64"},{"name":"stoppedValidators","type":"uint64"},{"name":"totalSigningKeys","type":"uint64"},{"name":"usedSigningKeys","type":"uint64"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_token","type":"address"}],"name":"transferToVault","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_sender","type":"address"},{"name":"_role","type":"bytes32"},{"name":"_params","type":"uint256[]"}],"name":"canPerform","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getEVMScriptRegistry","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PUBKEY_LENGTH","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getNodeOperatorsCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_stakingLimit","type":"uint64"}],"name":"setNodeOperatorStakingLimit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"getSigningKey","outputs":[{"name":"key","type":"bytes"},{"name":"depositSignature","type":"bytes"},{"name":"used","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_id","type":"uint256"},{"name":"_stoppedIncrement","type":"uint64"}],"name":"reportStoppedValidators","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_lido","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"REPORT_STOPPED_VALIDATORS_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getKeysOpIndex","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"kernel","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_ACTIVE_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"SET_NODE_OPERATOR_LIMIT_ROLE","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_operator_id","type":"uint256"}],"name":"getTotalSigningKeyCount","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isPetrified","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MAX_NODE_OPERATORS_COUNT","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_operator_id","type":"uint256"},{"name":"_index","type":"uint256"}],"name":"removeSigningKeyOperatorBH","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"MANAGE_SIGNING_KEYS","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"trimUnusedKeys","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"executor","type":"address"},{"indexed":false,"name":"script","type":"bytes"},{"indexed":false,"name":"input","type":"bytes"},{"indexed":false,"name":"returnData","type":"bytes"}],"name":"ScriptResult","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"vault","type":"address"},{"indexed":true,"name":"token","type":"address"},{"indexed":false,"name":"amount","type":"uint256"}],"name":"RecoverToVault","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"id","type":"uint256"},{"indexed":false,"name":"name","type":"string"},{"indexed":false,"name":"rewardAddress","type":"address"},{"indexed":false,"name":"stakingLimit","type":"uint64"}],"name":"NodeOperatorAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"active","type":"bool"}],"name":"NodeOperatorActiveSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"name","type":"string"}],"name":"NodeOperatorNameSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"rewardAddress","type":"address"}],"name":"NodeOperatorRewardAddressSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"stakingLimit","type":"uint64"}],"name":"NodeOperatorStakingLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"totalStopped","type":"uint64"}],"name":"NodeOperatorTotalStoppedValidatorsReported","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"id","type":"uint256"},{"indexed":false,"name":"totalKeysTrimmed","type":"uint64"}],"name":"NodeOperatorTotalKeysTrimmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"operatorId","type":"uint256"},{"indexed":false,"name":"pubkey","type":"bytes"}],"name":"SigningKeyAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"operatorId","type":"uint256"},{"indexed":false,"name":"pubkey","type":"bytes"}],"name":"SigningKeyRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"keysOpIndex","type":"uint256"}],"name":"KeysOpIndexSet","type":"event"}] \ No newline at end of file diff --git a/lib/abi/OrderedCallbacksArray.json b/lib/abi/OrderedCallbacksArray.json index bd33b6f41..881c3bb6c 100644 --- a/lib/abi/OrderedCallbacksArray.json +++ b/lib/abi/OrderedCallbacksArray.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_voting","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"callback","type":"address"},{"indexed":false,"internalType":"uint256","name":"atIndex","type":"uint256"}],"name":"CallbackAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"callback","type":"address"},{"indexed":false,"internalType":"uint256","name":"atIndex","type":"uint256"}],"name":"CallbackRemoved","type":"event"},{"inputs":[],"name":"MAX_CALLBACKS_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_callback","type":"address"}],"name":"addCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"callbacks","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"callbacksLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_callback","type":"address"},{"internalType":"uint256","name":"_atIndex","type":"uint256"}],"name":"insertCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_atIndex","type":"uint256"}],"name":"removeCallback","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_voting","type":"address"},{"internalType":"bytes4","name":"_requiredIface","type":"bytes4"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"callback","type":"address"},{"indexed":false,"internalType":"uint256","name":"atIndex","type":"uint256"}],"name":"CallbackAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"callback","type":"address"},{"indexed":false,"internalType":"uint256","name":"atIndex","type":"uint256"}],"name":"CallbackRemoved","type":"event"},{"inputs":[],"name":"MAX_CALLBACKS_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUIRED_INTERFACE","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_callback","type":"address"}],"name":"addCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"callbacks","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"callbacksLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_callback","type":"address"},{"internalType":"uint256","name":"_atIndex","type":"uint256"}],"name":"insertCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_atIndex","type":"uint256"}],"name":"removeCallback","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/SelfOwnedStETHBurner.json b/lib/abi/SelfOwnedStETHBurner.json index 3bb850e37..d70f513d1 100644 --- a/lib/abi/SelfOwnedStETHBurner.json +++ b/lib/abi/SelfOwnedStETHBurner.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_treasury","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"address","name":"_voting","type":"address"},{"internalType":"uint256","name":"_totalCoverSharesBurnt","type":"uint256"},{"internalType":"uint256","name":"_totalNonCoverSharesBurnt","type":"uint256"},{"internalType":"uint256","name":"_maxBurnAmountPerRunBasisPoints","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxBurnAmountPerRunBasisPoints","type":"uint256"}],"name":"BurnAmountPerRunQuotaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesAmount","type":"uint256"}],"name":"ExcessStETHRecovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCover","type":"bool"},{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesAmount","type":"uint256"}],"name":"StETHBurnRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCover","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesAmount","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBurnAmountPerRunQuota","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCoverSharesBurnt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExcessStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNonCoverSharesBurnt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"processLidoOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"recoverExcessStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stETH2Burn","type":"uint256"}],"name":"requestBurnMyStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stETH2Burn","type":"uint256"}],"name":"requestBurnMyStETHForCover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxBurnAmountPerRunBasisPoints","type":"uint256"}],"name":"setBurnAmountPerRunQuota","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_treasury","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"address","name":"_voting","type":"address"},{"internalType":"uint256","name":"_totalCoverSharesBurnt","type":"uint256"},{"internalType":"uint256","name":"_totalNonCoverSharesBurnt","type":"uint256"},{"internalType":"uint256","name":"_maxBurnAmountPerRunBasisPoints","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxBurnAmountPerRunBasisPoints","type":"uint256"}],"name":"BurnAmountPerRunQuotaChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ERC20Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ERC721Recovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesAmount","type":"uint256"}],"name":"ExcessStETHRecovered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCover","type":"bool"},{"indexed":true,"internalType":"address","name":"requestedBy","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesAmount","type":"uint256"}],"name":"StETHBurnRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bool","name":"isCover","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"sharesAmount","type":"uint256"}],"name":"StETHBurnt","type":"event"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TREASURY","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VOTING","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBurnAmountPerRunQuota","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getCoverSharesBurnt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExcessStETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNonCoverSharesBurnt","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"processLidoOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"recoverERC721","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"recoverExcessStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stETH2Burn","type":"uint256"}],"name":"requestBurnMyStETH","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stETH2Burn","type":"uint256"}],"name":"requestBurnMyStETHForCover","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxBurnAmountPerRunBasisPoints","type":"uint256"}],"name":"setBurnAmountPerRunQuota","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file From d4f5ebba243421892501807185709b75654ee90f Mon Sep 17 00:00:00 2001 From: Logachev Nikita Date: Sat, 14 May 2022 01:21:35 +0300 Subject: [PATCH 115/159] add tests for stake limit library --- .../test_helpers/StakeLimitUtilsMock.sol | 46 ++++ test/0.4.24/staking-limit.test.js | 198 ++++++++++++++++++ 2 files changed, 244 insertions(+) create mode 100644 contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol create mode 100644 test/0.4.24/staking-limit.test.js diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol new file mode 100644 index 000000000..3039b0a6f --- /dev/null +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -0,0 +1,46 @@ + +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.4.24; + +import "../lib/StakeLimitUtils.sol"; + +contract StakeLimitUtilsMock { + using StakeLimitUtils for uint256; + + function decodeStakeLimitSlot(uint256 _slotValue) public pure returns ( + uint96 maxStakeLimit, + uint96 stakeLimitIncPerBlock, + uint96 prevStakeLimit, + uint32 prevStakeBlockNumber + ) { + return _slotValue.decodeStakeLimitSlot(); + } + + function encodeStakeLimitSlot( + uint96 _maxStakeLimit, + uint96 _stakeLimitIncPerBlock, + uint96 _prevStakeLimit, + uint32 _prevStakeBlockNumber + ) public pure returns (uint256 ret) { + return StakeLimitUtils.encodeStakeLimitSlot(_maxStakeLimit, _stakeLimitIncPerBlock, _prevStakeLimit, _prevStakeBlockNumber); + } + + function calculateCurrentStakeLimit(uint256 _slotValue) public view returns(uint256 limit) { + return _slotValue.calculateCurrentStakeLimit(); + } + + function updatePrevStakeLimit(uint256 _slotValue, uint256 _newPrevLimit) public view returns(uint256) { + return _slotValue.updatePrevStakeLimit( _newPrevLimit); + } + + function isStakingPaused(uint256 _slotValue) public pure returns(bool) { + return _slotValue.isStakingPaused(); + } + + function isStakingRateLimited(uint256 _slotValue) public pure returns(bool) { + return _slotValue.isStakingRateLimited(); + } +} \ No newline at end of file diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js new file mode 100644 index 000000000..a5d119403 --- /dev/null +++ b/test/0.4.24/staking-limit.test.js @@ -0,0 +1,198 @@ +const { assert } = require('chai') +const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') +const { bn, MAX_UINT256 } = require('@aragon/contract-helpers-test') +const { toBN } = require('../helpers/utils') +const { waitBlocks } = require('../helpers/blockchain') + +const StakeLimitUtils = artifacts.require('StakeLimitUtilsMock.sol') + +const ETH = (value) => web3.utils.toWei(value + '', 'ether') + +// +// We need to pack four variables into the same 256bit-wide storage slot +// to lower the costs per each staking request. +// +// As a result, slot's memory aligned as follows: +// +// LSB ------------------------------------------------------------------------------> MSB +// 0______________________32______________128_________________________160______________256 +// |______________________|________________|___________________________|________________| +// | prevStakeBlockNumber | prevStakeLimit | maxStakeLimitGrowthBlocks | maxStakeLimit | +// |<----- 32 bits ------>|<-- 96 bits --->|<---------- 32 bits ------>|<--- 96 bits -->| +// +// +// NB: we represent `maxStakeLimitGrowthBlocks` as follows: +// `maxStakeLimitGrowthBlocks` = `maxStakeLimit` / `stakeLimitIncreasePerBlock` +// 32 bits 96 bits 96 bits +// + +contract('StakingLimits', () => { + let limits + + before('deploy base app', async () => { + limits = await StakeLimitUtils.new() + }) + + it('encode zeros', async () => { + const slot = await limits.encodeStakeLimitSlot(0, 0, 0, 0) + assertBn(slot, 0) + + const decodedSlot = await limits.decodeStakeLimitSlot(slot) + assertBn(decodedSlot.maxStakeLimit, 0) + assertBn(decodedSlot.stakeLimitIncPerBlock, 0) + assertBn(decodedSlot.prevStakeLimit, 0) + assertBn(decodedSlot.prevStakeBlockNumber, 0) + }) + + it('check 0 slot', async () => { + const slot = 0 + assertBn(slot, 0) + + const decodedSlot = await limits.decodeStakeLimitSlot(slot) + assertBn(decodedSlot.maxStakeLimit, 0) + assertBn(decodedSlot.stakeLimitIncPerBlock, 0) + assertBn(decodedSlot.prevStakeLimit, 0) + assertBn(decodedSlot.prevStakeBlockNumber, 0) + }) + + it('check staking pause', async () => { + const slot = 0 + const paused = await limits.isStakingPaused(slot) + assert.equal(paused, true, 'limits not paused') + + const maxStakeLimit = 10 + const slot2 = await limits.encodeStakeLimitSlot(maxStakeLimit, 0, 0, 0) + const paused2 = await limits.isStakingPaused(slot2) + assert.equal(paused2, false, 'limits not limited') + }) + + it('check staking rate limit', async () => { + const slot = 0 + const limited = await limits.isStakingRateLimited(slot) + + assert.equal(limited, false, 'limits not limited') + + const maxStakeLimit = 10 + const slot2 = await limits.encodeStakeLimitSlot(maxStakeLimit, 0, 0, 0) + const limited2 = await limits.isStakingRateLimited(slot2) + + assert.equal(limited2, true, 'limits not limited') + }) + + it('stake limit > max stake', async () => { + const slot = await limits.encodeStakeLimitSlot(5, 10, 0, 0) + const decodedSlot = await limits.decodeStakeLimitSlot(slot) + + assertBn(decodedSlot.maxStakeLimit, 5) + assertBn(decodedSlot.stakeLimitIncPerBlock, 0) + assertBn(decodedSlot.prevStakeLimit, 0) + assertBn(decodedSlot.prevStakeBlockNumber, 0) + }) + + it('check update calculate stake limit with different blocks', async () => { + const block = await web3.eth.getBlock('latest') + assert.equal(block.number, 1) + + const slot = await limits.encodeStakeLimitSlot(100, 50, 0, block.number) + + const currentStakeLimit = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit, 0) + + const block2 = await waitBlocks(1) + assert.equal(block2.number, 2) + const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit2, 50) + + const block3 = await waitBlocks(3) + assert.equal(block3.number, 5) + const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit3, 100) + }) + + it('check update stake limit', async () => { + const maxLimit = 100 + const incPerBlock = 50 + const block = await web3.eth.getBlock('latest') + assert.equal(block.number, 5) + + const slot = await limits.encodeStakeLimitSlot(maxLimit, incPerBlock, 0, block.number) + const decodedSlot = await limits.decodeStakeLimitSlot(slot) + assert.equal(decodedSlot.prevStakeBlockNumber, 5) + assert.equal(decodedSlot.prevStakeLimit, 0) + + const block2 = await waitBlocks(3) + assert.equal(block2.number, 8) + + const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit2, maxLimit) + + const deposit = 87 + const newSlot = await limits.updatePrevStakeLimit(slot, currentStakeLimit2 - deposit) + const decodedNewSlot = await limits.decodeStakeLimitSlot(newSlot) + assert.equal(decodedNewSlot.prevStakeBlockNumber, 8) + assert.equal(decodedNewSlot.prevStakeLimit, 13) + + // checking staking recovery + await waitBlocks(1) + const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(newSlot) + assertBn(currentStakeLimit3, 13 + incPerBlock) + + await waitBlocks(1) + const currentStakeLimit4 = await limits.calculateCurrentStakeLimit(newSlot) + assertBn(currentStakeLimit4, maxLimit) + }) + + it('max values', async () => { + const block = await web3.eth.getBlock('latest') + const maxLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 + const minIncPerBlock = 1 // uint96 + const maxPrevStakeLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 + const maxBlock = toBN(2).pow(toBN(32)).sub(toBN(1)) // uint32 + + const maxSlot = await limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock) + + const maxUint256 = toBN(2).pow(toBN(256)).sub(toBN(1)) + assertBn(maxSlot, maxUint256) + + const decodedRaw = await limits.decodeStakeLimitSlot(maxSlot) + + // console.log(decodedRaw) + + const maxStakeLimit = decodedRaw.maxStakeLimit + const stakeLimitIncPerBlock = decodedRaw.stakeLimitIncPerBlock + const prevStakeLimit = decodedRaw.prevStakeLimit + const prevStakeBlockNumber = decodedRaw.prevStakeBlockNumber + + const growthBlock = maxSlot.shrn(128) + // console.log(maxSlot.toString(2)) + console.log({ + stakeLimitIncPerBlock: stakeLimitIncPerBlock.toString(), + maxBlock: maxBlock.toString(), + maxStakeLimit: maxStakeLimit.toString(), + growthBlock: growthBlock.toTwos(32).toString(2) + }) + + assertBn(maxStakeLimit, maxLimit) + assertBn(stakeLimitIncPerBlock, minIncPerBlock) + assertBn(prevStakeLimit, maxPrevStakeLimit) + assertBn(prevStakeBlockNumber, maxBlock) + }) +}) + +function pad32(num) { + return pz(num, 32) +} + +function pad96(num) { + return pz(num, 96) +} + +function pad256(num) { + return pz(num, 256) +} + +function pz(num, size) { + var s = num + '' + while (s.length < size) s = '0' + s + return s +} From 4c611cbc80163b341f654d551226f6a7fdd12eeb Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 14 May 2022 11:57:20 +0000 Subject: [PATCH 116/159] fix: enforce stake limit invariants by the lib Move require statements inside the library. Update tests, giveup the absolute blocks numbering. --- contracts/0.4.24/Lido.sol | 7 ----- contracts/0.4.24/lib/StakeLimitUtils.sol | 13 +++++++-- test/0.4.24/staking-limit.test.js | 35 ++++++++++++------------ 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index cd1de728b..bdd72ae2b 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -180,13 +180,6 @@ contract Lido is ILido, StETH, AragonApp { ) external { _auth(STAKING_RESUME_ROLE); - require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_INCREASE"); - require( - (_stakeLimitIncreasePerBlock == 0) - || (_maxStakeLimit / _stakeLimitIncreasePerBlock <= uint32(-1)), - "TOO_SMALL_INCREASE" - ); - ( uint96 maxStakeLimit,, uint96 prevStakeLimit, diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index ca703eee1..acaa376a4 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -53,16 +53,23 @@ library StakeLimitUtils { */ function encodeStakeLimitSlot( uint96 _maxStakeLimit, - uint96 _stakeLimitIncPerBlock, + uint96 _stakeLimitIncreasePerBlock, uint96 _prevStakeLimit, uint32 _prevStakeBlockNumber ) internal pure returns (uint256 ret) { + require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_INCREASE"); + require( + (_stakeLimitIncreasePerBlock == 0) + || (_maxStakeLimit / _stakeLimitIncreasePerBlock <= uint32(-1)), + "TOO_SMALL_INCREASE" + ); + ret = uint256(_maxStakeLimit) << MAX_STAKE_LIMIT_OFFSET | uint256(_prevStakeLimit) << PREV_STAKE_LIMIT_OFFSET | uint256(_prevStakeBlockNumber) << PREV_STAKE_BLOCK_NUMBER_OFFSET; - if (_stakeLimitIncPerBlock > 0) { - ret |= uint256(uint32(_maxStakeLimit / _stakeLimitIncPerBlock)) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; + if (_stakeLimitIncreasePerBlock > 0) { + ret |= uint256(uint32(_maxStakeLimit / _stakeLimitIncreasePerBlock)) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; } } diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index a5d119403..f63e4a588 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -1,5 +1,5 @@ const { assert } = require('chai') -const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') +const { assertBn, assertRevert } = require('@aragon/contract-helpers-test/src/asserts') const { bn, MAX_UINT256 } = require('@aragon/contract-helpers-test') const { toBN } = require('../helpers/utils') const { waitBlocks } = require('../helpers/blockchain') @@ -79,19 +79,15 @@ contract('StakingLimits', () => { assert.equal(limited2, true, 'limits not limited') }) - it('stake limit > max stake', async () => { - const slot = await limits.encodeStakeLimitSlot(5, 10, 0, 0) - const decodedSlot = await limits.decodeStakeLimitSlot(slot) + it('stake limit increase > max stake', async () => { + await limits.encodeStakeLimitSlot(5, 0, 0, 0) + await limits.encodeStakeLimitSlot(5, 5, 0, 0) - assertBn(decodedSlot.maxStakeLimit, 5) - assertBn(decodedSlot.stakeLimitIncPerBlock, 0) - assertBn(decodedSlot.prevStakeLimit, 0) - assertBn(decodedSlot.prevStakeBlockNumber, 0) + assertRevert(limits.encodeStakeLimitSlot(5, 6, 0, 0), `TOO_LARGE_INCREASE`) }) it('check update calculate stake limit with different blocks', async () => { const block = await web3.eth.getBlock('latest') - assert.equal(block.number, 1) const slot = await limits.encodeStakeLimitSlot(100, 50, 0, block.number) @@ -99,12 +95,12 @@ contract('StakingLimits', () => { assertBn(currentStakeLimit, 0) const block2 = await waitBlocks(1) - assert.equal(block2.number, 2) + assert.equal(block2.number, block.number + 1) const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) assertBn(currentStakeLimit2, 50) const block3 = await waitBlocks(3) - assert.equal(block3.number, 5) + assert.equal(block3.number, block.number + 1 + 3) const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(slot) assertBn(currentStakeLimit3, 100) }) @@ -113,15 +109,14 @@ contract('StakingLimits', () => { const maxLimit = 100 const incPerBlock = 50 const block = await web3.eth.getBlock('latest') - assert.equal(block.number, 5) const slot = await limits.encodeStakeLimitSlot(maxLimit, incPerBlock, 0, block.number) const decodedSlot = await limits.decodeStakeLimitSlot(slot) - assert.equal(decodedSlot.prevStakeBlockNumber, 5) + assert.equal(decodedSlot.prevStakeBlockNumber, block.number) assert.equal(decodedSlot.prevStakeLimit, 0) const block2 = await waitBlocks(3) - assert.equal(block2.number, 8) + assert.equal(block2.number, block.number + 3) const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) assertBn(currentStakeLimit2, maxLimit) @@ -129,7 +124,7 @@ contract('StakingLimits', () => { const deposit = 87 const newSlot = await limits.updatePrevStakeLimit(slot, currentStakeLimit2 - deposit) const decodedNewSlot = await limits.decodeStakeLimitSlot(newSlot) - assert.equal(decodedNewSlot.prevStakeBlockNumber, 8) + assert.equal(decodedNewSlot.prevStakeBlockNumber, block2.number) assert.equal(decodedNewSlot.prevStakeLimit, 13) // checking staking recovery @@ -145,12 +140,18 @@ contract('StakingLimits', () => { it('max values', async () => { const block = await web3.eth.getBlock('latest') const maxLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 - const minIncPerBlock = 1 // uint96 + let minIncPerBlock = 1 // uint96 const maxPrevStakeLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 const maxBlock = toBN(2).pow(toBN(32)).sub(toBN(1)) // uint32 - const maxSlot = await limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock) + assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock), `TOO_SMALL_INCREASE`) + + minIncPerBlock = maxLimit.div(toBN(2).pow(toBN(32)).sub(toBN(1))) + minIncPerBlockForRevert = minIncPerBlock.div(toBN(2)) // reverts + assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlockForRevert, maxPrevStakeLimit, maxBlock), `TOO_SMALL_INCREASE`) + + const maxSlot = await limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock) const maxUint256 = toBN(2).pow(toBN(256)).sub(toBN(1)) assertBn(maxSlot, maxUint256) From 2208fc33f8d55792091db911ded7a181e0da9d47 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 14 May 2022 17:06:52 +0000 Subject: [PATCH 117/159] fix: use safeTransfer for ERC20 recovery Leverage OZ safeTransfer helper lib which deals with non-conformant tokens (USDT, for example). --- contracts/0.8.9/LidoMevTxFeeVault.sol | 6 ++++-- contracts/0.8.9/SelfOwnedStETHBurner.sol | 5 ++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 36af3d99a..9fc012ce8 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -7,7 +7,7 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-v4.4/token/ERC721/IERC721.sol"; - +import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; interface ILido { /** @@ -29,6 +29,8 @@ interface ILido { * happen much less often, only on LidoOracle beacon balance reports */ contract LidoMevTxFeeVault { + using SafeERC20 for IERC20; + address public immutable LIDO; address public immutable TREASURY; @@ -103,7 +105,7 @@ contract LidoMevTxFeeVault { emit ERC20Recovered(msg.sender, _token, _amount); - require(IERC20(_token).transfer(TREASURY, _amount)); + IERC20(_token).safeTransfer(TREASURY, _amount); } /** diff --git a/contracts/0.8.9/SelfOwnedStETHBurner.sol b/contracts/0.8.9/SelfOwnedStETHBurner.sol index 328c559a8..e39949170 100644 --- a/contracts/0.8.9/SelfOwnedStETHBurner.sol +++ b/contracts/0.8.9/SelfOwnedStETHBurner.sol @@ -7,6 +7,7 @@ pragma solidity 0.8.9; import "@openzeppelin/contracts-v4.4/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts-v4.4/token/ERC721/IERC721.sol"; +import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts-v4.4/utils/introspection/ERC165.sol"; import "@openzeppelin/contracts-v4.4/utils/math/Math.sol"; import "./interfaces/IBeaconReportReceiver.sol"; @@ -76,6 +77,8 @@ interface IOracle { * @dev Burning stETH means 'decrease total underlying shares amount to perform stETH token rebase' */ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, ERC165 { + using SafeERC20 for IERC20; + uint256 private constant MAX_BASIS_POINTS = 10000; uint256 private coverSharesBurnRequested; @@ -267,7 +270,7 @@ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, E emit ERC20Recovered(msg.sender, _token, _amount); - require(IERC20(_token).transfer(TREASURY, _amount)); + IERC20(_token).safeTransfer(TREASURY, _amount); } /** From 1d619f0f0bdbdc03fb4b8557acdf19aaa4885f1c Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 14 May 2022 17:12:28 +0000 Subject: [PATCH 118/159] fix: outdated docs and typos in LidoMevTxFeeVault --- contracts/0.8.9/LidoMevTxFeeVault.sol | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoMevTxFeeVault.sol index 9fc012ce8..08b7b5a58 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoMevTxFeeVault.sol @@ -22,9 +22,6 @@ interface ILido { /** * @title A vault for temporary storage of MEV and transaction fees * -* This contract has no payable functions because it's balance is supposed to be -* increased directly by ethereum protocol when transaction priority fees and extracted MEV -* rewards are earned by a validator. * These vault replenishments happen continuously through a day, while withdrawals * happen much less often, only on LidoOracle beacon balance reports */ @@ -36,7 +33,7 @@ contract LidoMevTxFeeVault { /** * Emitted when the ERC20 `token` recovered (e.g. transferred) - * to the Lido treasure address by `requestedBy` sender. + * to the Lido treasury address by `requestedBy` sender. */ event ERC20Recovered( address indexed requestedBy, @@ -46,7 +43,7 @@ contract LidoMevTxFeeVault { /** * Emitted when the ERC721-compatible `token` (NFT) recovered (e.g. transferred) - * to the Lido treasure address by `requestedBy` sender. + * to the Lido treasury address by `requestedBy` sender. */ event ERC721Recovered( address indexed requestedBy, From d8c4f458f87c31754525b7a4d2a5e47ff3f939de Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 14 May 2022 18:00:01 +0000 Subject: [PATCH 119/159] chore: update comments --- contracts/0.4.24/Lido.sol | 2 +- contracts/0.4.24/lib/StakeLimitUtils.sol | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index bdd72ae2b..e0a591e6e 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -780,7 +780,7 @@ contract Lido is ILido, StETH, AragonApp { /** * @dev Distributes rewards by minting and distributing corresponding amount of liquid tokens. - * @param _totalRewards Total rewards occurred on the Ethereum 2.0 side in wei + * @param _totalRewards Total rewards accrued on the Ethereum 2.0 side in wei */ function distributeRewards(uint256 _totalRewards) internal { // We need to take a defined percentage of the reported reward as a fee, and we do diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index acaa376a4..cac2261fb 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -18,10 +18,16 @@ pragma solidity 0.4.24; // |<----- 32 bits ------>|<-- 96 bits --->|<---------- 32 bits ------>|<--- 96 bits -->| // // -// NB: we represent `maxStakeLimitGrowthBlocks` as follows: +// NB: Internal representation conventions: +// +// represent `maxStakeLimitGrowthBlocks` as follows: // `maxStakeLimitGrowthBlocks` = `maxStakeLimit` / `stakeLimitIncreasePerBlock` // 32 bits 96 bits 96 bits // +// +// "staking paused" state is encoded by all fields being zero, +// "staking unlimited" state is encoded by maxStakeLimit being zero and prevStakeBlockNumber being non-zero. +// library StakeLimitUtils { uint256 internal constant MAX_STAKE_LIMIT_OFFSET = 160; From f95c20773882565489ff892043cefafa01e53012 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 14 May 2022 19:26:12 +0000 Subject: [PATCH 120/159] fix: use uint256 for stake limits uint256 for every interface whenever applicable instead of uint96 and uint32. --- contracts/0.4.24/Lido.sol | 11 ++--- contracts/0.4.24/interfaces/ILido.sol | 4 +- contracts/0.4.24/lib/StakeLimitUtils.sol | 42 ++++++++++--------- .../test_helpers/StakeLimitUtilsMock.sol | 18 ++++---- test/0.4.24/lido.test.js | 4 +- test/0.4.24/staking-limit.test.js | 12 ++++-- 6 files changed, 51 insertions(+), 40 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index e0a591e6e..4ae6e3677 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -168,6 +168,7 @@ contract Lido is ILido, StETH, AragonApp { * * To disable rate-limit pass zero arg values. * @dev Reverts if: + * - `_maxStakeLimit` >= 2^96 * - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` * - `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2^32 (only if `_stakeLimitIncreasePerBlock` != 0) * Emits `StakeResumed` event @@ -175,14 +176,14 @@ contract Lido is ILido, StETH, AragonApp { * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ function resumeStaking( - uint96 _maxStakeLimit, - uint96 _stakeLimitIncreasePerBlock + uint256 _maxStakeLimit, + uint256 _stakeLimitIncreasePerBlock ) external { _auth(STAKING_RESUME_ROLE); ( - uint96 maxStakeLimit,, - uint96 prevStakeLimit, + uint256 maxStakeLimit,, + uint256 prevStakeLimit, ) = STAKE_LIMIT_POSITION.getStorageUint256().decodeStakeLimitSlot(); // if staking was paused or unlimited previously, @@ -197,7 +198,7 @@ contract Lido is ILido, StETH, AragonApp { _maxStakeLimit, _stakeLimitIncreasePerBlock, prevStakeLimit, - uint32(block.number) + block.number ) ); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 5fa6757fc..b9cbf2a1c 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -41,7 +41,7 @@ interface ILido { * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ - function resumeStaking(uint96 _maxStakeLimit, uint96 _stakeLimitIncreasePerBlock) external; + function resumeStaking(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external; /** * @notice Check staking state: whether it's paused or not @@ -60,7 +60,7 @@ interface ILido { event Stopped(); event Resumed(); event StakingPaused(); - event StakingResumed(uint96 maxStakeLimit, uint96 stakeLimitIncreasePerBlock); + event StakingResumed(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index cac2261fb..da0dfe8a6 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -20,7 +20,7 @@ pragma solidity 0.4.24; // // NB: Internal representation conventions: // -// represent `maxStakeLimitGrowthBlocks` as follows: +// the `maxStakeLimitGrowthBlocks` field above represented as follows: // `maxStakeLimitGrowthBlocks` = `maxStakeLimit` / `stakeLimitIncreasePerBlock` // 32 bits 96 bits 96 bits // @@ -40,10 +40,10 @@ library StakeLimitUtils { * @notice Unpack the slot value into stake limit params and state. */ function decodeStakeLimitSlot(uint256 _slotValue) internal pure returns ( - uint96 maxStakeLimit, - uint96 stakeLimitIncPerBlock, - uint96 prevStakeLimit, - uint32 prevStakeBlockNumber + uint256 maxStakeLimit, + uint256 stakeLimitIncPerBlock, + uint256 prevStakeLimit, + uint256 prevStakeBlockNumber ) { maxStakeLimit = uint96(_slotValue >> MAX_STAKE_LIMIT_OFFSET); uint32 growthBlocks = uint32(_slotValue >> MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET); @@ -58,24 +58,28 @@ library StakeLimitUtils { * @notice Pack stake limit params and state into a slot. */ function encodeStakeLimitSlot( - uint96 _maxStakeLimit, - uint96 _stakeLimitIncreasePerBlock, - uint96 _prevStakeLimit, - uint32 _prevStakeBlockNumber + uint256 _maxStakeLimit, + uint256 _stakeLimitIncreasePerBlock, + uint256 _prevStakeLimit, + uint256 _prevStakeBlockNumber ) internal pure returns (uint256 ret) { - require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_INCREASE"); + require(_maxStakeLimit <= uint96(-1), "TOO_LARGE_MAX_STAKE_LIMIT"); + require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_LIMIT_INCREASE"); + require(_prevStakeLimit <= uint96(-1), "TOO_LARGE_PREV_STAKE_LIMIT"); + require(_prevStakeBlockNumber <= uint32(-1), "TOO_LARGE_BLOCK_NUMBER"); + require( (_stakeLimitIncreasePerBlock == 0) || (_maxStakeLimit / _stakeLimitIncreasePerBlock <= uint32(-1)), - "TOO_SMALL_INCREASE" + "TOO_SMALL_LIMIT_INCREASE" ); - ret = uint256(_maxStakeLimit) << MAX_STAKE_LIMIT_OFFSET - | uint256(_prevStakeLimit) << PREV_STAKE_LIMIT_OFFSET - | uint256(_prevStakeBlockNumber) << PREV_STAKE_BLOCK_NUMBER_OFFSET; + ret = _maxStakeLimit << MAX_STAKE_LIMIT_OFFSET + | _prevStakeLimit << PREV_STAKE_LIMIT_OFFSET + | _prevStakeBlockNumber << PREV_STAKE_BLOCK_NUMBER_OFFSET; if (_stakeLimitIncreasePerBlock > 0) { - ret |= uint256(uint32(_maxStakeLimit / _stakeLimitIncreasePerBlock)) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; + ret |= (_maxStakeLimit / _stakeLimitIncreasePerBlock) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; } } @@ -84,10 +88,10 @@ library StakeLimitUtils { */ function calculateCurrentStakeLimit(uint256 _slotValue) internal view returns(uint256 limit) { ( - uint96 maxStakeLimit, - uint96 stakeLimitIncPerBlock, - uint96 prevStakeLimit, - uint32 prevStakeBlockNumber + uint256 maxStakeLimit, + uint256 stakeLimitIncPerBlock, + uint256 prevStakeLimit, + uint256 prevStakeBlockNumber ) = decodeStakeLimitSlot(_slotValue); limit = prevStakeLimit + ((block.number - prevStakeBlockNumber) * stakeLimitIncPerBlock); diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol index 3039b0a6f..e39e94e96 100644 --- a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -11,19 +11,19 @@ contract StakeLimitUtilsMock { using StakeLimitUtils for uint256; function decodeStakeLimitSlot(uint256 _slotValue) public pure returns ( - uint96 maxStakeLimit, - uint96 stakeLimitIncPerBlock, - uint96 prevStakeLimit, - uint32 prevStakeBlockNumber + uint256 maxStakeLimit, + uint256 stakeLimitIncPerBlock, + uint256 prevStakeLimit, + uint256 prevStakeBlockNumber ) { return _slotValue.decodeStakeLimitSlot(); } function encodeStakeLimitSlot( - uint96 _maxStakeLimit, - uint96 _stakeLimitIncPerBlock, - uint96 _prevStakeLimit, - uint32 _prevStakeBlockNumber + uint256 _maxStakeLimit, + uint256 _stakeLimitIncPerBlock, + uint256 _prevStakeLimit, + uint256 _prevStakeBlockNumber ) public pure returns (uint256 ret) { return StakeLimitUtils.encodeStakeLimitSlot(_maxStakeLimit, _stakeLimitIncPerBlock, _prevStakeLimit, _prevStakeBlockNumber); } @@ -43,4 +43,4 @@ contract StakeLimitUtilsMock { function isStakingRateLimited(uint256 _slotValue) public pure returns(bool) { return _slotValue.isStakingRateLimited(); } -} \ No newline at end of file +} diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 4237e8eee..235c97b46 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -651,8 +651,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await mineNBlocks(10) await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) - await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_INCREASE`) - await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TOO_SMALL_INCREASE`) + await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_LIMIT_INCREASE`) + await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TOO_SMALL_LIMIT_INCREASE`) }) it('one-shot rate-limiting works', async () => { diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index f63e4a588..2186e24a1 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -83,7 +83,13 @@ contract('StakingLimits', () => { await limits.encodeStakeLimitSlot(5, 0, 0, 0) await limits.encodeStakeLimitSlot(5, 5, 0, 0) - assertRevert(limits.encodeStakeLimitSlot(5, 6, 0, 0), `TOO_LARGE_INCREASE`) + assertRevert(limits.encodeStakeLimitSlot(5, 6, 0, 0), `TOO_LARGE_LIMIT_INCREASE`) + }) + + it('stake limit reverts on large values', async () => { + assertRevert(limits.encodeStakeLimitSlot(toBN(2).pow(toBN(96)), 1, 1, 1), `TOO_LARGE_MAX_STAKE_LIMIT`) + assertRevert(limits.encodeStakeLimitSlot(1, 1, toBN(2).pow(toBN(96), 1), 1), `TOO_LARGE_PREV_STAKE_LIMIT`) + assertRevert(limits.encodeStakeLimitSlot(1, 1, 1, toBN(2).pow(toBN(32), 1)), `TOO_LARGE_BLOCK_NUMBER`) }) it('check update calculate stake limit with different blocks', async () => { @@ -144,12 +150,12 @@ contract('StakingLimits', () => { const maxPrevStakeLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 const maxBlock = toBN(2).pow(toBN(32)).sub(toBN(1)) // uint32 - assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock), `TOO_SMALL_INCREASE`) + assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock), `TOO_SMALL_LIMIT_INCREASE`) minIncPerBlock = maxLimit.div(toBN(2).pow(toBN(32)).sub(toBN(1))) minIncPerBlockForRevert = minIncPerBlock.div(toBN(2)) // reverts - assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlockForRevert, maxPrevStakeLimit, maxBlock), `TOO_SMALL_INCREASE`) + assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlockForRevert, maxPrevStakeLimit, maxBlock), `TOO_SMALL_LIMIT_INCREASE`) const maxSlot = await limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock) const maxUint256 = toBN(2).pow(toBN(256)).sub(toBN(1)) From daf6e0710c8d3bf34cc1b7d9f932b798a91820a3 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 14 May 2022 21:29:07 +0000 Subject: [PATCH 121/159] feat: frontend-oriented API for stake limits Add the `getCurrentStakeLimit` method. Add the `getStakeLimitFullInfo` method. --- contracts/0.4.24/Lido.sol | 54 +++++++++++++++++++++------ contracts/0.4.24/interfaces/ILido.sol | 19 ++++++++-- test/0.4.24/lido.test.js | 25 ++++++++++--- 3 files changed, 78 insertions(+), 20 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 4ae6e3677..83239b172 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -213,21 +213,53 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Calculate current stake limit value and return main params. - * See `resumeStaking` for the details. - * @dev Reverts if staking is paused - * NB: returns all zeros if staking rate-limit is disabled - */ - function calculateCurrentStakeLimit() external view returns ( + * @notice Returns how much Ether can be staked in the current block + * @dev Special return values: + * - max uint256 if staking is unlimited; + * - 0 if staking is paused or if limit is exhausted. + */ + function getCurrentStakeLimit() public view returns (uint256) { + uint256 slotValue = STAKE_LIMIT_POSITION.getStorageUint256(); + if (!slotValue.isStakingRateLimited()) { + return uint256(-1); + } + + return slotValue.calculateCurrentStakeLimit(); + } + + /** + * @notice Returns full info about stake limit + * @dev Might be used for advanced-level integration requests + * @return + * `isStakingPaused` the value returned by `isStakingPaused()` + * `isStakingLimitApplied` true if staking limit is set and false otherwise + * `currentStakeLimit` the value returned by `getCurrentStakingLimit()` + * + * Internals: + * `maxStakeLimit` internal max stake limit repr + * `stakeLimitIncPerBlock` internal stake limit increase per block repr + * `prevStakeLimit` // internal previously reached stake limit + * `prevStakeBlockNumber` // internal prevously seen block number + */ + function getStakeLimitFullInfo() external view returns ( + bool isStakingPaused, + bool isStakingLimitApplied, uint256 currentStakeLimit, uint256 maxStakeLimit, - uint256 stakeLimitIncreasePerBlock + uint256 stakeLimitIncPerBlock, + uint256 prevStakeLimit, + uint256 prevStakeBlockNumber ) { uint256 slotValue = STAKE_LIMIT_POSITION.getStorageUint256(); - require(!slotValue.isStakingPaused(), "STAKING_PAUSED"); - - (maxStakeLimit, stakeLimitIncreasePerBlock,,) = slotValue.decodeStakeLimitSlot(); - currentStakeLimit = slotValue.calculateCurrentStakeLimit(); + isStakingPaused = slotValue.isStakingPaused(); + isStakingLimitApplied = slotValue.isStakingRateLimited(); + currentStakeLimit = getCurrentStakeLimit(); + ( + maxStakeLimit, + stakeLimitIncPerBlock, + prevStakeLimit, + prevStakeBlockNumber + ) = slotValue.decodeStakeLimitSlot(); } /** diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index b9cbf2a1c..4844cf5c2 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -49,12 +49,25 @@ interface ILido { function isStakingPaused() external view returns (bool); /** - * @notice Calculate current stake limit value and return main params. + * @notice Returns how much Ether can be staked in the current block + * @dev Special return values: + * - `max uint256` if staking is unlimited + * - `0` if staking is paused or if limit is exhausted */ - function calculateCurrentStakeLimit() external view returns ( + function getCurrentStakeLimit() external view returns (uint256); + + /** + * @notice Returns full info about stake limit + * @dev Might be used for advanced-level integration requests + */ + function getStakeLimitFullInfo() external view returns ( + bool isStakingPaused, + bool isStakingLimitApplied, uint256 currentStakeLimit, uint256 maxStakeLimit, - uint256 stakeLimitIncreasePerBlock + uint256 stakeLimitIncPerBlock, + uint256 prevStakeLimit, + uint256 prevStakeBlockNumber ); event Stopped(); diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 235c97b46..6564e25e3 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -560,16 +560,30 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) const verifyRateLimitStake = async (expectedMaxStakeLimit, expectedLimitIncrease, expectedCurrentStakeLimit) => { - ;({ currentStakeLimit, maxStakeLimit, stakeLimitIncreasePerBlock } = await app.calculateCurrentStakeLimit()) - assertBn(maxStakeLimit, expectedMaxStakeLimit) - assertBn(stakeLimitIncreasePerBlock, expectedLimitIncrease) + currentStakeLimit = await app.getCurrentStakeLimit() + assertBn(currentStakeLimit, expectedCurrentStakeLimit) + ;({ + isStakingPaused, + isStakingLimitApplied, + currentStakeLimit, + maxStakeLimit, + stakeLimitIncPerBlock, + prevStakeLimit, + prevStakeBlockNumber + } = await app.getStakeLimitFullInfo()) + + assert.equal(isStakingPaused, false) + assert.equal(isStakingLimitApplied, expectedMaxStakeLimit !== 0) assertBn(currentStakeLimit, expectedCurrentStakeLimit) + assertBn(maxStakeLimit, expectedMaxStakeLimit) + assertBn(stakeLimitIncPerBlock, expectedLimitIncrease) } it('stake pause/unlimited resume works', async () => { let receipt - await verifyRateLimitStake(bn(0), bn(0), bn(0)) + const MAX_UINT256 = bn(2).pow(bn(256)).sub(bn(1)) + await verifyRateLimitStake(bn(0), bn(0), MAX_UINT256) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) @@ -578,7 +592,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'StakingPaused') assert.equal(await app.isStakingPaused(), true) - await assertRevert(app.calculateCurrentStakeLimit(), 'STAKING_PAUSED') await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) @@ -589,7 +602,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'StakingResumed') assert.equal(await app.isStakingPaused(), false) - await verifyRateLimitStake(bn(0), bn(0), bn(0)) + await verifyRateLimitStake(bn(0), bn(0), MAX_UINT256) await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(1.4) }) From 4f74910dc6eb7ba983a58a25c91820c772bf77b0 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 14 May 2022 21:46:08 +0000 Subject: [PATCH 122/159] chore: deal with bn in lido.test --- test/0.4.24/lido.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 6564e25e3..9c8ed609a 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -573,7 +573,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) } = await app.getStakeLimitFullInfo()) assert.equal(isStakingPaused, false) - assert.equal(isStakingLimitApplied, expectedMaxStakeLimit !== 0) + assert.equal(isStakingLimitApplied, expectedMaxStakeLimit.cmp(bn(0)) !== 0) assertBn(currentStakeLimit, expectedCurrentStakeLimit) assertBn(maxStakeLimit, expectedMaxStakeLimit) assertBn(stakeLimitIncPerBlock, expectedLimitIncrease) From bbb45268ef316a24f8b1377421b27591d8ffc7cc Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sat, 14 May 2022 21:58:16 +0000 Subject: [PATCH 123/159] chore: change comparison --- test/0.4.24/lido.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 9c8ed609a..1ccfd8f97 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -573,7 +573,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) } = await app.getStakeLimitFullInfo()) assert.equal(isStakingPaused, false) - assert.equal(isStakingLimitApplied, expectedMaxStakeLimit.cmp(bn(0)) !== 0) + assert.equal(isStakingLimitApplied, expectedMaxStakeLimit.toString() !== bn(0).toString()) assertBn(currentStakeLimit, expectedCurrentStakeLimit) assertBn(maxStakeLimit, expectedMaxStakeLimit) assertBn(stakeLimitIncPerBlock, expectedLimitIncrease) From 58dc6c5a53c9c14065e62cb2b70aba75147828c5 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sun, 15 May 2022 10:41:27 +0000 Subject: [PATCH 124/159] chore: fix comment about max NOs --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 1611878f6..7ef89c3bd 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -20,7 +20,7 @@ import "../lib/MemUtils.sol"; * * See the comment of `INodeOperatorsRegistry`. * - * NOTE: the code below assumes moderate amount of node operators, i.e. up to 50 (MAX_NODE_OPERATORS_COUNT). + * NOTE: the code below assumes moderate amount of node operators, i.e. up to `MAX_NODE_OPERATORS_COUNT`. */ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp { using SafeMath for uint256; From 19d6e425c685ab105d477113211936340969ad44 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sun, 15 May 2022 10:46:05 +0000 Subject: [PATCH 125/159] chore: better docs for `getStakeLimitFullInfo` --- contracts/0.4.24/Lido.sol | 10 +++++----- contracts/0.4.24/interfaces/ILido.sol | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 83239b172..6d99c0ea7 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -229,17 +229,17 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Returns full info about stake limit - * @dev Might be used for advanced-level integration requests + * @dev Might be used for the advanced integration requests. * @return * `isStakingPaused` the value returned by `isStakingPaused()` * `isStakingLimitApplied` true if staking limit is set and false otherwise * `currentStakeLimit` the value returned by `getCurrentStakingLimit()` * * Internals: - * `maxStakeLimit` internal max stake limit repr - * `stakeLimitIncPerBlock` internal stake limit increase per block repr - * `prevStakeLimit` // internal previously reached stake limit - * `prevStakeBlockNumber` // internal prevously seen block number + * `maxStakeLimit` internal max stake limit represenation + * `stakeLimitIncPerBlock` internal stake limit increase per block represenation + * `prevStakeLimit` internal previously reached stake limit represenation + * `prevStakeBlockNumber` internal prevously seen block number represenation */ function getStakeLimitFullInfo() external view returns ( bool isStakingPaused, diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 4844cf5c2..a7ce7f1e9 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -51,14 +51,14 @@ interface ILido { /** * @notice Returns how much Ether can be staked in the current block * @dev Special return values: - * - `max uint256` if staking is unlimited - * - `0` if staking is paused or if limit is exhausted + * - max uint256 if staking is unlimited; + * - 0 if staking is paused or if limit is exhausted. */ function getCurrentStakeLimit() external view returns (uint256); /** * @notice Returns full info about stake limit - * @dev Might be used for advanced-level integration requests + * @dev Might be used for the advanced integration requests. */ function getStakeLimitFullInfo() external view returns ( bool isStakingPaused, From 1242d24a43e245215ddf61fd08e2386c1beb965f Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Sun, 15 May 2022 22:05:04 +0000 Subject: [PATCH 126/159] fix: add new roles for the initial deploy --- contracts/0.4.24/template/LidoTemplate.sol | 9 +++++++-- docs/protocol-levers.md | 4 +++- scripts/multisig/12-check-dao.js | 14 +++++++++++++- test/deposit.test.js | 5 +++++ test/scenario/helpers/deploy.js | 9 +++++++++ 5 files changed, 37 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 7ae60e467..3334f47a1 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -622,7 +622,7 @@ contract LidoTemplate is IsContract { } // using loops to save contract size - bytes32[7] memory perms; + bytes32[10] memory perms; // Oracle perms[0] = _state.oracle.MANAGE_MEMBERS(); @@ -654,8 +654,13 @@ contract LidoTemplate is IsContract { perms[2] = _state.lido.MANAGE_WITHDRAWAL_KEY(); perms[3] = _state.lido.MANAGE_PROTOCOL_CONTRACTS_ROLE(); perms[4] = _state.lido.BURN_ROLE(); + perms[5] = _state.lido.RESUME_ROLE(); + perms[6] = _state.lido.STAKING_PAUSE_ROLE(); + perms[7] = _state.lido.STAKING_RESUME_ROLE(); + perms[8] = _state.lido.SET_MEV_TX_FEE_VAULT_ROLE(); + perms[9] = _state.lido.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(); - for (i = 0; i < 5; ++i) { + for (i = 0; i < 10; ++i) { _createPermissionForVoting(acl, _state.lido, perms[i], voting); } } diff --git a/docs/protocol-levers.md b/docs/protocol-levers.md index e53648e7d..32a64a3b9 100644 --- a/docs/protocol-levers.md +++ b/docs/protocol-levers.md @@ -100,8 +100,10 @@ if the amount of the buffered Ether becomes sufficiently large. ### Pausing -* Mutators: `stop()`, `resume()` +* Mutator: `stop()` * Permission required: `PAUSE_ROLE` +* Mutator: `resume()` + * Permission required: `RESUME_ROLE` * Accessor: `isStopped() returns (bool)` When paused, `Lido` doesn't accept user submissions, doesn't allow user withdrawals and oracle diff --git a/scripts/multisig/12-check-dao.js b/scripts/multisig/12-check-dao.js index 805268f38..6ec321866 100644 --- a/scripts/multisig/12-check-dao.js +++ b/scripts/multisig/12-check-dao.js @@ -566,7 +566,19 @@ async function assertDaoPermissions({ kernel, lido, oracle, nopsRegistry, agent, manager: voting, groups: [ { - roleNames: ['PAUSE_ROLE', 'MANAGE_FEE', 'MANAGE_WITHDRAWAL_KEY', 'SET_ORACLE', 'BURN_ROLE', 'SET_TREASURY', 'SET_INSURANCE_FUND'], + roleNames: [ + 'PAUSE_ROLE', + 'RESUME_ROLE', + 'MANAGE_FEE', + 'MANAGE_WITHDRAWAL_KEY', + 'SET_ORACLE', 'BURN_ROLE', + 'SET_TREASURY', + 'SET_INSURANCE_FUND', + 'STAKING_PAUSE_ROLE', + 'STAKING_RESUME_ROLE', + 'SET_MEV_TX_FEE_VAULT_ROLE', + 'SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE' + ], grantee: voting } ] diff --git a/test/deposit.test.js b/test/deposit.test.js index bcdc22729..42377f15d 100644 --- a/test/deposit.test.js +++ b/test/deposit.test.js @@ -80,8 +80,13 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use // Set up the app's permissions. await acl.createPermission(voting, app.address, await app.PAUSE_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.RESUME_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_FEE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) // await acl.createPermission(app.address, token.address, await token.MINT_ROLE(), appManager, { from: appManager }) // await acl.createPermission(app.address, token.address, await token.BURN_ROLE(), appManager, { from: appManager }) diff --git a/test/scenario/helpers/deploy.js b/test/scenario/helpers/deploy.js index 6f255a119..a0bf61a15 100644 --- a/test/scenario/helpers/deploy.js +++ b/test/scenario/helpers/deploy.js @@ -67,10 +67,13 @@ async function deployDaoAndPool(appManager, voting) { const [ POOL_PAUSE_ROLE, + POOL_RESUME_ROLE, POOL_MANAGE_FEE, POOL_MANAGE_WITHDRAWAL_KEY, POOL_BURN_ROLE, DEPOSIT_ROLE, + STAKING_PAUSE_ROLE, + STAKING_RESUME_ROLE, SET_MEV_TX_FEE_VAULT_ROLE, SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, @@ -82,10 +85,13 @@ async function deployDaoAndPool(appManager, voting) { NODE_OPERATOR_REGISTRY_REPORT_STOPPED_VALIDATORS_ROLE ] = await Promise.all([ pool.PAUSE_ROLE(), + pool.RESUME_ROLE(), pool.MANAGE_FEE(), pool.MANAGE_WITHDRAWAL_KEY(), pool.BURN_ROLE(), pool.DEPOSIT_ROLE(), + pool.STAKING_PAUSE_ROLE(), + pool.STAKING_RESUME_ROLE(), pool.SET_MEV_TX_FEE_VAULT_ROLE(), pool.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), nodeOperatorRegistry.MANAGE_SIGNING_KEYS(), @@ -100,9 +106,12 @@ async function deployDaoAndPool(appManager, voting) { await Promise.all([ // Allow voting to manage the pool acl.createPermission(voting, pool.address, POOL_PAUSE_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, POOL_RESUME_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, POOL_MANAGE_FEE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, POOL_MANAGE_WITHDRAWAL_KEY, appManager, { from: appManager }), acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, STAKING_PAUSE_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, STAKING_RESUME_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, SET_MEV_TX_FEE_VAULT_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }), From 3cf51b4b5ab22b90724f3a5b543ed79ea672803f Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 14:59:18 +0000 Subject: [PATCH 127/159] feat: refactor stake rate limit libs Make rate limit libs more handy and meaningful. NB: disable stake-limit unit tests for now. --- contracts/0.4.24/Lido.sol | 74 ++++----- contracts/0.4.24/interfaces/ILido.sol | 2 +- contracts/0.4.24/lib/StakeLimitUtils.sol | 127 ---------------- contracts/0.4.24/lib/StakeRateLimitUtils.sol | 143 ++++++++++++++++++ .../test_helpers/StakeLimitUtilsMock.sol | 75 ++++++--- test/0.4.24/lido.test.js | 4 +- test/0.4.24/staking-limit.test.js | 2 +- 7 files changed, 229 insertions(+), 198 deletions(-) delete mode 100644 contracts/0.4.24/lib/StakeLimitUtils.sol create mode 100644 contracts/0.4.24/lib/StakeRateLimitUtils.sol diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 6d99c0ea7..fd48120a4 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -17,7 +17,7 @@ import "./interfaces/ILidoMevTxFeeVault.sol"; import "./StETH.sol"; -import "./lib/StakeLimitUtils.sol"; +import "./lib/StakeRateLimitUtils.sol"; interface IERC721 { @@ -47,7 +47,8 @@ interface IERC721 { contract Lido is ILido, StETH, AragonApp { using SafeMath for uint256; using UnstructuredStorage for bytes32; - using StakeLimitUtils for uint256; + using StakeLimitUnstructuredStorage for bytes32; + using StakeRateLimitUtils for StakeLimitState.Data; /// ACL bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); @@ -129,8 +130,10 @@ contract Lido is ILido, StETH, AragonApp { _setProtocolContracts(_oracle, _treasury, _insuranceFund); - STAKE_LIMIT_POSITION.setStorageUint256( - StakeLimitUtils.encodeStakeLimitSlot(0, 0, 0, uint32(block.number)) + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( + 0, 0 // STAKING IS UNLIMITED + ) ); initialized(); @@ -181,24 +184,9 @@ contract Lido is ILido, StETH, AragonApp { ) external { _auth(STAKING_RESUME_ROLE); - ( - uint256 maxStakeLimit,, - uint256 prevStakeLimit, - ) = STAKE_LIMIT_POSITION.getStorageUint256().decodeStakeLimitSlot(); - - // if staking was paused or unlimited previously, - // or new limit is lower than previous, then - // reset prev stake limit to max - if ((maxStakeLimit == 0) || (_maxStakeLimit < prevStakeLimit)) { - prevStakeLimit = _maxStakeLimit; - } - - STAKE_LIMIT_POSITION.setStorageUint256( - StakeLimitUtils.encodeStakeLimitSlot( - _maxStakeLimit, - _stakeLimitIncreasePerBlock, - prevStakeLimit, - block.number + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( + _maxStakeLimit, _stakeLimitIncreasePerBlock ) ); @@ -209,7 +197,7 @@ contract Lido is ILido, StETH, AragonApp { * @notice Check staking state: whether it's paused or not */ function isStakingPaused() external view returns (bool) { - return STAKE_LIMIT_POSITION.getStorageUint256().isStakingPaused(); + return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingPaused(); } /** @@ -219,12 +207,12 @@ contract Lido is ILido, StETH, AragonApp { * - 0 if staking is paused or if limit is exhausted. */ function getCurrentStakeLimit() public view returns (uint256) { - uint256 slotValue = STAKE_LIMIT_POSITION.getStorageUint256(); - if (!slotValue.isStakingRateLimited()) { + StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + if (!stakeLimitData.isStakingRateLimited()) { return uint256(-1); } - return slotValue.calculateCurrentStakeLimit(); + return stakeLimitData.calculateCurrentStakeLimit(); } /** @@ -237,7 +225,7 @@ contract Lido is ILido, StETH, AragonApp { * * Internals: * `maxStakeLimit` internal max stake limit represenation - * `stakeLimitIncPerBlock` internal stake limit increase per block represenation + * `maxStakeLimitGrowthBlocks` internal max stake limit full restoration blocks * `prevStakeLimit` internal previously reached stake limit represenation * `prevStakeBlockNumber` internal prevously seen block number represenation */ @@ -246,20 +234,20 @@ contract Lido is ILido, StETH, AragonApp { bool isStakingLimitApplied, uint256 currentStakeLimit, uint256 maxStakeLimit, - uint256 stakeLimitIncPerBlock, + uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, uint256 prevStakeBlockNumber ) { - uint256 slotValue = STAKE_LIMIT_POSITION.getStorageUint256(); - isStakingPaused = slotValue.isStakingPaused(); - isStakingLimitApplied = slotValue.isStakingRateLimited(); + StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + + isStakingPaused = stakeLimitData.isStakingPaused(); + isStakingLimitApplied = stakeLimitData.isStakingRateLimited(); currentStakeLimit = getCurrentStakeLimit(); - ( - maxStakeLimit, - stakeLimitIncPerBlock, - prevStakeLimit, - prevStakeBlockNumber - ) = slotValue.decodeStakeLimitSlot(); + + maxStakeLimit = stakeLimitData.maxStakeLimit; + maxStakeLimitGrowthBlocks = stakeLimitData.maxStakeLimitGrowthBlocks; + prevStakeLimit = stakeLimitData.prevStakeLimit; + prevStakeBlockNumber = stakeLimitData.prevStakeBlockNumber; } /** @@ -692,16 +680,16 @@ contract Lido is ILido, StETH, AragonApp { function _submit(address _referral) internal whenNotStopped returns (uint256) { require(msg.value != 0, "ZERO_DEPOSIT"); - uint256 stakeLimitSlotValue = STAKE_LIMIT_POSITION.getStorageUint256(); - require(!stakeLimitSlotValue.isStakingPaused(), "STAKING_PAUSED"); + StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + require(!stakeLimitData.isStakingPaused(), "STAKING_PAUSED"); - if (stakeLimitSlotValue.isStakingRateLimited()) { - uint256 currentStakeLimit = stakeLimitSlotValue.calculateCurrentStakeLimit(); + if (stakeLimitData.isStakingRateLimited()) { + uint256 currentStakeLimit = stakeLimitData.calculateCurrentStakeLimit(); require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); - STAKE_LIMIT_POSITION.setStorageUint256( - stakeLimitSlotValue.updatePrevStakeLimit(currentStakeLimit - msg.value) + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + stakeLimitData.updatePrevStakeLimit(currentStakeLimit - msg.value) ); } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index a7ce7f1e9..a2116a2c2 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -65,7 +65,7 @@ interface ILido { bool isStakingLimitApplied, uint256 currentStakeLimit, uint256 maxStakeLimit, - uint256 stakeLimitIncPerBlock, + uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, uint256 prevStakeBlockNumber ); diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol deleted file mode 100644 index da0dfe8a6..000000000 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ /dev/null @@ -1,127 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Lido - -// SPDX-License-Identifier: GPL-3.0 - -/* See contracts/COMPILERS.md */ -pragma solidity 0.4.24; - -// -// We need to pack four variables into the same 256bit-wide storage slot -// to lower the costs per each staking request. -// -// As a result, slot's memory aligned as follows: -// -// LSB ------------------------------------------------------------------------------> MSB -// 0______________________32______________128_________________________160______________256 -// |______________________|________________|___________________________|________________| -// | prevStakeBlockNumber | prevStakeLimit | maxStakeLimitGrowthBlocks | maxStakeLimit | -// |<----- 32 bits ------>|<-- 96 bits --->|<---------- 32 bits ------>|<--- 96 bits -->| -// -// -// NB: Internal representation conventions: -// -// the `maxStakeLimitGrowthBlocks` field above represented as follows: -// `maxStakeLimitGrowthBlocks` = `maxStakeLimit` / `stakeLimitIncreasePerBlock` -// 32 bits 96 bits 96 bits -// -// -// "staking paused" state is encoded by all fields being zero, -// "staking unlimited" state is encoded by maxStakeLimit being zero and prevStakeBlockNumber being non-zero. -// - -library StakeLimitUtils { - uint256 internal constant MAX_STAKE_LIMIT_OFFSET = 160; - uint256 internal constant MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET = 128; - uint256 internal constant PREV_STAKE_LIMIT_OFFSET = 32; - uint256 internal constant PREV_STAKE_BLOCK_NUMBER_OFFSET = 0; - uint256 internal constant STAKE_LIMIT_PARAMS_MASK = uint256(-1) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; - - /** - * @notice Unpack the slot value into stake limit params and state. - */ - function decodeStakeLimitSlot(uint256 _slotValue) internal pure returns ( - uint256 maxStakeLimit, - uint256 stakeLimitIncPerBlock, - uint256 prevStakeLimit, - uint256 prevStakeBlockNumber - ) { - maxStakeLimit = uint96(_slotValue >> MAX_STAKE_LIMIT_OFFSET); - uint32 growthBlocks = uint32(_slotValue >> MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET); - if (growthBlocks > 0) { - stakeLimitIncPerBlock = maxStakeLimit / growthBlocks; - } - prevStakeLimit = uint96(_slotValue >> PREV_STAKE_LIMIT_OFFSET); - prevStakeBlockNumber = uint32(_slotValue >> PREV_STAKE_BLOCK_NUMBER_OFFSET); - } - - /** - * @notice Pack stake limit params and state into a slot. - */ - function encodeStakeLimitSlot( - uint256 _maxStakeLimit, - uint256 _stakeLimitIncreasePerBlock, - uint256 _prevStakeLimit, - uint256 _prevStakeBlockNumber - ) internal pure returns (uint256 ret) { - require(_maxStakeLimit <= uint96(-1), "TOO_LARGE_MAX_STAKE_LIMIT"); - require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_LIMIT_INCREASE"); - require(_prevStakeLimit <= uint96(-1), "TOO_LARGE_PREV_STAKE_LIMIT"); - require(_prevStakeBlockNumber <= uint32(-1), "TOO_LARGE_BLOCK_NUMBER"); - - require( - (_stakeLimitIncreasePerBlock == 0) - || (_maxStakeLimit / _stakeLimitIncreasePerBlock <= uint32(-1)), - "TOO_SMALL_LIMIT_INCREASE" - ); - - ret = _maxStakeLimit << MAX_STAKE_LIMIT_OFFSET - | _prevStakeLimit << PREV_STAKE_LIMIT_OFFSET - | _prevStakeBlockNumber << PREV_STAKE_BLOCK_NUMBER_OFFSET; - - if (_stakeLimitIncreasePerBlock > 0) { - ret |= (_maxStakeLimit / _stakeLimitIncreasePerBlock) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET; - } - } - - /** - * @notice Calculate stake limit for the current block. - */ - function calculateCurrentStakeLimit(uint256 _slotValue) internal view returns(uint256 limit) { - ( - uint256 maxStakeLimit, - uint256 stakeLimitIncPerBlock, - uint256 prevStakeLimit, - uint256 prevStakeBlockNumber - ) = decodeStakeLimitSlot(_slotValue); - - limit = prevStakeLimit + ((block.number - prevStakeBlockNumber) * stakeLimitIncPerBlock); - if (limit > maxStakeLimit) { - limit = maxStakeLimit; - } - } - - /** - * @notice Write new prev stake limit and current block number - */ - function updatePrevStakeLimit(uint256 _slotValue, uint256 _newPrevLimit) internal view returns(uint256) { - return ( - (_slotValue & STAKE_LIMIT_PARAMS_MASK) - | _newPrevLimit << PREV_STAKE_LIMIT_OFFSET - | block.number << PREV_STAKE_BLOCK_NUMBER_OFFSET - ); - } - - /** - * @notice check if staking is on pause (i.e. slot contains zero value) - */ - function isStakingPaused(uint256 _slotValue) internal pure returns(bool) { - return (_slotValue == 0); - } - - /** - * @notice check if rate limit is set (otherwise staking is unlimited) - */ - function isStakingRateLimited(uint256 _slotValue) internal pure returns(bool) { - return uint96(_slotValue >> MAX_STAKE_LIMIT_OFFSET) != 0; - } -} diff --git a/contracts/0.4.24/lib/StakeRateLimitUtils.sol b/contracts/0.4.24/lib/StakeRateLimitUtils.sol new file mode 100644 index 000000000..0b4c2f47e --- /dev/null +++ b/contracts/0.4.24/lib/StakeRateLimitUtils.sol @@ -0,0 +1,143 @@ +// SPDX-FileCopyrightText: 2022 Lido + +// SPDX-License-Identifier: GPL-3.0 + +/* See contracts/COMPILERS.md */ +pragma solidity 0.4.24; + +import "@aragon/os/contracts/common/UnstructuredStorage.sol"; + +// +// We need to pack four variables into the same 256bit-wide storage slot +// to lower the costs per each staking request. +// +// As a result, slot's memory aligned as follows: +// +// MSB ------------------------------------------------------------------------------> LSB +// 256____________160_________________________128_______________32_____________________ 0 +// |_______________|___________________________|________________|_______________________| +// | maxStakeLimit | maxStakeLimitGrowthBlocks | prevStakeLimit | prevStakeBlockNumber | +// |<-- 96 bits -->|<---------- 32 bits ------>|<-- 96 bits --->|<----- 32 bits ------->| +// +// +// NB: Internal representation conventions: +// +// - the `maxStakeLimitGrowthBlocks` field above represented as follows: +// `maxStakeLimitGrowthBlocks` = `maxStakeLimit` / `stakeLimitIncreasePerBlock` +// 32 bits 96 bits 96 bits +// +// +// - the "staking paused" state is encoded by all fields being zero, +// - the "staking unlimited" state is encoded by maxStakeLimit being zero and prevStakeBlockNumber being non-zero. +// + +// solidity <0.6 doesn't support top-level structs +// using the sentinel library to derive from +library StakeLimitState { + struct Data { + uint32 prevStakeBlockNumber; + uint96 prevStakeLimit; + uint32 maxStakeLimitGrowthBlocks; + uint96 maxStakeLimit; + } +} + +library StakeLimitUnstructuredStorage { + using UnstructuredStorage for bytes32; + + uint256 internal constant MAX_STAKE_LIMIT_OFFSET = 160; + uint256 internal constant MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET = 128; + uint256 internal constant PREV_STAKE_LIMIT_OFFSET = 32; + uint256 internal constant PREV_STAKE_BLOCK_NUMBER_OFFSET = 0; + + function getStorageStakeLimitStruct(bytes32 _position) internal view returns (StakeLimitState.Data memory ret) { + uint256 slotValue = _position.getStorageUint256(); + + ret.prevStakeBlockNumber = uint32(slotValue >> PREV_STAKE_BLOCK_NUMBER_OFFSET); + ret.prevStakeLimit = uint96(slotValue >> PREV_STAKE_LIMIT_OFFSET); + ret.maxStakeLimitGrowthBlocks = uint32(slotValue >> MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET); + ret.maxStakeLimit = uint96(slotValue >> MAX_STAKE_LIMIT_OFFSET); + } + + function setStorageStakeLimitStruct(bytes32 _position, StakeLimitState.Data memory _data) internal { + _position.setStorageUint256( + uint256(_data.prevStakeBlockNumber) << PREV_STAKE_BLOCK_NUMBER_OFFSET + | uint256(_data.prevStakeLimit) << PREV_STAKE_LIMIT_OFFSET + | uint256(_data.maxStakeLimitGrowthBlocks) << MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET + | uint256(_data.maxStakeLimit) << MAX_STAKE_LIMIT_OFFSET + ); + } +} + +library StakeRateLimitUtils { + /** + * @notice Calculate stake limit for the current block. + */ + function calculateCurrentStakeLimit(StakeLimitState.Data memory _data) internal view returns(uint256 limit) { + uint256 stakeLimitIncPerBlock; + if (_data.maxStakeLimitGrowthBlocks > 0) { + stakeLimitIncPerBlock = _data.maxStakeLimit / _data.maxStakeLimitGrowthBlocks; + } + + limit = _data.prevStakeLimit + ((block.number - _data.prevStakeBlockNumber) * stakeLimitIncPerBlock); + if (limit > _data.maxStakeLimit) { + limit = _data.maxStakeLimit; + } + } + + /** + * @notice check if staking is on pause (i.e. slot contains zero value) + */ + function isStakingPaused(StakeLimitState.Data memory _data) internal pure returns(bool) { + return (_data.maxStakeLimit == 0) + && (_data.maxStakeLimitGrowthBlocks == 0) + && (_data.prevStakeBlockNumber == 0) + && (_data.prevStakeLimit == 0); + } + + /** + * @notice check if rate limit is set (otherwise staking is unlimited) + */ + function isStakingRateLimited(StakeLimitState.Data memory _data) internal pure returns(bool) { + return _data.maxStakeLimit != 0; + } + + function resumeStakingWithNewLimit( + StakeLimitState.Data memory _data, + uint256 _maxStakeLimit, + uint256 _stakeLimitIncreasePerBlock + ) internal view returns (StakeLimitState.Data memory) { + require(_maxStakeLimit <= uint96(-1), "TOO_LARGE_MAX_STAKE_LIMIT"); + require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_LIMIT_INCREASE"); + require( + (_stakeLimitIncreasePerBlock == 0) + || (_maxStakeLimit / _stakeLimitIncreasePerBlock <= uint32(-1)), + "TOO_SMALL_LIMIT_INCREASE" + ); + + // if staking was paused or unlimited previously, + // or new limit is lower than previous, then + // reset prev stake limit to max + if ((_data.maxStakeLimit == 0) || (_maxStakeLimit < _data.prevStakeLimit)) { + _data.prevStakeLimit = uint96(_maxStakeLimit); + } + _data.maxStakeLimitGrowthBlocks = _stakeLimitIncreasePerBlock > 0 ? uint32(_maxStakeLimit / _stakeLimitIncreasePerBlock) : 0; + + _data.maxStakeLimit = uint96(_maxStakeLimit); + _data.prevStakeBlockNumber = uint32(block.number); + + return _data; + } + + function updatePrevStakeLimit( + StakeLimitState.Data memory _data, + uint256 _newPrevLimit + ) internal view returns (StakeLimitState.Data memory) { + assert(_newPrevLimit <= uint96(-1)); + + _data.prevStakeLimit = uint96(_newPrevLimit); + _data.prevStakeBlockNumber = uint32(block.number); + + return _data; + } +} diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol index e39e94e96..369b0cfde 100644 --- a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -5,42 +5,69 @@ pragma solidity 0.4.24; -import "../lib/StakeLimitUtils.sol"; +import "../lib/StakeRateLimitUtils.sol"; contract StakeLimitUtilsMock { - using StakeLimitUtils for uint256; + using UnstructuredStorage for bytes32; + using StakeLimitUnstructuredStorage for bytes32; + using StakeRateLimitUtils for StakeLimitState.Data; - function decodeStakeLimitSlot(uint256 _slotValue) public pure returns ( - uint256 maxStakeLimit, - uint256 stakeLimitIncPerBlock, - uint256 prevStakeLimit, - uint256 prevStakeBlockNumber + bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("abcdef"); + + function getStorageStakeRateLimit() public view returns ( + uint32 prevStakeBlockNumber, + uint96 prevStakeLimit, + uint32 maxStakeLimitGrowthBlocks, + uint96 maxStakeLimit ) { - return _slotValue.decodeStakeLimitSlot(); + StakeLimitState.Data memory data = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + + prevStakeBlockNumber = data.prevStakeBlockNumber; + prevStakeLimit = data.prevStakeLimit; + maxStakeLimitGrowthBlocks = data.maxStakeLimitGrowthBlocks; + maxStakeLimit = data.maxStakeLimit; } - function encodeStakeLimitSlot( - uint256 _maxStakeLimit, - uint256 _stakeLimitIncPerBlock, - uint256 _prevStakeLimit, - uint256 _prevStakeBlockNumber - ) public pure returns (uint256 ret) { - return StakeLimitUtils.encodeStakeLimitSlot(_maxStakeLimit, _stakeLimitIncPerBlock, _prevStakeLimit, _prevStakeBlockNumber); + function setStorageStakeRateLimitStruct( + uint32 _prevStakeBlockNumber, + uint96 _prevStakeLimit, + uint32 _maxStakeLimitGrowthBlocks, + uint96 _maxStakeLimit + ) public returns (uint256 ret) { + StakeLimitState.Data memory data; + data.prevStakeBlockNumber = _prevStakeBlockNumber; + data.prevStakeLimit = _prevStakeLimit; + data.maxStakeLimitGrowthBlocks = _maxStakeLimitGrowthBlocks; + data.maxStakeLimit = _maxStakeLimit; + + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct(data); + + return STAKE_LIMIT_POSITION.getStorageUint256(); } - function calculateCurrentStakeLimit(uint256 _slotValue) public view returns(uint256 limit) { - return _slotValue.calculateCurrentStakeLimit(); - } + function calculateCurrentStakeLimit() public view returns(uint256 limit) { + return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().calculateCurrentStakeLimit(); + } + + function isStakingPaused(uint256 _slotValue) public view returns(bool) { + return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingPaused(); + } - function updatePrevStakeLimit(uint256 _slotValue, uint256 _newPrevLimit) public view returns(uint256) { - return _slotValue.updatePrevStakeLimit( _newPrevLimit); + function isStakingRateLimited(uint256 _slotValue) public view returns(bool) { + return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingRateLimited(); } - function isStakingPaused(uint256 _slotValue) public pure returns(bool) { - return _slotValue.isStakingPaused(); + function resumeStakingWithNewLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( + _maxStakeLimit, _stakeLimitIncreasePerBlock + ) + ); } - function isStakingRateLimited(uint256 _slotValue) public pure returns(bool) { - return _slotValue.isStakingRateLimited(); + function updatePrevStakeLimit(uint256 _newPrevLimit) internal view returns (StakeLimitState.Data memory) { + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().updatePrevStakeLimit(_newPrevLimit) + ); } } diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 1ccfd8f97..596b3a8a9 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -567,7 +567,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) isStakingLimitApplied, currentStakeLimit, maxStakeLimit, - stakeLimitIncPerBlock, + maxStakeLimitGrowthBlocks, prevStakeLimit, prevStakeBlockNumber } = await app.getStakeLimitFullInfo()) @@ -576,7 +576,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assert.equal(isStakingLimitApplied, expectedMaxStakeLimit.toString() !== bn(0).toString()) assertBn(currentStakeLimit, expectedCurrentStakeLimit) assertBn(maxStakeLimit, expectedMaxStakeLimit) - assertBn(stakeLimitIncPerBlock, expectedLimitIncrease) + assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) } it('stake pause/unlimited resume works', async () => { diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index 2186e24a1..ea49f46eb 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -26,7 +26,7 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') // 32 bits 96 bits 96 bits // -contract('StakingLimits', () => { +contract.skip('StakingLimits', () => { let limits before('deploy base app', async () => { From 9a87bac07a770e449fb54b4c71513f5b1c0936a2 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 15:08:12 +0000 Subject: [PATCH 128/159] chore: update staking limit test comments --- test/0.4.24/staking-limit.test.js | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index ea49f46eb..ffbd39ca6 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -14,17 +14,23 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') // // As a result, slot's memory aligned as follows: // -// LSB ------------------------------------------------------------------------------> MSB -// 0______________________32______________128_________________________160______________256 -// |______________________|________________|___________________________|________________| -// | prevStakeBlockNumber | prevStakeLimit | maxStakeLimitGrowthBlocks | maxStakeLimit | -// |<----- 32 bits ------>|<-- 96 bits --->|<---------- 32 bits ------>|<--- 96 bits -->| +// MSB ------------------------------------------------------------------------------> LSB +// 256____________160_________________________128_______________32_____________________ 0 +// |_______________|___________________________|________________|_______________________| +// | maxStakeLimit | maxStakeLimitGrowthBlocks | prevStakeLimit | prevStakeBlockNumber | +// |<-- 96 bits -->|<---------- 32 bits ------>|<-- 96 bits --->|<----- 32 bits ------->| // // -// NB: we represent `maxStakeLimitGrowthBlocks` as follows: +// NB: Internal representation conventions: +// +// - the `maxStakeLimitGrowthBlocks` field above represented as follows: // `maxStakeLimitGrowthBlocks` = `maxStakeLimit` / `stakeLimitIncreasePerBlock` // 32 bits 96 bits 96 bits // +// +// - the "staking paused" state is encoded by all fields being zero, +// - the "staking unlimited" state is encoded by maxStakeLimit being zero and prevStakeBlockNumber being non-zero. +// contract.skip('StakingLimits', () => { let limits From b016e8511ef8251ded5986829b5a6c3421cee5ac Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 17:31:12 +0000 Subject: [PATCH 129/159] feat: rename mev/tx rewards to EL rewards globally Update the terms everywhere. --- README.md | 1 - apps/lido/app/src/App.js | 6 +- apps/lido/app/src/index.js | 2 +- apps/lido/app/src/script.js | 6 +- contracts/0.4.24/Lido.sol | 94 +++++----- contracts/0.4.24/interfaces/ILido.sol | 20 +-- ...ult.sol => ILidoExecLayerRewardsVault.sol} | 6 +- contracts/0.4.24/template/LidoTemplate.sol | 4 +- ...ault.sol => LidoExecLayerRewardsVault.sol} | 14 +- deployed-goerli.json | 3 - deployed-kiln.json | 5 +- deployed-kintsugi.json | 4 +- scripts/multisig/12-check-dao.js | 4 +- ... => 26-deploy-exec-layer-rewards-vault.js} | 8 +- ...tain-deployed-exec-layer-rewards-vault.js} | 8 +- .../28-vote-merge-ready-first-pack-upgrade.js | 36 ++-- test/0.4.24/lido.test.js | 96 +++++----- ...lt.js => lido-exec-layer-rewards-vault.js} | 79 ++++---- test/deposit.test.js | 6 +- ...xecution_layer_rewards_after_the_merge.js} | 168 +++++++++--------- test/scenario/helpers/deploy.js | 12 +- 21 files changed, 295 insertions(+), 287 deletions(-) rename contracts/0.4.24/interfaces/{ILidoMevTxFeeVault.sol => ILidoExecLayerRewardsVault.sol} (57%) rename contracts/0.8.9/{LidoMevTxFeeVault.sol => LidoExecLayerRewardsVault.sol} (88%) rename scripts/multisig/{26-deploy-mev-vault.js => 26-deploy-exec-layer-rewards-vault.js} (81%) rename scripts/multisig/{27-obtain-deployed-mev-vault.js => 27-obtain-deployed-exec-layer-rewards-vault.js} (89%) rename test/0.8.9/{lido-mev-tx-fee-vault.js => lido-exec-layer-rewards-vault.js} (62%) rename test/scenario/{mev_tx_fee_after_the_merge.js => execution_layer_rewards_after_the_merge.js} (81%) diff --git a/README.md b/README.md index 83c1e5e69..175dcc4b3 100644 --- a/README.md +++ b/README.md @@ -72,7 +72,6 @@ The contract also works as a wrapper that accepts stETH tokens and mints wstETH * Oracle: [`0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB`](https://goerli.etherscan.io/address/0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB) (proxy) * WstETH token: [`0x1643e812ae58766192cf7d2cf9567df2c37e9b7f`](https://goerli.etherscan.io/address/0x1643e812ae58766192cf7d2cf9567df2c37e9b7f) * Deposit Security Module: [`0xEd23AD3EA5Fb9d10e7371Caef1b141AD1C23A80c`](https://goerli.etherscan.io/address/0xEd23AD3EA5Fb9d10e7371Caef1b141AD1C23A80c) -* MEV Transaction Fee Vault [`0xece7301B3aeEC2b2B6C41a55dE831D47c205AaCC`](https://goerli.etherscan.io/address/0xece7301B3aeEC2b2B6C41a55dE831D47c205AaCC) * Aragon Voting: [`0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db`](https://goerli.etherscan.io/address/0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db) (proxy) * Aragon Token Manager: [`0xDfe76d11b365f5e0023343A367f0b311701B3bc1`](https://goerli.etherscan.io/address/0xDfe76d11b365f5e0023343A367f0b311701B3bc1) (proxy) * Aragon Finance: [`0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179`](https://goerli.etherscan.io/address/0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179) (proxy) diff --git a/apps/lido/app/src/App.js b/apps/lido/app/src/App.js index 7433d99f3..a139bd983 100644 --- a/apps/lido/app/src/App.js +++ b/apps/lido/app/src/App.js @@ -85,7 +85,7 @@ export default function App() { nodeOperatorsRegistry, depositContract, oracle, - mevTxFeeVault, + execLayerRewardsVault, // operators, // treasury, // insuranceFund, @@ -251,8 +251,8 @@ export default function App() { content: , }, { - label: 'MEV and Transaction Fees Vault', - content: , + label: 'Execution layer rewards Vault', + content: , }, ] }, [ diff --git a/apps/lido/app/src/index.js b/apps/lido/app/src/index.js index 79147276e..15bfcc5bd 100644 --- a/apps/lido/app/src/index.js +++ b/apps/lido/app/src/index.js @@ -22,7 +22,7 @@ const defaultState = { nodeOperatorsRegistry: defaultValue, depositContract: defaultValue, oracle: defaultValue, - mevTxFeeVault: defaultValue, + execLayerRewardsVault: defaultValue, operators: defaultValue, treasury: defaultValue, insuranceFund: defaultValue, diff --git a/apps/lido/app/src/script.js b/apps/lido/app/src/script.js index 3b1f725e5..96b9c860c 100644 --- a/apps/lido/app/src/script.js +++ b/apps/lido/app/src/script.js @@ -62,7 +62,7 @@ function initializeState() { nodeOperatorsRegistry: await getNodeOperatorsRegistry(), depositContract: await getDepositContract(), oracle: await getOracle(), - mevTxFeeVault: await getMevTxFeeVault(), + execLayerRewardsVault: await getExecLayerRewardsVault(), // operators: await getOperators(), // treasury: await getTreasury(), // insuranceFund: await getInsuranceFund(), @@ -108,8 +108,8 @@ function getOracle() { return app.call('getOracle').toPromise() } -function getMevTxFeeVault() { - return app.call('getMevTxFeeVault').toPromise() +function getExecLayerRewardsVault() { + return app.call('getExecLayerRewardsVault').toPromise() } // async function getOperators() { diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index fd48120a4..af96d7236 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -13,7 +13,7 @@ import "solidity-bytes-utils/contracts/BytesLib.sol"; import "./interfaces/ILido.sol"; import "./interfaces/INodeOperatorsRegistry.sol"; import "./interfaces/IDepositContract.sol"; -import "./interfaces/ILidoMevTxFeeVault.sol"; +import "./interfaces/ILidoExecLayerRewardsVault.sol"; import "./StETH.sol"; @@ -60,8 +60,10 @@ contract Lido is ILido, StETH, AragonApp { bytes32 constant public MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE"); bytes32 constant public BURN_ROLE = keccak256("BURN_ROLE"); bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE"); - bytes32 constant public SET_MEV_TX_FEE_VAULT_ROLE = keccak256("SET_MEV_TX_FEE_VAULT_ROLE"); - bytes32 constant public SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE = keccak256("SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE"); + bytes32 constant public SET_EXEC_LAYER_REWARDS_VAULT_ROLE = keccak256("SET_EXEC_LAYER_REWARDS_VAULT_ROLE"); + bytes32 constant public SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE = keccak256( + "SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE" + ); uint256 constant public PUBKEY_LENGTH = 48; uint256 constant public WITHDRAWAL_CREDENTIALS_LENGTH = 32; @@ -85,7 +87,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 internal constant NODE_OPERATORS_REGISTRY_POSITION = keccak256("lido.Lido.nodeOperatorsRegistry"); bytes32 internal constant TREASURY_POSITION = keccak256("lido.Lido.treasury"); bytes32 internal constant INSURANCE_FUND_POSITION = keccak256("lido.Lido.insuranceFund"); - bytes32 internal constant MEV_TX_FEE_VAULT_POSITION = keccak256("lido.Lido.mevTxFeeVault"); + bytes32 internal constant EXEC_LAYER_REWARDS_VAULT_POSITION = keccak256("lido.Lido.execLayerRewardsVault"); /// @dev storage slot position of the staking rate limit structure bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("lido.Lido.stakeLimit"); @@ -98,12 +100,12 @@ contract Lido is ILido, StETH, AragonApp { /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); - /// @dev percent in basis points of total pooled ether allowed to withdraw from MevTxFeeVault per LidoOracle report - bytes32 internal constant MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS = keccak256("lido.Lido.mevTxFeeWithdrawalLimitPoints"); + /// @dev percent in basis points of total pooled ether allowed to withdraw from ExecLayerRewardsVault per LidoOracle report + bytes32 internal constant EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION = keccak256("lido.Lido.execLayerRewardsWithdrawalLimitPoints"); - /// @dev Just a counter of total amount of MEV and transaction rewards received by Lido contract + /// @dev Just a counter of total amount of execurion layer rewards received by Lido contract /// Not used in the logic - bytes32 internal constant TOTAL_MEV_TX_FEE_COLLECTED_POSITION = keccak256("lido.Lido.totalMevTxFeeCollected"); + bytes32 internal constant TOTAL_EXEC_LAYER_REWARDS_COLLECTED_POSITION = keccak256("lido.Lido.totalExecLayerRewardsCollected"); /// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.Lido.withdrawalCredentials"); @@ -273,17 +275,17 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice A payable function for Mev Tx Fee rewards. Can be funded only by LidoMevTxFeeVault contract + * @notice A payable function for execution layer rewards. Can be funded only by ExecLayerRewardsVault contract * @dev We need a separate payable function because funds received by default payable function * are considered as funds submitted for minting stETH */ - function receiveMevTxFee() external payable { - require(msg.sender == MEV_TX_FEE_VAULT_POSITION.getStorageAddress()); + function receiveExecLayerRewards() external payable { + require(msg.sender == EXEC_LAYER_REWARDS_VAULT_POSITION.getStorageAddress()); - TOTAL_MEV_TX_FEE_COLLECTED_POSITION.setStorageUint256( - TOTAL_MEV_TX_FEE_COLLECTED_POSITION.getStorageUint256().add(msg.value)); + TOTAL_EXEC_LAYER_REWARDS_COLLECTED_POSITION.setStorageUint256( + TOTAL_EXEC_LAYER_REWARDS_COLLECTED_POSITION.getStorageUint256().add(msg.value)); - emit MevTxFeeReceived(msg.value); + emit ExecLayerRewardsReceived(msg.value); } /** @@ -413,26 +415,26 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Sets given address as the address of LidoMevTxFeeVault contract - * @param _mevTxFeeVault MEV and Tx Fees Vault contract address + * @dev Sets given address as the address of LidoExecLayerRewardsVault contract + * @param _execLayerRewardsVault Execution layer rewards vault contract address */ - function setMevTxFeeVault(address _mevTxFeeVault) external { - _auth(SET_MEV_TX_FEE_VAULT_ROLE); + function setExecLayerRewardsVault(address _execLayerRewardsVault) external { + _auth(SET_EXEC_LAYER_REWARDS_VAULT_ROLE); - MEV_TX_FEE_VAULT_POSITION.setStorageAddress(_mevTxFeeVault); + EXEC_LAYER_REWARDS_VAULT_POSITION.setStorageAddress(_execLayerRewardsVault); - emit LidoMevTxFeeVaultSet(_mevTxFeeVault); + emit LidoExecLayerRewardsVaultSet(_execLayerRewardsVault); } /** - * @dev Sets limit to amount of ETH to withdraw per LidoOracle report + * @dev Sets limit to amount of ETH to withdraw from execution layer rewards vault per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function setMevTxFeeWithdrawalLimit(uint16 _limitPoints) external { - _auth(SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE); + function setExecLayerRewardsWithdrawalLimit(uint16 _limitPoints) external { + _auth(SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE); - _setBPValue(MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS, _limitPoints); - emit MevTxFeeWithdrawalLimitSet(_limitPoints); + _setBPValue(EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION, _limitPoints); + emit ExecLayerRewardsWithdrawalLimitSet(_limitPoints); } /** @@ -447,7 +449,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Updates beacon states, collects rewards from MevTxFeeVault and distributes all rewards if beacon balance increased + * @notice Updates beacon states, collects rewards from LidoExecLayerRewardsVault and distributes all rewards if beacon balance increased * @dev periodically called by the Oracle contract * @param _beaconValidators number of Lido's keys in the beacon state * @param _beaconBalance summarized balance of Lido-controlled keys in wei @@ -475,20 +477,20 @@ contract Lido is ILido, StETH, AragonApp { BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); - // If LidoMevTxFeeVault address is not set just do as if there were no mevTxFee rewards at all + // If LidoExecLayerRewardsVault address is not set just do as if there were no execution layer rewards at all // Otherwise withdraw all rewards and put them to the buffer - // Thus, MEV tx fees are handled the same way as beacon rewards + // Thus, execution layer rewards are handled the same way as beacon rewards - uint256 mevRewards; - address mevVaultAddress = getMevTxFeeVault(); + uint256 execLayerRewards; + address execLayerRewardsVaultAddress = getExecLayerRewardsVault(); - if (mevVaultAddress != address(0)) { - mevRewards = ILidoMevTxFeeVault(mevVaultAddress).withdrawRewards( - (_getTotalPooledEther() * MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256()) / TOTAL_BASIS_POINTS + if (execLayerRewardsVaultAddress != address(0)) { + execLayerRewards = ILidoExecLayerRewardsVault(execLayerRewardsVaultAddress).withdrawRewards( + (_getTotalPooledEther() * EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS ); - if (mevRewards != 0) { - BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(mevRewards)); + if (execLayerRewards != 0) { + BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(execLayerRewards)); } } @@ -497,7 +499,7 @@ contract Lido is ILido, StETH, AragonApp { // See ADR #3 for details: https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); - distributeRewards(rewards.add(mevRewards)); + distributeRewards(rewards.add(execLayerRewards)); } } @@ -584,21 +586,21 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Get total amount of MEV and transaction fees collected to Lido contract - * @dev Ether got through LidoMevTxFeeVault is kept on this contract's balance the same way + * @notice Get total amount of execution level rewards collected to Lido contract + * @dev Ether got through LidoExecLayerRewardsVault is kept on this contract's balance the same way * as other buffered Ether is kept (until it gets deposited) - * @return uint256 of funds received as MEV and Transaction fees in wei + * @return uint256 of funds received as execution layer rewards (in wei) */ - function getTotalMevTxFeeCollected() external view returns (uint256) { - return TOTAL_MEV_TX_FEE_COLLECTED_POSITION.getStorageUint256(); + function getTotalExecLayerRewardsCollected() external view returns (uint256) { + return TOTAL_EXEC_LAYER_REWARDS_COLLECTED_POSITION.getStorageUint256(); } /** * @notice Get limit in basis points to amount of ETH to withdraw per LidoOracle report * @return uint256 limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function getMevTxFeeWithdrawalLimitPoints() external view returns (uint256) { - return MEV_TX_FEE_WITHDRAWAL_LIMIT_POINTS.getStorageUint256(); + function getExecLayerRewardsWithdrawalLimitPoints() external view returns (uint256) { + return EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256(); } /** @@ -650,10 +652,10 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns address of the contract set as LidoMevTxFeeVault + * @notice Returns address of the contract set as LidoExecLayerRewardsVault */ - function getMevTxFeeVault() public view returns (address) { - return MEV_TX_FEE_VAULT_POSITION.getStorageAddress(); + function getExecLayerRewardsVault() public view returns (address) { + return EXEC_LAYER_REWARDS_VAULT_POSITION.getStorageAddress(); } /** diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index a2116a2c2..0f37e81ca 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -128,17 +128,17 @@ interface ILido { event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints); /** - * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract + * @notice A payable function supposed to be funded only by LidoExecLayerRewardsVault contract * @dev We need a separate function because funds received by default payable function * are considered as funds submitted by a user for staking */ - function receiveMevTxFee() external payable; + function receiveExecLayerRewards() external payable; - // The amount of ETH withdrawn from LidoMevTxFeeVault contract to Lido contract - event MevTxFeeReceived(uint256 amount); + // The amount of ETH withdrawn from LidoExecLayerRewardsVault contract to Lido contract + event ExecLayerRewardsReceived(uint256 amount); - // Percent in basis points of total pooled ether allowed to withdraw from MevTxFeeVault per LidoOracle report - event MevTxFeeWithdrawalLimitSet(uint256 limitPoints); + // Percent in basis points of total pooled ether allowed to withdraw from LidoExecLayerRewardsVault per LidoOracle report + event ExecLayerRewardsWithdrawalLimitSet(uint256 limitPoints); /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` @@ -156,12 +156,12 @@ interface ILido { event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); /** - * @dev Sets given address as the address of LidoMevTxFeeVault contract - * @param _mevTxFeeVault MEV and Tx Fees Vault contract address + * @dev Sets given address as the address of LidoExecLayerRewardsVault contract + * @param _execLayerRewardsVault Execution layer rewards vault contract address */ - function setMevTxFeeVault(address _mevTxFeeVault) external; + function setExecLayerRewardsVault(address _execLayerRewardsVault) external; - event LidoMevTxFeeVaultSet(address mevTxFeeVault); + event LidoExecLayerRewardsVaultSet(address execLayerRewardsVault); /** * @notice Ether on the ETH 2.0 side reported by the oracle diff --git a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol b/contracts/0.4.24/interfaces/ILidoExecLayerRewardsVault.sol similarity index 57% rename from contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol rename to contracts/0.4.24/interfaces/ILidoExecLayerRewardsVault.sol index c33ad2aee..caf1faa7d 100644 --- a/contracts/0.4.24/interfaces/ILidoMevTxFeeVault.sol +++ b/contracts/0.4.24/interfaces/ILidoExecLayerRewardsVault.sol @@ -5,12 +5,12 @@ pragma solidity 0.4.24; -interface ILidoMevTxFeeVault { +interface ILidoExecLayerRewardsVault { /** - * @notice Withdraw all accumulated rewards to Lido contract + * @notice Withdraw all accumulated execution layer rewards to Lido contract * @param _maxAmount Max amount of ETH to withdraw - * @return amount uint256 of funds received as MEV and transaction fees in wei + * @return amount uint256 of funds received as execution layer rewards (in wei) */ function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount); } diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 3334f47a1..5f31c6a58 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -657,8 +657,8 @@ contract LidoTemplate is IsContract { perms[5] = _state.lido.RESUME_ROLE(); perms[6] = _state.lido.STAKING_PAUSE_ROLE(); perms[7] = _state.lido.STAKING_RESUME_ROLE(); - perms[8] = _state.lido.SET_MEV_TX_FEE_VAULT_ROLE(); - perms[9] = _state.lido.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(); + perms[8] = _state.lido.SET_EXEC_LAYER_REWARDS_VAULT_ROLE(); + perms[9] = _state.lido.SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE(); for (i = 0; i < 10; ++i) { _createPermissionForVoting(acl, _state.lido, perms[i], voting); diff --git a/contracts/0.8.9/LidoMevTxFeeVault.sol b/contracts/0.8.9/LidoExecLayerRewardsVault.sol similarity index 88% rename from contracts/0.8.9/LidoMevTxFeeVault.sol rename to contracts/0.8.9/LidoExecLayerRewardsVault.sol index 08b7b5a58..5f68faa15 100644 --- a/contracts/0.8.9/LidoMevTxFeeVault.sol +++ b/contracts/0.8.9/LidoExecLayerRewardsVault.sol @@ -11,21 +11,21 @@ import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; interface ILido { /** - * @notice A payable function supposed to be funded only by LidoMevTxFeeVault contract + * @notice A payable function supposed to be funded only by LidoExecLayerRewardsVault contract * @dev We need a separate function because funds received by default payable function * will go through entire deposit algorithm */ - function receiveMevTxFee() external payable; + function receiveExecLayerRewards() external payable; } /** -* @title A vault for temporary storage of MEV and transaction fees +* @title A vault for temporary storage of execution layer rewards (MEV and tx priority fee) * * These vault replenishments happen continuously through a day, while withdrawals * happen much less often, only on LidoOracle beacon balance reports */ -contract LidoMevTxFeeVault { +contract LidoExecLayerRewardsVault { using SafeERC20 for IERC20; address public immutable LIDO; @@ -67,7 +67,7 @@ contract LidoMevTxFeeVault { /** * @notice Allows the contract to receive ETH - * @dev MEV rewards may be sent as plain ETH transfers + * @dev execution layer rewards may be sent as plain ETH transfers */ receive() external payable { // no-op @@ -77,7 +77,7 @@ contract LidoMevTxFeeVault { * @notice Withdraw all accumulated rewards to Lido contract * @dev Can be called only by the Lido contract * @param _maxAmount Max amount of ETH to withdraw - * @return amount uint256 of funds received as MEV and transaction fees in wei + * @return amount uint256 of funds received as execution layer rewards (in wei) */ function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount) { require(msg.sender == LIDO, "ONLY_LIDO_CAN_WITHDRAW"); @@ -85,7 +85,7 @@ contract LidoMevTxFeeVault { uint256 balance = address(this).balance; amount = (balance > _maxAmount) ? _maxAmount : balance; if (amount > 0) { - ILido(LIDO).receiveMevTxFee{value: amount}(); + ILido(LIDO).receiveExecLayerRewards{value: amount}(); } return amount; } diff --git a/deployed-goerli.json b/deployed-goerli.json index ccb649c10..9fdc801c6 100644 --- a/deployed-goerli.json +++ b/deployed-goerli.json @@ -185,7 +185,4 @@ 4 ], "selfOwnedStETHBurnerAddress": "0xf6a64DcB06Ef7eB1ee94aDfD7D10ACB44D9A9888", - "mevTxFeeVaultDeployTx": "0x0cff10cc3830ff645032875eb895d4db007ce7cad9cfcd2668015f905ceb338f", - "mevTxFeeVaultAddress": "0xece7301B3aeEC2b2B6C41a55dE831D47c205AaCC", - "mevTxFeeRewardsEmulator": "0x636ebc9dd60ef85bbaf2abb0bad789676aac12d0" } diff --git a/deployed-kiln.json b/deployed-kiln.json index 5dafc1060..f8ca73cb9 100644 --- a/deployed-kiln.json +++ b/deployed-kiln.json @@ -226,8 +226,5 @@ "fullName": "aragon-voting.lidopm.eth", "id": "0x0abcd104777321a82b010357f20887d61247493d89d2e987ff57bcecbde00e1e", "proxyAddress": "0x84633bf8e3AE2508a81F09F46a47774F5aFD16B9" - }, - "mevTxFeeVaultDeployTx": "0x044a1aa15ed85bc58c9c27da391e74e3a21d0d5e47b9a657adb2b871627e88bd", - "mevTxFeeVaultAddress": "0xe3e01f9E940dDec242C3fdD7bbb855c3770bF999", - "mevTxFeeVaultUpgradeTx": "0xf6034ccfd75c67ce2392b6ddb1a7e6b2f3cced87cdf4747a86c295e857cd1c41" + } } diff --git a/deployed-kintsugi.json b/deployed-kintsugi.json index e39918bda..07328eccf 100644 --- a/deployed-kintsugi.json +++ b/deployed-kintsugi.json @@ -250,7 +250,5 @@ }, "lidoBaseDeployTx": "0x99f814a256ddedc706bb4b699fee8106ee3d12d2fa78da6d79544efd398aeac4", "oracleBaseDeployTx": "0x195866659be58414ac4a8095c5f6c1b1edc8f24baa48384bc4baa4c77c555af3", - "nodeOperatorsRegistryBaseDeployTx": "0x8e16572b1b3fe7ea5b8d67d2af9176566169b706f470d4cbd4fd1d256e339a25", - "mevTxFeeVaultDeployTx": "0x3d5375b7a19c6c8391fe65b3dbaabce4d5cc95cdeb910230c516ff434f686889", - "mevTxFeeVaultAddress": "0xcDB3885e9680105c02FcC740B184eA98Ef14af42" + "nodeOperatorsRegistryBaseDeployTx": "0x8e16572b1b3fe7ea5b8d67d2af9176566169b706f470d4cbd4fd1d256e339a25" } diff --git a/scripts/multisig/12-check-dao.js b/scripts/multisig/12-check-dao.js index 6ec321866..63c678516 100644 --- a/scripts/multisig/12-check-dao.js +++ b/scripts/multisig/12-check-dao.js @@ -576,8 +576,8 @@ async function assertDaoPermissions({ kernel, lido, oracle, nopsRegistry, agent, 'SET_INSURANCE_FUND', 'STAKING_PAUSE_ROLE', 'STAKING_RESUME_ROLE', - 'SET_MEV_TX_FEE_VAULT_ROLE', - 'SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE' + 'SET_EXEC_LAYER_REWARDS_VAULT_ROLE', + 'SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE' ], grantee: voting } diff --git a/scripts/multisig/26-deploy-mev-vault.js b/scripts/multisig/26-deploy-exec-layer-rewards-vault.js similarity index 81% rename from scripts/multisig/26-deploy-mev-vault.js rename to scripts/multisig/26-deploy-exec-layer-rewards-vault.js index 966b32dd8..fdebb15d7 100644 --- a/scripts/multisig/26-deploy-mev-vault.js +++ b/scripts/multisig/26-deploy-exec-layer-rewards-vault.js @@ -8,8 +8,8 @@ const { APP_NAMES } = require('./constants') const DEPLOYER = process.env.DEPLOYER || '' const REQUIRED_NET_STATE = ['daoInitialSettings', 'depositorParams', `app:${APP_NAMES.LIDO}`, `app:${APP_NAMES.NODE_OPERATORS_REGISTRY}`] -async function deployMevTxFeeVault({ web3, artifacts }) { - const appArtifact = 'LidoMevTxFeeVault' +async function deployExecLayerRewardsVault({ web3, artifacts }) { + const appArtifact = 'LidoExecLayerRewardsVault' const netId = await web3.eth.net.getId() logWideSplitter() @@ -27,10 +27,10 @@ async function deployMevTxFeeVault({ web3, artifacts }) { logSplitter() const args = [lidoAddress, treasuryAddr] - await saveDeployTx(appArtifact, `tx-26-deploy-mev-vault.json`, { + await saveDeployTx(appArtifact, `tx-26-deploy-exec-layer-rewards-vault.json`, { arguments: args, from: DEPLOYER || state.multisigAddress }) } -module.exports = runOrWrapScript(deployMevTxFeeVault, module) +module.exports = runOrWrapScript(deployExecLayerRewardsVault, module) diff --git a/scripts/multisig/27-obtain-deployed-mev-vault.js b/scripts/multisig/27-obtain-deployed-exec-layer-rewards-vault.js similarity index 89% rename from scripts/multisig/27-obtain-deployed-mev-vault.js rename to scripts/multisig/27-obtain-deployed-exec-layer-rewards-vault.js index 7a8281aad..8e240f637 100644 --- a/scripts/multisig/27-obtain-deployed-mev-vault.js +++ b/scripts/multisig/27-obtain-deployed-exec-layer-rewards-vault.js @@ -7,13 +7,13 @@ const { readNetworkState, persistNetworkState, assertRequiredNetworkState } = re const { APP_NAMES, APP_ARTIFACTS } = require('./constants') const REQUIRED_NET_STATE = [ - 'mevTxFeeVaultDeployTx', + 'execLayerRewardsVaultDeployTx', `app:${APP_NAMES.LIDO}`, ] async function obtainInstance({ web3, artifacts }) { // convert dash-ed appName to camel case-d - const appArtifact = 'LidoMevTxFeeVault' + const appArtifact = 'LidoExecLayerRewardsVault' const netId = await web3.eth.net.getId() logWideSplitter() @@ -23,12 +23,12 @@ async function obtainInstance({ web3, artifacts }) { assertRequiredNetworkState(state, REQUIRED_NET_STATE) logHeader(`${appArtifact} app base`) - const vault = await useOrGetDeployed(appArtifact, null, state.mevTxFeeVaultDeployTx) + const vault = await useOrGetDeployed(appArtifact, null, state.execLayerRewardsVaultDeployTx) log(`Checking...`) const lidoAddress = state[`app:${APP_NAMES.LIDO}`].proxyAddress await assertAddresses({ lidoAddress }, vault, appArtifact) persistNetworkState(network.name, netId, state, { - mevTxFeeVaultAddress: vault.address + execLayerRewardsVaultAddress: vault.address }) } diff --git a/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js index 576bafb69..27ce8ee63 100644 --- a/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js +++ b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js @@ -29,7 +29,7 @@ async function createVoting({ web3, artifacts }) { const state = readNetworkState(network.name, netId) assertRequiredNetworkState(state, REQUIRED_NET_STATE.concat([ - 'app:lido', 'app:node-operators-registry', 'app:oracle', 'mevTxFeeVaultAddress'])) + 'app:lido', 'app:node-operators-registry', 'app:oracle', 'execLayerRewardsVaultAddress'])) logSplitter() @@ -45,9 +45,9 @@ async function createVoting({ web3, artifacts }) { const oracleAddress = state[`app:oracle`].proxyAddress const lido = await artifacts.require('Lido').at(lidoAddress) const oracle = await artifacts.require('LidoOracle').at(oracleAddress) - const mevTxFeeVaultAddress = state.mevTxFeeVaultAddress + const execLayerRewardsVaultAddress = state.execLayerRewardsVaultAddress - const mevTxFeeWithdrawalLimitPoints = 2 // see https://github.com/lidofinance/lido-dao/issues/405 + const execLayerRewardsWithdrawalLimitPoints = 2 // see https://github.com/lidofinance/lido-dao/issues/405 const dailyStakingLimit = ETH(150000) const stakeLimitIncreasePerBlock = calcStakeLimitIncreasePerBlock(dailyStakingLimit) @@ -71,7 +71,7 @@ async function createVoting({ web3, artifacts }) { log(`Voting address:`, yl(votingAddress)) log(`Kernel:`, yl(kernel.address)) log(`ACL:`, yl(acl.address)) - log(`mevTxFeeWithdrawalLimitPoints: `, yl(mevTxFeeWithdrawalLimitPoints)) + log(`execLayerRewardsWithdrawalLimitPoints: `, yl(execLayerRewardsWithdrawalLimitPoints)) log(`dailyStakeLimit: `, yl(dailyStakingLimit)) log(`stakeLimitIncreasePerBlock: `, yl(stakeLimitIncreasePerBlock)) @@ -83,21 +83,21 @@ async function createVoting({ web3, artifacts }) { const oracleUpgradeCallData = await buildUpgradeTransaction('oracle', state, ens, kernel) - const grantSetMevVaultRoleCallData = await createGrantRoleForLidoAppCallData('SET_MEV_TX_FEE_VAULT_ROLE') - const grantSetMevWithdrawalLimitRoleCallData = await createGrantRoleForLidoAppCallData('SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE') + const grantSetExecLayerRewardsVaultRoleCallData = await createGrantRoleForLidoAppCallData('SET_EXEC_LAYER_REWARDS_VAULT_ROLE') + const grantSetExecLayerRewardsWithdrawalLimitRoleCallData = await createGrantRoleForLidoAppCallData('SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE') const grantResumeRoleCallData = await createGrantRoleForLidoAppCallData('RESUME_ROLE') const grantStakingPauseRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_PAUSE_ROLE') const grantStakingResumeRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_RESUME_ROLE') const grantManageProtocolContractsRoleCallData = await createGrantRoleForLidoAppCallData('MANAGE_PROTOCOL_CONTRACTS_ROLE') - const setMevTxFeeVaultCallData = { + const setExecLayerRewardsVaultCallData = { to: lidoAddress, - calldata: await lido.contract.methods.setMevTxFeeVault(mevTxFeeVaultAddress).encodeABI() + calldata: await lido.contract.methods.setExecLayerRewardsVault(execLayerRewardsVaultAddress).encodeABI() } - const setMevWithdrawalLimitCallData = { + const setExecLayerRewardsWithdrawalLimitCallData = { to: lidoAddress, - calldata: await lido.contract.methods.setMevTxFeeWithdrawalLimit(mevTxFeeWithdrawalLimitPoints).encodeABI() + calldata: await lido.contract.methods.setExecLayerRewardsWithdrawalLimit(execLayerRewardsWithdrawalLimitPoints).encodeABI() } const updateOracleVersionToV3CallData = { @@ -116,14 +116,14 @@ async function createVoting({ web3, artifacts }) { ...nodeOperatorsRegistryUpgradeCallData, ...oracleUpgradeCallData, updateOracleVersionToV3CallData, - grantSetMevVaultRoleCallData, - grantSetMevWithdrawalLimitRoleCallData, + grantSetExecLayerRewardsVaultRoleCallData, + grantSetExecLAyerRewardsWithdrawalLimitRoleCallData, grantResumeRoleCallData, grantStakingPauseRoleCallData, grantStakingResumeRoleCallData, grantManageProtocolContractsRoleCallData, - setMevTxFeeVaultCallData, - setMevWithdrawalLimitCallData, + setExecLayerRewardsVaultCallData, + setExecLayerRewardsWithdrawalLimitCallData, unpauseStakingCallData, ]) @@ -144,14 +144,14 @@ async function createVoting({ web3, artifacts }) { 5) Publishing new implementation in oracle app APM repo 6) Updating implementation of oracle app with new one 7) Call Oracle's finalizeUpgrade_v3() to update internal version counter -8) Grant role SET_MEV_TX_FEE_VAULT_ROLE to voting -9) Grant role SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE to voting +8) Grant role SET_EXEC_LAYER_REWARDS_VAULT_ROLE to voting +9) Grant role SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE to voting 10) Grant role RESUME_ROLE to voting 11) Grant role STAKING_PAUSE_ROLE to voting 12) Grant role STAKING_RESUME_ROLE to voting 13) Grant role MANAGE_PROTOCOL_CONTRACTS_ROLE to voting -14) Set deployed MevTxFeeVault to Lido contract -15) Set MevTxFee Withdrawal Limit to ${mevTxFeeWithdrawalLimitPoints} basis points +14) Set deployed LidoExecLayerRewardsVault to Lido contract +15) Set Execution Layer rewards withdrawal limit to ${execLayerRewardsWithdrawalLimitPoints} basis points 16) Unpause staking setting daily limit to ${fromE18ToString(dailyStakingLimit)}` await saveCallTxData(votingDesc, tokenManager, 'forward', txName, { diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 596b3a8a9..860533a85 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -11,7 +11,7 @@ const { getEthBalance, formatStEth: formamtStEth, formatBN } = require('../helpe const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const Lido = artifacts.require('LidoMock.sol') -const MevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') +const ExecLayerRewardsVault = artifacts.require('LidoExecLayerRewardsVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') @@ -57,7 +57,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let appBase, nodeOperatorsRegistryBase, app, oracle, depositContract, operators let treasuryAddr, insuranceAddr let dao, acl - let mevVault, rewarder + let execLayerRewardsVault, rewarder before('deploy base app', async () => { // Deploy the app's base contract. @@ -88,8 +88,10 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.BURN_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_PROTOCOL_CONTRACTS_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_EXEC_LAYER_REWARDS_VAULT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, { + from: appManager + }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) @@ -115,14 +117,14 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.setPool(app.address) await depositContract.reset() - mevVault = await MevTxFeeVault.new(app.address, treasuryAddr) - rewarder = await RewardEmulatorMock.new(mevVault.address) - let receipt = await app.setMevTxFeeVault(mevVault.address, { from: voting }) - assertEvent(receipt, 'LidoMevTxFeeVaultSet', { expectedArgs: { mevTxFeeVault: mevVault.address } }) + execLayerRewardsVault = await ExecLayerRewardsVault.new(app.address, treasuryAddr) + rewarder = await RewardEmulatorMock.new(execLayerRewardsVault.address) + let receipt = await app.setExecLayerRewardsVault(execLayerRewardsVault.address, { from: voting }) + assertEvent(receipt, 'LidoExecLayerRewardsVaultSet', { expectedArgs: { execLayerRewardsVault: execLayerRewardsVault.address } }) - const mevTxFeeWithdrawalLimitPoints = 3 - receipt = await app.setMevTxFeeWithdrawalLimit(mevTxFeeWithdrawalLimitPoints, { from: voting }) - assertEvent(receipt, 'MevTxFeeWithdrawalLimitSet', { expectedArgs: { limitPoints: mevTxFeeWithdrawalLimitPoints } }) + const execLayerRewardsWithdrawalLimitPoints = 3 + receipt = await app.setExecLayerRewardsWithdrawalLimit(execLayerRewardsWithdrawalLimitPoints, { from: voting }) + assertEvent(receipt, 'ExecLayerRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: execLayerRewardsWithdrawalLimitPoints } }) }) const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { @@ -154,7 +156,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) } const logLidoState = async () => { - const mevVaultBalance = await getEthBalance(mevVault.address) + const execLayerRewardsVaultBalance = await getEthBalance(execLayerRewardsVault.address) const lidoBalance = await getEthBalance(app.address) const lidoTotalSupply = formatBN(await app.totalSupply()) const lidoTotalPooledEther = formatBN(await app.getTotalPooledEther()) @@ -166,7 +168,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const beaconBalance = formatEther(beaconStat.beaconBalance) console.log({ - mevVaultBalance, + execLayerRewardsVaultBalance, lidoBalance, lidoTotalSupply, lidoTotalPooledEther, @@ -191,7 +193,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) console.log() } - const setupNodeOperatorsForMevTxFeeVaultTests = async (userAddress, initialDepositAmount) => { + const setupNodeOperatorsForExecLayerRewardsVaultTests = async (userAddress, initialDepositAmount) => { await app.setFee(1000, { from: voting }) // 10% await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) @@ -214,75 +216,75 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await app.methods['depositBufferedEther()']({ from: depositor }) } - it('MEV distribution works when zero rewards reported', async () => { + it('Execution layer rewards distribution works when zero rewards reported', async () => { const depositAmount = 32 - const mevAmount = depositAmount / TOTAL_BASIS_POINTS + const execLayerRewards = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = 0 - await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForExecLayerRewardsVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(mevAmount) }) + await rewarder.reward({ from: user1, value: ETH(execLayerRewards) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(mevAmount)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + mevAmount)) - assertBn(await app.getTotalMevTxFeeCollected(), ETH(mevAmount)) + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + execLayerRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(execLayerRewards)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + execLayerRewards)) + assertBn(await app.getTotalExecLayerRewardsCollected(), ETH(execLayerRewards)) }) - it('MEV distribution works when negative rewards reported', async () => { + it('Execution layer rewards distribution works when negative rewards reported', async () => { const depositAmount = 32 - const mevAmount = depositAmount / TOTAL_BASIS_POINTS + const execLayerRewards = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = -2 - await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForExecLayerRewardsVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(mevAmount) }) + await rewarder.reward({ from: user1, value: ETH(execLayerRewards) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(mevAmount)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + mevAmount + beaconRewards)) - assertBn(await app.getTotalMevTxFeeCollected(), ETH(mevAmount)) + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + execLayerRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(execLayerRewards)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + execLayerRewards + beaconRewards)) + assertBn(await app.getTotalExecLayerRewardsCollected(), ETH(execLayerRewards)) }) - it('MEV distribution works when positive rewards reported', async () => { + it('Execution layer rewards distribution works when positive rewards reported', async () => { const depositAmount = 32 - const mevAmount = depositAmount / TOTAL_BASIS_POINTS + const execLayerRewards = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = 3 - await setupNodeOperatorsForMevTxFeeVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForExecLayerRewardsVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(mevAmount) }) + await rewarder.reward({ from: user1, value: ETH(execLayerRewards) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) const protocolFeePoints = await app.getFee() const shareOfRewardsForStakers = (TOTAL_BASIS_POINTS - protocolFeePoints) / TOTAL_BASIS_POINTS - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + mevAmount + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(mevAmount)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + shareOfRewardsForStakers * (mevAmount + beaconRewards))) - assertBn(await app.getTotalMevTxFeeCollected(), ETH(mevAmount)) + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + execLayerRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(execLayerRewards)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + shareOfRewardsForStakers * (execLayerRewards + beaconRewards))) + assertBn(await app.getTotalExecLayerRewardsCollected(), ETH(execLayerRewards)) }) - it('Attempt to set invalid MEV Tx Fee withdrawal limit', async () => { - const initialValue = await app.getMevTxFeeWithdrawalLimitPoints() + it('Attempt to set invalid execution layer rewards withdrawal limit', async () => { + const initialValue = await app.getExecLayerRewardsWithdrawalLimitPoints() - assertEvent(await app.setMevTxFeeWithdrawalLimit(1, { from: voting }), 'MevTxFeeWithdrawalLimitSet', { + assertEvent(await app.setExecLayerRewardsWithdrawalLimit(1, { from: voting }), 'ExecLayerRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: 1 } }) - await assertNoEvent(app.setMevTxFeeWithdrawalLimit(1, { from: voting }), 'MevTxFeeWithdrawalLimitSet') + await assertNoEvent(app.setExecLayerRewardsWithdrawalLimit(1, { from: voting }), 'ExecLayerRewardsWithdrawalLimitSet') - await app.setMevTxFeeWithdrawalLimit(10000, { from: voting }) - await assertRevert(app.setMevTxFeeWithdrawalLimit(10001, { from: voting }), 'VALUE_OVER_100_PERCENT') + await app.setExecLayerRewardsWithdrawalLimit(10000, { from: voting }) + await assertRevert(app.setExecLayerRewardsWithdrawalLimit(10001, { from: voting }), 'VALUE_OVER_100_PERCENT') - await app.setMevTxFeeWithdrawalLimit(initialValue, { from: voting }) + await app.setExecLayerRewardsWithdrawalLimit(initialValue, { from: voting }) - // unable to receive mev tx fee from arbitrary account - assertRevert(app.receiveMevTxFee({ from: user1, value: ETH(1) })) + // unable to receive execution layer rewards from arbitrary account + assertRevert(app.receiveExecLayerRewards({ from: user1, value: ETH(1) })) }) it('setFee works', async () => { diff --git a/test/0.8.9/lido-mev-tx-fee-vault.js b/test/0.8.9/lido-exec-layer-rewards-vault.js similarity index 62% rename from test/0.8.9/lido-mev-tx-fee-vault.js rename to test/0.8.9/lido-exec-layer-rewards-vault.js index 486e45ceb..184d452a4 100644 --- a/test/0.8.9/lido-mev-tx-fee-vault.js +++ b/test/0.8.9/lido-exec-layer-rewards-vault.js @@ -4,7 +4,7 @@ const { newDao, newApp } = require('../0.4.24/helpers/dao') const { assert } = require('chai') -const LidoMevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') +const LidoExecLayerRewardsVault = artifacts.require('LidoExecLayerRewardsVault.sol') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') @@ -20,8 +20,8 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') const stETH = ETH const stETHShares = ETH -contract('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { - let oracle, lido, mevVault +contract('LidoExecLayerRewardsVault', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { + let oracle, lido, execLayerRewardsVault let treasuryAddr let dao, acl, operators @@ -54,26 +54,28 @@ contract('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, another await oracle.setPool(lido.address) await depositContract.reset() - mevVault = await LidoMevTxFeeVault.new(lido.address, treasuryAddr, { from: deployer }) + execLayerRewardsVault = await LidoExecLayerRewardsVault.new(lido.address, treasuryAddr, { from: deployer }) }) - it('Addresses which are not Lido contract cannot withdraw from MEV vault', async () => { - await assertRevert(mevVault.withdrawRewards(12345, { from: anotherAccount }), 'ONLY_LIDO_CAN_WITHDRAW') - await assertRevert(mevVault.withdrawRewards(12345, { from: deployer }), 'ONLY_LIDO_CAN_WITHDRAW') - await assertRevert(mevVault.withdrawRewards(12345, { from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') + it('Addresses which are not Lido contract cannot withdraw from execution layer rewards vault', async () => { + await assertRevert(execLayerRewardsVault.withdrawRewards(12345, { from: anotherAccount }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(execLayerRewardsVault.withdrawRewards(12345, { from: deployer }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(execLayerRewardsVault.withdrawRewards(12345, { from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') }) - it('MEV Tx Fee Vault can receive Ether by plain transfers (no call data)', async () => { - const before = +(await web3.eth.getBalance(mevVault.address)).toString() + it('Execution layer rewards vault can receive Ether by plain transfers (no call data)', async () => { + const before = +(await web3.eth.getBalance(execLayerRewardsVault.address)).toString() const amount = 0.02 - await web3.eth.sendTransaction({ to: mevVault.address, from: anotherAccount, value: ETH(amount) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(before + amount)) + await web3.eth.sendTransaction({ to: execLayerRewardsVault.address, from: anotherAccount, value: ETH(amount) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(before + amount)) }) - it('MEV Tx Fee Vault refuses to receive Ether by transfers with call data', async () => { - const before = +(await web3.eth.getBalance(mevVault.address)).toString() + it('Execution layer rewards vault refuses to receive Ether by transfers with call data', async () => { + const before = +(await web3.eth.getBalance(execLayerRewardsVault.address)).toString() const amount = 0.02 - await assertRevert(web3.eth.sendTransaction({ to: mevVault.address, from: anotherAccount, value: ETH(amount), data: '0x12345678' })) + await assertRevert( + web3.eth.sendTransaction({ to: execLayerRewardsVault.address, from: anotherAccount, value: ETH(amount), data: '0x12345678' }) + ) }) describe('Recover ERC20 / ERC721', () => { @@ -108,11 +110,11 @@ contract('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, another }) it(`can't recover zero ERC20 amount`, async () => { - assertRevert(mevVault.recoverERC20(mockERC20Token.address, bn(0)), `ZERO_RECOVERY_AMOUNT`) + assertRevert(execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(0)), `ZERO_RECOVERY_AMOUNT`) }) it(`can't recover zero-address ERC20`, async () => { - assertRevert(mevVault.recoverERC20(ZERO_ADDRESS, bn(10))) + assertRevert(execLayerRewardsVault.recoverERC20(ZERO_ADDRESS, bn(10))) }) it(`can't recover stETH by recoverERC20`, async () => { @@ -122,74 +124,77 @@ contract('LidoMevTxFeeVault', ([appManager, voting, deployer, depositor, another await web3.eth.sendTransaction({ from: anotherAccount, to: lido.address, value: ETH(10) }) // check 10 stETH minted on balance assertBn(await lido.balanceOf(anotherAccount), stETH(10)) - // transfer 5 stETH to the mevVault account - await lido.transfer(mevVault.address, stETH(5), { from: anotherAccount }) + // transfer 5 stETH to the execLayerRewardsVault account + await lido.transfer(execLayerRewardsVault.address, stETH(5), { from: anotherAccount }) assertBn(await lido.balanceOf(anotherAccount), stETH(5)) - assertBn(await lido.balanceOf(mevVault.address), stETH(5)) + assertBn(await lido.balanceOf(execLayerRewardsVault.address), stETH(5)) }) it(`recover some accidentally sent ERC20`, async () => { - // distribute deployer's balance among anotherAccount and mevVault + // distribute deployer's balance among anotherAccount and execLayerRewardsVault await mockERC20Token.transfer(anotherAccount, bn(400000), { from: deployer }) - await mockERC20Token.transfer(mevVault.address, bn(600000), { from: deployer }) + await mockERC20Token.transfer(execLayerRewardsVault.address, bn(600000), { from: deployer }) // check the resulted state assertBn(await mockERC20Token.balanceOf(deployer), bn(0)) assertBn(await mockERC20Token.balanceOf(anotherAccount), bn(400000)) - assertBn(await mockERC20Token.balanceOf(mevVault.address), bn(600000)) + assertBn(await mockERC20Token.balanceOf(execLayerRewardsVault.address), bn(600000)) // recover ERC20 - const firstReceipt = await mevVault.recoverERC20(mockERC20Token.address, bn(100000), { from: deployer }) + const firstReceipt = await execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(100000), { from: deployer }) assertEvent(firstReceipt, `ERC20Recovered`, { expectedArgs: { requestedBy: deployer, token: mockERC20Token.address, amount: bn(100000) } }) - const secondReceipt = await mevVault.recoverERC20(mockERC20Token.address, bn(400000), { from: anotherAccount }) + const secondReceipt = await execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(400000), { from: anotherAccount }) assertEvent(secondReceipt, `ERC20Recovered`, { expectedArgs: { requestedBy: anotherAccount, token: mockERC20Token.address, amount: bn(400000) } }) // check balances again - assertBn(await mockERC20Token.balanceOf(mevVault.address), bn(100000)) + assertBn(await mockERC20Token.balanceOf(execLayerRewardsVault.address), bn(100000)) assertBn(await mockERC20Token.balanceOf(treasuryAddr), bn(500000)) assertBn(await mockERC20Token.balanceOf(deployer), bn(0)) assertBn(await mockERC20Token.balanceOf(anotherAccount), bn(400000)) // recover last portion - const lastReceipt = await mevVault.recoverERC20(mockERC20Token.address, bn(100000), { from: anotherAccount }) + const lastReceipt = await execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(100000), { from: anotherAccount }) assertEvent(lastReceipt, `ERC20Recovered`, { expectedArgs: { requestedBy: anotherAccount, token: mockERC20Token.address, amount: bn(100000) } }) // balance is zero already, have to be reverted - assertRevert(mevVault.recoverERC20(mockERC20Token.address, bn(1), { from: deployer }), `ERC20: transfer amount exceeds balance`) + assertRevert( + execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(1), { from: deployer }), + `ERC20: transfer amount exceeds balance` + ) }) it(`can't recover zero-address ERC721(NFT)`, async () => { - assertRevert(mevVault.recoverERC721(ZERO_ADDRESS, 0)) + assertRevert(execLayerRewardsVault.recoverERC721(ZERO_ADDRESS, 0)) }) it(`recover some accidentally sent NFTs`, async () => { - // send nft1 to anotherAccount and nft2 to the mevVault address + // send nft1 to anotherAccount and nft2 to the execLayerRewardsVault address await mockNFT.transferFrom(deployer, anotherAccount, nft1, { from: deployer }) - await mockNFT.transferFrom(deployer, mevVault.address, nft2, { from: deployer }) + await mockNFT.transferFrom(deployer, execLayerRewardsVault.address, nft2, { from: deployer }) // check the new holders' rights assertBn(await mockNFT.balanceOf(deployer), bn(0)) assertBn(await mockNFT.balanceOf(anotherAccount), bn(1)) - assertBn(await mockNFT.balanceOf(mevVault.address), bn(1)) + assertBn(await mockNFT.balanceOf(execLayerRewardsVault.address), bn(1)) // recover nft2 should work - const receiptNfc2 = await mevVault.recoverERC721(mockNFT.address, nft2, { from: anotherAccount }) + const receiptNfc2 = await execLayerRewardsVault.recoverERC721(mockNFT.address, nft2, { from: anotherAccount }) assertEvent(receiptNfc2, `ERC721Recovered`, { expectedArgs: { requestedBy: anotherAccount, token: mockNFT.address, tokenId: nft2 } }) // but nft1 recovery should revert - assertRevert(mevVault.recoverERC721(mockNFT.address, nft1), `ERC721: transfer caller is not owner nor approved`) + assertRevert(execLayerRewardsVault.recoverERC721(mockNFT.address, nft1), `ERC721: transfer caller is not owner nor approved`) - // send nft1 to mevVault and recover it - await mockNFT.transferFrom(anotherAccount, mevVault.address, nft1, { from: anotherAccount }) - const receiptNft1 = await mevVault.recoverERC721(mockNFT.address, nft1, { from: deployer }) + // send nft1 to execLayerRewardsVault and recover it + await mockNFT.transferFrom(anotherAccount, execLayerRewardsVault.address, nft1, { from: anotherAccount }) + const receiptNft1 = await execLayerRewardsVault.recoverERC721(mockNFT.address, nft1, { from: deployer }) assertEvent(receiptNft1, `ERC721Recovered`, { expectedArgs: { requestedBy: deployer, token: mockNFT.address, tokenId: nft1 } }) diff --git a/test/deposit.test.js b/test/deposit.test.js index 42377f15d..3c6742a69 100644 --- a/test/deposit.test.js +++ b/test/deposit.test.js @@ -83,8 +83,10 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use await acl.createPermission(voting, app.address, await app.RESUME_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_FEE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_VAULT_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_EXEC_LAYER_REWARDS_VAULT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, { + from: appManager + }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) diff --git a/test/scenario/mev_tx_fee_after_the_merge.js b/test/scenario/execution_layer_rewards_after_the_merge.js similarity index 81% rename from test/scenario/mev_tx_fee_after_the_merge.js rename to test/scenario/execution_layer_rewards_after_the_merge.js index ef60fcbe2..24ae9b366 100644 --- a/test/scenario/mev_tx_fee_after_the_merge.js +++ b/test/scenario/execution_layer_rewards_after_the_merge.js @@ -10,7 +10,7 @@ const { signDepositData } = require('../0.8.9/helpers/signatures') const { waitBlocks } = require('../helpers/blockchain') const addresses = require('@aragon/contract-helpers-test/src/addresses') -const LidoMevTxFeeVault = artifacts.require('LidoMevTxFeeVault.sol') +const LidoExecLayerRewardsVault = artifacts.require('LidoExecLayerRewardsVault.sol') const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') @@ -32,15 +32,15 @@ contract('Lido: merge acceptance', (addresses) => { user3, // unrelated address nobody, - // MEV source - userMev + // Execution layer rewards source + userExecLayerRewards ] = addresses let pool, nodeOperatorRegistry, token let oracleMock, depositContractMock let treasuryAddr, insuranceAddr, guardians let depositSecurityModule, depositRoot - let rewarder, mevVault + let rewarder, execLayerRewardsVault // Total fee is 1% const totalFeePoints = 0.01 * TOTAL_BASIS_POINTS @@ -103,16 +103,16 @@ contract('Lido: merge acceptance', (addresses) => { depositRoot = await depositContractMock.get_deposit_root() - mevVault = await LidoMevTxFeeVault.new(pool.address, treasuryAddr) - await pool.setMevTxFeeVault(mevVault.address, { from: voting }) + execLayerRewardsVault = await LidoExecLayerRewardsVault.new(pool.address, treasuryAddr) + await pool.setExecLayerRewardsVault(execLayerRewardsVault.address, { from: voting }) // At first go through tests assuming there is no withdrawal limit - await pool.setMevTxFeeWithdrawalLimit(TOTAL_BASIS_POINTS, { from: voting }) + await pool.setExecLayerRewardsWithdrawalLimit(TOTAL_BASIS_POINTS, { from: voting }) - rewarder = await RewardEmulatorMock.new(mevVault.address) + rewarder = await RewardEmulatorMock.new(execLayerRewardsVault.address) assertBn(await web3.eth.getBalance(rewarder.address), ETH(0), 'rewarder balance') - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Fee and its distribution are in basis points, 10000 corresponding to 100% @@ -346,12 +346,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.totalSupply(), tokens(3 + 30 + 64), 'token total supply') }) - it('collect 9 ETH MEV and tx rewards to the vault', async () => { - await rewarder.reward({ from: userMev, value: ETH(9) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(9), 'MEV vault balance') + it('collect 9 ETH execution layer rewards to the vault', async () => { + await rewarder.reward({ from: userExecLayerRewards, value: ETH(9) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(9), 'Execution layer rewards vault balance') }) - it('the oracle reports balance increase on Ethereum2 side (+32 ETH) and claims collected MEV and tx fee rewards (+9 ETH)', async () => { + it('the oracle reports balance increase on Ethereum2 side (+32 ETH) and claims collected execution layer rewards (+9 ETH)', async () => { const epoch = 100 // Total shares are equal to deposited eth before ratio change and fee mint @@ -368,8 +368,8 @@ contract('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(96)) - // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') + // Execution layer rewards just claimed + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares increased because fee minted (fee shares added) // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -378,12 +378,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(newTotalShares, new BN('97289047169125663202'), 'total shares') - const mevAmount = 9 + const execLayerRewards = 9 // Total pooled Ether increased const newTotalPooledEther = await pool.getTotalPooledEther() - assertBn(newTotalPooledEther, ETH(33 + 96 + mevAmount), 'total pooled ether') + assertBn(newTotalPooledEther, ETH(33 + 96 + execLayerRewards), 'total pooled ether') // Ether2 stat reported by the pool changed correspondingly @@ -391,13 +391,13 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') assertBn(ether2Stat.beaconBalance, ETH(96), 'remote ether2') - // Buffered Ether amount changed on MEV/tx rewards - assertBn(await pool.getBufferedEther(), ETH(33 + mevAmount), 'buffered ether') + // Buffered Ether amount changed on execution layer rewards + assertBn(await pool.getBufferedEther(), ETH(33 + execLayerRewards), 'buffered ether') // New tokens was minted to distribute fee - assertBn(await token.totalSupply(), tokens(129 + mevAmount), 'token total supply') + assertBn(await token.totalSupply(), tokens(129 + execLayerRewards), 'token total supply') - const reward = toBN(ETH(96 - 64 + mevAmount)) + const reward = toBN(ETH(96 - 64 + execLayerRewards)) const mintedAmount = new BN(totalFeePoints).mul(reward).divn(TOTAL_BASIS_POINTS) // Token user balances increased @@ -437,15 +437,15 @@ contract('Lido: merge acceptance', (addresses) => { ) }) - it('collect another 7 ETH MEV and tx rewards to the vault', async () => { - await rewarder.reward({ from: userMev, value: ETH(2) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(2), 'MEV vault balance') + it('collect another 7 ETH execution layer rewards to the vault', async () => { + await rewarder.reward({ from: userExecLayerRewards, value: ETH(2) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(2), 'Execution layer rewards vault balance') - await rewarder.reward({ from: userMev, value: ETH(5) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(7), 'MEV vault balance') + await rewarder.reward({ from: userExecLayerRewards, value: ETH(5) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(7), 'Execution layer rewards vault balance') }) - it('the oracle reports same balance on Ethereum2 side (+0 ETH) and claims collected MEV and tx fee rewards (+7 ETH)', async () => { + it('the oracle reports same balance on Ethereum2 side (+0 ETH) and claims collected execution layer rewards (+7 ETH)', async () => { const epoch = 101 // Total shares are equal to deposited eth before ratio change and fee mint @@ -460,8 +460,8 @@ contract('Lido: merge acceptance', (addresses) => { // Reporting the same balance as it was before (96ETH => 96ETH) await oracleMock.reportBeacon(epoch, 2, ETH(96)) - // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') + // Execution layer rewards just claimed + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -480,7 +480,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') assertBn(ether2Stat.beaconBalance, ETH(96), 'remote ether2') - // Buffered Ether amount changed on MEV/tx rewards + // Buffered Ether amount changed on execution layer rewards assertBn(await pool.getBufferedEther(), ETH(42 + 7), 'buffered ether') assertBn(await token.totalSupply(), tokens(145), 'token total supply') @@ -501,12 +501,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(nodeOperator2.address), new BN('107699275362318839'), 'operator_2 tokens') }) - it('collect another 5 ETH MEV and tx rewards to the vault', async () => { - await rewarder.reward({ from: userMev, value: ETH(5) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(5), 'MEV vault balance') + it('collect another 5 ETH execution layer rewards to the vault', async () => { + await rewarder.reward({ from: userExecLayerRewards, value: ETH(5) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(5), 'Execution layer rewards vault balance') }) - it('the oracle reports loss on Ethereum2 side (-2 ETH) and claims collected MEV and tx fee rewards (+5 ETH)', async () => { + it('the oracle reports loss on Ethereum2 side (-2 ETH) and claims collected execution layer rewards (+5 ETH)', async () => { const epoch = 102 // Total shares are equal to deposited eth before ratio change and fee mint @@ -521,8 +521,8 @@ contract('Lido: merge acceptance', (addresses) => { // Reporting balance decrease (96ETH => 94ETH) await oracleMock.reportBeacon(epoch, 2, ETH(94)) - // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') + // Execution layer rewards just claimed + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -538,7 +538,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') assertBn(ether2Stat.beaconBalance, ETH(94), 'remote ether2') - // Buffered Ether amount changed on MEV/tx rewards + // Buffered Ether amount changed on execution layer rewards assertBn(await pool.getBufferedEther(), ETH(49 + 5), 'buffered ether') assertBn(await token.totalSupply(), tokens(145 + 3), 'token total supply') @@ -559,12 +559,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(nodeOperator2.address), new BN('109927536231884057'), 'operator_2 tokens') }) - it('collect another 3 ETH MEV and tx rewards to the vault', async () => { - await rewarder.reward({ from: userMev, value: ETH(3) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(3), 'MEV vault balance') + it('collect another 3 ETH execution layer rewards to the vault', async () => { + await rewarder.reward({ from: userExecLayerRewards, value: ETH(3) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(3), 'Execution layer rewards vault balance') }) - it('the oracle reports loss on Ethereum2 side (-3 ETH) and claims collected MEV and tx fee rewards (+3 ETH)', async () => { + it('the oracle reports loss on Ethereum2 side (-3 ETH) and claims collected execution layer rewards (+3 ETH)', async () => { const epoch = 103 // Total shares are equal to deposited eth before ratio change and fee mint @@ -579,8 +579,8 @@ contract('Lido: merge acceptance', (addresses) => { // Reporting balance decrease (94ETH => 91ETH) await oracleMock.reportBeacon(epoch, 2, ETH(91)) - // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') + // Execution layer rewards just claimed + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -596,7 +596,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') assertBn(ether2Stat.beaconBalance, ETH(91), 'remote ether2') - // Buffered Ether amount changed on MEV/tx rewards + // Buffered Ether amount changed on execution layer rewards assertBn(await pool.getBufferedEther(), ETH(54 + 3), 'buffered ether') assertBn(await token.totalSupply(), tokens(148), 'token total supply') @@ -615,12 +615,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(nodeOperator2.address), new BN('109927536231884057'), 'operator_2 tokens') }) - it('collect another 2 ETH MEV and tx rewards to the vault', async () => { - await rewarder.reward({ from: userMev, value: ETH(2) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(2), 'MEV vault balance') + it('collect another 2 ETH execution layer rewards to the vault', async () => { + await rewarder.reward({ from: userExecLayerRewards, value: ETH(2) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(2), 'Execution layer rewards vault balance') }) - it('the oracle reports loss on Ethereum2 side (-8 ETH) and claims collected MEV and tx fee rewards (+2 ETH)', async () => { + it('the oracle reports loss on Ethereum2 side (-8 ETH) and claims collected execution layer rewards (+2 ETH)', async () => { const epoch = 104 // Total shares are equal to deposited eth before ratio change and fee mint @@ -635,8 +635,8 @@ contract('Lido: merge acceptance', (addresses) => { // Reporting balance decrease (91ETH => 83ETH) await oracleMock.reportBeacon(epoch, 2, ETH(83)) - // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') + // Execution layer rewards just claimed + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -652,7 +652,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') assertBn(ether2Stat.beaconBalance, ETH(83), 'remote ether2') - // Buffered Ether amount changed on MEV/tx rewards + // Buffered Ether amount changed on execution layer rewards assertBn(await pool.getBufferedEther(), ETH(57 + 2), 'buffered ether') assertBn(await token.totalSupply(), tokens(142), 'token total supply') @@ -669,12 +669,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(await token.balanceOf(nodeOperator2.address), new BN('105471014492753622'), 'operator_2 tokens') }) - it('collect another 3 ETH MEV and tx rewards to the vault', async () => { - await rewarder.reward({ from: userMev, value: ETH(3) }) - assertBn(await web3.eth.getBalance(mevVault.address), ETH(3), 'MEV vault balance') + it('collect another 3 ETH execution layer rewards to the vault', async () => { + await rewarder.reward({ from: userExecLayerRewards, value: ETH(3) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(3), 'Execution layer vault balance') }) - it('the oracle reports balance increase on Ethereum2 side (+2 ETH) and claims collected MEV and tx fee rewards (+3 ETH)', async () => { + it('the oracle reports balance increase on Ethereum2 side (+2 ETH) and claims collected execution layer rewards (+3 ETH)', async () => { const epoch = 105 // Total shares are equal to deposited eth before ratio change and fee mint @@ -689,8 +689,8 @@ contract('Lido: merge acceptance', (addresses) => { // Reporting balance increase (83ETH => 85ETH) await oracleMock.reportBeacon(epoch, 2, ETH(85)) - // Mev and tx rewards just claimed - assertBn(await web3.eth.getBalance(mevVault.address), ETH(0), 'MEV vault balance') + // Execution layer rewards just claimed + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares increased because fee minted (fee shares added) // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -707,7 +707,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') assertBn(ether2Stat.beaconBalance, ETH(85), 'remote ether2') - // Buffered Ether amount changed on MEV/tx rewards + // Buffered Ether amount changed on execution layer rewards assertBn(await pool.getBufferedEther(), ETH(59 + 3), 'buffered ether') assertBn(await token.totalSupply(), tokens(142 + 5), 'token total supply') @@ -734,7 +734,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn((await token.balanceOf(nodeOperator2.address)).divn(10), new BN('12164764492753623'), 'operator_2 tokens') }) - it('collect 0.1 ETH mevTxFee rewards to mevVault and withdraw it entirely by means of multiple oracle reports (+1 ETH)', async () => { + it('collect 0.1 ETH execution layer rewards to execLayerRewardsVault and withdraw it entirely by means of multiple oracle reports (+1 ETH)', async () => { const toNum = (bn) => { return +bn.toString() } @@ -746,7 +746,7 @@ contract('Lido: merge acceptance', (addresses) => { } // Specify different withdrawal limits for a few epochs to test different values - const getMevWithdrawalLimitFromEpoch = (_epoch) => { + const getExecLayerRewardsWithdrawalLimitFromEpoch = (_epoch) => { if (_epoch === 106) { return 2 } else if (_epoch === 107) { @@ -756,52 +756,58 @@ contract('Lido: merge acceptance', (addresses) => { } } - const mevAmount = toE18(0.1) - await rewarder.reward({ from: userMev, value: fromNum(mevAmount) }) - assertBn(await web3.eth.getBalance(mevVault.address), fromNum(mevAmount), 'MEV vault balance') + const execLayerRewards = toE18(0.1) + await rewarder.reward({ from: userExecLayerRewards, value: fromNum(execLayerRewards) }) + assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), fromNum(execLayerRewards), 'Execution layer rewards vault balance') let epoch = 106 let lastBeaconBalance = toE18(85) - await pool.setMevTxFeeWithdrawalLimit(getMevWithdrawalLimitFromEpoch(epoch), { from: voting }) + await pool.setExecLayerRewardsWithdrawalLimit(getExecLayerRewardsWithdrawalLimitFromEpoch(epoch), { from: voting }) - let mevWithdrawalLimitPoints = toNum(await pool.getMevTxFeeWithdrawalLimitPoints()) - let mevVaultBalance = toNum(await web3.eth.getBalance(mevVault.address)) + let execLayerRewardsWithdrawalLimitPoints = toNum(await pool.getExecLayerRewardsWithdrawalLimitPoints()) + let execLayerRewardsVaultBalance = toNum(await web3.eth.getBalance(execLayerRewardsVault.address)) let totalPooledEther = toNum(await pool.getTotalPooledEther()) let bufferedEther = toNum(await pool.getBufferedEther()) let totalSupply = toNum(await pool.totalSupply()) const beaconBalanceInc = toE18(1) - let mevWithdrawn = 0 + let execLayerRewardsWithdrawn = 0 - // Do multiple oracle reports to withdraw all ETH from MEV Tx Fee Vault - while (mevVaultBalance > 0) { - const mevWithdrawalLimit = getMevWithdrawalLimitFromEpoch(epoch) - await pool.setMevTxFeeWithdrawalLimit(mevWithdrawalLimit, { from: voting }) - mevWithdrawalLimitPoints = toNum(await pool.getMevTxFeeWithdrawalLimitPoints()) + // Do multiple oracle reports to withdraw all ETH from execution layer rewards vault + while (execLayerRewardsVaultBalance > 0) { + const execLayerRewardsWithdrawalLimit = getExecLayerRewardsWithdrawalLimitFromEpoch(epoch) + await pool.setExecLayerRewardsWithdrawalLimit(execLayerRewardsWithdrawalLimit, { from: voting }) + execLayerRewardsWithdrawalLimitPoints = toNum(await pool.getExecLayerRewardsWithdrawalLimitPoints()) - const maxMevAmountPerWithdrawal = Math.floor(((totalPooledEther + beaconBalanceInc) * mevWithdrawalLimitPoints) / TOTAL_BASIS_POINTS) - const mevToWithdraw = Math.min(maxMevAmountPerWithdrawal, mevVaultBalance) + const maxExecLayerRewardsAmountPerWithdrawal = Math.floor( + ((totalPooledEther + beaconBalanceInc) * execLayerRewardsWithdrawalLimitPoints) / TOTAL_BASIS_POINTS + ) + const execLayerRewardsToWithdraw = Math.min(maxExecLayerRewardsAmountPerWithdrawal, execLayerRewardsVaultBalance) // Reporting balance increase await oracleMock.reportBeacon(epoch, 2, fromNum(lastBeaconBalance + beaconBalanceInc)) - assertBn(await web3.eth.getBalance(mevVault.address), mevVaultBalance - mevToWithdraw, 'MEV vault balance') + assertBn( + await web3.eth.getBalance(execLayerRewardsVault.address), + execLayerRewardsVaultBalance - execLayerRewardsToWithdraw, + 'Execution layer rewards vault balance' + ) - assertBn(await pool.getTotalPooledEther(), totalPooledEther + beaconBalanceInc + mevToWithdraw, 'total pooled ether') + assertBn(await pool.getTotalPooledEther(), totalPooledEther + beaconBalanceInc + execLayerRewardsToWithdraw, 'total pooled ether') - assertBn(await pool.totalSupply(), totalSupply + beaconBalanceInc + mevToWithdraw, 'token total supply') + assertBn(await pool.totalSupply(), totalSupply + beaconBalanceInc + execLayerRewardsToWithdraw, 'token total supply') - assertBn(await pool.getBufferedEther(), bufferedEther + mevToWithdraw, 'buffered ether') + assertBn(await pool.getBufferedEther(), bufferedEther + execLayerRewardsToWithdraw, 'buffered ether') - mevVaultBalance = toNum(await web3.eth.getBalance(mevVault.address)) + execLayerRewardsVaultBalance = toNum(await web3.eth.getBalance(execLayerRewardsVault.address)) totalPooledEther = toNum(await pool.getTotalPooledEther()) bufferedEther = toNum(await pool.getBufferedEther()) totalSupply = toNum(await pool.totalSupply()) lastBeaconBalance += beaconBalanceInc epoch += 1 - mevWithdrawn += mevToWithdraw + execLayerRewardsWithdrawn += execLayerRewardsToWithdraw } - assert.equal(mevWithdrawn, mevAmount) + assert.equal(execLayerRewardsWithdrawn, execLayerRewards) }) }) diff --git a/test/scenario/helpers/deploy.js b/test/scenario/helpers/deploy.js index a0bf61a15..7d7fd02a3 100644 --- a/test/scenario/helpers/deploy.js +++ b/test/scenario/helpers/deploy.js @@ -74,8 +74,8 @@ async function deployDaoAndPool(appManager, voting) { DEPOSIT_ROLE, STAKING_PAUSE_ROLE, STAKING_RESUME_ROLE, - SET_MEV_TX_FEE_VAULT_ROLE, - SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE, + SET_EXEC_LAYER_REWARDS_VAULT_ROLE, + SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, NODE_OPERATOR_REGISTRY_ADD_NODE_OPERATOR_ROLE, NODE_OPERATOR_REGISTRY_SET_NODE_OPERATOR_ACTIVE_ROLE, @@ -92,8 +92,8 @@ async function deployDaoAndPool(appManager, voting) { pool.DEPOSIT_ROLE(), pool.STAKING_PAUSE_ROLE(), pool.STAKING_RESUME_ROLE(), - pool.SET_MEV_TX_FEE_VAULT_ROLE(), - pool.SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE(), + pool.SET_EXEC_LAYER_REWARDS_VAULT_ROLE(), + pool.SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE(), nodeOperatorRegistry.MANAGE_SIGNING_KEYS(), nodeOperatorRegistry.ADD_NODE_OPERATOR_ROLE(), nodeOperatorRegistry.SET_NODE_OPERATOR_ACTIVE_ROLE(), @@ -112,8 +112,8 @@ async function deployDaoAndPool(appManager, voting) { acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, STAKING_PAUSE_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, STAKING_RESUME_ROLE, appManager, { from: appManager }), - acl.createPermission(voting, pool.address, SET_MEV_TX_FEE_VAULT_ROLE, appManager, { from: appManager }), - acl.createPermission(voting, pool.address, SET_MEV_TX_FEE_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, SET_EXEC_LAYER_REWARDS_VAULT_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }), // Allow depositor to deposit buffered ether acl.createPermission(depositSecurityModule.address, pool.address, DEPOSIT_ROLE, appManager, { from: appManager }), From b0326cc0e767eb4e395e420d8cd58469d800cc72 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 17:36:58 +0000 Subject: [PATCH 130/159] chore: update `resumeStaking` docs --- contracts/0.4.24/Lido.sol | 4 ++-- contracts/0.4.24/interfaces/ILido.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index af96d7236..7b258b2ed 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -157,8 +157,8 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) - * or if new rate-limit params are required. + * @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) + * and updates the staking rate limit. * * Staking could be rate-limited by imposing a limit on the stake amount * at each moment in time. diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 0f37e81ca..d2cc51bb7 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -35,8 +35,8 @@ interface ILido { function pauseStaking() external; /** - * @notice Resume staking if `pauseStaking` was called previously (allow new submits transactions) - * or if new rate-limit params are required. + * @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) + * and updates the staking rate limit. * To disable rate-limit pass zero arg values. * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block From cc83ae0eece2b83a94149ab606553073afeb4944 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 17:43:09 +0000 Subject: [PATCH 131/159] chore: special return value 2**256 - 1 --- contracts/0.4.24/Lido.sol | 2 +- contracts/0.4.24/interfaces/ILido.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 7b258b2ed..6a534bd47 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -205,7 +205,7 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Returns how much Ether can be staked in the current block * @dev Special return values: - * - max uint256 if staking is unlimited; + * - 2^256 - 1 if staking is unlimited; * - 0 if staking is paused or if limit is exhausted. */ function getCurrentStakeLimit() public view returns (uint256) { diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index d2cc51bb7..9c0a60866 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -51,7 +51,7 @@ interface ILido { /** * @notice Returns how much Ether can be staked in the current block * @dev Special return values: - * - max uint256 if staking is unlimited; + * - 2^256 - 1 if staking is unlimited; * - 0 if staking is paused or if limit is exhausted. */ function getCurrentStakeLimit() external view returns (uint256); From 61287ac6d491bf2cfab3511a99f489123e02a6e1 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 17:52:15 +0000 Subject: [PATCH 132/159] chore: update comments --- contracts/0.4.24/Lido.sol | 171 +++++++++++++------------- contracts/0.4.24/interfaces/ILido.sol | 4 +- 2 files changed, 91 insertions(+), 84 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 6a534bd47..2b1be6a9b 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -203,11 +203,11 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns how much Ether can be staked in the current block - * @dev Special return values: - * - 2^256 - 1 if staking is unlimited; - * - 0 if staking is paused or if limit is exhausted. - */ + * @notice Returns how much Ether can be staked in the current block + * @dev Special return values: + * - 2^256 - 1 if staking is unlimited; + * - 0 if staking is paused or if limit is exhausted. + */ function getCurrentStakeLimit() public view returns (uint256) { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); if (!stakeLimitData.isStakingRateLimited()) { @@ -218,19 +218,19 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns full info about stake limit - * @dev Might be used for the advanced integration requests. - * @return - * `isStakingPaused` the value returned by `isStakingPaused()` - * `isStakingLimitApplied` true if staking limit is set and false otherwise - * `currentStakeLimit` the value returned by `getCurrentStakingLimit()` - * - * Internals: - * `maxStakeLimit` internal max stake limit represenation - * `maxStakeLimitGrowthBlocks` internal max stake limit full restoration blocks - * `prevStakeLimit` internal previously reached stake limit represenation - * `prevStakeBlockNumber` internal prevously seen block number represenation - */ + * @notice Returns full info about stake limit + * @dev Might be used for the advanced integration requests. + * @return + * `isStakingPaused` the value returned by `isStakingPaused()` + * `isStakingLimitApplied` true if staking limit is set and false otherwise + * `currentStakeLimit` the value returned by `getCurrentStakingLimit()` + * + * Internals: + * `maxStakeLimit` internal max stake limit represenation + * `maxStakeLimitGrowthBlocks` internal max stake limit full restoration blocks + * `prevStakeLimit` internal previously reached stake limit represenation + * `prevStakeBlockNumber` internal prevously seen block number represenation + */ function getStakeLimitFullInfo() external view returns ( bool isStakingPaused, bool isStakingLimitApplied, @@ -299,9 +299,9 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Deposits buffered ethers to the official DepositContract, making no more than `_maxDeposits` deposit calls. - * @dev This function is separated from submit() to reduce the cost of sending funds. - */ + * @notice Deposits buffered ethers to the official DepositContract, making no more than `_maxDeposits` deposit calls. + * @dev This function is separated from submit() to reduce the cost of sending funds. + */ function depositBufferedEther(uint256 _maxDeposits) external { _auth(DEPOSIT_ROLE); @@ -317,8 +317,8 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Stop pool routine operations - */ + * @notice Stop pool routine operations + */ function stop() external { _auth(PAUSE_ROLE); @@ -326,8 +326,8 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Resume pool routine operations - */ + * @notice Resume pool routine operations + */ function resume() external { _auth(RESUME_ROLE); @@ -335,10 +335,12 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Set fee rate to `_feeBasisPoints` basis points. - * The fees are accrued when oracles report staking results. - * @param _feeBasisPoints Fee rate, in basis points - */ + * @notice Set fee rate to `_feeBasisPoints` basis points. + * The fees are accrued when: + * - oracles report staking results (beacon chain balance increase) + * - validators gain execution layer rewards (priority fees and MEV) + * @param _feeBasisPoints Fee rate, in basis points + */ function setFee(uint16 _feeBasisPoints) external { _auth(MANAGE_FEE); @@ -347,12 +349,12 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Set fee distribution: - * `_treasuryFeeBasisPoints` basis points go to the treasury, - * `_insuranceFeeBasisPoints` basis points go to the insurance fund, - * `_operatorsFeeBasisPoints` basis points go to node operators. - * The sum has to be 10 000. - */ + * @notice Set fee distribution: + * `_treasuryFeeBasisPoints` basis points go to the treasury, + * `_insuranceFeeBasisPoints` basis points go to the insurance fund, + * `_operatorsFeeBasisPoints` basis points go to node operators. + * The sum has to be 10 000. + */ function setFeeDistribution( uint16 _treasuryFeeBasisPoints, uint16 _insuranceFeeBasisPoints, @@ -400,11 +402,11 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` - * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. - * @param _withdrawalCredentials hash of withdrawal multisignature key as accepted by - * the deposit_contract.deposit function - */ + * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` + * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. + * @param _withdrawalCredentials hash of withdrawal multisignature key as accepted by + * the deposit_contract.deposit function + */ function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external { _auth(MANAGE_WITHDRAWAL_KEY); @@ -438,10 +440,10 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Issues withdrawal request. Not implemented. - * @param _amount Amount of StETH to withdraw - * @param _pubkeyHash Receiving address - */ + * @notice Issues withdrawal request. Not implemented. + * @param _amount Amount of StETH to withdraw + * @param _pubkeyHash Receiving address + */ function withdraw(uint256 _amount, bytes32 _pubkeyHash) external whenNotStopped { /* solhint-disable-line no-unused-vars */ //will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). //at the moment withdrawals are not possible in the beacon chain and there's no workaround @@ -504,9 +506,9 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Send funds to recovery Vault. Overrides default AragonApp behaviour - * @param _token Token to be sent to recovery vault - */ + * @notice Send funds to recovery Vault. Overrides default AragonApp behaviour + * @param _token Token to be sent to recovery vault + */ function transferToVault(address _token) external { require(allowRecoverability(_token), "RECOVER_DISALLOWED"); address vault = getRecoveryVault(); @@ -528,10 +530,10 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Send NTFs to recovery Vault - * @param _token Token to be sent to recovery vault - * @param _tokenId Token Id - */ + * @notice Send NTFs to recovery Vault + * @param _token Token to be sent to recovery vault + * @param _tokenId Token Id + */ function transferERC721ToVault(address _token, uint256 _tokenId) external { require(_token != address(0), "ZERO_ADDRESS"); require(allowRecoverability(_token), "RECOVER_DISALLOWED"); @@ -545,15 +547,15 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns staking rewards fee rate - */ + * @notice Returns staking rewards fee rate + */ function getFee() public view returns (uint16 feeBasisPoints) { return uint16(FEE_POSITION.getStorageUint256()); } /** - * @notice Returns fee distribution proportion - */ + * @notice Returns fee distribution proportion + */ function getFeeDistribution() public view @@ -569,8 +571,8 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns current credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched - */ + * @notice Returns current credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched + */ function getWithdrawalCredentials() public view returns (bytes32) { return WITHDRAWAL_CREDENTIALS_POSITION.getStorageBytes32(); } @@ -604,8 +606,8 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Gets deposit contract handle - */ + * @notice Gets deposit contract handle + */ function getDepositContract() public view returns (IDepositContract) { return IDepositContract(DEPOSIT_CONTRACT_POSITION.getStorageAddress()); } @@ -619,22 +621,22 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Gets node operators registry interface handle - */ + * @notice Gets node operators registry interface handle + */ function getOperators() public view returns (INodeOperatorsRegistry) { return INodeOperatorsRegistry(NODE_OPERATORS_REGISTRY_POSITION.getStorageAddress()); } /** - * @notice Returns the treasury address - */ + * @notice Returns the treasury address + */ function getTreasury() public view returns (address) { return TREASURY_POSITION.getStorageAddress(); } /** - * @notice Returns the insurance fund address - */ + * @notice Returns the insurance fund address + */ function getInsuranceFund() public view returns (address) { return INSURANCE_FUND_POSITION.getStorageAddress(); } @@ -712,8 +714,8 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Emits {Transfer} and {TransferShares} events where `from` is 0 address. Indicates mint events. - */ + * @dev Emits {Transfer} and {TransferShares} events where `from` is 0 address. Indicates mint events. + */ function _emitTransferAfterMintingShares(address _to, uint256 _sharesAmount) internal { emit Transfer(address(0), _to, getPooledEthByShares(_sharesAmount)); emit TransferShares(address(0), _to, _sharesAmount); @@ -886,9 +888,9 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Records a deposit to the deposit_contract.deposit function - * @param _amount Total amount deposited to the ETH 2.0 side - */ + * @dev Records a deposit to the deposit_contract.deposit function + * @param _amount Total amount deposited to the ETH 2.0 side + */ function _markAsUnbuffered(uint256 _amount) internal { BUFFERED_ETHER_POSITION.setStorageUint256( BUFFERED_ETHER_POSITION.getStorageUint256().sub(_amount)); @@ -897,16 +899,16 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Write a value nominated in basis points - */ + * @dev Write a value nominated in basis points + */ function _setBPValue(bytes32 _slot, uint16 _value) internal { require(_value <= TOTAL_BASIS_POINTS, "VALUE_OVER_100_PERCENT"); _slot.setStorageUint256(uint256(_value)); } /** - * @dev Gets the amount of Ether temporary buffered on this contract balance - */ + * @dev Gets the amount of Ether temporary buffered on this contract balance + */ function _getBufferedEther() internal view returns (uint256) { uint256 buffered = BUFFERED_ETHER_POSITION.getStorageUint256(); assert(address(this).balance >= buffered); @@ -915,8 +917,8 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Gets unaccounted (excess) Ether on this contract balance - */ + * @dev Gets unaccounted (excess) Ether on this contract balance + */ function _getUnaccountedEther() internal view returns (uint256) { return address(this).balance.sub(_getBufferedEther()); } @@ -945,9 +947,9 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Padding memory array with zeroes up to 64 bytes on the right - * @param _b Memory array of size 32 .. 64 - */ + * @dev Padding memory array with zeroes up to 64 bytes on the right + * @param _b Memory array of size 32 .. 64 + */ function _pad64(bytes memory _b) internal pure returns (bytes memory) { assert(_b.length >= 32 && _b.length <= 64); if (64 == _b.length) @@ -963,9 +965,9 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Converting value to little endian bytes and padding up to 32 bytes on the right - * @param _value Number less than `2**64` for compatibility reasons - */ + * @dev Converting value to little endian bytes and padding up to 32 bytes on the right + * @param _value Number less than `2**64` for compatibility reasons + */ function _toLittleEndian64(uint256 _value) internal pure returns (uint256 result) { result = 0; uint256 temp_value = _value; @@ -978,7 +980,10 @@ contract Lido is ILido, StETH, AragonApp { result <<= (24 * 8); } - // size-efficient analog of the `auth(_role)` modifier + /** + * @dev Size-efficient analog of the `auth(_role)` modifier + * @param _role Permission name + */ function _auth(bytes32 _role) internal view auth(_role) { // no-op } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 9c0a60866..c61e188e3 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -91,7 +91,9 @@ interface ILido { /** * @notice Set fee rate to `_feeBasisPoints` basis points. - * The fees are accrued when oracles report staking results. + * The fees are accrued when: + * - oracles report staking results (beacon chain balance increase) + * - validators gain execution layer rewards (priority fees and MEV) * @param _feeBasisPoints Fee rate, in basis points */ function setFee(uint16 _feeBasisPoints) external; From ad6e5a831ef8d892a3b1b48e459ae9a2d516ae65 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 18:02:53 +0000 Subject: [PATCH 133/159] chore: sync docs, fix typos --- contracts/0.4.24/Lido.sol | 14 +++++------ contracts/0.4.24/StETH.sol | 2 +- contracts/0.4.24/interfaces/ILido.sol | 23 ++++++++++++------- contracts/0.8.9/LidoExecLayerRewardsVault.sol | 2 +- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 2b1be6a9b..259cbea50 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -275,7 +275,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice A payable function for execution layer rewards. Can be funded only by ExecLayerRewardsVault contract + * @notice A payable function for execution layer rewards. Can be called only by ExecLayerRewardsVault contract * @dev We need a separate payable function because funds received by default payable function * are considered as funds submitted for minting stETH */ @@ -349,11 +349,11 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Set fee distribution: - * `_treasuryFeeBasisPoints` basis points go to the treasury, - * `_insuranceFeeBasisPoints` basis points go to the insurance fund, - * `_operatorsFeeBasisPoints` basis points go to node operators. - * The sum has to be 10 000. + * @notice Set fee distribution + * @param _treasuryFeeBasisPoints basis points go to the treasury, + * @param _insuranceFeeBasisPoints basis points go to the insurance fund, + * @param _operatorsFeeBasisPoints basis points go to node operators. + * @dev The sum has to be 10 000. */ function setFeeDistribution( uint16 _treasuryFeeBasisPoints, @@ -417,7 +417,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Sets given address as the address of LidoExecLayerRewardsVault contract + * @dev Sets the address of LidoExecLayerRewardsVault contract * @param _execLayerRewardsVault Execution layer rewards vault contract address */ function setExecLayerRewardsVault(address _execLayerRewardsVault) external { diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 689e1347e..dcbd2ecd4 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -472,6 +472,6 @@ contract StETH is IERC20, Pausable { // This is equivalent to performing a send from `address` to each other token holder address, // but we cannot reflect this as it would require sending an unbounded number of events. - // We're emitting StETHBurnt event to provide an explicit rebase log record nonetheless. + // We're emitting `SharesBurnt` event to provide an explicit rebase log record nonetheless. } } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index c61e188e3..9919d7ba8 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -99,11 +99,11 @@ interface ILido { function setFee(uint16 _feeBasisPoints) external; /** - * @notice Set fee distribution: - * `_treasuryFeeBasisPoints` basis points go to the treasury, - * `_insuranceFeeBasisPoints` basis points go to the insurance fund, - * `_operatorsFeeBasisPoints` basis points go to node operators. - * The sum has to be 10 000. + * @notice Set fee distribution + * @param _treasuryFeeBasisPoints basis points go to the treasury, + * @param _insuranceFeeBasisPoints basis points go to the insurance fund, + * @param _operatorsFeeBasisPoints basis points go to node operators. + * @dev The sum has to be 10 000. */ function setFeeDistribution( uint16 _treasuryFeeBasisPoints, @@ -130,7 +130,7 @@ interface ILido { event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints); /** - * @notice A payable function supposed to be funded only by LidoExecLayerRewardsVault contract + * @notice A payable function supposed to be called only by LidoExecLayerRewardsVault contract * @dev We need a separate function because funds received by default payable function * are considered as funds submitted by a user for staking */ @@ -139,6 +139,12 @@ interface ILido { // The amount of ETH withdrawn from LidoExecLayerRewardsVault contract to Lido contract event ExecLayerRewardsReceived(uint256 amount); + /** + * @dev Sets limit to amount of ETH to withdraw from execution layer rewards vault per LidoOracle report + * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report + */ + function setExecLayerRewardsWithdrawalLimit(uint16 _limitPoints) external; + // Percent in basis points of total pooled ether allowed to withdraw from LidoExecLayerRewardsVault per LidoOracle report event ExecLayerRewardsWithdrawalLimitSet(uint256 limitPoints); @@ -158,11 +164,12 @@ interface ILido { event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); /** - * @dev Sets given address as the address of LidoExecLayerRewardsVault contract + * @dev Sets the address of LidoExecLayerRewardsVault contract * @param _execLayerRewardsVault Execution layer rewards vault contract address */ function setExecLayerRewardsVault(address _execLayerRewardsVault) external; + // The `execLayerRewardsVault` was set as the execution layer rewards vault for Lido event LidoExecLayerRewardsVaultSet(address execLayerRewardsVault); /** @@ -184,7 +191,7 @@ interface ILido { // Records a deposit made by a user event Submitted(address indexed sender, uint256 amount, address referral); - // The `_amount` of ether was sent to the deposit_contract.deposit function. + // The `amount` of ether was sent to the deposit_contract.deposit function event Unbuffered(uint256 amount); /** diff --git a/contracts/0.8.9/LidoExecLayerRewardsVault.sol b/contracts/0.8.9/LidoExecLayerRewardsVault.sol index 5f68faa15..24b0a8d34 100644 --- a/contracts/0.8.9/LidoExecLayerRewardsVault.sol +++ b/contracts/0.8.9/LidoExecLayerRewardsVault.sol @@ -11,7 +11,7 @@ import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; interface ILido { /** - * @notice A payable function supposed to be funded only by LidoExecLayerRewardsVault contract + * @notice A payable function supposed to be called only by LidoExecLayerRewardsVault contract * @dev We need a separate function because funds received by default payable function * will go through entire deposit algorithm */ From 59e33764959ac00b6c994dd0457aecb18714085d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 18:10:07 +0000 Subject: [PATCH 134/159] chore: update `pauseStaking` doc --- contracts/0.4.24/Lido.sol | 10 +++++----- contracts/0.4.24/interfaces/ILido.sol | 8 ++++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 259cbea50..72746cc8a 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -142,11 +142,11 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Cut-off new stake (every new staking transaction submitting user-provided ETH - * would revert if `pauseStake` was called previously). - * @dev A way to pause stake without pushing PAUSE for the whole proto. - * The main goal is to prevent huge APR losses for existing stakers due to high demands - * on post-Merge entry queue. + * @notice Stops accepting new Ether to the protocol. + * + * @dev While accepting new Ether is stopped, calls to the `submit` function, + * as well as to the default payable function, will revert. + * * Emits `StakingPaused` event. */ function pauseStaking() external { diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 9919d7ba8..440e5c5a1 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -29,8 +29,12 @@ interface ILido { function resume() external; /** - * @notice Cut-off new stake (every new staking transaction submitting user-provided ETH - * would revert if `pauseStake` was called previously). + * @notice Stops accepting new Ether to the protocol. + * + * @dev While accepting new Ether is stopped, calls to the `submit` function, + * as well as to the default payable function, will revert. + * + * Emits `StakingPaused` event. */ function pauseStaking() external; From f71695adf1ca4cb9c00f6190a5bae1dd659e7c29 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 18:30:55 +0000 Subject: [PATCH 135/159] fix: use `isStakingLimitApplied` everywhere --- contracts/0.4.24/Lido.sol | 8 ++++---- contracts/0.4.24/interfaces/ILido.sol | 2 +- contracts/0.4.24/lib/StakeRateLimitUtils.sol | 4 ++-- contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol | 8 ++++---- test/0.4.24/lido.test.js | 8 ++++---- test/0.4.24/staking-limit.test.js | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 72746cc8a..007ba7f14 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -171,7 +171,7 @@ contract Lido is ILido, StETH, AragonApp { * │──────────────────────────────────────────────────> Time * │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events * - * To disable rate-limit pass zero arg values. + * NB: To resume without limits pass zero arg values. * @dev Reverts if: * - `_maxStakeLimit` >= 2^96 * - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` @@ -210,7 +210,7 @@ contract Lido is ILido, StETH, AragonApp { */ function getCurrentStakeLimit() public view returns (uint256) { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); - if (!stakeLimitData.isStakingRateLimited()) { + if (!stakeLimitData.isStakingLimitApplied()) { return uint256(-1); } @@ -243,7 +243,7 @@ contract Lido is ILido, StETH, AragonApp { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); isStakingPaused = stakeLimitData.isStakingPaused(); - isStakingLimitApplied = stakeLimitData.isStakingRateLimited(); + isStakingLimitApplied = stakeLimitData.isStakingLimitApplied(); currentStakeLimit = getCurrentStakeLimit(); maxStakeLimit = stakeLimitData.maxStakeLimit; @@ -687,7 +687,7 @@ contract Lido is ILido, StETH, AragonApp { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); require(!stakeLimitData.isStakingPaused(), "STAKING_PAUSED"); - if (stakeLimitData.isStakingRateLimited()) { + if (stakeLimitData.isStakingLimitApplied()) { uint256 currentStakeLimit = stakeLimitData.calculateCurrentStakeLimit(); require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 440e5c5a1..68cc74da7 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -41,7 +41,7 @@ interface ILido { /** * @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) * and updates the staking rate limit. - * To disable rate-limit pass zero arg values. + * NB: To resume without limits pass zero arg values. * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ diff --git a/contracts/0.4.24/lib/StakeRateLimitUtils.sol b/contracts/0.4.24/lib/StakeRateLimitUtils.sol index 0b4c2f47e..c8e6d392c 100644 --- a/contracts/0.4.24/lib/StakeRateLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeRateLimitUtils.sol @@ -96,9 +96,9 @@ library StakeRateLimitUtils { } /** - * @notice check if rate limit is set (otherwise staking is unlimited) + * @notice check if staking limit is applied (otherwise staking is unlimited) */ - function isStakingRateLimited(StakeLimitState.Data memory _data) internal pure returns(bool) { + function isStakingLimitApplied(StakeLimitState.Data memory _data) internal pure returns(bool) { return _data.maxStakeLimit != 0; } diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol index 369b0cfde..6f9b763de 100644 --- a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -14,7 +14,7 @@ contract StakeLimitUtilsMock { bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("abcdef"); - function getStorageStakeRateLimit() public view returns ( + function getStorageStakeLimit() public view returns ( uint32 prevStakeBlockNumber, uint96 prevStakeLimit, uint32 maxStakeLimitGrowthBlocks, @@ -28,7 +28,7 @@ contract StakeLimitUtilsMock { maxStakeLimit = data.maxStakeLimit; } - function setStorageStakeRateLimitStruct( + function setStorageStakeLimitStruct( uint32 _prevStakeBlockNumber, uint96 _prevStakeLimit, uint32 _maxStakeLimitGrowthBlocks, @@ -53,8 +53,8 @@ contract StakeLimitUtilsMock { return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingPaused(); } - function isStakingRateLimited(uint256 _slotValue) public view returns(bool) { - return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingRateLimited(); + function isStakingLimitApplied(uint256 _slotValue) public view returns(bool) { + return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingLimitApplied(); } function resumeStakingWithNewLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 860533a85..d6c566790 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -581,7 +581,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) } - it('stake pause/unlimited resume works', async () => { + it('staking pause & unlimited resume works', async () => { let receipt const MAX_UINT256 = bn(2).pow(bn(256)).sub(bn(1)) @@ -617,7 +617,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) } } - it('stake rate-limiting works', async () => { + it('staking resume with a limit works', async () => { let receipt const blocksToReachMaxStakeLimit = 300 @@ -670,7 +670,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TOO_SMALL_LIMIT_INCREASE`) }) - it('one-shot rate-limiting works', async () => { + it('resume staking with an one-shot limit works', async () => { let receipt const expectedMaxStakeLimit = ETH(7) @@ -698,7 +698,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) }) - it('changing various rate-limits work', async () => { + it('resume with various changing limits work', async () => { let receipt const expectedMaxStakeLimit = ETH(9) diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index ffbd39ca6..77934de51 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -74,13 +74,13 @@ contract.skip('StakingLimits', () => { it('check staking rate limit', async () => { const slot = 0 - const limited = await limits.isStakingRateLimited(slot) + const limited = await limits.isStakingLimitApplied(slot) assert.equal(limited, false, 'limits not limited') const maxStakeLimit = 10 const slot2 = await limits.encodeStakeLimitSlot(maxStakeLimit, 0, 0, 0) - const limited2 = await limits.isStakingRateLimited(slot2) + const limited2 = await limits.isStakingLimitApplied(slot2) assert.equal(limited2, true, 'limits not limited') }) From 7837182d50fd084aa976cafa41c2de11c2202564 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Mon, 16 May 2022 21:35:15 +0300 Subject: [PATCH 136/159] DepositSecurityModule: setter for lastDeposit block and update scripts --- contracts/0.8.9/DepositSecurityModule.sol | 10 ++- .../multisig/17-obtain-deployed-depositor.js | 11 ++- .../multisig/18-depositor-add-guardians.js | 11 ++- .../29-deploy-new-depositor-instance.js | 82 +++++++++++++++++++ .../30-initialize-updated-depositor.js | 42 ++++++++++ 5 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 scripts/multisig/29-deploy-new-depositor-instance.js create mode 100644 scripts/multisig/30-initialize-updated-depositor.js diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index c289bef66..3f0d59c63 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -220,7 +220,7 @@ contract DepositSecurityModule { } function _setGuardianQuorum(uint256 newValue) internal { - // we're intentionally allowing setting quorum value higher than the number of quardians + // we're intentionally allowing setting quorum value higher than the number of guardians quorum = newValue; emit GuardianQuorumChanged(newValue); } @@ -384,6 +384,14 @@ contract DepositSecurityModule { } + /** + * Sets `lastDepositBlock`. Only callable by the owner. + */ + function setLastDepositBlock(uint256 newLastDepositBlock) external onlyOwner { + lastDepositBlock = newLastDepositBlock; + } + + /** * Returns whether depositBufferedEther can be called, given that the caller will provide * guardian attestations of non-stale deposit root and `keysOpIndex`, and the number of diff --git a/scripts/multisig/17-obtain-deployed-depositor.js b/scripts/multisig/17-obtain-deployed-depositor.js index 0245a942e..68089deeb 100644 --- a/scripts/multisig/17-obtain-deployed-depositor.js +++ b/scripts/multisig/17-obtain-deployed-depositor.js @@ -36,9 +36,16 @@ async function obtainInstance({ web3, artifacts, appName = APP }) { await assertParams(state.depositorParams, depositor, appArtifact) await assertAddresses({ lidoAddress, nosAddress, depositContractAddress }, depositor, appArtifact) - persistNetworkState(network.name, netId, state, { + + // If the depositor is already deployed save its previous address + const depositorCurrentAddress = state['depositorAddress'] + const newDepositorState = { depositorAddress: depositor.address - }) + } + if (depositorCurrentAddress !== undefined && depositor.address !== depositorCurrentAddress) { + newDepositorState['depositorPreviousAddress'] = depositorCurrentAddress + } + persistNetworkState(network.name, netId, state, newDepositorState) } async function assertParams({ maxDepositsPerBlock, minDepositBlockDistance, pauseIntentValidityPeriodBlocks }, instance, desc) { diff --git a/scripts/multisig/18-depositor-add-guardians.js b/scripts/multisig/18-depositor-add-guardians.js index 06d5c98ae..d807f1c30 100644 --- a/scripts/multisig/18-depositor-add-guardians.js +++ b/scripts/multisig/18-depositor-add-guardians.js @@ -11,15 +11,18 @@ async function obtainInstance({ web3, artifacts }) { const appArtifact = 'DepositSecurityModule' const netId = await web3.eth.net.getId() - logWideSplitter() - log(`Network ID:`, yl(netId)) - const state = readNetworkState(network.name, netId) assertRequiredNetworkState(state, REQUIRED_NET_STATE) - const depositor = await artifacts.require(appArtifact).at(state.depositorAddress) const { guardians = [], quorum = 1 } = state.depositorParams + + logWideSplitter() + log(`Network ID:`, yl(netId)) + console.log("Going to set these params in addGuardians(guardians, quorum):") + console.log({ guardians, quorum }) + console.log() + const depositor = await artifacts.require(appArtifact).at(state.depositorAddress) await saveCallTxData(`Set guardians`, depositor, 'addGuardians', `tx-18-depositor-add-guardians.json`, { arguments: [guardians, quorum], from: DEPLOYER || state.multisigAddress diff --git a/scripts/multisig/29-deploy-new-depositor-instance.js b/scripts/multisig/29-deploy-new-depositor-instance.js new file mode 100644 index 000000000..097a8c7f1 --- /dev/null +++ b/scripts/multisig/29-deploy-new-depositor-instance.js @@ -0,0 +1,82 @@ +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, logSplitter, logWideSplitter, yl, gr } = require('../helpers/log') +const { saveDeployTx } = require('../helpers/deploy') +const { readNetworkState, assertRequiredNetworkState, persistNetworkState } = require('../helpers/persisted-network-state') +const { assert, strictEqual } = require('../helpers/assert') + +const { APP_NAMES } = require('./constants') + +const DEPLOYER = process.env.DEPLOYER || '' +const REQUIRED_NET_STATE = [ + 'daoInitialSettings', + 'depositorParams', + 'depositorAddress', + `app:${APP_NAMES.LIDO}`, + `app:${APP_NAMES.NODE_OPERATORS_REGISTRY}` +] + +function assertParameterIsTheSame(contractValue, stateValue, paramName) { + // console.log(typeof(contractValue)) + // assert.equal(+contractValue.toString(), stateValue, + assert.equal(contractValue, stateValue, + `Value of ${paramName} in state and in the deployed contract differ`) +} + +async function upgradeApp({ web3, artifacts }) { + const appArtifact = 'DepositSecurityModule' + const netId = await web3.eth.net.getId() + + logWideSplitter() + log(`Network ID:`, yl(netId)) + + const state = readNetworkState(network.name, netId) + assertRequiredNetworkState(state, REQUIRED_NET_STATE) + const lidoAddress = state[`app:${APP_NAMES.LIDO}`].proxyAddress + log(`Using Lido address:`, yl(lidoAddress)) + const nosAddress = state[`app:${APP_NAMES.NODE_OPERATORS_REGISTRY}`].proxyAddress + log(`Using NOS address:`, yl(nosAddress)) + const { depositContractAddress } = state.daoInitialSettings.beaconSpec + log(`Using Deposit Contract:`, yl(depositContractAddress)) + logSplitter() + + const depositor = await artifacts.require(appArtifact).at(state.depositorAddress) + + const { maxDepositsPerBlock, minDepositBlockDistance, pauseIntentValidityPeriodBlocks, quorum, guardians } = state.depositorParams + + const args = [ + lidoAddress, + depositContractAddress, + nosAddress, + netId, + maxDepositsPerBlock, + minDepositBlockDistance, + pauseIntentValidityPeriodBlocks + ] + console.log("Constructor arguments (for use for source code verification): " + args.join(' ')) + + assertParameterIsTheSame(await depositor.LIDO(), lidoAddress, 'lidoAddress') + assertParameterIsTheSame(await depositor.DEPOSIT_CONTRACT(), depositContractAddress, 'depositContractAddress') + assertParameterIsTheSame(await depositor.getMaxDeposits(), maxDepositsPerBlock, 'maxDepositsPerBlock') + assertParameterIsTheSame(await depositor.getMinDepositBlockDistance(), minDepositBlockDistance, 'minDepositBlockDistance') + assertParameterIsTheSame(await depositor.getPauseIntentValidityPeriodBlocks(), pauseIntentValidityPeriodBlocks, 'pauseIntentValidityPeriodBlocks') + + // Also check guardians and quorum in state file correspond to the values in the contract + assertParameterIsTheSame(await depositor.getGuardianQuorum(), quorum, 'quorum') + assertParameterIsTheSame(await depositor.getGuardians(), guardians, 'guardians') + + await saveDeployTx(appArtifact, `tx-29-deploy-new-depositor-instance.json`, { + arguments: args, + from: DEPLOYER || state.multisigAddress + }) + persistNetworkState(network.name, netId, state, { + depositorConstructorArgs: args + }) + + logSplitter() + log(gr(`Before continuing the deployment, please send all contract creation transactions`)) + log(gr(`that you can find in the files listed above. You may use a multisig address`)) + log(gr(`if it supports deploying new contract instances.`)) + logSplitter() +} + +module.exports = runOrWrapScript(upgradeApp, module) diff --git a/scripts/multisig/30-initialize-updated-depositor.js b/scripts/multisig/30-initialize-updated-depositor.js new file mode 100644 index 000000000..a81f2f55b --- /dev/null +++ b/scripts/multisig/30-initialize-updated-depositor.js @@ -0,0 +1,42 @@ +const runOrWrapScript = require('../helpers/run-or-wrap-script') +const { log, logWideSplitter, logHeader, yl, gr } = require('../helpers/log') +const { saveCallTxData } = require('../helpers/tx-data') +const { readNetworkState, assertRequiredNetworkState } = require('../helpers/persisted-network-state') +const { assert } = require('chai') + +const DEPLOYER = process.env.DEPLOYER || '' +const REQUIRED_NET_STATE = ['depositorAddress', 'depositorParams', 'depositorPreviousAddress'] + +async function initializeDepositor({ web3, artifacts }) { + // convert dash-ed appName to camel case-d + const appArtifact = 'DepositSecurityModule' + const netId = await web3.eth.net.getId() + + logWideSplitter() + log(`Network ID:`, yl(netId)) + + const state = readNetworkState(network.name, netId) + assertRequiredNetworkState(state, REQUIRED_NET_STATE) + + const oldDepositor = await artifacts.require(appArtifact).at(state.depositorPreviousAddress) + const guardians = await oldDepositor.getGuardians() + const quorum = +(await oldDepositor.getGuardianQuorum()).toString() + + console.log("Going to set these params in addGuardians(guardians, quorum):") + console.log({ guardians, quorum }) + console.log() + + const depositor = await artifacts.require(appArtifact).at(state.depositorAddress) + assert.notEqual(depositor.getGuardians(), guardians, 'Guardians list on the new contract are supposed to be empty') + + await saveCallTxData(`Set guardians`, depositor, 'addGuardians', `tx-30-initialize-updated-depositor.json`, { + arguments: [guardians, quorum], + from: DEPLOYER || state.multisigAddress + }) + + log.splitter() + log(gr(`Before continuing the deployment, please send all transactions listed above.`)) + log(gr(`You must complete it positively and execute before continuing with the deployment!`)) + log.splitter() +} +module.exports = runOrWrapScript(initializeDepositor, module) From 4f0b77369cecbb22393895069f20af63286049ae Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 20:21:47 +0000 Subject: [PATCH 137/159] fix: a bunch of rate limit improvements --- contracts/0.4.24/Lido.sol | 36 +++++++------------- contracts/0.4.24/interfaces/ILido.sol | 7 ++-- contracts/0.4.24/lib/StakeRateLimitUtils.sol | 6 +++- test/0.4.24/lido.test.js | 23 ++++--------- 4 files changed, 27 insertions(+), 45 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 007ba7f14..7e926057d 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -152,8 +152,7 @@ contract Lido is ILido, StETH, AragonApp { function pauseStaking() external { _auth(STAKING_PAUSE_ROLE); - STAKE_LIMIT_POSITION.setStorageUint256(0); - emit StakingPaused(); + _pauseStaking(); } /** @@ -208,33 +207,20 @@ contract Lido is ILido, StETH, AragonApp { * - 2^256 - 1 if staking is unlimited; * - 0 if staking is paused or if limit is exhausted. */ - function getCurrentStakeLimit() public view returns (uint256) { - StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); - if (!stakeLimitData.isStakingLimitApplied()) { - return uint256(-1); - } - - return stakeLimitData.calculateCurrentStakeLimit(); + function getCurrentStakeLimit() external view returns (uint256) { + return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().calculateCurrentStakeLimit(); } /** * @notice Returns full info about stake limit * @dev Might be used for the advanced integration requests. * @return - * `isStakingPaused` the value returned by `isStakingPaused()` - * `isStakingLimitApplied` true if staking limit is set and false otherwise - * `currentStakeLimit` the value returned by `getCurrentStakingLimit()` - * - * Internals: * `maxStakeLimit` internal max stake limit represenation * `maxStakeLimitGrowthBlocks` internal max stake limit full restoration blocks * `prevStakeLimit` internal previously reached stake limit represenation * `prevStakeBlockNumber` internal prevously seen block number represenation */ - function getStakeLimitFullInfo() external view returns ( - bool isStakingPaused, - bool isStakingLimitApplied, - uint256 currentStakeLimit, + function getStakeLimitInternalInfo() external view returns ( uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, @@ -242,10 +228,6 @@ contract Lido is ILido, StETH, AragonApp { ) { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); - isStakingPaused = stakeLimitData.isStakingPaused(); - isStakingLimitApplied = stakeLimitData.isStakingLimitApplied(); - currentStakeLimit = getCurrentStakeLimit(); - maxStakeLimit = stakeLimitData.maxStakeLimit; maxStakeLimitGrowthBlocks = stakeLimitData.maxStakeLimitGrowthBlocks; prevStakeLimit = stakeLimitData.prevStakeLimit; @@ -323,10 +305,12 @@ contract Lido is ILido, StETH, AragonApp { _auth(PAUSE_ROLE); _stop(); + _pauseStaking(); } /** * @notice Resume pool routine operations + * @dev Staking should be resumed manually after this call using the desired limits */ function resume() external { _auth(RESUME_ROLE); @@ -681,7 +665,7 @@ contract Lido is ILido, StETH, AragonApp { * @param _referral address of referral. * @return amount of StETH shares generated */ - function _submit(address _referral) internal whenNotStopped returns (uint256) { + function _submit(address _referral) internal returns (uint256) { require(msg.value != 0, "ZERO_DEPOSIT"); StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); @@ -980,6 +964,12 @@ contract Lido is ILido, StETH, AragonApp { result <<= (24 * 8); } + function _pauseStaking() internal { + StakeLimitState.Data memory zeroState; + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct(zeroState); + emit StakingPaused(); + } + /** * @dev Size-efficient analog of the `auth(_role)` modifier * @param _role Permission name diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 68cc74da7..9984fafde 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -61,13 +61,10 @@ interface ILido { function getCurrentStakeLimit() external view returns (uint256); /** - * @notice Returns full info about stake limit + * @notice Returns internal info about stake limit * @dev Might be used for the advanced integration requests. */ - function getStakeLimitFullInfo() external view returns ( - bool isStakingPaused, - bool isStakingLimitApplied, - uint256 currentStakeLimit, + function getStakeLimitInternalInfo() external view returns ( uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, diff --git a/contracts/0.4.24/lib/StakeRateLimitUtils.sol b/contracts/0.4.24/lib/StakeRateLimitUtils.sol index c8e6d392c..5052d0301 100644 --- a/contracts/0.4.24/lib/StakeRateLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeRateLimitUtils.sol @@ -74,6 +74,10 @@ library StakeRateLimitUtils { * @notice Calculate stake limit for the current block. */ function calculateCurrentStakeLimit(StakeLimitState.Data memory _data) internal view returns(uint256 limit) { + if (!isStakingLimitApplied(_data)) { + return uint256(-1); + } + uint256 stakeLimitIncPerBlock; if (_data.maxStakeLimitGrowthBlocks > 0) { stakeLimitIncPerBlock = _data.maxStakeLimit / _data.maxStakeLimitGrowthBlocks; @@ -117,7 +121,7 @@ library StakeRateLimitUtils { // if staking was paused or unlimited previously, // or new limit is lower than previous, then - // reset prev stake limit to max + // reset prev stake limit to the new max stake limit if ((_data.maxStakeLimit == 0) || (_maxStakeLimit < _data.prevStakeLimit)) { _data.prevStakeLimit = uint96(_maxStakeLimit); } diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index d6c566790..7c59bca44 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -564,19 +564,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const verifyRateLimitStake = async (expectedMaxStakeLimit, expectedLimitIncrease, expectedCurrentStakeLimit) => { currentStakeLimit = await app.getCurrentStakeLimit() assertBn(currentStakeLimit, expectedCurrentStakeLimit) - ;({ - isStakingPaused, - isStakingLimitApplied, - currentStakeLimit, - maxStakeLimit, - maxStakeLimitGrowthBlocks, - prevStakeLimit, - prevStakeBlockNumber - } = await app.getStakeLimitFullInfo()) - - assert.equal(isStakingPaused, false) - assert.equal(isStakingLimitApplied, expectedMaxStakeLimit.toString() !== bn(0).toString()) - assertBn(currentStakeLimit, expectedCurrentStakeLimit) + ;({ maxStakeLimit, maxStakeLimitGrowthBlocks, prevStakeLimit, prevStakeBlockNumber } = await app.getStakeLimitInternalInfo()) + assertBn(maxStakeLimit, expectedMaxStakeLimit) assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) } @@ -980,12 +969,14 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.stop({ from: user2 }), 'APP_AUTH_FAILED') await app.stop({ from: voting }) - await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }), 'CONTRACT_IS_STOPPED') - await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }), 'CONTRACT_IS_STOPPED') - await assertRevert(app.submit('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', { from: user1, value: ETH(4) }), 'CONTRACT_IS_STOPPED') + await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }), 'STAKING_PAUSED') + await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }), 'STAKING_PAUSED') + await assertRevert(app.submit('0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', { from: user1, value: ETH(4) }), 'STAKING_PAUSED') await assertRevert(app.resume({ from: user2 }), 'APP_AUTH_FAILED') await app.resume({ from: voting }) + await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }), 'STAKING_PAUSED') + await app.resumeStaking(0, 0, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }) await app.methods['depositBufferedEther()']({ from: depositor }) From a656c04062c3246048d65889e86d8f80d01dbe8d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 21:54:24 +0000 Subject: [PATCH 138/159] fix: tune el rewards vault naming a bit --- apps/lido/app/src/App.js | 4 +- apps/lido/app/src/index.js | 2 +- apps/lido/app/src/script.js | 6 +- contracts/0.4.24/Lido.sol | 84 ++++++------- contracts/0.4.24/interfaces/ILido.sol | 24 ++-- ...ol => ILidoExecutionLayerRewardsVault.sol} | 2 +- contracts/0.4.24/template/LidoTemplate.sol | 4 +- ...sol => LidoExecutionLayerRewardsVault.sol} | 6 +- scripts/multisig/12-check-dao.js | 4 +- .../26-deploy-exec-layer-rewards-vault.js | 8 +- ...btain-deployed-exec-layer-rewards-vault.js | 8 +- .../28-vote-merge-ready-first-pack-upgrade.js | 36 +++--- test/0.4.24/lido.test.js | 84 ++++++------- test/0.8.9/lido-exec-layer-rewards-vault.js | 71 ++++++----- test/deposit.test.js | 4 +- ...execution_layer_rewards_after_the_merge.js | 114 +++++++++--------- test/scenario/helpers/deploy.js | 12 +- 17 files changed, 235 insertions(+), 238 deletions(-) rename contracts/0.4.24/interfaces/{ILidoExecLayerRewardsVault.sol => ILidoExecutionLayerRewardsVault.sol} (90%) rename contracts/0.8.9/{LidoExecLayerRewardsVault.sol => LidoExecutionLayerRewardsVault.sol} (95%) diff --git a/apps/lido/app/src/App.js b/apps/lido/app/src/App.js index a139bd983..069b0a361 100644 --- a/apps/lido/app/src/App.js +++ b/apps/lido/app/src/App.js @@ -85,7 +85,7 @@ export default function App() { nodeOperatorsRegistry, depositContract, oracle, - execLayerRewardsVault, + executionLayerRewardsVault, // operators, // treasury, // insuranceFund, @@ -252,7 +252,7 @@ export default function App() { }, { label: 'Execution layer rewards Vault', - content: , + content: , }, ] }, [ diff --git a/apps/lido/app/src/index.js b/apps/lido/app/src/index.js index 15bfcc5bd..fc4b50742 100644 --- a/apps/lido/app/src/index.js +++ b/apps/lido/app/src/index.js @@ -22,7 +22,7 @@ const defaultState = { nodeOperatorsRegistry: defaultValue, depositContract: defaultValue, oracle: defaultValue, - execLayerRewardsVault: defaultValue, + executionLayerRewardsVault: defaultValue, operators: defaultValue, treasury: defaultValue, insuranceFund: defaultValue, diff --git a/apps/lido/app/src/script.js b/apps/lido/app/src/script.js index 96b9c860c..321be96e5 100644 --- a/apps/lido/app/src/script.js +++ b/apps/lido/app/src/script.js @@ -62,7 +62,7 @@ function initializeState() { nodeOperatorsRegistry: await getNodeOperatorsRegistry(), depositContract: await getDepositContract(), oracle: await getOracle(), - execLayerRewardsVault: await getExecLayerRewardsVault(), + executionLayerRewardsVault: await getExecutionLayerRewardsVault(), // operators: await getOperators(), // treasury: await getTreasury(), // insuranceFund: await getInsuranceFund(), @@ -108,8 +108,8 @@ function getOracle() { return app.call('getOracle').toPromise() } -function getExecLayerRewardsVault() { - return app.call('getExecLayerRewardsVault').toPromise() +function getExecutionLayerRewardsVault() { + return app.call('getExecutionLayerRewardsVault').toPromise() } // async function getOperators() { diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 7e926057d..032443713 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -13,7 +13,7 @@ import "solidity-bytes-utils/contracts/BytesLib.sol"; import "./interfaces/ILido.sol"; import "./interfaces/INodeOperatorsRegistry.sol"; import "./interfaces/IDepositContract.sol"; -import "./interfaces/ILidoExecLayerRewardsVault.sol"; +import "./interfaces/ILidoExecutionLayerRewardsVault.sol"; import "./StETH.sol"; @@ -60,9 +60,9 @@ contract Lido is ILido, StETH, AragonApp { bytes32 constant public MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE"); bytes32 constant public BURN_ROLE = keccak256("BURN_ROLE"); bytes32 constant public DEPOSIT_ROLE = keccak256("DEPOSIT_ROLE"); - bytes32 constant public SET_EXEC_LAYER_REWARDS_VAULT_ROLE = keccak256("SET_EXEC_LAYER_REWARDS_VAULT_ROLE"); - bytes32 constant public SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE = keccak256( - "SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE" + bytes32 constant public SET_EL_REWARDS_VAULT_ROLE = keccak256("SET_EL_REWARDS_VAULT_ROLE"); + bytes32 constant public SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE = keccak256( + "SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE" ); uint256 constant public PUBKEY_LENGTH = 48; @@ -87,7 +87,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 internal constant NODE_OPERATORS_REGISTRY_POSITION = keccak256("lido.Lido.nodeOperatorsRegistry"); bytes32 internal constant TREASURY_POSITION = keccak256("lido.Lido.treasury"); bytes32 internal constant INSURANCE_FUND_POSITION = keccak256("lido.Lido.insuranceFund"); - bytes32 internal constant EXEC_LAYER_REWARDS_VAULT_POSITION = keccak256("lido.Lido.execLayerRewardsVault"); + bytes32 internal constant EL_REWARDS_VAULT_POSITION = keccak256("lido.Lido.executionLayerRewardsVault"); /// @dev storage slot position of the staking rate limit structure bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("lido.Lido.stakeLimit"); @@ -100,12 +100,12 @@ contract Lido is ILido, StETH, AragonApp { /// @dev number of Lido's validators available in the Beacon state bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); - /// @dev percent in basis points of total pooled ether allowed to withdraw from ExecLayerRewardsVault per LidoOracle report - bytes32 internal constant EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION = keccak256("lido.Lido.execLayerRewardsWithdrawalLimitPoints"); + /// @dev percent in basis points of total pooled ether allowed to withdraw from LidoExecutionLayerRewardsVault per LidoOracle report + bytes32 internal constant EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION = keccak256("lido.Lido.ELRewardsWithdrawalLimitPoints"); /// @dev Just a counter of total amount of execurion layer rewards received by Lido contract /// Not used in the logic - bytes32 internal constant TOTAL_EXEC_LAYER_REWARDS_COLLECTED_POSITION = keccak256("lido.Lido.totalExecLayerRewardsCollected"); + bytes32 internal constant TOTAL_EL_REWARDS_COLLECTED_POSITION = keccak256("lido.Lido.totalELRewardsCollected"); /// @dev Credentials which allows the DAO to withdraw Ether on the 2.0 side bytes32 internal constant WITHDRAWAL_CREDENTIALS_POSITION = keccak256("lido.Lido.withdrawalCredentials"); @@ -257,17 +257,17 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice A payable function for execution layer rewards. Can be called only by ExecLayerRewardsVault contract + * @notice A payable function for execution layer rewards. Can be called only by ExecutionLayerRewardsVault contract * @dev We need a separate payable function because funds received by default payable function * are considered as funds submitted for minting stETH */ - function receiveExecLayerRewards() external payable { - require(msg.sender == EXEC_LAYER_REWARDS_VAULT_POSITION.getStorageAddress()); + function receiveELRewards() external payable { + require(msg.sender == EL_REWARDS_VAULT_POSITION.getStorageAddress()); - TOTAL_EXEC_LAYER_REWARDS_COLLECTED_POSITION.setStorageUint256( - TOTAL_EXEC_LAYER_REWARDS_COLLECTED_POSITION.getStorageUint256().add(msg.value)); + TOTAL_EL_REWARDS_COLLECTED_POSITION.setStorageUint256( + TOTAL_EL_REWARDS_COLLECTED_POSITION.getStorageUint256().add(msg.value)); - emit ExecLayerRewardsReceived(msg.value); + emit ELRewardsReceived(msg.value); } /** @@ -401,26 +401,26 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Sets the address of LidoExecLayerRewardsVault contract - * @param _execLayerRewardsVault Execution layer rewards vault contract address + * @dev Sets the address of LidoExecutionLayerRewardsVault contract + * @param _executionLayerRewardsVault Execution layer rewards vault contract address */ - function setExecLayerRewardsVault(address _execLayerRewardsVault) external { - _auth(SET_EXEC_LAYER_REWARDS_VAULT_ROLE); + function setELRewardsVault(address _executionLayerRewardsVault) external { + _auth(SET_EL_REWARDS_VAULT_ROLE); - EXEC_LAYER_REWARDS_VAULT_POSITION.setStorageAddress(_execLayerRewardsVault); + EL_REWARDS_VAULT_POSITION.setStorageAddress(_executionLayerRewardsVault); - emit LidoExecLayerRewardsVaultSet(_execLayerRewardsVault); + emit ELRewardsVaultSet(_executionLayerRewardsVault); } /** * @dev Sets limit to amount of ETH to withdraw from execution layer rewards vault per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function setExecLayerRewardsWithdrawalLimit(uint16 _limitPoints) external { - _auth(SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE); + function setELRewardsWithdrawalLimit(uint16 _limitPoints) external { + _auth(SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE); - _setBPValue(EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION, _limitPoints); - emit ExecLayerRewardsWithdrawalLimitSet(_limitPoints); + _setBPValue(EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION, _limitPoints); + emit ELRewardsWithdrawalLimitSet(_limitPoints); } /** @@ -435,7 +435,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Updates beacon states, collects rewards from LidoExecLayerRewardsVault and distributes all rewards if beacon balance increased + * @notice Updates beacon states, collects rewards from LidoExecutionLayerRewardsVault and distributes all rewards if beacon balance increased * @dev periodically called by the Oracle contract * @param _beaconValidators number of Lido's keys in the beacon state * @param _beaconBalance summarized balance of Lido-controlled keys in wei @@ -463,20 +463,20 @@ contract Lido is ILido, StETH, AragonApp { BEACON_BALANCE_POSITION.setStorageUint256(_beaconBalance); BEACON_VALIDATORS_POSITION.setStorageUint256(_beaconValidators); - // If LidoExecLayerRewardsVault address is not set just do as if there were no execution layer rewards at all + // If LidoExecutionLayerRewardsVault address is not set just do as if there were no execution layer rewards at all // Otherwise withdraw all rewards and put them to the buffer // Thus, execution layer rewards are handled the same way as beacon rewards - uint256 execLayerRewards; - address execLayerRewardsVaultAddress = getExecLayerRewardsVault(); + uint256 executionLayerRewards; + address executionLayerRewardsVaultAddress = getELRewardsVault(); - if (execLayerRewardsVaultAddress != address(0)) { - execLayerRewards = ILidoExecLayerRewardsVault(execLayerRewardsVaultAddress).withdrawRewards( - (_getTotalPooledEther() * EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS + if (executionLayerRewardsVaultAddress != address(0)) { + executionLayerRewards = ILidoExecutionLayerRewardsVault(executionLayerRewardsVaultAddress).withdrawRewards( + (_getTotalPooledEther() * EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS ); - if (execLayerRewards != 0) { - BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(execLayerRewards)); + if (executionLayerRewards != 0) { + BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(executionLayerRewards)); } } @@ -485,7 +485,7 @@ contract Lido is ILido, StETH, AragonApp { // See ADR #3 for details: https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); - distributeRewards(rewards.add(execLayerRewards)); + distributeRewards(rewards.add(executionLayerRewards)); } } @@ -573,20 +573,20 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Get total amount of execution level rewards collected to Lido contract - * @dev Ether got through LidoExecLayerRewardsVault is kept on this contract's balance the same way + * @dev Ether got through LidoExecutionLayerRewardsVault is kept on this contract's balance the same way * as other buffered Ether is kept (until it gets deposited) * @return uint256 of funds received as execution layer rewards (in wei) */ - function getTotalExecLayerRewardsCollected() external view returns (uint256) { - return TOTAL_EXEC_LAYER_REWARDS_COLLECTED_POSITION.getStorageUint256(); + function getTotalELRewardsCollected() external view returns (uint256) { + return TOTAL_EL_REWARDS_COLLECTED_POSITION.getStorageUint256(); } /** * @notice Get limit in basis points to amount of ETH to withdraw per LidoOracle report * @return uint256 limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function getExecLayerRewardsWithdrawalLimitPoints() external view returns (uint256) { - return EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256(); + function getELRewardsWithdrawalLimitPoints() external view returns (uint256) { + return EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256(); } /** @@ -638,10 +638,10 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns address of the contract set as LidoExecLayerRewardsVault + * @notice Returns address of the contract set as LidoExecutionLayerRewardsVault */ - function getExecLayerRewardsVault() public view returns (address) { - return EXEC_LAYER_REWARDS_VAULT_POSITION.getStorageAddress(); + function getELRewardsVault() public view returns (address) { + return EL_REWARDS_VAULT_POSITION.getStorageAddress(); } /** diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 9984fafde..4fbfee4eb 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -131,23 +131,23 @@ interface ILido { event FeeDistributionSet(uint16 treasuryFeeBasisPoints, uint16 insuranceFeeBasisPoints, uint16 operatorsFeeBasisPoints); /** - * @notice A payable function supposed to be called only by LidoExecLayerRewardsVault contract + * @notice A payable function supposed to be called only by LidoExecutionLayerRewardsVault contract * @dev We need a separate function because funds received by default payable function * are considered as funds submitted by a user for staking */ - function receiveExecLayerRewards() external payable; + function receiveELRewards() external payable; - // The amount of ETH withdrawn from LidoExecLayerRewardsVault contract to Lido contract - event ExecLayerRewardsReceived(uint256 amount); + // The amount of ETH withdrawn from LidoExecutionLayerRewardsVault contract to Lido contract + event ELRewardsReceived(uint256 amount); /** * @dev Sets limit to amount of ETH to withdraw from execution layer rewards vault per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function setExecLayerRewardsWithdrawalLimit(uint16 _limitPoints) external; + function setELRewardsWithdrawalLimit(uint16 _limitPoints) external; - // Percent in basis points of total pooled ether allowed to withdraw from LidoExecLayerRewardsVault per LidoOracle report - event ExecLayerRewardsWithdrawalLimitSet(uint256 limitPoints); + // Percent in basis points of total pooled ether allowed to withdraw from LidoExecutionLayerRewardsVault per LidoOracle report + event ELRewardsWithdrawalLimitSet(uint256 limitPoints); /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` @@ -165,13 +165,13 @@ interface ILido { event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); /** - * @dev Sets the address of LidoExecLayerRewardsVault contract - * @param _execLayerRewardsVault Execution layer rewards vault contract address + * @dev Sets the address of LidoExecutionLayerRewardsVault contract + * @param _executionLayerRewardsVault Execution layer rewards vault contract address */ - function setExecLayerRewardsVault(address _execLayerRewardsVault) external; + function setELRewardsVault(address _executionLayerRewardsVault) external; - // The `execLayerRewardsVault` was set as the execution layer rewards vault for Lido - event LidoExecLayerRewardsVaultSet(address execLayerRewardsVault); + // The `executionLayerRewardsVault` was set as the execution layer rewards vault for Lido + event ELRewardsVaultSet(address executionLayerRewardsVault); /** * @notice Ether on the ETH 2.0 side reported by the oracle diff --git a/contracts/0.4.24/interfaces/ILidoExecLayerRewardsVault.sol b/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol similarity index 90% rename from contracts/0.4.24/interfaces/ILidoExecLayerRewardsVault.sol rename to contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol index caf1faa7d..6f53da857 100644 --- a/contracts/0.4.24/interfaces/ILidoExecLayerRewardsVault.sol +++ b/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol @@ -5,7 +5,7 @@ pragma solidity 0.4.24; -interface ILidoExecLayerRewardsVault { +interface ILidoExecutionLayerRewardsVault { /** * @notice Withdraw all accumulated execution layer rewards to Lido contract diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 5f31c6a58..cc81581f0 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -657,8 +657,8 @@ contract LidoTemplate is IsContract { perms[5] = _state.lido.RESUME_ROLE(); perms[6] = _state.lido.STAKING_PAUSE_ROLE(); perms[7] = _state.lido.STAKING_RESUME_ROLE(); - perms[8] = _state.lido.SET_EXEC_LAYER_REWARDS_VAULT_ROLE(); - perms[9] = _state.lido.SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE(); + perms[8] = _state.lido.SET_EL_REWARDS_VAULT_ROLE(); + perms[9] = _state.lido.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(); for (i = 0; i < 10; ++i) { _createPermissionForVoting(acl, _state.lido, perms[i], voting); diff --git a/contracts/0.8.9/LidoExecLayerRewardsVault.sol b/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol similarity index 95% rename from contracts/0.8.9/LidoExecLayerRewardsVault.sol rename to contracts/0.8.9/LidoExecutionLayerRewardsVault.sol index 24b0a8d34..d6bc219f6 100644 --- a/contracts/0.8.9/LidoExecLayerRewardsVault.sol +++ b/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol @@ -15,7 +15,7 @@ interface ILido { * @dev We need a separate function because funds received by default payable function * will go through entire deposit algorithm */ - function receiveExecLayerRewards() external payable; + function receiveELRewards() external payable; } @@ -25,7 +25,7 @@ interface ILido { * These vault replenishments happen continuously through a day, while withdrawals * happen much less often, only on LidoOracle beacon balance reports */ -contract LidoExecLayerRewardsVault { +contract LidoExecutionLayerRewardsVault { using SafeERC20 for IERC20; address public immutable LIDO; @@ -85,7 +85,7 @@ contract LidoExecLayerRewardsVault { uint256 balance = address(this).balance; amount = (balance > _maxAmount) ? _maxAmount : balance; if (amount > 0) { - ILido(LIDO).receiveExecLayerRewards{value: amount}(); + ILido(LIDO).receiveELRewards{value: amount}(); } return amount; } diff --git a/scripts/multisig/12-check-dao.js b/scripts/multisig/12-check-dao.js index 63c678516..f1a6a53f5 100644 --- a/scripts/multisig/12-check-dao.js +++ b/scripts/multisig/12-check-dao.js @@ -576,8 +576,8 @@ async function assertDaoPermissions({ kernel, lido, oracle, nopsRegistry, agent, 'SET_INSURANCE_FUND', 'STAKING_PAUSE_ROLE', 'STAKING_RESUME_ROLE', - 'SET_EXEC_LAYER_REWARDS_VAULT_ROLE', - 'SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE' + 'SET_EL_REWARDS_VAULT_ROLE', + 'SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE' ], grantee: voting } diff --git a/scripts/multisig/26-deploy-exec-layer-rewards-vault.js b/scripts/multisig/26-deploy-exec-layer-rewards-vault.js index fdebb15d7..93f4285ad 100644 --- a/scripts/multisig/26-deploy-exec-layer-rewards-vault.js +++ b/scripts/multisig/26-deploy-exec-layer-rewards-vault.js @@ -8,8 +8,8 @@ const { APP_NAMES } = require('./constants') const DEPLOYER = process.env.DEPLOYER || '' const REQUIRED_NET_STATE = ['daoInitialSettings', 'depositorParams', `app:${APP_NAMES.LIDO}`, `app:${APP_NAMES.NODE_OPERATORS_REGISTRY}`] -async function deployExecLayerRewardsVault({ web3, artifacts }) { - const appArtifact = 'LidoExecLayerRewardsVault' +async function deployELRewardsVault({ web3, artifacts }) { + const appArtifact = 'LidoExecutionLayerRewardsVault' const netId = await web3.eth.net.getId() logWideSplitter() @@ -27,10 +27,10 @@ async function deployExecLayerRewardsVault({ web3, artifacts }) { logSplitter() const args = [lidoAddress, treasuryAddr] - await saveDeployTx(appArtifact, `tx-26-deploy-exec-layer-rewards-vault.json`, { + await saveDeployTx(appArtifact, `tx-26-deploy-execution-layer-rewards-vault.json`, { arguments: args, from: DEPLOYER || state.multisigAddress }) } -module.exports = runOrWrapScript(deployExecLayerRewardsVault, module) +module.exports = runOrWrapScript(deployELRewardsVault, module) diff --git a/scripts/multisig/27-obtain-deployed-exec-layer-rewards-vault.js b/scripts/multisig/27-obtain-deployed-exec-layer-rewards-vault.js index 8e240f637..0864c67ee 100644 --- a/scripts/multisig/27-obtain-deployed-exec-layer-rewards-vault.js +++ b/scripts/multisig/27-obtain-deployed-exec-layer-rewards-vault.js @@ -7,13 +7,13 @@ const { readNetworkState, persistNetworkState, assertRequiredNetworkState } = re const { APP_NAMES, APP_ARTIFACTS } = require('./constants') const REQUIRED_NET_STATE = [ - 'execLayerRewardsVaultDeployTx', + 'executionLayerRewardsVaultDeployTx', `app:${APP_NAMES.LIDO}`, ] async function obtainInstance({ web3, artifacts }) { // convert dash-ed appName to camel case-d - const appArtifact = 'LidoExecLayerRewardsVault' + const appArtifact = 'LidoExecutionLayerRewardsVault' const netId = await web3.eth.net.getId() logWideSplitter() @@ -23,12 +23,12 @@ async function obtainInstance({ web3, artifacts }) { assertRequiredNetworkState(state, REQUIRED_NET_STATE) logHeader(`${appArtifact} app base`) - const vault = await useOrGetDeployed(appArtifact, null, state.execLayerRewardsVaultDeployTx) + const vault = await useOrGetDeployed(appArtifact, null, state.executionLayerRewardsVaultDeployTx) log(`Checking...`) const lidoAddress = state[`app:${APP_NAMES.LIDO}`].proxyAddress await assertAddresses({ lidoAddress }, vault, appArtifact) persistNetworkState(network.name, netId, state, { - execLayerRewardsVaultAddress: vault.address + executionLayerRewardsVaultAddress: vault.address }) } diff --git a/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js index 27ce8ee63..6082c7e1a 100644 --- a/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js +++ b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js @@ -29,7 +29,7 @@ async function createVoting({ web3, artifacts }) { const state = readNetworkState(network.name, netId) assertRequiredNetworkState(state, REQUIRED_NET_STATE.concat([ - 'app:lido', 'app:node-operators-registry', 'app:oracle', 'execLayerRewardsVaultAddress'])) + 'app:lido', 'app:node-operators-registry', 'app:oracle', 'executionLayerRewardsVaultAddress'])) logSplitter() @@ -45,9 +45,9 @@ async function createVoting({ web3, artifacts }) { const oracleAddress = state[`app:oracle`].proxyAddress const lido = await artifacts.require('Lido').at(lidoAddress) const oracle = await artifacts.require('LidoOracle').at(oracleAddress) - const execLayerRewardsVaultAddress = state.execLayerRewardsVaultAddress + const elRewardsVaultAddress = state.executionLayerRewardsVaultAddress - const execLayerRewardsWithdrawalLimitPoints = 2 // see https://github.com/lidofinance/lido-dao/issues/405 + const elRewardsWithdrawalLimitPoints = 2 // see https://github.com/lidofinance/lido-dao/issues/405 const dailyStakingLimit = ETH(150000) const stakeLimitIncreasePerBlock = calcStakeLimitIncreasePerBlock(dailyStakingLimit) @@ -71,7 +71,7 @@ async function createVoting({ web3, artifacts }) { log(`Voting address:`, yl(votingAddress)) log(`Kernel:`, yl(kernel.address)) log(`ACL:`, yl(acl.address)) - log(`execLayerRewardsWithdrawalLimitPoints: `, yl(execLayerRewardsWithdrawalLimitPoints)) + log(`ELRewardsWithdrawalLimitPoints: `, yl(elRewardsWithdrawalLimitPoints)) log(`dailyStakeLimit: `, yl(dailyStakingLimit)) log(`stakeLimitIncreasePerBlock: `, yl(stakeLimitIncreasePerBlock)) @@ -83,21 +83,21 @@ async function createVoting({ web3, artifacts }) { const oracleUpgradeCallData = await buildUpgradeTransaction('oracle', state, ens, kernel) - const grantSetExecLayerRewardsVaultRoleCallData = await createGrantRoleForLidoAppCallData('SET_EXEC_LAYER_REWARDS_VAULT_ROLE') - const grantSetExecLayerRewardsWithdrawalLimitRoleCallData = await createGrantRoleForLidoAppCallData('SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE') + const grantSetELRewardsVaultRoleCallData = await createGrantRoleForLidoAppCallData('SET_EL_REWARDS_VAULT_ROLE') + const grantSetELRewardsWithdrawalLimitRoleCallData = await createGrantRoleForLidoAppCallData('SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE') const grantResumeRoleCallData = await createGrantRoleForLidoAppCallData('RESUME_ROLE') const grantStakingPauseRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_PAUSE_ROLE') const grantStakingResumeRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_RESUME_ROLE') const grantManageProtocolContractsRoleCallData = await createGrantRoleForLidoAppCallData('MANAGE_PROTOCOL_CONTRACTS_ROLE') - const setExecLayerRewardsVaultCallData = { + const setELRewardsVaultCallData = { to: lidoAddress, - calldata: await lido.contract.methods.setExecLayerRewardsVault(execLayerRewardsVaultAddress).encodeABI() + calldata: await lido.contract.methods.setELRewardsVault(elRewardsVaultAddress).encodeABI() } - const setExecLayerRewardsWithdrawalLimitCallData = { + const setELRewardsWithdrawalLimitCallData = { to: lidoAddress, - calldata: await lido.contract.methods.setExecLayerRewardsWithdrawalLimit(execLayerRewardsWithdrawalLimitPoints).encodeABI() + calldata: await lido.contract.methods.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimitPoints).encodeABI() } const updateOracleVersionToV3CallData = { @@ -116,14 +116,14 @@ async function createVoting({ web3, artifacts }) { ...nodeOperatorsRegistryUpgradeCallData, ...oracleUpgradeCallData, updateOracleVersionToV3CallData, - grantSetExecLayerRewardsVaultRoleCallData, - grantSetExecLAyerRewardsWithdrawalLimitRoleCallData, + grantSetELRewardsVaultRoleCallData, + grantSetELRewardsWithdrawalLimitRoleCallData, grantResumeRoleCallData, grantStakingPauseRoleCallData, grantStakingResumeRoleCallData, grantManageProtocolContractsRoleCallData, - setExecLayerRewardsVaultCallData, - setExecLayerRewardsWithdrawalLimitCallData, + setELRewardsVaultCallData, + setELRewardsWithdrawalLimitCallData, unpauseStakingCallData, ]) @@ -144,14 +144,14 @@ async function createVoting({ web3, artifacts }) { 5) Publishing new implementation in oracle app APM repo 6) Updating implementation of oracle app with new one 7) Call Oracle's finalizeUpgrade_v3() to update internal version counter -8) Grant role SET_EXEC_LAYER_REWARDS_VAULT_ROLE to voting -9) Grant role SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE to voting +8) Grant role SET_EL_REWARDS_VAULT_ROLE to voting +9) Grant role SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE to voting 10) Grant role RESUME_ROLE to voting 11) Grant role STAKING_PAUSE_ROLE to voting 12) Grant role STAKING_RESUME_ROLE to voting 13) Grant role MANAGE_PROTOCOL_CONTRACTS_ROLE to voting -14) Set deployed LidoExecLayerRewardsVault to Lido contract -15) Set Execution Layer rewards withdrawal limit to ${execLayerRewardsWithdrawalLimitPoints} basis points +14) Set deployed LidoExecutionLayerRewardsVault to Lido contract +15) Set Execution Layer rewards withdrawal limit to ${elRewardsWithdrawalLimitPoints} basis points 16) Unpause staking setting daily limit to ${fromE18ToString(dailyStakingLimit)}` await saveCallTxData(votingDesc, tokenManager, 'forward', txName, { diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 7c59bca44..0f779552f 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -11,7 +11,7 @@ const { getEthBalance, formatStEth: formamtStEth, formatBN } = require('../helpe const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const Lido = artifacts.require('LidoMock.sol') -const ExecLayerRewardsVault = artifacts.require('LidoExecLayerRewardsVault.sol') +const ELRewardsVault = artifacts.require('LidoExecutionLayerRewardsVault.sol') const OracleMock = artifacts.require('OracleMock.sol') const DepositContractMock = artifacts.require('DepositContractMock.sol') const ERC20Mock = artifacts.require('ERC20Mock.sol') @@ -57,7 +57,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let appBase, nodeOperatorsRegistryBase, app, oracle, depositContract, operators let treasuryAddr, insuranceAddr let dao, acl - let execLayerRewardsVault, rewarder + let elRewardsVault, rewarder before('deploy base app', async () => { // Deploy the app's base contract. @@ -88,8 +88,8 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.BURN_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_PROTOCOL_CONTRACTS_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_EXEC_LAYER_REWARDS_VAULT_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, { + await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_VAULT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) @@ -117,14 +117,14 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await oracle.setPool(app.address) await depositContract.reset() - execLayerRewardsVault = await ExecLayerRewardsVault.new(app.address, treasuryAddr) - rewarder = await RewardEmulatorMock.new(execLayerRewardsVault.address) - let receipt = await app.setExecLayerRewardsVault(execLayerRewardsVault.address, { from: voting }) - assertEvent(receipt, 'LidoExecLayerRewardsVaultSet', { expectedArgs: { execLayerRewardsVault: execLayerRewardsVault.address } }) + elRewardsVault = await ELRewardsVault.new(app.address, treasuryAddr) + rewarder = await RewardEmulatorMock.new(elRewardsVault.address) + let receipt = await app.setELRewardsVault(elRewardsVault.address, { from: voting }) + assertEvent(receipt, 'ELRewardsVaultSet', { expectedArgs: { executionLayerRewardsVault: elRewardsVault.address } }) - const execLayerRewardsWithdrawalLimitPoints = 3 - receipt = await app.setExecLayerRewardsWithdrawalLimit(execLayerRewardsWithdrawalLimitPoints, { from: voting }) - assertEvent(receipt, 'ExecLayerRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: execLayerRewardsWithdrawalLimitPoints } }) + const elRewardsWithdrawalLimitPoints = 3 + receipt = await app.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimitPoints, { from: voting }) + assertEvent(receipt, 'ELRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: elRewardsWithdrawalLimitPoints } }) }) const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { @@ -156,7 +156,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) } const logLidoState = async () => { - const execLayerRewardsVaultBalance = await getEthBalance(execLayerRewardsVault.address) + const elRewardsVaultBalance = await getEthBalance(elRewardsVault.address) const lidoBalance = await getEthBalance(app.address) const lidoTotalSupply = formatBN(await app.totalSupply()) const lidoTotalPooledEther = formatBN(await app.getTotalPooledEther()) @@ -168,7 +168,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const beaconBalance = formatEther(beaconStat.beaconBalance) console.log({ - execLayerRewardsVaultBalance, + elRewardsVaultBalance, lidoBalance, lidoTotalSupply, lidoTotalPooledEther, @@ -193,7 +193,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) console.log() } - const setupNodeOperatorsForExecLayerRewardsVaultTests = async (userAddress, initialDepositAmount) => { + const setupNodeOperatorsForELRewardsVaultTests = async (userAddress, initialDepositAmount) => { await app.setFee(1000, { from: voting }) // 10% await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) @@ -218,73 +218,73 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) it('Execution layer rewards distribution works when zero rewards reported', async () => { const depositAmount = 32 - const execLayerRewards = depositAmount / TOTAL_BASIS_POINTS + const elRewards = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = 0 - await setupNodeOperatorsForExecLayerRewardsVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(execLayerRewards) }) + await rewarder.reward({ from: user1, value: ETH(elRewards) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + execLayerRewards + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(execLayerRewards)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + execLayerRewards)) - assertBn(await app.getTotalExecLayerRewardsCollected(), ETH(execLayerRewards)) + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(elRewards)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + elRewards)) + assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) }) it('Execution layer rewards distribution works when negative rewards reported', async () => { const depositAmount = 32 - const execLayerRewards = depositAmount / TOTAL_BASIS_POINTS + const elRewards = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = -2 - await setupNodeOperatorsForExecLayerRewardsVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(execLayerRewards) }) + await rewarder.reward({ from: user1, value: ETH(elRewards) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + execLayerRewards + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(execLayerRewards)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + execLayerRewards + beaconRewards)) - assertBn(await app.getTotalExecLayerRewardsCollected(), ETH(execLayerRewards)) + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(elRewards)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + elRewards + beaconRewards)) + assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) }) it('Execution layer rewards distribution works when positive rewards reported', async () => { const depositAmount = 32 - const execLayerRewards = depositAmount / TOTAL_BASIS_POINTS + const elRewards = depositAmount / TOTAL_BASIS_POINTS const beaconRewards = 3 - await setupNodeOperatorsForExecLayerRewardsVaultTests(user2, ETH(depositAmount)) + await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) await oracle.reportBeacon(100, 1, ETH(depositAmount)) - await rewarder.reward({ from: user1, value: ETH(execLayerRewards) }) + await rewarder.reward({ from: user1, value: ETH(elRewards) }) await oracle.reportBeacon(101, 1, ETH(depositAmount + beaconRewards)) const protocolFeePoints = await app.getFee() const shareOfRewardsForStakers = (TOTAL_BASIS_POINTS - protocolFeePoints) / TOTAL_BASIS_POINTS - assertBn(await app.getTotalPooledEther(), ETH(depositAmount + execLayerRewards + beaconRewards)) - assertBn(await app.getBufferedEther(), ETH(execLayerRewards)) - assertBn(await app.balanceOf(user2), STETH(depositAmount + shareOfRewardsForStakers * (execLayerRewards + beaconRewards))) - assertBn(await app.getTotalExecLayerRewardsCollected(), ETH(execLayerRewards)) + assertBn(await app.getTotalPooledEther(), ETH(depositAmount + elRewards + beaconRewards)) + assertBn(await app.getBufferedEther(), ETH(elRewards)) + assertBn(await app.balanceOf(user2), STETH(depositAmount + shareOfRewardsForStakers * (elRewards + beaconRewards))) + assertBn(await app.getTotalELRewardsCollected(), ETH(elRewards)) }) it('Attempt to set invalid execution layer rewards withdrawal limit', async () => { - const initialValue = await app.getExecLayerRewardsWithdrawalLimitPoints() + const initialValue = await app.getELRewardsWithdrawalLimitPoints() - assertEvent(await app.setExecLayerRewardsWithdrawalLimit(1, { from: voting }), 'ExecLayerRewardsWithdrawalLimitSet', { + assertEvent(await app.setELRewardsWithdrawalLimit(1, { from: voting }), 'ELRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: 1 } }) - await assertNoEvent(app.setExecLayerRewardsWithdrawalLimit(1, { from: voting }), 'ExecLayerRewardsWithdrawalLimitSet') + await assertNoEvent(app.setELRewardsWithdrawalLimit(1, { from: voting }), 'ELRewardsWithdrawalLimitSet') - await app.setExecLayerRewardsWithdrawalLimit(10000, { from: voting }) - await assertRevert(app.setExecLayerRewardsWithdrawalLimit(10001, { from: voting }), 'VALUE_OVER_100_PERCENT') + await app.setELRewardsWithdrawalLimit(10000, { from: voting }) + await assertRevert(app.setELRewardsWithdrawalLimit(10001, { from: voting }), 'VALUE_OVER_100_PERCENT') - await app.setExecLayerRewardsWithdrawalLimit(initialValue, { from: voting }) + await app.setELRewardsWithdrawalLimit(initialValue, { from: voting }) // unable to receive execution layer rewards from arbitrary account - assertRevert(app.receiveExecLayerRewards({ from: user1, value: ETH(1) })) + assertRevert(app.receiveELRewards({ from: user1, value: ETH(1) })) }) it('setFee works', async () => { diff --git a/test/0.8.9/lido-exec-layer-rewards-vault.js b/test/0.8.9/lido-exec-layer-rewards-vault.js index 184d452a4..18e44f82f 100644 --- a/test/0.8.9/lido-exec-layer-rewards-vault.js +++ b/test/0.8.9/lido-exec-layer-rewards-vault.js @@ -4,7 +4,7 @@ const { newDao, newApp } = require('../0.4.24/helpers/dao') const { assert } = require('chai') -const LidoExecLayerRewardsVault = artifacts.require('LidoExecLayerRewardsVault.sol') +const LidoELRewardsVault = artifacts.require('LidoExecutionLayerRewardsVault.sol') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') @@ -20,8 +20,8 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') const stETH = ETH const stETHShares = ETH -contract('LidoExecLayerRewardsVault', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { - let oracle, lido, execLayerRewardsVault +contract('LidoExecutionLayerRewardsVault', ([appManager, voting, deployer, depositor, anotherAccount, ...otherAccounts]) => { + let oracle, lido, elRewardsVault let treasuryAddr let dao, acl, operators @@ -54,27 +54,27 @@ contract('LidoExecLayerRewardsVault', ([appManager, voting, deployer, depositor, await oracle.setPool(lido.address) await depositContract.reset() - execLayerRewardsVault = await LidoExecLayerRewardsVault.new(lido.address, treasuryAddr, { from: deployer }) + elRewardsVault = await LidoELRewardsVault.new(lido.address, treasuryAddr, { from: deployer }) }) it('Addresses which are not Lido contract cannot withdraw from execution layer rewards vault', async () => { - await assertRevert(execLayerRewardsVault.withdrawRewards(12345, { from: anotherAccount }), 'ONLY_LIDO_CAN_WITHDRAW') - await assertRevert(execLayerRewardsVault.withdrawRewards(12345, { from: deployer }), 'ONLY_LIDO_CAN_WITHDRAW') - await assertRevert(execLayerRewardsVault.withdrawRewards(12345, { from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(elRewardsVault.withdrawRewards(12345, { from: anotherAccount }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(elRewardsVault.withdrawRewards(12345, { from: deployer }), 'ONLY_LIDO_CAN_WITHDRAW') + await assertRevert(elRewardsVault.withdrawRewards(12345, { from: appManager }), 'ONLY_LIDO_CAN_WITHDRAW') }) it('Execution layer rewards vault can receive Ether by plain transfers (no call data)', async () => { - const before = +(await web3.eth.getBalance(execLayerRewardsVault.address)).toString() + const before = +(await web3.eth.getBalance(elRewardsVault.address)).toString() const amount = 0.02 - await web3.eth.sendTransaction({ to: execLayerRewardsVault.address, from: anotherAccount, value: ETH(amount) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(before + amount)) + await web3.eth.sendTransaction({ to: elRewardsVault.address, from: anotherAccount, value: ETH(amount) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(before + amount)) }) it('Execution layer rewards vault refuses to receive Ether by transfers with call data', async () => { - const before = +(await web3.eth.getBalance(execLayerRewardsVault.address)).toString() + const before = +(await web3.eth.getBalance(elRewardsVault.address)).toString() const amount = 0.02 await assertRevert( - web3.eth.sendTransaction({ to: execLayerRewardsVault.address, from: anotherAccount, value: ETH(amount), data: '0x12345678' }) + web3.eth.sendTransaction({ to: elRewardsVault.address, from: anotherAccount, value: ETH(amount), data: '0x12345678' }) ) }) @@ -110,11 +110,11 @@ contract('LidoExecLayerRewardsVault', ([appManager, voting, deployer, depositor, }) it(`can't recover zero ERC20 amount`, async () => { - assertRevert(execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(0)), `ZERO_RECOVERY_AMOUNT`) + assertRevert(elRewardsVault.recoverERC20(mockERC20Token.address, bn(0)), `ZERO_RECOVERY_AMOUNT`) }) it(`can't recover zero-address ERC20`, async () => { - assertRevert(execLayerRewardsVault.recoverERC20(ZERO_ADDRESS, bn(10))) + assertRevert(elRewardsVault.recoverERC20(ZERO_ADDRESS, bn(10))) }) it(`can't recover stETH by recoverERC20`, async () => { @@ -124,77 +124,74 @@ contract('LidoExecLayerRewardsVault', ([appManager, voting, deployer, depositor, await web3.eth.sendTransaction({ from: anotherAccount, to: lido.address, value: ETH(10) }) // check 10 stETH minted on balance assertBn(await lido.balanceOf(anotherAccount), stETH(10)) - // transfer 5 stETH to the execLayerRewardsVault account - await lido.transfer(execLayerRewardsVault.address, stETH(5), { from: anotherAccount }) + // transfer 5 stETH to the elRewardsVault account + await lido.transfer(elRewardsVault.address, stETH(5), { from: anotherAccount }) assertBn(await lido.balanceOf(anotherAccount), stETH(5)) - assertBn(await lido.balanceOf(execLayerRewardsVault.address), stETH(5)) + assertBn(await lido.balanceOf(elRewardsVault.address), stETH(5)) }) it(`recover some accidentally sent ERC20`, async () => { - // distribute deployer's balance among anotherAccount and execLayerRewardsVault + // distribute deployer's balance among anotherAccount and elRewardsVault await mockERC20Token.transfer(anotherAccount, bn(400000), { from: deployer }) - await mockERC20Token.transfer(execLayerRewardsVault.address, bn(600000), { from: deployer }) + await mockERC20Token.transfer(elRewardsVault.address, bn(600000), { from: deployer }) // check the resulted state assertBn(await mockERC20Token.balanceOf(deployer), bn(0)) assertBn(await mockERC20Token.balanceOf(anotherAccount), bn(400000)) - assertBn(await mockERC20Token.balanceOf(execLayerRewardsVault.address), bn(600000)) + assertBn(await mockERC20Token.balanceOf(elRewardsVault.address), bn(600000)) // recover ERC20 - const firstReceipt = await execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(100000), { from: deployer }) + const firstReceipt = await elRewardsVault.recoverERC20(mockERC20Token.address, bn(100000), { from: deployer }) assertEvent(firstReceipt, `ERC20Recovered`, { expectedArgs: { requestedBy: deployer, token: mockERC20Token.address, amount: bn(100000) } }) - const secondReceipt = await execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(400000), { from: anotherAccount }) + const secondReceipt = await elRewardsVault.recoverERC20(mockERC20Token.address, bn(400000), { from: anotherAccount }) assertEvent(secondReceipt, `ERC20Recovered`, { expectedArgs: { requestedBy: anotherAccount, token: mockERC20Token.address, amount: bn(400000) } }) // check balances again - assertBn(await mockERC20Token.balanceOf(execLayerRewardsVault.address), bn(100000)) + assertBn(await mockERC20Token.balanceOf(elRewardsVault.address), bn(100000)) assertBn(await mockERC20Token.balanceOf(treasuryAddr), bn(500000)) assertBn(await mockERC20Token.balanceOf(deployer), bn(0)) assertBn(await mockERC20Token.balanceOf(anotherAccount), bn(400000)) // recover last portion - const lastReceipt = await execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(100000), { from: anotherAccount }) + const lastReceipt = await elRewardsVault.recoverERC20(mockERC20Token.address, bn(100000), { from: anotherAccount }) assertEvent(lastReceipt, `ERC20Recovered`, { expectedArgs: { requestedBy: anotherAccount, token: mockERC20Token.address, amount: bn(100000) } }) // balance is zero already, have to be reverted - assertRevert( - execLayerRewardsVault.recoverERC20(mockERC20Token.address, bn(1), { from: deployer }), - `ERC20: transfer amount exceeds balance` - ) + assertRevert(elRewardsVault.recoverERC20(mockERC20Token.address, bn(1), { from: deployer }), `ERC20: transfer amount exceeds balance`) }) it(`can't recover zero-address ERC721(NFT)`, async () => { - assertRevert(execLayerRewardsVault.recoverERC721(ZERO_ADDRESS, 0)) + assertRevert(elRewardsVault.recoverERC721(ZERO_ADDRESS, 0)) }) it(`recover some accidentally sent NFTs`, async () => { - // send nft1 to anotherAccount and nft2 to the execLayerRewardsVault address + // send nft1 to anotherAccount and nft2 to the elRewardsVault address await mockNFT.transferFrom(deployer, anotherAccount, nft1, { from: deployer }) - await mockNFT.transferFrom(deployer, execLayerRewardsVault.address, nft2, { from: deployer }) + await mockNFT.transferFrom(deployer, elRewardsVault.address, nft2, { from: deployer }) // check the new holders' rights assertBn(await mockNFT.balanceOf(deployer), bn(0)) assertBn(await mockNFT.balanceOf(anotherAccount), bn(1)) - assertBn(await mockNFT.balanceOf(execLayerRewardsVault.address), bn(1)) + assertBn(await mockNFT.balanceOf(elRewardsVault.address), bn(1)) // recover nft2 should work - const receiptNfc2 = await execLayerRewardsVault.recoverERC721(mockNFT.address, nft2, { from: anotherAccount }) + const receiptNfc2 = await elRewardsVault.recoverERC721(mockNFT.address, nft2, { from: anotherAccount }) assertEvent(receiptNfc2, `ERC721Recovered`, { expectedArgs: { requestedBy: anotherAccount, token: mockNFT.address, tokenId: nft2 } }) // but nft1 recovery should revert - assertRevert(execLayerRewardsVault.recoverERC721(mockNFT.address, nft1), `ERC721: transfer caller is not owner nor approved`) + assertRevert(elRewardsVault.recoverERC721(mockNFT.address, nft1), `ERC721: transfer caller is not owner nor approved`) - // send nft1 to execLayerRewardsVault and recover it - await mockNFT.transferFrom(anotherAccount, execLayerRewardsVault.address, nft1, { from: anotherAccount }) - const receiptNft1 = await execLayerRewardsVault.recoverERC721(mockNFT.address, nft1, { from: deployer }) + // send nft1 to elRewardsVault and recover it + await mockNFT.transferFrom(anotherAccount, elRewardsVault.address, nft1, { from: anotherAccount }) + const receiptNft1 = await elRewardsVault.recoverERC721(mockNFT.address, nft1, { from: deployer }) assertEvent(receiptNft1, `ERC721Recovered`, { expectedArgs: { requestedBy: deployer, token: mockNFT.address, tokenId: nft1 } }) diff --git a/test/deposit.test.js b/test/deposit.test.js index 3c6742a69..295acb8ad 100644 --- a/test/deposit.test.js +++ b/test/deposit.test.js @@ -83,8 +83,8 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use await acl.createPermission(voting, app.address, await app.RESUME_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_FEE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.MANAGE_WITHDRAWAL_KEY(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_EXEC_LAYER_REWARDS_VAULT_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, { + await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_VAULT_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) diff --git a/test/scenario/execution_layer_rewards_after_the_merge.js b/test/scenario/execution_layer_rewards_after_the_merge.js index 24ae9b366..3cfcd7a0c 100644 --- a/test/scenario/execution_layer_rewards_after_the_merge.js +++ b/test/scenario/execution_layer_rewards_after_the_merge.js @@ -10,7 +10,7 @@ const { signDepositData } = require('../0.8.9/helpers/signatures') const { waitBlocks } = require('../helpers/blockchain') const addresses = require('@aragon/contract-helpers-test/src/addresses') -const LidoExecLayerRewardsVault = artifacts.require('LidoExecLayerRewardsVault.sol') +const LidoELRewardsVault = artifacts.require('LidoExecutionLayerRewardsVault.sol') const RewardEmulatorMock = artifacts.require('RewardEmulatorMock.sol') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') @@ -33,14 +33,14 @@ contract('Lido: merge acceptance', (addresses) => { // unrelated address nobody, // Execution layer rewards source - userExecLayerRewards + userELRewards ] = addresses let pool, nodeOperatorRegistry, token let oracleMock, depositContractMock let treasuryAddr, insuranceAddr, guardians let depositSecurityModule, depositRoot - let rewarder, execLayerRewardsVault + let rewarder, elRewardsVault // Total fee is 1% const totalFeePoints = 0.01 * TOTAL_BASIS_POINTS @@ -103,16 +103,16 @@ contract('Lido: merge acceptance', (addresses) => { depositRoot = await depositContractMock.get_deposit_root() - execLayerRewardsVault = await LidoExecLayerRewardsVault.new(pool.address, treasuryAddr) - await pool.setExecLayerRewardsVault(execLayerRewardsVault.address, { from: voting }) + elRewardsVault = await LidoELRewardsVault.new(pool.address, treasuryAddr) + await pool.setELRewardsVault(elRewardsVault.address, { from: voting }) // At first go through tests assuming there is no withdrawal limit - await pool.setExecLayerRewardsWithdrawalLimit(TOTAL_BASIS_POINTS, { from: voting }) + await pool.setELRewardsWithdrawalLimit(TOTAL_BASIS_POINTS, { from: voting }) - rewarder = await RewardEmulatorMock.new(execLayerRewardsVault.address) + rewarder = await RewardEmulatorMock.new(elRewardsVault.address) assertBn(await web3.eth.getBalance(rewarder.address), ETH(0), 'rewarder balance') - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Fee and its distribution are in basis points, 10000 corresponding to 100% @@ -347,8 +347,8 @@ contract('Lido: merge acceptance', (addresses) => { }) it('collect 9 ETH execution layer rewards to the vault', async () => { - await rewarder.reward({ from: userExecLayerRewards, value: ETH(9) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(9), 'Execution layer rewards vault balance') + await rewarder.reward({ from: userELRewards, value: ETH(9) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(9), 'Execution layer rewards vault balance') }) it('the oracle reports balance increase on Ethereum2 side (+32 ETH) and claims collected execution layer rewards (+9 ETH)', async () => { @@ -369,7 +369,7 @@ contract('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(96)) // Execution layer rewards just claimed - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares increased because fee minted (fee shares added) // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -378,12 +378,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(newTotalShares, new BN('97289047169125663202'), 'total shares') - const execLayerRewards = 9 + const elRewards = 9 // Total pooled Ether increased const newTotalPooledEther = await pool.getTotalPooledEther() - assertBn(newTotalPooledEther, ETH(33 + 96 + execLayerRewards), 'total pooled ether') + assertBn(newTotalPooledEther, ETH(33 + 96 + elRewards), 'total pooled ether') // Ether2 stat reported by the pool changed correspondingly @@ -392,12 +392,12 @@ contract('Lido: merge acceptance', (addresses) => { assertBn(ether2Stat.beaconBalance, ETH(96), 'remote ether2') // Buffered Ether amount changed on execution layer rewards - assertBn(await pool.getBufferedEther(), ETH(33 + execLayerRewards), 'buffered ether') + assertBn(await pool.getBufferedEther(), ETH(33 + elRewards), 'buffered ether') // New tokens was minted to distribute fee - assertBn(await token.totalSupply(), tokens(129 + execLayerRewards), 'token total supply') + assertBn(await token.totalSupply(), tokens(129 + elRewards), 'token total supply') - const reward = toBN(ETH(96 - 64 + execLayerRewards)) + const reward = toBN(ETH(96 - 64 + elRewards)) const mintedAmount = new BN(totalFeePoints).mul(reward).divn(TOTAL_BASIS_POINTS) // Token user balances increased @@ -438,11 +438,11 @@ contract('Lido: merge acceptance', (addresses) => { }) it('collect another 7 ETH execution layer rewards to the vault', async () => { - await rewarder.reward({ from: userExecLayerRewards, value: ETH(2) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(2), 'Execution layer rewards vault balance') + await rewarder.reward({ from: userELRewards, value: ETH(2) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(2), 'Execution layer rewards vault balance') - await rewarder.reward({ from: userExecLayerRewards, value: ETH(5) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(7), 'Execution layer rewards vault balance') + await rewarder.reward({ from: userELRewards, value: ETH(5) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(7), 'Execution layer rewards vault balance') }) it('the oracle reports same balance on Ethereum2 side (+0 ETH) and claims collected execution layer rewards (+7 ETH)', async () => { @@ -461,7 +461,7 @@ contract('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(96)) // Execution layer rewards just claimed - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -502,8 +502,8 @@ contract('Lido: merge acceptance', (addresses) => { }) it('collect another 5 ETH execution layer rewards to the vault', async () => { - await rewarder.reward({ from: userExecLayerRewards, value: ETH(5) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(5), 'Execution layer rewards vault balance') + await rewarder.reward({ from: userELRewards, value: ETH(5) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(5), 'Execution layer rewards vault balance') }) it('the oracle reports loss on Ethereum2 side (-2 ETH) and claims collected execution layer rewards (+5 ETH)', async () => { @@ -522,7 +522,7 @@ contract('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(94)) // Execution layer rewards just claimed - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -560,8 +560,8 @@ contract('Lido: merge acceptance', (addresses) => { }) it('collect another 3 ETH execution layer rewards to the vault', async () => { - await rewarder.reward({ from: userExecLayerRewards, value: ETH(3) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(3), 'Execution layer rewards vault balance') + await rewarder.reward({ from: userELRewards, value: ETH(3) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(3), 'Execution layer rewards vault balance') }) it('the oracle reports loss on Ethereum2 side (-3 ETH) and claims collected execution layer rewards (+3 ETH)', async () => { @@ -580,7 +580,7 @@ contract('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(91)) // Execution layer rewards just claimed - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -616,8 +616,8 @@ contract('Lido: merge acceptance', (addresses) => { }) it('collect another 2 ETH execution layer rewards to the vault', async () => { - await rewarder.reward({ from: userExecLayerRewards, value: ETH(2) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(2), 'Execution layer rewards vault balance') + await rewarder.reward({ from: userELRewards, value: ETH(2) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(2), 'Execution layer rewards vault balance') }) it('the oracle reports loss on Ethereum2 side (-8 ETH) and claims collected execution layer rewards (+2 ETH)', async () => { @@ -636,7 +636,7 @@ contract('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(83)) // Execution layer rewards just claimed - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares preserved because fee shares NOT minted // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -670,8 +670,8 @@ contract('Lido: merge acceptance', (addresses) => { }) it('collect another 3 ETH execution layer rewards to the vault', async () => { - await rewarder.reward({ from: userExecLayerRewards, value: ETH(3) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(3), 'Execution layer vault balance') + await rewarder.reward({ from: userELRewards, value: ETH(3) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(3), 'Execution layer vault balance') }) it('the oracle reports balance increase on Ethereum2 side (+2 ETH) and claims collected execution layer rewards (+3 ETH)', async () => { @@ -690,7 +690,7 @@ contract('Lido: merge acceptance', (addresses) => { await oracleMock.reportBeacon(epoch, 2, ETH(85)) // Execution layer rewards just claimed - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') + assertBn(await web3.eth.getBalance(elRewardsVault.address), ETH(0), 'Execution layer rewards vault balance') // Total shares increased because fee minted (fee shares added) // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) @@ -734,7 +734,7 @@ contract('Lido: merge acceptance', (addresses) => { assertBn((await token.balanceOf(nodeOperator2.address)).divn(10), new BN('12164764492753623'), 'operator_2 tokens') }) - it('collect 0.1 ETH execution layer rewards to execLayerRewardsVault and withdraw it entirely by means of multiple oracle reports (+1 ETH)', async () => { + it('collect 0.1 ETH execution layer rewards to elRewardsVault and withdraw it entirely by means of multiple oracle reports (+1 ETH)', async () => { const toNum = (bn) => { return +bn.toString() } @@ -746,7 +746,7 @@ contract('Lido: merge acceptance', (addresses) => { } // Specify different withdrawal limits for a few epochs to test different values - const getExecLayerRewardsWithdrawalLimitFromEpoch = (_epoch) => { + const getELRewardsWithdrawalLimitFromEpoch = (_epoch) => { if (_epoch === 106) { return 2 } else if (_epoch === 107) { @@ -756,58 +756,58 @@ contract('Lido: merge acceptance', (addresses) => { } } - const execLayerRewards = toE18(0.1) - await rewarder.reward({ from: userExecLayerRewards, value: fromNum(execLayerRewards) }) - assertBn(await web3.eth.getBalance(execLayerRewardsVault.address), fromNum(execLayerRewards), 'Execution layer rewards vault balance') + const elRewards = toE18(0.1) + await rewarder.reward({ from: userELRewards, value: fromNum(elRewards) }) + assertBn(await web3.eth.getBalance(elRewardsVault.address), fromNum(elRewards), 'Execution layer rewards vault balance') let epoch = 106 let lastBeaconBalance = toE18(85) - await pool.setExecLayerRewardsWithdrawalLimit(getExecLayerRewardsWithdrawalLimitFromEpoch(epoch), { from: voting }) + await pool.setELRewardsWithdrawalLimit(getELRewardsWithdrawalLimitFromEpoch(epoch), { from: voting }) - let execLayerRewardsWithdrawalLimitPoints = toNum(await pool.getExecLayerRewardsWithdrawalLimitPoints()) - let execLayerRewardsVaultBalance = toNum(await web3.eth.getBalance(execLayerRewardsVault.address)) + let elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimitPoints()) + let elRewardsVaultBalance = toNum(await web3.eth.getBalance(elRewardsVault.address)) let totalPooledEther = toNum(await pool.getTotalPooledEther()) let bufferedEther = toNum(await pool.getBufferedEther()) let totalSupply = toNum(await pool.totalSupply()) const beaconBalanceInc = toE18(1) - let execLayerRewardsWithdrawn = 0 + let elRewardsWithdrawn = 0 // Do multiple oracle reports to withdraw all ETH from execution layer rewards vault - while (execLayerRewardsVaultBalance > 0) { - const execLayerRewardsWithdrawalLimit = getExecLayerRewardsWithdrawalLimitFromEpoch(epoch) - await pool.setExecLayerRewardsWithdrawalLimit(execLayerRewardsWithdrawalLimit, { from: voting }) - execLayerRewardsWithdrawalLimitPoints = toNum(await pool.getExecLayerRewardsWithdrawalLimitPoints()) + while (elRewardsVaultBalance > 0) { + const elRewardsWithdrawalLimit = getELRewardsWithdrawalLimitFromEpoch(epoch) + await pool.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimit, { from: voting }) + elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimitPoints()) - const maxExecLayerRewardsAmountPerWithdrawal = Math.floor( - ((totalPooledEther + beaconBalanceInc) * execLayerRewardsWithdrawalLimitPoints) / TOTAL_BASIS_POINTS + const maxELRewardsAmountPerWithdrawal = Math.floor( + ((totalPooledEther + beaconBalanceInc) * elRewardsWithdrawalLimitPoints) / TOTAL_BASIS_POINTS ) - const execLayerRewardsToWithdraw = Math.min(maxExecLayerRewardsAmountPerWithdrawal, execLayerRewardsVaultBalance) + const elRewardsToWithdraw = Math.min(maxELRewardsAmountPerWithdrawal, elRewardsVaultBalance) // Reporting balance increase await oracleMock.reportBeacon(epoch, 2, fromNum(lastBeaconBalance + beaconBalanceInc)) assertBn( - await web3.eth.getBalance(execLayerRewardsVault.address), - execLayerRewardsVaultBalance - execLayerRewardsToWithdraw, + await web3.eth.getBalance(elRewardsVault.address), + elRewardsVaultBalance - elRewardsToWithdraw, 'Execution layer rewards vault balance' ) - assertBn(await pool.getTotalPooledEther(), totalPooledEther + beaconBalanceInc + execLayerRewardsToWithdraw, 'total pooled ether') + assertBn(await pool.getTotalPooledEther(), totalPooledEther + beaconBalanceInc + elRewardsToWithdraw, 'total pooled ether') - assertBn(await pool.totalSupply(), totalSupply + beaconBalanceInc + execLayerRewardsToWithdraw, 'token total supply') + assertBn(await pool.totalSupply(), totalSupply + beaconBalanceInc + elRewardsToWithdraw, 'token total supply') - assertBn(await pool.getBufferedEther(), bufferedEther + execLayerRewardsToWithdraw, 'buffered ether') + assertBn(await pool.getBufferedEther(), bufferedEther + elRewardsToWithdraw, 'buffered ether') - execLayerRewardsVaultBalance = toNum(await web3.eth.getBalance(execLayerRewardsVault.address)) + elRewardsVaultBalance = toNum(await web3.eth.getBalance(elRewardsVault.address)) totalPooledEther = toNum(await pool.getTotalPooledEther()) bufferedEther = toNum(await pool.getBufferedEther()) totalSupply = toNum(await pool.totalSupply()) lastBeaconBalance += beaconBalanceInc epoch += 1 - execLayerRewardsWithdrawn += execLayerRewardsToWithdraw + elRewardsWithdrawn += elRewardsToWithdraw } - assert.equal(execLayerRewardsWithdrawn, execLayerRewards) + assert.equal(elRewardsWithdrawn, elRewards) }) }) diff --git a/test/scenario/helpers/deploy.js b/test/scenario/helpers/deploy.js index 7d7fd02a3..ba809d5b7 100644 --- a/test/scenario/helpers/deploy.js +++ b/test/scenario/helpers/deploy.js @@ -74,8 +74,8 @@ async function deployDaoAndPool(appManager, voting) { DEPOSIT_ROLE, STAKING_PAUSE_ROLE, STAKING_RESUME_ROLE, - SET_EXEC_LAYER_REWARDS_VAULT_ROLE, - SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE, + SET_EL_REWARDS_VAULT_ROLE, + SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, NODE_OPERATOR_REGISTRY_ADD_NODE_OPERATOR_ROLE, NODE_OPERATOR_REGISTRY_SET_NODE_OPERATOR_ACTIVE_ROLE, @@ -92,8 +92,8 @@ async function deployDaoAndPool(appManager, voting) { pool.DEPOSIT_ROLE(), pool.STAKING_PAUSE_ROLE(), pool.STAKING_RESUME_ROLE(), - pool.SET_EXEC_LAYER_REWARDS_VAULT_ROLE(), - pool.SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE(), + pool.SET_EL_REWARDS_VAULT_ROLE(), + pool.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), nodeOperatorRegistry.MANAGE_SIGNING_KEYS(), nodeOperatorRegistry.ADD_NODE_OPERATOR_ROLE(), nodeOperatorRegistry.SET_NODE_OPERATOR_ACTIVE_ROLE(), @@ -112,8 +112,8 @@ async function deployDaoAndPool(appManager, voting) { acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, STAKING_PAUSE_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, STAKING_RESUME_ROLE, appManager, { from: appManager }), - acl.createPermission(voting, pool.address, SET_EXEC_LAYER_REWARDS_VAULT_ROLE, appManager, { from: appManager }), - acl.createPermission(voting, pool.address, SET_EXEC_LAYER_REWARDS_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, SET_EL_REWARDS_VAULT_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }), // Allow depositor to deposit buffered ether acl.createPermission(depositSecurityModule.address, pool.address, DEPOSIT_ROLE, appManager, { from: appManager }), From f99b2f7b9be9a50e24387d587e12bc04c21f320f Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 16 May 2022 22:07:30 +0000 Subject: [PATCH 139/159] chore: improve stake rate limit lib docs --- contracts/0.4.24/lib/StakeRateLimitUtils.sol | 31 ++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/lib/StakeRateLimitUtils.sol b/contracts/0.4.24/lib/StakeRateLimitUtils.sol index 5052d0301..ce2d2fbee 100644 --- a/contracts/0.4.24/lib/StakeRateLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeRateLimitUtils.sol @@ -32,8 +32,11 @@ import "@aragon/os/contracts/common/UnstructuredStorage.sol"; // // solidity <0.6 doesn't support top-level structs -// using the sentinel library to derive from +// using the library to have a proper namespace library StakeLimitState { + /** + * @dev Internal representation struct (slot-wide) + */ struct Data { uint32 prevStakeBlockNumber; uint96 prevStakeLimit; @@ -45,11 +48,19 @@ library StakeLimitState { library StakeLimitUnstructuredStorage { using UnstructuredStorage for bytes32; + /// @dev Storage offset for `maxStakeLimit` (bits) uint256 internal constant MAX_STAKE_LIMIT_OFFSET = 160; + /// @dev Storage offset for `maxStakeLimitGrowthBlocks` (bits) uint256 internal constant MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET = 128; + /// @dev Storage offset for `prevStakeLimit` (bits) uint256 internal constant PREV_STAKE_LIMIT_OFFSET = 32; + /// @dev Storage offset for `prevStakeBlockNumber` (bits) uint256 internal constant PREV_STAKE_BLOCK_NUMBER_OFFSET = 0; + /** + * @dev Read stake limit state from the unstructure storage position + * @param _position storage offset + */ function getStorageStakeLimitStruct(bytes32 _position) internal view returns (StakeLimitState.Data memory ret) { uint256 slotValue = _position.getStorageUint256(); @@ -59,6 +70,11 @@ library StakeLimitUnstructuredStorage { ret.maxStakeLimit = uint96(slotValue >> MAX_STAKE_LIMIT_OFFSET); } + /** + * @dev Write stake limit state to the unstructure storage position + * @param _position storage offset + * @param _data stake limit state structure instance + */ function setStorageStakeLimitStruct(bytes32 _position, StakeLimitState.Data memory _data) internal { _position.setStorageUint256( uint256(_data.prevStakeBlockNumber) << PREV_STAKE_BLOCK_NUMBER_OFFSET @@ -72,6 +88,9 @@ library StakeLimitUnstructuredStorage { library StakeRateLimitUtils { /** * @notice Calculate stake limit for the current block. + * @dev special returns: + * - 0 if limit is exhausted or staking pause was set + * - 2^256 - 1 if there is no limit was set */ function calculateCurrentStakeLimit(StakeLimitState.Data memory _data) internal view returns(uint256 limit) { if (!isStakingLimitApplied(_data)) { @@ -90,7 +109,7 @@ library StakeRateLimitUtils { } /** - * @notice check if staking is on pause (i.e. slot contains zero value) + * @notice check if staking is on pause (i.e. every byte in the slot has a zero value) */ function isStakingPaused(StakeLimitState.Data memory _data) internal pure returns(bool) { return (_data.maxStakeLimit == 0) @@ -106,6 +125,11 @@ library StakeRateLimitUtils { return _data.maxStakeLimit != 0; } + /** + * @notice prepare stake limit repr to resume staking with the desired limits + * @param _maxStakeLimit stake limit max value + * @param _stakeLimitIncreasePerBlock stake limit increase (restoration) per block + */ function resumeStakingWithNewLimit( StakeLimitState.Data memory _data, uint256 _maxStakeLimit, @@ -133,6 +157,9 @@ library StakeRateLimitUtils { return _data; } + /** + * @notice update stake limit repr after submitting user's eth + */ function updatePrevStakeLimit( StakeLimitState.Data memory _data, uint256 _newPrevLimit From 41ecebf2814d40d56084c8b1f79fff33b9ec758d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 05:47:29 +0000 Subject: [PATCH 140/159] test: add tests for `setLastDepositBlock` --- test/0.8.9/deposit-security-module.test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/0.8.9/deposit-security-module.test.js b/test/0.8.9/deposit-security-module.test.js index c1985318f..ace5bbbe8 100644 --- a/test/0.8.9/deposit-security-module.test.js +++ b/test/0.8.9/deposit-security-module.test.js @@ -573,6 +573,12 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { }) }) describe('levers', () => { + it('setLastDepositBlock', async () => { + assertRevert(depositSecurityModule.setLastDepositBlock(10, { from: stranger }), `not an owner`) + + await depositSecurityModule.setLastDepositBlock(10, { from: owner }) + assert.equal(await depositSecurityModule.getLastDepositBlock(), 10, 'wrong last deposit block') + }) it('setNodeOperatorsRegistry sets new value for nodeOperatorsRegistry if called by owner', async () => { const newNodeOperatorsRegistry = await NodeOperatorsRegistryMockForSecurityModule.new() assert.notEqual( From f4b8257bdd814c236ae110158b0a27b238948563 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 06:06:36 +0000 Subject: [PATCH 141/159] fix: doc and naming cleanups --- contracts/0.4.24/Lido.sol | 14 +++---- contracts/0.4.24/interfaces/ILido.sol | 5 +++ ...RateLimitUtils.sol => StakeLimitUtils.sol} | 16 +++++--- .../test_helpers/StakeLimitUtilsMock.sol | 4 +- test/0.4.24/lido.test.js | 40 +++++++++---------- test/0.4.24/staking-limit.test.js | 2 +- 6 files changed, 46 insertions(+), 35 deletions(-) rename contracts/0.4.24/lib/{StakeRateLimitUtils.sol => StakeLimitUtils.sol} (93%) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 032443713..b81a904cd 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -17,7 +17,7 @@ import "./interfaces/ILidoExecutionLayerRewardsVault.sol"; import "./StETH.sol"; -import "./lib/StakeRateLimitUtils.sol"; +import "./lib/StakeLimitUtils.sol"; interface IERC721 { @@ -48,7 +48,7 @@ contract Lido is ILido, StETH, AragonApp { using SafeMath for uint256; using UnstructuredStorage for bytes32; using StakeLimitUnstructuredStorage for bytes32; - using StakeRateLimitUtils for StakeLimitState.Data; + using StakeLimitUtils for StakeLimitState.Data; /// ACL bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); @@ -212,13 +212,13 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns full info about stake limit + * @notice Returns internal info about stake limit * @dev Might be used for the advanced integration requests. * @return - * `maxStakeLimit` internal max stake limit represenation - * `maxStakeLimitGrowthBlocks` internal max stake limit full restoration blocks - * `prevStakeLimit` internal previously reached stake limit represenation - * `prevStakeBlockNumber` internal prevously seen block number represenation + * `maxStakeLimit` max stake limit + * `maxStakeLimitGrowthBlocks` blocks needed to restore max stake limit from the fully exhausted state + * `prevStakeLimit` previously reached stake limit + * `prevStakeBlockNumber` prevously seen block number */ function getStakeLimitInternalInfo() external view returns ( uint256 maxStakeLimit, diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 4fbfee4eb..174746601 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -63,6 +63,11 @@ interface ILido { /** * @notice Returns internal info about stake limit * @dev Might be used for the advanced integration requests. + * @return + * `maxStakeLimit` max stake limit + * `maxStakeLimitGrowthBlocks` blocks needed to restore max stake limit from the fully exhausted state + * `prevStakeLimit` previously reached stake limit + * `prevStakeBlockNumber` prevously seen block number */ function getStakeLimitInternalInfo() external view returns ( uint256 maxStakeLimit, diff --git a/contracts/0.4.24/lib/StakeRateLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol similarity index 93% rename from contracts/0.4.24/lib/StakeRateLimitUtils.sol rename to contracts/0.4.24/lib/StakeLimitUtils.sol index ce2d2fbee..fa7edf4f9 100644 --- a/contracts/0.4.24/lib/StakeRateLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -28,11 +28,14 @@ import "@aragon/os/contracts/common/UnstructuredStorage.sol"; // // // - the "staking paused" state is encoded by all fields being zero, -// - the "staking unlimited" state is encoded by maxStakeLimit being zero and prevStakeBlockNumber being non-zero. +// - the "staking unlimited" state is encoded by `maxStakeLimit` being zero and `prevStakeBlockNumber` being non-zero. // -// solidity <0.6 doesn't support top-level structs -// using the library to have a proper namespace +/** +* @notice Library for the internal structs definitions +* @dev solidity <0.6 doesn't support top-level structs +* using the library to have a proper namespace +*/ library StakeLimitState { /** * @dev Internal representation struct (slot-wide) @@ -85,10 +88,13 @@ library StakeLimitUnstructuredStorage { } } -library StakeRateLimitUtils { +/** +* @notice Interface library with helper functions to deal with stake limit struct in a more high-level approach. +*/ +library StakeLimitUtils { /** * @notice Calculate stake limit for the current block. - * @dev special returns: + * @dev special return values: * - 0 if limit is exhausted or staking pause was set * - 2^256 - 1 if there is no limit was set */ diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol index 6f9b763de..c39ffef7a 100644 --- a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -5,12 +5,12 @@ pragma solidity 0.4.24; -import "../lib/StakeRateLimitUtils.sol"; +import "../lib/StakeLimitUtils.sol"; contract StakeLimitUtilsMock { using UnstructuredStorage for bytes32; using StakeLimitUnstructuredStorage for bytes32; - using StakeRateLimitUtils for StakeLimitState.Data; + using StakeLimitUtils for StakeLimitState.Data; bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("abcdef"); diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 0f779552f..eaef0aaeb 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -561,7 +561,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) }) - const verifyRateLimitStake = async (expectedMaxStakeLimit, expectedLimitIncrease, expectedCurrentStakeLimit) => { + const verifyStakeLimitState = async (expectedMaxStakeLimit, expectedLimitIncrease, expectedCurrentStakeLimit) => { currentStakeLimit = await app.getCurrentStakeLimit() assertBn(currentStakeLimit, expectedCurrentStakeLimit) ;({ maxStakeLimit, maxStakeLimitGrowthBlocks, prevStakeLimit, prevStakeBlockNumber } = await app.getStakeLimitInternalInfo()) @@ -574,7 +574,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let receipt const MAX_UINT256 = bn(2).pow(bn(256)).sub(bn(1)) - await verifyRateLimitStake(bn(0), bn(0), MAX_UINT256) + await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) @@ -593,7 +593,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'StakingResumed') assert.equal(await app.isStakingPaused(), false) - await verifyRateLimitStake(bn(0), bn(0), MAX_UINT256) + await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256) await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(1.4) }) @@ -622,38 +622,38 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(1)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(1)) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }), `STAKE_LIMIT`) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, bn(ETH(1)).add(limitIncreasePerBlock)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, bn(ETH(1)).add(limitIncreasePerBlock)) // expect to grow for another 1.5 ETH since last submit // every revert produces new block, so we need to account that block await mineNBlocks(blocksToReachMaxStakeLimit / 2 - 1) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2.5)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2.5)) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.6) }), `STAKE_LIMIT`) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2.5), referral: ZERO_ADDRESS } }) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(2)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(2)) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(3)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(3)) // once again, we are subtracting blocks number induced by revert checks await mineNBlocks(blocksToReachMaxStakeLimit / 3 - 4) receipt = await app.submit(ZERO_ADDRESS, { from: user1, value: ETH(1) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user1, amount: ETH(1), referral: ZERO_ADDRESS } }) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) // check that limit is restored completely await mineNBlocks(blocksToReachMaxStakeLimit) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) // check that limit is capped by maxLimit value and doesn't grow infinitely await mineNBlocks(10) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_LIMIT_INCREASE`) await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TOO_SMALL_LIMIT_INCREASE`) @@ -674,17 +674,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2)) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) await mineNBlocks(100) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) }) it('resume with various changing limits work', async () => { @@ -702,7 +702,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyRateLimitStake(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) const smallerExpectedMaxStakeLimit = ETH(5) const smallerLimitIncreasePerBlock = bn(smallerExpectedMaxStakeLimit).divn(200) @@ -716,7 +716,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyRateLimitStake(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) + await verifyStakeLimitState(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) const largerExpectedMaxStakeLimit = ETH(10) const largerLimitIncreasePerBlock = bn(largerExpectedMaxStakeLimit).divn(1000) @@ -730,7 +730,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyRateLimitStake(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) + await verifyStakeLimitState(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) }) it('reverts when trying to call unknown function', async () => { diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index 77934de51..ad0f4a297 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -29,7 +29,7 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') // // // - the "staking paused" state is encoded by all fields being zero, -// - the "staking unlimited" state is encoded by maxStakeLimit being zero and prevStakeBlockNumber being non-zero. +// - the "staking unlimited" state is encoded by `maxStakeLimit` being zero and `prevStakeBlockNumber` being non-zero. // contract.skip('StakingLimits', () => { From 0b7046dd389296848d49a0b8a8f271f75da9c91e Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Tue, 17 May 2022 10:17:52 +0300 Subject: [PATCH 142/159] fix a number of typos, mostly in comments --- contracts/0.4.24/Lido.sol | 4 ++-- contracts/0.4.24/StETH.sol | 4 ++-- contracts/0.4.24/interfaces/ILido.sol | 2 +- contracts/0.4.24/lib/StakeLimitUtils.sol | 4 ++-- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 2 +- contracts/0.4.24/template/LidoTemplate.sol | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index b81a904cd..7c0f688f8 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -103,7 +103,7 @@ contract Lido is ILido, StETH, AragonApp { /// @dev percent in basis points of total pooled ether allowed to withdraw from LidoExecutionLayerRewardsVault per LidoOracle report bytes32 internal constant EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION = keccak256("lido.Lido.ELRewardsWithdrawalLimitPoints"); - /// @dev Just a counter of total amount of execurion layer rewards received by Lido contract + /// @dev Just a counter of total amount of execution layer rewards received by Lido contract /// Not used in the logic bytes32 internal constant TOTAL_EL_REWARDS_COLLECTED_POSITION = keccak256("lido.Lido.totalELRewardsCollected"); @@ -218,7 +218,7 @@ contract Lido is ILido, StETH, AragonApp { * `maxStakeLimit` max stake limit * `maxStakeLimitGrowthBlocks` blocks needed to restore max stake limit from the fully exhausted state * `prevStakeLimit` previously reached stake limit - * `prevStakeBlockNumber` prevously seen block number + * `prevStakeBlockNumber` previously seen block number */ function getStakeLimitInternalInfo() external view returns ( uint256 maxStakeLimit, diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index dcbd2ecd4..8cef5b3d5 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -42,7 +42,7 @@ import "./lib/Pausable.sol"; * emitting an event for each token holder and thus running an unbounded loop. * * The token inherits from `Pausable` and uses `whenNotStopped` modifier for methods - * which change `shares` or `allowances`. `_stop` and `_resume` functions are overriden + * which change `shares` or `allowances`. `_stop` and `_resume` functions are overridden * in `Lido.sol` and might be called by an account with the `PAUSE_ROLE` assigned by the * DAO. This is useful for emergency scenarios, e.g. a protocol bug, where one might want * to freeze all token transfers and approvals until the emergency is resolved. @@ -346,7 +346,7 @@ contract StETH is IERC20, Pausable { /** * @return the total amount (in wei) of Ether controlled by the protocol. - * @dev This is used for calaulating tokens from shares and vice versa. + * @dev This is used for calculating tokens from shares and vice versa. * @dev This function is required to be implemented in a derived contract. */ function _getTotalPooledEther() internal view returns (uint256); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 174746601..5e20606c3 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -67,7 +67,7 @@ interface ILido { * `maxStakeLimit` max stake limit * `maxStakeLimitGrowthBlocks` blocks needed to restore max stake limit from the fully exhausted state * `prevStakeLimit` previously reached stake limit - * `prevStakeBlockNumber` prevously seen block number + * `prevStakeBlockNumber` previously seen block number */ function getStakeLimitInternalInfo() external view returns ( uint256 maxStakeLimit, diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index fa7edf4f9..b11235d71 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -61,7 +61,7 @@ library StakeLimitUnstructuredStorage { uint256 internal constant PREV_STAKE_BLOCK_NUMBER_OFFSET = 0; /** - * @dev Read stake limit state from the unstructure storage position + * @dev Read stake limit state from the unstructured storage position * @param _position storage offset */ function getStorageStakeLimitStruct(bytes32 _position) internal view returns (StakeLimitState.Data memory ret) { @@ -74,7 +74,7 @@ library StakeLimitUnstructuredStorage { } /** - * @dev Write stake limit state to the unstructure storage position + * @dev Write stake limit state to the unstructured storage position * @param _position storage offset * @param _data stake limit state structure instance */ diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 7ef89c3bd..e5d7d8a89 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -340,7 +340,7 @@ contract NodeOperatorsRegistry is INodeOperatorsRegistry, IsContract, AragonApp // Finding the best suitable operator uint256 bestOperatorIdx = cache.length; // 'not found' flag uint256 smallestStake; - // The loop is ligthweight comparing to an ether transfer and .deposit invocation + // The loop is lightweight comparing to an ether transfer and .deposit invocation for (uint256 idx = 0; idx < cache.length; ++idx) { entry = cache[idx]; diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index cc81581f0..07c6708d9 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -394,7 +394,7 @@ contract LidoTemplate is IsContract { uint64 _vestingCliff, uint64 _vestingEnd, bool _vestingRevokable, - uint256 _extectedFinalTotalSupply + uint256 _expectedFinalTotalSupply ) onlyOwner external @@ -414,7 +414,7 @@ contract LidoTemplate is IsContract { _vestingCliff, _vestingEnd, _vestingRevokable, - _extectedFinalTotalSupply + _expectedFinalTotalSupply ); emit TmplTokensIssued(totalAmount); From 583e2132a0ea963a6c198876285e740dc18f0181 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 07:48:56 +0000 Subject: [PATCH 143/159] fix: stake limit corner cases --- contracts/0.4.24/Lido.sol | 11 ++++++++++- contracts/0.4.24/lib/StakeLimitUtils.sol | 21 ++++++++------------- test/0.4.24/lido.test.js | 1 + 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index b81a904cd..0089b3ced 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -208,7 +208,16 @@ contract Lido is ILido, StETH, AragonApp { * - 0 if staking is paused or if limit is exhausted. */ function getCurrentStakeLimit() external view returns (uint256) { - return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().calculateCurrentStakeLimit(); + StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + + if (stakeLimitData.isStakingPaused()) { + return 0; + } + if (!stakeLimitData.isStakingLimitApplied()) { + return uint256(-1); + } + + return stakeLimitData.calculateCurrentStakeLimit(); } /** diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index fa7edf4f9..66fdcf4e2 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -94,17 +94,10 @@ library StakeLimitUnstructuredStorage { library StakeLimitUtils { /** * @notice Calculate stake limit for the current block. - * @dev special return values: - * - 0 if limit is exhausted or staking pause was set - * - 2^256 - 1 if there is no limit was set */ function calculateCurrentStakeLimit(StakeLimitState.Data memory _data) internal view returns(uint256 limit) { - if (!isStakingLimitApplied(_data)) { - return uint256(-1); - } - uint256 stakeLimitIncPerBlock; - if (_data.maxStakeLimitGrowthBlocks > 0) { + if (_data.maxStakeLimitGrowthBlocks != 0) { stakeLimitIncPerBlock = _data.maxStakeLimit / _data.maxStakeLimitGrowthBlocks; } @@ -118,10 +111,12 @@ library StakeLimitUtils { * @notice check if staking is on pause (i.e. every byte in the slot has a zero value) */ function isStakingPaused(StakeLimitState.Data memory _data) internal pure returns(bool) { - return (_data.maxStakeLimit == 0) - && (_data.maxStakeLimitGrowthBlocks == 0) - && (_data.prevStakeBlockNumber == 0) - && (_data.prevStakeLimit == 0); + return ( + _data.maxStakeLimit + | _data.maxStakeLimitGrowthBlocks + | _data.prevStakeBlockNumber + | _data.prevStakeLimit + ) == 0; } /** @@ -155,7 +150,7 @@ library StakeLimitUtils { if ((_data.maxStakeLimit == 0) || (_maxStakeLimit < _data.prevStakeLimit)) { _data.prevStakeLimit = uint96(_maxStakeLimit); } - _data.maxStakeLimitGrowthBlocks = _stakeLimitIncreasePerBlock > 0 ? uint32(_maxStakeLimit / _stakeLimitIncreasePerBlock) : 0; + _data.maxStakeLimitGrowthBlocks = _stakeLimitIncreasePerBlock != 0 ? uint32(_maxStakeLimit / _stakeLimitIncreasePerBlock) : 0; _data.maxStakeLimit = uint96(_maxStakeLimit); _data.prevStakeBlockNumber = uint32(block.number); diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index eaef0aaeb..1e2427658 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -583,6 +583,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'StakingPaused') assert.equal(await app.isStakingPaused(), true) + verifyStakeLimitState(bn(0), bn(0), bn(0)) await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) From 81eb4deb3b70ae269f2b73047776ca43680c5b8f Mon Sep 17 00:00:00 2001 From: Azat Serikov Date: Tue, 17 May 2022 16:36:03 +0600 Subject: [PATCH 144/159] refactor: rename withdrawal limit points to withdrawal limit for consistency --- contracts/0.4.24/Lido.sol | 10 +++++----- test/0.4.24/lido.test.js | 2 +- .../execution_layer_rewards_after_the_merge.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 19e8e1eab..3c38cfd70 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -101,7 +101,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 internal constant BEACON_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators"); /// @dev percent in basis points of total pooled ether allowed to withdraw from LidoExecutionLayerRewardsVault per LidoOracle report - bytes32 internal constant EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION = keccak256("lido.Lido.ELRewardsWithdrawalLimitPoints"); + bytes32 internal constant EL_REWARDS_WITHDRAWAL_LIMIT_POSITION = keccak256("lido.Lido.ELRewardsWithdrawalLimit"); /// @dev Just a counter of total amount of execution layer rewards received by Lido contract /// Not used in the logic @@ -428,7 +428,7 @@ contract Lido is ILido, StETH, AragonApp { function setELRewardsWithdrawalLimit(uint16 _limitPoints) external { _auth(SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE); - _setBPValue(EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION, _limitPoints); + _setBPValue(EL_REWARDS_WITHDRAWAL_LIMIT_POSITION, _limitPoints); emit ELRewardsWithdrawalLimitSet(_limitPoints); } @@ -481,7 +481,7 @@ contract Lido is ILido, StETH, AragonApp { if (executionLayerRewardsVaultAddress != address(0)) { executionLayerRewards = ILidoExecutionLayerRewardsVault(executionLayerRewardsVaultAddress).withdrawRewards( - (_getTotalPooledEther() * EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS + (_getTotalPooledEther() * EL_REWARDS_WITHDRAWAL_LIMIT_POSITION.getStorageUint256()) / TOTAL_BASIS_POINTS ); if (executionLayerRewards != 0) { @@ -594,8 +594,8 @@ contract Lido is ILido, StETH, AragonApp { * @notice Get limit in basis points to amount of ETH to withdraw per LidoOracle report * @return uint256 limit in basis points to amount of ETH to withdraw per LidoOracle report */ - function getELRewardsWithdrawalLimitPoints() external view returns (uint256) { - return EL_REWARDS_WITHDRAWAL_LIMIT_POINTS_POSITION.getStorageUint256(); + function getELRewardsWithdrawalLimit() external view returns (uint256) { + return EL_REWARDS_WITHDRAWAL_LIMIT_POSITION.getStorageUint256(); } /** diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 1e2427658..58a332c16 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -270,7 +270,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) it('Attempt to set invalid execution layer rewards withdrawal limit', async () => { - const initialValue = await app.getELRewardsWithdrawalLimitPoints() + const initialValue = await app.getELRewardsWithdrawalLimit() assertEvent(await app.setELRewardsWithdrawalLimit(1, { from: voting }), 'ELRewardsWithdrawalLimitSet', { expectedArgs: { limitPoints: 1 } diff --git a/test/scenario/execution_layer_rewards_after_the_merge.js b/test/scenario/execution_layer_rewards_after_the_merge.js index 3cfcd7a0c..d48fe46f0 100644 --- a/test/scenario/execution_layer_rewards_after_the_merge.js +++ b/test/scenario/execution_layer_rewards_after_the_merge.js @@ -764,7 +764,7 @@ contract('Lido: merge acceptance', (addresses) => { let lastBeaconBalance = toE18(85) await pool.setELRewardsWithdrawalLimit(getELRewardsWithdrawalLimitFromEpoch(epoch), { from: voting }) - let elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimitPoints()) + let elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimit()) let elRewardsVaultBalance = toNum(await web3.eth.getBalance(elRewardsVault.address)) let totalPooledEther = toNum(await pool.getTotalPooledEther()) let bufferedEther = toNum(await pool.getBufferedEther()) @@ -776,7 +776,7 @@ contract('Lido: merge acceptance', (addresses) => { while (elRewardsVaultBalance > 0) { const elRewardsWithdrawalLimit = getELRewardsWithdrawalLimitFromEpoch(epoch) await pool.setELRewardsWithdrawalLimit(elRewardsWithdrawalLimit, { from: voting }) - elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimitPoints()) + elRewardsWithdrawalLimitPoints = toNum(await pool.getELRewardsWithdrawalLimit()) const maxELRewardsAmountPerWithdrawal = Math.floor( ((totalPooledEther + beaconBalanceInc) * elRewardsWithdrawalLimitPoints) / TOTAL_BASIS_POINTS From 71076c33ea4ca370bd7c91bd66439e92c972952c Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 16:37:24 +0000 Subject: [PATCH 145/159] fix: remove `StakingResumed` params They are misleading from the off-chain perspective. --- contracts/0.4.24/Lido.sol | 2 +- contracts/0.4.24/interfaces/ILido.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 19e8e1eab..5d9889c19 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -191,7 +191,7 @@ contract Lido is ILido, StETH, AragonApp { ) ); - emit StakingResumed(_maxStakeLimit, _stakeLimitIncreasePerBlock); + emit StakingResumed(); } /** diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 5e20606c3..cf1a72112 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -79,7 +79,7 @@ interface ILido { event Stopped(); event Resumed(); event StakingPaused(); - event StakingResumed(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); + event StakingResumed(); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). From 6e44ac7491af9f705141098c28a501586047b850 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 17:02:14 +0000 Subject: [PATCH 146/159] fix: remove `withdraw` stub at all --- contracts/0.4.24/Lido.sol | 17 +++++------------ contracts/0.4.24/interfaces/ILido.sol | 11 ++++------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 5d9889c19..acef9b22f 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -36,13 +36,17 @@ interface IERC721 { * until transfers become available in Ethereum 2.0. * Whitepaper: https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf * -* NOTE: the code below assumes moderate amount of node operators, e.g. up to 50. +* NOTE: the code below assumes moderate amount of node operators, e.g. up to 200. * * Since balances of all token holders change when the amount of total pooled Ether * changes, this token cannot fully implement ERC20 standard: it only emits `Transfer` * events upon explicit transfer between holders. In contrast, when Lido oracle reports * rewards, no Transfer events are generated: doing so would require emitting an event * for each token holder and thus running an unbounded loop. +* +* At the moment withdrawals are not possible in the beacon chain and there's no workaround. +* Pool will be upgraded to an actual implementation when withdrawals are enabled +* (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). */ contract Lido is ILido, StETH, AragonApp { using SafeMath for uint256; @@ -432,17 +436,6 @@ contract Lido is ILido, StETH, AragonApp { emit ELRewardsWithdrawalLimitSet(_limitPoints); } - /** - * @notice Issues withdrawal request. Not implemented. - * @param _amount Amount of StETH to withdraw - * @param _pubkeyHash Receiving address - */ - function withdraw(uint256 _amount, bytes32 _pubkeyHash) external whenNotStopped { /* solhint-disable-line no-unused-vars */ - //will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). - //at the moment withdrawals are not possible in the beacon chain and there's no workaround - revert("NOT_IMPLEMENTED_YET"); - } - /** * @notice Updates beacon states, collects rewards from LidoExecutionLayerRewardsVault and distributes all rewards if beacon balance increased * @dev periodically called by the Oracle contract diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index cf1a72112..1d9c39b27 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -13,6 +13,10 @@ pragma solidity 0.4.24; * and stakes it via the deposit_contract.sol contract. It doesn't hold ether on it's balance, * only a small portion (buffer) of it. * It also mints new tokens for rewards generated at the ETH 2.0 side. + * + * At the moment withdrawals are not possible in the beacon chain and there's no workaround. + * Pool will be upgraded to an actual implementation when withdrawals are enabled + * (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023). */ interface ILido { function totalSupply() external view returns (uint256); @@ -200,13 +204,6 @@ interface ILido { // The `amount` of ether was sent to the deposit_contract.deposit function event Unbuffered(uint256 amount); - /** - * @notice Issues withdrawal request. Large withdrawals will be processed only after the phase 2 launch. - * @param _amount Amount of StETH to burn - * @param _pubkeyHash Receiving address - */ - function withdraw(uint256 _amount, bytes32 _pubkeyHash) external; - // Requested withdrawal of `etherAmount` to `pubkeyHash` on the ETH 2.0 side, `tokenAmount` burned by `sender`, // `sentFromBuffer` was sent on the current Ethereum side. event Withdrawal(address indexed sender, uint256 tokenAmount, uint256 sentFromBuffer, From 7f1da3c805229147d1ceea55fa7a0d1cfb52bdb4 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 17:10:47 +0000 Subject: [PATCH 147/159] fix: `SharesBurnt` with pre- post- stETH balance --- contracts/0.4.24/StETH.sol | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 8cef5b3d5..29d00537a 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -100,12 +100,14 @@ contract StETH is IERC20, Pausable { * The stETH amount is calculated just before the burning incurred rebase. * * @param account holder of the burnt shares - * @param amount amount of burnt shares (expressed in stETH just before burning invocation) - * @param sharesAmount amount of burnt shares (expressed in shares themselves) + * @param preRebaseTokenAmount amount of stETH the burnt shares corresponded to before the burn-incurred token rebase + * @param postRebaseTokenAmount amount of stETH the burnt shares corresponded to after the burn-incurred token rebase + * @param sharesAmount amount of burnt shares */ event SharesBurnt( address indexed account, - uint256 amount, + uint256 preRebaseTokenAmount, + uint256 postRebaseTokenAmount, uint256 sharesAmount ); @@ -458,14 +460,17 @@ contract StETH is IERC20, Pausable { uint256 accountShares = shares[_account]; require(_sharesAmount <= accountShares, "BURN_AMOUNT_EXCEEDS_BALANCE"); - uint256 amount = getPooledEthByShares(_sharesAmount); - emit SharesBurnt(_account, amount, _sharesAmount); + uint256 preRebaseTokenAmount = getPooledEthByShares(_sharesAmount); newTotalShares = _getTotalShares().sub(_sharesAmount); TOTAL_SHARES_POSITION.setStorageUint256(newTotalShares); shares[_account] = accountShares.sub(_sharesAmount); + uint256 postRebaseTokenAmount = getPooledEthByShares(_sharesAmount); + + emit SharesBurnt(_account, preRebaseTokenAmount, postRebaseTokenAmount, _sharesAmount); + // Notice: we're not emitting a Transfer event to the zero address here since shares burn // works by redistributing the amount of tokens corresponding to the burned shares between // all other token holders. The total supply of the token doesn't change as the result. From ef62245295769c87c0d71830618cff2c35d50be9 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 17:14:23 +0000 Subject: [PATCH 148/159] chore: update `SharesBurnt` event docs --- contracts/0.4.24/StETH.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/0.4.24/StETH.sol b/contracts/0.4.24/StETH.sol index 29d00537a..970c83658 100644 --- a/contracts/0.4.24/StETH.sol +++ b/contracts/0.4.24/StETH.sol @@ -97,11 +97,11 @@ contract StETH is IERC20, Pausable { * * @dev Reports simultaneously burnt shares amount * and corresponding stETH amount. - * The stETH amount is calculated just before the burning incurred rebase. + * The stETH amount is calculated twice: before and after the burning incurred rebase. * * @param account holder of the burnt shares - * @param preRebaseTokenAmount amount of stETH the burnt shares corresponded to before the burn-incurred token rebase - * @param postRebaseTokenAmount amount of stETH the burnt shares corresponded to after the burn-incurred token rebase + * @param preRebaseTokenAmount amount of stETH the burnt shares corresponded to before the burn + * @param postRebaseTokenAmount amount of stETH the burnt shares corresponded to after the burn * @param sharesAmount amount of burnt shares */ event SharesBurnt( From a2dcf3c4cd38e2767cce8e50fc3df2234ce22228 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 17:30:16 +0000 Subject: [PATCH 149/159] doc: update StakeLimitUtils comments --- contracts/0.4.24/lib/StakeLimitUtils.sol | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index 171084184..c4e1f948a 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -64,13 +64,13 @@ library StakeLimitUnstructuredStorage { * @dev Read stake limit state from the unstructured storage position * @param _position storage offset */ - function getStorageStakeLimitStruct(bytes32 _position) internal view returns (StakeLimitState.Data memory ret) { + function getStorageStakeLimitStruct(bytes32 _position) internal view returns (StakeLimitState.Data memory stakeLimit) { uint256 slotValue = _position.getStorageUint256(); - ret.prevStakeBlockNumber = uint32(slotValue >> PREV_STAKE_BLOCK_NUMBER_OFFSET); - ret.prevStakeLimit = uint96(slotValue >> PREV_STAKE_LIMIT_OFFSET); - ret.maxStakeLimitGrowthBlocks = uint32(slotValue >> MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET); - ret.maxStakeLimit = uint96(slotValue >> MAX_STAKE_LIMIT_OFFSET); + stakeLimit.prevStakeBlockNumber = uint32(slotValue >> PREV_STAKE_BLOCK_NUMBER_OFFSET); + stakeLimit.prevStakeLimit = uint96(slotValue >> PREV_STAKE_LIMIT_OFFSET); + stakeLimit.maxStakeLimitGrowthBlocks = uint32(slotValue >> MAX_STAKE_LIMIT_GROWTH_BLOCKS_OFFSET); + stakeLimit.maxStakeLimit = uint96(slotValue >> MAX_STAKE_LIMIT_OFFSET); } /** @@ -128,6 +128,8 @@ library StakeLimitUtils { /** * @notice prepare stake limit repr to resume staking with the desired limits + * @dev input `_data` param is mutated and the func returns effectively the same pointer + * @param _data stake limit state struct * @param _maxStakeLimit stake limit max value * @param _stakeLimitIncreasePerBlock stake limit increase (restoration) per block */ @@ -160,14 +162,17 @@ library StakeLimitUtils { /** * @notice update stake limit repr after submitting user's eth + * @dev input `_data` param is mutated and the func returns effectively the same pointer + * @param _data stake limit state struct + * @param _newPrevStakeLimit new value for the `prevStakeLimit` field */ function updatePrevStakeLimit( StakeLimitState.Data memory _data, - uint256 _newPrevLimit + uint256 _newPrevStakeLimit ) internal view returns (StakeLimitState.Data memory) { - assert(_newPrevLimit <= uint96(-1)); + assert(_newPrevStakeLimit <= uint96(-1)); - _data.prevStakeLimit = uint96(_newPrevLimit); + _data.prevStakeLimit = uint96(_newPrevStakeLimit); _data.prevStakeBlockNumber = uint32(block.number); return _data; From 2cd1b7cb6aaa50b920945b66fd794683486d6b54 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 18:00:32 +0000 Subject: [PATCH 150/159] fix: bring back `getStakeLimitFullInfo` --- contracts/0.4.24/Lido.sol | 28 ++++++++++++++++++--------- contracts/0.4.24/interfaces/ILido.sol | 21 ++++++++++++-------- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 6a5b97df3..10b02ed0c 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -225,15 +225,20 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Returns internal info about stake limit + * @notice Returns full info about current stake limit params and state * @dev Might be used for the advanced integration requests. - * @return - * `maxStakeLimit` max stake limit - * `maxStakeLimitGrowthBlocks` blocks needed to restore max stake limit from the fully exhausted state - * `prevStakeLimit` previously reached stake limit - * `prevStakeBlockNumber` previously seen block number - */ - function getStakeLimitInternalInfo() external view returns ( + * @return isStakingPaused staking pause state (equivalent to return of isStakingPaused()) + * @return isStakingLimitApplied whether the stake limit is set + * @return currentStakeLimit current stake limit (equivalent to return of getCurrentStakeLimit()) + * @return maxStakeLimit max stake limit + * @return maxStakeLimitGrowthBlocks blocks needed to restore max stake limit from the fully exhausted state + * @return prevStakeLimit previously reached stake limit + * @return prevStakeBlockNumber previously seen block number + */ + function getStakeLimitFullInfo() external view returns ( + bool isStakingPaused, + bool isStakingLimitApplied, + uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, @@ -241,6 +246,11 @@ contract Lido is ILido, StETH, AragonApp { ) { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + isStakingPaused = stakeLimitData.isStakingPaused(); + isStakingLimitApplied = stakeLimitData.isStakingLimitApplied(); + + currentStakeLimit = isStakingLimitApplied ? stakeLimitData.calculateCurrentStakeLimit() : uint256(-1); + maxStakeLimit = stakeLimitData.maxStakeLimit; maxStakeLimitGrowthBlocks = stakeLimitData.maxStakeLimitGrowthBlocks; prevStakeLimit = stakeLimitData.prevStakeLimit; @@ -574,7 +584,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Get total amount of execution level rewards collected to Lido contract + * @notice Get total amount of execution layer rewards collected to Lido contract * @dev Ether got through LidoExecutionLayerRewardsVault is kept on this contract's balance the same way * as other buffered Ether is kept (until it gets deposited) * @return uint256 of funds received as execution layer rewards (in wei) diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 1d9c39b27..8f3592f09 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -65,15 +65,20 @@ interface ILido { function getCurrentStakeLimit() external view returns (uint256); /** - * @notice Returns internal info about stake limit + * @notice Returns full info about current stake limit params and state * @dev Might be used for the advanced integration requests. - * @return - * `maxStakeLimit` max stake limit - * `maxStakeLimitGrowthBlocks` blocks needed to restore max stake limit from the fully exhausted state - * `prevStakeLimit` previously reached stake limit - * `prevStakeBlockNumber` previously seen block number - */ - function getStakeLimitInternalInfo() external view returns ( + * @return isStakingPaused staking pause state (equivalent to return of isStakingPaused()) + * @return isStakingLimitApplied whether the stake limit is set + * @return currentStakeLimit current stake limit (equivalent to return of getCurrentStakeLimit()) + * @return maxStakeLimit max stake limit + * @return maxStakeLimitGrowthBlocks blocks needed to restore max stake limit from the fully exhausted state + * @return prevStakeLimit previously reached stake limit + * @return prevStakeBlockNumber previously seen block number + */ + function getStakeLimitFullInfo() external view returns ( + bool isStakingPaused, + bool isStakingLimitApplied, + uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, uint256 prevStakeLimit, From d043ecc9d0fdf2c7cec3c20febd69c1d8e60dacc Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 18:44:15 +0000 Subject: [PATCH 151/159] fix: various comment and doc fixes --- contracts/0.4.24/Lido.sol | 31 ++++++++-------- contracts/0.4.24/interfaces/ILido.sol | 29 +++++++++------ .../ILidoExecutionLayerRewardsVault.sol | 2 +- .../0.8.9/LidoExecutionLayerRewardsVault.sol | 35 +++++++++---------- contracts/0.8.9/SelfOwnedStETHBurner.sol | 6 ++-- 5 files changed, 53 insertions(+), 50 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 10b02ed0c..6c28f8dc8 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -119,8 +119,8 @@ contract Lido is ILido, StETH, AragonApp { * @param _depositContract official ETH2 Deposit contract * @param _oracle oracle contract * @param _operators instance of Node Operators Registry - * @param _treasury contract which accumulates treasury fee - * @param _insuranceFund contract which accumulates insurance fee + * @param _treasury treasury contract + * @param _insuranceFund insurance fund contract */ function initialize( IDepositContract _depositContract, @@ -195,7 +195,7 @@ contract Lido is ILido, StETH, AragonApp { ) ); - emit StakingResumed(); + emit StakingResumed(_maxStakeLimit, _stakeLimitIncreasePerBlock); } /** @@ -281,8 +281,8 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice A payable function for execution layer rewards. Can be called only by ExecutionLayerRewardsVault contract - * @dev We need a separate payable function because funds received by default payable function - * are considered as funds submitted for minting stETH + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit */ function receiveELRewards() external payable { require(msg.sender == EL_REWARDS_VAULT_POSITION.getStorageAddress()); @@ -389,14 +389,14 @@ contract Lido is ILido, StETH, AragonApp { * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). * * @dev Oracle contract specified here is allowed to make - * periodical updates of beacon states + * periodical updates of beacon stats * by calling pushBeacon. Treasury contract specified here is used - * to accumulate the protocol treasury fee.Insurance fund contract + * to accumulate the protocol treasury fee. Insurance fund contract * specified here is used to accumulate the protocol insurance fee. * * @param _oracle oracle contract - * @param _treasury treasury contract which accumulates treasury fee - * @param _insuranceFund insurance fund contract which accumulates insurance fee + * @param _treasury treasury contract + * @param _insuranceFund insurance fund contract */ function setProtocolContracts( address _oracle, @@ -411,8 +411,7 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. - * @param _withdrawalCredentials hash of withdrawal multisignature key as accepted by - * the deposit_contract.deposit function + * @param _withdrawalCredentials withdrawal credentials field as defined in the Ethereum PoS consensus specs */ function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external { _auth(MANAGE_WITHDRAWAL_KEY); @@ -436,7 +435,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Sets limit to amount of ETH to withdraw from execution layer rewards vault per LidoOracle report + * @dev Sets limit on amount of ETH to withdraw from execution layer rewards vault per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ function setELRewardsWithdrawalLimit(uint16 _limitPoints) external { @@ -447,7 +446,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @notice Updates beacon states, collects rewards from LidoExecutionLayerRewardsVault and distributes all rewards if beacon balance increased + * @notice Updates beacon stats, collects rewards from LidoExecutionLayerRewardsVault and distributes all rewards if beacon balance increased * @dev periodically called by the Oracle contract * @param _beaconValidators number of Lido's keys in the beacon state * @param _beaconBalance summarized balance of Lido-controlled keys in wei @@ -577,7 +576,7 @@ contract Lido is ILido, StETH, AragonApp { * @notice Get the amount of Ether temporary buffered on this contract balance * @dev Buffered balance is kept on the contract from the moment the funds are received from user * until the moment they are actually sent to the official Deposit contract. - * @return uint256 of buffered funds in wei + * @return amount of buffered funds in wei */ function getBufferedEther() external view returns (uint256) { return _getBufferedEther(); @@ -587,7 +586,7 @@ contract Lido is ILido, StETH, AragonApp { * @notice Get total amount of execution layer rewards collected to Lido contract * @dev Ether got through LidoExecutionLayerRewardsVault is kept on this contract's balance the same way * as other buffered Ether is kept (until it gets deposited) - * @return uint256 of funds received as execution layer rewards (in wei) + * @return amount of funds received as execution layer rewards (in wei) */ function getTotalELRewardsCollected() external view returns (uint256) { return TOTAL_EL_REWARDS_COLLECTED_POSITION.getStorageUint256(); @@ -595,7 +594,7 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Get limit in basis points to amount of ETH to withdraw per LidoOracle report - * @return uint256 limit in basis points to amount of ETH to withdraw per LidoOracle report + * @return limit in basis points to amount of ETH to withdraw per LidoOracle report */ function getELRewardsWithdrawalLimit() external view returns (uint256) { return EL_REWARDS_WITHDRAWAL_LIMIT_POSITION.getStorageUint256(); diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 8f3592f09..801b157e9 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -88,13 +88,21 @@ interface ILido { event Stopped(); event Resumed(); event StakingPaused(); - event StakingResumed(); + + /** + * @notice resumeStaking was called with the provided params + * @dev use `getCurrentStakeLimit()` and `getCurrentStakeLimit()` funcs to check the actual limit + * NB: if limits are not set then both args have zero values. + * @param _maxStakeLimit max stake limit value + * @param _stakeLimitIncreasePerBlock stake limit increase per single block + */ + event StakingResumed(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). * @param _oracle oracle contract - * @param _treasury treasury contract which accumulates treasury fee - * @param _insuranceFund insurance fund contract which accumulates insurance fee + * @param _treasury treasury contract + * @param _insuranceFund insurance fund contract */ function setProtocolContracts( address _oracle, @@ -146,8 +154,8 @@ interface ILido { /** * @notice A payable function supposed to be called only by LidoExecutionLayerRewardsVault contract - * @dev We need a separate function because funds received by default payable function - * are considered as funds submitted by a user for staking + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit */ function receiveELRewards() external payable; @@ -155,7 +163,7 @@ interface ILido { event ELRewardsReceived(uint256 amount); /** - * @dev Sets limit to amount of ETH to withdraw from execution layer rewards vault per LidoOracle report + * @dev Sets limit on amount of ETH to withdraw from execution layer rewards vault per LidoOracle report * @param _limitPoints limit in basis points to amount of ETH to withdraw per LidoOracle report */ function setELRewardsWithdrawalLimit(uint16 _limitPoints) external; @@ -166,8 +174,7 @@ interface ILido { /** * @notice Set credentials to withdraw ETH on ETH 2.0 side after the phase 2 is launched to `_withdrawalCredentials` * @dev Note that setWithdrawalCredentials discards all unused signing keys as the signatures are invalidated. - * @param _withdrawalCredentials hash of withdrawal multisignature key as accepted by - * the deposit_contract.deposit function + * @param _withdrawalCredentials withdrawal credentials field as defined in the Ethereum PoS consensus specs */ function setWithdrawalCredentials(bytes32 _withdrawalCredentials) external; @@ -179,9 +186,9 @@ interface ILido { event WithdrawalCredentialsSet(bytes32 withdrawalCredentials); /** - * @dev Sets the address of LidoExecutionLayerRewardsVault contract - * @param _executionLayerRewardsVault Execution layer rewards vault contract address - */ + * @dev Sets the address of LidoExecutionLayerRewardsVault contract + * @param _executionLayerRewardsVault Execution layer rewards vault contract address + */ function setELRewardsVault(address _executionLayerRewardsVault) external; // The `executionLayerRewardsVault` was set as the execution layer rewards vault for Lido diff --git a/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol b/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol index 6f53da857..701643ef6 100644 --- a/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol +++ b/contracts/0.4.24/interfaces/ILidoExecutionLayerRewardsVault.sol @@ -10,7 +10,7 @@ interface ILidoExecutionLayerRewardsVault { /** * @notice Withdraw all accumulated execution layer rewards to Lido contract * @param _maxAmount Max amount of ETH to withdraw - * @return amount uint256 of funds received as execution layer rewards (in wei) + * @return amount of funds received as execution layer rewards (in wei) */ function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount); } diff --git a/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol b/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol index d6bc219f6..4380c0fa7 100644 --- a/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol +++ b/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol @@ -11,20 +11,17 @@ import "@openzeppelin/contracts-v4.4/token/ERC20/utils/SafeERC20.sol"; interface ILido { /** - * @notice A payable function supposed to be called only by LidoExecLayerRewardsVault contract - * @dev We need a separate function because funds received by default payable function - * will go through entire deposit algorithm - */ + * @notice A payable function supposed to be called only by LidoExecLayerRewardsVault contract + * @dev We need a dedicated function because funds received by the default payable function + * are treated as a user deposit + */ function receiveELRewards() external payable; } /** -* @title A vault for temporary storage of execution layer rewards (MEV and tx priority fee) -* -* These vault replenishments happen continuously through a day, while withdrawals -* happen much less often, only on LidoOracle beacon balance reports -*/ + * @title A vault for temporary storage of execution layer rewards (MEV and tx priority fee) + */ contract LidoExecutionLayerRewardsVault { using SafeERC20 for IERC20; @@ -32,7 +29,7 @@ contract LidoExecutionLayerRewardsVault { address public immutable TREASURY; /** - * Emitted when the ERC20 `token` recovered (e.g. transferred) + * Emitted when the ERC20 `token` recovered (i.e. transferred) * to the Lido treasury address by `requestedBy` sender. */ event ERC20Recovered( @@ -42,7 +39,7 @@ contract LidoExecutionLayerRewardsVault { ); /** - * Emitted when the ERC721-compatible `token` (NFT) recovered (e.g. transferred) + * Emitted when the ERC721-compatible `token` (NFT) recovered (i.e. transferred) * to the Lido treasury address by `requestedBy` sender. */ event ERC721Recovered( @@ -66,19 +63,19 @@ contract LidoExecutionLayerRewardsVault { } /** - * @notice Allows the contract to receive ETH - * @dev execution layer rewards may be sent as plain ETH transfers - */ + * @notice Allows the contract to receive ETH + * @dev execution layer rewards may be sent as plain ETH transfers + */ receive() external payable { // no-op } /** - * @notice Withdraw all accumulated rewards to Lido contract - * @dev Can be called only by the Lido contract - * @param _maxAmount Max amount of ETH to withdraw - * @return amount uint256 of funds received as execution layer rewards (in wei) - */ + * @notice Withdraw all accumulated rewards to Lido contract + * @dev Can be called only by the Lido contract + * @param _maxAmount Max amount of ETH to withdraw + * @return amount of funds received as execution layer rewards (in wei) + */ function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount) { require(msg.sender == LIDO, "ONLY_LIDO_CAN_WITHDRAW"); diff --git a/contracts/0.8.9/SelfOwnedStETHBurner.sol b/contracts/0.8.9/SelfOwnedStETHBurner.sol index e39949170..1894f77cc 100644 --- a/contracts/0.8.9/SelfOwnedStETHBurner.sol +++ b/contracts/0.8.9/SelfOwnedStETHBurner.sol @@ -120,7 +120,7 @@ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, E ); /** - * Emitted when the excessive stETH `amount` (corresponding to `sharesAmount` shares) recovered (e.g. transferred) + * Emitted when the excessive stETH `amount` (corresponding to `sharesAmount` shares) recovered (i.e. transferred) * to the Lido treasure address by `requestedBy` sender. */ event ExcessStETHRecovered( @@ -130,7 +130,7 @@ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, E ); /** - * Emitted when the ERC20 `token` recovered (e.g. transferred) + * Emitted when the ERC20 `token` recovered (i.e. transferred) * to the Lido treasure address by `requestedBy` sender. */ event ERC20Recovered( @@ -140,7 +140,7 @@ contract SelfOwnedStETHBurner is ISelfOwnedStETHBurner, IBeaconReportReceiver, E ); /** - * Emitted when the ERC721-compatible `token` (NFT) recovered (e.g. transferred) + * Emitted when the ERC721-compatible `token` (NFT) recovered (i.e. transferred) * to the Lido treasure address by `requestedBy` sender. */ event ERC721Recovered( From 6843b84da72e2096c6ed386d23459a5001a77c4e Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 19:49:02 +0000 Subject: [PATCH 152/159] fix: lido contract consistency --- contracts/0.4.24/Lido.sol | 26 ++++++++++++++------------ contracts/0.4.24/interfaces/ILido.sol | 6 +++--- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 6c28f8dc8..34448b050 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -211,17 +211,8 @@ contract Lido is ILido, StETH, AragonApp { * - 2^256 - 1 if staking is unlimited; * - 0 if staking is paused or if limit is exhausted. */ - function getCurrentStakeLimit() external view returns (uint256) { - StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); - - if (stakeLimitData.isStakingPaused()) { - return 0; - } - if (!stakeLimitData.isStakingLimitApplied()) { - return uint256(-1); - } - - return stakeLimitData.calculateCurrentStakeLimit(); + function getCurrentStakeLimit() public view returns (uint256) { + return _getCurrentStakeLimit(STAKE_LIMIT_POSITION.getStorageStakeLimitStruct()); } /** @@ -249,7 +240,7 @@ contract Lido is ILido, StETH, AragonApp { isStakingPaused = stakeLimitData.isStakingPaused(); isStakingLimitApplied = stakeLimitData.isStakingLimitApplied(); - currentStakeLimit = isStakingLimitApplied ? stakeLimitData.calculateCurrentStakeLimit() : uint256(-1); + currentStakeLimit = _getCurrentStakeLimit(stakeLimitData); maxStakeLimit = stakeLimitData.maxStakeLimit; maxStakeLimitGrowthBlocks = stakeLimitData.maxStakeLimitGrowthBlocks; @@ -981,6 +972,17 @@ contract Lido is ILido, StETH, AragonApp { emit StakingPaused(); } + function _getCurrentStakeLimit(StakeLimitState.Data memory _stakeLimitData) internal view returns(uint256) { + if (_stakeLimitData.isStakingPaused()) { + return 0; + } + if (!_stakeLimitData.isStakingLimitApplied()) { + return uint256(-1); + } + + return _stakeLimitData.calculateCurrentStakeLimit(); + } + /** * @dev Size-efficient analog of the `auth(_role)` modifier * @param _role Permission name diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index 801b157e9..b66243446 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -93,10 +93,10 @@ interface ILido { * @notice resumeStaking was called with the provided params * @dev use `getCurrentStakeLimit()` and `getCurrentStakeLimit()` funcs to check the actual limit * NB: if limits are not set then both args have zero values. - * @param _maxStakeLimit max stake limit value - * @param _stakeLimitIncreasePerBlock stake limit increase per single block + * @param maxStakeLimit max stake limit value + * @param stakeLimitIncreasePerBlock stake limit increase per single block */ - event StakingResumed(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock); + event StakingResumed(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). From 67aaa21257eb30b9e8e9ad8dd944e70a3d0a0c08 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 17 May 2022 19:49:52 +0000 Subject: [PATCH 153/159] test: review tests after refactorings --- test/0.4.24/lido.test.js | 124 +++++++++++++++++++------------------- test/0.4.24/steth.test.js | 33 ++++++++-- 2 files changed, 89 insertions(+), 68 deletions(-) diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 58a332c16..612cf7159 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -561,12 +561,29 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) }) - const verifyStakeLimitState = async (expectedMaxStakeLimit, expectedLimitIncrease, expectedCurrentStakeLimit) => { + const verifyStakeLimitState = async ( + expectedMaxStakeLimit, + expectedLimitIncrease, + expectedCurrentStakeLimit, + expectedIsStakingPaused, + expectedIsStakingLimited + ) => { currentStakeLimit = await app.getCurrentStakeLimit() assertBn(currentStakeLimit, expectedCurrentStakeLimit) - ;({ maxStakeLimit, maxStakeLimitGrowthBlocks, prevStakeLimit, prevStakeBlockNumber } = await app.getStakeLimitInternalInfo()) + ;({ + isStakingPaused, + isStakingLimitApplied, + currentStakeLimit, + maxStakeLimit, + maxStakeLimitGrowthBlocks, + prevStakeLimit, + prevStakeBlockNumber + } = await app.getStakeLimitFullInfo()) + assertBn(currentStakeLimit, expectedCurrentStakeLimit) assertBn(maxStakeLimit, expectedMaxStakeLimit) + assert.equal(isStakingPaused, expectedIsStakingPaused) + assert.equal(isStakingLimitApplied, expectedIsStakingLimited) assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) } @@ -574,7 +591,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) let receipt const MAX_UINT256 = bn(2).pow(bn(256)).sub(bn(1)) - await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256) + await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256, false, false) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) @@ -583,7 +600,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'StakingPaused') assert.equal(await app.isStakingPaused(), true) - verifyStakeLimitState(bn(0), bn(0), bn(0)) + verifyStakeLimitState(bn(0), bn(0), bn(0), true, false) await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) @@ -594,7 +611,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'StakingResumed') assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256) + await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256, false, false) await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(1.4) }) @@ -623,38 +640,38 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(1)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(1), false, true) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }), `STAKE_LIMIT`) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, bn(ETH(1)).add(limitIncreasePerBlock)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, bn(ETH(1)).add(limitIncreasePerBlock), false, true) // expect to grow for another 1.5 ETH since last submit // every revert produces new block, so we need to account that block await mineNBlocks(blocksToReachMaxStakeLimit / 2 - 1) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2.5)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2.5), false, true) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.6) }), `STAKE_LIMIT`) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2.5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2.5), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(2)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(2), false, true) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(3)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, limitIncreasePerBlock.muln(3), false, true) // once again, we are subtracting blocks number induced by revert checks await mineNBlocks(blocksToReachMaxStakeLimit / 3 - 4) receipt = await app.submit(ZERO_ADDRESS, { from: user1, value: ETH(1) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user1, amount: ETH(1), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0), false, true) // check that limit is restored completely await mineNBlocks(blocksToReachMaxStakeLimit) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) // check that limit is capped by maxLimit value and doesn't grow infinitely await mineNBlocks(10) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_LIMIT_INCREASE`) await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TOO_SMALL_LIMIT_INCREASE`) @@ -675,17 +692,17 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(2), false, true) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0), false, true) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(0.1) }), `STAKE_LIMIT`) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0), false, true) await mineNBlocks(100) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0)) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, ETH(0), false, true) }) it('resume with various changing limits work', async () => { @@ -703,7 +720,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit) + await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) const smallerExpectedMaxStakeLimit = ETH(5) const smallerLimitIncreasePerBlock = bn(smallerExpectedMaxStakeLimit).divn(200) @@ -717,7 +734,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) + await verifyStakeLimitState(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit, false, true) const largerExpectedMaxStakeLimit = ETH(10) const largerLimitIncreasePerBlock = bn(largerExpectedMaxStakeLimit).divn(1000) @@ -731,7 +748,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit) + await verifyStakeLimitState(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit, false, true) }) it('reverts when trying to call unknown function', async () => { @@ -804,41 +821,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(await app.getBufferedEther(), ETH(5)) }) - it('withdrawal method reverts', async () => { - await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) - await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) - - await operators.setNodeOperatorStakingLimit(0, UNLIMITED, { from: voting }) - await operators.setNodeOperatorStakingLimit(1, UNLIMITED, { from: voting }) - - await app.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) - await operators.addSigningKeys( - 0, - 6, - hexConcat( - pad('0x010203', 48), - pad('0x010204', 48), - pad('0x010205', 48), - pad('0x010206', 48), - pad('0x010207', 48), - pad('0x010208', 48) - ), - hexConcat(pad('0x01', 96), pad('0x01', 96), pad('0x01', 96), pad('0x01', 96), pad('0x01', 96), pad('0x01', 96)), - { from: voting } - ) - - await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(1) }) - await app.methods['depositBufferedEther()']({ from: depositor }) - assertBn(await app.getTotalPooledEther(), ETH(1)) - assertBn(await app.totalSupply(), tokens(1)) - assertBn(await app.getBufferedEther(), ETH(1)) - - await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) - - await assertRevert(app.withdraw(tokens(1), pad('0x1000', 32), { from: nobody }), 'NOT_IMPLEMENTED_YET') - await assertRevert(app.withdraw(tokens(1), pad('0x1000', 32), { from: user1 }), 'NOT_IMPLEMENTED_YET') - }) - it('handleOracleReport works', async () => { await operators.addNodeOperator('1', ADDRESS_1, { from: voting }) await operators.addNodeOperator('2', ADDRESS_2, { from: voting }) @@ -1393,18 +1375,34 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.burnShares(user1, ETH(1), { from: nobody }), 'APP_AUTH_FAILED') // voting can burn shares of any user - const expectedAmount = await app.getPooledEthByShares(ETH(0.5)) + const expectedPreTokenAmount = await app.getPooledEthByShares(ETH(0.5)) let receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: ETH(0.5) } }) + const expectedPostTokenAmount = await app.getPooledEthByShares(ETH(0.5)) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: expectedPreTokenAmount, + postRebaseTokenAmount: expectedPostTokenAmount, + sharesAmount: ETH(0.5) + } + }) - const expectedDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) + const expectedPreDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) receipt = await app.burnShares(user1, ETH(0.5), { from: voting }) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedDoubledAmount, sharesAmount: ETH(0.5) } }) + const expectedPostDoubledAmount = await app.getPooledEthByShares(ETH(0.5)) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: expectedPreDoubledAmount, + postRebaseTokenAmount: expectedPostDoubledAmount, + sharesAmount: ETH(0.5) + } + }) - assertBn(expectedAmount.mul(bn(2)), expectedDoubledAmount) + assertBn(expectedPreTokenAmount.mul(bn(2)), expectedPreDoubledAmount) assertBn(tokens(0), await app.getPooledEthByShares(ETH(0.5))) - // user1 has zero shares afteralls + // user1 has zero shares after all assertBn(await app.sharesOf(user1), tokens(0)) // voting can't continue burning if user already has no shares diff --git a/test/0.4.24/steth.test.js b/test/0.4.24/steth.test.js index 4ccf0455e..be35c12e0 100644 --- a/test/0.4.24/steth.test.js +++ b/test/0.4.24/steth.test.js @@ -370,7 +370,14 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { it('burning zero value works', async () => { const receipt = await stEth.burnShares(user1, tokens(0)) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: tokens(0), sharesAmount: tokens(0) } }) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: tokens(0), + postRebaseTokenAmount: tokens(0), + sharesAmount: tokens(0) + } + }) assertBn(await stEth.totalSupply(), tokens(300)) assertBn(await stEth.balanceOf(user1), tokens(100)) @@ -392,9 +399,17 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { totalSupply.mul(totalShares.sub(user1Shares)).div(totalSupply.sub(user1Balance).add(bn(tokens(10)))) ) - const expectedAmount = await stEth.getPooledEthByShares(sharesToBurn) + const expectedPreTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) const receipt = await stEth.burnShares(user1, sharesToBurn) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) + const expectedPostTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: expectedPreTokenAmount, + postRebaseTokenAmount: expectedPostTokenAmount, + sharesAmount: sharesToBurn + } + }) assertBn(await stEth.totalSupply(), tokens(300)) assertBn(await stEth.balanceOf(user1), bn(tokens(90)).subn(1)) // expected round error @@ -418,9 +433,17 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { totalSupply.mul(totalShares.sub(user1Shares)).div(totalSupply.sub(user1Balance).add(bn(tokens(50)))) ) - const expectedAmount = await stEth.getPooledEthByShares(sharesToBurn) + const expectedPreTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) const receipt = await stEth.burnShares(user1, sharesToBurn) - assertEvent(receipt, 'SharesBurnt', { expectedArgs: { account: user1, amount: expectedAmount, sharesAmount: sharesToBurn } }) + const expectedPostTokenAmount = await stEth.getPooledEthByShares(sharesToBurn) + assertEvent(receipt, 'SharesBurnt', { + expectedArgs: { + account: user1, + preRebaseTokenAmount: expectedPreTokenAmount, + postRebaseTokenAmount: expectedPostTokenAmount, + sharesAmount: sharesToBurn + } + }) assertBn(await stEth.balanceOf(user1), tokens(50)) From 4dd06b6dc2b357956e6c7d502d123cc640b8850d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 18 May 2022 14:51:07 +0000 Subject: [PATCH 154/159] feat: add `ETHReceived` event for ELRewardsVault --- contracts/0.8.9/LidoExecutionLayerRewardsVault.sol | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol b/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol index 4380c0fa7..2d1250be2 100644 --- a/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol +++ b/contracts/0.8.9/LidoExecutionLayerRewardsVault.sol @@ -48,6 +48,13 @@ contract LidoExecutionLayerRewardsVault { uint256 tokenId ); + /** + * Emitted when the vault received ETH + */ + event ETHReceived( + uint256 amount + ); + /** * Ctor * @@ -67,7 +74,7 @@ contract LidoExecutionLayerRewardsVault { * @dev execution layer rewards may be sent as plain ETH transfers */ receive() external payable { - // no-op + emit ETHReceived(msg.value); } /** From e3137a562e92d1dc1f5912c353101846e6b2dab6 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 18 May 2022 15:00:30 +0000 Subject: [PATCH 155/159] fix: rename `distributeRewards` to `distributeFee` --- contracts/0.4.24/Lido.sol | 6 ++-- .../0.4.24/test_helpers/LidoPushableMock.sol | 10 +++---- test/0.4.24/lidoHandleOracleReport.test.js | 30 +++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 34448b050..b85d9b2c1 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -487,7 +487,7 @@ contract Lido is ILido, StETH, AragonApp { // See ADR #3 for details: https://research.lido.fi/t/rewards-distribution-after-the-merge-architecture-decision-record/1535 if (_beaconBalance > rewardBase) { uint256 rewards = _beaconBalance.sub(rewardBase); - distributeRewards(rewards.add(executionLayerRewards)); + distributeFee(rewards.add(executionLayerRewards)); } } @@ -790,10 +790,10 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Distributes rewards by minting and distributing corresponding amount of liquid tokens. + * @dev Distributes fee by minting and distributing corresponding amount of liquid tokens. * @param _totalRewards Total rewards accrued on the Ethereum 2.0 side in wei */ - function distributeRewards(uint256 _totalRewards) internal { + function distributeFee(uint256 _totalRewards) internal { // We need to take a defined percentage of the reported reward as a fee, and we do // this by minting new token shares and assigning them to the fee recipients (see // StETH docs for the explanation of the shares mechanics). The staking rewards fee diff --git a/contracts/0.4.24/test_helpers/LidoPushableMock.sol b/contracts/0.4.24/test_helpers/LidoPushableMock.sol index 35c3d5c17..704b3d346 100644 --- a/contracts/0.4.24/test_helpers/LidoPushableMock.sol +++ b/contracts/0.4.24/test_helpers/LidoPushableMock.sol @@ -14,7 +14,7 @@ import "./VaultMock.sol"; contract LidoPushableMock is Lido { uint256 public totalRewards; - bool public distributeRewardsCalled; + bool public distributeFeeCalled; function initialize( IDepositContract depositContract, @@ -57,13 +57,13 @@ contract LidoPushableMock is Lido { initialized(); } - function resetDistributeRewards() public { + function resetDistributeFee() public { totalRewards = 0; - distributeRewardsCalled = false; + distributeFeeCalled = false; } - function distributeRewards(uint256 _totalRewards) internal { + function distributeFee(uint256 _totalRewards) internal { totalRewards = _totalRewards; - distributeRewardsCalled = true; + distributeFeeCalled = true; } } diff --git a/test/0.4.24/lidoHandleOracleReport.test.js b/test/0.4.24/lidoHandleOracleReport.test.js index c8de0f421..747dd30c9 100644 --- a/test/0.4.24/lidoHandleOracleReport.test.js +++ b/test/0.4.24/lidoHandleOracleReport.test.js @@ -53,7 +53,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(0)) assertBn(await app.getTotalPooledEther(), ETH(0)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -62,7 +62,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(0)) assertBn(await app.getTotalPooledEther(), ETH(0)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) @@ -80,7 +80,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(12)) assertBn(await app.getTotalPooledEther(), ETH(12)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -89,7 +89,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(12)) assertBn(await app.getTotalPooledEther(), ETH(12)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) @@ -113,7 +113,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(3)) assertBn(await app.getTotalPooledEther(), ETH(35)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -122,7 +122,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 1, beaconValidators: 0, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(3)) assertBn(await app.getTotalPooledEther(), ETH(35)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -131,7 +131,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(31) }) assertBn(await app.getBufferedEther(), ETH(3)) assertBn(await app.getTotalPooledEther(), ETH(34)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -140,7 +140,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 1, beaconValidators: 1, beaconBalance: ETH(32) }) assertBn(await app.getBufferedEther(), ETH(3)) assertBn(await app.getTotalPooledEther(), ETH(35)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) @@ -164,7 +164,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(0) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(37)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -173,7 +173,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(1) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(38)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -182,7 +182,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 2, beaconBalance: ETH(62) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(67)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) @@ -191,7 +191,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(31) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(68)) - assert.equal(await app.distributeRewardsCalled(), true) + assert.equal(await app.distributeFeeCalled(), true) assertBn(await app.totalRewards(), ETH(1)) }) @@ -200,7 +200,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 2, beaconBalance: ETH(63) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(68)) - assert.equal(await app.distributeRewardsCalled(), true) + assert.equal(await app.distributeFeeCalled(), true) assertBn(await app.totalRewards(), ETH(1)) }) @@ -209,7 +209,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 2, beaconValidators: 1, beaconBalance: ETH(30) }) assertBn(await app.getBufferedEther(), ETH(5)) assertBn(await app.getTotalPooledEther(), ETH(67)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) @@ -232,7 +232,7 @@ contract('Lido handleOracleReport', ([appManager, voting, user1, user2, user3, n checkStat({ depositedValidators: 5, beaconValidators: 4, beaconBalance: ETH(1) }) assertBn(await app.getBufferedEther(), ETH(0)) assertBn(await app.getTotalPooledEther(), ETH(33)) - assert.equal(await app.distributeRewardsCalled(), false) + assert.equal(await app.distributeFeeCalled(), false) assertBn(await app.totalRewards(), 0) }) }) From a0f833657367e94c8df712e78d20c4cf09dca25e Mon Sep 17 00:00:00 2001 From: Logachev Nikita Date: Wed, 18 May 2022 19:27:52 +0300 Subject: [PATCH 156/159] update stake limit tests --- .../test_helpers/StakeLimitUtilsMock.sol | 18 +- test/0.4.24/staking-limit.test.js | 160 ++++++++---------- 2 files changed, 81 insertions(+), 97 deletions(-) diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol index c39ffef7a..9604ccde4 100644 --- a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -14,12 +14,13 @@ contract StakeLimitUtilsMock { bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("abcdef"); - function getStorageStakeLimit() public view returns ( + function getStorageStakeLimit(uint256 _slotValue) public view returns ( uint32 prevStakeBlockNumber, uint96 prevStakeLimit, uint32 maxStakeLimitGrowthBlocks, uint96 maxStakeLimit ) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); StakeLimitState.Data memory data = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); prevStakeBlockNumber = data.prevStakeBlockNumber; @@ -33,7 +34,7 @@ contract StakeLimitUtilsMock { uint96 _prevStakeLimit, uint32 _maxStakeLimitGrowthBlocks, uint96 _maxStakeLimit - ) public returns (uint256 ret) { + ) public view returns (uint256 ret) { StakeLimitState.Data memory data; data.prevStakeBlockNumber = _prevStakeBlockNumber; data.prevStakeLimit = _prevStakeLimit; @@ -41,23 +42,26 @@ contract StakeLimitUtilsMock { data.maxStakeLimit = _maxStakeLimit; STAKE_LIMIT_POSITION.setStorageStakeLimitStruct(data); - return STAKE_LIMIT_POSITION.getStorageUint256(); } - function calculateCurrentStakeLimit() public view returns(uint256 limit) { + function calculateCurrentStakeLimit(uint256 _slotValue) public view returns(uint256 limit) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().calculateCurrentStakeLimit(); } function isStakingPaused(uint256 _slotValue) public view returns(bool) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingPaused(); } function isStakingLimitApplied(uint256 _slotValue) public view returns(bool) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingLimitApplied(); } - function resumeStakingWithNewLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { + function resumeStakingWithNewLimit(uint256 _slotValue, uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( _maxStakeLimit, _stakeLimitIncreasePerBlock @@ -65,9 +69,11 @@ contract StakeLimitUtilsMock { ); } - function updatePrevStakeLimit(uint256 _newPrevLimit) internal view returns (StakeLimitState.Data memory) { + function updatePrevStakeLimit(uint256 _slotValue, uint256 _newPrevLimit) public view returns (uint256) { + STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().updatePrevStakeLimit(_newPrevLimit) ); + return STAKE_LIMIT_POSITION.getStorageUint256(); } } diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index ad0f4a297..9bf37e23d 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -32,7 +32,7 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') // - the "staking unlimited" state is encoded by `maxStakeLimit` being zero and `prevStakeBlockNumber` being non-zero. // -contract.skip('StakingLimits', () => { +contract('StakingLimits', ([account1]) => { let limits before('deploy base app', async () => { @@ -40,90 +40,98 @@ contract.skip('StakingLimits', () => { }) it('encode zeros', async () => { - const slot = await limits.encodeStakeLimitSlot(0, 0, 0, 0) + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) assertBn(slot, 0) - const decodedSlot = await limits.decodeStakeLimitSlot(slot) - assertBn(decodedSlot.maxStakeLimit, 0) - assertBn(decodedSlot.stakeLimitIncPerBlock, 0) - assertBn(decodedSlot.prevStakeLimit, 0) + const decodedSlot = await limits.getStorageStakeLimit(slot) assertBn(decodedSlot.prevStakeBlockNumber, 0) - }) - - it('check 0 slot', async () => { - const slot = 0 - assertBn(slot, 0) - - const decodedSlot = await limits.decodeStakeLimitSlot(slot) - assertBn(decodedSlot.maxStakeLimit, 0) - assertBn(decodedSlot.stakeLimitIncPerBlock, 0) assertBn(decodedSlot.prevStakeLimit, 0) - assertBn(decodedSlot.prevStakeBlockNumber, 0) + assertBn(decodedSlot.maxStakeLimitGrowthBlocks, 0) + assertBn(decodedSlot.maxStakeLimit, 0) }) it('check staking pause', async () => { - const slot = 0 + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) const paused = await limits.isStakingPaused(slot) assert.equal(paused, true, 'limits not paused') const maxStakeLimit = 10 - const slot2 = await limits.encodeStakeLimitSlot(maxStakeLimit, 0, 0, 0) + const slot2 = await limits.setStorageStakeLimitStruct(0, 0, 0, maxStakeLimit) const paused2 = await limits.isStakingPaused(slot2) assert.equal(paused2, false, 'limits not limited') }) it('check staking rate limit', async () => { - const slot = 0 + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) const limited = await limits.isStakingLimitApplied(slot) - assert.equal(limited, false, 'limits not limited') + assert.equal(limited, false, 'limits are limited') const maxStakeLimit = 10 - const slot2 = await limits.encodeStakeLimitSlot(maxStakeLimit, 0, 0, 0) + const slot2 = await limits.setStorageStakeLimitStruct(0, 0, 0, maxStakeLimit) const limited2 = await limits.isStakingLimitApplied(slot2) - assert.equal(limited2, true, 'limits not limited') + assert.equal(limited2, true, 'limits are not limited') }) it('stake limit increase > max stake', async () => { - await limits.encodeStakeLimitSlot(5, 0, 0, 0) - await limits.encodeStakeLimitSlot(5, 5, 0, 0) - - assertRevert(limits.encodeStakeLimitSlot(5, 6, 0, 0), `TOO_LARGE_LIMIT_INCREASE`) + let maxStakeLimit = 5 + let maxStakeLimitIncreasePerBlock = 0 + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) + await limits.resumeStakingWithNewLimit(slot, maxStakeLimit, maxStakeLimitIncreasePerBlock) + + maxStakeLimit = 5 + maxStakeLimitIncreasePerBlock = 5 + await limits.resumeStakingWithNewLimit(slot, maxStakeLimit, maxStakeLimitIncreasePerBlock) + + maxStakeLimit = 5 + maxStakeLimitGrowthBlocks = 6 + assertRevert(limits.resumeStakingWithNewLimit(slot, maxStakeLimit, maxStakeLimitGrowthBlocks), 'TOO_LARGE_LIMIT_INCREASE') }) it('stake limit reverts on large values', async () => { - assertRevert(limits.encodeStakeLimitSlot(toBN(2).pow(toBN(96)), 1, 1, 1), `TOO_LARGE_MAX_STAKE_LIMIT`) - assertRevert(limits.encodeStakeLimitSlot(1, 1, toBN(2).pow(toBN(96), 1), 1), `TOO_LARGE_PREV_STAKE_LIMIT`) - assertRevert(limits.encodeStakeLimitSlot(1, 1, 1, toBN(2).pow(toBN(32), 1)), `TOO_LARGE_BLOCK_NUMBER`) + let maxStakeLimit = toBN(2).pow(toBN(96)) + let maxStakeLimitIncreasePerBlock = 1 + const slot = await limits.setStorageStakeLimitStruct(0, 0, 0, 0) + assertRevert(limits.resumeStakingWithNewLimit(slot, maxStakeLimit, maxStakeLimitIncreasePerBlock), 'TOO_LARGE_MAX_STAKE_LIMIT') + + maxStakeLimit = toBN(2).mul(toBN(10).pow(toBN(18))) + maxStakeLimitIncreasePerBlock = toBN(10) + assertRevert(limits.resumeStakingWithNewLimit(slot, maxStakeLimit, maxStakeLimitIncreasePerBlock), `TOO_SMALL_LIMIT_INCREASE`) }) it('check update calculate stake limit with different blocks', async () => { const block = await web3.eth.getBlock('latest') - const slot = await limits.encodeStakeLimitSlot(100, 50, 0, block.number) + const maxStakeLimit = 100 + const increasePerBlock = 50 + const maxStakeLimitGrowthBlocks = maxStakeLimit / increasePerBlock + + const slot = await limits.setStorageStakeLimitStruct(block.number, 0, maxStakeLimitGrowthBlocks, maxStakeLimit) - const currentStakeLimit = await limits.calculateCurrentStakeLimit(slot) - assertBn(currentStakeLimit, 0) + const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit2, 0) const block2 = await waitBlocks(1) assert.equal(block2.number, block.number + 1) - const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) - assertBn(currentStakeLimit2, 50) + const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit3, 50) const block3 = await waitBlocks(3) assert.equal(block3.number, block.number + 1 + 3) - const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(slot) - assertBn(currentStakeLimit3, 100) + const currentStakeLimit4 = await limits.calculateCurrentStakeLimit(slot) + assertBn(currentStakeLimit4, 100) }) it('check update stake limit', async () => { - const maxLimit = 100 - const incPerBlock = 50 const block = await web3.eth.getBlock('latest') - const slot = await limits.encodeStakeLimitSlot(maxLimit, incPerBlock, 0, block.number) - const decodedSlot = await limits.decodeStakeLimitSlot(slot) + const maxStakeLimit = 100 + const increasePerBlock = 50 + const maxStakeLimitGrowthBlocks = maxStakeLimit / increasePerBlock + + const slot = await limits.setStorageStakeLimitStruct(block.number, 0, maxStakeLimitGrowthBlocks, maxStakeLimit) + const decodedSlot = await limits.getStorageStakeLimit(slot) assert.equal(decodedSlot.prevStakeBlockNumber, block.number) assert.equal(decodedSlot.prevStakeLimit, 0) @@ -131,81 +139,51 @@ contract.skip('StakingLimits', () => { assert.equal(block2.number, block.number + 3) const currentStakeLimit2 = await limits.calculateCurrentStakeLimit(slot) - assertBn(currentStakeLimit2, maxLimit) + assertBn(currentStakeLimit2, maxStakeLimit) const deposit = 87 const newSlot = await limits.updatePrevStakeLimit(slot, currentStakeLimit2 - deposit) - const decodedNewSlot = await limits.decodeStakeLimitSlot(newSlot) + const decodedNewSlot = await limits.getStorageStakeLimit(newSlot) assert.equal(decodedNewSlot.prevStakeBlockNumber, block2.number) assert.equal(decodedNewSlot.prevStakeLimit, 13) // checking staking recovery await waitBlocks(1) const currentStakeLimit3 = await limits.calculateCurrentStakeLimit(newSlot) - assertBn(currentStakeLimit3, 13 + incPerBlock) + assertBn(currentStakeLimit3, 13 + increasePerBlock) await waitBlocks(1) const currentStakeLimit4 = await limits.calculateCurrentStakeLimit(newSlot) - assertBn(currentStakeLimit4, maxLimit) + assertBn(currentStakeLimit4, maxStakeLimit) }) it('max values', async () => { const block = await web3.eth.getBlock('latest') - const maxLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 - let minIncPerBlock = 1 // uint96 - const maxPrevStakeLimit = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 - const maxBlock = toBN(2).pow(toBN(32)).sub(toBN(1)) // uint32 - assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock), `TOO_SMALL_LIMIT_INCREASE`) + const max32 = toBN(2).pow(toBN(32)).sub(toBN(1)) // uint32 + const max96 = toBN(2).pow(toBN(96)).sub(toBN(1)) // uint96 - minIncPerBlock = maxLimit.div(toBN(2).pow(toBN(32)).sub(toBN(1))) + const maxStakeLimit = max96 // uint96 + const maxStakeLimitGrowthBlocks = max32 + const maxPrevStakeLimit = max96 // uint96 + const maxBlock = max32 // uint32 - minIncPerBlockForRevert = minIncPerBlock.div(toBN(2)) // reverts - assertRevert(limits.encodeStakeLimitSlot(maxLimit, minIncPerBlockForRevert, maxPrevStakeLimit, maxBlock), `TOO_SMALL_LIMIT_INCREASE`) + // check that we CAN set max value - const maxSlot = await limits.encodeStakeLimitSlot(maxLimit, minIncPerBlock, maxPrevStakeLimit, maxBlock) + const maxSlot = await limits.setStorageStakeLimitStruct(maxBlock, maxPrevStakeLimit, maxStakeLimitGrowthBlocks, maxStakeLimit) const maxUint256 = toBN(2).pow(toBN(256)).sub(toBN(1)) assertBn(maxSlot, maxUint256) - const decodedRaw = await limits.decodeStakeLimitSlot(maxSlot) - - // console.log(decodedRaw) + const decodedRaw = await limits.getStorageStakeLimit(maxSlot) - const maxStakeLimit = decodedRaw.maxStakeLimit - const stakeLimitIncPerBlock = decodedRaw.stakeLimitIncPerBlock - const prevStakeLimit = decodedRaw.prevStakeLimit - const prevStakeBlockNumber = decodedRaw.prevStakeBlockNumber + const decodedMaxLimit = decodedRaw.maxStakeLimit + const decodedMaxStakeLimitGrowthBlocks = decodedRaw.maxStakeLimitGrowthBlocks + const decodedPrevStakeLimit = decodedRaw.prevStakeLimit + const decodedPrevStakeBlockNumber = decodedRaw.prevStakeBlockNumber - const growthBlock = maxSlot.shrn(128) - // console.log(maxSlot.toString(2)) - console.log({ - stakeLimitIncPerBlock: stakeLimitIncPerBlock.toString(), - maxBlock: maxBlock.toString(), - maxStakeLimit: maxStakeLimit.toString(), - growthBlock: growthBlock.toTwos(32).toString(2) - }) - - assertBn(maxStakeLimit, maxLimit) - assertBn(stakeLimitIncPerBlock, minIncPerBlock) - assertBn(prevStakeLimit, maxPrevStakeLimit) - assertBn(prevStakeBlockNumber, maxBlock) + assertBn(decodedMaxLimit, max96) + assertBn(decodedMaxStakeLimitGrowthBlocks, max32) + assertBn(decodedPrevStakeLimit, max96) + assertBn(decodedPrevStakeBlockNumber, max32) }) }) - -function pad32(num) { - return pz(num, 32) -} - -function pad96(num) { - return pz(num, 96) -} - -function pad256(num) { - return pz(num, 256) -} - -function pz(num, size) { - var s = num + '' - while (s.length < size) s = '0' + s - return s -} From 3d70d5b0ca5bdda258d39c7f06d96db3f4b27849 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 18 May 2022 17:16:15 +0000 Subject: [PATCH 157/159] feat: refactor lido interface Add more meaningful API for staking limits Rename STAKING_PAUSE_ROLE to STAKING_CONTROL_ROLE [skip ci] Tests needs to be updated --- contracts/0.4.24/Lido.sol | 99 ++++++++++--------- contracts/0.4.24/interfaces/ILido.sol | 53 ++++++---- contracts/0.4.24/lib/StakeLimitUtils.sol | 54 +++++++--- contracts/0.4.24/template/LidoTemplate.sol | 2 +- .../test_helpers/StakeLimitUtilsMock.sol | 14 ++- scripts/multisig/12-check-dao.js | 2 +- .../28-vote-merge-ready-first-pack-upgrade.js | 6 +- test/0.4.24/lido.test.js | 6 +- test/0.4.24/staking-limit.test.js | 6 +- test/deposit.test.js | 2 +- test/scenario/helpers/deploy.js | 6 +- 11 files changed, 155 insertions(+), 95 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index b85d9b2c1..7e902e318 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -58,7 +58,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 constant public PAUSE_ROLE = keccak256("PAUSE_ROLE"); bytes32 constant public RESUME_ROLE = keccak256("RESUME_ROLE"); bytes32 constant public STAKING_PAUSE_ROLE = keccak256("STAKING_PAUSE_ROLE"); - bytes32 constant public STAKING_RESUME_ROLE = keccak256("STAKING_RESUME_ROLE"); + bytes32 constant public STAKING_CONTROL_ROLE = keccak256("STAKING_CONTROL_ROLE"); bytes32 constant public MANAGE_FEE = keccak256("MANAGE_FEE"); bytes32 constant public MANAGE_WITHDRAWAL_KEY = keccak256("MANAGE_WITHDRAWAL_KEY"); bytes32 constant public MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE"); @@ -136,17 +136,11 @@ contract Lido is ILido, StETH, AragonApp { _setProtocolContracts(_oracle, _treasury, _insuranceFund); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( - 0, 0 // STAKING IS UNLIMITED - ) - ); - initialized(); } /** - * @notice Stops accepting new Ether to the protocol. + * @notice Stops accepting new Ether to the protocol * * @dev While accepting new Ether is stopped, calls to the `submit` function, * as well as to the default payable function, will revert. @@ -161,10 +155,25 @@ contract Lido is ILido, StETH, AragonApp { /** * @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) - * and updates the staking rate limit. + * NB: Staking could be rate-limited by imposing a limit on the stake amount + * at each moment in time, see `setStakingLimit()` and `removeStakingLimit()` + * + * @dev Preserves staking limit if it was set previously * - * Staking could be rate-limited by imposing a limit on the stake amount - * at each moment in time. + * Emits `StakingResumed` event + */ + function resumeStaking() external { + _auth(STAKING_CONTROL_ROLE); + + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(false) + ); + + emit StakingResumed(); + } + + /** + * @notice Sets the staking rate limit * * ▲ Stake limit * │..... ..... ........ ... .... ... Stake limit = max @@ -174,28 +183,43 @@ contract Lido is ILido, StETH, AragonApp { * │──────────────────────────────────────────────────> Time * │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events * - * NB: To resume without limits pass zero arg values. * @dev Reverts if: + * - `_maxStakeLimit` == 0 * - `_maxStakeLimit` >= 2^96 * - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` * - `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2^32 (only if `_stakeLimitIncreasePerBlock` != 0) - * Emits `StakeResumed` event + * + * Emits `StakingLimitSet` event + * * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ - function resumeStaking( - uint256 _maxStakeLimit, - uint256 _stakeLimitIncreasePerBlock - ) external { - _auth(STAKING_RESUME_ROLE); + function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external { + _auth(STAKING_CONTROL_ROLE); STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( - _maxStakeLimit, _stakeLimitIncreasePerBlock + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakingLimit( + _maxStakeLimit, + _stakeLimitIncreasePerBlock ) ); - emit StakingResumed(_maxStakeLimit, _stakeLimitIncreasePerBlock); + emit StakingLimitSet(_maxStakeLimit, _stakeLimitIncreasePerBlock); + } + + /** + * @notice Removes the staking rate limit + * + * Emits `StakingLimitRemoved` event + */ + function removeStakingLimit() external { + _auth(STAKING_CONTROL_ROLE); + + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().removeStakingLimit() + ); + + emit StakingLimitRemoved(); } /** @@ -219,7 +243,7 @@ contract Lido is ILido, StETH, AragonApp { * @notice Returns full info about current stake limit params and state * @dev Might be used for the advanced integration requests. * @return isStakingPaused staking pause state (equivalent to return of isStakingPaused()) - * @return isStakingLimitApplied whether the stake limit is set + * @return isStakingLimitSet whether the stake limit is set * @return currentStakeLimit current stake limit (equivalent to return of getCurrentStakeLimit()) * @return maxStakeLimit max stake limit * @return maxStakeLimitGrowthBlocks blocks needed to restore max stake limit from the fully exhausted state @@ -228,7 +252,7 @@ contract Lido is ILido, StETH, AragonApp { */ function getStakeLimitFullInfo() external view returns ( bool isStakingPaused, - bool isStakingLimitApplied, + bool isStakingLimitSet, uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, @@ -238,7 +262,7 @@ contract Lido is ILido, StETH, AragonApp { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); isStakingPaused = stakeLimitData.isStakingPaused(); - isStakingLimitApplied = stakeLimitData.isStakingLimitApplied(); + isStakingLimitSet = stakeLimitData.isStakingLimitSet(); currentStakeLimit = _getCurrentStakeLimit(stakeLimitData); @@ -515,23 +539,6 @@ contract Lido is ILido, StETH, AragonApp { emit RecoverToVault(vault, _token, balance); } - /** - * @notice Send NTFs to recovery Vault - * @param _token Token to be sent to recovery vault - * @param _tokenId Token Id - */ - function transferERC721ToVault(address _token, uint256 _tokenId) external { - require(_token != address(0), "ZERO_ADDRESS"); - require(allowRecoverability(_token), "RECOVER_DISALLOWED"); - - address vault = getRecoveryVault(); - require(vault != address(0), "RECOVER_VAULT_ZERO"); - - IERC721(_token).transferFrom(address(this), vault, _tokenId); - - emit RecoverERC721ToVault(vault, _token, _tokenId); - } - /** * @notice Returns staking rewards fee rate */ @@ -673,7 +680,7 @@ contract Lido is ILido, StETH, AragonApp { StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); require(!stakeLimitData.isStakingPaused(), "STAKING_PAUSED"); - if (stakeLimitData.isStakingLimitApplied()) { + if (stakeLimitData.isStakingLimitSet()) { uint256 currentStakeLimit = stakeLimitData.calculateCurrentStakeLimit(); require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); @@ -967,8 +974,10 @@ contract Lido is ILido, StETH, AragonApp { } function _pauseStaking() internal { - StakeLimitState.Data memory zeroState; - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct(zeroState); + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(true) + ); + emit StakingPaused(); } @@ -976,7 +985,7 @@ contract Lido is ILido, StETH, AragonApp { if (_stakeLimitData.isStakingPaused()) { return 0; } - if (!_stakeLimitData.isStakingLimitApplied()) { + if (!_stakeLimitData.isStakingLimitSet()) { return uint256(-1); } diff --git a/contracts/0.4.24/interfaces/ILido.sol b/contracts/0.4.24/interfaces/ILido.sol index b66243446..17b5427cc 100644 --- a/contracts/0.4.24/interfaces/ILido.sol +++ b/contracts/0.4.24/interfaces/ILido.sol @@ -33,7 +33,7 @@ interface ILido { function resume() external; /** - * @notice Stops accepting new Ether to the protocol. + * @notice Stops accepting new Ether to the protocol * * @dev While accepting new Ether is stopped, calls to the `submit` function, * as well as to the default payable function, will revert. @@ -44,12 +44,37 @@ interface ILido { /** * @notice Resumes accepting new Ether to the protocol (if `pauseStaking` was called previously) - * and updates the staking rate limit. - * NB: To resume without limits pass zero arg values. + * NB: Staking could be rate-limited by imposing a limit on the stake amount + * at each moment in time, see `setStakingLimit()` and `removeStakingLimit()` + * + * @dev Preserves staking limit if it was set previously + * + * Emits `StakingResumed` event + */ + function resumeStaking() external; + + /** + * @notice Sets the staking rate limit + * + * @dev Reverts if: + * - `_maxStakeLimit` == 0 + * - `_maxStakeLimit` >= 2^96 + * - `_maxStakeLimit` < `_stakeLimitIncreasePerBlock` + * - `_maxStakeLimit` / `_stakeLimitIncreasePerBlock` >= 2^32 (only if `_stakeLimitIncreasePerBlock` != 0) + * + * Emits `StakingLimitSet` event + * * @param _maxStakeLimit max stake limit value * @param _stakeLimitIncreasePerBlock stake limit increase per single block */ - function resumeStaking(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external; + function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external; + + /** + * @notice Removes the staking rate limit + * + * Emits `StakingLimitRemoved` event + */ + function removeStakingLimit() external; /** * @notice Check staking state: whether it's paused or not @@ -68,7 +93,7 @@ interface ILido { * @notice Returns full info about current stake limit params and state * @dev Might be used for the advanced integration requests. * @return isStakingPaused staking pause state (equivalent to return of isStakingPaused()) - * @return isStakingLimitApplied whether the stake limit is set + * @return isStakingLimitSet whether the stake limit is set * @return currentStakeLimit current stake limit (equivalent to return of getCurrentStakeLimit()) * @return maxStakeLimit max stake limit * @return maxStakeLimitGrowthBlocks blocks needed to restore max stake limit from the fully exhausted state @@ -77,7 +102,7 @@ interface ILido { */ function getStakeLimitFullInfo() external view returns ( bool isStakingPaused, - bool isStakingLimitApplied, + bool isStakingLimitSet, uint256 currentStakeLimit, uint256 maxStakeLimit, uint256 maxStakeLimitGrowthBlocks, @@ -87,16 +112,11 @@ interface ILido { event Stopped(); event Resumed(); - event StakingPaused(); - /** - * @notice resumeStaking was called with the provided params - * @dev use `getCurrentStakeLimit()` and `getCurrentStakeLimit()` funcs to check the actual limit - * NB: if limits are not set then both args have zero values. - * @param maxStakeLimit max stake limit value - * @param stakeLimitIncreasePerBlock stake limit increase per single block - */ - event StakingResumed(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); + event StakingPaused(); + event StakingResumed(); + event StakingLimitSet(uint256 maxStakeLimit, uint256 stakeLimitIncreasePerBlock); + event StakingLimitRemoved(); /** * @notice Set Lido protocol contracts (oracle, treasury, insurance fund). @@ -241,7 +261,4 @@ interface ILido { * @return beaconBalance - total amount of Beacon-side Ether (sum of all the balances of Lido validators) */ function getBeaconStat() external view returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance); - - // Requested ERC721 recovery from the `Lido` to the designated `recoveryVault` vault. - event RecoverERC721ToVault(address indexed vault, address indexed token, uint256 tokenId); } diff --git a/contracts/0.4.24/lib/StakeLimitUtils.sol b/contracts/0.4.24/lib/StakeLimitUtils.sol index c4e1f948a..f9353e255 100644 --- a/contracts/0.4.24/lib/StakeLimitUtils.sol +++ b/contracts/0.4.24/lib/StakeLimitUtils.sol @@ -27,7 +27,7 @@ import "@aragon/os/contracts/common/UnstructuredStorage.sol"; // 32 bits 96 bits 96 bits // // -// - the "staking paused" state is encoded by all fields being zero, +// - the "staking paused" state is encoded by `prevStakeBlockNumber` being zero, // - the "staking unlimited" state is encoded by `maxStakeLimit` being zero and `prevStakeBlockNumber` being non-zero. // @@ -108,36 +108,32 @@ library StakeLimitUtils { } /** - * @notice check if staking is on pause (i.e. every byte in the slot has a zero value) + * @notice check if staking is on pause */ function isStakingPaused(StakeLimitState.Data memory _data) internal pure returns(bool) { - return ( - _data.maxStakeLimit - | _data.maxStakeLimitGrowthBlocks - | _data.prevStakeBlockNumber - | _data.prevStakeLimit - ) == 0; + return _data.prevStakeBlockNumber == 0; } /** - * @notice check if staking limit is applied (otherwise staking is unlimited) + * @notice check if staking limit is set (otherwise staking is unlimited) */ - function isStakingLimitApplied(StakeLimitState.Data memory _data) internal pure returns(bool) { + function isStakingLimitSet(StakeLimitState.Data memory _data) internal pure returns(bool) { return _data.maxStakeLimit != 0; } /** - * @notice prepare stake limit repr to resume staking with the desired limits + * @notice update stake limit repr with the desired limits * @dev input `_data` param is mutated and the func returns effectively the same pointer * @param _data stake limit state struct * @param _maxStakeLimit stake limit max value * @param _stakeLimitIncreasePerBlock stake limit increase (restoration) per block */ - function resumeStakingWithNewLimit( + function setStakingLimit( StakeLimitState.Data memory _data, uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock ) internal view returns (StakeLimitState.Data memory) { + require(_maxStakeLimit != 0, "ZERO_MAX_STAKE_LIMIT"); require(_maxStakeLimit <= uint96(-1), "TOO_LARGE_MAX_STAKE_LIMIT"); require(_maxStakeLimit >= _stakeLimitIncreasePerBlock, "TOO_LARGE_LIMIT_INCREASE"); require( @@ -155,7 +151,23 @@ library StakeLimitUtils { _data.maxStakeLimitGrowthBlocks = _stakeLimitIncreasePerBlock != 0 ? uint32(_maxStakeLimit / _stakeLimitIncreasePerBlock) : 0; _data.maxStakeLimit = uint96(_maxStakeLimit); - _data.prevStakeBlockNumber = uint32(block.number); + + if (_data.prevStakeBlockNumber != 0) { + _data.prevStakeBlockNumber = uint32(block.number); + } + + return _data; + } + + /** + * @notice update stake limit repr to remove the limit + * @dev input `_data` param is mutated and the func returns effectively the same pointer + * @param _data stake limit state struct + */ + function removeStakingLimit( + StakeLimitState.Data memory _data + ) internal view returns (StakeLimitState.Data memory) { + _data.maxStakeLimit = 0; return _data; } @@ -171,10 +183,26 @@ library StakeLimitUtils { uint256 _newPrevStakeLimit ) internal view returns (StakeLimitState.Data memory) { assert(_newPrevStakeLimit <= uint96(-1)); + assert(_data.prevStakeBlockNumber != 0); _data.prevStakeLimit = uint96(_newPrevStakeLimit); _data.prevStakeBlockNumber = uint32(block.number); return _data; } + + /** + * @notice set stake limit pause state (on or off) + * @dev input `_data` param is mutated and the func returns effectively the same pointer + * @param _data stake limit state struct + * @param _isPaused pause state flag + */ + function setStakeLimitPauseState( + StakeLimitState.Data memory _data, + bool _isPaused + ) internal view returns (StakeLimitState.Data memory) { + _data.prevStakeBlockNumber = uint32(_isPaused ? 0 : block.number); + + return _data; + } } diff --git a/contracts/0.4.24/template/LidoTemplate.sol b/contracts/0.4.24/template/LidoTemplate.sol index 07c6708d9..5f8fa4138 100644 --- a/contracts/0.4.24/template/LidoTemplate.sol +++ b/contracts/0.4.24/template/LidoTemplate.sol @@ -656,7 +656,7 @@ contract LidoTemplate is IsContract { perms[4] = _state.lido.BURN_ROLE(); perms[5] = _state.lido.RESUME_ROLE(); perms[6] = _state.lido.STAKING_PAUSE_ROLE(); - perms[7] = _state.lido.STAKING_RESUME_ROLE(); + perms[7] = _state.lido.STAKING_CONTROL_ROLE(); perms[8] = _state.lido.SET_EL_REWARDS_VAULT_ROLE(); perms[9] = _state.lido.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(); diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol index c39ffef7a..e98086972 100644 --- a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -53,13 +53,13 @@ contract StakeLimitUtilsMock { return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingPaused(); } - function isStakingLimitApplied(uint256 _slotValue) public view returns(bool) { - return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingLimitApplied(); + function isStakingLimitSet(uint256 _slotValue) public view returns(bool) { + return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingLimitSet(); } - function resumeStakingWithNewLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { + function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().resumeStakingWithNewLimit( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakingLimit( _maxStakeLimit, _stakeLimitIncreasePerBlock ) ); @@ -70,4 +70,10 @@ contract StakeLimitUtilsMock { STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().updatePrevStakeLimit(_newPrevLimit) ); } + + function setStakeLimitPauseState(bool _isPaused) public view { + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(_isPaused) + ); + } } diff --git a/scripts/multisig/12-check-dao.js b/scripts/multisig/12-check-dao.js index f1a6a53f5..e11f3f191 100644 --- a/scripts/multisig/12-check-dao.js +++ b/scripts/multisig/12-check-dao.js @@ -575,7 +575,7 @@ async function assertDaoPermissions({ kernel, lido, oracle, nopsRegistry, agent, 'SET_TREASURY', 'SET_INSURANCE_FUND', 'STAKING_PAUSE_ROLE', - 'STAKING_RESUME_ROLE', + 'STAKING_CONTROL_ROLE', 'SET_EL_REWARDS_VAULT_ROLE', 'SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE' ], diff --git a/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js index 6082c7e1a..cf06ca6f5 100644 --- a/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js +++ b/scripts/multisig/28-vote-merge-ready-first-pack-upgrade.js @@ -87,7 +87,7 @@ async function createVoting({ web3, artifacts }) { const grantSetELRewardsWithdrawalLimitRoleCallData = await createGrantRoleForLidoAppCallData('SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE') const grantResumeRoleCallData = await createGrantRoleForLidoAppCallData('RESUME_ROLE') const grantStakingPauseRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_PAUSE_ROLE') - const grantStakingResumeRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_RESUME_ROLE') + const grantStakingControlRoleCallData = await createGrantRoleForLidoAppCallData('STAKING_CONTROL_ROLE') const grantManageProtocolContractsRoleCallData = await createGrantRoleForLidoAppCallData('MANAGE_PROTOCOL_CONTRACTS_ROLE') const setELRewardsVaultCallData = { @@ -120,7 +120,7 @@ async function createVoting({ web3, artifacts }) { grantSetELRewardsWithdrawalLimitRoleCallData, grantResumeRoleCallData, grantStakingPauseRoleCallData, - grantStakingResumeRoleCallData, + grantStakingControlRoleCallData, grantManageProtocolContractsRoleCallData, setELRewardsVaultCallData, setELRewardsWithdrawalLimitCallData, @@ -148,7 +148,7 @@ async function createVoting({ web3, artifacts }) { 9) Grant role SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE to voting 10) Grant role RESUME_ROLE to voting 11) Grant role STAKING_PAUSE_ROLE to voting -12) Grant role STAKING_RESUME_ROLE to voting +12) Grant role STAKING_CONTROL_ROLE to voting 13) Grant role MANAGE_PROTOCOL_CONTRACTS_ROLE to voting 14) Set deployed LidoExecutionLayerRewardsVault to Lido contract 15) Set Execution Layer rewards withdrawal limit to ${elRewardsWithdrawalLimitPoints} basis points diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 612cf7159..3e164b9eb 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -93,7 +93,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_CONTROL_ROLE(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.MANAGE_SIGNING_KEYS(), appManager, { from: appManager }) await acl.createPermission(voting, operators.address, await operators.ADD_NODE_OPERATOR_ROLE(), appManager, { from: appManager }) @@ -572,7 +572,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(currentStakeLimit, expectedCurrentStakeLimit) ;({ isStakingPaused, - isStakingLimitApplied, + isStakingLimitSet, currentStakeLimit, maxStakeLimit, maxStakeLimitGrowthBlocks, @@ -583,7 +583,7 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(currentStakeLimit, expectedCurrentStakeLimit) assertBn(maxStakeLimit, expectedMaxStakeLimit) assert.equal(isStakingPaused, expectedIsStakingPaused) - assert.equal(isStakingLimitApplied, expectedIsStakingLimited) + assert.equal(isStakingLimitSet, expectedIsStakingLimited) assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) } diff --git a/test/0.4.24/staking-limit.test.js b/test/0.4.24/staking-limit.test.js index ad0f4a297..d6f80fc16 100644 --- a/test/0.4.24/staking-limit.test.js +++ b/test/0.4.24/staking-limit.test.js @@ -28,7 +28,7 @@ const ETH = (value) => web3.utils.toWei(value + '', 'ether') // 32 bits 96 bits 96 bits // // -// - the "staking paused" state is encoded by all fields being zero, +// - the "staking paused" state is encoded by `prevStakeBlockNumber` being zero, // - the "staking unlimited" state is encoded by `maxStakeLimit` being zero and `prevStakeBlockNumber` being non-zero. // @@ -74,13 +74,13 @@ contract.skip('StakingLimits', () => { it('check staking rate limit', async () => { const slot = 0 - const limited = await limits.isStakingLimitApplied(slot) + const limited = await limits.isStakingLimitSet(slot) assert.equal(limited, false, 'limits not limited') const maxStakeLimit = 10 const slot2 = await limits.encodeStakeLimitSlot(maxStakeLimit, 0, 0, 0) - const limited2 = await limits.isStakingLimitApplied(slot2) + const limited2 = await limits.isStakingLimitSet(slot2) assert.equal(limited2, true, 'limits not limited') }) diff --git a/test/deposit.test.js b/test/deposit.test.js index 295acb8ad..4e0f1f717 100644 --- a/test/deposit.test.js +++ b/test/deposit.test.js @@ -88,7 +88,7 @@ contract('Lido with official deposit contract', ([appManager, voting, user1, use from: appManager }) await acl.createPermission(voting, app.address, await app.STAKING_PAUSE_ROLE(), appManager, { from: appManager }) - await acl.createPermission(voting, app.address, await app.STAKING_RESUME_ROLE(), appManager, { from: appManager }) + await acl.createPermission(voting, app.address, await app.STAKING_CONTROL_ROLE(), appManager, { from: appManager }) // await acl.createPermission(app.address, token.address, await token.MINT_ROLE(), appManager, { from: appManager }) // await acl.createPermission(app.address, token.address, await token.BURN_ROLE(), appManager, { from: appManager }) diff --git a/test/scenario/helpers/deploy.js b/test/scenario/helpers/deploy.js index ba809d5b7..b6334e503 100644 --- a/test/scenario/helpers/deploy.js +++ b/test/scenario/helpers/deploy.js @@ -73,7 +73,7 @@ async function deployDaoAndPool(appManager, voting) { POOL_BURN_ROLE, DEPOSIT_ROLE, STAKING_PAUSE_ROLE, - STAKING_RESUME_ROLE, + STAKING_CONTROL_ROLE, SET_EL_REWARDS_VAULT_ROLE, SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE, NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, @@ -91,7 +91,7 @@ async function deployDaoAndPool(appManager, voting) { pool.BURN_ROLE(), pool.DEPOSIT_ROLE(), pool.STAKING_PAUSE_ROLE(), - pool.STAKING_RESUME_ROLE(), + pool.STAKING_CONTROL_ROLE(), pool.SET_EL_REWARDS_VAULT_ROLE(), pool.SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE(), nodeOperatorRegistry.MANAGE_SIGNING_KEYS(), @@ -111,7 +111,7 @@ async function deployDaoAndPool(appManager, voting) { acl.createPermission(voting, pool.address, POOL_MANAGE_WITHDRAWAL_KEY, appManager, { from: appManager }), acl.createPermission(voting, pool.address, POOL_BURN_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, STAKING_PAUSE_ROLE, appManager, { from: appManager }), - acl.createPermission(voting, pool.address, STAKING_RESUME_ROLE, appManager, { from: appManager }), + acl.createPermission(voting, pool.address, STAKING_CONTROL_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, SET_EL_REWARDS_VAULT_ROLE, appManager, { from: appManager }), acl.createPermission(voting, pool.address, SET_EL_REWARDS_WITHDRAWAL_LIMIT_ROLE, appManager, { from: appManager }), From 8c8e67ee008af8e8c9beab1b967b40f398617d0e Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 18 May 2022 18:44:50 +0000 Subject: [PATCH 158/159] feat: resume incurs resumeStaking --- contracts/0.4.24/Lido.sol | 15 +++-- contracts/0.4.24/test_helpers/LidoMock.sol | 1 + test/0.4.24/lido.test.js | 77 ++++++++++------------ 3 files changed, 44 insertions(+), 49 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 7e902e318..f6068fc6e 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -165,11 +165,7 @@ contract Lido is ILido, StETH, AragonApp { function resumeStaking() external { _auth(STAKING_CONTROL_ROLE); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(false) - ); - - emit StakingResumed(); + _resumeStaking(); } /** @@ -354,6 +350,7 @@ contract Lido is ILido, StETH, AragonApp { _auth(RESUME_ROLE); _resume(); + _resumeStaking(); } /** @@ -981,6 +978,14 @@ contract Lido is ILido, StETH, AragonApp { emit StakingPaused(); } + function _resumeStaking() internal { + STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(false) + ); + + emit StakingResumed(); + } + function _getCurrentStakeLimit(StakeLimitState.Data memory _stakeLimitData) internal view returns(uint256) { if (_stakeLimitData.isStakingPaused()) { return 0; diff --git a/contracts/0.4.24/test_helpers/LidoMock.sol b/contracts/0.4.24/test_helpers/LidoMock.sol index 8aa191157..be7dc3844 100644 --- a/contracts/0.4.24/test_helpers/LidoMock.sol +++ b/contracts/0.4.24/test_helpers/LidoMock.sol @@ -28,6 +28,7 @@ contract LidoMock is Lido { ); _resume(); + _resumeStaking(); } /** diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index 3e164b9eb..e32487be8 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -570,6 +570,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) ) => { currentStakeLimit = await app.getCurrentStakeLimit() assertBn(currentStakeLimit, expectedCurrentStakeLimit) + + isStakingPaused = await app.isStakingPaused() + assert.equal(isStakingPaused, expectedIsStakingPaused) ;({ isStakingPaused, isStakingLimitSet, @@ -584,7 +587,10 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertBn(maxStakeLimit, expectedMaxStakeLimit) assert.equal(isStakingPaused, expectedIsStakingPaused) assert.equal(isStakingLimitSet, expectedIsStakingLimited) - assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) + + if (isStakingLimitSet) { + assertBn(maxStakeLimitGrowthBlocks, expectedLimitIncrease > 0 ? expectedMaxStakeLimit / expectedLimitIncrease : 0) + } } it('staking pause & unlimited resume works', async () => { @@ -598,19 +604,14 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.pauseStaking(), 'APP_AUTH_FAILED') receipt = await app.pauseStaking({ from: voting }) assertEvent(receipt, 'StakingPaused') - - assert.equal(await app.isStakingPaused(), true) verifyStakeLimitState(bn(0), bn(0), bn(0), true, false) await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(2) }), `STAKING_PAUSED`) await assertRevert(app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }), `STAKING_PAUSED`) - const disableStakeLimit = 0 - await assertRevert(app.resumeStaking(disableStakeLimit, disableStakeLimit), 'APP_AUTH_FAILED') - receipt = await app.resumeStaking(disableStakeLimit, disableStakeLimit, { from: voting }) + await assertRevert(app.resumeStaking(), 'APP_AUTH_FAILED') + receipt = await app.resumeStaking({ from: voting }) assertEvent(receipt, 'StakingResumed') - assert.equal(await app.isStakingPaused(), false) - await verifyStakeLimitState(bn(0), bn(0), MAX_UINT256, false, false) await web3.eth.sendTransaction({ to: app.address, from: user2, value: ETH(1.1) }) @@ -631,15 +632,16 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const expectedMaxStakeLimit = ETH(3) const limitIncreasePerBlock = bn(expectedMaxStakeLimit).div(bn(blocksToReachMaxStakeLimit)) // 1 * 10**16 - receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.resumeStaking({ from: voting }) + assertEvent(receipt, 'StakingResumed') + receipt = await app.setStakingLimit(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: expectedMaxStakeLimit, stakeLimitIncreasePerBlock: limitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(2) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(2), referral: ZERO_ADDRESS } }) @@ -673,8 +675,9 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await mineNBlocks(10) await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) - await assertRevert(app.resumeStaking(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_LIMIT_INCREASE`) - await assertRevert(app.resumeStaking(ETH(1), bn(10), { from: voting }), `TOO_SMALL_LIMIT_INCREASE`) + await assertRevert(app.setStakingLimit(ETH(0), ETH(0), { from: voting }), `ZERO_MAX_STAKE_LIMIT`) + await assertRevert(app.setStakingLimit(ETH(1), ETH(1.1), { from: voting }), `TOO_LARGE_LIMIT_INCREASE`) + await assertRevert(app.setStakingLimit(ETH(1), bn(10), { from: voting }), `TOO_SMALL_LIMIT_INCREASE`) }) it('resume staking with an one-shot limit works', async () => { @@ -683,15 +686,16 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const expectedMaxStakeLimit = ETH(7) const limitIncreasePerBlock = 0 - receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.resumeStaking({ from: voting }) + assertEvent(receipt, 'StakingResumed') + receipt = await app.setStakingLimit(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: expectedMaxStakeLimit, stakeLimitIncreasePerBlock: limitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) receipt = await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(5) }) assertEvent(receipt, 'Submitted', { expectedArgs: { sender: user2, amount: ETH(5), referral: ZERO_ADDRESS } }) @@ -711,44 +715,48 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) const expectedMaxStakeLimit = ETH(9) const limitIncreasePerBlock = bn(expectedMaxStakeLimit).divn(100) - receipt = await app.resumeStaking(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.resumeStaking({ from: voting }) + assertEvent(receipt, 'StakingResumed') + receipt = await app.setStakingLimit(expectedMaxStakeLimit, limitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: expectedMaxStakeLimit, stakeLimitIncreasePerBlock: limitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) await verifyStakeLimitState(expectedMaxStakeLimit, limitIncreasePerBlock, expectedMaxStakeLimit, false, true) const smallerExpectedMaxStakeLimit = ETH(5) const smallerLimitIncreasePerBlock = bn(smallerExpectedMaxStakeLimit).divn(200) - receipt = await app.resumeStaking(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.setStakingLimit(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: smallerExpectedMaxStakeLimit, stakeLimitIncreasePerBlock: smallerLimitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) await verifyStakeLimitState(smallerExpectedMaxStakeLimit, smallerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit, false, true) const largerExpectedMaxStakeLimit = ETH(10) const largerLimitIncreasePerBlock = bn(largerExpectedMaxStakeLimit).divn(1000) - receipt = await app.resumeStaking(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, { from: voting }) - assertEvent(receipt, 'StakingResumed', { + receipt = await app.setStakingLimit(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, { from: voting }) + assertEvent(receipt, 'StakingLimitSet', { expectedArgs: { maxStakeLimit: largerExpectedMaxStakeLimit, stakeLimitIncreasePerBlock: largerLimitIncreasePerBlock } }) - assert.equal(await app.isStakingPaused(), false) await verifyStakeLimitState(largerExpectedMaxStakeLimit, largerLimitIncreasePerBlock, smallerExpectedMaxStakeLimit, false, true) + + receipt = await app.removeStakingLimit({ from: voting }) + assertEvent(receipt, 'StakingLimitRemoved') + + await verifyStakeLimitState(0, 0, bn(2).pow(bn(256)).sub(bn(1)), false, false) }) it('reverts when trying to call unknown function', async () => { @@ -958,8 +966,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) await assertRevert(app.resume({ from: user2 }), 'APP_AUTH_FAILED') await app.resume({ from: voting }) - await assertRevert(web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }), 'STAKING_PAUSED') - await app.resumeStaking(0, 0, { from: voting }) await web3.eth.sendTransaction({ to: app.address, from: user1, value: ETH(4) }) await app.methods['depositBufferedEther()']({ from: depositor }) @@ -1458,18 +1464,12 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) }) context('recovery vault', () => { - let nftToken - beforeEach(async () => { await anyToken.mint(app.address, 100) - - nftToken = await ERC721Mock.new() - await nftToken.mint(app.address, 777) }) it('reverts when vault is not set', async () => { await assertRevert(app.transferToVault(anyToken.address, { from: nobody }), 'RECOVER_VAULT_ZERO') - await assertRevert(app.transferERC721ToVault(nftToken.address, 777, { from: nobody }), 'RECOVER_VAULT_ZERO') }) context('recovery works with vault mock deployed', () => { @@ -1492,17 +1492,6 @@ contract('Lido', ([appManager, voting, user1, user2, user3, nobody, depositor]) assertEvent(receipt, 'RecoverToVault', { expectedArgs: { vault: vault.address, token: anyToken.address, amount: 100 } }) }) - it('recovery with nft tokens works and emits event', async () => { - await assertRevert(app.transferERC721ToVault(ZERO_ADDRESS, 777, { from: nobody })) - - assert.equal(await nftToken.ownerOf(777), app.address) - - const receipt = await app.transferERC721ToVault(nftToken.address, 777, { from: nobody }) - assertEvent(receipt, 'RecoverERC721ToVault', { expectedArgs: { vault: vault.address, token: nftToken.address, tokenId: 777 } }) - - assert.equal(await nftToken.ownerOf(777), vault.address) - }) - it('recovery with unaccounted ether works and emits event', async () => { await app.makeUnaccountedEther({ from: user1, value: ETH(10) }) const receipt = await app.transferToVault(ZERO_ADDRESS, { from: nobody }) From e8e47ad36281bfdfe29d463c147d07ace58a9c74 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 18 May 2022 20:44:19 +0000 Subject: [PATCH 159/159] feat: implement suggestions by @arwer13 Rename STAKE_LIMIT_POSITION to STAKING_STATE_POSITION. Remove extra comma. Better comments for the `distributeFee` func. --- contracts/0.4.24/Lido.sol | 31 +++++------ .../test_helpers/StakeLimitUtilsMock.sol | 52 +++++++++---------- deployed-goerli.json | 2 +- 3 files changed, 43 insertions(+), 42 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index f6068fc6e..90e5d55a4 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -94,7 +94,7 @@ contract Lido is ILido, StETH, AragonApp { bytes32 internal constant EL_REWARDS_VAULT_POSITION = keccak256("lido.Lido.executionLayerRewardsVault"); /// @dev storage slot position of the staking rate limit structure - bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("lido.Lido.stakeLimit"); + bytes32 internal constant STAKING_STATE_POSITION = keccak256("lido.Lido.stakeLimit"); /// @dev amount of Ether (on the current Ethereum side) buffered on this smart contract balance bytes32 internal constant BUFFERED_ETHER_POSITION = keccak256("lido.Lido.bufferedEther"); /// @dev number of deposited validators (incrementing counter of deposit operations). @@ -121,6 +121,7 @@ contract Lido is ILido, StETH, AragonApp { * @param _operators instance of Node Operators Registry * @param _treasury treasury contract * @param _insuranceFund insurance fund contract + * NB: by default, staking and the whole Lido pool are in paused state */ function initialize( IDepositContract _depositContract, @@ -193,8 +194,8 @@ contract Lido is ILido, StETH, AragonApp { function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external { _auth(STAKING_CONTROL_ROLE); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakingLimit( + STAKING_STATE_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.getStorageStakeLimitStruct().setStakingLimit( _maxStakeLimit, _stakeLimitIncreasePerBlock ) @@ -211,8 +212,8 @@ contract Lido is ILido, StETH, AragonApp { function removeStakingLimit() external { _auth(STAKING_CONTROL_ROLE); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().removeStakingLimit() + STAKING_STATE_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.getStorageStakeLimitStruct().removeStakingLimit() ); emit StakingLimitRemoved(); @@ -222,7 +223,7 @@ contract Lido is ILido, StETH, AragonApp { * @notice Check staking state: whether it's paused or not */ function isStakingPaused() external view returns (bool) { - return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingPaused(); + return STAKING_STATE_POSITION.getStorageStakeLimitStruct().isStakingPaused(); } /** @@ -232,7 +233,7 @@ contract Lido is ILido, StETH, AragonApp { * - 0 if staking is paused or if limit is exhausted. */ function getCurrentStakeLimit() public view returns (uint256) { - return _getCurrentStakeLimit(STAKE_LIMIT_POSITION.getStorageStakeLimitStruct()); + return _getCurrentStakeLimit(STAKING_STATE_POSITION.getStorageStakeLimitStruct()); } /** @@ -255,7 +256,7 @@ contract Lido is ILido, StETH, AragonApp { uint256 prevStakeLimit, uint256 prevStakeBlockNumber ) { - StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + StakeLimitState.Data memory stakeLimitData = STAKING_STATE_POSITION.getStorageStakeLimitStruct(); isStakingPaused = stakeLimitData.isStakingPaused(); isStakingLimitSet = stakeLimitData.isStakingLimitSet(); @@ -674,7 +675,7 @@ contract Lido is ILido, StETH, AragonApp { function _submit(address _referral) internal returns (uint256) { require(msg.value != 0, "ZERO_DEPOSIT"); - StakeLimitState.Data memory stakeLimitData = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + StakeLimitState.Data memory stakeLimitData = STAKING_STATE_POSITION.getStorageStakeLimitStruct(); require(!stakeLimitData.isStakingPaused(), "STAKING_PAUSED"); if (stakeLimitData.isStakingLimitSet()) { @@ -682,7 +683,7 @@ contract Lido is ILido, StETH, AragonApp { require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.setStorageStakeLimitStruct( stakeLimitData.updatePrevStakeLimit(currentStakeLimit - msg.value) ); } @@ -794,7 +795,7 @@ contract Lido is ILido, StETH, AragonApp { } /** - * @dev Distributes fee by minting and distributing corresponding amount of liquid tokens. + * @dev Distributes fee portion of the rewards by minting and distributing corresponding amount of liquid tokens. * @param _totalRewards Total rewards accrued on the Ethereum 2.0 side in wei */ function distributeFee(uint256 _totalRewards) internal { @@ -971,16 +972,16 @@ contract Lido is ILido, StETH, AragonApp { } function _pauseStaking() internal { - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(true) + STAKING_STATE_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(true) ); emit StakingPaused(); } function _resumeStaking() internal { - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(false) + STAKING_STATE_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(false) ); emit StakingResumed(); diff --git a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol index 74bbfa0f9..3b3f4d244 100644 --- a/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol +++ b/contracts/0.4.24/test_helpers/StakeLimitUtilsMock.sol @@ -12,7 +12,7 @@ contract StakeLimitUtilsMock { using StakeLimitUnstructuredStorage for bytes32; using StakeLimitUtils for StakeLimitState.Data; - bytes32 internal constant STAKE_LIMIT_POSITION = keccak256("abcdef"); + bytes32 internal constant STAKING_STATE_POSITION = keccak256("abcdef"); function getStorageStakeLimit(uint256 _slotValue) public view returns ( uint32 prevStakeBlockNumber, @@ -20,8 +20,8 @@ contract StakeLimitUtilsMock { uint32 maxStakeLimitGrowthBlocks, uint96 maxStakeLimit ) { - STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); - StakeLimitState.Data memory data = STAKE_LIMIT_POSITION.getStorageStakeLimitStruct(); + STAKING_STATE_POSITION.setStorageUint256(_slotValue); + StakeLimitState.Data memory data = STAKING_STATE_POSITION.getStorageStakeLimitStruct(); prevStakeBlockNumber = data.prevStakeBlockNumber; prevStakeLimit = data.prevStakeLimit; @@ -41,55 +41,55 @@ contract StakeLimitUtilsMock { data.maxStakeLimitGrowthBlocks = _maxStakeLimitGrowthBlocks; data.maxStakeLimit = _maxStakeLimit; - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct(data); - return STAKE_LIMIT_POSITION.getStorageUint256(); + STAKING_STATE_POSITION.setStorageStakeLimitStruct(data); + return STAKING_STATE_POSITION.getStorageUint256(); } function calculateCurrentStakeLimit(uint256 _slotValue) public view returns(uint256 limit) { - STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); - return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().calculateCurrentStakeLimit(); + STAKING_STATE_POSITION.setStorageUint256(_slotValue); + return STAKING_STATE_POSITION.getStorageStakeLimitStruct().calculateCurrentStakeLimit(); } function isStakingPaused(uint256 _slotValue) public view returns(bool) { - STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); - return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingPaused(); + STAKING_STATE_POSITION.setStorageUint256(_slotValue); + return STAKING_STATE_POSITION.getStorageStakeLimitStruct().isStakingPaused(); } function isStakingLimitSet(uint256 _slotValue) public view returns(bool) { - STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); - return STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().isStakingLimitSet(); + STAKING_STATE_POSITION.setStorageUint256(_slotValue); + return STAKING_STATE_POSITION.getStorageStakeLimitStruct().isStakingLimitSet(); } function setStakingLimit(uint256 _slotValue, uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) public view { - STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakingLimit( + STAKING_STATE_POSITION.setStorageUint256(_slotValue); + STAKING_STATE_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.getStorageStakeLimitStruct().setStakingLimit( _maxStakeLimit, _stakeLimitIncreasePerBlock ) ); } function removeStakingLimit(uint256 _slotValue) public view returns(uint256) { - STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().removeStakingLimit() + STAKING_STATE_POSITION.setStorageUint256(_slotValue); + STAKING_STATE_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.getStorageStakeLimitStruct().removeStakingLimit() ); - return STAKE_LIMIT_POSITION.getStorageUint256(); + return STAKING_STATE_POSITION.getStorageUint256(); } function updatePrevStakeLimit(uint256 _slotValue, uint256 _newPrevLimit) public view returns(uint256) { - STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().updatePrevStakeLimit(_newPrevLimit) + STAKING_STATE_POSITION.setStorageUint256(_slotValue); + STAKING_STATE_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.getStorageStakeLimitStruct().updatePrevStakeLimit(_newPrevLimit) ); - return STAKE_LIMIT_POSITION.getStorageUint256(); + return STAKING_STATE_POSITION.getStorageUint256(); } function setStakeLimitPauseState(uint256 _slotValue, bool _isPaused) public view returns(uint256) { - STAKE_LIMIT_POSITION.setStorageUint256(_slotValue); - STAKE_LIMIT_POSITION.setStorageStakeLimitStruct( - STAKE_LIMIT_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(_isPaused) + STAKING_STATE_POSITION.setStorageUint256(_slotValue); + STAKING_STATE_POSITION.setStorageStakeLimitStruct( + STAKING_STATE_POSITION.getStorageStakeLimitStruct().setStakeLimitPauseState(_isPaused) ); - return STAKE_LIMIT_POSITION.getStorageUint256(); + return STAKING_STATE_POSITION.getStorageUint256(); } } diff --git a/deployed-goerli.json b/deployed-goerli.json index 9fdc801c6..ec634865f 100644 --- a/deployed-goerli.json +++ b/deployed-goerli.json @@ -184,5 +184,5 @@ 0, 4 ], - "selfOwnedStETHBurnerAddress": "0xf6a64DcB06Ef7eB1ee94aDfD7D10ACB44D9A9888", + "selfOwnedStETHBurnerAddress": "0xf6a64DcB06Ef7eB1ee94aDfD7D10ACB44D9A9888" }