From 0bb2e323b57966b5495a7e5e0d57b5372af95e26 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Mon, 13 Mar 2023 23:02:52 +0700 Subject: [PATCH 01/66] feat: wq tests --- ...drawal-queue-requests-finalization.test.js | 71 +++++++- test/0.8.9/withdrawal-queue.test.js | 158 +++++++++++------- 2 files changed, 164 insertions(+), 65 deletions(-) diff --git a/test/0.8.9/withdrawal-queue-requests-finalization.test.js b/test/0.8.9/withdrawal-queue-requests-finalization.test.js index a350d1085..b592126fd 100644 --- a/test/0.8.9/withdrawal-queue-requests-finalization.test.js +++ b/test/0.8.9/withdrawal-queue-requests-finalization.test.js @@ -1,7 +1,7 @@ const { contract, ethers } = require('hardhat') const { itParam } = require('mocha-param') -const { StETH, shareRate, e18, e27, toBN } = require('../helpers/utils') +const { StETH, shareRate, e18, e27, toBN, ETH } = require('../helpers/utils') const { assert } = require('../helpers/assert') const { MAX_UINT256 } = require('../helpers/constants') const { EvmSnapshot } = require('../helpers/blockchain') @@ -71,6 +71,75 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { await snapshot.rollback() }) + context('calculateFinalizationBatches', () => { + afterEach(async () => { + await snapshot.rollback() + }) + it('reverts on invalid state', async () => { + await assert.reverts( + withdrawalQueue.calculateFinalizationBatches(shareRate(300), 100000, 1000, [ + ETH(10), + true, + Array(36).fill(0), + 0, + ]), + 'InvalidState()' + ) + await assert.reverts( + withdrawalQueue.calculateFinalizationBatches(shareRate(300), 100000, 1000, [0, false, Array(36).fill(0), 0]), + 'InvalidState()' + ) + }) + + it('works correctly on multiple calls', async () => { + const [requestId1, requestId2] = await withdrawalQueue.requestWithdrawals.call([ETH(1), ETH(1)], user, { + from: user, + }) + await withdrawalQueue.requestWithdrawals([ETH(1), ETH(1)], user, { + from: user, + }) + const calculatedBatches1 = await withdrawalQueue.calculateFinalizationBatches(shareRate(1), 10000000000, 1, [ + ETH(2), + false, + Array(36).fill(0), + 0, + ]) + + assert.equals(calculatedBatches1.remainingEthBudget, ETH(1)) + assert.equals(calculatedBatches1.finished, false) + assert.equals(calculatedBatches1.batchesLength, 1) + assert.equals(calculatedBatches1.batches[0], requestId1) + const calculatedBatches2 = await withdrawalQueue.calculateFinalizationBatches( + shareRate(1), + 10000000000, + 1, + calculatedBatches1 + ) + assert.equals(calculatedBatches2.remainingEthBudget, 0) + assert.equals(calculatedBatches2.finished, true) + assert.equals(calculatedBatches2.batchesLength, 1) + assert.equals(calculatedBatches2.batches[0], requestId2) + }) + + it('stops on maxTimestamp', async () => { + const [requestId1] = await withdrawalQueue.requestWithdrawals.call([ETH(1)], user, { + from: user, + }) + await withdrawalQueue.requestWithdrawals([ETH(1)], user, { + from: user, + }) + const [status] = await withdrawalQueue.getWithdrawalStatus([requestId1]) + const calculatedBatches1 = await withdrawalQueue.calculateFinalizationBatches( + shareRate(1), + +status.timestamp - 1, + 10, + [ETH(2), false, Array(36).fill(0), 0] + ) + assert.equals(calculatedBatches1.finished, true) + assert.equals(calculatedBatches1.batchesLength, 0) + }) + }) + context('1 request', () => { itParam('same rate ', [0.25, 0.5, 1], async (postFinalizationRate) => { const finalizationShareRate = shareRate(1) diff --git a/test/0.8.9/withdrawal-queue.test.js b/test/0.8.9/withdrawal-queue.test.js index a443e052c..06b825a46 100644 --- a/test/0.8.9/withdrawal-queue.test.js +++ b/test/0.8.9/withdrawal-queue.test.js @@ -171,6 +171,10 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, withdrawalQueue.onOracleReport(true, +timestamp + 1000000, +timestamp + 1100000, { from: steth.address }), 'InvalidReportTimestamp()' ) + await assert.reverts( + withdrawalQueue.onOracleReport(true, +timestamp - 100, +timestamp + 1100000, { from: steth.address }), + 'InvalidReportTimestamp()' + ) // enable timestamp = await getCurrentBlockTimestamp() const tx1 = await withdrawalQueue.onOracleReport(true, timestamp, timestamp, { from: steth.address }) @@ -357,6 +361,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const amount = bn(ETH(300)) beforeEach('Enqueue a request', async () => { + await snapshot.rollback() await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) }) @@ -436,6 +441,23 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await assert.reverts(withdrawalQueue.prefinalize([1], shareRate(0)), 'ZeroShareRate()') }) + it('batch reverts if share rate is zero', async () => { + await steth.setTotalPooledEther(ETH(900)) + await steth.mintShares(user, shares(1)) + await steth.approve(withdrawalQueue.address, StETH(600), { from: user }) + + await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) + await assert.reverts(withdrawalQueue.prefinalize([2, 1], shareRate(1)), 'BatchesAreNotSorted()') + }) + + it('reverts in batches are empty', async () => { + await assert.reverts(withdrawalQueue.prefinalize([], shareRate(1.5)), 'EmptyBatches()') + await assert.reverts( + withdrawalQueue.finalize([], shareRate(300), { from: steth.address, value: amount }), + 'EmptyBatches()' + ) + }) + it('reverts if request with given id did not even created', async () => { const idAhead = +(await withdrawalQueue.getLastRequestId()) + 1 @@ -648,11 +670,60 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) }) - context('claim scenarios', async () => { + context.skip('claim fuzzing', () => { + const fuzzClaim = async (perRequestWEI, requestCount, finalizedWEI) => { + await withdrawalQueue.requestWithdrawals(Array(requestCount).fill(perRequestWEI), user, { from: user }) + const requestIds = await withdrawalQueue.getWithdrawalRequests(user, { from: user }) + + const id = await withdrawalQueue.getLastRequestId() + await withdrawalQueue.finalize([id], shareRate(1), { from: steth.address, value: finalizedWEI }) + + const hints = await withdrawalQueue.findCheckpointHints( + requestIds, + 1, + await withdrawalQueue.getLastCheckpointIndex() + ) + + // this causes division by zero + const claimableEth = await withdrawalQueue.getClaimableEther(requestIds, hints).catch((e) => { + throw new Error( + // hack to fix error objects with bigInit causing `can't serialise bigInt` with wrong trace + JSON.parse(JSON.stringify(e, (_, value) => (typeof value === 'bigint' ? value.toString() : value))) + ) + }) + + const totalClaimable = claimableEth.reduce((s, i) => s.iadd(i) && s, bn(0)) + assert.equals(totalClaimable, finalizedWEI, `Total Claimable doesn't add up to finalized amount`) + + const balanceBefore = bn(await ethers.provider.getBalance(user)) + await withdrawalQueue.claimWithdrawals(requestIds, hints, { from: user }) + const balanceAfter = bn(await ethers.provider.getBalance(user)) + assert.equals(balanceBefore.addn(finalizedWEI), balanceAfter, `Total Claimed doesn't add up to finalized amount`) + } + + it('distribute&claim 10wei per 100*100WEI requests ', async () => { + await fuzzClaim(100, 100, 1000) + }) + + it('distribute&claim 1wei per 100*100WEI requests', async () => { + await fuzzClaim(100, 100, 100) + }) + + it('distribute&claim 1 wei per 10*MAX_STETH_WITHDRAWAL_AMOUNT requests', async () => { + const MAX_STETH_WITHDRAWAL_AMOUNT = await withdrawalQueue.MAX_STETH_WITHDRAWAL_AMOUNT() + // account for stEth~ { const requestCount = 5 const requestsAmounts = Array(requestCount).fill(StETH(1)) - const total = StETH(requestCount) - const normalizedShareRate = shareRate(+total / +(await steth.getSharesByPooledEth(total))) + let requestIds beforeEach(async () => { @@ -662,49 +733,47 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('direct', async () => { + const normalizedShareRate = await currentRate() const balanceBefore = bn(await ethers.provider.getBalance(user)) const id = await withdrawalQueue.getLastRequestId() const batch = await withdrawalQueue.prefinalize([id], normalizedShareRate) - assert.equals(total, batch.ethToLock) + withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) for (let index = 0; index < requestIds.length; index++) { const requestId = requestIds[index] - const tx = await withdrawalQueue.claimWithdrawal(requestId, { from: user }) - assert.emits(tx, 'WithdrawalClaimed', { requestId, owner: user, receiver: user, amountOfETH: ETH(1) }) + await withdrawalQueue.claimWithdrawal(requestId, { from: user, gasPrice: 0 }) } const balanceAfter = bn(await ethers.provider.getBalance(user)) - assert.equals(balanceAfter, balanceBefore.add(bn(total))) + assert.equals(balanceAfter, balanceBefore.add(bn(batch.ethToLock))) }) it('reverse', async () => { + const normalizedShareRate = await currentRate() const balanceBefore = bn(await ethers.provider.getBalance(user)) const id = await withdrawalQueue.getLastRequestId() const batch = await withdrawalQueue.prefinalize([id], normalizedShareRate) - assert.equals(total, batch.ethToLock) withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) for (let index = requestIds.length - 1; index >= 0; index--) { const requestId = requestIds[index] - const tx = await withdrawalQueue.claimWithdrawal(requestId, { from: user }) - assert.emits(tx, 'WithdrawalClaimed', { requestId, owner: user, receiver: user, amountOfETH: ETH(1) }) + await withdrawalQueue.claimWithdrawal(requestId, { from: user, gasPrice: 0 }) } const balanceAfter = bn(await ethers.provider.getBalance(user)) - assert.equals(balanceAfter, balanceBefore.add(bn(total))) + assert.equals(balanceAfter, balanceBefore.add(bn(batch.ethToLock))) }) it('random', async () => { + const normalizedShareRate = await currentRate() const randomIds = [...requestIds].sort(() => 0.5 - Math.random()) const balanceBefore = bn(await ethers.provider.getBalance(user)) const id = await withdrawalQueue.getLastRequestId() const batch = await withdrawalQueue.prefinalize([id], normalizedShareRate) - assert.equals(total, batch.ethToLock) withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) for (let index = 0; index < randomIds.length; index++) { const requestId = randomIds[index] - const tx = await withdrawalQueue.claimWithdrawal(requestId, { from: user }) - assert.emits(tx, 'WithdrawalClaimed', { requestId, owner: user, receiver: user, amountOfETH: ETH(1) }) + await withdrawalQueue.claimWithdrawal(requestId, { from: user, gasPrice: 0 }) } const balanceAfter = bn(await ethers.provider.getBalance(user)) - assert.equals(balanceAfter, balanceBefore.add(bn(total))) + assert.equals(balanceAfter, balanceBefore.add(bn(batch.ethToLock))) }) it('different rates', async () => { @@ -719,64 +788,25 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) totalDistributedEth.iadd(bn(batch.ethToLock)) } - const id = await withdrawalQueue.getLastRequestId() - await withdrawalQueue.finalize([id], await currentRate(), { from: steth.address, value: total }) for (let index = 0; index < requestIds.length; index++) { const requestId = requestIds[index] - await withdrawalQueue.claimWithdrawal(requestId, { from: user }) + await withdrawalQueue.claimWithdrawal(requestId, { from: user, gasPrice: 0 }) } const balanceAfter = bn(await ethers.provider.getBalance(user)) assert.equals(balanceAfter, balanceBefore.add(totalDistributedEth)) }) - }) - - context.skip('claim fuzzing', () => { - const fuzzClaim = async (perRequestWEI, requestCount, finalizedWEI) => { - await withdrawalQueue.requestWithdrawals(Array(requestCount).fill(perRequestWEI), user, { from: user }) - const requestIds = await withdrawalQueue.getWithdrawalRequests(user, { from: user }) - - const id = await withdrawalQueue.getLastRequestId() - await withdrawalQueue.finalize([id], shareRate(1), { from: steth.address, value: finalizedWEI }) - - const hints = await withdrawalQueue.findCheckpointHints( - requestIds, - 1, - await withdrawalQueue.getLastCheckpointIndex() - ) - - // this causes division by zero - const claimableEth = await withdrawalQueue.getClaimableEther(requestIds, hints).catch((e) => { - throw new Error( - // hack to fix error objects with bigInit causing `can't serialise bigInt` with wrong trace - JSON.parse(JSON.stringify(e, (_, value) => (typeof value === 'bigint' ? value.toString() : value))) - ) - }) - - const totalClaimable = claimableEth.reduce((s, i) => s.iadd(i) && s, bn(0)) - assert.equals(totalClaimable, finalizedWEI, `Total Claimable doesn't add up to finalized amount`) + it('100% discount', async () => { const balanceBefore = bn(await ethers.provider.getBalance(user)) - await withdrawalQueue.claimWithdrawals(requestIds, hints, { from: user }) + const id = await withdrawalQueue.getLastRequestId() + withdrawalQueue.finalize([id], 0, { from: steth.address, value: 0 }) + for (let index = 0; index < requestIds.length; index++) { + const requestId = requestIds[index] + const tx = await withdrawalQueue.claimWithdrawal(requestId, { from: user, gasPrice: 0 }) + assert.emits(tx, 'WithdrawalClaimed', { requestId, owner: user, receiver: user, amountOfETH: 0 }) + } const balanceAfter = bn(await ethers.provider.getBalance(user)) - assert.equals(balanceBefore.addn(finalizedWEI), balanceAfter, `Total Claimed doesn't add up to finalized amount`) - } - - it('distribute&claim 10wei per 100*100WEI requests ', async () => { - await fuzzClaim(100, 100, 1000) - }) - - it('distribute&claim 1wei per 100*100WEI requests', async () => { - await fuzzClaim(100, 100, 100) - }) - - it('distribute&claim 1 wei per 10*MAX_STETH_WITHDRAWAL_AMOUNT requests', async () => { - const MAX_STETH_WITHDRAWAL_AMOUNT = await withdrawalQueue.MAX_STETH_WITHDRAWAL_AMOUNT() - // account for stEth~ Date: Tue, 14 Mar 2023 16:25:22 +0700 Subject: [PATCH 02/66] feat: missing wq tests and clean up --- .../0.8.9/test_helpers/ERC721ReceiverMock.sol | 6 + test/0.8.9/withdrawal-queue-nft.test.js | 2 +- test/0.8.9/withdrawal-queue.test.js | 206 ++++++++---------- 3 files changed, 103 insertions(+), 111 deletions(-) diff --git a/contracts/0.8.9/test_helpers/ERC721ReceiverMock.sol b/contracts/0.8.9/test_helpers/ERC721ReceiverMock.sol index aae6ff1cc..c6f3331b8 100644 --- a/contracts/0.8.9/test_helpers/ERC721ReceiverMock.sol +++ b/contracts/0.8.9/test_helpers/ERC721ReceiverMock.sol @@ -24,4 +24,10 @@ contract ERC721ReceiverMock is IERC721Receiver { } return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); } + + receive() external payable { + if (!doesAcceptTokens) { + revert(ERROR_MSG); + } + } } diff --git a/test/0.8.9/withdrawal-queue-nft.test.js b/test/0.8.9/withdrawal-queue-nft.test.js index 552a23c21..c9a2f627a 100644 --- a/test/0.8.9/withdrawal-queue-nft.test.js +++ b/test/0.8.9/withdrawal-queue-nft.test.js @@ -35,7 +35,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, tokenUriManager, erc721ReceiverMock = await ERC721ReceiverMock.new({ from: owner }) await steth.setTotalPooledEther(ETH(600)) - // we need 1 ETH additionally to pay gas on finalization because coverage ingnores gasPrice=0 + // we need 1 ETH additionally to pay gas on finalization because coverage ignores gasPrice=0 await setBalance(steth.address, ETH(600 + 1)) await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) diff --git a/test/0.8.9/withdrawal-queue.test.js b/test/0.8.9/withdrawal-queue.test.js index 06b825a46..9a8ab0867 100644 --- a/test/0.8.9/withdrawal-queue.test.js +++ b/test/0.8.9/withdrawal-queue.test.js @@ -1,4 +1,4 @@ -const { contract, ethers, web3 } = require('hardhat') +const { contract, ethers, web3, artifacts } = require('hardhat') const { bn, getEventArgument, ZERO_ADDRESS } = require('@aragon/contract-helpers-test') const { ETH, StETH, shareRate, shares } = require('../helpers/utils') @@ -7,10 +7,12 @@ const { signPermit, makeDomainSeparator } = require('../0.6.12/helpers/permit_he const { MAX_UINT256, ACCOUNTS_AND_KEYS } = require('../0.6.12/helpers/constants') const { impersonate, EvmSnapshot, getCurrentBlockTimestamp, setBalance } = require('../helpers/blockchain') +const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock') + const { deployWithdrawalQueue } = require('./withdrawal-queue-deploy.test') contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, oracle]) => { - let withdrawalQueue, steth, wsteth + let withdrawalQueue, steth, wsteth, normalizedShareRate const snapshot = new EvmSnapshot(ethers.provider) @@ -37,6 +39,8 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) + normalizedShareRate = (await currentRate()).toString(10) + await impersonate(ethers.provider, steth.address) await snapshot.make() }) @@ -55,7 +59,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, assert.equals(await withdrawalQueue.getLockedEtherAmount(), ETH(0)) }) - context('Pause/Resume', async () => { + context('Pause/Resume', () => { it('only correct roles can alter pause state', async () => { const [PAUSE_ROLE, RESUME_ROLE] = await Promise.all([withdrawalQueue.PAUSE_ROLE(), withdrawalQueue.RESUME_ROLE()]) await withdrawalQueue.grantRole(PAUSE_ROLE, pauser, { from: daoAgent }) @@ -145,7 +149,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) }) - context('BunkerMode', async () => { + context('BunkerMode', () => { it('init config', async () => { assert(!(await withdrawalQueue.isBunkerModeActive())) assert.equals(ethers.constants.MaxUint256, await withdrawalQueue.bunkerModeSinceTimestamp()) @@ -190,7 +194,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) }) - context('Request', async () => { + context('Request', () => { it('One can request a withdrawal', async () => { const receipt = await withdrawalQueue.requestWithdrawals([StETH(300)], owner, { from: user }) const requestId = getEventArgument(receipt, 'WithdrawalRequested', 'requestId') @@ -357,7 +361,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) }) - context('Finalization', async () => { + context('Finalization', () => { const amount = bn(ETH(300)) beforeEach('Enqueue a request', async () => { @@ -396,8 +400,8 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) - const batch = await withdrawalQueue.prefinalize.call([2], shareRate(300)) - await withdrawalQueue.finalize([2], shareRate(300), { from: steth.address, value: batch.ethToLock }) + const batch = await withdrawalQueue.prefinalize.call([2], normalizedShareRate) + await withdrawalQueue.finalize([2], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) assert.equals(batch.sharesToBurn, shares(2)) assert.equals(await withdrawalQueue.getLastRequestId(), 2) @@ -416,7 +420,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) - await withdrawalQueue.finalize([1], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) assert.equals(await withdrawalQueue.getLastRequestId(), 2) assert.equals(await withdrawalQueue.getLastFinalizedRequestId(), 1) @@ -426,7 +430,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await ethers.provider.getBalance(withdrawalQueue.address) ) - await withdrawalQueue.finalize([2], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([2], normalizedShareRate, { from: steth.address, value: amount }) assert.equals(await withdrawalQueue.getLastRequestId(), 2) assert.equals(await withdrawalQueue.getLastFinalizedRequestId(), 2) @@ -453,7 +457,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, it('reverts in batches are empty', async () => { await assert.reverts(withdrawalQueue.prefinalize([], shareRate(1.5)), 'EmptyBatches()') await assert.reverts( - withdrawalQueue.finalize([], shareRate(300), { from: steth.address, value: amount }), + withdrawalQueue.finalize([], normalizedShareRate, { from: steth.address, value: amount }), 'EmptyBatches()' ) }) @@ -462,23 +466,23 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const idAhead = +(await withdrawalQueue.getLastRequestId()) + 1 await assert.reverts( - withdrawalQueue.finalize([idAhead], shareRate(300), { from: steth.address, value: amount }), + withdrawalQueue.finalize([idAhead], normalizedShareRate, { from: steth.address, value: amount }), `InvalidRequestId(${idAhead})` ) - await assert.reverts(withdrawalQueue.prefinalize([idAhead], shareRate(300)), `InvalidRequestId(${idAhead})`) + await assert.reverts(withdrawalQueue.prefinalize([idAhead], normalizedShareRate), `InvalidRequestId(${idAhead})`) }) it('reverts if request with given id was finalized already', async () => { const id = +(await withdrawalQueue.getLastRequestId()) - await withdrawalQueue.finalize([id], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: amount }) await assert.reverts( - withdrawalQueue.finalize([id], shareRate(300), { from: steth.address, value: amount }), + withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: amount }), `InvalidRequestId(${id})` ) - await assert.reverts(withdrawalQueue.prefinalize([id], shareRate(300)), `InvalidRequestId(${id})`) + await assert.reverts(withdrawalQueue.prefinalize([id], normalizedShareRate), `InvalidRequestId(${id})`) }) it('reverts if given amount to finalize exceeds requested', async () => { @@ -486,7 +490,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const amountExceeded = bn(ETH(400)) await assert.reverts( - withdrawalQueue.finalize([id], shareRate(300), { from: steth.address, value: amountExceeded }), + withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: amountExceeded }), `TooMuchEtherToFinalize(${+amountExceeded}, ${+amount})` ) }) @@ -498,8 +502,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('works', async () => { - await withdrawalQueue.finalize([1], shareRate(300), { from: steth.address, value: ETH(1) }) - + await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: ETH(1) }) assert.almostEqual(await withdrawalQueue.getClaimableEther([1], [1]), ETH(1), 100) }) @@ -542,16 +545,37 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await assert.reverts(withdrawalQueue.getClaimableEther([3], [1]), 'InvalidHint(1)') }) + + it('works on multiple checkpoints, no discount', async () => { + const requestCount = 5 + const shareRate = await currentRate() + await withdrawalQueue.finalize([1], shareRate, { from: steth.address, value: ETH(1) }) + for (let index = 0; index < requestCount; index++) { + await withdrawalQueue.requestWithdrawals([ETH(1)], owner, { from: user }) + await withdrawalQueue.finalize([index + 2], shareRate, { from: steth.address, value: ETH(1) }) + } + const requestIds = Array(requestCount + 1) + .fill(0) + .map((_, i) => i + 1) + + const hints = await withdrawalQueue.findCheckpointHints( + requestIds, + 1, + await withdrawalQueue.getLastCheckpointIndex() + ) + const claimableEth = await withdrawalQueue.getClaimableEther(requestIds, hints) + claimableEth.forEach((eth) => assert.almostEqual(eth, ETH(1), 100)) + }) }) - context('claimWithdrawal()', async () => { + context('claimWithdrawal()', () => { const amount = ETH(300) beforeEach('Enqueue a request', async () => { await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) }) it('Owner can claim a finalized request to recipient address', async () => { - await withdrawalQueue.finalize([1], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) const balanceBefore = bn(await ethers.provider.getBalance(user)) @@ -573,7 +597,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('reverts if sender is not owner', async () => { - await withdrawalQueue.finalize([1], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) await assert.reverts( withdrawalQueue.claimWithdrawalsTo([1], [1], owner, { from: stranger }), `NotOwner("${stranger}", "${owner}")` @@ -581,14 +605,24 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('reverts if there is not enough balance', async () => { - await withdrawalQueue.finalize([1], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) await setBalance(withdrawalQueue.address, ETH(200)) await assert.reverts(withdrawalQueue.claimWithdrawalsTo([1], [1], owner, { from: owner }), 'NotEnoughEther()') }) + + it('reverts if receiver declines', async () => { + const receiver = await ERC721ReceiverMock.new({ from: owner }) + await receiver.setDoesAcceptTokens(false, { from: owner }) + await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) + await assert.reverts( + withdrawalQueue.claimWithdrawalsTo([1], [1], receiver.address, { from: owner }), + 'CantSendValueRecipientMayHaveReverted()' + ) + }) }) it('Owner can claim a finalized request without hint', async () => { - await withdrawalQueue.finalize([1], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) const balanceBefore = bn(await ethers.provider.getBalance(owner)) @@ -616,25 +650,27 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) - await withdrawalQueue.finalize([2], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([2], normalizedShareRate, { from: steth.address, value: amount }) await assert.reverts(withdrawalQueue.claimWithdrawals([1], [0], { from: owner }), 'InvalidHint(0)') await assert.reverts(withdrawalQueue.claimWithdrawals([1], [2], { from: owner }), 'InvalidHint(2)') }) it('Cant withdraw token two times', async () => { - await withdrawalQueue.finalize([1], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) await withdrawalQueue.claimWithdrawal(1, { from: owner }) await assert.reverts(withdrawalQueue.claimWithdrawal(1, { from: owner }), 'RequestAlreadyClaimed(1)') }) it('Discounted withdrawals produce less eth', async () => { - await withdrawalQueue.finalize([1], shareRate(150), { from: steth.address, value: ETH(150) }) + const batch = await withdrawalQueue.prefinalize([1], shareRate(150)) + await withdrawalQueue.finalize([1], shareRate(150), { from: steth.address, value: batch.ethToLock }) const balanceBefore = bn(await ethers.provider.getBalance(owner)) - assert.equals(await withdrawalQueue.getLockedEtherAmount(), ETH(150)) + assert.equals(await withdrawalQueue.getLockedEtherAmount(), batch.ethToLock) + + const tx = await withdrawalQueue.claimWithdrawal(1, { from: owner }).catch() - const tx = await withdrawalQueue.claimWithdrawal(1, { from: owner }) assert.equals(await withdrawalQueue.getLockedEtherAmount(), ETH(0)) // tx.receipt.gasUsed is a workaround for coverage, because it ignores gasPrice=0 @@ -670,53 +706,26 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) }) - context.skip('claim fuzzing', () => { - const fuzzClaim = async (perRequestWEI, requestCount, finalizedWEI) => { - await withdrawalQueue.requestWithdrawals(Array(requestCount).fill(perRequestWEI), user, { from: user }) - const requestIds = await withdrawalQueue.getWithdrawalRequests(user, { from: user }) - - const id = await withdrawalQueue.getLastRequestId() - await withdrawalQueue.finalize([id], shareRate(1), { from: steth.address, value: finalizedWEI }) - - const hints = await withdrawalQueue.findCheckpointHints( - requestIds, - 1, - await withdrawalQueue.getLastCheckpointIndex() - ) - - // this causes division by zero - const claimableEth = await withdrawalQueue.getClaimableEther(requestIds, hints).catch((e) => { - throw new Error( - // hack to fix error objects with bigInit causing `can't serialise bigInt` with wrong trace - JSON.parse(JSON.stringify(e, (_, value) => (typeof value === 'bigint' ? value.toString() : value))) - ) - }) - - const totalClaimable = claimableEth.reduce((s, i) => s.iadd(i) && s, bn(0)) - assert.equals(totalClaimable, finalizedWEI, `Total Claimable doesn't add up to finalized amount`) - - const balanceBefore = bn(await ethers.provider.getBalance(user)) - await withdrawalQueue.claimWithdrawals(requestIds, hints, { from: user }) - const balanceAfter = bn(await ethers.provider.getBalance(user)) - assert.equals(balanceBefore.addn(finalizedWEI), balanceAfter, `Total Claimed doesn't add up to finalized amount`) - } + context('claimWithdrawals()', () => { + const amount = ETH(20) - it('distribute&claim 10wei per 100*100WEI requests ', async () => { - await fuzzClaim(100, 100, 1000) + beforeEach('Enqueue a request', async () => { + await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) }) - it('distribute&claim 1wei per 100*100WEI requests', async () => { - await fuzzClaim(100, 100, 100) - }) + it('claims correct requests', async () => { + await steth.mintShares(owner, shares(300)) // 1 share to user and 299 shares to owner total = 300 ETH + await steth.approve(withdrawalQueue.address, StETH(300), { from: owner }) + + const secondRequestAmount = ETH(10) + await withdrawalQueue.requestWithdrawals([secondRequestAmount], owner, { from: owner }) + const secondRequestId = await withdrawalQueue.getLastRequestId() + await withdrawalQueue.finalize([secondRequestId], normalizedShareRate, { from: steth.address, value: ETH(30) }) - it('distribute&claim 1 wei per 10*MAX_STETH_WITHDRAWAL_AMOUNT requests', async () => { - const MAX_STETH_WITHDRAWAL_AMOUNT = await withdrawalQueue.MAX_STETH_WITHDRAWAL_AMOUNT() - // account for stEth~ { - const numOfRequests = 10 - const requests = Array(numOfRequests).fill(ETH(20)) - const discountedPrices = Array(numOfRequests) - .fill() - .map((_, i) => ETH(i)) - const sharesPerRequest = await steth.getSharesByPooledEth(ETH(20)) - const discountShareRates = discountedPrices.map((p) => shareRate(+p / +sharesPerRequest)) - + context('findCheckpointHints()', () => { beforeEach(async () => { + const numOfRequests = 10 + const requests = Array(numOfRequests).fill(ETH(20)) + const discountedPrices = Array(numOfRequests) + .fill() + .map((_, i) => ETH(i)) + const sharesPerRequest = await steth.getSharesByPooledEth(ETH(20)) + const discountShareRates = discountedPrices.map((p) => shareRate(+p / +sharesPerRequest)) + await withdrawalQueue.requestWithdrawals(requests, owner, { from: user }) for (let i = 1; i <= numOfRequests; i++) { await withdrawalQueue.finalize([i], discountShareRates[i - 1], { @@ -915,8 +924,8 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('returns not found when indexes have negative overlap', async () => { - const batch = await withdrawalQueue.prefinalize.call([requestId], shareRate(300)) - await withdrawalQueue.finalize([requestId], shareRate(300), { from: steth.address, value: batch.ethToLock }) + const batch = await withdrawalQueue.prefinalize.call([requestId], normalizedShareRate) + await withdrawalQueue.finalize([requestId], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) const lastCheckpointIndex = await withdrawalQueue.getLastCheckpointIndex() const hints = await withdrawalQueue.findCheckpointHints( [requestId], @@ -928,8 +937,8 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('returns hints array with one item for list from single request id', async () => { - const batch = await withdrawalQueue.prefinalize.call([requestId], shareRate(300)) - await withdrawalQueue.finalize([requestId], shareRate(300), { from: steth.address, value: batch.ethToLock }) + const batch = await withdrawalQueue.prefinalize.call([requestId], normalizedShareRate) + await withdrawalQueue.finalize([requestId], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) const lastCheckpointIndex = await withdrawalQueue.getLastCheckpointIndex() const hints = await withdrawalQueue.findCheckpointHints([requestId], 1, lastCheckpointIndex) assert.equal(hints.length, 1) @@ -988,29 +997,6 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) }) - context('claimWithdrawals()', () => { - const amount = ETH(20) - - beforeEach('Enqueue a request', async () => { - await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) - }) - - it('claims correct requests', async () => { - await steth.mintShares(owner, shares(300)) // 1 share to user and 299 shares to owner total = 300 ETH - await steth.approve(withdrawalQueue.address, StETH(300), { from: owner }) - - const secondRequestAmount = ETH(10) - await withdrawalQueue.requestWithdrawals([secondRequestAmount], owner, { from: owner }) - const secondRequestId = await withdrawalQueue.getLastRequestId() - await withdrawalQueue.finalize([secondRequestId], shareRate(300), { from: steth.address, value: ETH(30) }) - - const balanceBefore = bn(await ethers.provider.getBalance(owner)) - const tx = await withdrawalQueue.claimWithdrawals([1, 2], [1, 1], { from: owner, gasPrice: 0 }) - // tx.receipt.gasUsed is a workaround for coverage, because it ignores gasPrice=0 - assert.almostEqual(await ethers.provider.getBalance(owner), balanceBefore.add(bn(ETH(30))), tx.receipt.gasUsed) - }) - }) - context('requestWithdrawals()', () => { it('works correctly with non empty payload and different tokens', async () => { await steth.mintShares(user, shares(10)) @@ -1148,7 +1134,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) }) - context('Transfer request', async () => { + context('Transfer request', () => { const amount = ETH(300) let requestId @@ -1192,7 +1178,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it("One can't change claimed request", async () => { - await withdrawalQueue.finalize([requestId], shareRate(300), { from: steth.address, value: amount }) + await withdrawalQueue.finalize([requestId], normalizedShareRate, { from: steth.address, value: amount }) await withdrawalQueue.claimWithdrawal(requestId, { from: user }) await assert.reverts( From 316f30b14599c7ff444e67a163a2d947984f6d33 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Mon, 13 Mar 2023 21:05:51 +0700 Subject: [PATCH 03/66] =?UTF-8?q?test:=20NodeOperatorsRegistry=20happy=20p?= =?UTF-8?q?ath=20=E2=80=94=20first=20base=20iteration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...node-operators-registry-happy-path.test.js | 248 ++++++++++++++++++ 1 file changed, 248 insertions(+) create mode 100644 test/0.4.24/node-operators-registry-happy-path.test.js diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js new file mode 100644 index 000000000..0f65b9322 --- /dev/null +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -0,0 +1,248 @@ +const { contract, web3 } = require('hardhat') +const { assert } = require('../helpers/assert') + +const signingKeys = require('../helpers/signing-keys') +const { ETH } = require('../helpers/utils') +const { DSMAttestMessage } = require('../helpers/signatures') +const { deployProtocol } = require('../helpers/protocol') +const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') + +const ADDRESS_1 = '0x0000000000000000000000000000000000000001' +const ADDRESS_2 = '0x0000000000000000000000000000000000000002' +const ADDRESS_3 = '0x0000000000000000000000000000000000000003' + +const NODE_OPERATORS = [ + { + id: 0, + name: 'Node operator #1', + rewardAddressInitial: ADDRESS_1, + totalSigningKeysCount: 10, + depositedSigningKeysCount: 5, + exitedSigningKeysCount: 1, + vettedSigningKeysCount: 6, + stuckValidatorsCount: 0, + refundedValidatorsCount: 0, + stuckPenaltyEndAt: 0, + }, + { + id: 1, + isActive: false, + name: 'Node operator #2', + rewardAddressInitial: ADDRESS_2, + totalSigningKeysCount: 15, + depositedSigningKeysCount: 7, + exitedSigningKeysCount: 0, + vettedSigningKeysCount: 10, + stuckValidatorsCount: 0, + refundedValidatorsCount: 0, + stuckPenaltyEndAt: 0, + }, + { + id: 2, + isActive: false, + name: 'Node operator #3', + rewardAddressInitial: ADDRESS_3, + totalSigningKeysCount: 10, + depositedSigningKeysCount: 0, + exitedSigningKeysCount: 0, + vettedSigningKeysCount: 5, + stuckValidatorsCount: 0, + refundedValidatorsCount: 0, + stuckPenaltyEndAt: 0, + }, +] + +const Operator1 = NODE_OPERATORS[0] +// const Operator2 = NODE_OPERATORS[1] +const Operator3 = NODE_OPERATORS[2] + +const forEachSync = async (arr, cb) => { + for (let i = 0; i < arr.length; ++i) { + await cb(arr[i], i) + } +} + +contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, user1, nobody]) => { + // let app + // let locator + // let steth + // let dao + let dsm + let lido + let nor + let stakingRouter + let depositContract + let depositRoot + let voting + let rewardAddresses + let guardians + let withdrawalCredentials + + before('deploy base app', async () => { + const deployed = await deployProtocol({ + stakingModulesFactory: async (protocol) => { + const curatedModule = await setupNodeOperatorsRegistry(protocol) + return [ + { + module: curatedModule, + name: 'Curated', + targetShares: 10000, + moduleFee: 500, + treasuryFee: 500, + }, + ] + }, + }) + + rewardAddresses = [rewards1, rewards2, rewards3] + + // app = deployed.dao + lido = deployed.pool + nor = deployed.stakingModules[0] + // steth = deployed.token + // locator = deployed.lidoLocator + stakingRouter = deployed.stakingRouter + depositContract = deployed.depositContract + depositRoot = await depositContract.get_deposit_root() + dsm = deployed.depositSecurityModule + guardians = deployed.guardians + voting = deployed.voting.address + // treasuryAddr = deployed.treasury.address + + withdrawalCredentials = '0x'.padEnd(66, '1234') + await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) + }) + + describe('Happy path', () => { + it('Add node operator', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + const initialName = `operator ${i + 1}` + const tx = await nor.addNodeOperator(initialName, operatorData.rewardAddressInitial, { from: voting }) + const expectedStakingLimit = 0 + + assert.emits(tx, 'NodeOperatorAdded', { + nodeOperatorId: operatorData.id, + name: initialName, + rewardAddress: operatorData.rewardAddressInitial, + stakingLimit: expectedStakingLimit, + }) + + assert.isTrue(await nor.getNodeOperatorIsActive(operatorData.id)) + const operator = await nor.getNodeOperator(operatorData.id, true) + assert.isTrue(operator.active) + assert.equals(operator.name, initialName) + assert.equals(operator.rewardAddress, operatorData.rewardAddressInitial) + assert.equals(operator.stakingLimit, 0) + assert.equals(operator.stoppedValidators, 0) + assert.equals(operator.totalSigningKeys, 0) + assert.equals(operator.usedSigningKeys, 0) + }) + + assert.equals(await nor.getNodeOperatorsCount(), NODE_OPERATORS.length) + }) + + // TODO: Move this block after keys manipulations to check how it will affect them + it('Deactivate node operator 3', async () => { + const activeOperatorsBefore = await nor.getActiveNodeOperatorsCount() + const tx = await nor.deactivateNodeOperator(Operator3.id, { from: voting }) + const operator = await nor.getNodeOperator(Operator3.id, true) + const activeOperatorsAfter = await nor.getActiveNodeOperatorsCount() + + assert.isFalse(await nor.getNodeOperatorIsActive(Operator3.id)) + assert.isFalse(operator.active) + assert.equals(Number(activeOperatorsBefore) - 1, Number(activeOperatorsAfter)) + assert.emits(tx, 'NodeOperatorActiveSet', { nodeOperatorId: Operator3.id, active: false }) + }) + + it('Set name', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + await nor.setNodeOperatorName(operatorData.id, operatorData.name, { from: voting }) + const operator = await nor.getNodeOperator(operatorData.id, true) + assert.equals(operator.name, operatorData.name) + }) + }) + + it('Set reward address', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + const rewardAddress = rewardAddresses[i] + await nor.setNodeOperatorRewardAddress(operatorData.id, rewardAddress, { from: voting }) + const operator = await nor.getNodeOperator(operatorData.id, true) + assert.equals(operator.rewardAddress, rewardAddress) + }) + }) + + it('Add signing keys', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + const keys = new signingKeys.FakeValidatorKeys(operatorData.totalSigningKeysCount) + await nor.addSigningKeys(operatorData.id, keys.count, ...keys.slice(), { from: voting }) + + const operator = await nor.getNodeOperator(operatorData.id, true) + const keysCount = await nor.getTotalSigningKeyCount(operatorData.id) + const unusedKeysCount = await nor.getUnusedSigningKeyCount(operatorData.id) + assert.equals(keys.count, operator.totalSigningKeys.toNumber()) + assert.equals(keys.count, keysCount) + assert.equals(keys.count, unusedKeysCount) + + for (let i = 0; i < keys.count; ++i) { + const { key, depositSignature } = await nor.getSigningKey(operatorData.id, i) + const [expectedPublicKey, expectedSignature] = keys.get(i) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) + } + }) + }) + + let stateTotalVetted = 0 + let stateTotaldeposited = 0 + + it('Set staking limit', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + if (!(await nor.getNodeOperatorIsActive(operatorData.id))) return + stateTotalVetted += operatorData.vettedSigningKeysCount + await nor.setNodeOperatorStakingLimit(operatorData.id, operatorData.vettedSigningKeysCount, { from: voting }) + const operator = await nor.getNodeOperator(operatorData.id, true) + assert.equals(operator.stakingLimit, operatorData.vettedSigningKeysCount) + }) + + const stakingModuleSummary = await nor.getStakingModuleSummary() + assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted) + }) + + it('Obtain deposit data', async () => { + const [curated] = await stakingRouter.getStakingModules() + + await web3.eth.sendTransaction({ to: lido.address, from: user1, value: ETH(32) }) + + const block = await web3.eth.getBlock('latest') + const keysOpIndex = await nor.getKeysOpIndex() + + DSMAttestMessage.setMessagePrefix(await dsm.ATTEST_MESSAGE_PREFIX()) + + const attest = new DSMAttestMessage(block.number, block.hash, depositRoot, curated.id, keysOpIndex) + const signatures = [ + attest.sign(guardians.privateKeys[guardians.addresses[0]]), + attest.sign(guardians.privateKeys[guardians.addresses[1]]), + ] + + // triggers flow: + // DSM.depositBufferedEther() -> Lido.deposit() -> StakingRouter.deposit() -> Module.obtainDepositData() + await dsm.depositBufferedEther(block.number, block.hash, depositRoot, curated.id, keysOpIndex, '0x', signatures) + + stateTotaldeposited += 1 + + const depositCallCount = await depositContract.totalCalls() + assert.equals(depositCallCount, 1) + + const regCall = await depositContract.calls.call(0) + const { key, depositSignature } = await nor.getSigningKey(Operator1.id, 0) + assert.equal(regCall.pubkey, key) + assert.equal(regCall.signature, depositSignature) + assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) + assert.equals(regCall.value, ETH(32)) + + const stakingModuleSummary = await nor.getStakingModuleSummary() + assert.equals(stakingModuleSummary.totalDepositedValidators, stateTotaldeposited) + assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted - stateTotaldeposited) + }) + }) +}) From b205b93927b208641b708040f05d344f7b249102 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Mar 2023 15:59:31 +0700 Subject: [PATCH 04/66] test: NodeOperatorsRegistry happy path more checks --- ...node-operators-registry-happy-path.test.js | 66 +++++++++++++++---- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index 0f65b9322..99dfdfc82 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -17,7 +17,6 @@ const NODE_OPERATORS = [ name: 'Node operator #1', rewardAddressInitial: ADDRESS_1, totalSigningKeysCount: 10, - depositedSigningKeysCount: 5, exitedSigningKeysCount: 1, vettedSigningKeysCount: 6, stuckValidatorsCount: 0, @@ -30,7 +29,6 @@ const NODE_OPERATORS = [ name: 'Node operator #2', rewardAddressInitial: ADDRESS_2, totalSigningKeysCount: 15, - depositedSigningKeysCount: 7, exitedSigningKeysCount: 0, vettedSigningKeysCount: 10, stuckValidatorsCount: 0, @@ -43,7 +41,6 @@ const NODE_OPERATORS = [ name: 'Node operator #3', rewardAddressInitial: ADDRESS_3, totalSigningKeysCount: 10, - depositedSigningKeysCount: 0, exitedSigningKeysCount: 0, vettedSigningKeysCount: 5, stuckValidatorsCount: 0, @@ -53,7 +50,7 @@ const NODE_OPERATORS = [ ] const Operator1 = NODE_OPERATORS[0] -// const Operator2 = NODE_OPERATORS[1] +const Operator2 = NODE_OPERATORS[1] const Operator3 = NODE_OPERATORS[2] const forEachSync = async (arr, cb) => { @@ -211,7 +208,8 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us it('Obtain deposit data', async () => { const [curated] = await stakingRouter.getStakingModules() - await web3.eth.sendTransaction({ to: lido.address, from: user1, value: ETH(32) }) + const stakesDeposited = 3 + await web3.eth.sendTransaction({ to: lido.address, from: user1, value: ETH(32 * stakesDeposited) }) const block = await web3.eth.getBlock('latest') const keysOpIndex = await nor.getKeysOpIndex() @@ -228,21 +226,61 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us // DSM.depositBufferedEther() -> Lido.deposit() -> StakingRouter.deposit() -> Module.obtainDepositData() await dsm.depositBufferedEther(block.number, block.hash, depositRoot, curated.id, keysOpIndex, '0x', signatures) - stateTotaldeposited += 1 + stateTotaldeposited += stakesDeposited const depositCallCount = await depositContract.totalCalls() - assert.equals(depositCallCount, 1) - - const regCall = await depositContract.calls.call(0) - const { key, depositSignature } = await nor.getSigningKey(Operator1.id, 0) - assert.equal(regCall.pubkey, key) - assert.equal(regCall.signature, depositSignature) - assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) - assert.equals(regCall.value, ETH(32)) + assert.equals(depositCallCount, stakesDeposited) + + { + // Deposit call #1 + const regCall = await depositContract.calls.call(0) + const { key, depositSignature } = await nor.getSigningKey(Operator1.id, 0) + assert.equal(regCall.pubkey, key) + assert.equal(regCall.signature, depositSignature) + assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) + assert.equals(regCall.value, ETH(32)) + } + + { + // Deposit call #2 + const regCall = await depositContract.calls.call(1) + const { key, depositSignature } = await nor.getSigningKey(Operator1.id, 1) + assert.equal(regCall.pubkey, key) + assert.equal(regCall.signature, depositSignature) + assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) + assert.equals(regCall.value, ETH(32)) + } + + { + // Deposit call #3 + const regCall = await depositContract.calls.call(2) + const { key, depositSignature } = await nor.getSigningKey(Operator2.id, 0) + assert.equal(regCall.pubkey, key) + assert.equal(regCall.signature, depositSignature) + assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) + assert.equals(regCall.value, ETH(32)) + } const stakingModuleSummary = await nor.getStakingModuleSummary() assert.equals(stakingModuleSummary.totalDepositedValidators, stateTotaldeposited) assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted - stateTotaldeposited) + + const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) + assert.equals(summaryOperator1.totalDepositedValidators, 2) + assert.equals(summaryOperator1.depositableValidatorsCount, Operator1.vettedSigningKeysCount - 2) + + const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) + assert.equals(summaryOperator2.totalDepositedValidators, 1) + assert.equals(summaryOperator2.depositableValidatorsCount, Operator2.vettedSigningKeysCount - 1) + }) + + it('Rewards distribution', async () => { + const totalRewardsShare = web3.utils.toWei('15') + const distribution = await nor.getRewardsDistribution(totalRewardsShare) + assert.equal(distribution.shares[0], web3.utils.toWei('10')) + assert.equal(distribution.shares[1], web3.utils.toWei('5')) + assert.equal(distribution.recipients[0], rewards1) + assert.equal(distribution.recipients[1], rewards2) }) }) }) From 80a83ca0e3675dca1bd1a51b301fcce36267977c Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Mar 2023 19:51:34 +0700 Subject: [PATCH 05/66] test: NodeOperators happy path todos development plan --- ...node-operators-registry-happy-path.test.js | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index 99dfdfc82..1aa300f1d 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -205,6 +205,17 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted) }) + it('TargetLimit', async () => { + // affects on obtain deposit data + // should be called with StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() + // todo: + // nor.getNodeOperatorSummary(Operator1.id) + // — check default is zero + // — set + // — check it was setted + // — check depositable keys changed + }) + it('Obtain deposit data', async () => { const [curated] = await stakingRouter.getStakingModules() @@ -261,6 +272,8 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us assert.equals(regCall.value, ETH(32)) } + // TODO: check that TargetLimit affected deposited value + const stakingModuleSummary = await nor.getStakingModuleSummary() assert.equals(stakingModuleSummary.totalDepositedValidators, stateTotaldeposited) assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted - stateTotaldeposited) @@ -282,5 +295,66 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us assert.equal(distribution.recipients[0], rewards1) assert.equal(distribution.recipients[1], rewards2) }) + + // TODO: Exited validators + // AccountingOracle.submitReport() -> StakingRouter...() — only for memory + // [optional — if can be mocked] assert ... -> Lido...() -> NOR.onRewardsMinted() was called + // + // AccountingOracle.submitReportExtraDataList() + // -> StakingRouter.reportStakingModuleExitedValidatorsCountByNodeOperator() + // -> StakingRouter.reportStakingModuleStuckValidatorsCountByNodeOperator() + // -> NOR.updateExitedValidatorsCount() + // assert exited validators count + // assert rewards was transfered with NOR._distributeRewards() + // assert rewards was transfered to operators for his exited validators + // assert TargetLimit was changed to zero if any key stucked. ... -> NOR._updateSummaryMaxValidatorsCount() + + // TODO: ... -> StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished() -> NOR.onExitedAndStuckValidatorsCountsUpdated() + // assert rewards was transfered to exited validators + + // TODO: StakingRouter.unsafeSetExitedValidatorsCount() -> NOR.onExitedAndStuckValidatorsCountsUpdated() + + // TODO: TargetLimit allows to deposit after exit + + // TODO: disable TargetLimit and try to deposit again + + // TODO: NOR.removeSigningKey() + // NOR.removeSigningKeys() + // assert that staking limit was changed + // check if we need to assert target limit + + // TODO: NOR....() + // Deactivate Operator that was in use before + // Make another deposit to check that deactivated node operator will not get deposit + // Assert rewards not distributed to disabled operator + + // TODO: NOR.activateNodeOperator() + // Activate Operator again and it will be used in deposit flow + + // TODO: StakingRouter.setWithdrawalCredentials() -> NOR.onWithdrawalCredentialsChanged() + // assert depositable of all operators should be zero + // assert totalValidatorsCount of all operators == deposited validators + // assert NOR.getStakingModuleSummary() — depositable = 0, exited = same, deposited = same + + // TODO: manipulate with stuck validators + // this value can come from from two sources: + // AccountingOracle.submitReportExtraDataList() + // NOR.unsafeUpdateValidatorsCount() + // assert that NOR.onExitedAndStuckValidatorsCountsUpdated() should be called + + // TODO: [optional] unsafeUpdateValidatorsCount + + // TODO: [do more research] Refuneded keys + // 1. Operator already have stucked keys + // 2. Set refunded via StakingRouter...() -> NOR.updateRefundedValidatorsCount() with refunded == stuckKeys + // 3. Wait for half of penalty delay and check that penalty still with NOR.getRewardsDistribution() and Oracle report and obtain deposit data + // 4. Wait for end of penalty delay and check that it is gone + // assert NOR.getStuckPenaltyDelay() + // assert NOR.setStuckPenaltyDelay() + // assert penalty affects on TargetLimit + + // TODO: [optional] add NOR.getNonce() somewhere + + // TODO: [optional] assert NOR._getSigningKeysAllocationData() if it is possible }) }) From b42a91305bf4c48ddf2456eca72d96127d689366 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 15 Mar 2023 15:25:15 +0700 Subject: [PATCH 06/66] feat: add legacy oracle rebase test --- .../0.4.24/test_helpers/MockLegacyOracle.sol | 6 +- .../oracle/MockLidoForAccountingOracle.sol | 21 +++++++ test/0.4.24/legacy-oracle.test.js | 62 ++++++++++++++++++- 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/contracts/0.4.24/test_helpers/MockLegacyOracle.sol b/contracts/0.4.24/test_helpers/MockLegacyOracle.sol index f12162aa4..fc8b1f8fa 100644 --- a/contracts/0.4.24/test_helpers/MockLegacyOracle.sol +++ b/contracts/0.4.24/test_helpers/MockLegacyOracle.sol @@ -91,7 +91,11 @@ contract MockLegacyOracle is ILegacyOracle, LegacyOracle { } function initializeAsV3() external { - CONTRACT_VERSION_POSITION_DEPRECATED.setStorageUint256(3); + CONTRACT_VERSION_POSITION_DEPRECATED.setStorageUint256(3); + } + + function setLido(address lido) external { + LIDO_POSITION.setStorageAddress(lido); } } diff --git a/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol b/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol index c8e2117e9..e665ff8cb 100644 --- a/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol +++ b/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol @@ -4,9 +4,22 @@ pragma solidity 0.8.9; import { ILido } from "../../oracle/AccountingOracle.sol"; +interface IPostTokenRebaseReceiver { + function handlePostTokenRebase( + uint256 _reportTimestamp, + uint256 _timeElapsed, + uint256 _preTotalShares, + uint256 _preTotalEther, + uint256 _postTotalShares, + uint256 _postTotalEther, + uint256 _sharesMintedAsFees + ) external; +} contract MockLidoForAccountingOracle is ILido { + address legacyOracle; + struct HandleOracleReportLastCall { uint256 currentReportTimestamp; uint256 secondsElapsedSinceLastReport; @@ -26,6 +39,10 @@ contract MockLidoForAccountingOracle is ILido { return _handleOracleReportLastCall; } + function setLegacyOracle(address addr) external { + legacyOracle = addr; + } + /// /// ILido /// @@ -51,5 +68,9 @@ contract MockLidoForAccountingOracle is ILido { _handleOracleReportLastCall.withdrawalFinalizationBatches = withdrawalFinalizationBatches; _handleOracleReportLastCall.simulatedShareRate = simulatedShareRate; ++_handleOracleReportLastCall.callCount; + + if(legacyOracle != address(0)){ + IPostTokenRebaseReceiver(legacyOracle).handlePostTokenRebase(currentReportTimestamp,secondsElapsedSinceLastReport,0,0,1,1,1); + } } } diff --git a/test/0.4.24/legacy-oracle.test.js b/test/0.4.24/legacy-oracle.test.js index 700ae3c3a..55897682a 100644 --- a/test/0.4.24/legacy-oracle.test.js +++ b/test/0.4.24/legacy-oracle.test.js @@ -23,6 +23,7 @@ const { ZERO_HASH, CONSENSUS_VERSION, computeTimestampAtEpoch, + SECONDS_PER_FRAME, } = require('../0.8.9/oracle/accounting-oracle-deploy.test') const getReportFields = (override = {}) => ({ @@ -204,6 +205,8 @@ contract('LegacyOracle', ([admin, stranger]) => { }) const { consensus, oracle } = deployedInfra await initAccountingOracle({ admin, oracle, consensus, shouldMigrateLegacyOracle: true }) + await deployedInfra.lido.setLegacyOracle(proxy.address) + await proxyAsOldImplementation.setLido(deployedInfra.lido.address) }) it('upgrade implementation', async () => { @@ -212,17 +215,21 @@ contract('LegacyOracle', ([admin, stranger]) => { await proxyAsNewImplementation.finalizeUpgrade_v4(deployedInfra.oracle.address) }) - it('submit report', async () => { - await deployedInfra.consensus.advanceTimeToNextFrameStart() + it('first report since migration', async () => { const { refSlot } = await deployedInfra.consensus.getCurrentFrame() const reportFields = getReportFields({ refSlot: +refSlot, }) + const reportItems = getAccountingReportDataItems(reportFields) const reportHash = calcAccountingReportDataHash(reportItems) await deployedInfra.consensus.addMember(admin, 1, { from: admin }) await deployedInfra.consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: admin }) const oracleVersion = +(await deployedInfra.oracle.getContractVersion()) + + // first report since migration has off timeElapsed because lastProcessingRefSlot is calculated per legacy frame ref + const timeElapsed = (+refSlot - +(await deployedInfra.oracle.getLastProcessingRefSlot())) * SECONDS_PER_SLOT + const tx = await deployedInfra.oracle.submitReportData(reportItems, oracleVersion, { from: admin }) const epochId = Math.floor((+refSlot + 1) / SLOTS_PER_EPOCH) @@ -236,12 +243,29 @@ contract('LegacyOracle', ([admin, stranger]) => { }, { abi: LegacyOracleAbi } ) + assert.emits( + tx, + 'PostTotalShares', + { + postTotalPooledEther: 1, + preTotalPooledEther: 0, + timeElapsed, + totalShares: 1, + }, + { abi: LegacyOracleAbi } + ) + + const lastCompletedReportDelta = await proxyAsNewImplementation.getLastCompletedReportDelta() + assert.equals(lastCompletedReportDelta.postTotalPooledEther, 1) + assert.equals(lastCompletedReportDelta.preTotalPooledEther, 0) + assert.equals(lastCompletedReportDelta.timeElapsed, timeElapsed) const completedEpoch = await proxyAsNewImplementation.getLastCompletedEpochId() assert.equals(completedEpoch, epochId) }) it('time in sync with consensus', async () => { await deployedInfra.consensus.advanceTimeToNextFrameStart() + const { frameEpochId, frameStartTime, frameEndTime } = await proxyAsNewImplementation.getCurrentFrame() const consensusFrame = await deployedInfra.consensus.getCurrentFrame() const refSlot = consensusFrame.refSlot.toNumber() @@ -250,6 +274,38 @@ contract('LegacyOracle', ([admin, stranger]) => { assert.equals(frameEndTime, computeTimestampAtEpoch(+frameEpochId + EPOCHS_PER_FRAME) - 1) }) - it.skip('handlePostTokenRebase from lido') + it('second report', async () => { + const { refSlot } = await deployedInfra.consensus.getCurrentFrame() + const reportFields = getReportFields({ + refSlot: +refSlot, + }) + const reportItems = getAccountingReportDataItems(reportFields) + const reportHash = calcAccountingReportDataHash(reportItems) + await deployedInfra.consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: admin }) + const oracleVersion = +(await deployedInfra.oracle.getContractVersion()) + const tx = await deployedInfra.oracle.submitReportData(reportItems, oracleVersion, { from: admin }) + const epochId = Math.floor((+refSlot + 1) / SLOTS_PER_EPOCH) + assert.emits( + tx, + 'Completed', + { + epochId, + beaconBalance: web3.utils.toWei(reportFields.clBalanceGwei, 'gwei'), + beaconValidators: reportFields.numValidators, + }, + { abi: LegacyOracleAbi } + ) + assert.emits( + tx, + 'PostTotalShares', + { + postTotalPooledEther: 1, + preTotalPooledEther: 0, + timeElapsed: SECONDS_PER_FRAME, + totalShares: 1, + }, + { abi: LegacyOracleAbi } + ) + }) }) }) From 5e1df7cac9c5dcfb467b67e7b5590ddb89940971 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 15 Mar 2023 16:28:54 +0700 Subject: [PATCH 07/66] chore: wq tests styling fixes --- .../0.8.9/test_helpers/ERC721ReceiverMock.sol | 15 +++++++------ ...drawal-queue-requests-finalization.test.js | 18 +++++++--------- test/0.8.9/withdrawal-queue.test.js | 21 +++++++------------ 3 files changed, 24 insertions(+), 30 deletions(-) diff --git a/contracts/0.8.9/test_helpers/ERC721ReceiverMock.sol b/contracts/0.8.9/test_helpers/ERC721ReceiverMock.sol index c6f3331b8..92e5713d9 100644 --- a/contracts/0.8.9/test_helpers/ERC721ReceiverMock.sol +++ b/contracts/0.8.9/test_helpers/ERC721ReceiverMock.sol @@ -3,9 +3,9 @@ pragma solidity 0.8.9; -import {IERC721Receiver} from "@openzeppelin/contracts-v4.4/token/ERC721/IERC721Receiver.sol"; +import { IERC721Receiver } from "@openzeppelin/contracts-v4.4/token/ERC721/IERC721Receiver.sol"; -contract ERC721ReceiverMock is IERC721Receiver { +contract ERC721ReceiverMock is IERC721Receiver { bool public doesAcceptTokens; string public ERROR_MSG = "ERC721_NOT_ACCEPT_TOKENS"; @@ -22,12 +22,15 @@ contract ERC721ReceiverMock is IERC721Receiver { if (!doesAcceptTokens) { revert(ERROR_MSG); } - return bytes4(keccak256("onERC721Received(address,address,uint256,bytes)")); + return + bytes4( + keccak256("onERC721Received(address,address,uint256,bytes)") + ); } - receive() external payable { - if (!doesAcceptTokens) { + receive() external payable { + if (!doesAcceptTokens) { revert(ERROR_MSG); } - } + } } diff --git a/test/0.8.9/withdrawal-queue-requests-finalization.test.js b/test/0.8.9/withdrawal-queue-requests-finalization.test.js index b592126fd..87de94fe8 100644 --- a/test/0.8.9/withdrawal-queue-requests-finalization.test.js +++ b/test/0.8.9/withdrawal-queue-requests-finalization.test.js @@ -57,6 +57,11 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { steth = deployed.steth withdrawalQueue = deployed.withdrawalQueue + withdrawalQueue.requestWithdrawalsWithResults = async (...args) => { + const result = await withdrawalQueue.requestWithdrawals.call(...args) + await withdrawalQueue.requestWithdrawals(...args) + return result + } await steth.mintShares(user, e18(10)) await steth.approve(withdrawalQueue.address, StETH(10), { from: user }) @@ -72,9 +77,6 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { }) context('calculateFinalizationBatches', () => { - afterEach(async () => { - await snapshot.rollback() - }) it('reverts on invalid state', async () => { await assert.reverts( withdrawalQueue.calculateFinalizationBatches(shareRate(300), 100000, 1000, [ @@ -92,10 +94,7 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { }) it('works correctly on multiple calls', async () => { - const [requestId1, requestId2] = await withdrawalQueue.requestWithdrawals.call([ETH(1), ETH(1)], user, { - from: user, - }) - await withdrawalQueue.requestWithdrawals([ETH(1), ETH(1)], user, { + const [requestId1, requestId2] = await withdrawalQueue.requestWithdrawalsWithResults([ETH(1), ETH(1)], user, { from: user, }) const calculatedBatches1 = await withdrawalQueue.calculateFinalizationBatches(shareRate(1), 10000000000, 1, [ @@ -122,10 +121,7 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { }) it('stops on maxTimestamp', async () => { - const [requestId1] = await withdrawalQueue.requestWithdrawals.call([ETH(1)], user, { - from: user, - }) - await withdrawalQueue.requestWithdrawals([ETH(1)], user, { + const [requestId1] = await withdrawalQueue.requestWithdrawalsWithResults([ETH(1)], user, { from: user, }) const [status] = await withdrawalQueue.getWithdrawalStatus([requestId1]) diff --git a/test/0.8.9/withdrawal-queue.test.js b/test/0.8.9/withdrawal-queue.test.js index b3a8a5d1e..455e0167f 100644 --- a/test/0.8.9/withdrawal-queue.test.js +++ b/test/0.8.9/withdrawal-queue.test.js @@ -13,7 +13,7 @@ const { deployWithdrawalQueue } = require('./withdrawal-queue-deploy.test') contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, oracle]) => { let withdrawalQueue, steth, wsteth, normalizedShareRate - + const ALLOWED_ERROR_WEI = 100 const snapshot = new EvmSnapshot(ethers.provider) const currentRate = async () => @@ -34,7 +34,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, withdrawalQueue = deployed.withdrawalQueue await steth.setTotalPooledEther(ETH(600)) - // we need 1 ETH additionally to pay gas on finalization because coverage ignores gasPrice=0 + // we need 1 ETH additionally to pay gas on finalization because solidity-coverage ignores gasPrice=0 await setBalance(steth.address, ETH(600 + 1)) await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) @@ -365,7 +365,6 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const amount = bn(ETH(300)) beforeEach('Enqueue a request', async () => { - await snapshot.rollback() await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) }) @@ -626,10 +625,9 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const balanceBefore = bn(await ethers.provider.getBalance(owner)) - const tx = await withdrawalQueue.claimWithdrawal(1, { from: owner }) + await withdrawalQueue.claimWithdrawal(1, { from: owner, gasPrice: 0 }) - // tx.receipt.gasUsed is a workaround for coverage, because it ignores gasPrice=0 - assert.almostEqual(await ethers.provider.getBalance(owner), balanceBefore.add(bn(amount)), tx.receipt.gasUsed) + assert.equals(await ethers.provider.getBalance(owner), balanceBefore.add(bn(amount))) }) it('One cant claim not finalized or not existed request', async () => { @@ -669,12 +667,11 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const balanceBefore = bn(await ethers.provider.getBalance(owner)) assert.equals(await withdrawalQueue.getLockedEtherAmount(), batch.ethToLock) - const tx = await withdrawalQueue.claimWithdrawal(1, { from: owner }).catch() + await withdrawalQueue.claimWithdrawal(1, { from: owner, gasPrice: 0 }) assert.equals(await withdrawalQueue.getLockedEtherAmount(), ETH(0)) - // tx.receipt.gasUsed is a workaround for coverage, because it ignores gasPrice=0 - assert.almostEqual(bn(await ethers.provider.getBalance(owner)).sub(balanceBefore), ETH(150), tx.receipt.gasUsed) + assert.almostEqual(bn(await ethers.provider.getBalance(owner)).sub(balanceBefore), ETH(150), ALLOWED_ERROR_WEI) }) it('One can claim a lot of withdrawals with different discounts', async () => { @@ -723,9 +720,8 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await withdrawalQueue.finalize([secondRequestId], normalizedShareRate, { from: steth.address, value: ETH(30) }) const balanceBefore = bn(await ethers.provider.getBalance(owner)) - const tx = await withdrawalQueue.claimWithdrawals([1, 2], [1, 1], { from: owner, gasPrice: 0 }) - // tx.receipt.gasUsed is a workaround for coverage, because it ignores gasPrice=0 - assert.almostEqual(await ethers.provider.getBalance(owner), balanceBefore.add(bn(ETH(30))), tx.receipt.gasUsed) + await withdrawalQueue.claimWithdrawals([1, 2], [1, 1], { from: owner, gasPrice: 0 }) + assert.almostEqual(await ethers.provider.getBalance(owner), balanceBefore.add(bn(ETH(30))), ALLOWED_ERROR_WEI * 2) }) }) @@ -736,7 +732,6 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, let requestIds beforeEach(async () => { - await snapshot.rollback() await withdrawalQueue.requestWithdrawals(requestsAmounts, user, { from: user }) requestIds = await withdrawalQueue.getWithdrawalRequests(user, { from: user }) }) From ad03b378d80961040d9f1914a29803fc250d2a22 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Wed, 15 Mar 2023 16:46:33 +0700 Subject: [PATCH 08/66] =?UTF-8?q?test:=20NodeOperatorsRegistry=20happy=20p?= =?UTF-8?q?ath=20=E2=80=94=20target=20limit=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...node-operators-registry-happy-path.test.js | 93 +++++++++---------- test/0.4.24/node-operators-registry.test.js | 7 +- test/helpers/staking-modules.js | 2 + 3 files changed, 53 insertions(+), 49 deletions(-) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index 1aa300f1d..8e70cd006 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -5,7 +5,7 @@ const signingKeys = require('../helpers/signing-keys') const { ETH } = require('../helpers/utils') const { DSMAttestMessage } = require('../helpers/signatures') const { deployProtocol } = require('../helpers/protocol') -const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') +const { setupNodeOperatorsRegistry, NodeOperatorsRegistry } = require('../helpers/staking-modules') const ADDRESS_1 = '0x0000000000000000000000000000000000000001' const ADDRESS_2 = '0x0000000000000000000000000000000000000002' @@ -205,21 +205,44 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted) }) - it('TargetLimit', async () => { - // affects on obtain deposit data - // should be called with StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() - // todo: - // nor.getNodeOperatorSummary(Operator1.id) - // — check default is zero - // — set - // — check it was setted - // — check depositable keys changed + it('Set target limit', async () => { + const [curated] = await stakingRouter.getStakingModules() + const operatorId = Operator2.id + + let summary = await nor.getNodeOperatorSummary(operatorId) + assert.equals(summary.isTargetLimitActive, false) + assert.equals(summary.targetValidatorsCount, 0) + assert.equals(summary.depositableValidatorsCount, 10) + + // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() + const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorId, true, 1, { from: voting }) + + summary = await nor.getNodeOperatorSummary(operatorId) + assert.equals(summary.isTargetLimitActive, true) + assert.equals(summary.targetValidatorsCount, 1) + assert.equals(summary.depositableValidatorsCount, 1) + + assert.emits( + tx, + 'TargetValidatorsCountChanged', + { nodeOperatorId: operatorId, targetValidatorsCount: 1 }, + { abi: NodeOperatorsRegistry._json.abi } + ) }) + async function assertDepositCall(operatorId, callIdx, keyIdx) { + const regCall = await depositContract.calls.call(callIdx) + const { key, depositSignature } = await nor.getSigningKey(operatorId, keyIdx) + assert.equal(regCall.pubkey, key) + assert.equal(regCall.signature, depositSignature) + assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) + assert.equals(regCall.value, ETH(32)) + } + it('Obtain deposit data', async () => { const [curated] = await stakingRouter.getStakingModules() - const stakesDeposited = 3 + const stakesDeposited = 4 await web3.eth.sendTransaction({ to: lido.address, from: user1, value: ETH(32 * stakesDeposited) }) const block = await web3.eth.getBlock('latest') @@ -242,55 +265,29 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us const depositCallCount = await depositContract.totalCalls() assert.equals(depositCallCount, stakesDeposited) - { - // Deposit call #1 - const regCall = await depositContract.calls.call(0) - const { key, depositSignature } = await nor.getSigningKey(Operator1.id, 0) - assert.equal(regCall.pubkey, key) - assert.equal(regCall.signature, depositSignature) - assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) - assert.equals(regCall.value, ETH(32)) - } - - { - // Deposit call #2 - const regCall = await depositContract.calls.call(1) - const { key, depositSignature } = await nor.getSigningKey(Operator1.id, 1) - assert.equal(regCall.pubkey, key) - assert.equal(regCall.signature, depositSignature) - assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) - assert.equals(regCall.value, ETH(32)) - } - - { - // Deposit call #3 - const regCall = await depositContract.calls.call(2) - const { key, depositSignature } = await nor.getSigningKey(Operator2.id, 0) - assert.equal(regCall.pubkey, key) - assert.equal(regCall.signature, depositSignature) - assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) - assert.equals(regCall.value, ETH(32)) - } - - // TODO: check that TargetLimit affected deposited value + // Target Limit affects here, that's why operator 1 receives 3 deposits, while operator 2 got only 1 + await assertDepositCall(Operator1.id, 0, 0) + await assertDepositCall(Operator1.id, 1, 1) + await assertDepositCall(Operator1.id, 2, 2) + await assertDepositCall(Operator2.id, 3, 0) const stakingModuleSummary = await nor.getStakingModuleSummary() assert.equals(stakingModuleSummary.totalDepositedValidators, stateTotaldeposited) - assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted - stateTotaldeposited) + assert.equals(stakingModuleSummary.depositableValidatorsCount, 3) const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) - assert.equals(summaryOperator1.totalDepositedValidators, 2) - assert.equals(summaryOperator1.depositableValidatorsCount, Operator1.vettedSigningKeysCount - 2) + assert.equals(summaryOperator1.totalDepositedValidators, 3) + assert.equals(summaryOperator1.depositableValidatorsCount, 3) const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) assert.equals(summaryOperator2.totalDepositedValidators, 1) - assert.equals(summaryOperator2.depositableValidatorsCount, Operator2.vettedSigningKeysCount - 1) + assert.equals(summaryOperator2.depositableValidatorsCount, 0) }) it('Rewards distribution', async () => { - const totalRewardsShare = web3.utils.toWei('15') + const totalRewardsShare = web3.utils.toWei('20') const distribution = await nor.getRewardsDistribution(totalRewardsShare) - assert.equal(distribution.shares[0], web3.utils.toWei('10')) + assert.equal(distribution.shares[0], web3.utils.toWei('15')) assert.equal(distribution.shares[1], web3.utils.toWei('5')) assert.equal(distribution.recipients[0], rewards1) assert.equal(distribution.recipients[1], rewards2) diff --git a/test/0.4.24/node-operators-registry.test.js b/test/0.4.24/node-operators-registry.test.js index d9d3e602a..ffbb88620 100644 --- a/test/0.4.24/node-operators-registry.test.js +++ b/test/0.4.24/node-operators-registry.test.js @@ -1519,10 +1519,15 @@ contract('NodeOperatorsRegistry', (addresses) => { const targetLimit = 10 const isTargetLimitSet = true - await app.updateTargetValidatorsLimits(firstNodeOperatorId, isTargetLimitSet, targetLimit, { + const tx = await app.updateTargetValidatorsLimits(firstNodeOperatorId, isTargetLimitSet, targetLimit, { from: stakingRouter, }) + assert.emits(tx, 'TargetValidatorsCountChanged', { + nodeOperatorId: firstNodeOperatorId, + targetValidatorsCount: targetLimit, + }) + const keysStatTotal = await app.getStakingModuleSummary() const expectedExitedValidatorsCount = NODE_OPERATORS[firstNodeOperatorId].exitedSigningKeysCount + diff --git a/test/helpers/staking-modules.js b/test/helpers/staking-modules.js index edc4ecce9..d0df8e6b8 100644 --- a/test/helpers/staking-modules.js +++ b/test/helpers/staking-modules.js @@ -98,5 +98,7 @@ async function setupNodeOperatorsRegistry({ dao, acl, lidoLocator, stakingRouter } module.exports = { + NodeOperatorsRegistry, + NodeOperatorsRegistryMock, setupNodeOperatorsRegistry, } From 7115e0f01cca493aa66be28f4348b685d3dede82 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 15 Mar 2023 18:26:57 +0700 Subject: [PATCH 09/66] fix: naming and 100% discount test --- test/0.8.9/withdrawal-queue.test.js | 60 +++++++++++++++-------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/test/0.8.9/withdrawal-queue.test.js b/test/0.8.9/withdrawal-queue.test.js index 455e0167f..199a52963 100644 --- a/test/0.8.9/withdrawal-queue.test.js +++ b/test/0.8.9/withdrawal-queue.test.js @@ -12,7 +12,7 @@ const ERC721ReceiverMock = artifacts.require('ERC721ReceiverMock') const { deployWithdrawalQueue } = require('./withdrawal-queue-deploy.test') contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, oracle]) => { - let withdrawalQueue, steth, wsteth, normalizedShareRate + let withdrawalQueue, steth, wsteth, defaultShareRate const ALLOWED_ERROR_WEI = 100 const snapshot = new EvmSnapshot(ethers.provider) @@ -39,7 +39,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await steth.mintShares(user, shares(1)) await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) - normalizedShareRate = (await currentRate()).toString(10) + defaultShareRate = (await currentRate()).toString(10) await impersonate(ethers.provider, steth.address) await snapshot.make() @@ -399,8 +399,8 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await steth.approve(withdrawalQueue.address, StETH(300), { from: user }) await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) - const batch = await withdrawalQueue.prefinalize.call([2], normalizedShareRate) - await withdrawalQueue.finalize([2], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) + const batch = await withdrawalQueue.prefinalize.call([2], defaultShareRate) + await withdrawalQueue.finalize([2], defaultShareRate, { from: steth.address, value: batch.ethToLock }) assert.equals(batch.sharesToBurn, shares(2)) assert.equals(await withdrawalQueue.getLastRequestId(), 2) @@ -419,7 +419,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) - await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], defaultShareRate, { from: steth.address, value: amount }) assert.equals(await withdrawalQueue.getLastRequestId(), 2) assert.equals(await withdrawalQueue.getLastFinalizedRequestId(), 1) @@ -429,7 +429,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await ethers.provider.getBalance(withdrawalQueue.address) ) - await withdrawalQueue.finalize([2], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([2], defaultShareRate, { from: steth.address, value: amount }) assert.equals(await withdrawalQueue.getLastRequestId(), 2) assert.equals(await withdrawalQueue.getLastFinalizedRequestId(), 2) @@ -453,10 +453,10 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await assert.reverts(withdrawalQueue.prefinalize([2, 1], shareRate(1)), 'BatchesAreNotSorted()') }) - it('reverts in batches are empty', async () => { + it('reverts if batches are empty', async () => { await assert.reverts(withdrawalQueue.prefinalize([], shareRate(1.5)), 'EmptyBatches()') await assert.reverts( - withdrawalQueue.finalize([], normalizedShareRate, { from: steth.address, value: amount }), + withdrawalQueue.finalize([], defaultShareRate, { from: steth.address, value: amount }), 'EmptyBatches()' ) }) @@ -465,23 +465,23 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const idAhead = +(await withdrawalQueue.getLastRequestId()) + 1 await assert.reverts( - withdrawalQueue.finalize([idAhead], normalizedShareRate, { from: steth.address, value: amount }), + withdrawalQueue.finalize([idAhead], defaultShareRate, { from: steth.address, value: amount }), `InvalidRequestId(${idAhead})` ) - await assert.reverts(withdrawalQueue.prefinalize([idAhead], normalizedShareRate), `InvalidRequestId(${idAhead})`) + await assert.reverts(withdrawalQueue.prefinalize([idAhead], defaultShareRate), `InvalidRequestId(${idAhead})`) }) it('reverts if request with given id was finalized already', async () => { const id = +(await withdrawalQueue.getLastRequestId()) - await withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([id], defaultShareRate, { from: steth.address, value: amount }) await assert.reverts( - withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: amount }), + withdrawalQueue.finalize([id], defaultShareRate, { from: steth.address, value: amount }), `InvalidRequestId(${id})` ) - await assert.reverts(withdrawalQueue.prefinalize([id], normalizedShareRate), `InvalidRequestId(${id})`) + await assert.reverts(withdrawalQueue.prefinalize([id], defaultShareRate), `InvalidRequestId(${id})`) }) it('reverts if given amount to finalize exceeds requested', async () => { @@ -489,7 +489,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const amountExceeded = bn(ETH(400)) await assert.reverts( - withdrawalQueue.finalize([id], normalizedShareRate, { from: steth.address, value: amountExceeded }), + withdrawalQueue.finalize([id], defaultShareRate, { from: steth.address, value: amountExceeded }), `TooMuchEtherToFinalize(${+amountExceeded}, ${+amount})` ) }) @@ -501,7 +501,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('works', async () => { - await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: ETH(1) }) + await withdrawalQueue.finalize([1], defaultShareRate, { from: steth.address, value: ETH(1) }) assert.almostEqual(await withdrawalQueue.getClaimableEther([1], [1]), ETH(1), 100) }) @@ -574,7 +574,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('Owner can claim a finalized request to recipient address', async () => { - await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], defaultShareRate, { from: steth.address, value: amount }) const balanceBefore = bn(await ethers.provider.getBalance(user)) @@ -596,7 +596,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('reverts if sender is not owner', async () => { - await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], defaultShareRate, { from: steth.address, value: amount }) await assert.reverts( withdrawalQueue.claimWithdrawalsTo([1], [1], owner, { from: stranger }), `NotOwner("${stranger}", "${owner}")` @@ -604,7 +604,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('reverts if there is not enough balance', async () => { - await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], defaultShareRate, { from: steth.address, value: amount }) await setBalance(withdrawalQueue.address, ETH(200)) await assert.reverts(withdrawalQueue.claimWithdrawalsTo([1], [1], owner, { from: owner }), 'NotEnoughEther()') }) @@ -612,7 +612,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, it('reverts if receiver declines', async () => { const receiver = await ERC721ReceiverMock.new({ from: owner }) await receiver.setDoesAcceptTokens(false, { from: owner }) - await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], defaultShareRate, { from: steth.address, value: amount }) await assert.reverts( withdrawalQueue.claimWithdrawalsTo([1], [1], receiver.address, { from: owner }), 'CantSendValueRecipientMayHaveReverted()' @@ -621,7 +621,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('Owner can claim a finalized request without hint', async () => { - await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], defaultShareRate, { from: steth.address, value: amount }) const balanceBefore = bn(await ethers.provider.getBalance(owner)) @@ -648,13 +648,13 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, await withdrawalQueue.requestWithdrawals([amount], owner, { from: user }) - await withdrawalQueue.finalize([2], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([2], defaultShareRate, { from: steth.address, value: amount }) await assert.reverts(withdrawalQueue.claimWithdrawals([1], [0], { from: owner }), 'InvalidHint(0)') await assert.reverts(withdrawalQueue.claimWithdrawals([1], [2], { from: owner }), 'InvalidHint(2)') }) it('Cant withdraw token two times', async () => { - await withdrawalQueue.finalize([1], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([1], defaultShareRate, { from: steth.address, value: amount }) await withdrawalQueue.claimWithdrawal(1, { from: owner }) await assert.reverts(withdrawalQueue.claimWithdrawal(1, { from: owner }), 'RequestAlreadyClaimed(1)') @@ -717,7 +717,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, const secondRequestAmount = ETH(10) await withdrawalQueue.requestWithdrawals([secondRequestAmount], owner, { from: owner }) const secondRequestId = await withdrawalQueue.getLastRequestId() - await withdrawalQueue.finalize([secondRequestId], normalizedShareRate, { from: steth.address, value: ETH(30) }) + await withdrawalQueue.finalize([secondRequestId], defaultShareRate, { from: steth.address, value: ETH(30) }) const balanceBefore = bn(await ethers.provider.getBalance(owner)) await withdrawalQueue.claimWithdrawals([1, 2], [1, 1], { from: owner, gasPrice: 0 }) @@ -803,7 +803,9 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, it('100% discount', async () => { const balanceBefore = bn(await ethers.provider.getBalance(user)) const id = await withdrawalQueue.getLastRequestId() - withdrawalQueue.finalize([id], 0, { from: steth.address, value: 0 }) + const batches = await withdrawalQueue.prefinalize([id], 1) + assert.equals(batches.ethToLock, 0) + withdrawalQueue.finalize([id], 1, { from: steth.address, value: batches.ethToLock }) for (let index = 0; index < requestIds.length; index++) { const requestId = requestIds[index] const tx = await withdrawalQueue.claimWithdrawal(requestId, { from: user, gasPrice: 0 }) @@ -919,8 +921,8 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('returns not found when indexes have negative overlap', async () => { - const batch = await withdrawalQueue.prefinalize.call([requestId], normalizedShareRate) - await withdrawalQueue.finalize([requestId], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) + const batch = await withdrawalQueue.prefinalize.call([requestId], defaultShareRate) + await withdrawalQueue.finalize([requestId], defaultShareRate, { from: steth.address, value: batch.ethToLock }) const lastCheckpointIndex = await withdrawalQueue.getLastCheckpointIndex() const hints = await withdrawalQueue.findCheckpointHints( [requestId], @@ -932,8 +934,8 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it('returns hints array with one item for list from single request id', async () => { - const batch = await withdrawalQueue.prefinalize.call([requestId], normalizedShareRate) - await withdrawalQueue.finalize([requestId], normalizedShareRate, { from: steth.address, value: batch.ethToLock }) + const batch = await withdrawalQueue.prefinalize.call([requestId], defaultShareRate) + await withdrawalQueue.finalize([requestId], defaultShareRate, { from: steth.address, value: batch.ethToLock }) const lastCheckpointIndex = await withdrawalQueue.getLastCheckpointIndex() const hints = await withdrawalQueue.findCheckpointHints([requestId], 1, lastCheckpointIndex) assert.equal(hints.length, 1) @@ -1173,7 +1175,7 @@ contract('WithdrawalQueue', ([owner, stranger, daoAgent, user, pauser, resumer, }) it("One can't change claimed request", async () => { - await withdrawalQueue.finalize([requestId], normalizedShareRate, { from: steth.address, value: amount }) + await withdrawalQueue.finalize([requestId], defaultShareRate, { from: steth.address, value: amount }) await withdrawalQueue.claimWithdrawal(requestId, { from: user }) await assert.reverts( From 236d6e7cf0488c14fce03c79e36f3b37f4ba8a32 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Thu, 16 Mar 2023 22:06:17 +0700 Subject: [PATCH 10/66] test: NodeOperatorsRegistry registry happy path basic validators exit and stuck --- ...node-operators-registry-happy-path.test.js | 223 +++++++++++++----- 1 file changed, 167 insertions(+), 56 deletions(-) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index 8e70cd006..5c044390f 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -2,10 +2,19 @@ const { contract, web3 } = require('hardhat') const { assert } = require('../helpers/assert') const signingKeys = require('../helpers/signing-keys') -const { ETH } = require('../helpers/utils') const { DSMAttestMessage } = require('../helpers/signatures') const { deployProtocol } = require('../helpers/protocol') const { setupNodeOperatorsRegistry, NodeOperatorsRegistry } = require('../helpers/staking-modules') +const { e18, e27, toBN, ETH } = require('../helpers/utils') +const { + getAccountingReportDataItems, + encodeExtraDataItems, + packExtraDataList, + calcExtraDataListHash, + calcAccountingReportDataHash, +} = require('../0.8.9/oracle/accounting-oracle-deploy.test') + +const E9 = toBN(10).pow(toBN(9)) const ADDRESS_1 = '0x0000000000000000000000000000000000000001' const ADDRESS_2 = '0x0000000000000000000000000000000000000002' @@ -17,11 +26,7 @@ const NODE_OPERATORS = [ name: 'Node operator #1', rewardAddressInitial: ADDRESS_1, totalSigningKeysCount: 10, - exitedSigningKeysCount: 1, vettedSigningKeysCount: 6, - stuckValidatorsCount: 0, - refundedValidatorsCount: 0, - stuckPenaltyEndAt: 0, }, { id: 1, @@ -29,11 +34,7 @@ const NODE_OPERATORS = [ name: 'Node operator #2', rewardAddressInitial: ADDRESS_2, totalSigningKeysCount: 15, - exitedSigningKeysCount: 0, vettedSigningKeysCount: 10, - stuckValidatorsCount: 0, - refundedValidatorsCount: 0, - stuckPenaltyEndAt: 0, }, { id: 2, @@ -41,11 +42,7 @@ const NODE_OPERATORS = [ name: 'Node operator #3', rewardAddressInitial: ADDRESS_3, totalSigningKeysCount: 10, - exitedSigningKeysCount: 0, vettedSigningKeysCount: 5, - stuckValidatorsCount: 0, - refundedValidatorsCount: 0, - stuckPenaltyEndAt: 0, }, ] @@ -60,10 +57,6 @@ const forEachSync = async (arr, cb) => { } contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, user1, nobody]) => { - // let app - // let locator - // let steth - // let dao let dsm let lido let nor @@ -74,6 +67,20 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us let rewardAddresses let guardians let withdrawalCredentials + let consensus + let oracle + let consensusVersion + let signers + let consensusMember + + async function assertDepositCall(operatorId, callIdx, keyIdx) { + const regCall = await depositContract.calls.call(callIdx) + const { key, depositSignature } = await nor.getSigningKey(operatorId, keyIdx) + assert.equal(regCall.pubkey, key) + assert.equal(regCall.signature, depositSignature) + assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) + assert.equals(regCall.value, ETH(32)) + } before('deploy base app', async () => { const deployed = await deployProtocol({ @@ -93,28 +100,32 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us rewardAddresses = [rewards1, rewards2, rewards3] - // app = deployed.dao lido = deployed.pool nor = deployed.stakingModules[0] - // steth = deployed.token - // locator = deployed.lidoLocator stakingRouter = deployed.stakingRouter depositContract = deployed.depositContract depositRoot = await depositContract.get_deposit_root() dsm = deployed.depositSecurityModule guardians = deployed.guardians - voting = deployed.voting.address - // treasuryAddr = deployed.treasury.address + voting = deployed.voting + consensus = deployed.consensusContract + oracle = deployed.oracle + signers = deployed.signers + consensusMember = signers[2].address + + consensusVersion = await oracle.getConsensusVersion() + await consensus.removeMember(signers[4].address, 2, { from: voting.address }) + await consensus.removeMember(signers[3].address, 1, { from: voting.address }) withdrawalCredentials = '0x'.padEnd(66, '1234') - await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) + await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting.address }) }) describe('Happy path', () => { it('Add node operator', async () => { await forEachSync(NODE_OPERATORS, async (operatorData, i) => { const initialName = `operator ${i + 1}` - const tx = await nor.addNodeOperator(initialName, operatorData.rewardAddressInitial, { from: voting }) + const tx = await nor.addNodeOperator(initialName, operatorData.rewardAddressInitial, { from: voting.address }) const expectedStakingLimit = 0 assert.emits(tx, 'NodeOperatorAdded', { @@ -141,7 +152,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us // TODO: Move this block after keys manipulations to check how it will affect them it('Deactivate node operator 3', async () => { const activeOperatorsBefore = await nor.getActiveNodeOperatorsCount() - const tx = await nor.deactivateNodeOperator(Operator3.id, { from: voting }) + const tx = await nor.deactivateNodeOperator(Operator3.id, { from: voting.address }) const operator = await nor.getNodeOperator(Operator3.id, true) const activeOperatorsAfter = await nor.getActiveNodeOperatorsCount() @@ -153,7 +164,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us it('Set name', async () => { await forEachSync(NODE_OPERATORS, async (operatorData, i) => { - await nor.setNodeOperatorName(operatorData.id, operatorData.name, { from: voting }) + await nor.setNodeOperatorName(operatorData.id, operatorData.name, { from: voting.address }) const operator = await nor.getNodeOperator(operatorData.id, true) assert.equals(operator.name, operatorData.name) }) @@ -162,7 +173,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us it('Set reward address', async () => { await forEachSync(NODE_OPERATORS, async (operatorData, i) => { const rewardAddress = rewardAddresses[i] - await nor.setNodeOperatorRewardAddress(operatorData.id, rewardAddress, { from: voting }) + await nor.setNodeOperatorRewardAddress(operatorData.id, rewardAddress, { from: voting.address }) const operator = await nor.getNodeOperator(operatorData.id, true) assert.equals(operator.rewardAddress, rewardAddress) }) @@ -171,7 +182,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us it('Add signing keys', async () => { await forEachSync(NODE_OPERATORS, async (operatorData, i) => { const keys = new signingKeys.FakeValidatorKeys(operatorData.totalSigningKeysCount) - await nor.addSigningKeys(operatorData.id, keys.count, ...keys.slice(), { from: voting }) + await nor.addSigningKeys(operatorData.id, keys.count, ...keys.slice(), { from: voting.address }) const operator = await nor.getNodeOperator(operatorData.id, true) const keysCount = await nor.getTotalSigningKeyCount(operatorData.id) @@ -196,7 +207,9 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us await forEachSync(NODE_OPERATORS, async (operatorData, i) => { if (!(await nor.getNodeOperatorIsActive(operatorData.id))) return stateTotalVetted += operatorData.vettedSigningKeysCount - await nor.setNodeOperatorStakingLimit(operatorData.id, operatorData.vettedSigningKeysCount, { from: voting }) + await nor.setNodeOperatorStakingLimit(operatorData.id, operatorData.vettedSigningKeysCount, { + from: voting.address, + }) const operator = await nor.getNodeOperator(operatorData.id, true) assert.equals(operator.stakingLimit, operatorData.vettedSigningKeysCount) }) @@ -215,7 +228,9 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us assert.equals(summary.depositableValidatorsCount, 10) // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() - const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorId, true, 1, { from: voting }) + const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorId, true, 1, { + from: voting.address, + }) summary = await nor.getNodeOperatorSummary(operatorId) assert.equals(summary.isTargetLimitActive, true) @@ -230,15 +245,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us ) }) - async function assertDepositCall(operatorId, callIdx, keyIdx) { - const regCall = await depositContract.calls.call(callIdx) - const { key, depositSignature } = await nor.getSigningKey(operatorId, keyIdx) - assert.equal(regCall.pubkey, key) - assert.equal(regCall.signature, depositSignature) - assert.equal(regCall.withdrawal_credentials, withdrawalCredentials) - assert.equals(regCall.value, ETH(32)) - } - it('Obtain deposit data', async () => { const [curated] = await stakingRouter.getStakingModules() @@ -293,23 +299,128 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us assert.equal(distribution.recipients[1], rewards2) }) - // TODO: Exited validators - // AccountingOracle.submitReport() -> StakingRouter...() — only for memory - // [optional — if can be mocked] assert ... -> Lido...() -> NOR.onRewardsMinted() was called - // - // AccountingOracle.submitReportExtraDataList() - // -> StakingRouter.reportStakingModuleExitedValidatorsCountByNodeOperator() - // -> StakingRouter.reportStakingModuleStuckValidatorsCountByNodeOperator() - // -> NOR.updateExitedValidatorsCount() - // assert exited validators count - // assert rewards was transfered with NOR._distributeRewards() - // assert rewards was transfered to operators for his exited validators - // assert TargetLimit was changed to zero if any key stucked. ... -> NOR._updateSummaryMaxValidatorsCount() - - // TODO: ... -> StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished() -> NOR.onExitedAndStuckValidatorsCountsUpdated() - // assert rewards was transfered to exited validators - - // TODO: StakingRouter.unsafeSetExitedValidatorsCount() -> NOR.onExitedAndStuckValidatorsCountsUpdated() + it('Validators exiting and stuck', async () => { + const { refSlot } = await consensus.getCurrentFrame() + const [curated] = await stakingRouter.getStakingModules() + + const extraData = { + exitedKeys: [{ moduleId: 1, nodeOpIds: [0], keysCounts: [2] }], + stuckKeys: [{ moduleId: 1, nodeOpIds: [1], keysCounts: [1] }], + } + + const extraDataItems = encodeExtraDataItems(extraData) + const extraDataList = packExtraDataList(extraDataItems) + const extraDataHash = calcExtraDataListHash(extraDataList) + + const reportFields = { + consensusVersion, + numValidators: 4, + clBalanceGwei: toBN(ETH(32 * 4)).div(E9), + stakingModuleIdsWithNewlyExitedValidators: [curated.id], + numExitedValidatorsByStakingModule: [1], + withdrawalVaultBalance: e18(0), + elRewardsVaultBalance: e18(0), + sharesRequestedToBurn: e18(0), + withdrawalFinalizationBatches: [], + simulatedShareRate: e27(1), + isBunkerMode: false, + extraDataFormat: 1, + refSlot: +refSlot, + extraDataHash, + extraDataItemsCount: 2, + } + + const reportItems = getAccountingReportDataItems(reportFields) + const reportHash = calcAccountingReportDataHash(reportItems) + + await consensus.submitReport(+refSlot, reportHash, consensusVersion, { from: consensusMember }) + + // Mentionable internal calls + // AccountingOracle.submitReportData() + // -> Lido.handleOracleReport()._processRewards()._distributeFee() + // -> StakingRouter.reportRewardsMinted() -> NOR.onRewardsMinted() + // ._transferModuleRewards()._transferShares() + // -> StakingRouter.updateExitedValidatorsCountByStakingModule() -> StakingRouter.stakingModule[id].exitedValidatorsCount = exitedCount + // TODO: [optional] assert those tied calls + await oracle.submitReportData(reportItems, consensusVersion, { from: consensusMember }) + + // Mentionable internal calls + // AccountingOracle.submitReportExtraDataList() + // -> StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished() -> NOR.onExitedAndStuckValidatorsCountsUpdated()._distributeRewards() + // emits NOR.NodeOperatorPenalized + // emits NOR.RewardsDistributed + // -> stETH.transferShares() + // -> Burner.requestBurnShares() + // -> StakingRouter.reportStakingModuleExitedValidatorsCountByNodeOperator() -> NOR.updateExitedValidatorsCount() -> + // emits NOR.ExitedSigningKeysCountChanged + // ._saveSummarySigningKeysStats() + // ._updateSummaryMaxValidatorsCount() + // -> StakingRouter.reportStakingModuleStuckValidatorsCountByNodeOperator() -> NOR.updateStuckValidatorsCount() -> + // emits NOR.StuckPenaltyStateChanged + // ._saveOperatorStuckPenaltyStats() + // ._updateSummaryMaxValidatorsCount() + const tx = await oracle.submitReportExtraDataList(extraDataList, { from: voting.address }) + + assert.emits( + tx, + 'ExitedSigningKeysCountChanged', + { nodeOperatorId: Operator1.id, exitedValidatorsCount: 2 }, + { abi: NodeOperatorsRegistry._json.abi } + ) + + assert.emits( + tx, + 'StuckPenaltyStateChanged', + { + nodeOperatorId: Operator2.id, + stuckValidatorsCount: 1, + refundedValidatorsCount: 0, + stuckPenaltyEndTimestamp: 0, + }, + { abi: NodeOperatorsRegistry._json.abi } + ) + + const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) + assert.equals(summaryOperator1.totalExitedValidators, 2) + + const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) + assert.equals(summaryOperator2.stuckValidatorsCount, 1) + + // TODO: assert emits NodeOperatorPenalized + // TODO: assert emits RewardsDistributed + // TODO: assert rewards was transfered with NOR._distributeRewards() + // TODO: assert rewards was transfered to operators for his exited validators + // TODO: assert TargetLimit was changed to zero if any key stucked. ... -> NOR._updateSummaryMaxValidatorsCount() + }) + + it('unsafeSetExitedValidatorsCount', async () => { + const [curated] = await stakingRouter.getStakingModules() + const correction = { + currentModuleExitedValidatorsCount: 1, + currentNodeOperatorExitedValidatorsCount: 2, + currentNodeOperatorStuckValidatorsCount: 0, + newModuleExitedValidatorsCount: 1, + newNodeOperatorExitedValidatorsCount: 3, + newNodeOperatorStuckValidatorsCount: 0, + } + + // StakingRouter.unsafeSetExitedValidatorsCount() -> NOR.onExitedAndStuckValidatorsCountsUpdated()._distributeRewards() + // emits NOR.NodeOperatorPenalized + // emits NOR.RewardsDistributed + // -> stETH.transferShares() + // -> Burner.requestBurnShares() + await stakingRouter.unsafeSetExitedValidatorsCount(curated.id, Operator1.id, true, correction, { + from: voting.address, + }) + + const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) + assert.equals(summaryOperator1.totalExitedValidators, 3) + + // TODO: assert emits NOR.NodeOperatorPenalized + // TODO: assert emits NOR.RewardsDistributed + // TODO: assert rewards was transfered with NOR._distributeRewards() + // TODO: assert rewards was transfered to operators for his exited validators + }) // TODO: TargetLimit allows to deposit after exit From 48e08368e37bc9f1fd73122e62f14d8834010e54 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 16 Mar 2023 18:39:07 +0300 Subject: [PATCH 11/66] fix: `DSM.canDeposit == false` if bad module id --- contracts/0.8.9/DepositSecurityModule.sol | 12 +++++++++- ...kingRouterMockForDepositSecurityModule.sol | 10 +++++++++ test/0.8.9/deposit-security-module.test.js | 22 ++++++++++++++++++- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 4439ff348..931decaf5 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -26,6 +26,7 @@ interface IStakingRouter { function getStakingModuleIsActive(uint256 _stakingModuleId) external view returns (bool); function getStakingModuleNonce(uint256 _stakingModuleId) external view returns (uint256); function getStakingModuleLastDepositBlock(uint256 _stakingModuleId) external view returns (uint256); + function getStakingModuleIds() external view returns (uint256[] memory stakingModuleIds); } @@ -377,11 +378,20 @@ contract DepositSecurityModule { * such attestations will be enough to reach quorum. */ function canDeposit(uint256 stakingModuleId) external view returns (bool) { + uint256[] memory registeredIds = STAKING_ROUTER.getStakingModuleIds(); + bool isStakingModuleRegistered; + for (uint256 i; i < registeredIds.length; ++i) { + if (registeredIds[i] == stakingModuleId) { + isStakingModuleRegistered = true; + break; + } + } bool isModuleActive = STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId); uint256 lastDepositBlock = STAKING_ROUTER.getStakingModuleLastDepositBlock(stakingModuleId); bool isLidoCanDeposit = LIDO.canDeposit(); return ( - isModuleActive + isStakingModuleRegistered + && isModuleActive && quorum > 0 && block.number - lastDepositBlock >= minDepositBlockDistance && isLidoCanDeposit diff --git a/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol b/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol index 49cc729f7..934b0cd57 100644 --- a/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol +++ b/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol @@ -15,6 +15,11 @@ contract StakingRouterMockForDepositSecurityModule is IStakingRouter { StakingRouter.StakingModuleStatus private status; uint256 private stakingModuleNonce; uint256 private stakingModuleLastDepositBlock; + uint256 private registeredStakingModuleId; + + constructor(uint256 stakingModuleId) { + registeredStakingModuleId = stakingModuleId; + } function deposit( uint256 maxDepositsCount, @@ -25,6 +30,11 @@ contract StakingRouterMockForDepositSecurityModule is IStakingRouter { return maxDepositsCount; } + function getStakingModuleIds() external view returns (uint256[] memory stakingModuleIds) { + stakingModuleIds = new uint256[](1); + stakingModuleIds[0] = registeredStakingModuleId; + } + function getStakingModuleStatus(uint256) external view returns (StakingRouter.StakingModuleStatus) { return status; } diff --git a/test/0.8.9/deposit-security-module.test.js b/test/0.8.9/deposit-security-module.test.js index 5ed520c05..4f0b45678 100644 --- a/test/0.8.9/deposit-security-module.test.js +++ b/test/0.8.9/deposit-security-module.test.js @@ -48,7 +48,7 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { before('deploy mock contracts', async () => { lidoMock = await LidoMockForDepositSecurityModule.new() - stakingRouterMock = await StakingRouterMockForDepositSecurityModule.new() + stakingRouterMock = await StakingRouterMockForDepositSecurityModule.new(STAKING_MODULE) depositContractMock = await DepositContractMockForDepositSecurityModule.new() depositSecurityModule = await DepositSecurityModule.new( @@ -1042,6 +1042,26 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { assert.isTrue(currentBlockNumber - lastDepositBlockNumber >= minDepositBlockDistance) assert.isTrue(await depositSecurityModule.canDeposit(STAKING_MODULE)) }) + it('false if unknown staking module id', async () => { + await depositSecurityModule.addGuardian(GUARDIAN1, 1, { from: owner }) + + assert.equal( + await stakingRouterMock.getStakingModuleIsDepositsPaused(STAKING_MODULE + 1), + false, + 'invariant failed: isPaused' + ) + assert.isTrue((await depositSecurityModule.getGuardianQuorum()) > 0, 'invariant failed: quorum > 0') + + const lastDepositBlockNumber = await web3.eth.getBlockNumber() + await stakingRouterMock.setStakingModuleLastDepositBlock(lastDepositBlockNumber) + await waitBlocks(2 * MIN_DEPOSIT_BLOCK_DISTANCE) + + const currentBlockNumber = await web3.eth.getBlockNumber() + const minDepositBlockDistance = await depositSecurityModule.getMinDepositBlockDistance() + + assert.isTrue(currentBlockNumber - lastDepositBlockNumber >= minDepositBlockDistance) + assert.isFalse(await depositSecurityModule.canDeposit(STAKING_MODULE + 1)) + }) it('false if paused and quorum > 0 and currentBlock - lastDepositBlock >= minDepositBlockDistance', async () => { await depositSecurityModule.addGuardians([GUARDIAN1, guardian], 1, { from: owner }) assert.isTrue((await depositSecurityModule.getGuardianQuorum()) > 0, 'invariant failed: quorum > 0') From ac8b87d79e09f3838a1ec9b4072b4a4c207f1438 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 16 Mar 2023 18:49:42 +0300 Subject: [PATCH 12/66] fix: Lido.handleOracleReport swap steps 6 and 7 The idea is to harden the accounting processing Shares should be burnt BEFORE the fee distribution --- contracts/0.4.24/Lido.sol | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contracts/0.4.24/Lido.sol b/contracts/0.4.24/Lido.sol index 852a8fed9..8f66bff68 100644 --- a/contracts/0.4.24/Lido.sol +++ b/contracts/0.4.24/Lido.sol @@ -1175,8 +1175,8 @@ contract Lido is Versioned, StETHPermit, AragonApp { * 4. Pass the accounting values to sanity checker to smoothen positive token rebase * (i.e., postpone the extra rewards to be applied during the next rounds) * 5. Invoke finalization of the withdrawal requests - * 6. Distribute protocol fee (treasury & node operators) - * 7. Burn excess shares within the allowed limit (can postpone some shares to be burnt later) + * 6. Burn excess shares within the allowed limit (can postpone some shares to be burnt later) + * 7. Distribute protocol fee (treasury & node operators) * 8. Complete token rebase by informing observers (emit an event and call the external receivers if any) * 9. Sanity check for the provided simulated share rate */ @@ -1261,6 +1261,13 @@ contract Lido is Versioned, StETHPermit, AragonApp { ); // Step 6. + // Burn the previously requested shares + if (reportContext.sharesToBurn > 0) { + IBurner(contracts.burner).commitSharesToBurn(reportContext.sharesToBurn); + _burnShares(contracts.burner, reportContext.sharesToBurn); + } + + // Step 7. // Distribute protocol fee (treasury & node operators) reportContext.sharesMintedAsFees = _processRewards( reportContext, @@ -1269,13 +1276,6 @@ contract Lido is Versioned, StETHPermit, AragonApp { elRewards ); - // Step 7. - // Burn the previously requested shares - if (reportContext.sharesToBurn > 0) { - IBurner(contracts.burner).commitSharesToBurn(reportContext.sharesToBurn); - _burnShares(contracts.burner, reportContext.sharesToBurn); - } - // Step 8. // Complete token rebase by informing observers (emit an event and call the external receivers if any) ( From 6ff14425cdde5668567a72a41f5704df0f8349e2 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 16 Mar 2023 20:40:56 +0300 Subject: [PATCH 13/66] fix: +`hasStakingModule` for StakingRouter --- contracts/0.8.9/StakingRouter.sol | 4 ++++ .../StakingRouterMockForDepositSecurityModule.sol | 5 ++--- lib/abi/StakingRouter.json | 2 +- test/0.8.9/staking-router/staking-router.test.js | 8 +++++++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index e47c09e46..f16144475 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -535,6 +535,10 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return STAKING_MODULES_COUNT_POSITION.getStorageUint256(); } + function hasStakingModule(uint256 _stakingModuleId) external view returns (bool) { + return _stakingModuleId != 0 && _stakingModuleId <= LAST_STAKING_MODULE_ID_POSITION.getStorageUint256(); + } + /** * @dev Returns status of staking module */ diff --git a/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol b/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol index 934b0cd57..1dc091f0a 100644 --- a/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol +++ b/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol @@ -30,9 +30,8 @@ contract StakingRouterMockForDepositSecurityModule is IStakingRouter { return maxDepositsCount; } - function getStakingModuleIds() external view returns (uint256[] memory stakingModuleIds) { - stakingModuleIds = new uint256[](1); - stakingModuleIds[0] = registeredStakingModuleId; + function hasStakingModule(uint256 _stakingModuleId) external view returns (bool) { + return _stakingModuleId == registeredStakingModuleId; } function getStakingModuleStatus(uint256) external view returns (StakingRouter.StakingModuleStatus) { diff --git a/lib/abi/StakingRouter.json b/lib/abi/StakingRouter.json index 4d4b74251..265201b94 100644 --- a/lib/abi/StakingRouter.json +++ b/lib/abi/StakingRouter.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/test/0.8.9/staking-router/staking-router.test.js b/test/0.8.9/staking-router/staking-router.test.js index 6fed47211..910366b07 100644 --- a/test/0.8.9/staking-router/staking-router.test.js +++ b/test/0.8.9/staking-router/staking-router.test.js @@ -63,9 +63,11 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { assert.equals(await router.getWithdrawalCredentials(), wc) assert.equals(await router.getLido(), lido) assert.equals(await router.getStakingModulesCount(), 0) + assert.equals(await router.hasStakingModule(0), false) + assert.equals(await router.hasStakingModule(1), false) assert.equals(await router.getRoleMemberCount(DEFAULT_ADMIN_ROLE), 1) - assert.equals(await router.hasRole(DEFAULT_ADMIN_ROLE, admin), true) + assert.isTrue(await router.hasRole(DEFAULT_ADMIN_ROLE, admin)) assert.equals(initialTx.logs.length, 3) @@ -210,9 +212,11 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { stakingModule = await StakingModuleMock.new({ from: deployer }) + assert.equals(await router.hasStakingModule(1), false) await router.addStakingModule('Test module', stakingModule.address, 100, 1000, 2000, { from: appManager, }) + assert.equals(await router.hasStakingModule(1), true) await stakingModule.setAvailableKeysCount(100, { from: deployer }) @@ -346,7 +350,9 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { it('staking modules limit is 32', async () => { for (let i = 0; i < 32; i++) { const stakingModule = await StakingModuleMock.new({ from: deployer }) + assert.equals(await router.hasStakingModule(i + 1), false) await router.addStakingModule('Test module', stakingModule.address, 100, 100, 100, { from: appManager }) + assert.equals(await router.hasStakingModule(i + 1), true) } const oneMoreStakingModule = await StakingModuleMock.new({ from: deployer }) From b944985392b19070510ee0dc3513e9577dfaf5a6 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 16 Mar 2023 20:42:38 +0300 Subject: [PATCH 14/66] fix: use `hasStakingModule` in DSM --- contracts/0.8.9/DepositSecurityModule.sol | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 931decaf5..798ee667b 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -26,7 +26,7 @@ interface IStakingRouter { function getStakingModuleIsActive(uint256 _stakingModuleId) external view returns (bool); function getStakingModuleNonce(uint256 _stakingModuleId) external view returns (uint256); function getStakingModuleLastDepositBlock(uint256 _stakingModuleId) external view returns (uint256); - function getStakingModuleIds() external view returns (uint256[] memory stakingModuleIds); + function hasStakingModule(uint256 _stakingModuleId) external view returns (bool); } @@ -378,19 +378,12 @@ contract DepositSecurityModule { * such attestations will be enough to reach quorum. */ function canDeposit(uint256 stakingModuleId) external view returns (bool) { - uint256[] memory registeredIds = STAKING_ROUTER.getStakingModuleIds(); - bool isStakingModuleRegistered; - for (uint256 i; i < registeredIds.length; ++i) { - if (registeredIds[i] == stakingModuleId) { - isStakingModuleRegistered = true; - break; - } - } + bool isModuleRegistered = STAKING_ROUTER.hasStakingModule(stakingModuleId); bool isModuleActive = STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId); uint256 lastDepositBlock = STAKING_ROUTER.getStakingModuleLastDepositBlock(stakingModuleId); bool isLidoCanDeposit = LIDO.canDeposit(); return ( - isStakingModuleRegistered + isModuleRegistered && isModuleActive && quorum > 0 && block.number - lastDepositBlock >= minDepositBlockDistance From 399a81fd0adfc680bc8a4a9dc317e6b065f4e6e1 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 16 Mar 2023 20:53:35 +0300 Subject: [PATCH 15/66] chore: add doc for `hasStakingModule` --- contracts/0.8.9/StakingRouter.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index f16144475..b117dcdf8 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -535,6 +535,9 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version return STAKING_MODULES_COUNT_POSITION.getStorageUint256(); } + /** + * @dev Returns true if staking module with the given id was registered via `addStakingModule`, false otherwise + */ function hasStakingModule(uint256 _stakingModuleId) external view returns (bool) { return _stakingModuleId != 0 && _stakingModuleId <= LAST_STAKING_MODULE_ID_POSITION.getStorageUint256(); } From 4449f410d2e72d8744341796c893d71df3cb2f4d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 16 Mar 2023 20:54:53 +0300 Subject: [PATCH 16/66] chore: asserts style --- test/0.8.9/staking-router/staking-router.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/0.8.9/staking-router/staking-router.test.js b/test/0.8.9/staking-router/staking-router.test.js index 910366b07..423df7a75 100644 --- a/test/0.8.9/staking-router/staking-router.test.js +++ b/test/0.8.9/staking-router/staking-router.test.js @@ -67,7 +67,7 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { assert.equals(await router.hasStakingModule(1), false) assert.equals(await router.getRoleMemberCount(DEFAULT_ADMIN_ROLE), 1) - assert.isTrue(await router.hasRole(DEFAULT_ADMIN_ROLE, admin)) + assert.equals(await router.hasRole(DEFAULT_ADMIN_ROLE, admin), true) assert.equals(initialTx.logs.length, 3) From 825ceb75a701377cf2d5d224f1347693e14f1c71 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 16 Mar 2023 22:05:06 +0300 Subject: [PATCH 17/66] fix: review fixes --- contracts/0.8.9/DepositSecurityModule.sol | 6 +++--- contracts/0.8.9/StakingRouter.sol | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/0.8.9/DepositSecurityModule.sol b/contracts/0.8.9/DepositSecurityModule.sol index 798ee667b..1de248148 100644 --- a/contracts/0.8.9/DepositSecurityModule.sol +++ b/contracts/0.8.9/DepositSecurityModule.sol @@ -378,13 +378,13 @@ contract DepositSecurityModule { * such attestations will be enough to reach quorum. */ function canDeposit(uint256 stakingModuleId) external view returns (bool) { - bool isModuleRegistered = STAKING_ROUTER.hasStakingModule(stakingModuleId); + if (!STAKING_ROUTER.hasStakingModule(stakingModuleId)) return false; + bool isModuleActive = STAKING_ROUTER.getStakingModuleIsActive(stakingModuleId); uint256 lastDepositBlock = STAKING_ROUTER.getStakingModuleLastDepositBlock(stakingModuleId); bool isLidoCanDeposit = LIDO.canDeposit(); return ( - isModuleRegistered - && isModuleActive + isModuleActive && quorum > 0 && block.number - lastDepositBlock >= minDepositBlockDistance && isLidoCanDeposit diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index b117dcdf8..8361273eb 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -539,7 +539,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version * @dev Returns true if staking module with the given id was registered via `addStakingModule`, false otherwise */ function hasStakingModule(uint256 _stakingModuleId) external view returns (bool) { - return _stakingModuleId != 0 && _stakingModuleId <= LAST_STAKING_MODULE_ID_POSITION.getStorageUint256(); + return _getStorageStakingIndicesMapping()[_stakingModuleId] != 0; } /** From ddf756779a07076460272a24b4491ef5542cd5e5 Mon Sep 17 00:00:00 2001 From: Artyom Veremeenko Date: Thu, 16 Mar 2023 23:09:42 +0400 Subject: [PATCH 18/66] fix(AccountingOracle ctor): SM INFO-11, revert is zero lido address --- contracts/0.8.9/oracle/AccountingOracle.sol | 2 ++ lib/abi/AccountingOracle.json | 2 +- test/0.8.9/oracle/accounting-oracle-deploy.test.js | 9 +++++++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index 53463f59d..b9c8055cb 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -92,6 +92,7 @@ contract AccountingOracle is BaseOracle { error LidoLocatorCannotBeZero(); error AdminCannotBeZero(); error LegacyOracleCannotBeZero(); + error LidoCannotBeZero(); error IncorrectOracleMigration(uint256 code); error SenderNotAllowed(); error InvalidExitedValidatorsData(); @@ -154,6 +155,7 @@ contract AccountingOracle is BaseOracle { { if (lidoLocator == address(0)) revert LidoLocatorCannotBeZero(); if (legacyOracle == address(0)) revert LegacyOracleCannotBeZero(); + if (lido == address(0)) revert LidoCannotBeZero(); LOCATOR = ILidoLocator(lidoLocator); LIDO = lido; LEGACY_ORACLE = legacyOracle; diff --git a/lib/abi/AccountingOracle.json b/lib/abi/AccountingOracle.json index 84fcf5043..9a7d92f0b 100644 --- a/lib/abi/AccountingOracle.json +++ b/lib/abi/AccountingOracle.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"lidoLocator","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"AdminCannotBeZero","type":"error"},{"inputs":[],"name":"CannotSubmitExtraDataBeforeMainData","type":"error"},{"inputs":[],"name":"ExtraDataAlreadyProcessed","type":"error"},{"inputs":[],"name":"ExtraDataHashCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataItemsCountCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataListOnlySupportsSingleTx","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"IncorrectOracleMigration","type":"error"},{"inputs":[{"internalType":"uint256","name":"initialRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"InitialRefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidExitedValidatorsData","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataItem","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataSortOrder","type":"error"},{"inputs":[],"name":"LegacyOracleCannotBeZero","type":"error"},{"inputs":[],"name":"LidoLocatorCannotBeZero","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NumExitedValidatorsCannotDecrease","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[],"name":"RefSlotAlreadyProcessing","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"SenderNotAllowed","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedFormat","type":"uint256"},{"internalType":"uint256","name":"receivedFormat","type":"uint256"}],"name":"UnexpectedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedExtraDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedIndex","type":"uint256"},{"internalType":"uint256","name":"receivedIndex","type":"uint256"}],"name":"UnexpectedExtraDataIndex","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedCount","type":"uint256"},{"internalType":"uint256","name":"receivedCount","type":"uint256"}],"name":"UnexpectedExtraDataItemsCount","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[{"internalType":"uint256","name":"format","type":"uint256"}],"name":"UnsupportedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"dataType","type":"uint256"}],"name":"UnsupportedExtraDataType","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsProcessed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"ExtraDataSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"processedItemsCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"WarnExtraDataIncompleteProcessing","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_EMPTY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_LIST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_EXITED_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_STUCK_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LEGACY_ORACLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBMIT_DATA_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProcessingState","outputs":[{"components":[{"internalType":"uint256","name":"currentFrameRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bytes32","name":"mainDataHash","type":"bytes32"},{"internalType":"bool","name":"mainDataSubmitted","type":"bool"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bool","name":"extraDataSubmitted","type":"bool"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"},{"internalType":"uint256","name":"extraDataItemsSubmitted","type":"uint256"}],"internalType":"struct AccountingOracle.ProcessingState","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"lastProcessingRefSlot","type":"uint256"}],"name":"initializeWithoutMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"numValidators","type":"uint256"},{"internalType":"uint256","name":"clBalanceGwei","type":"uint256"},{"internalType":"uint256[]","name":"stakingModuleIdsWithNewlyExitedValidators","type":"uint256[]"},{"internalType":"uint256[]","name":"numExitedValidatorsByStakingModule","type":"uint256[]"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256[]","name":"withdrawalFinalizationBatches","type":"uint256[]"},{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"bool","name":"isBunkerMode","type":"bool"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"}],"internalType":"struct AccountingOracle.ReportData","name":"data","type":"tuple"},{"internalType":"uint256","name":"contractVersion","type":"uint256"}],"name":"submitReportData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"submitReportExtraDataEmpty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"items","type":"bytes"}],"name":"submitReportExtraDataList","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 +[{"inputs":[{"internalType":"address","name":"lidoLocator","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"AdminCannotBeZero","type":"error"},{"inputs":[],"name":"CannotSubmitExtraDataBeforeMainData","type":"error"},{"inputs":[],"name":"ExtraDataAlreadyProcessed","type":"error"},{"inputs":[],"name":"ExtraDataHashCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataItemsCountCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataListOnlySupportsSingleTx","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"IncorrectOracleMigration","type":"error"},{"inputs":[{"internalType":"uint256","name":"initialRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"InitialRefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidExitedValidatorsData","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataItem","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataSortOrder","type":"error"},{"inputs":[],"name":"LegacyOracleCannotBeZero","type":"error"},{"inputs":[],"name":"LidoCannotBeZero","type":"error"},{"inputs":[],"name":"LidoLocatorCannotBeZero","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NumExitedValidatorsCannotDecrease","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[],"name":"RefSlotAlreadyProcessing","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"SenderNotAllowed","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedFormat","type":"uint256"},{"internalType":"uint256","name":"receivedFormat","type":"uint256"}],"name":"UnexpectedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedExtraDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedIndex","type":"uint256"},{"internalType":"uint256","name":"receivedIndex","type":"uint256"}],"name":"UnexpectedExtraDataIndex","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedCount","type":"uint256"},{"internalType":"uint256","name":"receivedCount","type":"uint256"}],"name":"UnexpectedExtraDataItemsCount","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[{"internalType":"uint256","name":"format","type":"uint256"}],"name":"UnsupportedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"dataType","type":"uint256"}],"name":"UnsupportedExtraDataType","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsProcessed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"ExtraDataSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"processedItemsCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"WarnExtraDataIncompleteProcessing","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_EMPTY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_LIST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_EXITED_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_STUCK_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LEGACY_ORACLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBMIT_DATA_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProcessingState","outputs":[{"components":[{"internalType":"uint256","name":"currentFrameRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bytes32","name":"mainDataHash","type":"bytes32"},{"internalType":"bool","name":"mainDataSubmitted","type":"bool"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bool","name":"extraDataSubmitted","type":"bool"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"},{"internalType":"uint256","name":"extraDataItemsSubmitted","type":"uint256"}],"internalType":"struct AccountingOracle.ProcessingState","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"lastProcessingRefSlot","type":"uint256"}],"name":"initializeWithoutMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"numValidators","type":"uint256"},{"internalType":"uint256","name":"clBalanceGwei","type":"uint256"},{"internalType":"uint256[]","name":"stakingModuleIdsWithNewlyExitedValidators","type":"uint256[]"},{"internalType":"uint256[]","name":"numExitedValidatorsByStakingModule","type":"uint256[]"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256[]","name":"withdrawalFinalizationBatches","type":"uint256[]"},{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"bool","name":"isBunkerMode","type":"bool"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"}],"internalType":"struct AccountingOracle.ReportData","name":"data","type":"tuple"},{"internalType":"uint256","name":"contractVersion","type":"uint256"}],"name":"submitReportData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"submitReportExtraDataEmpty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"items","type":"bytes"}],"name":"submitReportExtraDataList","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/test/0.8.9/oracle/accounting-oracle-deploy.test.js b/test/0.8.9/oracle/accounting-oracle-deploy.test.js index 1815151c9..4e603cf3b 100644 --- a/test/0.8.9/oracle/accounting-oracle-deploy.test.js +++ b/test/0.8.9/oracle/accounting-oracle-deploy.test.js @@ -104,6 +104,7 @@ async function deployAccountingOracleSetup( getLegacyOracle = deployMockLegacyOracle, lidoLocatorAddr: lidoLocatorAddrArg, legacyOracleAddr: legacyOracleAddrArg, + lidoAddr: lidoAddrArg, } = {} ) { const locatorAddr = (await deployLocatorWithDummyAddressesImplementation(admin)).address @@ -118,7 +119,7 @@ async function deployAccountingOracleSetup( const oracle = await AccountingOracle.new( lidoLocatorAddrArg || locatorAddr, - lido.address, + lidoAddrArg || lido.address, legacyOracleAddrArg || legacyOracle.address, secondsPerSlot, genesisTime, @@ -134,7 +135,7 @@ async function deployAccountingOracleSetup( initialEpoch, }) await updateLocatorImplementation(locatorAddr, admin, { - lido: lido.address, + lido: lidoAddrArg || lido.address, stakingRouter: stakingRouter.address, withdrawalQueue: withdrawalQueue.address, oracleReportSanityChecker: oracleReportSanityChecker.address, @@ -449,6 +450,10 @@ contract('AccountingOracle', ([admin, member1]) => { ) }) + it('constructor reverts if lido address is zero', async () => { + await assert.reverts(deployAccountingOracleSetup(admin, { lidoAddr: ZERO_ADDRESS }), 'LidoCannotBeZero()') + }) + it('initialize reverts if admin address is zero', async () => { const deployed = await deployAccountingOracleSetup(admin) await updateInitialEpoch(deployed.consensus) From 0afb23c67aeb79984ca416c91f6c1477f9326efb Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Thu, 16 Mar 2023 22:34:51 +0300 Subject: [PATCH 19/66] fix: review fixes --- ...kingRouterMockForDepositSecurityModule.sol | 41 ++++++++++++++----- test/0.8.9/deposit-security-module.test.js | 15 +++++-- .../staking-router/staking-router.test.js | 9 ++++ 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol b/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol index 1dc091f0a..b078d4d37 100644 --- a/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol +++ b/contracts/0.8.9/test_helpers/StakingRouterMockForDepositSecurityModule.sol @@ -9,6 +9,8 @@ import {StakingRouter} from "../StakingRouter.sol"; contract StakingRouterMockForDepositSecurityModule is IStakingRouter { + error StakingModuleUnregistered(); + event StakingModuleDeposited(uint256 maxDepositsCount, uint24 stakingModuleId, bytes depositCalldata); event StakingModuleStatusSet(uint24 indexed stakingModuleId, StakingRouter.StakingModuleStatus status, address setBy); @@ -25,51 +27,63 @@ contract StakingRouterMockForDepositSecurityModule is IStakingRouter { uint256 maxDepositsCount, uint256 stakingModuleId, bytes calldata depositCalldata - ) external payable returns (uint256 keysCount) { + ) external whenModuleIsRegistered(stakingModuleId) payable returns (uint256 keysCount) { emit StakingModuleDeposited(maxDepositsCount, uint24(stakingModuleId), depositCalldata); return maxDepositsCount; } - function hasStakingModule(uint256 _stakingModuleId) external view returns (bool) { + function hasStakingModule(uint256 _stakingModuleId) public view returns (bool) { return _stakingModuleId == registeredStakingModuleId; } - function getStakingModuleStatus(uint256) external view returns (StakingRouter.StakingModuleStatus) { + function getStakingModuleStatus(uint256 stakingModuleId) external view whenModuleIsRegistered(stakingModuleId) returns (StakingRouter.StakingModuleStatus) { return status; } - function setStakingModuleStatus(uint256 _stakingModuleId, StakingRouter.StakingModuleStatus _status) external { + function setStakingModuleStatus( + uint256 _stakingModuleId, StakingRouter.StakingModuleStatus _status + ) external whenModuleIsRegistered(_stakingModuleId) { emit StakingModuleStatusSet(uint24(_stakingModuleId), _status, msg.sender); status = _status; } - function pauseStakingModule(uint256 stakingModuleId) external { + function pauseStakingModule(uint256 stakingModuleId) external whenModuleIsRegistered(stakingModuleId) { emit StakingModuleStatusSet(uint24(stakingModuleId), StakingRouter.StakingModuleStatus.DepositsPaused, msg.sender); status = StakingRouter.StakingModuleStatus.DepositsPaused; } - function resumeStakingModule(uint256 stakingModuleId) external { + function resumeStakingModule(uint256 stakingModuleId) external whenModuleIsRegistered(stakingModuleId) { emit StakingModuleStatusSet(uint24(stakingModuleId), StakingRouter.StakingModuleStatus.Active, msg.sender); status = StakingRouter.StakingModuleStatus.Active; } - function getStakingModuleIsStopped(uint256) external view returns (bool) { + function getStakingModuleIsStopped( + uint256 stakingModuleId + ) external view whenModuleIsRegistered(stakingModuleId) returns (bool) { return status == StakingRouter.StakingModuleStatus.Stopped; } - function getStakingModuleIsDepositsPaused(uint256) external view returns (bool) { + function getStakingModuleIsDepositsPaused( + uint256 stakingModuleId + ) external view whenModuleIsRegistered(stakingModuleId) returns (bool) { return status == StakingRouter.StakingModuleStatus.DepositsPaused; } - function getStakingModuleIsActive(uint256) external view returns (bool) { + function getStakingModuleIsActive( + uint256 stakingModuleId + ) external view whenModuleIsRegistered(stakingModuleId) returns (bool) { return status == StakingRouter.StakingModuleStatus.Active; } - function getStakingModuleNonce(uint256) external view returns (uint256) { + function getStakingModuleNonce( + uint256 stakingModuleId + ) external view whenModuleIsRegistered(stakingModuleId) returns (uint256) { return stakingModuleNonce; } - function getStakingModuleLastDepositBlock(uint256) external view returns (uint256) { + function getStakingModuleLastDepositBlock( + uint256 stakingModuleId + ) external view whenModuleIsRegistered(stakingModuleId) returns (uint256) { return stakingModuleLastDepositBlock; } @@ -80,4 +94,9 @@ contract StakingRouterMockForDepositSecurityModule is IStakingRouter { function setStakingModuleLastDepositBlock(uint256 value) external { stakingModuleLastDepositBlock = value; } + + modifier whenModuleIsRegistered(uint256 _stakingModuleId) { + if (!hasStakingModule(_stakingModuleId)) revert StakingModuleUnregistered(); + _; + } } diff --git a/test/0.8.9/deposit-security-module.test.js b/test/0.8.9/deposit-security-module.test.js index 4f0b45678..1e6ccc061 100644 --- a/test/0.8.9/deposit-security-module.test.js +++ b/test/0.8.9/deposit-security-module.test.js @@ -1045,10 +1045,17 @@ contract('DepositSecurityModule', ([owner, stranger, guardian]) => { it('false if unknown staking module id', async () => { await depositSecurityModule.addGuardian(GUARDIAN1, 1, { from: owner }) - assert.equal( - await stakingRouterMock.getStakingModuleIsDepositsPaused(STAKING_MODULE + 1), - false, - 'invariant failed: isPaused' + await assert.reverts( + stakingRouterMock.getStakingModuleIsDepositsPaused(STAKING_MODULE + 1), + `StakingModuleUnregistered()` + ) + await assert.reverts( + stakingRouterMock.getStakingModuleIsActive(STAKING_MODULE + 1), + `StakingModuleUnregistered()` + ) + await assert.reverts( + stakingRouterMock.getStakingModuleLastDepositBlock(STAKING_MODULE + 1), + `StakingModuleUnregistered()` ) assert.isTrue((await depositSecurityModule.getGuardianQuorum()) > 0, 'invariant failed: quorum > 0') diff --git a/test/0.8.9/staking-router/staking-router.test.js b/test/0.8.9/staking-router/staking-router.test.js index 423df7a75..c79725622 100644 --- a/test/0.8.9/staking-router/staking-router.test.js +++ b/test/0.8.9/staking-router/staking-router.test.js @@ -227,6 +227,15 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { await revert() }) + it('reverts if module is unregistered', async () => { + await assert.reverts(router.getStakingModuleIsActive(123), `StakingModuleUnregistered()`) + await assert.reverts(router.getStakingModuleLastDepositBlock(123), `StakingModuleUnregistered()`) + await assert.reverts(router.getStakingModuleIsDepositsPaused(123), `StakingModuleUnregistered()`) + await assert.reverts(router.getStakingModuleNonce(123), `StakingModuleUnregistered()`) + await assert.reverts(router.getStakingModuleIsStopped(123), `StakingModuleUnregistered()`) + await assert.reverts(router.getStakingModuleStatus(123), `StakingModuleUnregistered()`) + }) + it('reverts if module address exists', async () => { await assert.revertsWithCustomError( router.addStakingModule('Test', stakingModule.address, 100, 1000, 2000, { from: appManager }), From 87720a60a7d5e687ebbfc8b113eba54795f4a6e5 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Fri, 17 Mar 2023 14:04:01 +0700 Subject: [PATCH 20/66] test: NodeOperatorsRegistry happy path few more steps --- ...node-operators-registry-happy-path.test.js | 87 +++++++++++++++++-- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index 5c044390f..404442d42 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -249,7 +249,9 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us const [curated] = await stakingRouter.getStakingModules() const stakesDeposited = 4 - await web3.eth.sendTransaction({ to: lido.address, from: user1, value: ETH(32 * stakesDeposited) }) + const depositedValue = ETH(32 * stakesDeposited) + + await web3.eth.sendTransaction({ to: lido.address, from: user1, value: depositedValue }) const block = await web3.eth.getBlock('latest') const keysOpIndex = await nor.getKeysOpIndex() @@ -281,11 +283,15 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us assert.equals(stakingModuleSummary.totalDepositedValidators, stateTotaldeposited) assert.equals(stakingModuleSummary.depositableValidatorsCount, 3) + const operator1 = await nor.getNodeOperator(Operator1.id, true) const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) + assert.equals(operator1.usedSigningKeys, 3) assert.equals(summaryOperator1.totalDepositedValidators, 3) assert.equals(summaryOperator1.depositableValidatorsCount, 3) + const operator2 = await nor.getNodeOperator(Operator2.id, true) const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) + assert.equals(operator2.usedSigningKeys, 1) assert.equals(summaryOperator2.totalDepositedValidators, 1) assert.equals(summaryOperator2.depositableValidatorsCount, 0) }) @@ -380,7 +386,9 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us { abi: NodeOperatorsRegistry._json.abi } ) + const operator1 = await nor.getNodeOperator(Operator1.id, true) const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) + assert.equals(operator1.stoppedValidators, 2) assert.equals(summaryOperator1.totalExitedValidators, 2) const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) @@ -390,7 +398,9 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us // TODO: assert emits RewardsDistributed // TODO: assert rewards was transfered with NOR._distributeRewards() // TODO: assert rewards was transfered to operators for his exited validators - // TODO: assert TargetLimit was changed to zero if any key stucked. ... -> NOR._updateSummaryMaxValidatorsCount() + + // TODO: assert TargetLimit changes to zero if any key stucked. ... -> NOR._updateSummaryMaxValidatorsCount() + // assert.equals(summaryOperator2.targetValidatorsCount, 0) }) it('unsafeSetExitedValidatorsCount', async () => { @@ -422,14 +432,75 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us // TODO: assert rewards was transfered to operators for his exited validators }) - // TODO: TargetLimit allows to deposit after exit + /** + * TODO: TargetLimit allows to deposit after exit + */ + + it('Disable TargetLimit', async () => { + const [curated] = await stakingRouter.getStakingModules() + const operatorId = Operator2.id + + let summary = await nor.getNodeOperatorSummary(operatorId) + assert.equals(summary.isTargetLimitActive, true) + assert.equals(summary.targetValidatorsCount, 1) + assert.equals(summary.depositableValidatorsCount, 0) + + // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() + const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorId, false, 0, { + from: voting.address, + }) + + summary = await nor.getNodeOperatorSummary(operatorId) + assert.equals(summary.isTargetLimitActive, false) + assert.equals(summary.targetValidatorsCount, 0) + // TODO: assert.equals(summary.depositableValidatorsCount, ?) - // TODO: disable TargetLimit and try to deposit again + assert.emits( + tx, + 'TargetValidatorsCountChanged', + { nodeOperatorId: operatorId, targetValidatorsCount: 0 }, + { abi: NodeOperatorsRegistry._json.abi } + ) + }) + + /** + * Deposit again after disabling TargetLimit + */ + + it('Remove one signing key', async () => { + const operatorId = Operator1.id + + const operatorBefore = await nor.getNodeOperator(operatorId, true) + const keysCountBefore = await nor.getTotalSigningKeyCount(operatorId) + const unusedKeysCountBefore = await nor.getUnusedSigningKeyCount(operatorId) + + const keyIdxToRemove = +operatorBefore.usedSigningKeys + const keyBefore = await nor.getSigningKey(operatorId, keyIdxToRemove) + + await nor.removeSigningKey(operatorId, keyIdxToRemove, { from: voting.address }) + + const operatorAfter = await nor.getNodeOperator(operatorId, true) + const keysCountAfter = await nor.getTotalSigningKeyCount(operatorId) + const unusedKeysCountAfter = await nor.getUnusedSigningKeyCount(operatorId) + const keyAfter = await nor.getSigningKey(operatorId, keyIdxToRemove) + + assert.equals(+operatorBefore.totalSigningKeys - 1, +operatorAfter.totalSigningKeys) + assert.equals(+keysCountBefore - 1, +keysCountAfter) + assert.equals(+unusedKeysCountBefore - 1, +unusedKeysCountAfter) + assert.notEqual(keyBefore.key, keyAfter.key) + assert.notEqual(keyBefore.depositSignature, keyAfter.depositSignature) + + // TODO: Why operatorBefore is 6 here?? + // console.log(+operatorBefore.stakingLimit) + // console.log(+operatorAfter.stakingLimit) + + // TODO: assert that staking limit was changed + // TODO: check if we need to assert target limit + }) - // TODO: NOR.removeSigningKey() - // NOR.removeSigningKeys() - // assert that staking limit was changed - // check if we need to assert target limit + // TODO: + // it('Remove multiple signing keys', async () => { + // }) // TODO: NOR....() // Deactivate Operator that was in use before From 585f866775f2dfbb8256e74c00b3e43255ed4eb5 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Fri, 17 Mar 2023 15:16:12 +0700 Subject: [PATCH 21/66] chore: more explicit comments --- .../oracle/MockLidoForAccountingOracle.sol | 39 +++++++++++++------ test/0.4.24/legacy-oracle.test.js | 4 +- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol b/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol index e665ff8cb..958a97a2d 100644 --- a/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol +++ b/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.9; -import { ILido } from "../../oracle/AccountingOracle.sol"; +import { ILido } from '../../oracle/AccountingOracle.sol'; interface IPostTokenRebaseReceiver { function handlePostTokenRebase( @@ -17,7 +17,6 @@ interface IPostTokenRebaseReceiver { } contract MockLidoForAccountingOracle is ILido { - address legacyOracle; struct HandleOracleReportLastCall { @@ -35,7 +34,11 @@ contract MockLidoForAccountingOracle is ILido { HandleOracleReportLastCall internal _handleOracleReportLastCall; - function getLastCall_handleOracleReport() external view returns (HandleOracleReportLastCall memory) { + function getLastCall_handleOracleReport() + external + view + returns (HandleOracleReportLastCall memory) + { return _handleOracleReportLastCall; } @@ -58,19 +61,33 @@ contract MockLidoForAccountingOracle is ILido { uint256[] calldata withdrawalFinalizationBatches, uint256 simulatedShareRate ) external { - _handleOracleReportLastCall.currentReportTimestamp = currentReportTimestamp; - _handleOracleReportLastCall.secondsElapsedSinceLastReport = secondsElapsedSinceLastReport; + _handleOracleReportLastCall + .currentReportTimestamp = currentReportTimestamp; + _handleOracleReportLastCall + .secondsElapsedSinceLastReport = secondsElapsedSinceLastReport; _handleOracleReportLastCall.numValidators = numValidators; _handleOracleReportLastCall.clBalance = clBalance; - _handleOracleReportLastCall.withdrawalVaultBalance = withdrawalVaultBalance; - _handleOracleReportLastCall.elRewardsVaultBalance = elRewardsVaultBalance; - _handleOracleReportLastCall.sharesRequestedToBurn = sharesRequestedToBurn; - _handleOracleReportLastCall.withdrawalFinalizationBatches = withdrawalFinalizationBatches; + _handleOracleReportLastCall + .withdrawalVaultBalance = withdrawalVaultBalance; + _handleOracleReportLastCall + .elRewardsVaultBalance = elRewardsVaultBalance; + _handleOracleReportLastCall + .sharesRequestedToBurn = sharesRequestedToBurn; + _handleOracleReportLastCall + .withdrawalFinalizationBatches = withdrawalFinalizationBatches; _handleOracleReportLastCall.simulatedShareRate = simulatedShareRate; ++_handleOracleReportLastCall.callCount; - if(legacyOracle != address(0)){ - IPostTokenRebaseReceiver(legacyOracle).handlePostTokenRebase(currentReportTimestamp,secondsElapsedSinceLastReport,0,0,1,1,1); + if (legacyOracle != address(0)) { + IPostTokenRebaseReceiver(legacyOracle).handlePostTokenRebase( + currentReportTimestamp /* IGNORED reportTimestamp */, + secondsElapsedSinceLastReport /* timeElapsed */, + 0 /* IGNORED preTotalShares */, + 0 /* preTotalEther */, + 1 /* postTotalShares */, + 1 /* postTotalEther */, + 1 /* IGNORED sharesMintedAsFees */ + ); } } } diff --git a/test/0.4.24/legacy-oracle.test.js b/test/0.4.24/legacy-oracle.test.js index 55897682a..b1dd527dd 100644 --- a/test/0.4.24/legacy-oracle.test.js +++ b/test/0.4.24/legacy-oracle.test.js @@ -227,8 +227,10 @@ contract('LegacyOracle', ([admin, stranger]) => { await deployedInfra.consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: admin }) const oracleVersion = +(await deployedInfra.oracle.getContractVersion()) - // first report since migration has off timeElapsed because lastProcessingRefSlot is calculated per legacy frame ref + // first report since migration has timeElapsed 1 slot off because lastProcessingRefSlot is calculated per legacy frame ref + // calculated as in code const timeElapsed = (+refSlot - +(await deployedInfra.oracle.getLastProcessingRefSlot())) * SECONDS_PER_SLOT + assert.equals(timeElapsed, SECONDS_PER_FRAME - SECONDS_PER_SLOT) const tx = await deployedInfra.oracle.submitReportData(reportItems, oracleVersion, { from: admin }) From c1d1816d80117b54b9dbd71c88fd154ef8bfcb41 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Fri, 17 Mar 2023 17:53:32 +0700 Subject: [PATCH 22/66] fix: linting --- .../0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol b/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol index 958a97a2d..80bc498b4 100644 --- a/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol +++ b/contracts/0.8.9/test_helpers/oracle/MockLidoForAccountingOracle.sol @@ -2,7 +2,7 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.9; -import { ILido } from '../../oracle/AccountingOracle.sol'; +import { ILido } from "../../oracle/AccountingOracle.sol"; interface IPostTokenRebaseReceiver { function handlePostTokenRebase( From 2818522c9e214fb13281446f80acb5fb6e65e6f6 Mon Sep 17 00:00:00 2001 From: Sam Kozin Date: Fri, 17 Mar 2023 15:12:35 +0200 Subject: [PATCH 23/66] oracle: remove an erroneous and excess check the requirement for the number of exited validators to not decrease is already checked in the staking router, and it's compared per module which is a more strict check --- contracts/0.8.9/StakingRouter.sol | 78 ++++++++++++++++++- contracts/0.8.9/oracle/AccountingOracle.sol | 27 ++----- .../MockStakingRouterForAccountingOracle.sol | 14 +++- ...counting-oracle-submit-report-data.test.js | 21 ----- 4 files changed, 97 insertions(+), 43 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 8361273eb..064d8b199 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -299,20 +299,63 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } + /// @notice Updates total numbers of exited validators for staking modules with the specified + /// module ids. + /// + /// @param _stakingModuleIds Ids of the staking modules to be updated. + /// @param _exitedValidatorsCounts New counts of exited validators for the specified staking modules. + /// + /// @return The total increase in the aggregate number of exited validators accross all updated modules. + /// + /// The total numbers are stored in the staking router and can differ from the totals obtained by calling + /// `IStakingModule.getStakingModuleSummary()`. The overall process of updating validator counts is the following: + /// + /// 1. In the first data submission phase, the oracle calls `updateExitedValidatorsCountByStakingModule` on the + /// staking router, passing the totals by module. The staking router stores these totals and uses them to + /// distribute new stake and staking fees between the modules. There can only be single call of this function + /// per oracle reporting frame. + /// + /// 2. In the first part of the second data submittion phase, the oracle calls + /// `StakingRouter.reportStakingModuleStuckValidatorsCountByNodeOperator` on the staking router which passes the + /// counts by node operator to the staking module by calling `IStakingModule.updateStuckValidatorsCount`. + /// This can be done multiple times for the same module, passing data for different subsets of node operators. + /// + /// 3. In the second part of the second data submittion phase, the oracle calls + /// `StakingRouter.reportStakingModuleExitedValidatorsCountByNodeOperator` on the staking router which passes + /// the counts by node operator to the staking module by calling `IStakingModule.updateExitedValidatorsCount`. + /// This can be done multiple times for the same module, passing data for different subsets of node + /// operators. + /// + /// 4. At the end of the second data submission phase, it's expected for the aggragate exited validators count + /// accross all module's node operators (stored in the module) to match the total count for this module + /// (stored in the staking router). However, it might happen that the second phase of data submission doesn't + /// finish until the new oracle reporting frame is started, in which case staking router will emit a warning + /// event `StakingModuleExitedValidatorsIncompleteReporting` when the first data submission phase is performed + /// for a new reporting frame. This condition will result in the staking module having an incomplete data about + /// the exited and maybe stuck validator counts during the whole reporting frame. Handling this condition is + /// the responsibility of each staking module. + /// + /// 5. When the second reporting phase is finshed, i.e. when the oracle submitted the complete data on the stuck + /// and exited validator counts per node operator for the current reporting frame, the oracle calls + /// `StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished` which, in turn, calls + /// `IStakingModule.onExitedAndStuckValidatorsCountsUpdated` on all modules. + /// function updateExitedValidatorsCountByStakingModule( uint256[] calldata _stakingModuleIds, uint256[] calldata _exitedValidatorsCounts ) external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) + returns (uint256) { if (_stakingModuleIds.length != _exitedValidatorsCounts.length) { revert ArraysLengthMismatch(_stakingModuleIds.length, _exitedValidatorsCounts.length); } - uint256 stakingModuleId; + uint256 newlyExitedValidatorsCount; + for (uint256 i = 0; i < _stakingModuleIds.length; ) { - stakingModuleId = _stakingModuleIds[i]; + uint256 stakingModuleId = _stakingModuleIds[i]; StakingModule storage stakingModule = _getStakingModuleById(stakingModuleId); uint256 prevReportedExitedValidatorsCount = stakingModule.exitedValidatorsCount; @@ -320,6 +363,8 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version revert ExitedValidatorsCountCannotDecrease(); } + newlyExitedValidatorsCount += _exitedValidatorsCounts[i] - prevReportedExitedValidatorsCount; + ( uint256 totalExitedValidatorsCount, /* uint256 totalDepositedValidators */, @@ -337,8 +382,20 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version stakingModule.exitedValidatorsCount = _exitedValidatorsCounts[i]; unchecked { ++i; } } + + return newlyExitedValidatorsCount; } + /// @notice Updates exited validators counts per node operator for the staking module with + /// the specified id. + /// + /// See the docs for `updateExitedValidatorsCountByStakingModule` for the description of the + /// overall update process. + /// + /// @param _stakingModuleId The id of the staking modules to be updated. + /// @param _nodeOperatorIds Ids of the node operators to be updated. + /// @param _exitedValidatorsCounts New counts of exited validators for the specified node operators. + /// function reportStakingModuleExitedValidatorsCountByNodeOperator( uint256 _stakingModuleId, bytes calldata _nodeOperatorIds, @@ -440,6 +497,16 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } + /// @notice Updates stuck validators counts per node operator for the staking module with + /// the specified id. + /// + /// See the docs for `updateExitedValidatorsCountByStakingModule` for the description of the + /// overall update process. + /// + /// @param _stakingModuleId The id of the staking modules to be updated. + /// @param _nodeOperatorIds Ids of the node operators to be updated. + /// @param _stuckValidatorsCounts New counts of stuck validators for the specified node operators. + /// function reportStakingModuleStuckValidatorsCountByNodeOperator( uint256 _stakingModuleId, bytes calldata _nodeOperatorIds, @@ -453,6 +520,13 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version IStakingModule(moduleAddr).updateStuckValidatorsCount(_nodeOperatorIds, _stuckValidatorsCounts); } + /// @notice Called by the oracle when the second phase of data reporting finishes, i.e. when the + /// oracle submitted the complete data on the stuck and exited validator counts per node operator + /// for the current reporting frame. + /// + /// See the docs for `updateExitedValidatorsCountByStakingModule` for the description of the + /// overall update process. + /// function onValidatorsCountsByNodeOperatorReportingFinished() external onlyRole(REPORT_EXITED_VALIDATORS_ROLE) diff --git a/contracts/0.8.9/oracle/AccountingOracle.sol b/contracts/0.8.9/oracle/AccountingOracle.sol index b9c8055cb..8f38f82bd 100644 --- a/contracts/0.8.9/oracle/AccountingOracle.sol +++ b/contracts/0.8.9/oracle/AccountingOracle.sol @@ -57,12 +57,10 @@ interface IOracleReportSanityChecker { } interface IStakingRouter { - function getExitedValidatorsCountAcrossAllModules() external view returns (uint256); - function updateExitedValidatorsCountByStakingModule( uint256[] calldata moduleIds, uint256[] calldata exitedValidatorsCounts - ) external; + ) external returns (uint256); function reportStakingModuleExitedValidatorsCountByNodeOperator( uint256 stakingModuleId, @@ -96,7 +94,6 @@ contract AccountingOracle is BaseOracle { error IncorrectOracleMigration(uint256 code); error SenderNotAllowed(); error InvalidExitedValidatorsData(); - error NumExitedValidatorsCannotDecrease(); error UnsupportedExtraDataFormat(uint256 format); error UnsupportedExtraDataType(uint256 itemIndex, uint256 dataType); error CannotSubmitExtraDataBeforeMainData(); @@ -657,32 +654,24 @@ contract AccountingOracle is BaseOracle { unchecked { ++i; } } - uint256 exitedValidators = 0; for (uint256 i = 0; i < stakingModuleIds.length;) { if (numExitedValidatorsByStakingModule[i] == 0) { revert InvalidExitedValidatorsData(); - } else { - exitedValidators += numExitedValidatorsByStakingModule[i]; } unchecked { ++i; } } - uint256 prevExitedValidators = stakingRouter.getExitedValidatorsCountAcrossAllModules(); - if (exitedValidators < prevExitedValidators) { - revert NumExitedValidatorsCannotDecrease(); - } + uint256 newlyExitedValidatorsCount = stakingRouter.updateExitedValidatorsCountByStakingModule( + stakingModuleIds, + numExitedValidatorsByStakingModule + ); - uint256 exitedValidatorsPerDay = - (exitedValidators - prevExitedValidators) * (1 days) / + uint256 exitedValidatorsRatePerDay = + newlyExitedValidatorsCount * (1 days) / (SECONDS_PER_SLOT * slotsElapsed); IOracleReportSanityChecker(LOCATOR.oracleReportSanityChecker()) - .checkExitedValidatorsRatePerDay(exitedValidatorsPerDay); - - stakingRouter.updateExitedValidatorsCountByStakingModule( - stakingModuleIds, - numExitedValidatorsByStakingModule - ); + .checkExitedValidatorsRatePerDay(exitedValidatorsRatePerDay); } function _submitReportExtraDataEmpty() internal { diff --git a/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol b/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol index 0efb0d2f4..3fadda5a9 100644 --- a/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol +++ b/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol @@ -19,6 +19,8 @@ contract MockStakingRouterForAccountingOracle is IStakingRouter { bytes keysCounts; } + mapping(uint256 => uint256) internal _exitedKeysCountsByModuleId; + uint256 internal _exitedKeysCountAcrossAllModules; UpdateExitedKeysByModuleCallData internal _lastCall_updateExitedKeysByModule; @@ -57,10 +59,20 @@ contract MockStakingRouterForAccountingOracle is IStakingRouter { function updateExitedValidatorsCountByStakingModule( uint256[] calldata moduleIds, uint256[] calldata exitedKeysCounts - ) external { + ) external returns (uint256) { _lastCall_updateExitedKeysByModule.moduleIds = moduleIds; _lastCall_updateExitedKeysByModule.exitedKeysCounts = exitedKeysCounts; ++_lastCall_updateExitedKeysByModule.callCount; + + uint256 newlyExitedValidatorsCount; + + for (uint256 i = 0; i < moduleIds.length; ++i) { + uint256 moduleId = moduleIds[i]; + newlyExitedValidatorsCount += exitedKeysCounts[i] - _exitedKeysCountsByModuleId[moduleId]; + _exitedKeysCountsByModuleId[moduleId] = exitedKeysCounts[i]; + } + + return newlyExitedValidatorsCount; } function reportStakingModuleExitedValidatorsCountByNodeOperator( diff --git a/test/0.8.9/oracle/accounting-oracle-submit-report-data.test.js b/test/0.8.9/oracle/accounting-oracle-submit-report-data.test.js index 7778696fb..fb91dc5fa 100644 --- a/test/0.8.9/oracle/accounting-oracle-submit-report-data.test.js +++ b/test/0.8.9/oracle/accounting-oracle-submit-report-data.test.js @@ -379,27 +379,6 @@ contract('AccountingOracle', ([admin, member1]) => { ) }) - it('reverts with NumExitedValidatorsCannotDecrease if total count of exited validators less then previous exited number', async () => { - const totalExitedValidators = reportFields.numExitedValidatorsByStakingModule.reduce( - (sum, curr) => sum + curr, - 0 - ) - await mockStakingRouter.setExitedKeysCountAcrossAllModules(totalExitedValidators + 1) - await assert.reverts( - oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), - 'NumExitedValidatorsCannotDecrease()' - ) - }) - - it('does not reverts with NumExitedValidatorsCannotDecrease if total count of exited validators equals to previous exited number', async () => { - const totalExitedValidators = reportFields.numExitedValidatorsByStakingModule.reduce( - (sum, curr) => sum + curr, - 0 - ) - await mockStakingRouter.setExitedKeysCountAcrossAllModules(totalExitedValidators) - await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) - }) - it('reverts with ExitedValidatorsLimitExceeded if exited validators rate limit will be reached', async () => { // Really simple test here for now // TODO: Come up with more tests for better coverage of edge-case scenarios that can be accrued From 35d7327aac0374daa6e48d28bd0305b788242058 Mon Sep 17 00:00:00 2001 From: Sam Kozin Date: Fri, 17 Mar 2023 15:16:28 +0200 Subject: [PATCH 24/66] staking router: remove a fn that's not needed anymore --- contracts/0.8.9/StakingRouter.sol | 10 --------- .../MockStakingRouterForAccountingOracle.sol | 9 -------- .../staking-router-keys-reporting.test.js | 22 +++---------------- 3 files changed, 3 insertions(+), 38 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 064d8b199..5303a833e 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -553,16 +553,6 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version } } - function getExitedValidatorsCountAcrossAllModules() external view returns (uint256) { - uint256 stakingModulesCount = getStakingModulesCount(); - uint256 exitedValidatorsCount = 0; - for (uint256 i; i < stakingModulesCount; ) { - exitedValidatorsCount += _getStakingModuleByIndex(i).exitedValidatorsCount; - unchecked { ++i; } - } - return exitedValidatorsCount; - } - /** * @notice Returns all registered staking modules */ diff --git a/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol b/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol index 3fadda5a9..0b9475322 100644 --- a/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol +++ b/contracts/0.8.9/test_helpers/oracle/MockStakingRouterForAccountingOracle.sol @@ -21,7 +21,6 @@ contract MockStakingRouterForAccountingOracle is IStakingRouter { mapping(uint256 => uint256) internal _exitedKeysCountsByModuleId; - uint256 internal _exitedKeysCountAcrossAllModules; UpdateExitedKeysByModuleCallData internal _lastCall_updateExitedKeysByModule; ReportKeysByNodeOperatorCallData[] public calls_reportExitedKeysByNodeOperator; @@ -30,10 +29,6 @@ contract MockStakingRouterForAccountingOracle is IStakingRouter { uint256 public totalCalls_onValidatorsCountsByNodeOperatorReportingFinished; - function setExitedKeysCountAcrossAllModules(uint256 count) external { - _exitedKeysCountAcrossAllModules = count; - } - function lastCall_updateExitedKeysByModule() external view returns (UpdateExitedKeysByModuleCallData memory) { @@ -52,10 +47,6 @@ contract MockStakingRouterForAccountingOracle is IStakingRouter { /// IStakingRouter /// - function getExitedValidatorsCountAcrossAllModules() external view returns (uint256) { - return _exitedKeysCountAcrossAllModules; - } - function updateExitedValidatorsCountByStakingModule( uint256[] calldata moduleIds, uint256[] calldata exitedKeysCounts diff --git a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js index 7f7a825f4..1a5bbd838 100644 --- a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js +++ b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js @@ -78,9 +78,6 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { it('initially, router assumes no staking modules have exited validators', async () => { const info = await router.getStakingModule(module1Id) assert.equals(info.exitedValidatorsCount, 0) - - const totalExited = await router.getExitedValidatorsCountAcrossAllModules() - assert.equals(totalExited, 0) }) it('reverts total exited validators without REPORT_EXITED_VALIDATORS_ROLE', async () => { @@ -121,11 +118,6 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { assert.equals(info.exitedValidatorsCount, 3) }) - it('exited validators count accross all modules gets updated', async () => { - const totalExited = await router.getExitedValidatorsCountAcrossAllModules() - assert.equals(totalExited, 3) - }) - it('no functions were called on the module', async () => { const callInfo = await getCallInfo(module1) assert.equal(callInfo.updateStuckValidatorsCount.callCount, 0) @@ -405,9 +397,9 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { await module1.setTotalExitedValidatorsCount(2) }) - it(`router's view on exited validators count accross all modules stays the same`, async () => { - const totalExited = await router.getExitedValidatorsCountAcrossAllModules() - assert.equals(totalExited, 3) + it(`router's view on exited validators count stays the same`, async () => { + const info = await router.getStakingModule(module1Id) + assert.equals(info.exitedValidatorsCount, 3) }) it(`calling onValidatorsCountsByNodeOperatorReportingFinished still doesn't call anything on the module`, async () => { @@ -536,9 +528,6 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { const info2 = await router.getStakingModule(moduleIds[1]) assert.equals(info2.exitedValidatorsCount, 0) - - const totalExited = await router.getExitedValidatorsCountAcrossAllModules() - assert.equals(totalExited, 0) }) it('reporting 3 exited keys total for module 1 and 2 exited keys total for module 2', async () => { @@ -553,11 +542,6 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { assert.equals(info2.exitedValidatorsCount, 2) }) - it('exited validators count accross all modules gets updated', async () => { - const totalExited = await router.getExitedValidatorsCountAcrossAllModules() - assert.equals(totalExited, 5) - }) - it('revert on decreased exited keys for modules', async () => { await assert.reverts( router.updateExitedValidatorsCountByStakingModule(moduleIds, [2, 1], { from: admin }), From f523e46eb819eae9b6df9dbae01d95f4be219f28 Mon Sep 17 00:00:00 2001 From: Sam Kozin Date: Fri, 17 Mar 2023 15:24:20 +0200 Subject: [PATCH 25/66] abi: update --- lib/abi/AccountingOracle.json | 2 +- lib/abi/IStakingRouter.json | 2 +- lib/abi/StakingRouter.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/abi/AccountingOracle.json b/lib/abi/AccountingOracle.json index 9a7d92f0b..5f583046f 100644 --- a/lib/abi/AccountingOracle.json +++ b/lib/abi/AccountingOracle.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"lidoLocator","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"AdminCannotBeZero","type":"error"},{"inputs":[],"name":"CannotSubmitExtraDataBeforeMainData","type":"error"},{"inputs":[],"name":"ExtraDataAlreadyProcessed","type":"error"},{"inputs":[],"name":"ExtraDataHashCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataItemsCountCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataListOnlySupportsSingleTx","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"IncorrectOracleMigration","type":"error"},{"inputs":[{"internalType":"uint256","name":"initialRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"InitialRefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidExitedValidatorsData","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataItem","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataSortOrder","type":"error"},{"inputs":[],"name":"LegacyOracleCannotBeZero","type":"error"},{"inputs":[],"name":"LidoCannotBeZero","type":"error"},{"inputs":[],"name":"LidoLocatorCannotBeZero","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"NumExitedValidatorsCannotDecrease","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[],"name":"RefSlotAlreadyProcessing","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"SenderNotAllowed","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedFormat","type":"uint256"},{"internalType":"uint256","name":"receivedFormat","type":"uint256"}],"name":"UnexpectedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedExtraDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedIndex","type":"uint256"},{"internalType":"uint256","name":"receivedIndex","type":"uint256"}],"name":"UnexpectedExtraDataIndex","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedCount","type":"uint256"},{"internalType":"uint256","name":"receivedCount","type":"uint256"}],"name":"UnexpectedExtraDataItemsCount","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[{"internalType":"uint256","name":"format","type":"uint256"}],"name":"UnsupportedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"dataType","type":"uint256"}],"name":"UnsupportedExtraDataType","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsProcessed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"ExtraDataSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"processedItemsCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"WarnExtraDataIncompleteProcessing","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_EMPTY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_LIST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_EXITED_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_STUCK_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LEGACY_ORACLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBMIT_DATA_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProcessingState","outputs":[{"components":[{"internalType":"uint256","name":"currentFrameRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bytes32","name":"mainDataHash","type":"bytes32"},{"internalType":"bool","name":"mainDataSubmitted","type":"bool"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bool","name":"extraDataSubmitted","type":"bool"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"},{"internalType":"uint256","name":"extraDataItemsSubmitted","type":"uint256"}],"internalType":"struct AccountingOracle.ProcessingState","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"lastProcessingRefSlot","type":"uint256"}],"name":"initializeWithoutMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"numValidators","type":"uint256"},{"internalType":"uint256","name":"clBalanceGwei","type":"uint256"},{"internalType":"uint256[]","name":"stakingModuleIdsWithNewlyExitedValidators","type":"uint256[]"},{"internalType":"uint256[]","name":"numExitedValidatorsByStakingModule","type":"uint256[]"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256[]","name":"withdrawalFinalizationBatches","type":"uint256[]"},{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"bool","name":"isBunkerMode","type":"bool"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"}],"internalType":"struct AccountingOracle.ReportData","name":"data","type":"tuple"},{"internalType":"uint256","name":"contractVersion","type":"uint256"}],"name":"submitReportData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"submitReportExtraDataEmpty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"items","type":"bytes"}],"name":"submitReportExtraDataList","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 +[{"inputs":[{"internalType":"address","name":"lidoLocator","type":"address"},{"internalType":"address","name":"lido","type":"address"},{"internalType":"address","name":"legacyOracle","type":"address"},{"internalType":"uint256","name":"secondsPerSlot","type":"uint256"},{"internalType":"uint256","name":"genesisTime","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AddressCannotBeSame","type":"error"},{"inputs":[],"name":"AddressCannotBeZero","type":"error"},{"inputs":[],"name":"AdminCannotBeZero","type":"error"},{"inputs":[],"name":"CannotSubmitExtraDataBeforeMainData","type":"error"},{"inputs":[],"name":"ExtraDataAlreadyProcessed","type":"error"},{"inputs":[],"name":"ExtraDataHashCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataItemsCountCannotBeZeroForNonEmptyData","type":"error"},{"inputs":[],"name":"ExtraDataListOnlySupportsSingleTx","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"IncorrectOracleMigration","type":"error"},{"inputs":[{"internalType":"uint256","name":"initialRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"InitialRefSlotCannotBeLessThanProcessingOne","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[],"name":"InvalidExitedValidatorsData","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataItem","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"}],"name":"InvalidExtraDataSortOrder","type":"error"},{"inputs":[],"name":"LegacyOracleCannotBeZero","type":"error"},{"inputs":[],"name":"LidoCannotBeZero","type":"error"},{"inputs":[],"name":"LidoLocatorCannotBeZero","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"OnlyConsensusContractCanSubmitReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"ProcessingDeadlineMissed","type":"error"},{"inputs":[],"name":"RefSlotAlreadyProcessing","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"prevRefSlot","type":"uint256"}],"name":"RefSlotCannotDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingRefSlot","type":"uint256"}],"name":"RefSlotMustBeGreaterThanProcessingOne","type":"error"},{"inputs":[],"name":"SenderNotAllowed","type":"error"},{"inputs":[],"name":"UnexpectedChainConfig","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedVersion","type":"uint256"},{"internalType":"uint256","name":"receivedVersion","type":"uint256"}],"name":"UnexpectedConsensusVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedFormat","type":"uint256"},{"internalType":"uint256","name":"receivedFormat","type":"uint256"}],"name":"UnexpectedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"bytes32","name":"consensusHash","type":"bytes32"},{"internalType":"bytes32","name":"receivedHash","type":"bytes32"}],"name":"UnexpectedExtraDataHash","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedIndex","type":"uint256"},{"internalType":"uint256","name":"receivedIndex","type":"uint256"}],"name":"UnexpectedExtraDataIndex","type":"error"},{"inputs":[{"internalType":"uint256","name":"expectedCount","type":"uint256"},{"internalType":"uint256","name":"receivedCount","type":"uint256"}],"name":"UnexpectedExtraDataItemsCount","type":"error"},{"inputs":[{"internalType":"uint256","name":"consensusRefSlot","type":"uint256"},{"internalType":"uint256","name":"dataRefSlot","type":"uint256"}],"name":"UnexpectedRefSlot","type":"error"},{"inputs":[{"internalType":"uint256","name":"format","type":"uint256"}],"name":"UnsupportedExtraDataFormat","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"dataType","type":"uint256"}],"name":"UnsupportedExtraDataType","type":"error"},{"inputs":[],"name":"VersionCannotBeSame","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"addr","type":"address"},{"indexed":true,"internalType":"address","name":"prevAddr","type":"address"}],"name":"ConsensusHashContractSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"version","type":"uint256"},{"indexed":true,"internalType":"uint256","name":"prevVersion","type":"uint256"}],"name":"ConsensusVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsProcessed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"ExtraDataSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"ProcessingStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"},{"indexed":false,"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"}],"name":"ReportSubmitted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"processedItemsCount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"itemsCount","type":"uint256"}],"name":"WarnExtraDataIncompleteProcessing","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"refSlot","type":"uint256"}],"name":"WarnProcessingMissed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_EMPTY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_FORMAT_LIST","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_EXITED_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EXTRA_DATA_TYPE_STUCK_VALIDATORS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"GENESIS_TIME","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LEGACY_ORACLE","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LIDO","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCATOR","outputs":[{"internalType":"contract ILidoLocator","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_CONTRACT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_CONSENSUS_VERSION_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SECONDS_PER_SLOT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SUBMIT_DATA_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusReport","outputs":[{"internalType":"bytes32","name":"hash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bool","name":"processingStarted","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getConsensusVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLastProcessingRefSlot","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getProcessingState","outputs":[{"components":[{"internalType":"uint256","name":"currentFrameRefSlot","type":"uint256"},{"internalType":"uint256","name":"processingDeadlineTime","type":"uint256"},{"internalType":"bytes32","name":"mainDataHash","type":"bytes32"},{"internalType":"bool","name":"mainDataSubmitted","type":"bool"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bool","name":"extraDataSubmitted","type":"bool"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"},{"internalType":"uint256","name":"extraDataItemsSubmitted","type":"uint256"}],"internalType":"struct AccountingOracle.ProcessingState","name":"result","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"admin","type":"address"},{"internalType":"address","name":"consensusContract","type":"address"},{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"lastProcessingRefSlot","type":"uint256"}],"name":"initializeWithoutMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"setConsensusContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"version","type":"uint256"}],"name":"setConsensusVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"reportHash","type":"bytes32"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"submitConsensusReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"consensusVersion","type":"uint256"},{"internalType":"uint256","name":"refSlot","type":"uint256"},{"internalType":"uint256","name":"numValidators","type":"uint256"},{"internalType":"uint256","name":"clBalanceGwei","type":"uint256"},{"internalType":"uint256[]","name":"stakingModuleIdsWithNewlyExitedValidators","type":"uint256[]"},{"internalType":"uint256[]","name":"numExitedValidatorsByStakingModule","type":"uint256[]"},{"internalType":"uint256","name":"withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256[]","name":"withdrawalFinalizationBatches","type":"uint256[]"},{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"bool","name":"isBunkerMode","type":"bool"},{"internalType":"uint256","name":"extraDataFormat","type":"uint256"},{"internalType":"bytes32","name":"extraDataHash","type":"bytes32"},{"internalType":"uint256","name":"extraDataItemsCount","type":"uint256"}],"internalType":"struct AccountingOracle.ReportData","name":"data","type":"tuple"},{"internalType":"uint256","name":"contractVersion","type":"uint256"}],"name":"submitReportData","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"submitReportExtraDataEmpty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"items","type":"bytes"}],"name":"submitReportExtraDataList","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/IStakingRouter.json b/lib/abi/IStakingRouter.json index 4cdff3a03..cc4b059f3 100644 --- a/lib/abi/IStakingRouter.json +++ b/lib/abi/IStakingRouter.json @@ -1 +1 @@ -[{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"moduleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"moduleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}] \ No newline at end of file diff --git a/lib/abi/StakingRouter.json b/lib/abi/StakingRouter.json index 265201b94..47a2c0110 100644 --- a/lib/abi/StakingRouter.json +++ b/lib/abi/StakingRouter.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file From e470fca0136b7ddf1c621df5de805640c288ffcf Mon Sep 17 00:00:00 2001 From: KRogLA Date: Fri, 17 Mar 2023 14:34:29 +0100 Subject: [PATCH 26/66] fix: jumbled constant names --- 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 82153ac11..c6f452893 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -611,7 +611,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { */ function _updateStuckValidatorsCount(uint256 _nodeOperatorId, uint64 _stuckValidatorsCount) internal { Packed64x4.Packed memory stuckPenaltyStats = _loadOperatorStuckPenaltyStats(_nodeOperatorId); - uint64 curStuckValidatorsCount = stuckPenaltyStats.get(REFUNDED_VALIDATORS_COUNT_OFFSET); + uint64 curStuckValidatorsCount = stuckPenaltyStats.get(STUCK_VALIDATORS_COUNT_OFFSET); if (_stuckValidatorsCount == curStuckValidatorsCount) return; Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); @@ -620,7 +620,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { <= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) - signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET) ); - uint64 curRefundedValidatorsCount = stuckPenaltyStats.get(STUCK_VALIDATORS_COUNT_OFFSET); + uint64 curRefundedValidatorsCount = stuckPenaltyStats.get(REFUNDED_VALIDATORS_COUNT_OFFSET); if (_stuckValidatorsCount <= curRefundedValidatorsCount && curStuckValidatorsCount > curRefundedValidatorsCount) { stuckPenaltyStats.set(STUCK_PENALTY_END_TIMESTAMP_OFFSET, uint64(block.timestamp + getStuckPenaltyDelay())); } From cde1fbdff842c16f8eded272ef135678dbc478f3 Mon Sep 17 00:00:00 2001 From: KRogLA Date: Fri, 17 Mar 2023 14:47:45 +0100 Subject: [PATCH 27/66] refactor: rename constants --- .../0.4.24/nos/NodeOperatorsRegistry.sol | 74 +++++++++---------- .../NodeOperatorsRegistryMock.sol | 28 +++---- 2 files changed, 51 insertions(+), 51 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index c6f452893..5a9fdaee9 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -86,13 +86,13 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { // SigningKeysStats /// @dev Operator's max validator keys count approved for deposit by the DAO - uint8 internal constant VETTED_KEYS_COUNT_OFFSET = 0; + uint8 internal constant TOTAL_VETTED_KEYS_COUNT_OFFSET = 0; /// @dev Number of keys in the EXITED state for this operator for all time - uint8 internal constant EXITED_KEYS_COUNT_OFFSET = 1; + uint8 internal constant TOTAL_EXITED_KEYS_COUNT_OFFSET = 1; /// @dev Total number of keys of this operator for all time uint8 internal constant TOTAL_KEYS_COUNT_OFFSET = 2; /// @dev Number of keys of this operator which were in DEPOSITED state for all time - uint8 internal constant DEPOSITED_KEYS_COUNT_OFFSET = 3; + uint8 internal constant TOTAL_DEPOSITED_KEYS_COUNT_OFFSET = 3; // TargetValidatorsStats /// @dev Flag enable/disable limiting target active validators count for operator @@ -227,9 +227,9 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { uint64 depositedSigningKeysCount; for (uint256 nodeOperatorId; nodeOperatorId < totalOperators; ++nodeOperatorId) { signingKeysStats = _loadOperatorSigningKeysStats(nodeOperatorId); - vettedSigningKeysCountBefore = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); + vettedSigningKeysCountBefore = signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET); totalSigningKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET); - depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + depositedSigningKeysCount = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); uint64 vettedSigningKeysCountAfter; if (!_nodeOperators[nodeOperatorId].active) { @@ -245,7 +245,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { } if (vettedSigningKeysCountBefore != vettedSigningKeysCountAfter) { - signingKeysStats.set(VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCountAfter); + signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCountAfter); _saveOperatorSigningKeysStats(nodeOperatorId, signingKeysStats); emit VettedSigningKeysCountChanged(nodeOperatorId, vettedSigningKeysCountAfter); } @@ -265,7 +265,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { summarySigningKeysStats.set( SUMMARY_EXITED_KEYS_COUNT_OFFSET, summarySigningKeysStats.get(SUMMARY_EXITED_KEYS_COUNT_OFFSET).add( - signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET) + signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET) ) ); summarySigningKeysStats.set( @@ -354,12 +354,12 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { emit NodeOperatorActiveSet(_nodeOperatorId, false); Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - uint64 vettedSigningKeysCount = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); - uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + uint64 vettedSigningKeysCount = signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET); + uint64 depositedSigningKeysCount = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); // reset vetted keys count to the deposited validators count if (vettedSigningKeysCount > depositedSigningKeysCount) { - signingKeysStats.set(VETTED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); + signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); emit VettedSigningKeysCountChanged(_nodeOperatorId, depositedSigningKeysCount); @@ -407,8 +407,8 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _onlyCorrectNodeOperatorState(getNodeOperatorIsActive(_nodeOperatorId)); Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - uint64 vettedSigningKeysCountBefore = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); - uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + uint64 vettedSigningKeysCountBefore = signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET); + uint64 depositedSigningKeysCount = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); uint64 totalSigningKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET); uint64 vettedSigningKeysCountAfter = uint64( @@ -421,7 +421,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { return; } - signingKeysStats.set(VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCountAfter); + signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCountAfter); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); emit VettedSigningKeysCountChanged(_nodeOperatorId, vettedSigningKeysCountAfter); @@ -563,15 +563,15 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); int64 totalExitedValidatorsDelta = - int64(_exitedValidatorsKeysCount) - int64(signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET)); + int64(_exitedValidatorsKeysCount) - int64(signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET)); if (totalExitedValidatorsDelta != 0) { - _requireValidRange(_exitedValidatorsKeysCount <= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET)); + _requireValidRange(_exitedValidatorsKeysCount <= signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET)); if (totalExitedValidatorsDelta < 0 && !_allowDecrease) { revert("EXITED_VALIDATORS_COUNT_DECREASED"); } - signingKeysStats.set(EXITED_KEYS_COUNT_OFFSET, _exitedValidatorsKeysCount); + signingKeysStats.set(TOTAL_EXITED_KEYS_COUNT_OFFSET, _exitedValidatorsKeysCount); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); emit ExitedSigningKeysCountChanged(_nodeOperatorId, _exitedValidatorsKeysCount); @@ -617,7 +617,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); _requireValidRange( _stuckValidatorsCount - <= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) - signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET) + <= signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET) - signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET) ); uint64 curRefundedValidatorsCount = stuckPenaltyStats.get(REFUNDED_VALIDATORS_COUNT_OFFSET); @@ -643,7 +643,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { if (_refundedValidatorsCount == curRefundedValidatorsCount) return; Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - _requireValidRange(_refundedValidatorsCount <= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET)); + _requireValidRange(_refundedValidatorsCount <= signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET)); uint64 curStuckValidatorsCount = stuckPenaltyStats.get(STUCK_VALIDATORS_COUNT_OFFSET); if (_refundedValidatorsCount >= curStuckValidatorsCount && curRefundedValidatorsCount < curStuckValidatorsCount) { @@ -702,13 +702,13 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { for (uint256 nodeOperatorId = _indexFrom; nodeOperatorId <= _indexTo; ++nodeOperatorId) { signingKeysStats = _loadOperatorSigningKeysStats(nodeOperatorId); - uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + uint64 depositedSigningKeysCount = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); trimmedKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET) - depositedSigningKeysCount; if (trimmedKeysCount == 0) continue; totalTrimmedKeysCount += trimmedKeysCount; signingKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, depositedSigningKeysCount); - signingKeysStats.set(VETTED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); + signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); _saveOperatorSigningKeysStats(nodeOperatorId, signingKeysStats); _updateSummaryMaxValidatorsCount(nodeOperatorId); @@ -766,8 +766,8 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); Packed64x4.Packed memory operatorTargetStats = _loadOperatorTargetValidatorsStats(_nodeOperatorId); - exitedSigningKeysCount = signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET); - depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + exitedSigningKeysCount = signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET); + depositedSigningKeysCount = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); maxSigningKeysCount = operatorTargetStats.get(MAX_VALIDATORS_COUNT_OFFSET); } @@ -775,9 +775,9 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); Packed64x4.Packed memory operatorTargetStats = _loadOperatorTargetValidatorsStats(_nodeOperatorId); - uint64 exitedSigningKeysCount = signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET); - uint64 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); - uint64 vettedSigningKeysCount = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); + uint64 exitedSigningKeysCount = signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET); + uint64 depositedSigningKeysCount = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); + uint64 vettedSigningKeysCount = signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET); uint64 oldMaxSigningKeysCount = operatorTargetStats.get(MAX_VALIDATORS_COUNT_OFFSET); uint64 newMaxSigningKeysCount = depositedSigningKeysCount; @@ -862,9 +862,9 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats; for (uint256 i; i < _nodeOperatorIds.length; ++i) { signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorIds[i]); - depositedSigningKeysCountBefore = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + depositedSigningKeysCountBefore = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); depositedSigningKeysCountAfter = - signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET) + uint64(_activeKeyCountsAfterAllocation[i]); + signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET) + uint64(_activeKeyCountsAfterAllocation[i]); keysCount = depositedSigningKeysCountAfter.sub(depositedSigningKeysCountBefore); if (keysCount == 0) continue; @@ -875,7 +875,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { loadedKeysCount += keysCount; emit DepositedSigningKeysCountChanged(_nodeOperatorIds[i], depositedSigningKeysCountAfter); - signingKeysStats.set(DEPOSITED_KEYS_COUNT_OFFSET, depositedSigningKeysCountAfter); + signingKeysStats.set(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, depositedSigningKeysCountAfter); _saveOperatorSigningKeysStats(_nodeOperatorIds[i], signingKeysStats); _updateSummaryMaxValidatorsCount(_nodeOperatorIds[i]); } @@ -916,10 +916,10 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - stakingLimit = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); - stoppedValidators = signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET); + stakingLimit = signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET); + stoppedValidators = signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET); totalSigningKeys = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET); - usedSigningKeys = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + usedSigningKeys = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); } /// @notice Returns the rewards distribution proportional to the effective stake for each node operator. @@ -943,7 +943,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(operatorId); uint256 activeValidatorsCount = - signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) - signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET); + signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET) - signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET); totalActiveValidatorsCount = totalActiveValidatorsCount.add(activeValidatorsCount); recipients[idx] = _nodeOperators[operatorId].rewardAddress; @@ -1067,17 +1067,17 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); uint256 totalSigningKeysCount = signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET); // comapring _fromIndex.add(_keysCount) <= totalSigningKeysCount is enough as totalSigningKeysCount is always less than MAX_UINT64 - _requireValidRange(_fromIndex >= signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) && _fromIndex.add(_keysCount) <= totalSigningKeysCount); + _requireValidRange(_fromIndex >= signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET) && _fromIndex.add(_keysCount) <= totalSigningKeysCount); totalSigningKeysCount = SIGNING_KEYS_MAPPING_NAME.removeKeysSigs(_nodeOperatorId, _fromIndex, _keysCount, totalSigningKeysCount); signingKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, uint64(totalSigningKeysCount)); emit TotalSigningKeysCountChanged(_nodeOperatorId, totalSigningKeysCount); - uint64 vettedSigningKeysCount = signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET); + uint64 vettedSigningKeysCount = signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET); if (_fromIndex < vettedSigningKeysCount) { // decreasing the staking limit so the key at _index can't be used anymore - signingKeysStats.set(VETTED_KEYS_COUNT_OFFSET, uint64(_fromIndex)); + signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, uint64(_fromIndex)); emit VettedSigningKeysCountChanged(_nodeOperatorId, _fromIndex); } _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); @@ -1106,7 +1106,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _onlyExistedNodeOperator(_nodeOperatorId); Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - return signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET) - signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + return signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET) - signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); } /// @notice Returns n-th signing key of the node operator #`_nodeOperatorId` @@ -1142,7 +1142,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); _requireValidRange(_offset.add(_limit) <= signingKeysStats.get(TOTAL_KEYS_COUNT_OFFSET)); - uint256 depositedSigningKeysCount = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + uint256 depositedSigningKeysCount = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); (pubkeys, signatures) = SigningKeys.initKeysSigsBuf(_limit); used = new bool[](_limit); diff --git a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol index c8a313d26..1fd6a7b88 100644 --- a/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol +++ b/contracts/0.4.24/test_helpers/NodeOperatorsRegistryMock.sol @@ -9,12 +9,12 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { function increaseNodeOperatorDepositedSigningKeysCount(uint256 _nodeOperatorId, uint64 _keysCount) external { Packed64x4.Packed memory signingKeysStats = _nodeOperators[_nodeOperatorId].signingKeysStats; - signingKeysStats.set(DEPOSITED_KEYS_COUNT_OFFSET, signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET) + _keysCount); + signingKeysStats.set(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET) + _keysCount); _nodeOperators[_nodeOperatorId].signingKeysStats = signingKeysStats; Packed64x4.Packed memory totalSigningKeysStats = _loadSummarySigningKeysStats(); totalSigningKeysStats.set( - DEPOSITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET).add(_keysCount) + TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET).add(_keysCount) ); _saveSummarySigningKeysStats(totalSigningKeysStats); @@ -26,34 +26,34 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { Packed64x4.Packed memory signingKeysStats; for (uint256 i; i < nodeOperatorsCount; ++i) { signingKeysStats = _loadOperatorSigningKeysStats(i); - testing_setDepositedSigningKeysCount(i, signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET)); + testing_setDepositedSigningKeysCount(i, signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET)); } } function testing_markAllKeysDeposited(uint256 _nodeOperatorId) external { _onlyExistedNodeOperator(_nodeOperatorId); Packed64x4.Packed memory signingKeysStats = _nodeOperators[_nodeOperatorId].signingKeysStats; - testing_setDepositedSigningKeysCount(_nodeOperatorId, signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET)); + testing_setDepositedSigningKeysCount(_nodeOperatorId, signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET)); } function testing_setDepositedSigningKeysCount(uint256 _nodeOperatorId, uint256 _depositedSigningKeysCount) public { _onlyExistedNodeOperator(_nodeOperatorId); // NodeOperator storage nodeOperator = _nodeOperators[_nodeOperatorId]; Packed64x4.Packed memory signingKeysStats = _loadOperatorSigningKeysStats(_nodeOperatorId); - uint64 depositedSigningKeysCountBefore = signingKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET); + uint64 depositedSigningKeysCountBefore = signingKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET); if (_depositedSigningKeysCount == depositedSigningKeysCountBefore) { return; } require( - _depositedSigningKeysCount <= signingKeysStats.get(VETTED_KEYS_COUNT_OFFSET), + _depositedSigningKeysCount <= signingKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET), "DEPOSITED_SIGNING_KEYS_COUNT_TOO_HIGH" ); require( - _depositedSigningKeysCount >= signingKeysStats.get(EXITED_KEYS_COUNT_OFFSET), "DEPOSITED_SIGNING_KEYS_COUNT_TOO_LOW" + _depositedSigningKeysCount >= signingKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET), "DEPOSITED_SIGNING_KEYS_COUNT_TOO_LOW" ); - signingKeysStats.set(DEPOSITED_KEYS_COUNT_OFFSET, uint64(_depositedSigningKeysCount)); + signingKeysStats.set(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, uint64(_depositedSigningKeysCount)); _saveOperatorSigningKeysStats(_nodeOperatorId, signingKeysStats); emit DepositedSigningKeysCountChanged(_nodeOperatorId, _depositedSigningKeysCount); @@ -87,9 +87,9 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { operator.rewardAddress = _rewardAddress; Packed64x4.Packed memory signingKeysStats; - signingKeysStats.set(DEPOSITED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); - signingKeysStats.set(VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCount); - signingKeysStats.set(EXITED_KEYS_COUNT_OFFSET, exitedSigningKeysCount); + signingKeysStats.set(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, depositedSigningKeysCount); + signingKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, vettedSigningKeysCount); + signingKeysStats.set(TOTAL_EXITED_KEYS_COUNT_OFFSET, exitedSigningKeysCount); signingKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, totalSigningKeysCount); operator.signingKeysStats = signingKeysStats; @@ -101,9 +101,9 @@ contract NodeOperatorsRegistryMock is NodeOperatorsRegistry { emit NodeOperatorAdded(id, _name, _rewardAddress, 0); Packed64x4.Packed memory totalSigningKeysStats = _loadSummarySigningKeysStats(); - totalSigningKeysStats.set(VETTED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(VETTED_KEYS_COUNT_OFFSET).add(vettedSigningKeysCount)); - totalSigningKeysStats.set(DEPOSITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(DEPOSITED_KEYS_COUNT_OFFSET).add(depositedSigningKeysCount)); - totalSigningKeysStats.set(EXITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(EXITED_KEYS_COUNT_OFFSET).add(exitedSigningKeysCount)); + totalSigningKeysStats.set(TOTAL_VETTED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(TOTAL_VETTED_KEYS_COUNT_OFFSET).add(vettedSigningKeysCount)); + totalSigningKeysStats.set(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(TOTAL_DEPOSITED_KEYS_COUNT_OFFSET).add(depositedSigningKeysCount)); + totalSigningKeysStats.set(TOTAL_EXITED_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(TOTAL_EXITED_KEYS_COUNT_OFFSET).add(exitedSigningKeysCount)); totalSigningKeysStats.set(TOTAL_KEYS_COUNT_OFFSET, totalSigningKeysStats.get(TOTAL_KEYS_COUNT_OFFSET).add(totalSigningKeysCount)); _saveSummarySigningKeysStats(totalSigningKeysStats); } From f82651b75f5ed4269f0033109154f25ab9f07729 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Sat, 18 Mar 2023 14:03:51 +0700 Subject: [PATCH 28/66] feat: updateExitedValidatorsCount result tests and result helper --- .../staking-router/report-exited-keys.test.js | 44 ++++++++++++++----- .../staking-router-keys-reporting.test.js | 26 ++++++++--- ...drawal-queue-requests-finalization.test.js | 8 +--- test/helpers/utils.js | 9 ++++ 4 files changed, 66 insertions(+), 21 deletions(-) diff --git a/test/0.8.9/staking-router/report-exited-keys.test.js b/test/0.8.9/staking-router/report-exited-keys.test.js index ff34a0db7..38295ab7a 100644 --- a/test/0.8.9/staking-router/report-exited-keys.test.js +++ b/test/0.8.9/staking-router/report-exited-keys.test.js @@ -1,7 +1,7 @@ const { contract, ethers } = require('hardhat') const { assert } = require('../../helpers/assert') const { EvmSnapshot } = require('../../helpers/blockchain') -const { hexConcat, hex, ETH } = require('../../helpers/utils') +const { hexConcat, hex, ETH, contractMethodWithResult } = require('../../helpers/utils') const { deployProtocol } = require('../../helpers/protocol') const { setupNodeOperatorsRegistry } = require('../../helpers/staking-modules') @@ -26,6 +26,9 @@ contract('StakingRouter', ([admin, depositor]) => { }) router = deployed.stakingRouter + router.updateExitedValidatorsCountByStakingModuleWithResult = contractMethodWithResult( + router.updateExitedValidatorsCountByStakingModule + ) voting = deployed.voting.address operators = await setupNodeOperatorsRegistry(deployed, true) module2 = await setupNodeOperatorsRegistry(deployed, true) @@ -154,9 +157,16 @@ contract('StakingRouter', ([admin, depositor]) => { assert.equal(+distribution.shares[0], op1shareBefore * sharesDistribute) assert.equal(+distribution.shares[1], op2shareBefore * sharesDistribute) - // //update exited validators + // update exited validators const exitValidatorsCount = 20 - await router.updateExitedValidatorsCountByStakingModule([module1Id], [exitValidatorsCount], { from: admin }) + const newlyExitedValidatorsCount = await router.updateExitedValidatorsCountByStakingModuleWithResult( + [module1Id], + [exitValidatorsCount], + { + from: admin, + } + ) + assert.equals(newlyExitedValidatorsCount, exitValidatorsCount) const nodeOpIds = [0] const exitedValidatorsCounts = [exitValidatorsCount] @@ -219,7 +229,12 @@ contract('StakingRouter', ([admin, depositor]) => { // //update exited validators const exitValidatorsCount = 20 - await router.updateExitedValidatorsCountByStakingModule([module1Id], [exitValidatorsCount], { from: admin }) + const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModuleWithResult( + [module1Id], + [exitValidatorsCount], + { from: admin } + ) + assert.equals(newlyExitedCount, exitValidatorsCount) const nodeOpIds = [0] const exitedValidatorsCounts = [exitValidatorsCount] @@ -289,9 +304,13 @@ contract('StakingRouter', ([admin, depositor]) => { assert.deepEqual([15, 5], await maxDepositsPerModule()) // update exited validators - let exitValidatorsCount = 1 - await router.updateExitedValidatorsCountByStakingModule([module1Id], [exitValidatorsCount], { from: admin }) - + const exitValidatorsCount = 1 + const exitedCount = await router.updateExitedValidatorsCountByStakingModuleWithResult( + [module1Id], + [exitValidatorsCount], + { from: admin } + ) + assert.equals(exitValidatorsCount, exitedCount) const nodeOpIds = [0] let exitedValidatorsCounts = [exitValidatorsCount] @@ -314,11 +333,16 @@ contract('StakingRouter', ([admin, depositor]) => { assert.deepEqual([16, 5], maxDepositsPerModuleAfterAlloc) // update next exited validators - exitValidatorsCount = 30 - exitedValidatorsCounts = [exitValidatorsCount] + const nextExitValidatorsCount = 30 + exitedValidatorsCounts = [nextExitValidatorsCount] keysData = hexConcat(...exitedValidatorsCounts.map((c) => hex(c, 16))) - await router.updateExitedValidatorsCountByStakingModule([module1Id], [exitValidatorsCount], { from: admin }) + const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModuleWithResult( + [module1Id], + [nextExitValidatorsCount], + { from: admin } + ) + assert.equals(newlyExitedCount, nextExitValidatorsCount - exitValidatorsCount) // report exited by module and node operator await router.reportStakingModuleExitedValidatorsCountByNodeOperator(module1Id, nodeOpIdsData, keysData, { from: admin, diff --git a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js index 1a5bbd838..653bec9e0 100644 --- a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js +++ b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js @@ -1,7 +1,7 @@ const { artifacts, contract, ethers } = require('hardhat') const { EvmSnapshot } = require('../../helpers/blockchain') const { assert } = require('../../helpers/assert') -const { hex, hexConcat, toNum } = require('../../helpers/utils') +const { hex, hexConcat, toNum, contractMethodWithResult } = require('../../helpers/utils') const { StakingModuleStub } = require('../../helpers/stubs/staking-module.stub') const StakingRouter = artifacts.require('StakingRouterMock.sol') @@ -17,6 +17,9 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { before(async () => { depositContract = await DepositContractMock.new({ from: deployer }) router = await StakingRouter.new(depositContract.address, { from: deployer }) + router.updateExitedValidatorsCountByStakingModuleWithResult = contractMethodWithResult( + router.updateExitedValidatorsCountByStakingModule + ) ;[module1, module2] = await Promise.all([ StakingModuleMock.new({ from: deployer }), StakingModuleMock.new({ from: deployer }), @@ -110,7 +113,10 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { }) it('reporting module 1 to have total 3 exited validators', async () => { - await router.updateExitedValidatorsCountByStakingModule([module1Id], [3], { from: admin }) + const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModuleWithResult([module1Id], [3], { + from: admin, + }) + assert.equals(newlyExitedCount, 3) }) it('staking module info gets updated', async () => { @@ -531,7 +537,10 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { }) it('reporting 3 exited keys total for module 1 and 2 exited keys total for module 2', async () => { - await router.updateExitedValidatorsCountByStakingModule(moduleIds, [3, 2], { from: admin }) + const newlyExited = await router.updateExitedValidatorsCountByStakingModuleWithResult(moduleIds, [3, 2], { + from: admin, + }) + assert.equals(newlyExited, 5) }) it('staking modules info gets updated', async () => { @@ -563,7 +572,11 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { const { totalExitedValidators: totalExitedValidators1 } = await module1.getStakingModuleSummary() const { totalExitedValidators: totalExitedValidators2 } = await module2.getStakingModuleSummary() - const tx = await router.updateExitedValidatorsCountByStakingModule(moduleIds, [3, 2], { from: admin }) + const args = [moduleIds, [3, 2], { from: admin }] + const newlyExited = await router.updateExitedValidatorsCountByStakingModule.call(...args) + assert.equals(newlyExited, 0) + const tx = await router.updateExitedValidatorsCountByStakingModule(...args) + assert.emits(tx, 'StakingModuleExitedValidatorsIncompleteReporting', { stakingModuleId: moduleIds[0], unreportedExitedValidatorsCount: prevReportedExitedValidatorsCount1 - totalExitedValidators1, @@ -885,7 +898,10 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { } // first correction - await router.updateExitedValidatorsCountByStakingModule([module1Id], [10], { from: admin }) + const newlyExited = await router.updateExitedValidatorsCountByStakingModuleWithResult([module1Id], [10], { + from: admin, + }) + assert.equals(newlyExited, 10) await assert.reverts( router.unsafeSetExitedValidatorsCount(module1Id, nodeOperatorId, false, ValidatorsCountsCorrection, { from: admin, diff --git a/test/0.8.9/withdrawal-queue-requests-finalization.test.js b/test/0.8.9/withdrawal-queue-requests-finalization.test.js index 87de94fe8..a1448ebbe 100644 --- a/test/0.8.9/withdrawal-queue-requests-finalization.test.js +++ b/test/0.8.9/withdrawal-queue-requests-finalization.test.js @@ -1,7 +1,7 @@ const { contract, ethers } = require('hardhat') const { itParam } = require('mocha-param') -const { StETH, shareRate, e18, e27, toBN, ETH } = require('../helpers/utils') +const { StETH, shareRate, e18, e27, toBN, ETH, contractMethodWithResult } = require('../helpers/utils') const { assert } = require('../helpers/assert') const { MAX_UINT256 } = require('../helpers/constants') const { EvmSnapshot } = require('../helpers/blockchain') @@ -57,11 +57,7 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { steth = deployed.steth withdrawalQueue = deployed.withdrawalQueue - withdrawalQueue.requestWithdrawalsWithResults = async (...args) => { - const result = await withdrawalQueue.requestWithdrawals.call(...args) - await withdrawalQueue.requestWithdrawals(...args) - return result - } + withdrawalQueue.requestWithdrawalsWithResults = contractMethodWithResult(withdrawalQueue.requestWithdrawals) await steth.mintShares(user, e18(10)) await steth.approve(withdrawalQueue.address, StETH(10), { from: user }) diff --git a/test/helpers/utils.js b/test/helpers/utils.js index ee296ec8b..cdae8ce40 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -170,6 +170,14 @@ function getFirstEventArgs(receipt, eventName, abi = undefined) { return events[0].args } +function contractMethodWithResult(method) { + return async (...args) => { + const result = await method.call(...args) + await method(...args) + return result + } +} + module.exports = { ZERO_ADDRESS, ZERO_HASH, @@ -201,5 +209,6 @@ module.exports = { calcSharesMintedAsFees, getFirstEventArgs, calcShareRateDeltaE27, + contractMethodWithResult, limitRebase, } From 3156650b4477ab62f23b3460ce4f0ff120b6b219 Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Sun, 19 Mar 2023 16:06:15 +0400 Subject: [PATCH 29/66] Refactor the GenericStub test helper contract --- contracts/0.8.9/test_helpers/ContractStub.sol | 302 ++++++++++++++++++ contracts/0.8.9/test_helpers/GenericStub.sol | 224 ------------- test/0.4.24/lido-deposit-scenarios.test.js | 150 ++++----- .../staking-router-keys-reporting.test.js | 25 +- .../staking-router/staking-router.test.js | 24 +- test/helpers/stubs/generic.stub.js | 190 ----------- test/helpers/stubs/staking-module.stub.js | 61 ---- 7 files changed, 388 insertions(+), 588 deletions(-) create mode 100644 contracts/0.8.9/test_helpers/ContractStub.sol delete mode 100644 contracts/0.8.9/test_helpers/GenericStub.sol delete mode 100644 test/helpers/stubs/generic.stub.js delete mode 100644 test/helpers/stubs/staking-module.stub.js diff --git a/contracts/0.8.9/test_helpers/ContractStub.sol b/contracts/0.8.9/test_helpers/ContractStub.sol new file mode 100644 index 000000000..202189a18 --- /dev/null +++ b/contracts/0.8.9/test_helpers/ContractStub.sol @@ -0,0 +1,302 @@ +// SPDX-FileCopyrightText: 2023 Lido +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity 0.8.9; + +/// +/// DATA TYPES +/// + +/// @notice Stores method stubs of the ContractStub's frame +struct ContractStubFrame { + /// @notice list of method stubs declared in the given frame (order is not guaranteed) + MethodStub[] methodStubs; + /// @notice method stub indices increased to 1 by the id of methods stub + mapping(bytes32 => uint256) indicesByIdOneBased; +} + +/// @notice Method stub config +struct MethodStub { + /// @notice value of the msg.data on which method stub will be triggered on + bytes input; + /// @notice abi encoded data to be returned or reverted from the method stub + bytes output; + /// @notice whether method ends with Yul's revert() or return() instruction + bool isRevert; + /// @notice potentially state modifying side effects. Side effects take place + /// ONLY when isRevert is set to false. + SideEffects sideEffects; +} + +/// @notice Side effects of the method stub +struct SideEffects { + /// @notice whether the ContractStub__call event should be emitted on method stub execution + bool traceable; + /// @notice number of the frame to set as active after the method stub executed + uint256 nextFrame; + /// @notice logs to generate during method stub execution + Log[] logs; + /// @notice list of calls to external contracts + ExternalCall[] externalCalls; + /// @notice ETH transfers to make via ETHForwarder contract instances. Use when a recipient + /// doesn't accept ETH by default + ForwardETH[] ethForwards; +} + +/// @notice Stores Yul's log instruction data +struct Log { + LogType logType; + bytes data; + bytes32 t1; + bytes32 t2; + bytes32 t3; + bytes32 t4; +} + +/// @notice Type of Yul's log instruction +enum LogType { + LOG0, + LOG1, + LOG2, + LOG3, + LOG4 +} + +struct ForwardETH { + address payable recipient; + uint256 value; +} + +struct ExternalCall { + address payable callee; + bytes data; + uint256 value; + uint256 gas; +} + +/// +/// MAIN CONTRACTS +/// + +/// @notice Allows to stub the functionality of the Solidity contract +/// @dev WARNING: !!! DO NOT USE IT IN PRODUCTION !!! +contract ContractStub { + ContractStubStorage private immutable STORAGE; + bytes4 private immutable GET_STORAGE_ADDRESS_METHOD_ID; + + constructor(bytes4 _getStorageAddressMethodId) { + STORAGE = new ContractStubStorage(); + GET_STORAGE_ADDRESS_METHOD_ID = _getStorageAddressMethodId; + } + + // solhint-disable-next-line + fallback() external payable { + if (bytes4(msg.data) == GET_STORAGE_ADDRESS_METHOD_ID) { + _return(abi.encodePacked(address(STORAGE))); + } + MethodStub memory stub = _getMethodStub(); + if (stub.isRevert) { + _revert(stub.output); + } + _logEvents(stub.sideEffects.logs); + _forwardETH(stub.sideEffects.ethForwards); + _makeExternalCalls(stub.sideEffects.externalCalls); + _switchFrame(stub.sideEffects.nextFrame); + _leaveTrace(stub.sideEffects.traceable); + _return(stub.output); + } + + function _getMethodStub() internal view returns (MethodStub memory) { + return + STORAGE.hasMethodStub(msg.data) + ? STORAGE.getMethodStub(msg.data) + : STORAGE.getMethodStub(msg.data[:4]); + } + + function _revert(bytes memory _data) internal pure { + assembly { + revert(add(_data, 32), mload(_data)) + } + } + + function _return(bytes memory _data) internal pure { + assembly { + return(add(_data, 32), mload(_data)) + } + } + + function _switchFrame(uint256 _nextFrame) internal { + if (_nextFrame != type(uint256).max) { + STORAGE.activateFrame(_nextFrame); + } + } + + function _leaveTrace(bool _isTraceable) internal { + if (!_isTraceable) return; + emit ContractStub__called( + msg.sender, + bytes4(msg.data[:4]), + msg.data[4:], + msg.value, + block.number + ); + } + + function _logEvents(Log[] memory _logs) internal { + for (uint256 i = 0; i < _logs.length; ++i) { + bytes32 t1 = _logs[i].t1; + bytes32 t2 = _logs[i].t2; + bytes32 t3 = _logs[i].t3; + bytes32 t4 = _logs[i].t4; + bytes memory data = _logs[i].data; + uint256 dataLength = data.length; + if (_logs[i].logType == LogType.LOG0) { + assembly { + log0(add(data, 32), dataLength) + } + } else if (_logs[i].logType == LogType.LOG1) { + assembly { + log1(add(data, 32), dataLength, t1) + } + } else if (_logs[i].logType == LogType.LOG2) { + assembly { + log2(add(data, 32), dataLength, t1, t2) + } + } else if (_logs[i].logType == LogType.LOG3) { + assembly { + log3(add(data, 32), dataLength, t1, t2, t3) + } + } else if (_logs[i].logType == LogType.LOG4) { + assembly { + log4(add(data, 32), dataLength, t1, t2, t3, t4) + } + } + } + } + + function _forwardETH(ForwardETH[] memory _ethForwards) internal { + for (uint256 i = 0; i < _ethForwards.length; ++i) { + ForwardETH memory ethForward = _ethForwards[i]; + new ETHForwarder{ value: ethForward.value }(ethForward.recipient); + emit ContractStub__ethSent(ethForward.recipient, ethForward.value); + } + } + + function _makeExternalCalls(ExternalCall[] memory _calls) internal { + for (uint256 i = 0; i < _calls.length; ++i) { + ExternalCall memory externalCall = _calls[i]; + (bool success, bytes memory data) = externalCall.callee.call{ + value: externalCall.value, + gas: externalCall.gas == 0 ? gasleft() : externalCall.gas + }(externalCall.data); + emit ContractStub__callResult(externalCall, success, data); + } + } + + // solhint-disable-next-line + event ContractStub__ethSent(address recipient, uint256 value); + + // solhint-disable-next-line + event ContractStub__called( + address caller, + bytes4 methodId, + bytes callData, + uint256 value, + uint256 blockNumber + ); + + // solhint-disable-next-line + event ContractStub__callResult( + ExternalCall call, + bool success, + bytes response + ); +} + +/// @notice Keeps the state of the ContractStub instance +contract ContractStubStorage { + uint256 private constant EMPTY_FRAME_ID = type(uint256).max; + + mapping(uint256 => ContractStubFrame) private frames; + uint256 public currentFrameNumber; + + function hasMethodStub(bytes memory callData) external view returns (bool) { + bytes32 methodStubId = keccak256(callData); + return + frames[currentFrameNumber].indicesByIdOneBased[methodStubId] != 0; + } + + function getMethodStub( + bytes memory callData + ) external view returns (MethodStub memory) { + bytes32 methodStubId = keccak256(callData); + uint256 methodStubIndex = frames[currentFrameNumber] + .indicesByIdOneBased[methodStubId]; + if (methodStubIndex == 0) + revert ContractStub__MethodStubNotFound(callData); + return frames[currentFrameNumber].methodStubs[methodStubIndex - 1]; + } + + function addMethodStub( + uint256 _frameNumber, + MethodStub memory _methodStub + ) external { + uint256 frameNumber = _frameNumber == EMPTY_FRAME_ID + ? currentFrameNumber + : _frameNumber; + frames[frameNumber].methodStubs.push(); + bytes32 stubId = keccak256(_methodStub.input); + uint256 newStubIndex = frames[frameNumber].methodStubs.length - 1; + frames[frameNumber].indicesByIdOneBased[stubId] = newStubIndex + 1; + + MethodStub storage methodStub = frames[frameNumber].methodStubs[ + newStubIndex + ]; + + methodStub.input = _methodStub.input; + methodStub.output = _methodStub.output; + methodStub.isRevert = _methodStub.isRevert; + + SideEffects storage sideEffects = methodStub.sideEffects; + sideEffects.traceable = _methodStub.sideEffects.traceable; + sideEffects.nextFrame = _methodStub.sideEffects.nextFrame; + for (uint256 i = 0; i < _methodStub.sideEffects.logs.length; ++i) { + sideEffects.logs.push(_methodStub.sideEffects.logs[i]); + } + for ( + uint256 i = 0; + i < _methodStub.sideEffects.externalCalls.length; + ++i + ) { + sideEffects.externalCalls.push( + _methodStub.sideEffects.externalCalls[i] + ); + } + for ( + uint256 i = 0; + i < _methodStub.sideEffects.ethForwards.length; + ++i + ) { + sideEffects.ethForwards.push( + _methodStub.sideEffects.ethForwards[i] + ); + } + } + + function activateFrame(uint256 _frameNumber) external { + currentFrameNumber = _frameNumber; + } + + error ContractStub__MethodStubNotFound(bytes callData); +} + +/// +/// HELPER CONTRACTS +/// + +/// @notice Helper contract to transfer ether via selfdestruct +contract ETHForwarder { + constructor(address payable _recipient) payable { + selfdestruct(_recipient); + } +} diff --git a/contracts/0.8.9/test_helpers/GenericStub.sol b/contracts/0.8.9/test_helpers/GenericStub.sol deleted file mode 100644 index 2450113a8..000000000 --- a/contracts/0.8.9/test_helpers/GenericStub.sol +++ /dev/null @@ -1,224 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Lido -// SPDX-License-Identifier: GPL-3.0 - -pragma solidity 0.8.9; - -enum LogType { - LOG0, - LOG1, - LOG2, - LOG3, - LOG4 -} - -contract ETHForwarder { - constructor(address payable _recipient) payable { - selfdestruct(_recipient); - } -} - -contract GenericStub { - type MethodID is bytes4; - type InputHash is bytes32; - type Topic is bytes32; - - // InputHash private immutable WILDCARD_INPUT_HASH; - - struct Log { - LogType logType; - bytes data; - bytes32 t1; - bytes32 t2; - bytes32 t3; - bytes32 t4; - } - - struct ForwardETH { - address payable recipient; - uint256 value; - } - - struct MethodStub { - /// @notice msg.data used for call - bytes input; - /// @notice abi encoded data to be returned from the method - bytes output; - /// @notice events to emit during method execution - Log[] logs; - /// @notice optional ETH send on method execution - ForwardETH forwardETH; - /// @notice shall method ends with revert instead of return - bool isRevert; - /// @notice index of the state to set as current after stub call - /// @dev this value is one based - uint256 nextStateIndexOneBased; - } - - struct StubState { - /// @notice list of all stubs (order is not guaranteed) - MethodStub[] stubs; - /// @notice indices of stubs increased to 1 - mapping(bytes32 => uint256) indicesByIdOneBased; - } - - StubState[] private _states; - uint256 private _currentStateIndexOneBased = 1; - - constructor() { - _states.push(); - } - - function GenericStub__addStub(MethodStub memory _stub) external { - GenericStub__addStub(_currentStateIndexOneBased - 1, _stub); - } - - function GenericStub__addStub(uint256 _stateIndex, MethodStub memory _stub) public { - StubState storage state = _getState(_stateIndex); - state.stubs.push(); - bytes32 stubId = keccak256(_stub.input); - uint256 newStubIndex = state.stubs.length - 1; - state.stubs[newStubIndex].input = _stub.input; - state.stubs[newStubIndex].output = _stub.output; - state.stubs[newStubIndex].forwardETH = _stub.forwardETH; - state.stubs[newStubIndex].isRevert = _stub.isRevert; - state.stubs[newStubIndex].nextStateIndexOneBased = _stub - .nextStateIndexOneBased; - - for (uint256 i = 0; i < _stub.logs.length; ++i) { - state.stubs[newStubIndex].logs.push(_stub.logs[i]); - } - state.indicesByIdOneBased[stubId] = newStubIndex + 1; - } - - function GenericStub__addState() external { - _states.push(); - } - - function GenericStub__setState(uint256 _stateIndex) external { - require(_stateIndex != 0, "INVALID_INDEX"); - if (_stateIndex > _states.length) { - revert GenericStub__StateIndexOutOfBounds( - _stateIndex, - _states.length - ); - } - _currentStateIndexOneBased = _stateIndex; - } - - // solhint-disable-next-line - fallback() external payable { - MethodStub memory stub = _getMethodStub(); - _forwardETH(stub.forwardETH); - _logEvents(stub.logs); - bytes memory output = stub.output; - uint256 outputLength = output.length; - if (stub.nextStateIndexOneBased != 0) { - _currentStateIndexOneBased = stub.nextStateIndexOneBased; - } - if (stub.isRevert) { - assembly { - revert(add(output, 32), outputLength) - } - } - // emit GenericStub__called( - // msg.sender, - // bytes4(msg.data[:4]), - // msg.data[4:], - // msg.value, - // block.number - // ); - assembly { - return(add(output, 32), outputLength) - } - } - - function _logEvents(Log[] memory _logs) internal { - for (uint256 i = 0; i < _logs.length; ++i) { - bytes32 t1 = _logs[i].t1; - bytes32 t2 = _logs[i].t2; - bytes32 t3 = _logs[i].t3; - bytes32 t4 = _logs[i].t4; - bytes memory data = _logs[i].data; - uint256 dataLength = data.length; - if (_logs[i].logType == LogType.LOG0) { - assembly { - log0(add(data, 32), dataLength) - } - } else if (_logs[i].logType == LogType.LOG1) { - assembly { - log1(add(data, 32), dataLength, t1) - } - } else if (_logs[i].logType == LogType.LOG2) { - assembly { - log2(add(data, 32), dataLength, t1, t2) - } - } else if (_logs[i].logType == LogType.LOG3) { - assembly { - log3(add(data, 32), dataLength, t1, t2, t3) - } - } else if (_logs[i].logType == LogType.LOG4) { - assembly { - log4(add(data, 32), dataLength, t1, t2, t3, t4) - } - } - } - } - - function _forwardETH(ForwardETH memory _ethForward) internal { - if (_ethForward.value == 0) return; - new ETHForwarder{ value: _ethForward.value }(_ethForward.recipient); - emit GenericStub__ethSent(_ethForward.recipient, _ethForward.value); - } - - function _getMethodStub() internal view returns (MethodStub memory) { - StubState storage currentState = _getState( - _currentStateIndexOneBased - 1 - ); - bytes32 methodStubId = keccak256(msg.data); - bytes32 methodStubWildcardId = keccak256(msg.data[:4]); - - uint256 methodStubIndex = currentState.indicesByIdOneBased[ - methodStubId - ]; - uint256 methodStubWildcardIndex = currentState.indicesByIdOneBased[ - methodStubWildcardId - ]; - - if (methodStubIndex == 0 && methodStubWildcardIndex == 0) { - revert GenericStub__MethodStubIsNotDefined(msg.data); - } - - return - methodStubIndex != 0 - ? currentState.stubs[methodStubIndex - 1] - : currentState.stubs[methodStubWildcardIndex - 1]; - } - - function _getState( - uint256 _stateIndex - ) internal view returns (StubState storage) { - if (_stateIndex >= _states.length) { - revert GenericStub__StateIndexOutOfBounds( - _stateIndex, - _states.length - ); - } - return _states[_stateIndex]; - } - - // solhint-disable-next-line - event GenericStub__ethSent(address recipient, uint256 value); - - // solhint-disable-next-line - event GenericStub__called( - address caller, - bytes4 methodId, - bytes callData, - uint256 value, - uint256 blockNumber - ); - - error GenericStub__StateIndexOutOfBounds(uint256 index, uint256 length); - error GenericStub__MethodStubIsNotDefined(bytes callData); - error GenericStub__ETHSendFailed(address recipient, uint256 value); -} diff --git a/test/0.4.24/lido-deposit-scenarios.test.js b/test/0.4.24/lido-deposit-scenarios.test.js index 3cb12ed8f..30e61c00e 100644 --- a/test/0.4.24/lido-deposit-scenarios.test.js +++ b/test/0.4.24/lido-deposit-scenarios.test.js @@ -4,22 +4,44 @@ const { EvmSnapshot, setBalance, getBalance } = require('../helpers/blockchain') const { ZERO_ADDRESS } = require('../helpers/constants') const { assert } = require('../helpers/assert') const { wei } = require('../helpers/wei') -const { StakingModuleStub } = require('../helpers/stubs/staking-module.stub') const { PUBKEY_LENGTH, FakeValidatorKeys, SIGNATURE_LENGTH } = require('../helpers/signing-keys') -const { GenericStub } = require('../helpers/stubs/generic.stub') +const { ContractStub } = require('../helpers/contract-stub') -contract('Lido deposit scenarios', ([staker, depositor]) => { +contract('Lido deposit scenarios', ([deployer, staker, depositor]) => { const STAKING_MODULE_ID = 1 const DEPOSIT_CALLDATA = '0x0' + const TOTAL_EXITED_VALIDATORS = 5 + const TOTAL_DEPOSITED_VALIDATORS = 16 + const DEPOSITABLE_VALIDATORS_COUNT = 2 + let lido, stakingRouter let stakingModuleStub, depositContractStub let snapshot + const stubObtainDepositDataReturns = (publicKeysBatch, signaturesBatch) => + ContractStub(stakingModuleStub) + .on('obtainDepositData', { + return: { + type: ['bytes', 'bytes'], + value: [publicKeysBatch, signaturesBatch], + }, + }) + .update({ from: deployer }) + before('prepare base Lido & StakingRouter setup', async () => { - stakingModuleStub = await StakingModuleStub.new() - depositContractStub = await GenericStub.new('contracts/0.6.11/deposit_contract.sol:IDepositContract') - // just accept all ether and do nothing - await GenericStub.stub(depositContractStub, 'deposit') + stakingModuleStub = await ContractStub('IStakingModule') + .on('getStakingModuleSummary', { + return: { + type: ['uint256', 'uint256', 'uint256'], + value: [TOTAL_EXITED_VALIDATORS, TOTAL_DEPOSITED_VALIDATORS, DEPOSITABLE_VALIDATORS_COUNT], + }, + }) + .create({ from: deployer }) + + depositContractStub = await ContractStub('contracts/0.6.11/deposit_contract.sol:IDepositContract') + .on('deposit') // just accept all ether and do nothing + .create({ from: deployer }) + const protocol = await deployProtocol({ stakingModulesFactory: async () => { return [ @@ -59,17 +81,8 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { await setBalance(lido, initialLidoETHBalance + unaccountedLidoETHBalance) assert.equal(await getBalance(lido), initialLidoETHBalance + unaccountedLidoETHBalance) - const depositableValidatorsCount = 2 - await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { - totalExitedValidators: 5, - totalDepositedValidators: 16, - depositableValidatorsCount, - }) - - const depositDataLength = depositableValidatorsCount - await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { - return: { depositDataLength }, - }) + const depositDataLength = DEPOSITABLE_VALIDATORS_COUNT + await stubObtainDepositDataReturns(...new FakeValidatorKeys(depositDataLength).slice()) const submitAmount = wei`320 ether` await lido.submit(ZERO_ADDRESS, { from: staker, value: wei.str(submitAmount) }) @@ -80,7 +93,7 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { await lido.deposit(maxDepositsCount, STAKING_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }) assert.equals(await getBalance(stakingRouter), initialStakingRouterBalance) - const depositedEther = wei`32 ether` * wei.min(maxDepositsCount, depositableValidatorsCount) + const depositedEther = wei`32 ether` * wei.min(maxDepositsCount, DEPOSITABLE_VALIDATORS_COUNT) assert.equals( await getBalance(lido), initialLidoETHBalance + unaccountedLidoETHBalance + submitAmount - depositedEther @@ -93,17 +106,8 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { await setBalance(stakingRouter, initialStakingRouterBalance) assert.equals(await getBalance(stakingRouter), initialStakingRouterBalance) - const depositableValidatorsCount = 2 - await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { - totalExitedValidators: 5, - totalDepositedValidators: 16, - depositableValidatorsCount, - }) - - const depositDataLength = depositableValidatorsCount + 2 - await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { - return: { depositDataLength }, - }) + const depositDataLength = DEPOSITABLE_VALIDATORS_COUNT + 2 + await stubObtainDepositDataReturns(...new FakeValidatorKeys(depositDataLength).slice()) const initialLidETHBalance = await getBalance(lido) @@ -116,7 +120,7 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { await assert.reverts( lido.deposit(maxDepositsCount, STAKING_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }), 'InvalidPublicKeysBatchLength', - [PUBKEY_LENGTH * depositDataLength, PUBKEY_LENGTH * depositableValidatorsCount] + [PUBKEY_LENGTH * depositDataLength, PUBKEY_LENGTH * DEPOSITABLE_VALIDATORS_COUNT] ) }) @@ -125,21 +129,12 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { await setBalance(stakingRouter, initialStakingRouterBalance) assert.equals(await getBalance(stakingRouter), initialStakingRouterBalance) - const depositableValidatorsCount = 2 - await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { - totalExitedValidators: 5, - totalDepositedValidators: 16, - depositableValidatorsCount, - }) - - const depositDataLength = depositableValidatorsCount + 2 + const depositDataLength = DEPOSITABLE_VALIDATORS_COUNT + 2 const depositData = new FakeValidatorKeys(depositDataLength) - await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { - return: { - publicKeysBatch: depositData.slice()[0], // two extra signatures returned - signaturesBatch: depositData.slice(0, depositableValidatorsCount)[1], - }, - }) + await stubObtainDepositDataReturns( + depositData.slice()[0], // two extra public keys returned + depositData.slice(0, DEPOSITABLE_VALIDATORS_COUNT)[1] + ) const initialLidETHBalance = await getBalance(lido) @@ -152,7 +147,7 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { await assert.reverts( lido.deposit(maxDepositsCount, STAKING_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }), 'InvalidPublicKeysBatchLength', - [PUBKEY_LENGTH * depositDataLength, PUBKEY_LENGTH * depositableValidatorsCount] + [PUBKEY_LENGTH * depositDataLength, PUBKEY_LENGTH * DEPOSITABLE_VALIDATORS_COUNT] ) }) @@ -161,21 +156,12 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { await setBalance(stakingRouter, initialStakingRouterBalance) assert.equals(await getBalance(stakingRouter), initialStakingRouterBalance) - const depositableValidatorsCount = 2 - await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { - totalExitedValidators: 5, - totalDepositedValidators: 16, - depositableValidatorsCount, - }) - - const depositDataLength = depositableValidatorsCount + 2 + const depositDataLength = DEPOSITABLE_VALIDATORS_COUNT + 2 const depositData = new FakeValidatorKeys(depositDataLength) - await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { - return: { - publicKeysBatch: depositData.slice(0, depositableValidatorsCount)[0], - signaturesBatch: depositData.slice()[1], // two extra signatures returned - }, - }) + await stubObtainDepositDataReturns( + depositData.slice(0, DEPOSITABLE_VALIDATORS_COUNT)[0], + depositData.slice()[1] // two extra signatures returned + ) const initialLidETHBalance = await getBalance(lido) @@ -188,15 +174,17 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { await assert.reverts( lido.deposit(maxDepositsCount, STAKING_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor }), 'InvalidSignaturesBatchLength', - [SIGNATURE_LENGTH * depositDataLength, SIGNATURE_LENGTH * depositableValidatorsCount] + [SIGNATURE_LENGTH * depositDataLength, SIGNATURE_LENGTH * DEPOSITABLE_VALIDATORS_COUNT] ) }) it('invalid ETH value was used for deposits in StakingRouter', async () => { // on each deposit call forward back 1 ether to the staking router - await GenericStub.stub(depositContractStub, 'deposit', { - forwardETH: { value: wei.str`1 ether`, recipient: stakingRouter.address }, - }) + await ContractStub(depositContractStub) + .on('deposit', { + ethForwards: [{ recipient: stakingRouter.address, value: wei.str`1 ether` }], + }) + .update({ from: deployer }) const submitAmount = wei`320 ether` const initialLidoETHBalance = await getBalance(lido) @@ -204,17 +192,9 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { assert.equal(await getBalance(lido), initialLidoETHBalance + submitAmount) - const depositableValidatorsCount = 2 - await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { - totalExitedValidators: 5, - totalDepositedValidators: 16, - depositableValidatorsCount, - }) + const depositDataLength = DEPOSITABLE_VALIDATORS_COUNT + await stubObtainDepositDataReturns(...new FakeValidatorKeys(depositDataLength).slice()) - const depositDataLength = depositableValidatorsCount - await StakingModuleStub.stubObtainDepositData(stakingModuleStub, { - return: { depositDataLength }, - }) const maxDepositsCount = 10 await assert.reverts(lido.deposit(maxDepositsCount, STAKING_MODULE_ID, DEPOSIT_CALLDATA, { from: depositor })) }) @@ -226,16 +206,11 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { assert.equal(await getBalance(lido), initialLidoETHBalance + submitAmount) - const depositableValidatorsCount = 2 - await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { - totalExitedValidators: 5, - totalDepositedValidators: 16, - depositableValidatorsCount, - }) - - await StakingModuleStub.stub(stakingModuleStub, 'obtainDepositData', { - revert: { reason: 'INVALID_ALLOCATED_KEYS_COUNT' }, - }) + await ContractStub(stakingModuleStub) + .on('obtainDepositData', { + revert: { reason: 'INVALID_ALLOCATED_KEYS_COUNT' }, + }) + .update({ from: deployer }) const maxDepositsCount = 10 await assert.reverts( @@ -248,13 +223,6 @@ contract('Lido deposit scenarios', ([staker, depositor]) => { const submitAmount = wei`100 ether` await lido.submit(ZERO_ADDRESS, { from: staker, value: wei.str(submitAmount) }) - const depositableValidatorsCount = 2 - await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleStub, { - totalExitedValidators: 5, - totalDepositedValidators: 16, - depositableValidatorsCount, - }) - const stakingModuleStateBefore = await stakingRouter.getStakingModule(STAKING_MODULE_ID) const maxDepositsCount = 0 diff --git a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js index 7f7a825f4..0c7ee9b3e 100644 --- a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js +++ b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js @@ -2,7 +2,7 @@ const { artifacts, contract, ethers } = require('hardhat') const { EvmSnapshot } = require('../../helpers/blockchain') const { assert } = require('../../helpers/assert') const { hex, hexConcat, toNum } = require('../../helpers/utils') -const { StakingModuleStub } = require('../../helpers/stubs/staking-module.stub') +const { ContractStub } = require('../../helpers/contract-stub') const StakingRouter = artifacts.require('StakingRouterMock.sol') const StakingModuleMock = artifacts.require('StakingModuleMock.sol') @@ -480,17 +480,20 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { ) it("doesn't revert when onExitedAndStuckValidatorsCountsUpdated reverted", async () => { - const stakingModuleWithBug = await StakingModuleStub.new() // staking module will revert with panic exit code - await StakingModuleStub.stub(stakingModuleWithBug, 'onExitedAndStuckValidatorsCountsUpdated', { - revert: { error: 'Panic', args: { type: ['uint256'], value: [0x01] } }, - }) - await StakingModuleStub.stubGetStakingModuleSummary(stakingModuleWithBug, { - totalExitedValidators: 0, - totalDepositedValidators: 0, - depositableValidatorsCount: 0, - }) - await router.addStakingModule('Staking Module With Bug', stakingModuleWithBug.address, 100, 1000, 2000, { + const buggedStakingModule = await ContractStub('IStakingModule') + .on('onExitedAndStuckValidatorsCountsUpdated', { + revert: { error: { name: 'Panic', args: { type: ['uint256'], value: [0x01] } } }, + }) + .on('getStakingModuleSummary', { + return: { + type: ['uint256', 'uint256', 'uint256'], + value: [0, 0, 0], + }, + }) + .create({ from: deployer }) + + await router.addStakingModule('Staking Module With Bug', buggedStakingModule.address, 100, 1000, 2000, { from: admin, }) const stakingModuleId = await router.getStakingModulesCount() diff --git a/test/0.8.9/staking-router/staking-router.test.js b/test/0.8.9/staking-router/staking-router.test.js index c79725622..d00dc4d92 100644 --- a/test/0.8.9/staking-router/staking-router.test.js +++ b/test/0.8.9/staking-router/staking-router.test.js @@ -5,7 +5,7 @@ const { BN } = require('bn.js') const { assert } = require('../../helpers/assert') const { EvmSnapshot } = require('../../helpers/blockchain') const { ETH, toBN } = require('../../helpers/utils') -const { StakingModuleStub } = require('../../helpers/stubs/staking-module.stub') +const { ContractStub } = require('../../helpers/contract-stub') const OssifiableProxy = artifacts.require('OssifiableProxy.sol') const DepositContractMock = artifacts.require('DepositContractMock') @@ -318,12 +318,14 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { }) it('set withdrawal credentials works when staking module reverts', async () => { - const stakingModuleWithBug = await StakingModuleStub.new() // staking module will revert with panic exit code - await StakingModuleStub.stub(stakingModuleWithBug, 'onWithdrawalCredentialsChanged', { - revert: { error: 'Panic', args: { type: ['uint256'], value: [0x01] } }, - }) - await router.addStakingModule('Staking Module With Bug', stakingModuleWithBug.address, 100, 1000, 2000, { + const buggedStakingModule = await ContractStub('IStakingModule') + .on('onWithdrawalCredentialsChanged', { + revert: { error: { name: 'Panic', args: { type: ['uint256'], value: [0x01] } } }, + }) + .create({ from: deployer }) + + await router.addStakingModule('Staking Module With Bug', buggedStakingModule.address, 100, 1000, 2000, { from: appManager, }) const stakingModuleId = await router.getStakingModulesCount() @@ -943,12 +945,12 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { }) it('handles reverted staking modules correctly', async () => { - const stakingModuleWithBug = await StakingModuleStub.new() // staking module will revert with message "UNHANDLED_ERROR" - await StakingModuleStub.stub(stakingModuleWithBug, 'onRewardsMinted', { - revert: { reason: 'UNHANDLED_ERROR' }, - }) - await router.addStakingModule('Staking Module With Bug', stakingModuleWithBug.address, 100, 1000, 2000, { + const buggedStakingModule = await ContractStub('IStakingModule') + .on('onRewardsMinted', { revert: { reason: 'UNHANDLED_ERROR' } }) + .create({ from: deployer }) + + await router.addStakingModule('Staking Module With Bug', buggedStakingModule.address, 100, 1000, 2000, { from: admin, }) const stakingModuleWithBugId = await router.getStakingModulesCount() diff --git a/test/helpers/stubs/generic.stub.js b/test/helpers/stubs/generic.stub.js deleted file mode 100644 index df48b8d40..000000000 --- a/test/helpers/stubs/generic.stub.js +++ /dev/null @@ -1,190 +0,0 @@ -const hre = require('hardhat') -const { ZERO_ADDRESS } = require('../constants') - -class GenericStub { - static LOG_TYPE = Object.freeze({ - LOG0: 0, - LOG1: 1, - LOG2: 2, - LOG3: 3, - LOG4: 4, - }) - - static GenericStubContract = hre.artifacts.require('GenericStub') - - static async new(contractName) { - const stubInstance = await GenericStub.GenericStubContract.new() - const StubbedContractFactory = hre.artifacts.require(contractName) - return StubbedContractFactory.at(stubInstance.address) - } - - static async addState(stubbedContract) { - const stubInstance = await GenericStub.GenericStubContract.at(stubbedContract.address) - await stubInstance.GenericStub__addState() - } - - static async setState(stubbedContract, stateIndex) { - const stubInstance = await GenericStub.GenericStubContract.at(stubbedContract.address) - await stubInstance.GenericStub__setState(stateIndex) - } - - /** - * @typedef {object} TypedTuple - stores a info about tuple type & value - * @property {string[]} - tuple with type names - * @property {any[]} - tuple with values for types - * - * @param {object} stubbedContract instance of the GenericStub contract to add stub - * - * @param {string} methodName name of the method to stub - * - * @param {object} config stubbed method params - * @param {object} state describes the state were stub declared and next transition - * @param {number} state.current index of the state where stub will be added - * @param {number} state.next index of the state which will be activated after the stub called - * @param {TypedTuple} [config.input] the input value to trigger the stub - * @param {TypedTuple} [config.return] the output value to return or revert from stub - * @param {object} [config.revert] the revert info when stub must finish with error - * @param {string} [config.revert.reason] the revert reason. Used when method reverts with string message - * @param {string} [config.revert.error] the custom error name when method must revert with custom error - * @param {TypedTuple} [config.revert.args] the arguments info for custom error - * @param {object} [config.forwardETH] amount and recipient where to send ETH - * @param {string} config.forwardETH.recipient recipient address of the ETH - * @param {object} config.forwardETH.value amount of ETH to send - * @param {object[]} [config.emit] events to emit when stub called - * @param {string} config.emit.name name of the event to emit - * @param {object} [config.emit.args] arguments of the event - * @param {string[]} [config.emit.args.type] tuple with type names - * @param {any[]} [config.emit.args.value] tuple with values for types - * @param {bool[]} [config.emit.args.indexed] is value indexed or not - */ - static async stub(stubbedContract, methodName, config = {}) { - const stubInstance = await GenericStub.GenericStubContract.at(stubbedContract.address) - - const { abi: abis } = stubbedContract - const methodAbis = abis.filter((abi) => abi.type === 'function' && abi.name === methodName) - - if (methodAbis.length > 1) { - throw new Error('Support of methods overloading has not implemented yet') - } - const [methodAbi] = methodAbis - - const configParser = new GenericStubConfigParser() - const { currentState, ...parsedConfig } = configParser.parse(methodAbi.signature, config) - - if (currentState === undefined) { - await stubInstance.GenericStub__addStub(Object.values(parsedConfig)) - } else { - await stubInstance.GenericStub__addStub(currentState, Object.values(parsedConfig)) - } - } -} - -module.exports = { - GenericStub, -} - -class GenericStubConfigParser { - parse(methodAbi, config) { - return { - input: this._parseInput(methodAbi, config), - output: this._parseOutput(config), - logs: this._parseLogs(config), - forwardETH: this._parseForwardETH(config), - isRevert: this._parseIsRevert(config), - currentState: this._parseCurrentState(config), - nextState: this._parseNextState(config), - } - } - - _parseInput(methodSignature, config) { - return methodSignature + this._encode(config.input || { type: [], value: [] }).slice(2) - } - - _parseOutput(config) { - if (config.return) { - return this._encode(config.return) - } - if (config.revert) { - return config.revert.error - ? this._encodeError(config.revert) - : this._encodeError({ error: 'Error', args: { type: ['string'], value: [config.revert.reason || ''] } }) - } - return this._encode({ type: [], value: [] }) - } - - _parseLogs(config) { - if (!config.emit || config.emit.length === 0) return [] - return config.emit.map((event) => { - // required field so just read it - const name = event.name - // if not passed event considered as without arguments - const args = event.args ? { type: event.args.type, value: event.args.value } : { type: [], value: [] } - // when indexed is passed take its values or consider all fields as non-indexed in other cases - const indexed = event.args && event.args.indexed ? event.args.indexed : args.value.map(() => false) - // filter all indexed args indices to pass them as topics - const indexedIndices = indexed.map((indexed, index) => (indexed ? index : -1)).filter((i) => i >= 0) - // filter all non-indexed args indices to pass them as data - const nonIndexedIndices = indexed.map((indexed, index) => (indexed ? -1 : index)).filter((i) => i >= 0) - - // signature of the event always goes as topic1 - const signature = this._eventSignature(name, args.type) - // collect argument into topics via ABI encoding - const topics = indexedIndices.map((i) => this._encode({ type: [args.type[i]], value: [args.value[i]] })) - // collect non-indexed args to encode them via ABI encoder and use it as data - const nonIndexedArgs = nonIndexedIndices - .map((i) => [args.type[i], args.value[i]]) - .reduce((args, [type, value]) => ({ type: [...args.type, type], value: [...args.value, value] }), { - type: [], - value: [], - }) - - const logType = topics.length + 1 // first topic is event signature - return [ - logType, - this._encode(nonIndexedArgs), - signature, - logType >= 2 ? topics[0] : '0x0', - logType >= 3 ? topics[1] : '0x0', - logType === 4 ? topics[2] : '0x0', - ] - }) - } - - _parseForwardETH(config) { - const { forwardETH = { recipient: ZERO_ADDRESS, value: 0 } } = config - return [forwardETH.recipient, forwardETH.value] - } - - _parseIsRevert(config) { - return !!config.revert - } - - _parseNextState(config) { - if (!config.state || !config.state.next) return 0 - return config.state.next + 1 - } - - _parseCurrentState(config) { - if (!config.state || !config.state.current) return undefined - return config.state.current - } - - _encode({ type, value }) { - return hre.ethers.utils.defaultAbiCoder.encode(type, value) - } - - _encodeError({ error, args }) { - const signature = this._errorSignature(error, args.type) - return signature + this._encode(args).slice(2) - } - - _errorSignature(name, argTypes) { - const fullName = `${name}(${argTypes.join(',')})` - return hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(fullName)).slice(0, 10) - } - - _eventSignature(name, argTypes) { - const fullName = `${name}(${argTypes.join(',')})` - return hre.ethers.utils.keccak256(hre.ethers.utils.toUtf8Bytes(fullName)) - } -} diff --git a/test/helpers/stubs/staking-module.stub.js b/test/helpers/stubs/staking-module.stub.js deleted file mode 100644 index 994a33d10..000000000 --- a/test/helpers/stubs/staking-module.stub.js +++ /dev/null @@ -1,61 +0,0 @@ -const { GenericStub } = require('./generic.stub') -const { FakeValidatorKeys } = require('../../helpers/signing-keys') - -class StakingModuleStub extends GenericStub { - static new() { - return GenericStub.new('IStakingModule') - } - - static async stubGetStakingModuleSummary( - stakingModuleStub, - { totalExitedValidators, totalDepositedValidators, depositableValidatorsCount }, - configOverrides = {} - ) { - await GenericStub.stub(stakingModuleStub, 'getStakingModuleSummary', { - return: { - type: ['uint256', 'uint256', 'uint256'], - value: [totalExitedValidators, totalDepositedValidators, depositableValidatorsCount], - }, - ...configOverrides, - }) - } - - /** - * @param {object} stakingModuleStub instance of GenericStub contract - * @param {object} config config for the method stub - * @param {object} config.input the input stub must return value for. When not set - * config.return value will be returned for any input - * @param {number} config.input.depositsCount the input value of the _depositsCount to trigger stub - * @param {string} config.input.calldata the input value of the _calldata to trigger stub - * @param {object} config.return the config for the return value - * @param {object} config.return.depositData the instance of the FakeValidatorKeys to return from the stub. - * If not set will be used FakeValidatorKeys instance of default length - * @param {number} config.return.depositDataLength the length of the FakeValidatorKeys instance - * to use for return value - * @param {string} config.return.publicKeysBatch the bytes batch of the public keys - * @param {string} config.return.signaturesBatch the bytes batch of the signatures - */ - static async stubObtainDepositData(stakingModuleStub, config) { - const input = config.input - ? { type: ['uint256', 'bytes'], value: [config.input.depositsCount, config.input.calldata] } - : undefined - const depositData = config.return.depositData - ? config.return.depositData - : new FakeValidatorKeys(config.return.depositDataLength) - const [defaultPublicKeysBatch, defaultSignaturesBatch] = depositData.slice() - await GenericStub.stub(stakingModuleStub, 'obtainDepositData', { - input, - return: { - type: ['bytes', 'bytes'], - value: [ - config.return.publicKeysBatch || defaultPublicKeysBatch, - config.return.signaturesBatch || defaultSignaturesBatch, - ], - }, - }) - } -} - -module.exports = { - StakingModuleStub, -} From 5893fde42a212d2605963246661a7ccacd986636 Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Sun, 19 Mar 2023 16:12:45 +0400 Subject: [PATCH 30/66] Refactor wei helper --- test/helpers/wei.js | 148 +++++++++++++++++++++----------------------- 1 file changed, 70 insertions(+), 78 deletions(-) diff --git a/test/helpers/wei.js b/test/helpers/wei.js index 7cb93aac2..776f672d7 100644 --- a/test/helpers/wei.js +++ b/test/helpers/wei.js @@ -1,98 +1,90 @@ const hre = require('hardhat') -function wei(...args) { - return parseWeiExpression(weiExpressionTag(...args)) +const ETHER_UNITS = [ + 'wei', + 'kwei', + 'mwei', + 'gwei', + 'nano', + 'nanoether', + 'micro', + 'microether', + 'milli', + 'milliether', + 'ether', + 'kether', + 'grand', + 'mether', + 'gether', + 'tether', +] + +const weiToString = (templateOrStringifiable, ...values) => processWeiTagInput(templateOrStringifiable, values) + +const weiToBigInt = (templateOrStringifiable, ...values) => BigInt(processWeiTagInput(templateOrStringifiable, values)) + +const wei = Object.assign(weiToBigInt, { + int: weiToBigInt, + str: weiToString, + min: (...values) => { + if (values.length === 0) { + throw new Error(`No arguments provided to wei.min() call`) + } + return values.reduce((min, value) => (wei.int(value) < min ? wei.int(value) : min), wei.int(values[0])) + }, + max: (...values) => { + if (values.length === 0) { + throw new Error(`No arguments provided to wei.min() call`) + } + return values.reduce((max, value) => (wei.int(value) > max ? wei.int(value) : max), wei.int(values[0])) + }, +}) + +function processWeiTagInput(templateOrStringifiable, values) { + return parseWeiExpression( + isTemplateStringArray(templateOrStringifiable) + ? templateToString(templateOrStringifiable, values) + : stringifiableToString(templateOrStringifiable) + ) } -wei.int = (...args) => { - if (args.length === 0) { - throw new Error('No arguments provided to wei.int() call') - } - - // when str is used as JS tag it first argument will be array of strings - if (Array.isArray(args[0]) && args[0].every((e) => typeof e === 'string')) { - return wei(...args) - } - - // when first argument is string, consider it as wei expression - if (typeof args[0] === 'string') { - return wei(...args) - } - - // in all other cases just cast first item to string and convert it to BigInt - return BigInt(args[0].toString()) -} - -wei.str = (...args) => { - if (args.length === 0) { - throw new Error('No arguments provided to wei.str() call') - } +function parseWeiExpression(expression) { + const [amount, unit = 'wei'] = expression + .replaceAll('_', '') // remove all _ from numbers written like '100_00' + .trim() // remove all leading and trailing spaces + .split(' ') // split amount and unit parts + .filter((v) => !!v) // remove all empty strings if value had redundant spaces between amount and unit parts + .map((v) => v.toLowerCase()) // needed for units - // when str is used as JS tag it first argument will be array of strings - if (Array.isArray(args[0]) && args[0].every((e) => typeof e === 'string')) { - return wei(...args).toString() + if (!Number.isFinite(+amount)) { + throw new Error(`Wei Parse Error: Amount "${amount}" is not a valid number`) } - // when first argument is string, consider it as wei expression - if (typeof args[0] === 'string') { - return wei(...args).toString() + if (!isValidEtherUnit(unit)) { + throw new Error(`Wei Parse Error: unsupported unit value: ${unit}`) } - // in all other cases just cast first item to string - return args[0].toString() -} - -wei.min = (...values) => { - if (values.length === 0) { - throw new Error(`No arguments provided to wei.min() call`) - } - return values.reduce((min, value) => (wei.int(value) < min ? wei.int(value) : min), wei.int(values[0])) + return hre.web3.utils.toWei(amount, unit) } -wei.max = (...values) => { - if (values.length === 0) { - throw new Error(`No arguments provided to wei.min() call`) - } - return values.reduce((max, value) => (wei.int(value) > max ? wei.int(value) : max), wei.int(values[0])) +function isValidEtherUnit(maybeUnit) { + return ETHER_UNITS.some((unit) => unit === maybeUnit) } -function weiExpressionTag(strings, ...values) { - if (!Array.isArray(strings) && typeof strings !== 'string') { - throw new Error(`wei was used with invalid arg type. Make sure that was passed valid JS template string`) - } - // when wei used not like js tag but called like regular function - // the first argument will be string instead of array of strings - if (typeof strings === 'string') { - strings = [strings] - } - - // case when wei used without arguments - if (strings.length === 1 && strings[0] === '' && values.length === 0) { - throw new Error('Empty wei tag template. Please specify expression inside wei`` tag') - } - - // combine interpolations in one expression - let expression = strings[0] - for (let i = 1; i < strings.length; ++i) { - expression += values[i - 1].toString() + strings[i] +function templateToString(template, args) { + let expression = template[0] + for (let i = 1; i < template.length; ++i) { + expression += args[i - 1].toString() + template[i] } return expression } -function parseWeiExpression(expression) { - const [amount, unit = 'wei'] = expression - .replaceAll('_', '') // remove all _ from numbers written like '100_00' - .trim() // remove all leading and trealing spaces - .split(' ') // split amount and unit parts - .filter((v) => !!v) // remove all empty strings if value had redundant spaces between amount and unit parts - - if (!Number.isFinite(+amount)) { - throw new Error(`Amount ${amount} is not a number`) - } - - return BigInt(hre.web3.utils.toWei(amount, unit.toLowerCase())) +function stringifiableToString(stringifiable) { + return stringifiable.toString() } -module.exports = { - wei, +function isTemplateStringArray(maybeTemplate) { + return !!maybeTemplate.raw && Array.isArray(maybeTemplate) && maybeTemplate.every((elem) => typeof elem === 'string') } + +module.exports = { wei } From 3fac54269e366d4fc5c4f045c6ed455ae3b9bdae Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Sun, 19 Mar 2023 16:20:11 +0400 Subject: [PATCH 31/66] Add unit tests for OssifiableProxy --- test/0.8.9/ossifiable-proxy.test.js | 233 ++++++++++++++++++++++++++++ 1 file changed, 233 insertions(+) create mode 100644 test/0.8.9/ossifiable-proxy.test.js diff --git a/test/0.8.9/ossifiable-proxy.test.js b/test/0.8.9/ossifiable-proxy.test.js new file mode 100644 index 000000000..68925cdce --- /dev/null +++ b/test/0.8.9/ossifiable-proxy.test.js @@ -0,0 +1,233 @@ +const { assert } = require('../helpers/assert') +const { contract, artifacts, network } = require('hardhat') + +const TruffleContract = require('@truffle/contract') +const { ContractStub } = require('../helpers/contract-stub') +const { ZERO_ADDRESS } = require('../helpers/constants') +const { EvmSnapshot } = require('../helpers/blockchain') + +const InitializableABI = [ + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'version', + type: 'uint256', + }, + ], + name: 'Initialized', + type: 'event', + }, + { + anonymous: false, + inputs: [], + name: 'ReceiveCalled', + type: 'event', + }, + { + inputs: [ + { + internalType: 'uint8', + name: 'version_', + type: 'uint8', + }, + ], + name: 'initialize', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'version', + outputs: [ + { + internalType: 'uint8', + name: '', + type: 'uint8', + }, + ], + stateMutability: 'view', + type: 'function', + }, +] + +const OssifiableProxy = artifacts.require('OssifiableProxy') +const InitializableContract = TruffleContract({ abi: InitializableABI }) + +contract('OssifiableProxy', ([deployer, admin, stranger]) => { + let currentImpl, newImpl, proxy, proxiedImpl + const snapshot = new EvmSnapshot(network.provider) + + before(async () => { + InitializableContract.setProvider(network.provider) + currentImpl = await ContractStub(InitializableContract).create({ from: deployer }) + newImpl = await ContractStub(InitializableContract) + .frame(0) + .on('receive', { emits: [{ name: 'ReceiveCalled' }] }) + .on('version', { return: { type: ['uint8'], value: [0] } }) + .on('initialize', { emits: [{ name: 'Initialized', args: { type: ['uint256'], value: [1] } }], nextFrame: 1 }) + + .frame(1) + .on('version', { return: { type: ['uint8'], value: [1] } }) + .create({ from: deployer }) + + proxy = await OssifiableProxy.new(currentImpl.address, admin, '0x', { from: deployer }) + proxiedImpl = await InitializableContract.at(proxy.address) + await snapshot.make() + }) + + afterEach(async () => snapshot.rollback()) + + describe('getters', () => { + it('proxy__getAdmin()', async () => { + assert.equal(await proxy.proxy__getAdmin(), admin) + }) + + it('proxy__getImplementation()', async () => { + assert.equal(await proxy.proxy__getImplementation(), currentImpl.address) + }) + + it('proxy__getIsOssified()', async () => { + assert.isFalse(await proxy.proxy__getIsOssified()) + }) + }) + + describe('proxy__ossify()', () => { + it('reverts with error "NotAdmin" when called by stranger', async () => { + await assert.reverts(proxy.proxy__ossify({ from: stranger }), 'NotAdmin()') + }) + + it('reverts with error "ProxyIsOssified" when called on ossified proxy', async () => { + // ossify proxy + await proxy.proxy__ossify({ from: admin }) + + // validate proxy is ossified + assert.isTrue(await proxy.proxy__getIsOssified()) + + await assert.reverts(proxy.proxy__ossify({ from: admin }), 'ProxyIsOssified()') + }) + + it('ossifies proxy', async () => { + const tx = await proxy.proxy__ossify({ from: admin }) + + // validate AdminChanged event was emitted + assert.emits(tx, 'AdminChanged', { previousAdmin: admin, newAdmin: ZERO_ADDRESS }) + + // validate ProxyOssified event was emitted + assert.emits(tx, 'ProxyOssified') + + // validate proxy is ossified + assert.isTrue(await proxy.proxy__getIsOssified()) + }) + }) + + describe('proxy__changeAdmin()', () => { + it('reverts with error "NotAdmin" when called by stranger', async () => { + await assert.reverts(proxy.proxy__changeAdmin(stranger, { from: stranger }), 'NotAdmin()') + }) + + it('reverts with error "ProxyIsOssified" when called on ossified proxy', async () => { + // ossify proxy + await proxy.proxy__ossify({ from: admin }) + + // validate proxy is ossified + assert.isTrue(await proxy.proxy__getIsOssified()) + + await assert.reverts(proxy.proxy__changeAdmin(stranger, { from: admin }), 'ProxyIsOssified()') + }) + + it('changes admin', async () => { + const tx = await proxy.proxy__changeAdmin(stranger, { from: admin }) + + // validate AdminChanged event was emitted + assert.emits(tx, 'AdminChanged', { previousAdmin: admin, newAdmin: stranger }) + + // validate admin was changed + assert.equal(await proxy.proxy__getAdmin(), stranger) + }) + }) + + describe('proxy__upgradeTo()', () => { + it('reverts with error "NotAdmin" called by stranger', async () => { + await assert.reverts(proxy.proxy__upgradeTo(newImpl.address, { from: stranger }), 'NotAdmin()') + }) + + it('reverts with error "ProxyIsOssified()" when called on ossified proxy', async () => { + // ossify proxy + await proxy.proxy__ossify({ from: admin }) + + // validate proxy is ossified + assert.isTrue(await proxy.proxy__getIsOssified()) + + await assert.reverts(proxy.proxy__upgradeTo(newImpl.address, { from: admin }), 'ProxyIsOssified()') + }) + + it('upgrades proxy to new implementation', async () => { + const tx = await proxy.proxy__upgradeTo(newImpl.address, { from: admin }) + + // validate Upgraded event was emitted + assert.emits(tx, 'Upgraded', { implementation: newImpl.address }) + + // validate implementation address was updated + assert.equal(await proxy.proxy__getImplementation(), newImpl.address) + }) + }) + + describe('proxy__upgradeToAndCall()', () => { + let initPayload + it('reverts with error "NotAdmin()" when called by stranger', async () => { + initPayload = newImpl.contract.methods.initialize(1).encodeABI() + await assert.reverts( + proxy.proxy__upgradeToAndCall(newImpl.address, initPayload, false, { + from: stranger, + }), + 'NotAdmin()' + ) + }) + + it('reverts with error "ProxyIsOssified()" whe called on ossified proxy', async () => { + // ossify proxy + await proxy.proxy__ossify({ from: admin }) + + // validate proxy is ossified + assert.isTrue(await proxy.proxy__getIsOssified()) + + await assert.reverts(proxy.proxy__upgradeToAndCall(newImpl.address, initPayload, false), 'ProxyIsOssified()') + }) + + it('upgrades proxy to new implementation when forceCall is false', async () => { + const tx = await proxy.proxy__upgradeToAndCall(newImpl.address, initPayload, false, { from: admin }) + + // validate Upgraded event was emitted + assert.emits(tx, 'Upgraded', { implementation: newImpl.address }) + + // validate Initialized event was emitted + assert.emits(tx, 'Initialized', { version: 1 }, { abi: InitializableABI }) + + // validate implementation address was updated + assert.equal(await proxy.proxy__getImplementation(), newImpl.address) + + // validate version was set + assert.equal(await proxiedImpl.version(), 1) + }) + + it('upgrades proxy to new implementation when forceCall is false', async () => { + const tx = await proxy.proxy__upgradeToAndCall(newImpl.address, '0x', true, { from: admin }) + + // validate Upgraded event was emitted + assert.emits(tx, 'Upgraded', { implementation: newImpl.address }) + + // validate ReceiveCalled event was emitted + assert.emits(tx, 'ReceiveCalled', {}, { abi: InitializableABI }) + + // validate implementation address was updated + assert.equal(await proxy.proxy__getImplementation(), newImpl.address) + + // validate version wasn't set + assert.equal(await proxiedImpl.version(), 0) + }) + }) +}) From 88fc35b7b1afa7bd21622cf785ed183cad425fae Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Mon, 20 Mar 2023 03:01:25 +0400 Subject: [PATCH 32/66] Add test case for incomplete exited keys reporting --- .../incomplete-exited-keys-reporting.test.js | 208 ++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 test/0.8.9/staking-router/incomplete-exited-keys-reporting.test.js diff --git a/test/0.8.9/staking-router/incomplete-exited-keys-reporting.test.js b/test/0.8.9/staking-router/incomplete-exited-keys-reporting.test.js new file mode 100644 index 000000000..792ee65c3 --- /dev/null +++ b/test/0.8.9/staking-router/incomplete-exited-keys-reporting.test.js @@ -0,0 +1,208 @@ +const { assert } = require('../../helpers/assert') +const { contract, artifacts } = require('hardhat') +const { ContractStub } = require('../../helpers/contract-stub') +const { hex, hexConcat } = require('../../helpers/utils') + +const StakingRouter = artifacts.require('StakingRouterMock') + +const sum = (...items) => items.reduce((s, v) => s + v, 0) +const packNodeOperatorIds = (nodeOperatorIds) => hexConcat(...nodeOperatorIds.map((i) => hex(i, 8))) +const packExitedValidatorCounts = (exitedValidatorsCount) => hexConcat(...exitedValidatorsCount.map((c) => hex(c, 16))) + +// Test covers the following scenario: +// 1. round i: oracle reports exited validators by staking module +// 2. round i: oracle reports exited validators by the node operator, but report is incomplete. +// Simulate a situation when all extra data can't be reported in one transaction +// 3. round i + 1: oracle reports exited validators by staking module +// 4. round i + 1: oracle reports exited validators missing in the first extra data report +contract('StakingRouter :: incomplete exited keys reporting', ([deployer, admin, lidoEOA]) => { + const deployStakingModuleStub = (firstGetStakingModuleSummary, secondGetStakingModuleSummary) => + ContractStub('IStakingModule') + .frame(0) + .on('getStakingModuleSummary', { + return: { + type: ['uint256', 'uint256', 'uint256'], + value: Object.values(firstGetStakingModuleSummary), + }, + }) + .on('updateExitedValidatorsCount', { nextFrame: 1 }) + + .frame(1) + .on('getStakingModuleSummary', { + return: { + type: ['uint256', 'uint256', 'uint256'], + value: Object.values(secondGetStakingModuleSummary), + }, + }) + .on('updateExitedValidatorsCount') + .create({ from: deployer }) + + const firstStakingModuleId = 1 + const secondStakingModuleId = 2 + + const defaultStakingModuleSummaries = { + [firstStakingModuleId]: { + totalExitedValidators: 0, + totalDepositedValidators: 30, + depositableValidatorsCount: 10, + }, + [secondStakingModuleId]: { + totalExitedValidators: 0, + totalDepositedValidators: 13, + depositableValidatorsCount: 20, + }, + } + + // oracle report data for round i + const firstOracleReport = { + byStakingModule: { [firstStakingModuleId]: 16, [secondStakingModuleId]: 11 }, + byNodeOperator: { + [firstStakingModuleId]: { nodeOperatorIds: [1, 2, 3, 4], exitedValidatorsCount: [2, 3, 4, 7] }, // full report + [secondStakingModuleId]: { nodeOperatorIds: [2, 4], exitedValidatorsCount: [1, 3] }, // partial report + }, + } + + // oracle report data for round i + 1 + const secondOracleReport = { + byStakingModule: { [secondStakingModuleId]: 11 }, + byNodeOperator: { + // deliver missing node operators data + [secondStakingModuleId]: { nodeOperatorIds: [3, 5, 6, 9], exitedValidatorsCount: [1, 2, 1, 3] }, + }, + } + + let depositContractStub, router, firstStakingModuleStub, secondStakingModuleStub + + before(async () => { + depositContractStub = await ContractStub('contracts/0.6.11/deposit_contract.sol:IDepositContract').create({ + from: deployer, + }) + router = await StakingRouter.new(depositContractStub.address, { from: deployer }) + + firstStakingModuleStub = await deployStakingModuleStub( + // return default staking module summary before first oracle report + defaultStakingModuleSummaries[firstStakingModuleId], + // after the first report, totalExitedValidators increased by sum of all exited validators + { + ...defaultStakingModuleSummaries[firstStakingModuleId], + totalExitedValidators: sum( + defaultStakingModuleSummaries[firstStakingModuleId].totalExitedValidators, + ...firstOracleReport.byNodeOperator[firstStakingModuleId].exitedValidatorsCount + ), + } + ) + + secondStakingModuleStub = await deployStakingModuleStub( + // return default staking module summary before first oracle report + defaultStakingModuleSummaries[secondStakingModuleId], + // after the first report, totalExitedValidators increased by sum of all exited validators + { + ...defaultStakingModuleSummaries[secondStakingModuleId], + totalExitedValidators: sum( + defaultStakingModuleSummaries[secondStakingModuleId].totalExitedValidators, + ...firstOracleReport.byNodeOperator[secondStakingModuleId].exitedValidatorsCount + ), + } + ) + + const wc = '0xff' + await router.initialize(admin, lidoEOA, wc, { from: deployer }) + + await router.grantRole(await router.MANAGE_WITHDRAWAL_CREDENTIALS_ROLE(), admin, { from: admin }) + await router.grantRole(await router.STAKING_MODULE_PAUSE_ROLE(), admin, { from: admin }) + await router.grantRole(await router.STAKING_MODULE_MANAGE_ROLE(), admin, { from: admin }) + await router.grantRole(await router.REPORT_EXITED_VALIDATORS_ROLE(), admin, { from: admin }) + + const addFirstStakingModuleTx = await router.addStakingModule( + 'module stub 1', + firstStakingModuleStub.address, + 100_00, // target share 100% + 10_00, // module fee 10% + 50_00, // treasury fee 50% from module fee + { from: admin } + ) + + // validate that actual staking module id equal to expected one + assert.isTrue( + addFirstStakingModuleTx.logs.some( + (e) => e.event === 'StakingModuleAdded' && e.args.stakingModuleId.toString() === firstStakingModuleId.toString() + ) + ) + + const addSecondStakingModuleTx = await router.addStakingModule( + 'module stub 2', + secondStakingModuleStub.address, + 10_00, // target share 10% + 10_00, // module fee 10% + 0, // treasury fee 0% from module fee + { from: admin } + ) + + // validate that actual staking module id equal to expected one + assert.isTrue( + addSecondStakingModuleTx.logs.some( + (e) => + e.event === 'StakingModuleAdded' && e.args.stakingModuleId.toString() === secondStakingModuleId.toString() + ) + ) + }) + + describe('test', () => { + it('round i: oracle reports exited validators by staking module', async () => { + await router.updateExitedValidatorsCountByStakingModule( + [firstStakingModuleId, secondStakingModuleId], + [ + firstOracleReport.byStakingModule[firstStakingModuleId], + firstOracleReport.byStakingModule[secondStakingModuleId], + ], + { from: admin } + ) + const [firstStakingModule, secondStakingModule] = await Promise.all([ + router.getStakingModule(firstStakingModuleId), + router.getStakingModule(secondStakingModuleId), + ]) + assert.equals(firstStakingModule.exitedValidatorsCount, firstOracleReport.byStakingModule[firstStakingModuleId]) + assert.equals(secondStakingModule.exitedValidatorsCount, firstOracleReport.byStakingModule[secondStakingModuleId]) + }) + + it('round i: oracle reports incompletely exited validators by node operator', async () => { + await router.reportStakingModuleExitedValidatorsCountByNodeOperator( + firstStakingModuleId, + packNodeOperatorIds(firstOracleReport.byNodeOperator[firstStakingModuleId].nodeOperatorIds), + packExitedValidatorCounts(firstOracleReport.byNodeOperator[firstStakingModuleId].exitedValidatorsCount), + { from: admin } + ) + + await router.reportStakingModuleExitedValidatorsCountByNodeOperator( + secondStakingModuleId, + packNodeOperatorIds(firstOracleReport.byNodeOperator[secondStakingModuleId].nodeOperatorIds), + packExitedValidatorCounts(firstOracleReport.byNodeOperator[secondStakingModuleId].exitedValidatorsCount), + { from: admin } + ) + }) + + it('round i + 1: oracle reports exited validators by staking module', async () => { + const tx = await router.updateExitedValidatorsCountByStakingModule( + [secondStakingModuleId], + [secondOracleReport.byStakingModule[secondStakingModuleId]], + { from: admin } + ) + + assert.emits(tx, 'StakingModuleExitedValidatorsIncompleteReporting', { + stakingModuleId: secondStakingModuleId, + unreportedExitedValidatorsCount: sum( + ...secondOracleReport.byNodeOperator[secondStakingModuleId].exitedValidatorsCount + ), + }) + }) + + it('round i + 1: oracle reports exited validators by node operators completely', async () => { + await router.reportStakingModuleExitedValidatorsCountByNodeOperator( + secondStakingModuleId, + packNodeOperatorIds(secondOracleReport.byNodeOperator[secondStakingModuleId].nodeOperatorIds), + packExitedValidatorCounts(secondOracleReport.byNodeOperator[secondStakingModuleId].exitedValidatorsCount), + { from: admin } + ) + }) + }) +}) From aa2875f21c01122ad5d94d4767fe18c1860125b5 Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Mon, 20 Mar 2023 03:02:13 +0400 Subject: [PATCH 33/66] Move mocha-param to devDependencies --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1d389849e..9947dc151 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "lerna": "^3.22.1", "lint-staged": ">=10", "minimatch": "^6.2.0", + "mocha-param": "^2.0.1", "node-gyp": "^8.4.1", "prettier": "^2.8.4", "solhint": "^3.3.7", @@ -133,7 +134,6 @@ "@aragon/os": "^4.4.0", "@openzeppelin/contracts": "3.4.0", "@openzeppelin/contracts-v4.4": "npm:@openzeppelin/contracts@4.4.1", - "mocha-param": "^2.0.1", "openzeppelin-solidity": "2.0.0" }, "overrides": { From 16e23ecb7476522a3d561a8bb9c81343805dab22 Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Mon, 20 Mar 2023 12:08:51 +0400 Subject: [PATCH 34/66] Add missing helper dependency --- test/helpers/contract-stub.js | 307 ++++++++++++++++++++++++++++++++++ 1 file changed, 307 insertions(+) create mode 100644 test/helpers/contract-stub.js diff --git a/test/helpers/contract-stub.js b/test/helpers/contract-stub.js new file mode 100644 index 000000000..b4107d26b --- /dev/null +++ b/test/helpers/contract-stub.js @@ -0,0 +1,307 @@ +const hre = require('hardhat') + +const MAX_UINT256 = BigInt(2n ** 256n - 1n).toString() +const EMPTY_FRAME_ID = MAX_UINT256 +const GET_STORAGE_ADDRESS_METHOD_ID = '0x00000001' + +const ContractStubStorage = hre.artifacts.require('ContractStubStorage') + +function ContractStub(artifact) { + return new ContractStubBuilder(artifact) +} + +function isTruffleContractName(maybeContractName) { + return typeof maybeContractName === 'string' +} + +function isTruffleContractFactory(maybeContractFactory) { + return typeof maybeContractFactory.new === 'function' +} + +function isTruffleContractInstance(maybeContractInstance) { + return ( + typeof maybeContractInstance.constructor === 'function' && + isTruffleContractFactory(maybeContractInstance.constructor) + ) +} + +function prepareTruffleArtifact(artifact) { + if (isTruffleContractName(artifact)) return { factory: hre.artifacts.require(artifact), instance: undefined } + if (isTruffleContractFactory(artifact)) return { factory: artifact, instance: undefined } + if (isTruffleContractInstance(artifact)) return { factory: artifact.constructor, instance: artifact } + throw new Error(`Unexpected artifact value "${artifact}"`) +} + +class ContractStubBuilder { + constructor(truffleArtifact) { + this._artifact = prepareTruffleArtifact(truffleArtifact) + this._methodNames = [] + this._stubBuildSteps = [] + this._currentFrame = EMPTY_FRAME_ID + } + + async create(txDetails) { + if (!this._artifact.instance) { + const stub = await hre.artifacts.require('ContractStub').new(GET_STORAGE_ADDRESS_METHOD_ID, txDetails) + this._artifact.instance = await this._artifact.factory.at(stub.address) + } + return this._stub(txDetails) + } + + async update(txDetails) { + await this._stub(txDetails) + } + + /** + * Stubs the method call + * + * @typedef {number | string | BN | BigInt } Numberable + * + * @typedef {Object} TypedTupleConfig - stores typed tuple value + * @property {string[]} type - types of the tuple elements + * @property {unknown[]} value - values of the tuple elements + * + * @typedef {Object} CustomErrorConfig - describes the custom error to revert with + * @property {string} name - the name of the error + * @property {TypedTupleConfig} args - the arguments of the error + * + * @typedef {Object} EventArgsConfig + * @property {string[]} type - types of the event arguments + * @property {unknown[]} value - values of the event arguments + * @property {boolean[]} indexed - array with flag whether the arg is indexed or not + * + * @typedef {Object} RevertConfig - describes the value to revert with + * @property {string=} reason - the error message to revert with + * @property {CustomErrorConfig=} error - the custom error to revert with + * + * @typedef {Object} ForwardETHConfig - the info about unconditional (even if recipient is not payable) + * ETH forwarding from the stub contract + * @property {string} recipient - address to forward ETH from the stub contract + * @property {Numberable} value - the amount of ETH to forward + * + * @typedef {Object} CallConfig - low level call method params + * @property {string} callee - address of the account to call + * @property {string=} data - msg.data to pass on call. By default no data passed + * @property {Numberable=} value - the amount of ETH to send with call. By default is 0.p + * @property {Numberable=} gas - the gas limit for the call. By default uses all gas + * + * @typedef {Object} EventConfig - the config of the event + * @property {string} name - name of the event + * @property {EventArgsConfig} args - arguments of the event + * + * @typedef {Object} MethodStubConfig + * @property {TypedTupleConfig=} input - when passed, stub will be triggered only when method is called + * with data matched input. When omitted, stub will be triggered for any call to method + * @property {TypedTupleConfig=} return - the value to return when stub is called + * @property {RevertConfig=} revert - the error to revert with when stub is called + * @property {ForwardETHConfig[]=} ethForwards - the info about unconditional (even if recipient + * is not payable) ETH forwarding from the stub contract + * @property {boolean=} traceable - whether to emit event on stub call. The default value is false + * @property {number=} nextFrame - the frame to set as active when the stub will be called. + * If not passed ContractStub stays in the same frame. + * @property {CallConfig[]=} calls - the list of external calls to make from method stub when it's triggered + * @property {EventConfig[]=} emits - the list of events to emit from method stub when it's triggered + * + * @param {string} methodName - name of the method to stub + * @param {MethodStubConfig} config - config of the method stub + */ + on(methodName, config = {}) { + this._methodNames.push(methodName) + this._stubBuildSteps.push({ currentFrame: this._currentFrame, ...config }) + return this + } + + /** + * Sets the active frame of the contract stub + * + * @param {number} frame - the number of the frame to set as active + */ + frame(frame) { + this._currentFrame = frame + return this + } + + async _stub(txDetails) { + for (let i = 0; i < this._methodNames.length; ++i) { + await this._stubMethod( + await this._getContractStubStorage(this._artifact.instance), + this._artifact.instance.abi, + this._methodNames[i], + this._stubBuildSteps[i], + txDetails + ) + } + + return this._artifact.instance + } + + async _stubMethod(contractStubStorage, abi, methodName, config, txDetails) { + const configParser = new ContractStubConfigParser() + const { currentFrame, stub } = configParser.parse(this._getMethodSignature(abi, methodName), config) + await contractStubStorage.addMethodStub(currentFrame, stub, txDetails) + } + + _getMethodSignature(abi, methodName) { + if (methodName === 'receive') return '0x' + const methodAbi = abi.filter((abi) => abi.type === 'function' && abi.name === methodName) + + if (methodAbi.length > 1) { + throw new Error('Support of methods overloading has not implemented yet') + } + return methodAbi[0].signature + } + + async _getContractStubStorage(stubInstance) { + const storageAddress = await hre.web3.eth.call({ + to: stubInstance.address, + data: GET_STORAGE_ADDRESS_METHOD_ID, + }) + return ContractStubStorage.at(storageAddress) + } +} + +class TypedTuple { + constructor(type, value) { + this.type = type + this.value = value + } + + static empty() { + return new TypedTuple([], []) + } + + static seed(type, value) { + return new TypedTuple([type], [value]) + } + + static create(type, value) { + return new TypedTuple(type, value) + } + + append(type, value) { + this.type.push(type) + this.value.push(value) + return this + } +} + +const EMPTY_TYPED_TUPLE = Object.freeze(TypedTuple.empty()) + +class ContractStubConfigParser { + parse(methodSignature, config) { + return { + currentFrame: this._parseCurrentFrame(config), + stub: [ + this._parseInput(methodSignature, config), + this._parseOutput(config), + this._parseIsRevert(config), + [ + this._parseTraceable(config), + this._parseNextFrame(config), + this._parseLogs(config), + this._parseCalls(config), + this._parseETHForwards(config), + ], + ], + } + } + + _parseInput(methodSignature, config) { + return methodSignature + this._encode(config.input || EMPTY_TYPED_TUPLE).slice(2) + } + + _parseOutput(config) { + if (config.return) { + return this._encode(config.return) + } + if (config.revert) { + return config.revert.error + ? this._encodeError(config.revert.error) + : this._encodeError({ name: 'Error', args: TypedTuple.create(['string'], [config.revert.reason || '']) }) + } + return this._encode(EMPTY_TYPED_TUPLE) + } + + _parseLogs(config) { + if (!config.emits || config.emits.length === 0) return [] + return config.emits.map((emitConfig) => { + // required field so just read it + const name = emitConfig.name + // if not passed event considered as without arguments + const args = emitConfig.args + ? { type: emitConfig.args.type, value: emitConfig.args.value } + : { type: [], value: [] } + // when indexed is passed take its values or consider all fields as non-indexed in other cases + const indexed = emitConfig.args && emitConfig.args.indexed ? emitConfig.args.indexed : args.value.map(() => false) + // filter all indexed args indices to pass them as topics + const indexedIndices = indexed.map((indexed, index) => (indexed ? index : -1)).filter((i) => i >= 0) + // filter all non-indexed args indices to pass them as data + const nonIndexedIndices = indexed.map((indexed, index) => (indexed ? -1 : index)).filter((i) => i >= 0) + + // signature of the event always goes as topic1 + const signature = this._eventSignature(name, args.type) + // collect argument into topics via ABI encoding + const topics = indexedIndices.map((i) => this._encode(TypedTuple.seed(args.type[i], args.value[i]))) + // collect non-indexed args to encode them via ABI encoder and use it as data + const nonIndexedArgs = nonIndexedIndices + .map((i) => [args.type[i], args.value[i]]) + .reduce((args, [type, value]) => args.append(type, value), TypedTuple.empty()) + + const logType = topics.length + 1 // first topic is event signature + return [ + logType, + this._encode(nonIndexedArgs), + signature, + logType >= 2 ? topics[0] : '0x0', + logType >= 3 ? topics[1] : '0x0', + logType === 4 ? topics[2] : '0x0', + ] + }) + } + + _parseETHForwards(config) { + return (config.ethForwards || []).map((forward) => [forward.recipient, forward.value.toString()]) + } + + _parseIsRevert(config) { + return !!config.revert + } + + _parseNextFrame(config) { + return config.nextFrame ?? EMPTY_FRAME_ID + } + + _parseCurrentFrame(config) { + return config.currentFrame ?? EMPTY_FRAME_ID + } + + _parseTraceable(config) { + return config.traceable ?? false + } + + _parseCalls(config) { + return (config.calls ?? []).map((call) => [call.callee, call.data ?? '0x', call.value ?? 0, call.gas ?? 0]) + } + + _encode(args) { + return hre.ethers.utils.defaultAbiCoder.encode(args.type, args.value) + } + + _encodeError(error) { + const args = error.args ?? EMPTY_TYPED_TUPLE + const signature = this._errorSignature(error.name, args.type) + return signature + this._encode(args).slice(2) + } + + _errorSignature(name, argTypes) { + const fullName = `${name}(${argTypes.join(',')})` + return hre.web3.utils.soliditySha3(fullName).slice(0, 10) + } + + _eventSignature(name, argTypes) { + const fullName = `${name}(${argTypes.join(',')})` + return hre.web3.utils.soliditySha3(fullName) + } +} + +module.exports = { ContractStub, ContractStubBuilder } From fee20139fe2222f1efe04a4435581ecffec6499c Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Mon, 20 Mar 2023 18:10:04 +0700 Subject: [PATCH 35/66] chore: better helper style --- .../staking-router/report-exited-keys.test.js | 14 ++++++------- .../staking-router-keys-reporting.test.js | 20 ++++++++++--------- ...drawal-queue-requests-finalization.test.js | 8 ++++---- test/helpers/utils.js | 6 +++--- 4 files changed, 24 insertions(+), 24 deletions(-) diff --git a/test/0.8.9/staking-router/report-exited-keys.test.js b/test/0.8.9/staking-router/report-exited-keys.test.js index 38295ab7a..d05781853 100644 --- a/test/0.8.9/staking-router/report-exited-keys.test.js +++ b/test/0.8.9/staking-router/report-exited-keys.test.js @@ -1,7 +1,7 @@ const { contract, ethers } = require('hardhat') const { assert } = require('../../helpers/assert') const { EvmSnapshot } = require('../../helpers/blockchain') -const { hexConcat, hex, ETH, contractMethodWithResult } = require('../../helpers/utils') +const { hexConcat, hex, ETH, addSendWithResult } = require('../../helpers/utils') const { deployProtocol } = require('../../helpers/protocol') const { setupNodeOperatorsRegistry } = require('../../helpers/staking-modules') @@ -26,9 +26,7 @@ contract('StakingRouter', ([admin, depositor]) => { }) router = deployed.stakingRouter - router.updateExitedValidatorsCountByStakingModuleWithResult = contractMethodWithResult( - router.updateExitedValidatorsCountByStakingModule - ) + addSendWithResult(router.updateExitedValidatorsCountByStakingModule) voting = deployed.voting.address operators = await setupNodeOperatorsRegistry(deployed, true) module2 = await setupNodeOperatorsRegistry(deployed, true) @@ -159,7 +157,7 @@ contract('StakingRouter', ([admin, depositor]) => { // update exited validators const exitValidatorsCount = 20 - const newlyExitedValidatorsCount = await router.updateExitedValidatorsCountByStakingModuleWithResult( + const newlyExitedValidatorsCount = await router.updateExitedValidatorsCountByStakingModule.sendWithResult( [module1Id], [exitValidatorsCount], { @@ -229,7 +227,7 @@ contract('StakingRouter', ([admin, depositor]) => { // //update exited validators const exitValidatorsCount = 20 - const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModuleWithResult( + const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModule.sendWithResult( [module1Id], [exitValidatorsCount], { from: admin } @@ -305,7 +303,7 @@ contract('StakingRouter', ([admin, depositor]) => { // update exited validators const exitValidatorsCount = 1 - const exitedCount = await router.updateExitedValidatorsCountByStakingModuleWithResult( + const exitedCount = await router.updateExitedValidatorsCountByStakingModule.sendWithResult( [module1Id], [exitValidatorsCount], { from: admin } @@ -337,7 +335,7 @@ contract('StakingRouter', ([admin, depositor]) => { exitedValidatorsCounts = [nextExitValidatorsCount] keysData = hexConcat(...exitedValidatorsCounts.map((c) => hex(c, 16))) - const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModuleWithResult( + const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModule.sendWithResult( [module1Id], [nextExitValidatorsCount], { from: admin } diff --git a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js index 653bec9e0..266e191c1 100644 --- a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js +++ b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js @@ -1,7 +1,7 @@ const { artifacts, contract, ethers } = require('hardhat') const { EvmSnapshot } = require('../../helpers/blockchain') const { assert } = require('../../helpers/assert') -const { hex, hexConcat, toNum, contractMethodWithResult } = require('../../helpers/utils') +const { hex, hexConcat, toNum, addSendWithResult } = require('../../helpers/utils') const { StakingModuleStub } = require('../../helpers/stubs/staking-module.stub') const StakingRouter = artifacts.require('StakingRouterMock.sol') @@ -17,9 +17,7 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { before(async () => { depositContract = await DepositContractMock.new({ from: deployer }) router = await StakingRouter.new(depositContract.address, { from: deployer }) - router.updateExitedValidatorsCountByStakingModuleWithResult = contractMethodWithResult( - router.updateExitedValidatorsCountByStakingModule - ) + addSendWithResult(router.updateExitedValidatorsCountByStakingModule) ;[module1, module2] = await Promise.all([ StakingModuleMock.new({ from: deployer }), StakingModuleMock.new({ from: deployer }), @@ -113,9 +111,13 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { }) it('reporting module 1 to have total 3 exited validators', async () => { - const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModuleWithResult([module1Id], [3], { - from: admin, - }) + const newlyExitedCount = await router.updateExitedValidatorsCountByStakingModule.sendWithResult( + [module1Id], + [3], + { + from: admin, + } + ) assert.equals(newlyExitedCount, 3) }) @@ -537,7 +539,7 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { }) it('reporting 3 exited keys total for module 1 and 2 exited keys total for module 2', async () => { - const newlyExited = await router.updateExitedValidatorsCountByStakingModuleWithResult(moduleIds, [3, 2], { + const newlyExited = await router.updateExitedValidatorsCountByStakingModule.sendWithResult(moduleIds, [3, 2], { from: admin, }) assert.equals(newlyExited, 5) @@ -898,7 +900,7 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { } // first correction - const newlyExited = await router.updateExitedValidatorsCountByStakingModuleWithResult([module1Id], [10], { + const newlyExited = await router.updateExitedValidatorsCountByStakingModule.sendWithResult([module1Id], [10], { from: admin, }) assert.equals(newlyExited, 10) diff --git a/test/0.8.9/withdrawal-queue-requests-finalization.test.js b/test/0.8.9/withdrawal-queue-requests-finalization.test.js index a1448ebbe..88f523a87 100644 --- a/test/0.8.9/withdrawal-queue-requests-finalization.test.js +++ b/test/0.8.9/withdrawal-queue-requests-finalization.test.js @@ -1,7 +1,7 @@ const { contract, ethers } = require('hardhat') const { itParam } = require('mocha-param') -const { StETH, shareRate, e18, e27, toBN, ETH, contractMethodWithResult } = require('../helpers/utils') +const { StETH, shareRate, e18, e27, toBN, ETH, addSendWithResult } = require('../helpers/utils') const { assert } = require('../helpers/assert') const { MAX_UINT256 } = require('../helpers/constants') const { EvmSnapshot } = require('../helpers/blockchain') @@ -57,7 +57,7 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { steth = deployed.steth withdrawalQueue = deployed.withdrawalQueue - withdrawalQueue.requestWithdrawalsWithResults = contractMethodWithResult(withdrawalQueue.requestWithdrawals) + addSendWithResult(withdrawalQueue.requestWithdrawals) await steth.mintShares(user, e18(10)) await steth.approve(withdrawalQueue.address, StETH(10), { from: user }) @@ -90,7 +90,7 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { }) it('works correctly on multiple calls', async () => { - const [requestId1, requestId2] = await withdrawalQueue.requestWithdrawalsWithResults([ETH(1), ETH(1)], user, { + const [requestId1, requestId2] = await withdrawalQueue.requestWithdrawals.sendWithResult([ETH(1), ETH(1)], user, { from: user, }) const calculatedBatches1 = await withdrawalQueue.calculateFinalizationBatches(shareRate(1), 10000000000, 1, [ @@ -117,7 +117,7 @@ contract('WithdrawalQueue', ([owner, daoAgent, user, anotherUser]) => { }) it('stops on maxTimestamp', async () => { - const [requestId1] = await withdrawalQueue.requestWithdrawalsWithResults([ETH(1)], user, { + const [requestId1] = await withdrawalQueue.requestWithdrawals.sendWithResult([ETH(1)], user, { from: user, }) const [status] = await withdrawalQueue.getWithdrawalStatus([requestId1]) diff --git a/test/helpers/utils.js b/test/helpers/utils.js index cdae8ce40..05b563a5a 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -170,8 +170,8 @@ function getFirstEventArgs(receipt, eventName, abi = undefined) { return events[0].args } -function contractMethodWithResult(method) { - return async (...args) => { +function addSendWithResult(method) { + method.sendWithResult = async (...args) => { const result = await method.call(...args) await method(...args) return result @@ -209,6 +209,6 @@ module.exports = { calcSharesMintedAsFees, getFirstEventArgs, calcShareRateDeltaE27, - contractMethodWithResult, + addSendWithResult, limitRebase, } From 043b41c815e8db52e8178aae9f89a05b96cd8689 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Mon, 20 Mar 2023 21:30:13 +0700 Subject: [PATCH 36/66] test: NodeOperatorsRegistry happy path exit/stuck asserts --- ...node-operators-registry-happy-path.test.js | 59 +++++++++++++++---- test/helpers/factories.js | 5 ++ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index 404442d42..a33722713 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -1,4 +1,5 @@ const { contract, web3 } = require('hardhat') +const { getEvents } = require('@aragon/contract-helpers-test') const { assert } = require('../helpers/assert') const signingKeys = require('../helpers/signing-keys') @@ -112,6 +113,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us oracle = deployed.oracle signers = deployed.signers consensusMember = signers[2].address + appManager = deployed.appManager consensusVersion = await oracle.getConsensusVersion() await consensus.removeMember(signers[4].address, 2, { from: voting.address }) @@ -297,8 +299,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us }) it('Rewards distribution', async () => { - const totalRewardsShare = web3.utils.toWei('20') - const distribution = await nor.getRewardsDistribution(totalRewardsShare) + const distribution = await nor.getRewardsDistribution(web3.utils.toWei('20')) assert.equal(distribution.shares[0], web3.utils.toWei('15')) assert.equal(distribution.shares[1], web3.utils.toWei('5')) assert.equal(distribution.recipients[0], rewards1) @@ -309,6 +310,13 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us const { refSlot } = await consensus.getCurrentFrame() const [curated] = await stakingRouter.getStakingModules() + const sharesNORBefore = +(await lido.sharesOf(nor.address)) + const sharesRewards1Before = +(await lido.sharesOf(rewards1)) + const sharesRewards2Before = +(await lido.sharesOf(rewards2)) + assert.equals(sharesNORBefore, 0) + assert.equals(sharesRewards1Before, 0) + assert.equals(sharesRewards2Before, 0) + const extraData = { exitedKeys: [{ moduleId: 1, nodeOpIds: [0], keysCounts: [2] }], stuckKeys: [{ moduleId: 1, nodeOpIds: [1], keysCounts: [1] }], @@ -321,9 +329,9 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us const reportFields = { consensusVersion, numValidators: 4, - clBalanceGwei: toBN(ETH(32 * 4)).div(E9), + clBalanceGwei: toBN(ETH(32 * 4 + 1)).div(E9), stakingModuleIdsWithNewlyExitedValidators: [curated.id], - numExitedValidatorsByStakingModule: [1], + numExitedValidatorsByStakingModule: [2], withdrawalVaultBalance: e18(0), elRewardsVaultBalance: e18(0), sharesRequestedToBurn: e18(0), @@ -350,6 +358,10 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us // TODO: [optional] assert those tied calls await oracle.submitReportData(reportItems, consensusVersion, { from: consensusMember }) + const sharesNORInMiddle = await lido.sharesOf(nor.address) + // TODO: Calculate this assert value + assert.isClose(sharesNORInMiddle, '49653579676674370', 10) + // Mentionable internal calls // AccountingOracle.submitReportExtraDataList() // -> StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished() -> NOR.onExitedAndStuckValidatorsCountsUpdated()._distributeRewards() @@ -386,30 +398,53 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us { abi: NodeOperatorsRegistry._json.abi } ) + // TODO: calculate those assert values + const rewardAmountForOperator1 = '24826789838337182' + const rewardAmountForOperator2 = '12413394919168591' + const penaltyAmountForOperator2 = rewardAmountForOperator2 + + const eventRewardsDistributed1 = getEvents(tx, 'RewardsDistributed', { + decodeForAbi: NodeOperatorsRegistry._json.abi, + })[0] + const eventRewardsDistributed2 = getEvents(tx, 'RewardsDistributed', { + decodeForAbi: NodeOperatorsRegistry._json.abi, + })[1] + const eventNodeOperatorPenalized = getEvents(tx, 'NodeOperatorPenalized', { + decodeForAbi: NodeOperatorsRegistry._json.abi, + })[0] + assert.addressEqual(eventRewardsDistributed1.args.rewardAddress, rewards1) + assert.addressEqual(eventRewardsDistributed2.args.rewardAddress, rewards2) + assert.addressEqual(eventNodeOperatorPenalized.args.recipientAddress, rewards2) + assert.isClose(eventRewardsDistributed1.args.sharesAmount, rewardAmountForOperator1, 10) + assert.isClose(eventRewardsDistributed2.args.sharesAmount, rewardAmountForOperator2, 10) + assert.isClose(eventNodeOperatorPenalized.args.sharesPenalizedAmount, penaltyAmountForOperator2, 10) + const operator1 = await nor.getNodeOperator(Operator1.id, true) const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) assert.equals(operator1.stoppedValidators, 2) assert.equals(summaryOperator1.totalExitedValidators, 2) + assert.equals(summaryOperator1.depositableValidatorsCount, 3) const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) assert.equals(summaryOperator2.stuckValidatorsCount, 1) + assert.equals(summaryOperator2.depositableValidatorsCount, 0) - // TODO: assert emits NodeOperatorPenalized - // TODO: assert emits RewardsDistributed - // TODO: assert rewards was transfered with NOR._distributeRewards() - // TODO: assert rewards was transfered to operators for his exited validators + const sharesNORAfter = await lido.sharesOf(nor.address) + const sharesRewards1After = await lido.sharesOf(rewards1) + const sharesRewards2After = await lido.sharesOf(rewards2) - // TODO: assert TargetLimit changes to zero if any key stucked. ... -> NOR._updateSummaryMaxValidatorsCount() - // assert.equals(summaryOperator2.targetValidatorsCount, 0) + assert.equals(sharesNORAfter, 0) + assert.isClose(sharesRewards1After, rewardAmountForOperator1, 10) + assert.isClose(sharesRewards2After, rewardAmountForOperator2, 10) }) it('unsafeSetExitedValidatorsCount', async () => { const [curated] = await stakingRouter.getStakingModules() const correction = { - currentModuleExitedValidatorsCount: 1, + currentModuleExitedValidatorsCount: 2, currentNodeOperatorExitedValidatorsCount: 2, currentNodeOperatorStuckValidatorsCount: 0, - newModuleExitedValidatorsCount: 1, + newModuleExitedValidatorsCount: 2, newNodeOperatorExitedValidatorsCount: 3, newNodeOperatorStuckValidatorsCount: 0, } diff --git a/test/helpers/factories.js b/test/helpers/factories.js index c080ddb22..a87939571 100644 --- a/test/helpers/factories.js +++ b/test/helpers/factories.js @@ -369,6 +369,8 @@ async function postSetup({ oracle, legacyOracle, consensusContract, + stakingModules, + burner, }) { await pool.initialize(lidoLocator.address, eip712StETH.address, { value: ETH(1) }) @@ -376,6 +378,9 @@ async function postSetup({ await oracle.grantRole(await oracle.MANAGE_CONSENSUS_CONTRACT_ROLE(), voting.address, { from: voting.address }) await oracle.grantRole(await oracle.MANAGE_CONSENSUS_VERSION_ROLE(), voting.address, { from: voting.address }) await oracle.grantRole(await oracle.SUBMIT_DATA_ROLE(), voting.address, { from: voting.address }) + for (const stakingModule of stakingModules) { + await burner.grantRole(await burner.REQUEST_BURN_SHARES_ROLE(), stakingModule.address, { from: appManager.address }) + } await legacyOracle.initialize(lidoLocator.address, consensusContract.address) From bc97a222b0325ca3062b84371d1e94acf6910a25 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Mon, 20 Mar 2023 22:11:05 +0300 Subject: [PATCH 37/66] fix: positive rebase limiter shares calculations --- .../0.8.9/lib/PositiveTokenRebaseLimiter.sol | 141 +++++++++++------- .../PositiveTokenRebaseLimiterMock.sol | 20 +-- .../positive-token-rebase-limiter.test.js | 141 +++++++++++------- test/helpers/utils.js | 19 ++- 4 files changed, 200 insertions(+), 121 deletions(-) diff --git a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol index caf199d87..7f509c71b 100644 --- a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol +++ b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol @@ -10,26 +10,60 @@ import {Math256} from "../../common/lib/Math256.sol"; * This library implements positive rebase limiter for `stETH` token. * One needs to initialize `LimiterState` with the desired parameters: * - _rebaseLimit (limiter max value, nominated in LIMITER_PRECISION_BASE) - * - _totalPooledEther (see `Lido.getTotalPooledEther()`) - * - _totalShares (see `Lido.getTotalShares()`) + * - _preTotalPooledEther (see `Lido.getTotalPooledEther()`), pre-rebase value + * - _preTotalShares (see `Lido.getTotalShares()`), pre-rebase value * * The limiter allows to account for: * - consensus layer balance updates (can be either positive or negative) * - total pooled ether changes (withdrawing funds from vaults on execution layer) - * - total shares changes (coverage application) + * - total shares changes (burning due to coverage, NOR penalization, withdrawals finalization, etc.) */ - /** - * @dev Internal limiter representation struct (storing in memory) - */ + * @dev Internal limiter representation struct (storing in memory) + */ struct TokenRebaseLimiterData { - uint256 totalPooledEther; // total pooled ether pre-rebase - uint256 totalShares; // total shares before pre-rebase - uint256 rebaseLimit; // positive rebase limit (target value) - uint256 accumulatedRebase; // accumulated rebase (previous value) + uint256 preTotalPooledEther; // pre-rebase total pooled ether + uint256 preTotalShares; // pre-rebase total shares + uint256 postTotalPooledEther; // accumulated post-rebase total pooled ether + uint256 rebaseLimit; // positive rebase limit (target value) } +/** + * + * Two-steps flow: account for total supply changes and then determine the shares allowed to be burnt. + * + * Conventions: + * R - token rebase limit (i.e, {postShareRate / preShareRate - 1} <= R); + * inc - total pooled ether increase; + * dec - total shares decrease. + * + * ### Step 1. Calculating the allowed total pooled ether changes (preTotalShares === postTotalShares) + * Used for `PositiveTokenRebaseLimiter.consumeLimit()`, `PositiveTokenRebaseLimiter.raiseLimit()`. + * + * R = ((preTotalPooledEther + inc) / preTotalShares) / ((preTotalPooledEther / preTotalShares)) - 1 + * R = inc/preTotalPooledEther + * + * isolating inc: + * + * ``` inc = R * preTotalPooledEther ``` + * + * ### Step 2. Calculating the allowed to burn shares (preTotalPooledEther != postTotalPooledEther) + * Used for `PositiveTokenRebaseLimiter.getSharesToBurnLimit()`. + * + * R = (postTotalPooledEther / (preTotalShares - dec)) / (preTotalPooledEther / preTotalShares) - 1, + * let X = postTotalPooledEther / preTotalPooledEther + * + * then: + * R = X * (preTotalShares / (preTotalShares - dec)) - 1 + * (R+1) * (preTotalShares - dec) = X * preTotalShares + * + * isolating dec: + * dec * (R + 1) = (R + 1 - X) * preTotalShares => + * + * ``` dec = preTotalShares * (R + 1 - postTotalPooledEther/preTotalPooledEther) / (R + 1) ``` + * + */ library PositiveTokenRebaseLimiter { /// @dev Precision base for the limiter (e.g.: 1e6 - 0.1%; 1e9 - 100%) uint256 public constant LIMITER_PRECISION_BASE = 10**9; @@ -37,25 +71,25 @@ library PositiveTokenRebaseLimiter { uint256 public constant UNLIMITED_REBASE = type(uint64).max; /** - * @dev Initialize the new `LimiterState` structure instance - * @param _rebaseLimit max limiter value (saturation point), see `LIMITER_PRECISION_BASE` - * @param _totalPooledEther total pooled ether, see `Lido.getTotalPooledEther()` - * @param _totalShares total shares, see `Lido.getTotalShares()` - * @return limiterState newly initialized limiter structure - */ + * @dev Initialize the new `LimiterState` structure instance + * @param _rebaseLimit max limiter value (saturation point), see `LIMITER_PRECISION_BASE` + * @param _preTotalPooledEther pre-rebase total pooled ether, see `Lido.getTotalPooledEther()` + * @param _preTotalShares pre-rebase total shares, see `Lido.getTotalShares()` + * @return limiterState newly initialized limiter structure + */ function initLimiterState( uint256 _rebaseLimit, - uint256 _totalPooledEther, - uint256 _totalShares + uint256 _preTotalPooledEther, + uint256 _preTotalShares ) internal pure returns (TokenRebaseLimiterData memory limiterState) { - if(_rebaseLimit == 0) revert TooLowTokenRebaseLimit(); - if(_rebaseLimit > UNLIMITED_REBASE) revert TooHighTokenRebaseLimit(); + if (_rebaseLimit == 0) revert TooLowTokenRebaseLimit(); + if (_rebaseLimit > UNLIMITED_REBASE) revert TooHighTokenRebaseLimit(); // special case - if(_totalPooledEther == 0) { _rebaseLimit = UNLIMITED_REBASE; } + if (_preTotalPooledEther == 0) { _rebaseLimit = UNLIMITED_REBASE; } - limiterState.totalPooledEther = _totalPooledEther; - limiterState.totalShares = _totalShares; + limiterState.postTotalPooledEther = limiterState.preTotalPooledEther = _preTotalPooledEther; + limiterState.preTotalShares = _preTotalShares; limiterState.rebaseLimit = _rebaseLimit; } @@ -65,7 +99,17 @@ library PositiveTokenRebaseLimiter { * @return true if limit is reached */ function isLimitReached(TokenRebaseLimiterData memory _limiterState) internal pure returns (bool) { - return _limiterState.accumulatedRebase == _limiterState.rebaseLimit; + if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return false; + if (_limiterState.postTotalPooledEther < _limiterState.preTotalPooledEther) return false; + + uint256 accumulatedEther = _limiterState.postTotalPooledEther - _limiterState.preTotalPooledEther; + uint256 accumulatedRebase; + + if (_limiterState.preTotalPooledEther > 0) { + accumulatedRebase = accumulatedEther * LIMITER_PRECISION_BASE / _limiterState.preTotalPooledEther; + } + + return accumulatedRebase >= _limiterState.rebaseLimit; } /** @@ -73,13 +117,9 @@ library PositiveTokenRebaseLimiter { * @param _limiterState limit repr struct */ function raiseLimit(TokenRebaseLimiterData memory _limiterState, uint256 _etherAmount) internal pure { - if(_limiterState.rebaseLimit == UNLIMITED_REBASE) { return; } - - uint256 projectedLimit = _limiterState.rebaseLimit + ( - _etherAmount * LIMITER_PRECISION_BASE - ) / _limiterState.totalPooledEther; + if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return; - _limiterState.rebaseLimit = Math256.min(projectedLimit, UNLIMITED_REBASE); + _limiterState.postTotalPooledEther -= _etherAmount; } /** @@ -93,22 +133,22 @@ library PositiveTokenRebaseLimiter { pure returns (uint256 consumedEther) { - if (_limiterState.rebaseLimit == UNLIMITED_REBASE) { - return _etherAmount; - } + if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return _etherAmount; - uint256 remainingRebase = _limiterState.rebaseLimit - _limiterState.accumulatedRebase; - uint256 remainingEther = (remainingRebase * _limiterState.totalPooledEther) / LIMITER_PRECISION_BASE; + uint256 prevPooledEther = _limiterState.postTotalPooledEther; + _limiterState.postTotalPooledEther += _etherAmount; - consumedEther = Math256.min(remainingEther, _etherAmount); + uint256 rebaseEtherLimit = + (_limiterState.rebaseLimit * _limiterState.preTotalPooledEther) / LIMITER_PRECISION_BASE; - if (consumedEther == remainingEther) { - _limiterState.accumulatedRebase = _limiterState.rebaseLimit; - } else { - _limiterState.accumulatedRebase += ( - consumedEther * LIMITER_PRECISION_BASE - ) / _limiterState.totalPooledEther; - } + _limiterState.postTotalPooledEther = Math256.min( + _limiterState.postTotalPooledEther, + _limiterState.preTotalPooledEther + rebaseEtherLimit + ); + + assert(_limiterState.postTotalPooledEther >= prevPooledEther); + + return _limiterState.postTotalPooledEther - prevPooledEther; } /** @@ -121,14 +161,15 @@ library PositiveTokenRebaseLimiter { pure returns (uint256 maxSharesToBurn) { - if (_limiterState.rebaseLimit == UNLIMITED_REBASE) { - return _limiterState.totalShares; - } + if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return _limiterState.preTotalShares; + + if (isLimitReached(_limiterState)) return 0; + + uint256 rebaseLimitPlus1 = _limiterState.rebaseLimit + LIMITER_PRECISION_BASE; + uint256 pooledEtherRate = + (_limiterState.postTotalPooledEther * LIMITER_PRECISION_BASE) / _limiterState.preTotalPooledEther; - uint256 remainingRebase = _limiterState.rebaseLimit - _limiterState.accumulatedRebase; - maxSharesToBurn = ( - _limiterState.totalShares * remainingRebase - ) / (LIMITER_PRECISION_BASE + remainingRebase); + maxSharesToBurn = (_limiterState.preTotalShares * (rebaseLimitPlus1 - pooledEtherRate)) / rebaseLimitPlus1; } error TooLowTokenRebaseLimit(); diff --git a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol index a2e33b4d7..7aaa8d572 100644 --- a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol +++ b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol @@ -18,24 +18,24 @@ contract PositiveTokenRebaseLimiterMock { external view returns ( - uint256 totalPooledEther, - uint256 totalShares, - uint256 rebaseLimit, - uint256 accumulatedRebase + uint256 preTotalPooledEther, + uint256 preTotalShares, + uint256 postTotalPooledEther, + uint256 rebaseLimit ) { - totalPooledEther = limiter.totalPooledEther; - totalShares = limiter.totalShares; + preTotalPooledEther = limiter.preTotalPooledEther; + preTotalShares = limiter.preTotalShares; + postTotalPooledEther = limiter.postTotalPooledEther; rebaseLimit = limiter.rebaseLimit; - accumulatedRebase = limiter.accumulatedRebase; } function initLimiterState( uint256 _rebaseLimit, - uint256 _totalPooledEther, - uint256 _totalShares + uint256 _preTotalPooledEther, + uint256 _preTotalShares ) external { - limiter = PositiveTokenRebaseLimiter.initLimiterState(_rebaseLimit, _totalPooledEther, _totalShares); + limiter = PositiveTokenRebaseLimiter.initLimiterState(_rebaseLimit, _preTotalPooledEther, _preTotalShares); } function isLimitReached() external view returns (bool) { diff --git a/test/0.8.9/positive-token-rebase-limiter.test.js b/test/0.8.9/positive-token-rebase-limiter.test.js index a7bebe038..1aed2f448 100644 --- a/test/0.8.9/positive-token-rebase-limiter.test.js +++ b/test/0.8.9/positive-token-rebase-limiter.test.js @@ -7,6 +7,7 @@ const { assert } = require('../helpers/assert') const PositiveTokenRebaseLimiter = artifacts.require('PositiveTokenRebaseLimiterMock.sol') const UNLIMITED_REBASE = bn(MAX_UINT64) +const e9 = bn('10').pow(bn('9')) contract('PositiveTokenRebaseLimiter', () => { let limiter, snapshot @@ -25,77 +26,77 @@ contract('PositiveTokenRebaseLimiter', () => { it('check uninitialized state', async () => { const limiterValues = await limiter.getLimiterValues() - assert.equals(limiterValues.totalPooledEther, 0) - assert.equals(limiterValues.totalShares, 0) + assert.equals(limiterValues.preTotalPooledEther, 0) + assert.equals(limiterValues.preTotalShares, 0) + assert.equals(limiterValues.postTotalPooledEther, 0) assert.equals(limiterValues.rebaseLimit, 0) - assert.equals(limiterValues.accumulatedRebase, 0) assert.isTrue(await limiter.isLimitReached()) }) it('initialization check', async () => { const rebaseLimit = UNLIMITED_REBASE.div(bn(10)) - const totalPooledEther = ETH(101) - const totalShares = ETH(75) - await limiter.initLimiterState(rebaseLimit, totalPooledEther, totalShares) + const preTotalPooledEther = ETH(101) + const preTotalShares = ETH(75) + await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) const limiterValues = await limiter.getLimiterValues() - assert.equals(limiterValues.totalPooledEther, totalPooledEther) - assert.equals(limiterValues.totalShares, totalShares) + assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.preTotalShares, preTotalShares) + assert.equals(limiterValues.postTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.rebaseLimit, rebaseLimit) - assert.equals(limiterValues.accumulatedRebase, bn(0)) assert.isFalse(await limiter.isLimitReached()) await assert.revertsWithCustomError( - limiter.initLimiterState(ETH(0), totalPooledEther, totalShares), + limiter.initLimiterState(ETH(0), preTotalPooledEther, preTotalShares), 'TooLowTokenRebaseLimit()' ) await assert.revertsWithCustomError( - limiter.initLimiterState(UNLIMITED_REBASE.add(bn(1)), totalPooledEther, totalShares), + limiter.initLimiterState(UNLIMITED_REBASE.add(bn(1)), preTotalPooledEther, preTotalShares), 'TooHighTokenRebaseLimit()' ) - await limiter.initLimiterState(UNLIMITED_REBASE, totalPooledEther, totalShares) + await limiter.initLimiterState(UNLIMITED_REBASE, preTotalPooledEther, preTotalShares) }) it('raise limit', async () => { const rebaseLimit = bn('7500') - const totalPooledEther = ETH(101) - const totalShares = ETH(75) - await limiter.initLimiterState(rebaseLimit, totalPooledEther, totalShares) + const preTotalPooledEther = ETH(101) + const preTotalShares = ETH(75) + await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) await limiter.raiseLimit(ETH(0)) const limiterValues0 = await limiter.getLimiterValues() - assert.equals(limiterValues0.totalPooledEther, totalPooledEther) - assert.equals(limiterValues0.totalShares, totalShares) + assert.equals(limiterValues0.preTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues0.preTotalShares, preTotalShares) + assert.equals(limiterValues0.postTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues0.rebaseLimit, rebaseLimit) - assert.equals(limiterValues0.accumulatedRebase, bn(0)) assert.isFalse(await limiter.isLimitReached()) await limiter.raiseLimit(ETH(1)) const limiterValuesNeg = await limiter.getLimiterValues() - assert.equals(limiterValuesNeg.totalPooledEther, totalPooledEther) - assert.equals(limiterValuesNeg.totalShares, totalShares) - assert.equals(limiterValuesNeg.rebaseLimit, bn(9908490)) - assert.equals(limiterValuesNeg.accumulatedRebase, bn(0)) + assert.equals(limiterValuesNeg.preTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValuesNeg.preTotalShares, preTotalShares) + assert.equals(limiterValuesNeg.rebaseLimit, rebaseLimit) + assert.equals(limiterValuesNeg.postTotalPooledEther, bn(preTotalPooledEther).sub(bn(ETH(1)))) assert.isFalse(await limiter.isLimitReached()) }) it('consume limit', async () => { const rebaseLimit = bn('7500') - const totalPooledEther = ETH(1000000) - const totalShares = ETH(750) - await limiter.initLimiterState(rebaseLimit, totalPooledEther, totalShares) + const preTotalPooledEther = ETH(1000000) + const preTotalShares = ETH(750) + await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) await limiter.consumeLimit(ETH(1)) assert.isFalse(await limiter.isLimitReached()) const limiterValues = await limiter.getLimiterValues() - assert.equals(limiterValues.totalPooledEther, totalPooledEther) - assert.equals(limiterValues.totalShares, totalShares) + assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.preTotalShares, preTotalShares) + assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(1)))) assert.equals(limiterValues.rebaseLimit, rebaseLimit) - assert.equals(limiterValues.accumulatedRebase, bn(1000)) assert.isFalse(await limiter.isLimitReached()) const tx = await limiter.consumeLimit(ETH(2)) @@ -114,9 +115,9 @@ contract('PositiveTokenRebaseLimiter', () => { it('raise and consume', async () => { const rebaseLimit = bn('5000') - const totalPooledEther = ETH(2000000) - const totalShares = ETH(1000000) - await limiter.initLimiterState(rebaseLimit, totalPooledEther, totalShares) + const preTotalPooledEther = ETH(2000000) + const preTotalShares = ETH(1000000) + await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) await limiter.raiseLimit(ETH(1)) assert.isFalse(await limiter.isLimitReached()) @@ -127,19 +128,27 @@ contract('PositiveTokenRebaseLimiter', () => { assert.isFalse(await limiter.isLimitReached()) const limiterValues = await limiter.getLimiterValues() - assert.equals(limiterValues.totalPooledEther, totalPooledEther) - assert.equals(limiterValues.totalShares, totalShares) - assert.equals(limiterValues.rebaseLimit, rebaseLimit.add(bn(500))) - assert.equals(limiterValues.accumulatedRebase, bn(1000)) + assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.preTotalShares, preTotalShares) + assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) + assert.equals(limiterValues.rebaseLimit, rebaseLimit) + + assert.equals(await limiter.getSharesToBurnLimit(), bn('4499977500112499437')) + + const preShareRate = bn(preTotalPooledEther).mul(e9).div(bn(preTotalShares)) + const postShareRate = bn(limiterValues.postTotalPooledEther) + .mul(e9) + .div(bn(preTotalShares).sub(await limiter.getSharesToBurnLimit())) - assert.equals(await limiter.getSharesToBurnLimit(), bn('4499979750091124589')) + const rebase = e9.mul(postShareRate).div(preShareRate).sub(e9) + assert.almostEqual(rebase, rebaseLimit, 1) }) it('raise, consume, and raise again', async () => { const rebaseLimit = bn('5000') - const totalPooledEther = ETH(2000000) - const totalShares = ETH(1000000) - await limiter.initLimiterState(rebaseLimit, totalPooledEther, totalShares) + const preTotalPooledEther = ETH(2000000) + const preTotalShares = ETH(1000000) + await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) await limiter.raiseLimit(ETH(1)) assert.isFalse(await limiter.isLimitReached()) @@ -148,30 +157,32 @@ contract('PositiveTokenRebaseLimiter', () => { assert.emits(tx, 'ReturnValue', { retValue: ETH(2) }) assert.isFalse(await limiter.isLimitReached()) - const limiterValues = await limiter.getLimiterValues() + let limiterValues = await limiter.getLimiterValues() - assert.equals(limiterValues.totalPooledEther, totalPooledEther) - assert.equals(limiterValues.totalShares, totalShares) - assert.equals(limiterValues.rebaseLimit, rebaseLimit.add(bn(500))) - assert.equals(limiterValues.accumulatedRebase, bn(1000)) + assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.preTotalShares, preTotalShares) + assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) + assert.equals(limiterValues.rebaseLimit, rebaseLimit) await limiter.raiseLimit(ETH(1)) assert.isFalse(await limiter.isLimitReached()) + limiterValues = await limiter.getLimiterValues() - assert.equals(limiterValues.totalPooledEther, totalPooledEther) - assert.equals(limiterValues.totalShares, totalShares) - assert.equals(limiterValues.rebaseLimit, rebaseLimit.add(bn(500))) - assert.equals(limiterValues.accumulatedRebase, bn(1000)) + assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.preTotalShares, preTotalShares) + assert.equals(limiterValues.postTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.rebaseLimit, rebaseLimit) assert.equals(await limiter.getSharesToBurnLimit(), bn('4999975000124999375')) }) it('zero tvl no reverts (means unlimited)', async () => { const rebaseLimit = bn('5000') - const totalPooledEther = ETH(0) - const totalShares = ETH(0) + const preTotalPooledEther = ETH(0) + const preTotalShares = ETH(0) - await limiter.initLimiterState(rebaseLimit, totalPooledEther, totalShares) + await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) + assert.equals(await limiter.getSharesToBurnLimit(), 0) await limiter.raiseLimit(ETH(0)) assert.isFalse(await limiter.isLimitReached()) @@ -190,4 +201,32 @@ contract('PositiveTokenRebaseLimiter', () => { const maxSharesToBurn = await limiter.getSharesToBurnLimit() assert.equals(maxSharesToBurn, 0) }) + + it('share rate ~1 case with huge withdrawal', async () => { + const rebaseLimit = bn('1000000') // 0.1% + const preTotalPooledEther = ETH('1000000') + const preTotalShares = ETH('1000000') + + await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) + await limiter.consumeLimit(ETH(1000)) + await limiter.raiseLimit(ETH(40000)) // withdrawal fulfillment + + assert.isFalse(await limiter.isLimitReached()) + const limiterValues = await limiter.getLimiterValues() + + assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.preTotalShares, preTotalShares) + assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).sub(bn(ETH(39000)))) + assert.equals(limiterValues.rebaseLimit, rebaseLimit) + + assert.equals(await limiter.getSharesToBurnLimit(), bn('39960039960039960039960')) + + const preShareRate = bn(preTotalPooledEther).mul(e9).div(bn(preTotalShares)) + const postShareRate = bn(limiterValues.postTotalPooledEther) + .mul(e9) + .div(bn(preTotalShares).sub(await limiter.getSharesToBurnLimit())) + + const rebase = e9.mul(postShareRate).div(preShareRate).sub(e9) + assert.almostEqual(rebase, rebaseLimit, 1) + }) }) diff --git a/test/helpers/utils.js b/test/helpers/utils.js index ee296ec8b..3f5e7392d 100644 --- a/test/helpers/utils.js +++ b/test/helpers/utils.js @@ -132,24 +132,23 @@ const calcSharesMintedAsFees = (rewards, fee, feePoints, prevTotalShares, newTot const limitRebase = (limitE9, preTotalPooledEther, preTotalShares, clBalanceUpdate, elBalanceUpdate, sharesToBurn) => { const bnE9 = toBN(e9(1)) - let accumulatedRebase = toBN(0) - const clRebase = toBN(clBalanceUpdate).mul(bnE9).div(toBN(preTotalPooledEther)) - accumulatedRebase = accumulatedRebase.add(clRebase) - if (limitE9.lte(accumulatedRebase)) { + const etherLimit = limitE9.mul(toBN(preTotalPooledEther)).div(bnE9).add(toBN(preTotalPooledEther)) + + const clRebase = toBN(preTotalPooledEther).add(toBN(clBalanceUpdate)) + if (etherLimit.lte(clRebase)) { return { elBalanceUpdate: 0, sharesToBurn: 0 } } - let remainLimit = limitE9.sub(accumulatedRebase) - const remainEther = remainLimit.mul(toBN(preTotalPooledEther)).div(bnE9) + const remainEther = etherLimit.sub(clRebase) if (remainEther.lte(toBN(elBalanceUpdate))) { return { elBalanceUpdate: remainEther, sharesToBurn: 0 } } - const elRebase = toBN(elBalanceUpdate).mul(bnE9).div(toBN(preTotalPooledEther)) - accumulatedRebase = accumulatedRebase.add(elRebase) - remainLimit = toBN(limitE9).sub(accumulatedRebase) + const postTotalPooledEther = clRebase.add(toBN(elBalanceUpdate)) + const rebaseLimitPlus1 = toBN(limitE9).add(bnE9) + const tvlRate = toBN(postTotalPooledEther).mul(bnE9).div(toBN(preTotalPooledEther)) - const remainShares = remainLimit.mul(toBN(preTotalShares)).div(bnE9.add(remainLimit)) + const remainShares = toBN(preTotalShares).mul(rebaseLimitPlus1.sub(tvlRate)).div(rebaseLimitPlus1) if (remainShares.lte(toBN(sharesToBurn))) { return { elBalanceUpdate, sharesToBurn: remainShares } From 035429dec58df551040b1c2dc0764a8db642bc4a Mon Sep 17 00:00:00 2001 From: Sam Kozin Date: Mon, 20 Mar 2023 22:03:09 +0200 Subject: [PATCH 38/66] hash consensus: improve docs --- contracts/0.8.9/oracle/HashConsensus.sol | 87 ++++++++++++++++++------ 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/contracts/0.8.9/oracle/HashConsensus.sol b/contracts/0.8.9/oracle/HashConsensus.sol index b8a949d85..301f93bcb 100644 --- a/contracts/0.8.9/oracle/HashConsensus.sol +++ b/contracts/0.8.9/oracle/HashConsensus.sol @@ -134,7 +134,9 @@ contract HashConsensus is AccessControlEnumerable { } struct ReportVariant { + // the reported hash bytes32 hash; + // how many unique members from the current set reported this hash in the current frame uint64 support; } @@ -230,8 +232,8 @@ contract HashConsensus is AccessControlEnumerable { /// Time /// - /// @notice Returns the chain configuration required to calculate - /// epoch and slot given a timestamp. + /// @notice Returns the immutable chain parameters required to calculate epoch and slot + /// given a timestamp. /// function getChainConfig() external view returns ( uint256 slotsPerEpoch, @@ -241,14 +243,31 @@ contract HashConsensus is AccessControlEnumerable { return (SLOTS_PER_EPOCH, SECONDS_PER_SLOT, GENESIS_TIME); } - /// @notice Returns the parameters required to calculate reporting frame given an epoch. + /// @notice Returns the time-related configuration. /// - function getFrameConfig() external view returns (uint256 initialEpoch, uint256 epochsPerFrame, uint256 fastLaneLengthSlots) { - return (_frameConfig.initialEpoch, _frameConfig.epochsPerFrame, _frameConfig.fastLaneLengthSlots); + /// @return initialEpoch Epoch of the frame with zero index. + /// @return epochsPerFrame Length of a frame in epochs. + /// @return fastLaneLengthSlots Length of the fast lane interval in slots; see `getIsFastLaneMember`. + /// + function getFrameConfig() external view returns ( + uint256 initialEpoch, + uint256 epochsPerFrame, + uint256 fastLaneLengthSlots + ) { + FrameConfig memory config = _frameConfig; + return (config.initialEpoch, config.epochsPerFrame, config.fastLaneLengthSlots); } /// @notice Returns the current reporting frame. /// + /// @return refSlot The frame's reference slot: if the data the consensus is being reached upon + /// includes or depends on any onchain state, this state should be queried at the + /// reference slot. If the slot contains a block, the state should include all changes + /// from that block. + /// + /// @return reportProcessingDeadlineSlot The last slot at which the report can be processed by + /// the report processor contract. + /// function getCurrentFrame() external view returns ( uint256 refSlot, uint256 reportProcessingDeadlineSlot @@ -257,13 +276,14 @@ contract HashConsensus is AccessControlEnumerable { return (frame.refSlot, frame.reportProcessingDeadlineSlot); } - /// @notice Returns the earliest possible reference slot. + /// @notice Returns the earliest possible reference slot, i.e. the reference slot of the + /// reporting frame with zero index. /// function getInitialRefSlot() external view returns (uint256) { return _getInitialFrame().refSlot; } - /// @notice Sets initial epoch given that the current initial epoch is in the future. + /// @notice Sets a new initial epoch given that the current initial epoch is in the future. /// /// @param initialEpoch The new initial epoch. /// @@ -286,6 +306,11 @@ contract HashConsensus is AccessControlEnumerable { } } + /// @notice Updates the time-related configuration. + /// + /// @param epochsPerFrame Length of a frame in epochs. + /// @param fastLaneLengthSlots Length of the fast lane interval in slots; see `getIsFastLaneMember`. + /// function setFrameConfig(uint256 epochsPerFrame, uint256 fastLaneLengthSlots) external onlyRole(MANAGE_FRAME_CONFIG_ROLE) { @@ -300,6 +325,8 @@ contract HashConsensus is AccessControlEnumerable { /// Members /// + /// @notice Returns whether the given address is currently a member of the consensus. + /// function getIsMember(address addr) external view returns (bool) { return _isMember(addr); } @@ -307,13 +334,31 @@ contract HashConsensus is AccessControlEnumerable { /// @notice Returns whether the given address is a fast lane member for the current reporting /// frame. /// - /// Fast lane members can, and expected to, submit a report during the first part of the frame - /// defined via `setFastLaneConfig`. Non-fast-lane members are only allowed to submit a report - /// after the "fast-lane" part of the frame passes. + /// Fast lane members is a subset of all members that changes each reporting frame. These + /// members can, and are expected to, submit a report during the first part of the frame called + /// the "fast lane interval" and defined via `setFrameConfig` or `setFastLaneLengthSlots`. Under + /// regular circumstances, all other members are only allowed to submit a report after the fast + /// lane interval passes. + /// + /// The fast lane subset consists of `quorum` members; selection is implemented as a sliding + /// window of the `quorum` width over member indices (mod total members). The window advances + /// by one index each reporting frame. /// - /// This is done to encourage each oracle from the full set to participate in reporting on a + /// This is done to encourage each member from the full set to participate in reporting on a /// regular basis, and identify any malfunctioning members. /// + /// With the fast lane mechanism active, it's sufficient for the monitoring to check that + /// consensus is consistently reached during the fast lane part of each frame to conclude that + /// all members are active and share the same consensus rules. + /// + /// However, there is no guarantee that, at any given time, it holds true that only the current + /// fast lane members can or were able to report during the currently-configured fast lane + /// interval of the current frame. In particular, this assumption can be violated in any frame + /// during which the members set, initial epoch, or the quorum number was changed, or the fast + /// lane interval length was increased. Thus, the fast lane mechanism should not be used for any + /// purpose other than monitoring of the members liveness, and monitoring tools should take into + /// consideration the potential irregularities within frames with any configuration changes. + /// function getIsFastLaneMember(address addr) external view returns (bool) { uint256 index1b = _memberIndices1b[addr]; unchecked { @@ -321,6 +366,9 @@ contract HashConsensus is AccessControlEnumerable { } } + /// @notice Returns all current members, together with the last reference slot each member + /// submitted a report for. + /// function getMembers() external view returns ( address[] memory addresses, uint256[] memory lastReportedRefSlots @@ -328,8 +376,10 @@ contract HashConsensus is AccessControlEnumerable { return _getMembers(false); } - /// @notice Returns the subset of oracle committee members (consisting of `quorum` items) that - /// changes on each frame. See `getIsFastLaneMember`. + /// @notice Returns the subset of the oracle committee members (consisting of `quorum` items) + /// that changes each frame. + /// + /// See `getIsFastLaneMember`. /// function getFastLaneMembers() external view returns ( address[] memory addresses, @@ -338,16 +388,9 @@ contract HashConsensus is AccessControlEnumerable { return _getMembers(true); } - /// @notice Sets the duration of the interval starting at the beginning of the frame during - /// which only the selected "fast lane" subset of oracle committee memebrs can (and expected - /// to) submit a report. - /// - /// The fast lane subset is a subset consisting of `quorum` oracles that changes on each frame. - /// This is done to encourage each oracle from the full set to participate in reporting on a - /// regular basis, and identify any malfunctioning members. + /// @notice Sets the duration of the fast lane interval of the reporting frame. /// - /// The subset selection is implemented as a sliding window of the `quorum` width over member - /// indices (mod total members). The window advances by one index each reporting frame. + /// See `getIsFastLaneMember`. /// /// @param fastLaneLengthSlots The length of the fast lane reporting interval in slots. Setting /// it to zero disables the fast lane subset, allowing any oracle to report starting from From 5a2d40f7e21f35e937c69e3e5fdfc201160b95cd Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 21 Mar 2023 03:04:27 +0700 Subject: [PATCH 39/66] test: NodeOperatorsRegistry more testcases and descriptions improvements --- ...node-operators-registry-happy-path.test.js | 837 ++++++++++-------- 1 file changed, 488 insertions(+), 349 deletions(-) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index a33722713..86fffec80 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -20,6 +20,10 @@ const E9 = toBN(10).pow(toBN(9)) const ADDRESS_1 = '0x0000000000000000000000000000000000000001' const ADDRESS_2 = '0x0000000000000000000000000000000000000002' const ADDRESS_3 = '0x0000000000000000000000000000000000000003' +const ADDRESS_4 = '0x0000000000000000000000000000000000000004' + +const NOR_ABI_GET_EV = { decodeForAbi: NodeOperatorsRegistry._json.abi } +const NOR_ABI_ASSERT_EV = { abi: NodeOperatorsRegistry._json.abi } const NODE_OPERATORS = [ { @@ -31,7 +35,6 @@ const NODE_OPERATORS = [ }, { id: 1, - isActive: false, name: 'Node operator #2', rewardAddressInitial: ADDRESS_2, totalSigningKeysCount: 15, @@ -39,17 +42,24 @@ const NODE_OPERATORS = [ }, { id: 2, - isActive: false, name: 'Node operator #3', rewardAddressInitial: ADDRESS_3, totalSigningKeysCount: 10, vettedSigningKeysCount: 5, }, + { + id: 3, + name: 'Node operator #4', + rewardAddressInitial: ADDRESS_4, + totalSigningKeysCount: 10, + vettedSigningKeysCount: 5, + }, ] const Operator1 = NODE_OPERATORS[0] const Operator2 = NODE_OPERATORS[1] const Operator3 = NODE_OPERATORS[2] +const Operator4 = NODE_OPERATORS[3] const forEachSync = async (arr, cb) => { for (let i = 0; i < arr.length; ++i) { @@ -57,7 +67,7 @@ const forEachSync = async (arr, cb) => { } } -contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, user1, nobody]) => { +contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, rewards4, user1, nobody]) => { let dsm let lido let nor @@ -74,7 +84,11 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us let signers let consensusMember - async function assertDepositCall(operatorId, callIdx, keyIdx) { + let stateTotalVetted = 0 + let stateTotalDepositable = 0 + let stateTotalDeposited = 0 + + async function assertDepositCall(callIdx, operatorId, keyIdx) { const regCall = await depositContract.calls.call(callIdx) const { key, depositSignature } = await nor.getSigningKey(operatorId, keyIdx) assert.equal(regCall.pubkey, key) @@ -83,6 +97,41 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us assert.equals(regCall.value, ETH(32)) } + async function assertOperatorDeposits(operatorData, deposited, keysLeft) { + const operator = await nor.getNodeOperator(operatorData.id, true) + const summary = await nor.getNodeOperatorSummary(operatorData.id) + assert.equals(operator.usedSigningKeys, deposited, `${operatorData.name} usedSigningKeys should be ${deposited}`) + assert.equals( + summary.totalDepositedValidators, + deposited, + `${operatorData.name} totalDepositedValidators should be ${deposited}` + ) + assert.equals( + summary.depositableValidatorsCount, + keysLeft, + `${operatorData.name} totalDepositedValidators should be ${keysLeft}` + ) + } + + async function assertTargetLimit(operatorData, isActive, limit, depositable) { + const summary = await nor.getNodeOperatorSummary(operatorData.id) + assert.equals( + summary.isTargetLimitActive, + isActive, + `${operatorData.name} isTargetLimitActive limit should be set to ${isActive}` + ) + assert.equals( + summary.targetValidatorsCount, + limit, + `${operatorData.name} targetValidatorsCount should be set to ${limit}` + ) + assert.equals( + summary.depositableValidatorsCount, + depositable, + `${operatorData.name} depositableValidatorsCount should be set to ${depositable}` + ) + } + before('deploy base app', async () => { const deployed = await deployProtocol({ stakingModulesFactory: async (protocol) => { @@ -99,7 +148,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us }, }) - rewardAddresses = [rewards1, rewards2, rewards3] + rewardAddresses = [rewards1, rewards2, rewards3, rewards4] lido = deployed.pool nor = deployed.stakingModules[0] @@ -124,419 +173,509 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, us }) describe('Happy path', () => { - it('Add node operator', async () => { - await forEachSync(NODE_OPERATORS, async (operatorData, i) => { - const initialName = `operator ${i + 1}` - const tx = await nor.addNodeOperator(initialName, operatorData.rewardAddressInitial, { from: voting.address }) - const expectedStakingLimit = 0 - - assert.emits(tx, 'NodeOperatorAdded', { - nodeOperatorId: operatorData.id, - name: initialName, - rewardAddress: operatorData.rewardAddressInitial, - stakingLimit: expectedStakingLimit, + context('Initial setup', () => { + it('Add node operator', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + const initialName = `operator ${i + 1}` + const tx = await nor.addNodeOperator(initialName, operatorData.rewardAddressInitial, { from: voting.address }) + const expectedStakingLimit = 0 + + assert.emits(tx, 'NodeOperatorAdded', { + nodeOperatorId: operatorData.id, + name: initialName, + rewardAddress: operatorData.rewardAddressInitial, + stakingLimit: expectedStakingLimit, + }) + + assert.isTrue(await nor.getNodeOperatorIsActive(operatorData.id)) + const operator = await nor.getNodeOperator(operatorData.id, true) + assert.isTrue(operator.active) + assert.equals(operator.name, initialName) + assert.equals(operator.rewardAddress, operatorData.rewardAddressInitial) + assert.equals(operator.stakingLimit, 0) + assert.equals(operator.stoppedValidators, 0) + assert.equals(operator.totalSigningKeys, 0) + assert.equals(operator.usedSigningKeys, 0) }) - assert.isTrue(await nor.getNodeOperatorIsActive(operatorData.id)) - const operator = await nor.getNodeOperator(operatorData.id, true) - assert.isTrue(operator.active) - assert.equals(operator.name, initialName) - assert.equals(operator.rewardAddress, operatorData.rewardAddressInitial) - assert.equals(operator.stakingLimit, 0) - assert.equals(operator.stoppedValidators, 0) - assert.equals(operator.totalSigningKeys, 0) - assert.equals(operator.usedSigningKeys, 0) + assert.equals(await nor.getNodeOperatorsCount(), NODE_OPERATORS.length) }) - assert.equals(await nor.getNodeOperatorsCount(), NODE_OPERATORS.length) - }) + // TODO: Move this block after keys manipulations to check how it will affect them + it('Deactivate node operator 4', async () => { + const operatorId = Operator4.id + const activeOperatorsBefore = await nor.getActiveNodeOperatorsCount() + const tx = await nor.deactivateNodeOperator(operatorId, { from: voting.address }) + const operator = await nor.getNodeOperator(operatorId, true) + const activeOperatorsAfter = await nor.getActiveNodeOperatorsCount() + + assert.isFalse(await nor.getNodeOperatorIsActive(operatorId)) + assert.isFalse(operator.active) + assert.equals(Number(activeOperatorsBefore) - 1, Number(activeOperatorsAfter)) + assert.emits(tx, 'NodeOperatorActiveSet', { nodeOperatorId: operatorId, active: false }) + }) - // TODO: Move this block after keys manipulations to check how it will affect them - it('Deactivate node operator 3', async () => { - const activeOperatorsBefore = await nor.getActiveNodeOperatorsCount() - const tx = await nor.deactivateNodeOperator(Operator3.id, { from: voting.address }) - const operator = await nor.getNodeOperator(Operator3.id, true) - const activeOperatorsAfter = await nor.getActiveNodeOperatorsCount() - - assert.isFalse(await nor.getNodeOperatorIsActive(Operator3.id)) - assert.isFalse(operator.active) - assert.equals(Number(activeOperatorsBefore) - 1, Number(activeOperatorsAfter)) - assert.emits(tx, 'NodeOperatorActiveSet', { nodeOperatorId: Operator3.id, active: false }) - }) + it('Set name', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + await nor.setNodeOperatorName(operatorData.id, operatorData.name, { from: voting.address }) + const operator = await nor.getNodeOperator(operatorData.id, true) + assert.equals(operator.name, operatorData.name) + }) + }) - it('Set name', async () => { - await forEachSync(NODE_OPERATORS, async (operatorData, i) => { - await nor.setNodeOperatorName(operatorData.id, operatorData.name, { from: voting.address }) - const operator = await nor.getNodeOperator(operatorData.id, true) - assert.equals(operator.name, operatorData.name) + it('Set reward address', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + const rewardAddress = rewardAddresses[i] + await nor.setNodeOperatorRewardAddress(operatorData.id, rewardAddress, { from: voting.address }) + const operator = await nor.getNodeOperator(operatorData.id, true) + assert.equals(operator.rewardAddress, rewardAddress) + }) }) - }) - it('Set reward address', async () => { - await forEachSync(NODE_OPERATORS, async (operatorData, i) => { - const rewardAddress = rewardAddresses[i] - await nor.setNodeOperatorRewardAddress(operatorData.id, rewardAddress, { from: voting.address }) - const operator = await nor.getNodeOperator(operatorData.id, true) - assert.equals(operator.rewardAddress, rewardAddress) + it('Add signing keys', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + const keys = new signingKeys.FakeValidatorKeys(operatorData.totalSigningKeysCount) + await nor.addSigningKeys(operatorData.id, keys.count, ...keys.slice(), { from: voting.address }) + + const operator = await nor.getNodeOperator(operatorData.id, true) + const keysCount = await nor.getTotalSigningKeyCount(operatorData.id) + const unusedKeysCount = await nor.getUnusedSigningKeyCount(operatorData.id) + assert.equals(keys.count, operator.totalSigningKeys.toNumber()) + assert.equals(keys.count, keysCount) + assert.equals(keys.count, unusedKeysCount) + + for (let i = 0; i < keys.count; ++i) { + const { key, depositSignature } = await nor.getSigningKey(operatorData.id, i) + const [expectedPublicKey, expectedSignature] = keys.get(i) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) + } + }) }) - }) - it('Add signing keys', async () => { - await forEachSync(NODE_OPERATORS, async (operatorData, i) => { - const keys = new signingKeys.FakeValidatorKeys(operatorData.totalSigningKeysCount) - await nor.addSigningKeys(operatorData.id, keys.count, ...keys.slice(), { from: voting.address }) + it('Set staking limit', async () => { + await forEachSync(NODE_OPERATORS, async (operatorData, i) => { + if (!(await nor.getNodeOperatorIsActive(operatorData.id))) return + stateTotalVetted += operatorData.vettedSigningKeysCount + await nor.setNodeOperatorStakingLimit(operatorData.id, operatorData.vettedSigningKeysCount, { + from: voting.address, + }) + const summary = await nor.getNodeOperatorSummary(operatorData.id) + const operator = await nor.getNodeOperator(operatorData.id, true) + assert.equals(operator.stakingLimit, operatorData.vettedSigningKeysCount) + assert.equals(summary.depositableValidatorsCount, operatorData.vettedSigningKeysCount) + }) - const operator = await nor.getNodeOperator(operatorData.id, true) - const keysCount = await nor.getTotalSigningKeyCount(operatorData.id) - const unusedKeysCount = await nor.getUnusedSigningKeyCount(operatorData.id) - assert.equals(keys.count, operator.totalSigningKeys.toNumber()) - assert.equals(keys.count, keysCount) - assert.equals(keys.count, unusedKeysCount) - - for (let i = 0; i < keys.count; ++i) { - const { key, depositSignature } = await nor.getSigningKey(operatorData.id, i) - const [expectedPublicKey, expectedSignature] = keys.get(i) - assert.equals(key, expectedPublicKey) - assert.equals(depositSignature, expectedSignature) - } + const stakingModuleSummary = await nor.getStakingModuleSummary() + assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted) + stateTotalDepositable = stateTotalVetted }) - }) - let stateTotalVetted = 0 - let stateTotaldeposited = 0 + it('Removing key with index less then stakingLimit will trim stakingLimit value to this border', async () => { + const operatorData = Operator1 + + const operatorBefore = await nor.getNodeOperator(operatorData.id, true) + const summaryBefore = await nor.getNodeOperatorSummary(operatorData.id) + const keysCountBefore = await nor.getTotalSigningKeyCount(operatorData.id) + const unusedKeysCountBefore = await nor.getUnusedSigningKeyCount(operatorData.id) + const keyIdxToRemove = 1 + const keyBefore = await nor.getSigningKey(operatorData.id, keyIdxToRemove) + assert.equals(operatorBefore.stakingLimit, operatorData.vettedSigningKeysCount) + + await nor.removeSigningKey(operatorData.id, keyIdxToRemove, { from: voting.address }) + + const operatorAfter = await nor.getNodeOperator(operatorData.id, true) + const summaryAfter = await nor.getNodeOperatorSummary(operatorData.id) + const keysCountAfter = await nor.getTotalSigningKeyCount(operatorData.id) + const unusedKeysCountAfter = await nor.getUnusedSigningKeyCount(operatorData.id) + const keyAfter = await nor.getSigningKey(operatorData.id, keyIdxToRemove) + + assert.equals(operatorAfter.stakingLimit, keyIdxToRemove) + assert.equals(+operatorBefore.totalSigningKeys - 1, +operatorAfter.totalSigningKeys) + assert.equals(+keysCountBefore - 1, +keysCountAfter) + assert.equals(+unusedKeysCountBefore - 1, +unusedKeysCountAfter) + assert.equals(summaryBefore.depositableValidatorsCount, operatorData.vettedSigningKeysCount) + assert.equals(summaryAfter.depositableValidatorsCount, keyIdxToRemove) + assert.notEqual(keyBefore.key, keyAfter.key) + assert.notEqual(keyBefore.depositSignature, keyAfter.depositSignature) + }) - it('Set staking limit', async () => { - await forEachSync(NODE_OPERATORS, async (operatorData, i) => { - if (!(await nor.getNodeOperatorIsActive(operatorData.id))) return + it('Set stakingLimit back after key removement', async () => { + const operatorData = Operator1 stateTotalVetted += operatorData.vettedSigningKeysCount await nor.setNodeOperatorStakingLimit(operatorData.id, operatorData.vettedSigningKeysCount, { from: voting.address, }) + const summary = await nor.getNodeOperatorSummary(operatorData.id) const operator = await nor.getNodeOperator(operatorData.id, true) assert.equals(operator.stakingLimit, operatorData.vettedSigningKeysCount) + assert.equals(summary.depositableValidatorsCount, operatorData.vettedSigningKeysCount) }) - const stakingModuleSummary = await nor.getStakingModuleSummary() - assert.equals(stakingModuleSummary.depositableValidatorsCount, stateTotalVetted) - }) + it('Set target limit to Operator 2', async () => { + const [curated] = await stakingRouter.getStakingModules() + const operatorData = Operator2 + const operatorId = operatorData.id + const targetLimitCount = 1 + + await assertTargetLimit(operatorData, false, 0, operatorData.vettedSigningKeysCount) - it('Set target limit', async () => { - const [curated] = await stakingRouter.getStakingModules() - const operatorId = Operator2.id + // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() + const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorId, true, targetLimitCount, { + from: voting.address, + }) - let summary = await nor.getNodeOperatorSummary(operatorId) - assert.equals(summary.isTargetLimitActive, false) - assert.equals(summary.targetValidatorsCount, 0) - assert.equals(summary.depositableValidatorsCount, 10) + await assertTargetLimit(operatorData, true, targetLimitCount, targetLimitCount) - // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() - const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorId, true, 1, { - from: voting.address, + assert.emits( + tx, + 'TargetValidatorsCountChanged', + { nodeOperatorId: operatorId, targetValidatorsCount: 1 }, + NOR_ABI_ASSERT_EV + ) + + stateTotalDepositable -= operatorData.vettedSigningKeysCount - targetLimitCount }) - summary = await nor.getNodeOperatorSummary(operatorId) - assert.equals(summary.isTargetLimitActive, true) - assert.equals(summary.targetValidatorsCount, 1) - assert.equals(summary.depositableValidatorsCount, 1) - - assert.emits( - tx, - 'TargetValidatorsCountChanged', - { nodeOperatorId: operatorId, targetValidatorsCount: 1 }, - { abi: NodeOperatorsRegistry._json.abi } - ) + it('Initial general summary values', async () => { + const summary = await nor.getStakingModuleSummary() + assert.equals(summary.totalExitedValidators, 0) + assert.equals(summary.totalDepositedValidators, 0) + assert.equals(summary.depositableValidatorsCount, stateTotalDepositable) + }) }) - it('Obtain deposit data', async () => { - const [curated] = await stakingRouter.getStakingModules() + context('Deposits distribution', () => { + it('Obtain deposit data', async () => { + const [curated] = await stakingRouter.getStakingModules() - const stakesDeposited = 4 - const depositedValue = ETH(32 * stakesDeposited) + const stakesDeposited = 6 + const depositedValue = ETH(32 * stakesDeposited) - await web3.eth.sendTransaction({ to: lido.address, from: user1, value: depositedValue }) + await web3.eth.sendTransaction({ to: lido.address, from: user1, value: depositedValue }) - const block = await web3.eth.getBlock('latest') - const keysOpIndex = await nor.getKeysOpIndex() + const block = await web3.eth.getBlock('latest') + const keysOpIndex = await nor.getKeysOpIndex() - DSMAttestMessage.setMessagePrefix(await dsm.ATTEST_MESSAGE_PREFIX()) + DSMAttestMessage.setMessagePrefix(await dsm.ATTEST_MESSAGE_PREFIX()) - const attest = new DSMAttestMessage(block.number, block.hash, depositRoot, curated.id, keysOpIndex) - const signatures = [ - attest.sign(guardians.privateKeys[guardians.addresses[0]]), - attest.sign(guardians.privateKeys[guardians.addresses[1]]), - ] + const attest = new DSMAttestMessage(block.number, block.hash, depositRoot, curated.id, keysOpIndex) + const signatures = [ + attest.sign(guardians.privateKeys[guardians.addresses[0]]), + attest.sign(guardians.privateKeys[guardians.addresses[1]]), + ] - // triggers flow: - // DSM.depositBufferedEther() -> Lido.deposit() -> StakingRouter.deposit() -> Module.obtainDepositData() - await dsm.depositBufferedEther(block.number, block.hash, depositRoot, curated.id, keysOpIndex, '0x', signatures) + /** + * Expected deposits fill 1 2 3 4 5 6 + * Operator 1 [ x x x ] + * Operator 2 (limit = 1) [ x ] + * Operator 3 [ x x ] + */ - stateTotaldeposited += stakesDeposited + // triggers flow: + // DSM.depositBufferedEther() -> Lido.deposit() -> StakingRouter.deposit() -> Module.obtainDepositData() + await dsm.depositBufferedEther(block.number, block.hash, depositRoot, curated.id, keysOpIndex, '0x', signatures) - const depositCallCount = await depositContract.totalCalls() - assert.equals(depositCallCount, stakesDeposited) + stateTotalDeposited += stakesDeposited - // Target Limit affects here, that's why operator 1 receives 3 deposits, while operator 2 got only 1 - await assertDepositCall(Operator1.id, 0, 0) - await assertDepositCall(Operator1.id, 1, 1) - await assertDepositCall(Operator1.id, 2, 2) - await assertDepositCall(Operator2.id, 3, 0) + const depositCallCount = await depositContract.totalCalls() + assert.equals(depositCallCount, stakesDeposited) - const stakingModuleSummary = await nor.getStakingModuleSummary() - assert.equals(stakingModuleSummary.totalDepositedValidators, stateTotaldeposited) - assert.equals(stakingModuleSummary.depositableValidatorsCount, 3) + // Target Limit affects here, that's why operator 2 receives only 1 deposit + await assertDepositCall(0, Operator1.id, 0) + await assertDepositCall(1, Operator1.id, 1) + await assertDepositCall(2, Operator1.id, 2) + await assertDepositCall(3, Operator2.id, 0) + await assertDepositCall(4, Operator3.id, 0) + await assertDepositCall(5, Operator3.id, 1) - const operator1 = await nor.getNodeOperator(Operator1.id, true) - const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) - assert.equals(operator1.usedSigningKeys, 3) - assert.equals(summaryOperator1.totalDepositedValidators, 3) - assert.equals(summaryOperator1.depositableValidatorsCount, 3) + const stakingModuleSummaryAfter = await nor.getStakingModuleSummary() + assert.equals(stakingModuleSummaryAfter.totalDepositedValidators, stateTotalDeposited) + assert.equals(stakingModuleSummaryAfter.depositableValidatorsCount, stateTotalDepositable - stateTotalDeposited) - const operator2 = await nor.getNodeOperator(Operator2.id, true) - const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) - assert.equals(operator2.usedSigningKeys, 1) - assert.equals(summaryOperator2.totalDepositedValidators, 1) - assert.equals(summaryOperator2.depositableValidatorsCount, 0) - }) + await assertOperatorDeposits(Operator1, 3, 3) + await assertOperatorDeposits(Operator2, 1, 0) + await assertOperatorDeposits(Operator3, 2, 3) - it('Rewards distribution', async () => { - const distribution = await nor.getRewardsDistribution(web3.utils.toWei('20')) - assert.equal(distribution.shares[0], web3.utils.toWei('15')) - assert.equal(distribution.shares[1], web3.utils.toWei('5')) - assert.equal(distribution.recipients[0], rewards1) - assert.equal(distribution.recipients[1], rewards2) + // TODO: assert disabled Operator 4 should not be called while depositing + }) + + it('Rewards distribution', async () => { + const distribution = await nor.getRewardsDistribution(web3.utils.toWei('30')) + assert.equal(distribution.shares[0], web3.utils.toWei('15')) + assert.equal(distribution.shares[1], web3.utils.toWei('5')) + assert.equal(distribution.shares[2], web3.utils.toWei('10')) + assert.equal(distribution.recipients[0], rewards1) + assert.equal(distribution.recipients[1], rewards2) + assert.equal(distribution.recipients[2], rewards3) + }) }) - it('Validators exiting and stuck', async () => { - const { refSlot } = await consensus.getCurrentFrame() - const [curated] = await stakingRouter.getStakingModules() - - const sharesNORBefore = +(await lido.sharesOf(nor.address)) - const sharesRewards1Before = +(await lido.sharesOf(rewards1)) - const sharesRewards2Before = +(await lido.sharesOf(rewards2)) - assert.equals(sharesNORBefore, 0) - assert.equals(sharesRewards1Before, 0) - assert.equals(sharesRewards2Before, 0) - - const extraData = { - exitedKeys: [{ moduleId: 1, nodeOpIds: [0], keysCounts: [2] }], - stuckKeys: [{ moduleId: 1, nodeOpIds: [1], keysCounts: [1] }], - } - - const extraDataItems = encodeExtraDataItems(extraData) - const extraDataList = packExtraDataList(extraDataItems) - const extraDataHash = calcExtraDataListHash(extraDataList) - - const reportFields = { - consensusVersion, - numValidators: 4, - clBalanceGwei: toBN(ETH(32 * 4 + 1)).div(E9), - stakingModuleIdsWithNewlyExitedValidators: [curated.id], - numExitedValidatorsByStakingModule: [2], - withdrawalVaultBalance: e18(0), - elRewardsVaultBalance: e18(0), - sharesRequestedToBurn: e18(0), - withdrawalFinalizationBatches: [], - simulatedShareRate: e27(1), - isBunkerMode: false, - extraDataFormat: 1, - refSlot: +refSlot, - extraDataHash, - extraDataItemsCount: 2, - } - - const reportItems = getAccountingReportDataItems(reportFields) - const reportHash = calcAccountingReportDataHash(reportItems) - - await consensus.submitReport(+refSlot, reportHash, consensusVersion, { from: consensusMember }) - - // Mentionable internal calls - // AccountingOracle.submitReportData() - // -> Lido.handleOracleReport()._processRewards()._distributeFee() - // -> StakingRouter.reportRewardsMinted() -> NOR.onRewardsMinted() - // ._transferModuleRewards()._transferShares() - // -> StakingRouter.updateExitedValidatorsCountByStakingModule() -> StakingRouter.stakingModule[id].exitedValidatorsCount = exitedCount - // TODO: [optional] assert those tied calls - await oracle.submitReportData(reportItems, consensusVersion, { from: consensusMember }) - - const sharesNORInMiddle = await lido.sharesOf(nor.address) - // TODO: Calculate this assert value - assert.isClose(sharesNORInMiddle, '49653579676674370', 10) - - // Mentionable internal calls - // AccountingOracle.submitReportExtraDataList() - // -> StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished() -> NOR.onExitedAndStuckValidatorsCountsUpdated()._distributeRewards() - // emits NOR.NodeOperatorPenalized - // emits NOR.RewardsDistributed - // -> stETH.transferShares() - // -> Burner.requestBurnShares() - // -> StakingRouter.reportStakingModuleExitedValidatorsCountByNodeOperator() -> NOR.updateExitedValidatorsCount() -> - // emits NOR.ExitedSigningKeysCountChanged - // ._saveSummarySigningKeysStats() - // ._updateSummaryMaxValidatorsCount() - // -> StakingRouter.reportStakingModuleStuckValidatorsCountByNodeOperator() -> NOR.updateStuckValidatorsCount() -> - // emits NOR.StuckPenaltyStateChanged - // ._saveOperatorStuckPenaltyStats() - // ._updateSummaryMaxValidatorsCount() - const tx = await oracle.submitReportExtraDataList(extraDataList, { from: voting.address }) - - assert.emits( - tx, - 'ExitedSigningKeysCountChanged', - { nodeOperatorId: Operator1.id, exitedValidatorsCount: 2 }, - { abi: NodeOperatorsRegistry._json.abi } - ) - - assert.emits( - tx, - 'StuckPenaltyStateChanged', - { - nodeOperatorId: Operator2.id, - stuckValidatorsCount: 1, - refundedValidatorsCount: 0, - stuckPenaltyEndTimestamp: 0, - }, - { abi: NodeOperatorsRegistry._json.abi } - ) + context('Validators exiting and stuck', () => { + it('Initial rewards state', async () => { + const sharesNOR = +(await lido.sharesOf(nor.address)) + const sharesRewards1 = +(await lido.sharesOf(rewards1)) + const sharesRewards2 = +(await lido.sharesOf(rewards2)) + const sharesRewards3 = +(await lido.sharesOf(rewards3)) + assert.equals(sharesNOR, 0) + assert.equals(sharesRewards1, 0) + assert.equals(sharesRewards2, 0) + assert.equals(sharesRewards3, 0) + }) + + let reportTx + + it('Consensus+oracle report', async () => { + const { refSlot } = await consensus.getCurrentFrame() + const [curated] = await stakingRouter.getStakingModules() + + const extraData = { + exitedKeys: [{ moduleId: 1, nodeOpIds: [0], keysCounts: [2] }], + stuckKeys: [{ moduleId: 1, nodeOpIds: [1, 2], keysCounts: [1, 1] }], + } + + const extraDataItems = encodeExtraDataItems(extraData) + const extraDataList = packExtraDataList(extraDataItems) + const extraDataHash = calcExtraDataListHash(extraDataList) + + const reportFields = { + consensusVersion, + numValidators: 6, + clBalanceGwei: toBN(ETH(32 * stateTotalDeposited + 1)).div(E9), + stakingModuleIdsWithNewlyExitedValidators: [curated.id], + numExitedValidatorsByStakingModule: [2], + withdrawalVaultBalance: e18(0), + elRewardsVaultBalance: e18(0), + sharesRequestedToBurn: e18(0), + withdrawalFinalizationBatches: [], + simulatedShareRate: e27(1), + isBunkerMode: false, + extraDataFormat: 1, + refSlot: +refSlot, + extraDataHash, + extraDataItemsCount: 2, + } + + const reportItems = getAccountingReportDataItems(reportFields) + const reportHash = calcAccountingReportDataHash(reportItems) + + await consensus.submitReport(+refSlot, reportHash, consensusVersion, { from: consensusMember }) + + // Mentionable internal calls + // AccountingOracle.submitReportData() + // -> Lido.handleOracleReport()._processRewards()._distributeFee() + // -> StakingRouter.reportRewardsMinted() -> NOR.onRewardsMinted() + // ._transferModuleRewards()._transferShares() + // -> StakingRouter.updateExitedValidatorsCountByStakingModule() -> StakingRouter.stakingModule[id].exitedValidatorsCount = exitedCount + // TODO: [optional] assert those tied calls + await oracle.submitReportData(reportItems, consensusVersion, { from: consensusMember }) + + const sharesNORInMiddle = await lido.sharesOf(nor.address) + // TODO: Calculate this assert value + assert.isClose(sharesNORInMiddle, '49767921609076843', 10) + + // Mentionable internal calls + // AccountingOracle.submitReportExtraDataList() + // -> StakingRouter.onValidatorsCountsByNodeOperatorReportingFinished() -> NOR.onExitedAndStuckValidatorsCountsUpdated()._distributeRewards() + // emits NOR.NodeOperatorPenalized + // emits NOR.RewardsDistributed + // -> stETH.transferShares() + // -> Burner.requestBurnShares() + // -> StakingRouter.reportStakingModuleExitedValidatorsCountByNodeOperator() -> NOR.updateExitedValidatorsCount() -> + // emits NOR.ExitedSigningKeysCountChanged + // ._saveSummarySigningKeysStats() + // ._updateSummaryMaxValidatorsCount() + // -> StakingRouter.reportStakingModuleStuckValidatorsCountByNodeOperator() -> NOR.updateStuckValidatorsCount() -> + // emits NOR.StuckPenaltyStateChanged + // ._saveOperatorStuckPenaltyStats() + // ._updateSummaryMaxValidatorsCount() + reportTx = await oracle.submitReportExtraDataList(extraDataList, { from: voting.address }) + }) // TODO: calculate those assert values - const rewardAmountForOperator1 = '24826789838337182' - const rewardAmountForOperator2 = '12413394919168591' + const rewardAmountForOperator1 = '12441980402269210' + const rewardAmountForOperator2 = '6220990201134605' + const rewardAmountForOperator3 = '12441980402269210' const penaltyAmountForOperator2 = rewardAmountForOperator2 + const penaltyAmountForOperator3 = rewardAmountForOperator3 - const eventRewardsDistributed1 = getEvents(tx, 'RewardsDistributed', { - decodeForAbi: NodeOperatorsRegistry._json.abi, - })[0] - const eventRewardsDistributed2 = getEvents(tx, 'RewardsDistributed', { - decodeForAbi: NodeOperatorsRegistry._json.abi, - })[1] - const eventNodeOperatorPenalized = getEvents(tx, 'NodeOperatorPenalized', { - decodeForAbi: NodeOperatorsRegistry._json.abi, - })[0] - assert.addressEqual(eventRewardsDistributed1.args.rewardAddress, rewards1) - assert.addressEqual(eventRewardsDistributed2.args.rewardAddress, rewards2) - assert.addressEqual(eventNodeOperatorPenalized.args.recipientAddress, rewards2) - assert.isClose(eventRewardsDistributed1.args.sharesAmount, rewardAmountForOperator1, 10) - assert.isClose(eventRewardsDistributed2.args.sharesAmount, rewardAmountForOperator2, 10) - assert.isClose(eventNodeOperatorPenalized.args.sharesPenalizedAmount, penaltyAmountForOperator2, 10) - - const operator1 = await nor.getNodeOperator(Operator1.id, true) - const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) - assert.equals(operator1.stoppedValidators, 2) - assert.equals(summaryOperator1.totalExitedValidators, 2) - assert.equals(summaryOperator1.depositableValidatorsCount, 3) - - const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) - assert.equals(summaryOperator2.stuckValidatorsCount, 1) - assert.equals(summaryOperator2.depositableValidatorsCount, 0) - - const sharesNORAfter = await lido.sharesOf(nor.address) - const sharesRewards1After = await lido.sharesOf(rewards1) - const sharesRewards2After = await lido.sharesOf(rewards2) - - assert.equals(sharesNORAfter, 0) - assert.isClose(sharesRewards1After, rewardAmountForOperator1, 10) - assert.isClose(sharesRewards2After, rewardAmountForOperator2, 10) - }) + it('Events should be emitted after exit/stuck report', async () => { + const tx = reportTx - it('unsafeSetExitedValidatorsCount', async () => { - const [curated] = await stakingRouter.getStakingModules() - const correction = { - currentModuleExitedValidatorsCount: 2, - currentNodeOperatorExitedValidatorsCount: 2, - currentNodeOperatorStuckValidatorsCount: 0, - newModuleExitedValidatorsCount: 2, - newNodeOperatorExitedValidatorsCount: 3, - newNodeOperatorStuckValidatorsCount: 0, - } - - // StakingRouter.unsafeSetExitedValidatorsCount() -> NOR.onExitedAndStuckValidatorsCountsUpdated()._distributeRewards() - // emits NOR.NodeOperatorPenalized - // emits NOR.RewardsDistributed - // -> stETH.transferShares() - // -> Burner.requestBurnShares() - await stakingRouter.unsafeSetExitedValidatorsCount(curated.id, Operator1.id, true, correction, { - from: voting.address, + assert.emits( + tx, + 'ExitedSigningKeysCountChanged', + { nodeOperatorId: Operator1.id, exitedValidatorsCount: 2 }, + NOR_ABI_ASSERT_EV + ) + + assert.emits( + tx, + 'StuckPenaltyStateChanged', + { + nodeOperatorId: Operator2.id, + stuckValidatorsCount: 1, + refundedValidatorsCount: 0, + stuckPenaltyEndTimestamp: 0, + }, + NOR_ABI_ASSERT_EV + ) + + const eventRewardsDistributed1 = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV)[0] + const eventRewardsDistributed2 = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV)[1] + const eventRewardsDistributed3 = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV)[2] + const eventOperator2Penalized = getEvents(tx, 'NodeOperatorPenalized', NOR_ABI_GET_EV)[0] + const eventOperator3Penalized = getEvents(tx, 'NodeOperatorPenalized', NOR_ABI_GET_EV)[1] + assert.addressEqual(eventRewardsDistributed1.args.rewardAddress, rewards1) + assert.addressEqual(eventRewardsDistributed2.args.rewardAddress, rewards2) + assert.addressEqual(eventRewardsDistributed3.args.rewardAddress, rewards3) + assert.addressEqual(eventOperator2Penalized.args.recipientAddress, rewards2) + assert.addressEqual(eventOperator3Penalized.args.recipientAddress, rewards3) + assert.isClose(eventRewardsDistributed1.args.sharesAmount, rewardAmountForOperator1, 10) + assert.isClose(eventRewardsDistributed2.args.sharesAmount, rewardAmountForOperator2, 10) + assert.isClose(eventRewardsDistributed3.args.sharesAmount, rewardAmountForOperator3, 10) + assert.isClose(eventOperator2Penalized.args.sharesPenalizedAmount, penaltyAmountForOperator2, 10) + assert.isClose(eventOperator3Penalized.args.sharesPenalizedAmount, penaltyAmountForOperator3, 10) }) - const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) - assert.equals(summaryOperator1.totalExitedValidators, 3) + it('Operator summaries after exit/stuck report', async () => { + const operator1 = await nor.getNodeOperator(Operator1.id, true) + const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) + assert.equals(operator1.stoppedValidators, 2) + assert.equals(summaryOperator1.totalExitedValidators, 2) + assert.equals(summaryOperator1.depositableValidatorsCount, 3) - // TODO: assert emits NOR.NodeOperatorPenalized - // TODO: assert emits NOR.RewardsDistributed - // TODO: assert rewards was transfered with NOR._distributeRewards() - // TODO: assert rewards was transfered to operators for his exited validators - }) + const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) + assert.equals(summaryOperator2.stuckValidatorsCount, 1) + assert.equals(summaryOperator2.depositableValidatorsCount, 0) - /** - * TODO: TargetLimit allows to deposit after exit - */ + const summaryOperator3 = await nor.getNodeOperatorSummary(Operator3.id) + assert.equals(summaryOperator3.stuckValidatorsCount, 1) + assert.equals(summaryOperator3.depositableValidatorsCount, 0) - it('Disable TargetLimit', async () => { - const [curated] = await stakingRouter.getStakingModules() - const operatorId = Operator2.id + const sharesNORAfter = await lido.sharesOf(nor.address) + const sharesRewards1After = await lido.sharesOf(rewards1) + const sharesRewards2After = await lido.sharesOf(rewards2) + const sharesRewards3After = await lido.sharesOf(rewards3) - let summary = await nor.getNodeOperatorSummary(operatorId) - assert.equals(summary.isTargetLimitActive, true) - assert.equals(summary.targetValidatorsCount, 1) - assert.equals(summary.depositableValidatorsCount, 0) + assert.isClose(sharesNORAfter, 0, 10) + assert.isClose(sharesRewards1After, rewardAmountForOperator1, 10) + assert.isClose(sharesRewards2After, rewardAmountForOperator2, 10) + assert.isClose(sharesRewards3After, rewardAmountForOperator3, 10) - // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() - const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorId, false, 0, { - from: voting.address, + // TODO: Assert disabled Operator 4 to be untouched }) - - summary = await nor.getNodeOperatorSummary(operatorId) - assert.equals(summary.isTargetLimitActive, false) - assert.equals(summary.targetValidatorsCount, 0) - // TODO: assert.equals(summary.depositableValidatorsCount, ?) - - assert.emits( - tx, - 'TargetValidatorsCountChanged', - { nodeOperatorId: operatorId, targetValidatorsCount: 0 }, - { abi: NodeOperatorsRegistry._json.abi } - ) }) - /** - * Deposit again after disabling TargetLimit - */ + context('Updating state unsafely', () => { + it('unsafeSetExitedValidatorsCount', async () => { + // const stakesDeposited = 6 + // const depositedValue = ETH(32 * stakesDeposited) + // await web3.eth.sendTransaction({ to: lido.address, from: user1, value: depositedValue }) + + const [curated] = await stakingRouter.getStakingModules() + const correction = { + currentModuleExitedValidatorsCount: 2, + currentNodeOperatorExitedValidatorsCount: 2, + currentNodeOperatorStuckValidatorsCount: 0, + newModuleExitedValidatorsCount: 3, + newNodeOperatorExitedValidatorsCount: 3, + newNodeOperatorStuckValidatorsCount: 0, + } + + // StakingRouter.unsafeSetExitedValidatorsCount() -> NOR.onExitedAndStuckValidatorsCountsUpdated()._distributeRewards() + // emits NOR.NodeOperatorPenalized + // emits NOR.RewardsDistributed + // -> stETH.transferShares() + // -> Burner.requestBurnShares() + const tx = await stakingRouter.unsafeSetExitedValidatorsCount(curated.id, Operator1.id, true, correction, { + from: voting.address, + }) + + const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) + assert.equals(summaryOperator1.totalExitedValidators, 3) - it('Remove one signing key', async () => { - const operatorId = Operator1.id + const events = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV) + console.log(events) + // const eventRewardsDistributed1 = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV) + // const eventOperator2Penalized = getEvents(tx, 'NodeOperatorPenalized', NOR_ABI_GET_EV) - const operatorBefore = await nor.getNodeOperator(operatorId, true) - const keysCountBefore = await nor.getTotalSigningKeyCount(operatorId) - const unusedKeysCountBefore = await nor.getUnusedSigningKeyCount(operatorId) + // TODO: assert emits NOR.NodeOperatorPenalized + // TODO: assert emits NOR.RewardsDistributed + // TODO: assert rewards was transfered with NOR._distributeRewards() + // TODO: assert rewards was transfered to operators for his exited validators + }) + }) - const keyIdxToRemove = +operatorBefore.usedSigningKeys - const keyBefore = await nor.getSigningKey(operatorId, keyIdxToRemove) + context('Keys and limits settings tweaks', () => { + /** + * TODO: TargetLimit allows to deposit after exit + */ - await nor.removeSigningKey(operatorId, keyIdxToRemove, { from: voting.address }) + it('Disable TargetLimit', async () => { + const [curated] = await stakingRouter.getStakingModules() + const operatorData = Operator2 - const operatorAfter = await nor.getNodeOperator(operatorId, true) - const keysCountAfter = await nor.getTotalSigningKeyCount(operatorId) - const unusedKeysCountAfter = await nor.getUnusedSigningKeyCount(operatorId) - const keyAfter = await nor.getSigningKey(operatorId, keyIdxToRemove) + await assertTargetLimit(operatorData, true, 1, 0) + + // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() + const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorData.id, false, 0, { + from: voting.address, + }) - assert.equals(+operatorBefore.totalSigningKeys - 1, +operatorAfter.totalSigningKeys) - assert.equals(+keysCountBefore - 1, +keysCountAfter) - assert.equals(+unusedKeysCountBefore - 1, +unusedKeysCountAfter) - assert.notEqual(keyBefore.key, keyAfter.key) - assert.notEqual(keyBefore.depositSignature, keyAfter.depositSignature) + // keysLeft still zero because of stucked key + await assertTargetLimit(operatorData, false, 0, 0) - // TODO: Why operatorBefore is 6 here?? - // console.log(+operatorBefore.stakingLimit) - // console.log(+operatorAfter.stakingLimit) + assert.emits( + tx, + 'TargetValidatorsCountChanged', + { nodeOperatorId: operatorData.id, targetValidatorsCount: 0 }, + NOR_ABI_ASSERT_EV + ) + }) - // TODO: assert that staking limit was changed - // TODO: check if we need to assert target limit + /** + * TODO: Deposit again after disabling TargetLimit + */ + + it('Remove multiple signing keys', async () => { + const operatorData = Operator1 + + const operatorBefore = await nor.getNodeOperator(operatorData.id, true) + const summaryBefore = await nor.getNodeOperatorSummary(operatorData.id) + const keysCountBefore = await nor.getTotalSigningKeyCount(operatorData.id) + const unusedKeysCountBefore = await nor.getUnusedSigningKeyCount(operatorData.id) + const keyIdxToRemove = +operatorBefore.usedSigningKeys + 1 + const keysCountToRemove = 2 + const key1Before = await nor.getSigningKey(operatorData.id, keyIdxToRemove) + const key2Before = await nor.getSigningKey(operatorData.id, keyIdxToRemove + 1) + + assert.equals(operatorBefore.stakingLimit, operatorData.vettedSigningKeysCount) + + await nor.removeSigningKeys(operatorData.id, keyIdxToRemove, keysCountToRemove, { from: voting.address }) + + const operatorAfter = await nor.getNodeOperator(operatorData.id, true) + const summaryAfter = await nor.getNodeOperatorSummary(operatorData.id) + const keysCountAfter = await nor.getTotalSigningKeyCount(operatorData.id) + const unusedKeysCountAfter = await nor.getUnusedSigningKeyCount(operatorData.id) + const key1After = await nor.getSigningKey(operatorData.id, keyIdxToRemove) + const key2After = await nor.getSigningKey(operatorData.id, keyIdxToRemove + 1) + + assert.equals(operatorAfter.stakingLimit, keyIdxToRemove) + assert.equals(+operatorBefore.totalSigningKeys - keysCountToRemove, +operatorAfter.totalSigningKeys) + assert.equals(+keysCountBefore - keysCountToRemove, +keysCountAfter) + assert.equals(+unusedKeysCountBefore - keysCountToRemove, +unusedKeysCountAfter) + assert.equals( + +summaryBefore.depositableValidatorsCount - keysCountToRemove, + +summaryAfter.depositableValidatorsCount + ) + assert.notEqual(key1Before.key, key1After.key) + assert.notEqual(key2Before.key, key2After.key) + assert.notEqual(key1Before.depositSignature, key1After.depositSignature) + assert.notEqual(key2Before.depositSignature, key2After.depositSignature) + }) }) - // TODO: - // it('Remove multiple signing keys', async () => { - // }) - // TODO: NOR....() // Deactivate Operator that was in use before // Make another deposit to check that deactivated node operator will not get deposit From cf231c465f6ef95d8e5032c20a5e0ff04269d3ce Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Tue, 21 Mar 2023 05:05:50 +0400 Subject: [PATCH 40/66] Revert from try catch on empty revert data --- contracts/0.8.9/StakingRouter.sol | 21 +++++++++++++++++++ lib/abi/StakingRouter.json | 2 +- .../staking-router-keys-reporting.test.js | 7 +++++++ .../staking-router/staking-router.test.js | 14 +++++++++++++ test/helpers/contract-stub.js | 11 +++++----- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 8361273eb..08d7d3ed2 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -54,6 +54,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version error InvalidDepositsValue(uint256 etherValue, uint256 depositsCount); error StakingModuleAddressExists(); error ArraysLengthMismatch(uint256 firstArrayLength, uint256 secondArrayLength); + error OutOfGas(); enum StakingModuleStatus { Active, // deposits and rewards allowed @@ -289,6 +290,12 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version address moduleAddr = _getStakingModuleById(_stakingModuleIds[i]).stakingModuleAddress; try IStakingModule(moduleAddr).onRewardsMinted(_totalShares[i]) {} catch (bytes memory lowLevelRevertData) { + /// @dev This check is required to prevent incorrect gas estimation of the method. + /// Without it, Ethereum nodes that use binary search for gas estimation may + /// return an invalid value when the onRewardsMinted() reverts because of the + /// "out of gas" error. Here we assume that the onRewardsMinted() method doesn't + /// have reverts with empty error data except "out of gas". + if (lowLevelRevertData.length == 0) revert OutOfGas(); emit RewardsMintedReportFailed( _stakingModuleIds[i], lowLevelRevertData @@ -468,6 +475,13 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version // oracle finished updating exited validators for all node ops try moduleContract.onExitedAndStuckValidatorsCountsUpdated() {} catch (bytes memory lowLevelRevertData) { + /// @dev This check is required to prevent incorrect gas estimation of the method. + /// Without it, Ethereum nodes that use binary search for gas estimation may + /// return an invalid value when the onExitedAndStuckValidatorsCountsUpdated() + /// reverts because of the "out of gas" error. Here we assume that the + /// onExitedAndStuckValidatorsCountsUpdated() method doesn't have reverts with + /// empty error data except "out of gas". + if (lowLevelRevertData.length == 0) revert OutOfGas(); emit ExitedAndStuckValidatorsCountsUpdateFailed( stakingModule.id, lowLevelRevertData @@ -1056,6 +1070,13 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version try IStakingModule(stakingModule.stakingModuleAddress) .onWithdrawalCredentialsChanged() {} catch (bytes memory lowLevelRevertData) { + /// @dev This check is required to prevent incorrect gas estimation of the method. + /// Without it, Ethereum nodes that use binary search for gas estimation may + /// return an invalid value when the onWithdrawalCredentialsChanged() + /// reverts because of the "out of gas" error. Here we assume that the + /// onWithdrawalCredentialsChanged() method doesn't have reverts with + /// empty error data except "out of gas". + if (lowLevelRevertData.length == 0) revert OutOfGas(); _setStakingModuleStatus(stakingModule, StakingModuleStatus.DepositsPaused); emit WithdrawalsCredentialsChangeFailed(stakingModule.id, lowLevelRevertData); } diff --git a/lib/abi/StakingRouter.json b/lib/abi/StakingRouter.json index 265201b94..aa9e55205 100644 --- a/lib/abi/StakingRouter.json +++ b/lib/abi/StakingRouter.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"OutOfGas","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js index 0c7ee9b3e..52f247a11 100644 --- a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js +++ b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js @@ -504,6 +504,13 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { stakingModuleId, lowLevelRevertData: '0x4e487b710000000000000000000000000000000000000000000000000000000000000001', }) + + // staking module will revert with out of gas error (revert data is empty bytes) + await ContractStub(buggedStakingModule) + .on('onExitedAndStuckValidatorsCountsUpdated', { revert: { reason: 'outOfGas' } }) + .update({ from: deployer }) + + await assert.reverts(router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }), 'OutOfGas()') }) }) diff --git a/test/0.8.9/staking-router/staking-router.test.js b/test/0.8.9/staking-router/staking-router.test.js index d00dc4d92..d3db490d9 100644 --- a/test/0.8.9/staking-router/staking-router.test.js +++ b/test/0.8.9/staking-router/staking-router.test.js @@ -351,6 +351,13 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { ) assert.isTrue(await router.getStakingModuleIsDepositsPaused(stakingModuleId)) + + // staking module will revert with out of gas error (revert data is empty bytes) + await ContractStub(buggedStakingModule) + .on('onWithdrawalCredentialsChanged', { revert: { reason: 'outOfGas' } }) + .update({ from: deployer }) + + await assert.reverts(router.setWithdrawalCredentials(newWC, { from: appManager }), 'OutOfGas()') }) }) @@ -971,6 +978,13 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { stakingModuleId: stakingModuleWithBugId, lowLevelRevertData: [errorMethodId, ...errorMessageEncoded].join(''), }) + + // staking module will revert with out of gas error (revert data is empty bytes) + await ContractStub(buggedStakingModule) + .on('onRewardsMinted', { revert: { reason: 'outOfGas' } }) + .update({ from: deployer }) + + await assert.reverts(router.reportRewardsMinted(stakingModuleIds, totalShares, { from: admin }), 'OutOfGas()') }) }) diff --git a/test/helpers/contract-stub.js b/test/helpers/contract-stub.js index b4107d26b..9ce10a777 100644 --- a/test/helpers/contract-stub.js +++ b/test/helpers/contract-stub.js @@ -213,11 +213,12 @@ class ContractStubConfigParser { _parseOutput(config) { if (config.return) { return this._encode(config.return) - } - if (config.revert) { - return config.revert.error - ? this._encodeError(config.revert.error) - : this._encodeError({ name: 'Error', args: TypedTuple.create(['string'], [config.revert.reason || '']) }) + } else if (config.revert && config.revert.reason === 'outOfGas') { + return this._encode(EMPTY_TYPED_TUPLE) + } else if (config.revert && config.revert.reason !== undefined) { + return this._encodeError({ name: 'Error', args: TypedTuple.create(['string'], [config.revert.reason]) }) + } else if (config.revert && config.revert.error) { + return this._encodeError(config.revert.error) } return this._encode(EMPTY_TYPED_TUPLE) } From 016e044eaf6f92eb09216f30f94ebaa818793383 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Tue, 21 Mar 2023 13:28:44 +0700 Subject: [PATCH 41/66] feat: wq gas stats --- test/0.8.9/withdrawal-queue-gas.test.js | 150 ++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 test/0.8.9/withdrawal-queue-gas.test.js diff --git a/test/0.8.9/withdrawal-queue-gas.test.js b/test/0.8.9/withdrawal-queue-gas.test.js new file mode 100644 index 000000000..7c2089b7a --- /dev/null +++ b/test/0.8.9/withdrawal-queue-gas.test.js @@ -0,0 +1,150 @@ +const { contract } = require('hardhat') +const { bn } = require('@aragon/contract-helpers-test') + +const { ETH, StETH, shares } = require('../helpers/utils') +const { setBalance } = require('../helpers/blockchain') +const { deployWithdrawalQueue } = require('./withdrawal-queue-deploy.test') + +contract('WithdrawalQueue', ([owner, user]) => { + let wq, steth, defaultShareRate, belowShareRate, aboveShareRate + + const currentRate = async () => + bn(await steth.getTotalPooledEther()) + .mul(bn(10).pow(bn(27))) + .div(await steth.getTotalShares()) + + before('Deploy', async () => { + const deployed = await deployWithdrawalQueue({ + stethOwner: owner, + queueAdmin: owner, + queuePauser: owner, + queueResumer: owner, + queueFinalizer: owner, + }) + + steth = deployed.steth + wq = deployed.withdrawalQueue + + await steth.setTotalPooledEther(ETH(600)) + await setBalance(steth.address, ETH(600)) + await steth.mintShares(user, shares(300)) + await steth.approve(wq.address, StETH(300), { from: user }) + defaultShareRate = await currentRate() + belowShareRate = defaultShareRate.divn(2) + aboveShareRate = defaultShareRate.muln(2) + }) + + const MAX_BATCH_SIZE = 256 + const REQ_AMOUNT = ETH(0.00001) + const batchIncrement = (i) => i * 2 + + it('requestWithdrawal', async () => { + const results = [] + for (let batch_size = 1; batch_size <= MAX_BATCH_SIZE; batch_size = batchIncrement(batch_size)) { + const args = [ + Array(batch_size).fill(REQ_AMOUNT), + user, + { + from: user, + gasPrice: 10, + }, + ] + const estimated = await wq.requestWithdrawals.estimateGas(...args) + args[args.length - 1].gasLimit = estimated + const tx = await wq.requestWithdrawals(...args) + results.push({ + 'batch size': batch_size, + estimated, + used: tx.receipt.gasUsed, + 'diff%': parseFloat((((estimated - tx.receipt.gasUsed) / estimated) * 100).toFixed(3)), + 'gas/req': Math.ceil(tx.receipt.gasUsed / batch_size), + }) + } + console.log('requestWithdrawals') + console.table(results) + }) + + it('pre/finalize', async () => { + const prefinalize_results = [] + const finalization_results = [] + let slash = false + for (let batch_size = 1; batch_size <= MAX_BATCH_SIZE; batch_size = batchIncrement(batch_size)) { + const batchStart = await wq.getLastFinalizedRequestId() + const batchEnd = batchStart.addn(batch_size) + const prefinalize_args = [[batchEnd], slash ? aboveShareRate : belowShareRate] + const [prefinalize_gas, prefinalize_res] = await Promise.all([ + wq.prefinalize.estimateGas(...prefinalize_args), + wq.prefinalize.call(...prefinalize_args), + ]) + prefinalize_results.push({ + 'batch size': batch_size, + gas: prefinalize_gas, + slash, + 'gas/req': Math.ceil(prefinalize_gas / batch_size), + }) + + const finalization_args = [ + [batchEnd], + slash ? aboveShareRate : belowShareRate, + { from: owner, value: prefinalize_res.ethToLock, gasPrice: 10 }, + ] + const estimated = await wq.finalize.estimateGas(...finalization_args) + finalization_args[finalization_args.length - 1].gasLimit = estimated + const tx = await wq.finalize(...finalization_args) + finalization_results.push({ + 'batch size': batch_size, + estimated, + used: tx.receipt.gasUsed, + 'diff%': parseFloat((((estimated - tx.receipt.gasUsed) / estimated) * 100).toFixed(3)), + 'gas/req': Math.ceil(tx.receipt.gasUsed / batch_size), + slash, + }) + + slash = !slash + } + console.log('Prefinalize') + console.table(prefinalize_results) + console.log('Finalize') + console.table(finalization_results) + }) + + it('claim', async () => { + const findHints_results = [] + const claim_results = [] + const lastCheckpointIndex = await wq.getLastCheckpointIndex() + let batchStart = 1 + for (let batch_size = 1; batch_size <= MAX_BATCH_SIZE; batch_size = batchIncrement(batch_size)) { + const requestIds = Array(batch_size) + .fill(0) + .map((_, i) => batchStart + i) + const findHintsArgs = [requestIds, 1, lastCheckpointIndex] + const [findHints_gas, findHints_res] = await Promise.all([ + wq.findCheckpointHints.estimateGas(...findHintsArgs), + wq.findCheckpointHints.call(...findHintsArgs), + ]) + findHints_results.push({ + 'batch size': batch_size, + gas: findHints_gas, + 'gas/req': Math.ceil(findHints_gas / batch_size), + }) + + /// Claiming + const claiming_args = [requestIds, findHints_res, { from: user, gasPrice: 10 }] + const estimated = await wq.claimWithdrawals.estimateGas(...claiming_args) + claiming_args[claiming_args.length - 1].gasLimit = estimated + const tx = await wq.claimWithdrawals(...claiming_args) + claim_results.push({ + 'batch size': batch_size, + estimated, + used: tx.receipt.gasUsed, + 'diff%': parseFloat((((estimated - tx.receipt.gasUsed) / estimated) * 100).toFixed(3)), + 'gas/req': Math.ceil(tx.receipt.gasUsed / batch_size), + }) + batchStart += batch_size + } + console.log('FindCheckpointsHints') + console.table(findHints_results) + console.log('claimWithdrawals') + console.table(claim_results) + }) +}) From ba40a534ba625407038230ca19069ffdf7bde8a9 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Tue, 21 Mar 2023 16:10:45 +0700 Subject: [PATCH 42/66] chore: refactor for timeouts --- test/0.8.9/withdrawal-queue-gas.test.js | 93 ++++++++++++++++--------- 1 file changed, 61 insertions(+), 32 deletions(-) diff --git a/test/0.8.9/withdrawal-queue-gas.test.js b/test/0.8.9/withdrawal-queue-gas.test.js index 7c2089b7a..742235062 100644 --- a/test/0.8.9/withdrawal-queue-gas.test.js +++ b/test/0.8.9/withdrawal-queue-gas.test.js @@ -1,5 +1,7 @@ +/* eslint-disable no-template-curly-in-string */ const { contract } = require('hardhat') const { bn } = require('@aragon/contract-helpers-test') +const { itParam } = require('mocha-param') const { ETH, StETH, shares } = require('../helpers/utils') const { setBalance } = require('../helpers/blockchain') @@ -13,6 +15,14 @@ contract('WithdrawalQueue', ([owner, user]) => { .mul(bn(10).pow(bn(27))) .div(await steth.getTotalShares()) + const MAX_BATCH_SIZE = 280 + const batchIncrement = (i) => i + 10 + const REQ_AMOUNT = ETH(0.00001) + const batchSizes = [] + for (let batch_size = 1; batch_size <= MAX_BATCH_SIZE; batch_size = batchIncrement(batch_size)) { + batchSizes.push(batch_size) + } + before('Deploy', async () => { const deployed = await deployWithdrawalQueue({ stethOwner: owner, @@ -34,19 +44,26 @@ contract('WithdrawalQueue', ([owner, user]) => { aboveShareRate = defaultShareRate.muln(2) }) - const MAX_BATCH_SIZE = 256 - const REQ_AMOUNT = ETH(0.00001) - const batchIncrement = (i) => i * 2 + context('requestWithdrawal', () => { + let results + + before(async () => { + results = [] + }) - it('requestWithdrawal', async () => { - const results = [] - for (let batch_size = 1; batch_size <= MAX_BATCH_SIZE; batch_size = batchIncrement(batch_size)) { + after(async () => { + console.log('requestWithdrawals') + console.table(results) + }) + + itParam('batch size ${value}', batchSizes, async (batch_size) => { const args = [ Array(batch_size).fill(REQ_AMOUNT), user, { from: user, - gasPrice: 10, + gasPrice: batch_size * 10, + gasLimit: 1000000000, }, ] const estimated = await wq.requestWithdrawals.estimateGas(...args) @@ -59,16 +76,26 @@ contract('WithdrawalQueue', ([owner, user]) => { 'diff%': parseFloat((((estimated - tx.receipt.gasUsed) / estimated) * 100).toFixed(3)), 'gas/req': Math.ceil(tx.receipt.gasUsed / batch_size), }) - } - console.log('requestWithdrawals') - console.table(results) + }) }) - it('pre/finalize', async () => { - const prefinalize_results = [] - const finalization_results = [] - let slash = false - for (let batch_size = 1; batch_size <= MAX_BATCH_SIZE; batch_size = batchIncrement(batch_size)) { + context('pre/finalize', () => { + let prefinalize_results, finalization_results, slash + + before(async () => { + prefinalize_results = [] + finalization_results = [] + slash = false + }) + + after(async () => { + console.log('Prefinalize') + console.table(prefinalize_results) + console.log('Finalize') + console.table(finalization_results) + }) + + itParam('batch size ${value}', batchSizes, async (batch_size) => { const batchStart = await wq.getLastFinalizedRequestId() const batchEnd = batchStart.addn(batch_size) const prefinalize_args = [[batchEnd], slash ? aboveShareRate : belowShareRate] @@ -99,21 +126,27 @@ contract('WithdrawalQueue', ([owner, user]) => { 'gas/req': Math.ceil(tx.receipt.gasUsed / batch_size), slash, }) - slash = !slash - } - console.log('Prefinalize') - console.table(prefinalize_results) - console.log('Finalize') - console.table(finalization_results) + }) }) - it('claim', async () => { - const findHints_results = [] - const claim_results = [] - const lastCheckpointIndex = await wq.getLastCheckpointIndex() - let batchStart = 1 - for (let batch_size = 1; batch_size <= MAX_BATCH_SIZE; batch_size = batchIncrement(batch_size)) { + context('findHints/claim', () => { + let findHints_results, claim_results, lastCheckpointIndex, batchStart + + before(async () => { + findHints_results = [] + claim_results = [] + lastCheckpointIndex = await wq.getLastCheckpointIndex() + batchStart = 1 + }) + + after(async () => { + console.log('FindCheckpointsHints') + console.table(findHints_results) + console.log('claimWithdrawals') + console.table(claim_results) + }) + itParam('batch size ${value}', batchSizes, async (batch_size) => { const requestIds = Array(batch_size) .fill(0) .map((_, i) => batchStart + i) @@ -141,10 +174,6 @@ contract('WithdrawalQueue', ([owner, user]) => { 'gas/req': Math.ceil(tx.receipt.gasUsed / batch_size), }) batchStart += batch_size - } - console.log('FindCheckpointsHints') - console.table(findHints_results) - console.log('claimWithdrawals') - console.table(claim_results) + }) }) }) From 0301ed6004dc4df5b987c7eb9784d418d04efb86 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Tue, 21 Mar 2023 16:31:43 +0700 Subject: [PATCH 43/66] fix: add gas increase and REPORT_GAS check --- test/0.8.9/withdrawal-queue-gas.test.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/test/0.8.9/withdrawal-queue-gas.test.js b/test/0.8.9/withdrawal-queue-gas.test.js index 742235062..fbba3ddd3 100644 --- a/test/0.8.9/withdrawal-queue-gas.test.js +++ b/test/0.8.9/withdrawal-queue-gas.test.js @@ -9,21 +9,24 @@ const { deployWithdrawalQueue } = require('./withdrawal-queue-deploy.test') contract('WithdrawalQueue', ([owner, user]) => { let wq, steth, defaultShareRate, belowShareRate, aboveShareRate - + let gasPrice = 1 const currentRate = async () => bn(await steth.getTotalPooledEther()) .mul(bn(10).pow(bn(27))) .div(await steth.getTotalShares()) const MAX_BATCH_SIZE = 280 - const batchIncrement = (i) => i + 10 + const batchIncrement = (i) => i * 2 const REQ_AMOUNT = ETH(0.00001) const batchSizes = [] for (let batch_size = 1; batch_size <= MAX_BATCH_SIZE; batch_size = batchIncrement(batch_size)) { batchSizes.push(batch_size) } - before('Deploy', async () => { + before('Deploy', async function () { + if (!process.env.REPORT_GAS) { + this.skip() + } const deployed = await deployWithdrawalQueue({ stethOwner: owner, queueAdmin: owner, @@ -62,7 +65,7 @@ contract('WithdrawalQueue', ([owner, user]) => { user, { from: user, - gasPrice: batch_size * 10, + gasPrice: gasPrice++, gasLimit: 1000000000, }, ] @@ -113,7 +116,7 @@ contract('WithdrawalQueue', ([owner, user]) => { const finalization_args = [ [batchEnd], slash ? aboveShareRate : belowShareRate, - { from: owner, value: prefinalize_res.ethToLock, gasPrice: 10 }, + { from: owner, value: prefinalize_res.ethToLock, gasPrice: gasPrice++ }, ] const estimated = await wq.finalize.estimateGas(...finalization_args) finalization_args[finalization_args.length - 1].gasLimit = estimated @@ -162,7 +165,7 @@ contract('WithdrawalQueue', ([owner, user]) => { }) /// Claiming - const claiming_args = [requestIds, findHints_res, { from: user, gasPrice: 10 }] + const claiming_args = [requestIds, findHints_res, { from: user, gasPrice: gasPrice++ }] const estimated = await wq.claimWithdrawals.estimateGas(...claiming_args) claiming_args[claiming_args.length - 1].gasLimit = estimated const tx = await wq.claimWithdrawals(...claiming_args) From a0e9dfc62b43b959b14281e044ff2356737b5f82 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Tue, 21 Mar 2023 16:41:18 +0700 Subject: [PATCH 44/66] fix: add rollback for clean up between tests --- test/0.8.9/withdrawal-queue-gas.test.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/0.8.9/withdrawal-queue-gas.test.js b/test/0.8.9/withdrawal-queue-gas.test.js index fbba3ddd3..741873a28 100644 --- a/test/0.8.9/withdrawal-queue-gas.test.js +++ b/test/0.8.9/withdrawal-queue-gas.test.js @@ -1,14 +1,14 @@ /* eslint-disable no-template-curly-in-string */ -const { contract } = require('hardhat') +const { contract, ethers } = require('hardhat') const { bn } = require('@aragon/contract-helpers-test') const { itParam } = require('mocha-param') const { ETH, StETH, shares } = require('../helpers/utils') -const { setBalance } = require('../helpers/blockchain') +const { setBalance, EvmSnapshot } = require('../helpers/blockchain') const { deployWithdrawalQueue } = require('./withdrawal-queue-deploy.test') contract('WithdrawalQueue', ([owner, user]) => { - let wq, steth, defaultShareRate, belowShareRate, aboveShareRate + let wq, steth, defaultShareRate, belowShareRate, aboveShareRate, snapshot let gasPrice = 1 const currentRate = async () => bn(await steth.getTotalPooledEther()) @@ -27,6 +27,8 @@ contract('WithdrawalQueue', ([owner, user]) => { if (!process.env.REPORT_GAS) { this.skip() } + snapshot = new EvmSnapshot(ethers.provider) + const deployed = await deployWithdrawalQueue({ stethOwner: owner, queueAdmin: owner, @@ -45,6 +47,11 @@ contract('WithdrawalQueue', ([owner, user]) => { defaultShareRate = await currentRate() belowShareRate = defaultShareRate.divn(2) aboveShareRate = defaultShareRate.muln(2) + await snapshot.make() + }) + + after('clean up', async () => { + await snapshot.rollback() }) context('requestWithdrawal', () => { From 232a226b4fc9b96ca5a0d3f9e9c7dc8b97206788 Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Tue, 21 Mar 2023 13:46:58 +0400 Subject: [PATCH 45/66] OutOfGas to ModuleOutOfGas. Add requirements comments --- contracts/0.8.9/StakingRouter.sol | 8 ++++---- contracts/0.8.9/interfaces/IStakingModule.sol | 10 +++++++++- lib/abi/StakingRouter.json | 2 +- .../staking-router-keys-reporting.test.js | 5 ++++- test/0.8.9/staking-router/staking-router.test.js | 7 +++++-- 5 files changed, 23 insertions(+), 9 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 08d7d3ed2..34e039d2e 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -54,7 +54,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version error InvalidDepositsValue(uint256 etherValue, uint256 depositsCount); error StakingModuleAddressExists(); error ArraysLengthMismatch(uint256 firstArrayLength, uint256 secondArrayLength); - error OutOfGas(); + error ModuleOutOfGas(); enum StakingModuleStatus { Active, // deposits and rewards allowed @@ -295,7 +295,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// return an invalid value when the onRewardsMinted() reverts because of the /// "out of gas" error. Here we assume that the onRewardsMinted() method doesn't /// have reverts with empty error data except "out of gas". - if (lowLevelRevertData.length == 0) revert OutOfGas(); + if (lowLevelRevertData.length == 0) revert ModuleOutOfGas(); emit RewardsMintedReportFailed( _stakingModuleIds[i], lowLevelRevertData @@ -481,7 +481,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// reverts because of the "out of gas" error. Here we assume that the /// onExitedAndStuckValidatorsCountsUpdated() method doesn't have reverts with /// empty error data except "out of gas". - if (lowLevelRevertData.length == 0) revert OutOfGas(); + if (lowLevelRevertData.length == 0) revert ModuleOutOfGas(); emit ExitedAndStuckValidatorsCountsUpdateFailed( stakingModule.id, lowLevelRevertData @@ -1076,7 +1076,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// reverts because of the "out of gas" error. Here we assume that the /// onWithdrawalCredentialsChanged() method doesn't have reverts with /// empty error data except "out of gas". - if (lowLevelRevertData.length == 0) revert OutOfGas(); + if (lowLevelRevertData.length == 0) revert ModuleOutOfGas(); _setStakingModuleStatus(stakingModule, StakingModuleStatus.DepositsPaused); emit WithdrawalsCredentialsChangeFailed(stakingModule.id, lowLevelRevertData); } diff --git a/contracts/0.8.9/interfaces/IStakingModule.sol b/contracts/0.8.9/interfaces/IStakingModule.sol index e7c878de9..f48f4a474 100644 --- a/contracts/0.8.9/interfaces/IStakingModule.sol +++ b/contracts/0.8.9/interfaces/IStakingModule.sol @@ -79,6 +79,8 @@ interface IStakingModule { /// @notice Called by StakingRouter to signal that stETH rewards were minted for this module. /// @param _totalShares Amount of stETH shares that were minted to reward all node operators. + /// @dev IMPORTANT: this method SHOULD revert with empty error data ONLY because of "out of gas". + /// Details about error data: https://docs.soliditylang.org/en/v0.8.9/control-structures.html#error-handling-assert-require-revert-and-exceptions function onRewardsMinted(uint256 _totalShares) external; /// @notice Updates the number of the validators of the given node operator that were requested @@ -142,11 +144,17 @@ interface IStakingModule { /// operator in this module has actually received any updated counts as a result of the report /// but given that the total number of exited validators returned from getStakingModuleSummary /// is the same as StakingRouter expects based on the total count received from the oracle. + /// + /// @dev IMPORTANT: this method SHOULD revert with empty error data ONLY because of "out of gas". + /// Details about error data: https://docs.soliditylang.org/en/v0.8.9/control-structures.html#error-handling-assert-require-revert-and-exceptions function onExitedAndStuckValidatorsCountsUpdated() external; /// @notice Called by StakingRouter when withdrawal credentials are changed. /// @dev This method MUST discard all StakingModule's unused deposit data cause they become - /// invalid after the withdrawal credentials are changed + /// invalid after the withdrawal credentials are changed + /// + /// @dev IMPORTANT: this method SHOULD revert with empty error data ONLY because of "out of gas". + /// Details about error data: https://docs.soliditylang.org/en/v0.8.9/control-structures.html#error-handling-assert-require-revert-and-exceptions function onWithdrawalCredentialsChanged() external; /// @dev Event to be emitted on StakingModule's nonce change diff --git a/lib/abi/StakingRouter.json b/lib/abi/StakingRouter.json index aa9e55205..e6a9596a1 100644 --- a/lib/abi/StakingRouter.json +++ b/lib/abi/StakingRouter.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"OutOfGas","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"ModuleOutOfGas","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getExitedValidatorsCountAcrossAllModules","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js index 52f247a11..39fc87218 100644 --- a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js +++ b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js @@ -510,7 +510,10 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { .on('onExitedAndStuckValidatorsCountsUpdated', { revert: { reason: 'outOfGas' } }) .update({ from: deployer }) - await assert.reverts(router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }), 'OutOfGas()') + await assert.reverts( + router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }), + 'ModuleOutOfGas()' + ) }) }) diff --git a/test/0.8.9/staking-router/staking-router.test.js b/test/0.8.9/staking-router/staking-router.test.js index d3db490d9..d712125ca 100644 --- a/test/0.8.9/staking-router/staking-router.test.js +++ b/test/0.8.9/staking-router/staking-router.test.js @@ -357,7 +357,7 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { .on('onWithdrawalCredentialsChanged', { revert: { reason: 'outOfGas' } }) .update({ from: deployer }) - await assert.reverts(router.setWithdrawalCredentials(newWC, { from: appManager }), 'OutOfGas()') + await assert.reverts(router.setWithdrawalCredentials(newWC, { from: appManager }), 'ModuleOutOfGas()') }) }) @@ -984,7 +984,10 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { .on('onRewardsMinted', { revert: { reason: 'outOfGas' } }) .update({ from: deployer }) - await assert.reverts(router.reportRewardsMinted(stakingModuleIds, totalShares, { from: admin }), 'OutOfGas()') + await assert.reverts( + router.reportRewardsMinted(stakingModuleIds, totalShares, { from: admin }), + 'ModuleOutOfGas()' + ) }) }) From c42344c081a62c849f52a31fb49f2eca30772407 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 21 Mar 2023 13:06:08 +0300 Subject: [PATCH 46/66] chore: remove extra brackets --- contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol index 7f509c71b..37e92aa5e 100644 --- a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol +++ b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol @@ -41,7 +41,7 @@ struct TokenRebaseLimiterData { * ### Step 1. Calculating the allowed total pooled ether changes (preTotalShares === postTotalShares) * Used for `PositiveTokenRebaseLimiter.consumeLimit()`, `PositiveTokenRebaseLimiter.raiseLimit()`. * - * R = ((preTotalPooledEther + inc) / preTotalShares) / ((preTotalPooledEther / preTotalShares)) - 1 + * R = ((preTotalPooledEther + inc) / preTotalShares) / (preTotalPooledEther / preTotalShares) - 1 * R = inc/preTotalPooledEther * * isolating inc: From 4b99da5ccfc1a0755ee0f6134730e1562b5baeaf Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Tue, 21 Mar 2023 17:06:50 +0700 Subject: [PATCH 47/66] fix: rollback --- test/0.8.9/withdrawal-queue-gas.test.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/0.8.9/withdrawal-queue-gas.test.js b/test/0.8.9/withdrawal-queue-gas.test.js index 741873a28..93596d454 100644 --- a/test/0.8.9/withdrawal-queue-gas.test.js +++ b/test/0.8.9/withdrawal-queue-gas.test.js @@ -50,8 +50,11 @@ contract('WithdrawalQueue', ([owner, user]) => { await snapshot.make() }) - after('clean up', async () => { - await snapshot.rollback() + after('clean up', async function () { + // only rollback if not skipped + if (process.env.REPORT_GAS) { + await snapshot.rollback() + } }) context('requestWithdrawal', () => { From 6de0fa6279d94d064dcbe103cecda9c24fc91595 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 21 Mar 2023 13:43:05 +0300 Subject: [PATCH 48/66] test: abandon aux event in favor of sendWithResult --- .../PositiveTokenRebaseLimiterMock.sol | 10 ++------- .../positive-token-rebase-limiter.test.js | 22 ++++++++----------- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol index 7aaa8d572..1cc28af58 100644 --- a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol +++ b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol @@ -10,10 +10,6 @@ contract PositiveTokenRebaseLimiterMock { TokenRebaseLimiterData public limiter; - event ReturnValue ( - uint256 retValue - ); - function getLimiterValues() external view @@ -48,12 +44,10 @@ contract PositiveTokenRebaseLimiterMock { limiter = limiterMemory; } - function consumeLimit(uint256 _etherAmount) external { + function consumeLimit(uint256 _etherAmount) external returns (uint256 consumedEther) { TokenRebaseLimiterData memory limiterMemory = limiter; - uint256 consumedEther = limiterMemory.consumeLimit(_etherAmount); + consumedEther = limiterMemory.consumeLimit(_etherAmount); limiter = limiterMemory; - - emit ReturnValue(consumedEther); } function getSharesToBurnLimit() external view returns (uint256) { diff --git a/test/0.8.9/positive-token-rebase-limiter.test.js b/test/0.8.9/positive-token-rebase-limiter.test.js index 1aed2f448..ebdbe1dde 100644 --- a/test/0.8.9/positive-token-rebase-limiter.test.js +++ b/test/0.8.9/positive-token-rebase-limiter.test.js @@ -2,7 +2,7 @@ const { artifacts, contract, ethers } = require('hardhat') const { bn, MAX_UINT64 } = require('@aragon/contract-helpers-test') const { EvmSnapshot } = require('../helpers/blockchain') -const { ETH } = require('../helpers/utils') +const { ETH, addSendWithResult } = require('../helpers/utils') const { assert } = require('../helpers/assert') const PositiveTokenRebaseLimiter = artifacts.require('PositiveTokenRebaseLimiterMock.sol') @@ -17,6 +17,8 @@ contract('PositiveTokenRebaseLimiter', () => { snapshot = new EvmSnapshot(ethers.provider) await snapshot.make() + + addSendWithResult(limiter.consumeLimit) }) afterEach(async () => { @@ -99,16 +101,13 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.rebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) - const tx = await limiter.consumeLimit(ETH(2)) - assert.emits(tx, 'ReturnValue', { retValue: ETH(2) }) + assert.equals(await limiter.consumeLimit.sendWithResult(ETH(2)), ETH(2)) assert.isFalse(await limiter.isLimitReached()) - const tx2 = await limiter.consumeLimit(ETH(4)) - assert.emits(tx2, 'ReturnValue', { retValue: ETH(4) }) + assert.equals(await limiter.consumeLimit.sendWithResult(ETH(4)), ETH(4)) assert.isFalse(await limiter.isLimitReached()) - const tx3 = await limiter.consumeLimit(ETH(1)) - assert.emits(tx3, 'ReturnValue', { retValue: ETH(0.5) }) + assert.equals(await limiter.consumeLimit.sendWithResult(ETH(1)), ETH(0.5)) assert.isTrue(await limiter.isLimitReached()) assert.equals(await limiter.getSharesToBurnLimit(), 0) }) @@ -122,8 +121,7 @@ contract('PositiveTokenRebaseLimiter', () => { await limiter.raiseLimit(ETH(1)) assert.isFalse(await limiter.isLimitReached()) - const tx = await limiter.consumeLimit(ETH(2)) - assert.emits(tx, 'ReturnValue', { retValue: ETH(2) }) + assert.equals(await limiter.consumeLimit.sendWithResult(ETH(2)), ETH(2)) assert.isFalse(await limiter.isLimitReached()) const limiterValues = await limiter.getLimiterValues() @@ -153,8 +151,7 @@ contract('PositiveTokenRebaseLimiter', () => { await limiter.raiseLimit(ETH(1)) assert.isFalse(await limiter.isLimitReached()) - const tx = await limiter.consumeLimit(ETH(2)) - assert.emits(tx, 'ReturnValue', { retValue: ETH(2) }) + assert.equals(await limiter.consumeLimit.sendWithResult(ETH(2)), ETH(2)) assert.isFalse(await limiter.isLimitReached()) let limiterValues = await limiter.getLimiterValues() @@ -194,9 +191,8 @@ contract('PositiveTokenRebaseLimiter', () => { await limiter.consumeLimit(ETH(1)) assert.isFalse(await limiter.isLimitReached()) - const ethTx = await limiter.consumeLimit(ETH(2)) + assert.equals(await limiter.consumeLimit.sendWithResult(ETH(2)), ETH(2)) assert.isFalse(await limiter.isLimitReached()) - assert.emits(ethTx, 'ReturnValue', { retValue: ETH(2) }) const maxSharesToBurn = await limiter.getSharesToBurnLimit() assert.equals(maxSharesToBurn, 0) From 0e85e10fcbcd1c2f4d115c99aab3bd1603998b6f Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 21 Mar 2023 19:35:49 +0700 Subject: [PATCH 49/66] test: NodeOperatorsRegistry happy path cleanup --- ...node-operators-registry-happy-path.test.js | 165 ++++++++++-------- 1 file changed, 89 insertions(+), 76 deletions(-) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index 86fffec80..089e91906 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -31,7 +31,7 @@ const NODE_OPERATORS = [ name: 'Node operator #1', rewardAddressInitial: ADDRESS_1, totalSigningKeysCount: 10, - vettedSigningKeysCount: 6, + vettedSigningKeysCount: 7, }, { id: 1, @@ -83,6 +83,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re let consensusVersion let signers let consensusMember + let curatedId let stateTotalVetted = 0 let stateTotalDepositable = 0 @@ -132,6 +133,18 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re ) } + async function assertRewardsDistributedEvent(tx, eventIdx, rewardsAddress, amount) { + const event = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV)[eventIdx] + assert.addressEqual(event.args.rewardAddress, rewardsAddress) + assert.isClose(event.args.sharesAmount, amount, 10) + } + + async function assertNodeOperatorPenalizedEvent(tx, eventIdx, rewardsAddress, amount) { + const event = getEvents(tx, 'NodeOperatorPenalized', NOR_ABI_GET_EV)[eventIdx] + assert.addressEqual(event.args.recipientAddress, rewardsAddress) + assert.isClose(event.args.sharesPenalizedAmount, amount, 10) + } + before('deploy base app', async () => { const deployed = await deployProtocol({ stakingModulesFactory: async (protocol) => { @@ -170,6 +183,9 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re withdrawalCredentials = '0x'.padEnd(66, '1234') await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting.address }) + + const [curated] = await stakingRouter.getStakingModules() + curatedId = curated.id }) describe('Happy path', () => { @@ -201,7 +217,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re assert.equals(await nor.getNodeOperatorsCount(), NODE_OPERATORS.length) }) - // TODO: Move this block after keys manipulations to check how it will affect them it('Deactivate node operator 4', async () => { const operatorId = Operator4.id const activeOperatorsBefore = await nor.getActiveNodeOperatorsCount() @@ -313,7 +328,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re }) it('Set target limit to Operator 2', async () => { - const [curated] = await stakingRouter.getStakingModules() const operatorData = Operator2 const operatorId = operatorData.id const targetLimitCount = 1 @@ -321,7 +335,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re await assertTargetLimit(operatorData, false, 0, operatorData.vettedSigningKeysCount) // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() - const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorId, true, targetLimitCount, { + const tx = await stakingRouter.updateTargetValidatorsLimits(curatedId, operatorId, true, targetLimitCount, { from: voting.address, }) @@ -347,8 +361,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re context('Deposits distribution', () => { it('Obtain deposit data', async () => { - const [curated] = await stakingRouter.getStakingModules() - const stakesDeposited = 6 const depositedValue = ETH(32 * stakesDeposited) @@ -359,7 +371,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re DSMAttestMessage.setMessagePrefix(await dsm.ATTEST_MESSAGE_PREFIX()) - const attest = new DSMAttestMessage(block.number, block.hash, depositRoot, curated.id, keysOpIndex) + const attest = new DSMAttestMessage(block.number, block.hash, depositRoot, curatedId, keysOpIndex) const signatures = [ attest.sign(guardians.privateKeys[guardians.addresses[0]]), attest.sign(guardians.privateKeys[guardians.addresses[1]]), @@ -374,7 +386,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re // triggers flow: // DSM.depositBufferedEther() -> Lido.deposit() -> StakingRouter.deposit() -> Module.obtainDepositData() - await dsm.depositBufferedEther(block.number, block.hash, depositRoot, curated.id, keysOpIndex, '0x', signatures) + await dsm.depositBufferedEther(block.number, block.hash, depositRoot, curatedId, keysOpIndex, '0x', signatures) stateTotalDeposited += stakesDeposited @@ -393,7 +405,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re assert.equals(stakingModuleSummaryAfter.totalDepositedValidators, stateTotalDeposited) assert.equals(stakingModuleSummaryAfter.depositableValidatorsCount, stateTotalDepositable - stateTotalDeposited) - await assertOperatorDeposits(Operator1, 3, 3) + await assertOperatorDeposits(Operator1, 3, 4) await assertOperatorDeposits(Operator2, 1, 0) await assertOperatorDeposits(Operator3, 2, 3) @@ -427,7 +439,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re it('Consensus+oracle report', async () => { const { refSlot } = await consensus.getCurrentFrame() - const [curated] = await stakingRouter.getStakingModules() const extraData = { exitedKeys: [{ moduleId: 1, nodeOpIds: [0], keysCounts: [2] }], @@ -442,7 +453,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re consensusVersion, numValidators: 6, clBalanceGwei: toBN(ETH(32 * stateTotalDeposited + 1)).div(E9), - stakingModuleIdsWithNewlyExitedValidators: [curated.id], + stakingModuleIdsWithNewlyExitedValidators: [curatedId], numExitedValidatorsByStakingModule: [2], withdrawalVaultBalance: e18(0), elRewardsVaultBalance: e18(0), @@ -467,7 +478,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re // -> StakingRouter.reportRewardsMinted() -> NOR.onRewardsMinted() // ._transferModuleRewards()._transferShares() // -> StakingRouter.updateExitedValidatorsCountByStakingModule() -> StakingRouter.stakingModule[id].exitedValidatorsCount = exitedCount - // TODO: [optional] assert those tied calls await oracle.submitReportData(reportItems, consensusVersion, { from: consensusMember }) const sharesNORInMiddle = await lido.sharesOf(nor.address) @@ -521,21 +531,11 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re NOR_ABI_ASSERT_EV ) - const eventRewardsDistributed1 = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV)[0] - const eventRewardsDistributed2 = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV)[1] - const eventRewardsDistributed3 = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV)[2] - const eventOperator2Penalized = getEvents(tx, 'NodeOperatorPenalized', NOR_ABI_GET_EV)[0] - const eventOperator3Penalized = getEvents(tx, 'NodeOperatorPenalized', NOR_ABI_GET_EV)[1] - assert.addressEqual(eventRewardsDistributed1.args.rewardAddress, rewards1) - assert.addressEqual(eventRewardsDistributed2.args.rewardAddress, rewards2) - assert.addressEqual(eventRewardsDistributed3.args.rewardAddress, rewards3) - assert.addressEqual(eventOperator2Penalized.args.recipientAddress, rewards2) - assert.addressEqual(eventOperator3Penalized.args.recipientAddress, rewards3) - assert.isClose(eventRewardsDistributed1.args.sharesAmount, rewardAmountForOperator1, 10) - assert.isClose(eventRewardsDistributed2.args.sharesAmount, rewardAmountForOperator2, 10) - assert.isClose(eventRewardsDistributed3.args.sharesAmount, rewardAmountForOperator3, 10) - assert.isClose(eventOperator2Penalized.args.sharesPenalizedAmount, penaltyAmountForOperator2, 10) - assert.isClose(eventOperator3Penalized.args.sharesPenalizedAmount, penaltyAmountForOperator3, 10) + await assertRewardsDistributedEvent(tx, 0, rewards1, rewardAmountForOperator1) + await assertRewardsDistributedEvent(tx, 1, rewards2, rewardAmountForOperator2) + await assertRewardsDistributedEvent(tx, 2, rewards3, rewardAmountForOperator3) + await assertNodeOperatorPenalizedEvent(tx, 0, rewards2, penaltyAmountForOperator2) + await assertNodeOperatorPenalizedEvent(tx, 1, rewards3, penaltyAmountForOperator3) }) it('Operator summaries after exit/stuck report', async () => { @@ -543,7 +543,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) assert.equals(operator1.stoppedValidators, 2) assert.equals(summaryOperator1.totalExitedValidators, 2) - assert.equals(summaryOperator1.depositableValidatorsCount, 3) + assert.equals(summaryOperator1.depositableValidatorsCount, 4) const summaryOperator2 = await nor.getNodeOperatorSummary(Operator2.id) assert.equals(summaryOperator2.stuckValidatorsCount, 1) @@ -568,12 +568,13 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re }) context('Updating state unsafely', () => { + let correctionTx + it('unsafeSetExitedValidatorsCount', async () => { - // const stakesDeposited = 6 - // const depositedValue = ETH(32 * stakesDeposited) - // await web3.eth.sendTransaction({ to: lido.address, from: user1, value: depositedValue }) + // should be distributed after update + await lido.transfer(nor.address, ETH(10), { from: user1 }) - const [curated] = await stakingRouter.getStakingModules() + const operatorData = Operator1 const correction = { currentModuleExitedValidatorsCount: 2, currentNodeOperatorExitedValidatorsCount: 2, @@ -588,23 +589,25 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re // emits NOR.RewardsDistributed // -> stETH.transferShares() // -> Burner.requestBurnShares() - const tx = await stakingRouter.unsafeSetExitedValidatorsCount(curated.id, Operator1.id, true, correction, { - from: voting.address, - }) - - const summaryOperator1 = await nor.getNodeOperatorSummary(Operator1.id) - assert.equals(summaryOperator1.totalExitedValidators, 3) + correctionTx = await stakingRouter.unsafeSetExitedValidatorsCount( + curatedId, + operatorData.id, + true, + correction, + { from: voting.address } + ) - const events = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV) - console.log(events) - // const eventRewardsDistributed1 = getEvents(tx, 'RewardsDistributed', NOR_ABI_GET_EV) - // const eventOperator2Penalized = getEvents(tx, 'NodeOperatorPenalized', NOR_ABI_GET_EV) + const summaryModule = await nor.getStakingModuleSummary() + const summaryOperator1 = await nor.getNodeOperatorSummary(operatorData.id) + assert.equals(summaryModule.totalExitedValidators, correction.newModuleExitedValidatorsCount) + assert.equals(summaryOperator1.totalExitedValidators, correction.newNodeOperatorExitedValidatorsCount) - // TODO: assert emits NOR.NodeOperatorPenalized - // TODO: assert emits NOR.RewardsDistributed - // TODO: assert rewards was transfered with NOR._distributeRewards() - // TODO: assert rewards was transfered to operators for his exited validators + // TODO: calculate those assert values + await assertRewardsDistributedEvent(correctionTx, 0, rewards2, '1658930720302561458') + await assertRewardsDistributedEvent(correctionTx, 1, rewards3, '3317861440605122916') }) + + // TODO: assert stuck operators and NodeOperatorPenalized event after unsafeSetExitedValidatorsCount() }) context('Keys and limits settings tweaks', () => { @@ -613,13 +616,12 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re */ it('Disable TargetLimit', async () => { - const [curated] = await stakingRouter.getStakingModules() const operatorData = Operator2 await assertTargetLimit(operatorData, true, 1, 0) // StakingRouter.updateTargetValidatorsLimits() -> NOR.updateTargetValidatorsLimits() - const tx = await stakingRouter.updateTargetValidatorsLimits(curated.id, operatorData.id, false, 0, { + const tx = await stakingRouter.updateTargetValidatorsLimits(curatedId, operatorData.id, false, 0, { from: voting.address, }) @@ -634,10 +636,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re ) }) - /** - * TODO: Deposit again after disabling TargetLimit - */ - it('Remove multiple signing keys', async () => { const operatorData = Operator1 @@ -666,7 +664,10 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re assert.equals(+keysCountBefore - keysCountToRemove, +keysCountAfter) assert.equals(+unusedKeysCountBefore - keysCountToRemove, +unusedKeysCountAfter) assert.equals( - +summaryBefore.depositableValidatorsCount - keysCountToRemove, + Math.min( + +summaryBefore.depositableValidatorsCount - keysCountToRemove, + +operatorAfter.stakingLimit - +operatorAfter.usedSigningKeys + ), +summaryAfter.depositableValidatorsCount ) assert.notEqual(key1Before.key, key1After.key) @@ -674,38 +675,50 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re assert.notEqual(key1Before.depositSignature, key1After.depositSignature) assert.notEqual(key2Before.depositSignature, key2After.depositSignature) }) + + it('Refund stucked keys for Operator 2', async () => { + // TODO: [do more research] Refuneded keys + // 1. Operator already have stucked keys + // 2. Set refunded via StakingRouter.updateRefundedValidatorsCount() -> NOR.updateRefundedValidatorsCount() with refunded == stuckKeys + // 3. Wait for half of penalty delay and check that penalty still with NOR.getRewardsDistribution() and Oracle report and obtain deposit data + // 4. Wait for end of penalty delay and check that it is gone + // assert NOR.getStuckPenaltyDelay() + // assert NOR.setStuckPenaltyDelay() + // assert penalty affects on TargetLimit + }) }) - // TODO: NOR....() - // Deactivate Operator that was in use before - // Make another deposit to check that deactivated node operator will not get deposit - // Assert rewards not distributed to disabled operator + context('Activation/deactivation', () => { + it('Deactivate operator 1', async () => { + // TODO: NOR....() + // Deactivate Operator that was in use before + }) + + it('Activate operator 4', async () => { + // Activate previously disabled Operator and check it will be used in deposit flow + }) + + it('Add keys to operator 4', async () => {}) + + it('Set operator 4 staking limit', async () => {}) - // TODO: NOR.activateNodeOperator() - // Activate Operator again and it will be used in deposit flow + it('Make another deposit', async () => { + // Make another deposit to check: + // — deactivated node operator will not get deposit + // — target limit was disabled properly before + // — operator with refunded keys gets deposits + }) + + it('Make a report for rewards distribution', async () => { + // Assert rewards not distributed to disabled operator + }) + }) // TODO: StakingRouter.setWithdrawalCredentials() -> NOR.onWithdrawalCredentialsChanged() // assert depositable of all operators should be zero // assert totalValidatorsCount of all operators == deposited validators // assert NOR.getStakingModuleSummary() — depositable = 0, exited = same, deposited = same - // TODO: manipulate with stuck validators - // this value can come from from two sources: - // AccountingOracle.submitReportExtraDataList() - // NOR.unsafeUpdateValidatorsCount() - // assert that NOR.onExitedAndStuckValidatorsCountsUpdated() should be called - - // TODO: [optional] unsafeUpdateValidatorsCount - - // TODO: [do more research] Refuneded keys - // 1. Operator already have stucked keys - // 2. Set refunded via StakingRouter...() -> NOR.updateRefundedValidatorsCount() with refunded == stuckKeys - // 3. Wait for half of penalty delay and check that penalty still with NOR.getRewardsDistribution() and Oracle report and obtain deposit data - // 4. Wait for end of penalty delay and check that it is gone - // assert NOR.getStuckPenaltyDelay() - // assert NOR.setStuckPenaltyDelay() - // assert penalty affects on TargetLimit - // TODO: [optional] add NOR.getNonce() somewhere // TODO: [optional] assert NOR._getSigningKeysAllocationData() if it is possible From 82f009c62cbaff46a4b4b75bea8dfb77f45a2ed8 Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Tue, 21 Mar 2023 21:29:18 +0400 Subject: [PATCH 50/66] Simulate zero value deposit on staking module addition --- contracts/0.8.9/StakingRouter.sol | 7 ++++ test/0.8.9/staking-router/digest.test.js | 11 +++--- .../staking-router/staking-router.test.js | 35 ++++++++++++++----- .../lido_rewards_distribution_math.test.js | 2 ++ 4 files changed, 43 insertions(+), 12 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 5303a833e..865dbf8e2 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -210,6 +210,13 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// https://docs.soliditylang.org/en/v0.8.17/types.html#enums newStakingModule.status = uint8(StakingModuleStatus.Active); + /// @dev Simulate zero value deposit to prevent real deposits into the new StakingModule via + /// DepositSecurityModule just after the addition. + /// See DepositSecurityModule.getMaxDeposits() for details + newStakingModule.lastDepositAt = uint64(block.timestamp); + newStakingModule.lastDepositBlock = block.number; + emit StakingRouterETHDeposited(newStakingModuleId, 0); + _setStakingModuleIndexById(newStakingModuleId, newStakingModuleIndex); LAST_STAKING_MODULE_ID_POSITION.setStorageUint256(newStakingModuleId); STAKING_MODULES_COUNT_POSITION.setStorageUint256(newStakingModuleIndex + 1); diff --git a/test/0.8.9/staking-router/digest.test.js b/test/0.8.9/staking-router/digest.test.js index 877eed769..9da68189d 100644 --- a/test/0.8.9/staking-router/digest.test.js +++ b/test/0.8.9/staking-router/digest.test.js @@ -38,6 +38,7 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { after(revert) let module1Id, module2Id + let module1AddedBlock, module2AddedBlock const nodeOperator1 = 0 let StakingModuleDigest, StakingModuleDigest2 @@ -55,6 +56,7 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { 5_000, // 50 % _treasuryFee { from: admin } ) + module1AddedBlock = await ethers.provider.getBlock() module1Id = +(await router.getStakingModuleIds())[0] }) @@ -67,6 +69,7 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { 3_000, // 50 % _treasuryFee { from: admin } ) + module2AddedBlock = await ethers.provider.getBlock() module2Id = +(await router.getStakingModuleIds())[1] }) @@ -186,8 +189,8 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { targetShare: '10000', status: '0', name: 'module 1', - lastDepositAt: '0', - lastDepositBlock: '0', + lastDepositAt: module1AddedBlock.timestamp.toString(), + lastDepositBlock: module1AddedBlock.number.toString(), exitedValidatorsCount: '0', }), summary: Object.values({ @@ -210,8 +213,8 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { targetShare: '9000', status: '0', name: 'module 2', - lastDepositAt: '0', - lastDepositBlock: '0', + lastDepositAt: module2AddedBlock.timestamp.toString(), + lastDepositBlock: module2AddedBlock.number.toString(), exitedValidatorsCount: '0', }), summary: Object.values({ diff --git a/test/0.8.9/staking-router/staking-router.test.js b/test/0.8.9/staking-router/staking-router.test.js index d00dc4d92..bb689604f 100644 --- a/test/0.8.9/staking-router/staking-router.test.js +++ b/test/0.8.9/staking-router/staking-router.test.js @@ -385,6 +385,8 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { treasuryFee: 200, expectedModuleId: 1, address: null, + lastDepositAt: null, + lastDepositBlock: null, }, { name: 'Test module 1', @@ -393,6 +395,8 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { treasuryFee: 200, expectedModuleId: 2, address: null, + lastDepositAt: null, + lastDepositBlock: null, }, ] @@ -511,7 +515,11 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { from: appManager, } ) - assert.equals(tx.logs.length, 3) + const latestBlock = await ethers.provider.getBlock() + stakingModulesParams[0].lastDepositAt = latestBlock.timestamp + stakingModulesParams[0].lastDepositBlock = latestBlock.number + + assert.equals(tx.logs.length, 4) await assert.emits(tx, 'StakingModuleAdded', { stakingModuleId: stakingModulesParams[0].expectedModuleId, stakingModule: stakingModule1.address, @@ -529,6 +537,10 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { treasuryFee: stakingModulesParams[0].treasuryFee, setBy: appManager, }) + await assert.emits(tx, 'StakingRouterETHDeposited', { + stakingModuleId: stakingModulesParams[0].expectedModuleId, + amount: 0, + }) assert.equals(await router.getStakingModulesCount(), 1) assert.equals( @@ -547,8 +559,8 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { assert.equals(module.treasuryFee, stakingModulesParams[0].treasuryFee) assert.equals(module.targetShare, stakingModulesParams[0].targetShare) assert.equals(module.status, StakingModuleStatus.Active) - assert.equals(module.lastDepositAt, 0) - assert.equals(module.lastDepositBlock, 0) + assert.equals(module.lastDepositAt, stakingModulesParams[0].lastDepositAt) + assert.equals(module.lastDepositBlock, stakingModulesParams[0].lastDepositBlock) }) it('add another staking module', async () => { @@ -562,8 +574,11 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { from: appManager, } ) + const latestBlock = await ethers.provider.getBlock() + stakingModulesParams[1].lastDepositAt = latestBlock.timestamp + stakingModulesParams[1].lastDepositBlock = latestBlock.number - assert.equals(tx.logs.length, 3) + assert.equals(tx.logs.length, 4) await assert.emits(tx, 'StakingModuleAdded', { stakingModuleId: stakingModulesParams[1].expectedModuleId, stakingModule: stakingModule2.address, @@ -581,6 +596,10 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { treasuryFee: stakingModulesParams[1].treasuryFee, setBy: appManager, }) + await assert.emits(tx, 'StakingRouterETHDeposited', { + stakingModuleId: stakingModulesParams[1].expectedModuleId, + amount: 0, + }) assert.equals(await router.getStakingModulesCount(), 2) assert.equals( @@ -599,8 +618,8 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { assert.equals(module.treasuryFee, stakingModulesParams[1].treasuryFee) assert.equals(module.targetShare, stakingModulesParams[1].targetShare) assert.equals(module.status, StakingModuleStatus.Active) - assert.equals(module.lastDepositAt, 0) - assert.equals(module.lastDepositBlock, 0) + assert.equals(module.lastDepositAt, stakingModulesParams[1].lastDepositAt) + assert.equals(module.lastDepositBlock, stakingModulesParams[1].lastDepositBlock) }) it('get staking modules list', async () => { @@ -613,8 +632,8 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { assert.equals(stakingModules[i].treasuryFee, stakingModulesParams[i].treasuryFee) assert.equals(stakingModules[i].targetShare, stakingModulesParams[i].targetShare) assert.equals(stakingModules[i].status, StakingModuleStatus.Active) - assert.equals(stakingModules[i].lastDepositAt, 0) - assert.equals(stakingModules[i].lastDepositBlock, 0) + assert.equals(stakingModules[i].lastDepositAt, stakingModulesParams[i].lastDepositAt) + assert.equals(stakingModules[i].lastDepositBlock, stakingModulesParams[i].lastDepositBlock) } }) diff --git a/test/scenario/lido_rewards_distribution_math.test.js b/test/scenario/lido_rewards_distribution_math.test.js index 73d0dee29..34f8c4ffe 100644 --- a/test/scenario/lido_rewards_distribution_math.test.js +++ b/test/scenario/lido_rewards_distribution_math.test.js @@ -652,6 +652,8 @@ contract('Lido: rewards distribution math', (addresses) => { { from: voting } ) + await waitBlocks(+(await depositSecurityModule.getMaxDeposits())) + const modulesList = await stakingRouter.getStakingModules() assert(modulesList.length, 2, 'module added') From 295d0b1b279f42f08472b38538b2f2fdc18d74b5 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 21 Mar 2023 20:52:35 +0300 Subject: [PATCH 51/66] test: smoothenTokenRebase tests --- .../0.8.9/lib/PositiveTokenRebaseLimiter.sol | 2 + hardhat.config.js | 3 +- .../oracle-report-sanity-checker.test.js | 478 ++++++++++++++++++ 3 files changed, 482 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol index 37e92aa5e..6f5715da2 100644 --- a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol +++ b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol @@ -42,6 +42,8 @@ struct TokenRebaseLimiterData { * Used for `PositiveTokenRebaseLimiter.consumeLimit()`, `PositiveTokenRebaseLimiter.raiseLimit()`. * * R = ((preTotalPooledEther + inc) / preTotalShares) / (preTotalPooledEther / preTotalShares) - 1 + * R = ((preTotalPooledEther + inc) / preTotalShares) * (preTotalShares / preTotalPooledEther) - 1 + * R = (preTotalPooledEther + inc) / preTotalPooledEther) - 1 * R = inc/preTotalPooledEther * * isolating inc: diff --git a/hardhat.config.js b/hardhat.config.js index b7ec8ee9d..857fa0509 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -70,6 +70,7 @@ const getNetConfig = (networkName, ethAccountName) => { ...base, accounts: { mnemonic: 'explain tackle mirror kit van hammer degree position ginger unfair soup bonus', + count: 30, }, url: 'http://localhost:8545', chainId: 1337, @@ -82,7 +83,7 @@ const getNetConfig = (networkName, ethAccountName) => { accounts: { // default hardhat's node mnemonic mnemonic: 'test test test test test test test test test test test junk', - count: 20, + count: 30, accountsBalance: '100000000000000000000000', gasPrice: 0, }, diff --git a/test/0.8.9/oracle-report-sanity-checker.test.js b/test/0.8.9/oracle-report-sanity-checker.test.js index a3ef5b8d5..f9d23f884 100644 --- a/test/0.8.9/oracle-report-sanity-checker.test.js +++ b/test/0.8.9/oracle-report-sanity-checker.test.js @@ -83,6 +83,12 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa ) }) + describe('getLidoLocator()', () => { + it('retrieves correct locator address', async () => { + assert.equals(await oracleReportSanityChecker.getLidoLocator(), lidoLocatorMock.address) + }) + }) + describe('setOracleReportLimits()', () => { it('sets limits correctly', async () => { const newLimitsList = { @@ -330,4 +336,476 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa await oracleReportSanityChecker.checkSimulatedShareRate(...Object.values(correctSimulatedShareRate)) }) }) + + describe('max positive rebase', () => { + const defaultSmoothenTokenRebaseParams = { + preTotalPooledEther: ETH(100), + preTotalShares: ETH(100), + preCLBalance: ETH(100), + postCLBalance: ETH(100), + withdrawalVaultBalance: 0, + elRewardsVaultBalance: 0, + sharesRequestedToBurn: 0, + etherToLockForWithdrawals: 0, + newSharesToBurnForWithdrawals: 0, + } + + it('getMaxPositiveTokenRebase works', async () => { + assert.equals( + await oracleReportSanityChecker.getMaxPositiveTokenRebase(), + defaultLimitsList.maxPositiveTokenRebase + ) + }) + + it('setMaxPositiveTokenRebase works', async () => { + const newRebaseLimit = 1_000_000 + assert.notEquals(newRebaseLimit, defaultLimitsList.maxPositiveTokenRebase) + + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { from: deployer }), + deployer, + 'MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE' + ) + + const tx = await oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { + from: managersRoster.maxPositiveTokenRebaseManagers[0], + }) + + assert.equals(await oracleReportSanityChecker.getMaxPositiveTokenRebase(), newRebaseLimit) + assert.emits(tx, 'MaxPositiveTokenRebaseSet', { maxPositiveTokenRebase: newRebaseLimit }) + }) + + it('all zero data works', async () => { + const { withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + preTotalPooledEther: 0, + preTotalShares: 0, + preCLBalance: 0, + postCLBalance: 0, + }) + ) + + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + }) + + it('trivial smoothen rebase works when post CL < pre CL and no withdrawals', async () => { + const newRebaseLimit = 100_000 // 0.01% + await oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { + from: managersRoster.maxPositiveTokenRebaseManagers[0], + }) + + let { withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ ...defaultSmoothenTokenRebaseParams, postCLBalance: ETH(99) }) + ) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(99), + elRewardsVaultBalance: ETH(0.1), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, ETH(0.1)) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // withdrawals + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(99), + withdrawalVaultBalance: ETH(0.1), + }) + )) + assert.equals(withdrawals, ETH(0.1)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // shares requested to burn + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(99), + sharesRequestedToBurn: ETH(0.1), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, ETH(0.1)) + assert.equals(sharesToBurn, ETH(0.1)) + }) + + it('trivial smoothen rebase works when post CL > pre CL and no withdrawals', async () => { + const newRebaseLimit = 100_000_000 // 10% + await oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { + from: managersRoster.maxPositiveTokenRebaseManagers[0], + }) + + let { withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ ...defaultSmoothenTokenRebaseParams, postCLBalance: ETH(100.01) }) + ) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(100.01), + elRewardsVaultBalance: ETH(0.1), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, ETH(0.1)) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // withdrawals + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(100.01), + withdrawalVaultBalance: ETH(0.1), + }) + )) + assert.equals(withdrawals, ETH(0.1)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // shares requested to burn + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(100.01), + sharesRequestedToBurn: ETH(0.1), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, ETH(0.1)) + assert.equals(sharesToBurn, ETH(0.1)) + }) + + it('non-trivial smoothen rebase works when post CL < pre CL and no withdrawals', async () => { + const newRebaseLimit = 10_000_000 // 1% + await oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { + from: managersRoster.maxPositiveTokenRebaseManagers[0], + }) + + let { withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ ...defaultSmoothenTokenRebaseParams, postCLBalance: ETH(99) }) + ) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(99), + elRewardsVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, ETH(2)) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // withdrawals + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(99), + withdrawalVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, ETH(2)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // withdrawals + el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(99), + withdrawalVaultBalance: ETH(5), + elRewardsVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, ETH(2)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // shares requested to burn + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(99), + sharesRequestedToBurn: ETH(5), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, '1980198019801980198') // ETH(100. - (99. / 1.01)) + assert.equals(sharesToBurn, '1980198019801980198') // the same as above since no withdrawals + }) + + it('non-trivial smoothen rebase works when post CL > pre CL and no withdrawals', async () => { + const newRebaseLimit = 20_000_000 // 2% + await oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { + from: managersRoster.maxPositiveTokenRebaseManagers[0], + }) + + let { withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ ...defaultSmoothenTokenRebaseParams, postCLBalance: ETH(101) }) + ) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(101), + elRewardsVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, ETH(1)) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // withdrawals + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(101), + withdrawalVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, ETH(1)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // withdrawals + el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(101), + elRewardsVaultBalance: ETH(5), + withdrawalVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, ETH(1)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, 0) + // shares requested to burn + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(101), + sharesRequestedToBurn: ETH(5), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, '980392156862745098') // ETH(100. - (101. / 1.02)) + assert.equals(sharesToBurn, '980392156862745098') // the same as above since no withdrawals + }) + + it('non-trivial smoothen rebase works when post CL < pre CL and withdrawals', async () => { + const newRebaseLimit = 5_000_000 // 0.5% + await oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { + from: managersRoster.maxPositiveTokenRebaseManagers[0], + }) + + const defaultRebaseParams = { + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(99), + etherToLockForWithdrawals: ETH(10), + newSharesToBurnForWithdrawals: ETH(10), + } + + let { withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase(...Object.values(defaultRebaseParams)) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, ETH(10)) + // el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultRebaseParams, + elRewardsVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, ETH(1.5)) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, '9950248756218905472') // 100. - 90.5 / 1.005 + // withdrawals + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultRebaseParams, + withdrawalVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, ETH(1.5)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, '9950248756218905472') // 100. - 90.5 / 1.005 + // withdrawals + el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultRebaseParams, + withdrawalVaultBalance: ETH(5), + elRewardsVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, ETH(1.5)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, '9950248756218905472') // 100. - 90.5 / 1.005 + // shares requested to burn + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultRebaseParams, + sharesRequestedToBurn: ETH(5), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, '1492537313432835820') // ETH(100. - (99. / 1.005)) + assert.equals(sharesToBurn, '11442786069651741293') // ETH(100. - (89. / 1.005)) + }) + + it('non-trivial smoothen rebase works when post CL > pre CL and withdrawals', async () => { + const newRebaseLimit = 40_000_000 // 4% + await oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { + from: managersRoster.maxPositiveTokenRebaseManagers[0], + }) + + const defaultRebaseParams = { + ...defaultSmoothenTokenRebaseParams, + postCLBalance: ETH(102), + etherToLockForWithdrawals: ETH(10), + newSharesToBurnForWithdrawals: ETH(10), + } + + let { withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase(...Object.values(defaultRebaseParams)) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, ETH(10)) + // el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultRebaseParams, + elRewardsVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, ETH(2)) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, '9615384615384615384') // 100. - 94. / 1.04 + // withdrawals + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultRebaseParams, + withdrawalVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, ETH(2)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, '9615384615384615384') // 100. - 94. / 1.04 + // withdrawals + el rewards + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultRebaseParams, + withdrawalVaultBalance: ETH(5), + elRewardsVaultBalance: ETH(5), + }) + )) + assert.equals(withdrawals, ETH(2)) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, 0) + assert.equals(sharesToBurn, '9615384615384615384') // 100. - 94. / 1.04 + // shares requested to burn + ;({ withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase( + ...Object.values({ + ...defaultRebaseParams, + sharesRequestedToBurn: ETH(5), + }) + )) + assert.equals(withdrawals, 0) + assert.equals(elRewards, 0) + assert.equals(simulatedSharesToBurn, '1923076923076923076') // ETH(100. - (102. / 1.04)) + assert.equals(sharesToBurn, '11538461538461538461') // ETH(100. - (92. / 1.04)) + }) + + it('share rate ~1 case with huge withdrawal', async () => { + const newRebaseLimit = 1_000_000 // 0.1% + await oracleReportSanityChecker.setMaxPositiveTokenRebase(newRebaseLimit, { + from: managersRoster.maxPositiveTokenRebaseManagers[0], + }) + + const rebaseParams = { + preTotalPooledEther: ETH('1000000'), + preTotalShares: ETH('1000000'), + preCLBalance: ETH('1000000'), + postCLBalance: ETH('1000000'), + elRewardsVaultBalance: ETH(500), + withdrawalVaultBalance: ETH(500), + sharesRequestedToBurn: ETH(0), + etherToLockForWithdrawals: ETH(40000), + newSharesToBurnForWithdrawals: ETH(40000), + } + + const { withdrawals, elRewards, simulatedSharesToBurn, sharesToBurn } = + await oracleReportSanityChecker.smoothenTokenRebase(...Object.values(rebaseParams)) + + assert.equals(withdrawals, ETH(500)) + assert.equals(elRewards, ETH(500)) + assert.equals(simulatedSharesToBurn, ETH(0)) + assert.equals(sharesToBurn, '39960039960039960039960') // ETH(1000000 - 961000. / 1.001) + }) + }) }) From 0f4188aaa8af29eca7fc2eb775908aeeaf35ec98 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Wed, 22 Mar 2023 01:10:46 +0700 Subject: [PATCH 52/66] test: NodeOperatorsRegistry happy path activation/deactivation flow and more minor asserts --- ...node-operators-registry-happy-path.test.js | 357 ++++++++++++------ 1 file changed, 252 insertions(+), 105 deletions(-) diff --git a/test/0.4.24/node-operators-registry-happy-path.test.js b/test/0.4.24/node-operators-registry-happy-path.test.js index 089e91906..380a1c9c3 100644 --- a/test/0.4.24/node-operators-registry-happy-path.test.js +++ b/test/0.4.24/node-operators-registry-happy-path.test.js @@ -7,6 +7,7 @@ const { DSMAttestMessage } = require('../helpers/signatures') const { deployProtocol } = require('../helpers/protocol') const { setupNodeOperatorsRegistry, NodeOperatorsRegistry } = require('../helpers/staking-modules') const { e18, e27, toBN, ETH } = require('../helpers/utils') +const { getCurrentBlockTimestamp, advanceChainTime, waitBlocks } = require('../helpers/blockchain') const { getAccountingReportDataItems, encodeExtraDataItems, @@ -15,6 +16,7 @@ const { calcAccountingReportDataHash, } = require('../0.8.9/oracle/accounting-oracle-deploy.test') +const PENALTY_DELAY = 24 * 60 * 60 // 1 days const E9 = toBN(10).pow(toBN(9)) const ADDRESS_1 = '0x0000000000000000000000000000000000000001' @@ -87,7 +89,64 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re let stateTotalVetted = 0 let stateTotalDepositable = 0 - let stateTotalDeposited = 0 + + /** + * Helpers + */ + + async function addKeysToOperator(operatorId, keysToAdd) { + const keys = new signingKeys.FakeValidatorKeys(keysToAdd) + const operatorBefore = await nor.getNodeOperator(operatorId, true) + const keysCountBefore = +(await nor.getTotalSigningKeyCount(operatorId)) + const unusedKeysCountBefore = +(await nor.getUnusedSigningKeyCount(operatorId)) + + await nor.addSigningKeys(operatorId, keys.count, ...keys.slice(), { from: voting.address }) + + const operator = await nor.getNodeOperator(operatorId, true) + const keysCount = +(await nor.getTotalSigningKeyCount(operatorId)) + const unusedKeysCount = +(await nor.getUnusedSigningKeyCount(operatorId)) + + assert.equals(+operatorBefore.totalSigningKeys + keys.count, operator.totalSigningKeys) + assert.equals(keysCountBefore + keys.count, keysCount) + assert.equals(unusedKeysCountBefore + keys.count, unusedKeysCount) + + for (let i = 0; i < keys.count; ++i) { + const { key, depositSignature } = await nor.getSigningKey(operatorId, i + keysCountBefore) + const [expectedPublicKey, expectedSignature] = keys.get(i) + assert.equals(key, expectedPublicKey) + assert.equals(depositSignature, expectedSignature) + } + } + + async function setNodeOperatorStakingLimit(operatorId, stakingLimit) { + const operatorBefore = await nor.getNodeOperator(operatorId, true) + const summaryBefore = await nor.getNodeOperatorSummary(operatorId) + const stakingModuleSummaryBefore = await nor.getStakingModuleSummary() + + const tx = await nor.setNodeOperatorStakingLimit(operatorId, stakingLimit, { from: voting.address }) + + const operator = await nor.getNodeOperator(operatorId, true) + const summary = await nor.getNodeOperatorSummary(operatorId) + const stakingModuleSummary = await nor.getStakingModuleSummary() + const expectedLimit = +operatorBefore.stakingLimit + stakingLimit + const expectedDepositable = +summaryBefore.depositableValidatorsCount + stakingLimit + + assert.equals(operator.stakingLimit, expectedLimit) + assert.equals(summary.depositableValidatorsCount, expectedDepositable) + assert.equals( + +stakingModuleSummaryBefore.depositableValidatorsCount + stakingLimit, + stakingModuleSummary.depositableValidatorsCount + ) + assert.emits( + tx, + 'VettedSigningKeysCountChanged', + { + nodeOperatorId: operatorId, + approvedValidatorsCount: expectedLimit, + }, + NOR_ABI_ASSERT_EV + ) + } async function assertDepositCall(callIdx, operatorId, keyIdx) { const regCall = await depositContract.calls.call(callIdx) @@ -101,16 +160,16 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re async function assertOperatorDeposits(operatorData, deposited, keysLeft) { const operator = await nor.getNodeOperator(operatorData.id, true) const summary = await nor.getNodeOperatorSummary(operatorData.id) - assert.equals(operator.usedSigningKeys, deposited, `${operatorData.name} usedSigningKeys should be ${deposited}`) + assert.equals(operator.usedSigningKeys, deposited, `${operatorData.name} usedSigningKeys asserting to ${deposited}`) assert.equals( summary.totalDepositedValidators, deposited, - `${operatorData.name} totalDepositedValidators should be ${deposited}` + `${operatorData.name} totalDepositedValidators asserting to ${deposited}` ) assert.equals( summary.depositableValidatorsCount, keysLeft, - `${operatorData.name} totalDepositedValidators should be ${keysLeft}` + `${operatorData.name} depositableValidatorsCount asserting to ${keysLeft}` ) } @@ -119,17 +178,17 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re assert.equals( summary.isTargetLimitActive, isActive, - `${operatorData.name} isTargetLimitActive limit should be set to ${isActive}` + `${operatorData.name} isTargetLimitActive limit asserting to ${isActive}` ) assert.equals( summary.targetValidatorsCount, limit, - `${operatorData.name} targetValidatorsCount should be set to ${limit}` + `${operatorData.name} targetValidatorsCount asserting to ${limit}` ) assert.equals( summary.depositableValidatorsCount, depositable, - `${operatorData.name} depositableValidatorsCount should be set to ${depositable}` + `${operatorData.name} depositableValidatorsCount asserting to ${depositable}` ) } @@ -145,6 +204,74 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re assert.isClose(event.args.sharesPenalizedAmount, amount, 10) } + async function makeDeposit(stakesDeposited) { + const depositedValue = ETH(32 * stakesDeposited) + const depositCallCountBefore = +(await depositContract.totalCalls()) + const stakingModuleSummaryBefore = await nor.getStakingModuleSummary() + + await web3.eth.sendTransaction({ to: lido.address, from: user1, value: depositedValue }) + + const block = await web3.eth.getBlock('latest') + const keysOpIndex = await nor.getKeysOpIndex() + + DSMAttestMessage.setMessagePrefix(await dsm.ATTEST_MESSAGE_PREFIX()) + + const attest = new DSMAttestMessage(block.number, block.hash, depositRoot, curatedId, keysOpIndex) + const signatures = [ + attest.sign(guardians.privateKeys[guardians.addresses[0]]), + attest.sign(guardians.privateKeys[guardians.addresses[1]]), + ] + + // triggers flow: + // DSM.depositBufferedEther() -> Lido.deposit() -> StakingRouter.deposit() -> Module.obtainDepositData() + await dsm.depositBufferedEther(block.number, block.hash, depositRoot, curatedId, keysOpIndex, '0x', signatures) + + const depositCallCount = +(await depositContract.totalCalls()) + const stakingModuleSummary = await nor.getStakingModuleSummary() + + assert.equals(depositCallCount - depositCallCountBefore, stakesDeposited) + assert.equals( + +stakingModuleSummaryBefore.depositableValidatorsCount - +stakingModuleSummary.depositableValidatorsCount, + stakesDeposited + ) + } + + async function deactivateNodeOperator(operatorId) { + const operatorBefore = await nor.getNodeOperator(operatorId, true) + const summaryBefore = await nor.getNodeOperatorSummary(operatorId) + const activeOperatorsBefore = await nor.getActiveNodeOperatorsCount() + const stakingModuleSummaryBefore = await nor.getStakingModuleSummary() + const keysToCut = +summaryBefore.depositableValidatorsCount + + const tx = await nor.deactivateNodeOperator(operatorId, { from: voting.address }) + + const operator = await nor.getNodeOperator(operatorId, true) + const summary = await nor.getNodeOperatorSummary(operatorId) + const activeOperatorsAfter = await nor.getActiveNodeOperatorsCount() + const stakingModuleSummary = await nor.getStakingModuleSummary() + + assert.isFalse(operator.active) + assert.isFalse(await nor.getNodeOperatorIsActive(operatorId)) + assert.equals(+activeOperatorsBefore - 1, +activeOperatorsAfter) + assert.emits(tx, 'NodeOperatorActiveSet', { nodeOperatorId: operatorId, active: false }) + if (+operatorBefore.stakingLimit - +operatorBefore.usedSigningKeys > 0) { + assert.emits(tx, 'VettedSigningKeysCountChanged', { + nodeOperatorId: operatorId, + approvedValidatorsCount: operator.usedSigningKeys, + }) + } + assert.equals(summary.depositableValidatorsCount, 0) + assert.equals(operator.stakingLimit, operator.usedSigningKeys) + assert.equals( + +stakingModuleSummaryBefore.depositableValidatorsCount - keysToCut, + stakingModuleSummary.depositableValidatorsCount + ) + } + + /** + * Deploy + */ + before('deploy base app', async () => { const deployed = await deployProtocol({ stakingModulesFactory: async (protocol) => { @@ -188,6 +315,10 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re curatedId = curated.id }) + /** + * Actual flow + */ + describe('Happy path', () => { context('Initial setup', () => { it('Add node operator', async () => { @@ -218,16 +349,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re }) it('Deactivate node operator 4', async () => { - const operatorId = Operator4.id - const activeOperatorsBefore = await nor.getActiveNodeOperatorsCount() - const tx = await nor.deactivateNodeOperator(operatorId, { from: voting.address }) - const operator = await nor.getNodeOperator(operatorId, true) - const activeOperatorsAfter = await nor.getActiveNodeOperatorsCount() - - assert.isFalse(await nor.getNodeOperatorIsActive(operatorId)) - assert.isFalse(operator.active) - assert.equals(Number(activeOperatorsBefore) - 1, Number(activeOperatorsAfter)) - assert.emits(tx, 'NodeOperatorActiveSet', { nodeOperatorId: operatorId, active: false }) + await deactivateNodeOperator(Operator4.id) }) it('Set name', async () => { @@ -249,22 +371,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re it('Add signing keys', async () => { await forEachSync(NODE_OPERATORS, async (operatorData, i) => { - const keys = new signingKeys.FakeValidatorKeys(operatorData.totalSigningKeysCount) - await nor.addSigningKeys(operatorData.id, keys.count, ...keys.slice(), { from: voting.address }) - - const operator = await nor.getNodeOperator(operatorData.id, true) - const keysCount = await nor.getTotalSigningKeyCount(operatorData.id) - const unusedKeysCount = await nor.getUnusedSigningKeyCount(operatorData.id) - assert.equals(keys.count, operator.totalSigningKeys.toNumber()) - assert.equals(keys.count, keysCount) - assert.equals(keys.count, unusedKeysCount) - - for (let i = 0; i < keys.count; ++i) { - const { key, depositSignature } = await nor.getSigningKey(operatorData.id, i) - const [expectedPublicKey, expectedSignature] = keys.get(i) - assert.equals(key, expectedPublicKey) - assert.equals(depositSignature, expectedSignature) - } + await addKeysToOperator(operatorData.id, operatorData.totalSigningKeysCount) }) }) @@ -272,13 +379,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re await forEachSync(NODE_OPERATORS, async (operatorData, i) => { if (!(await nor.getNodeOperatorIsActive(operatorData.id))) return stateTotalVetted += operatorData.vettedSigningKeysCount - await nor.setNodeOperatorStakingLimit(operatorData.id, operatorData.vettedSigningKeysCount, { - from: voting.address, - }) - const summary = await nor.getNodeOperatorSummary(operatorData.id) - const operator = await nor.getNodeOperator(operatorData.id, true) - assert.equals(operator.stakingLimit, operatorData.vettedSigningKeysCount) - assert.equals(summary.depositableValidatorsCount, operatorData.vettedSigningKeysCount) + await setNodeOperatorStakingLimit(operatorData.id, operatorData.vettedSigningKeysCount) }) const stakingModuleSummary = await nor.getStakingModuleSummary() @@ -357,43 +458,26 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re assert.equals(summary.totalDepositedValidators, 0) assert.equals(summary.depositableValidatorsCount, stateTotalDepositable) }) + + it('Modify penalty delay', async () => { + const tx = await nor.setStuckPenaltyDelay(PENALTY_DELAY, { from: voting.address }) + assert.emits(tx, 'StuckPenaltyDelayChanged', { stuckPenaltyDelay: PENALTY_DELAY }, NOR_ABI_ASSERT_EV) + assert.equals(await nor.getStuckPenaltyDelay(), PENALTY_DELAY) + }) }) context('Deposits distribution', () => { it('Obtain deposit data', async () => { - const stakesDeposited = 6 - const depositedValue = ETH(32 * stakesDeposited) - - await web3.eth.sendTransaction({ to: lido.address, from: user1, value: depositedValue }) - - const block = await web3.eth.getBlock('latest') - const keysOpIndex = await nor.getKeysOpIndex() - - DSMAttestMessage.setMessagePrefix(await dsm.ATTEST_MESSAGE_PREFIX()) - - const attest = new DSMAttestMessage(block.number, block.hash, depositRoot, curatedId, keysOpIndex) - const signatures = [ - attest.sign(guardians.privateKeys[guardians.addresses[0]]), - attest.sign(guardians.privateKeys[guardians.addresses[1]]), - ] - /** * Expected deposits fill 1 2 3 4 5 6 * Operator 1 [ x x x ] * Operator 2 (limit = 1) [ x ] * Operator 3 [ x x ] + * Operator 4 (inactive) [ ] */ - // triggers flow: - // DSM.depositBufferedEther() -> Lido.deposit() -> StakingRouter.deposit() -> Module.obtainDepositData() - await dsm.depositBufferedEther(block.number, block.hash, depositRoot, curatedId, keysOpIndex, '0x', signatures) - - stateTotalDeposited += stakesDeposited - - const depositCallCount = await depositContract.totalCalls() - assert.equals(depositCallCount, stakesDeposited) + await makeDeposit(6) - // Target Limit affects here, that's why operator 2 receives only 1 deposit await assertDepositCall(0, Operator1.id, 0) await assertDepositCall(1, Operator1.id, 1) await assertDepositCall(2, Operator1.id, 2) @@ -401,15 +485,9 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re await assertDepositCall(4, Operator3.id, 0) await assertDepositCall(5, Operator3.id, 1) - const stakingModuleSummaryAfter = await nor.getStakingModuleSummary() - assert.equals(stakingModuleSummaryAfter.totalDepositedValidators, stateTotalDeposited) - assert.equals(stakingModuleSummaryAfter.depositableValidatorsCount, stateTotalDepositable - stateTotalDeposited) - await assertOperatorDeposits(Operator1, 3, 4) await assertOperatorDeposits(Operator2, 1, 0) await assertOperatorDeposits(Operator3, 2, 3) - - // TODO: assert disabled Operator 4 should not be called while depositing }) it('Rewards distribution', async () => { @@ -452,7 +530,7 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re const reportFields = { consensusVersion, numValidators: 6, - clBalanceGwei: toBN(ETH(32 * stateTotalDeposited + 1)).div(E9), + clBalanceGwei: toBN(ETH(32 * 6 + 1)).div(E9), stakingModuleIdsWithNewlyExitedValidators: [curatedId], numExitedValidatorsByStakingModule: [2], withdrawalVaultBalance: e18(0), @@ -562,8 +640,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re assert.isClose(sharesRewards1After, rewardAmountForOperator1, 10) assert.isClose(sharesRewards2After, rewardAmountForOperator2, 10) assert.isClose(sharesRewards3After, rewardAmountForOperator3, 10) - - // TODO: Assert disabled Operator 4 to be untouched }) }) @@ -611,10 +687,6 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re }) context('Keys and limits settings tweaks', () => { - /** - * TODO: TargetLimit allows to deposit after exit - */ - it('Disable TargetLimit', async () => { const operatorData = Operator2 @@ -677,50 +749,125 @@ contract('NodeOperatorsRegistry', ([appManager, rewards1, rewards2, rewards3, re }) it('Refund stucked keys for Operator 2', async () => { - // TODO: [do more research] Refuneded keys - // 1. Operator already have stucked keys - // 2. Set refunded via StakingRouter.updateRefundedValidatorsCount() -> NOR.updateRefundedValidatorsCount() with refunded == stuckKeys - // 3. Wait for half of penalty delay and check that penalty still with NOR.getRewardsDistribution() and Oracle report and obtain deposit data - // 4. Wait for end of penalty delay and check that it is gone - // assert NOR.getStuckPenaltyDelay() - // assert NOR.setStuckPenaltyDelay() - // assert penalty affects on TargetLimit + const operatorData = Operator2 + const penaltyDelay = await nor.getStuckPenaltyDelay() + + // StakingRouter.updateRefundedValidatorsCount() -> NOR.updateRefundedValidatorsCount() + const tx = await stakingRouter.updateRefundedValidatorsCount(curatedId, operatorData.id, 1, { + from: voting.address, + }) + + const timestamp = await getCurrentBlockTimestamp() + const expectedPenaltyEnd = +timestamp + +penaltyDelay + const summary = await nor.getNodeOperatorSummary(operatorData.id) + + assert.equals(summary.stuckPenaltyEndTimestamp, expectedPenaltyEnd) + assert.equals(summary.depositableValidatorsCount, 0) + assert.equals(summary.refundedValidatorsCount, 1) + + assert.emits( + tx, + 'StuckPenaltyStateChanged', + { + nodeOperatorId: operatorData.id, + stuckValidatorsCount: 1, + refundedValidatorsCount: 1, + stuckPenaltyEndTimestamp: expectedPenaltyEnd, + }, + NOR_ABI_ASSERT_EV + ) + }) + + it('Wait for half of penalty delay and check that penalty still applies', async () => { + const operatorData = Operator2 + const penaltyDelay = await nor.getStuckPenaltyDelay() + + await advanceChainTime(penaltyDelay / 2) + await assert.reverts(nor.clearNodeOperatorPenalty(operatorData.id), 'CANT_CLEAR_PENALTY') + + const summary = await nor.getNodeOperatorSummary(operatorData.id) + assert.equals(summary.depositableValidatorsCount, 0) + assert.equals(summary.refundedValidatorsCount, 1) + }) + + it('Wait for end of penalty delay and check that it is gone', async () => { + const operatorData = Operator2 + const penaltyDelay = await nor.getStuckPenaltyDelay() + + await advanceChainTime(penaltyDelay / 2 + 100) + await nor.clearNodeOperatorPenalty(operatorData.id) + + const summary = await nor.getNodeOperatorSummary(operatorData.id) + assert.equals(summary.depositableValidatorsCount, 9) }) }) context('Activation/deactivation', () => { it('Deactivate operator 1', async () => { - // TODO: NOR....() - // Deactivate Operator that was in use before + await deactivateNodeOperator(Operator1.id) }) it('Activate operator 4', async () => { - // Activate previously disabled Operator and check it will be used in deposit flow - }) + const operatorData = Operator4 + const activeOperatorsBefore = await nor.getActiveNodeOperatorsCount() - it('Add keys to operator 4', async () => {}) + const tx = await nor.activateNodeOperator(operatorData.id, { from: voting.address }) - it('Set operator 4 staking limit', async () => {}) + const operator = await nor.getNodeOperator(operatorData.id, true) + const activeOperatorsAfter = await nor.getActiveNodeOperatorsCount() - it('Make another deposit', async () => { - // Make another deposit to check: - // — deactivated node operator will not get deposit - // — target limit was disabled properly before - // — operator with refunded keys gets deposits + assert.isTrue(operator.active) + assert.isTrue(await nor.getNodeOperatorIsActive(operatorData.id)) + assert.equals(+activeOperatorsBefore + 1, +activeOperatorsAfter) + assert.emits(tx, 'NodeOperatorActiveSet', { nodeOperatorId: operatorData.id, active: true }) + assert.equals(operator.stakingLimit, operator.usedSigningKeys) }) - it('Make a report for rewards distribution', async () => { - // Assert rewards not distributed to disabled operator + it('Set operator 4 staking limit', async () => { + await setNodeOperatorStakingLimit(Operator4.id, Operator4.vettedSigningKeysCount) }) + + it('Make another deposit', async () => { + /** + * Expected deposits fill 1 2 3 4 5 + * Operator 1 (inactive) [ ] + * Operator 2 (refunded) [ x x ] + * Operator 3 (stuck) [ ] + * Operator 4 (activated) [ x x x ] + */ + + await waitBlocks(+(await dsm.getMinDepositBlockDistance())) + await makeDeposit(5) + + // Continuing numbers from previous deposit + await assertDepositCall(6, Operator2.id, 1) + await assertDepositCall(7, Operator2.id, 2) + await assertDepositCall(8, Operator4.id, 0) + await assertDepositCall(9, Operator4.id, 1) + await assertDepositCall(10, Operator4.id, 2) + + await assertOperatorDeposits(Operator2, 3, 7) + await assertOperatorDeposits(Operator4, 3, 2) + }) + + // TODO: [optional] Make a report again if needed }) - // TODO: StakingRouter.setWithdrawalCredentials() -> NOR.onWithdrawalCredentialsChanged() - // assert depositable of all operators should be zero - // assert totalValidatorsCount of all operators == deposited validators - // assert NOR.getStakingModuleSummary() — depositable = 0, exited = same, deposited = same + context('Withdrawal credentials modifying', () => { + it('setWithdrawalCredentials', async () => { + const summaryBefore = await nor.getStakingModuleSummary() + withdrawalCredentials = '0x'.padEnd(66, '5678') + await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting.address }) + const summary = await nor.getStakingModuleSummary() + console.log(summary, summaryBefore) - // TODO: [optional] add NOR.getNonce() somewhere + assert.equals(summaryBefore.totalExitedValidators, summary.totalExitedValidators) + assert.equals(summaryBefore.totalDepositedValidators, summary.totalDepositedValidators) + assert.notEqual(summary.depositableValidatorsCount, summaryBefore.depositableValidatorsCount) + assert.equals(summary.depositableValidatorsCount, 0) + }) + }) - // TODO: [optional] assert NOR._getSigningKeysAllocationData() if it is possible + // TODO: [optional] assert NOR.getNonce() and NonceChanged event if needed }) }) From 137b3811421d7ce5445a1f69d0190c3937fb6de0 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 21 Mar 2023 22:05:56 +0300 Subject: [PATCH 53/66] test: more tests for sanity checker --- .../OracleReportSanityChecker.sol | 2 +- .../oracle-report-sanity-checker.test.js | 126 +++++++++++++++++- 2 files changed, 123 insertions(+), 5 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index f4b40ac52..1329ad6eb 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -105,7 +105,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { bytes32 public constant CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE = keccak256("CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE"); bytes32 public constant ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE = - keccak256("ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE_ROLE"); + keccak256("ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE"); bytes32 public constant ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE = keccak256("ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE"); bytes32 public constant SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE = diff --git a/test/0.8.9/oracle-report-sanity-checker.test.js b/test/0.8.9/oracle-report-sanity-checker.test.js index f9d23f884..9d740a13c 100644 --- a/test/0.8.9/oracle-report-sanity-checker.test.js +++ b/test/0.8.9/oracle-report-sanity-checker.test.js @@ -1,7 +1,7 @@ const { artifacts, contract, ethers } = require('hardhat') const { ETH } = require('../helpers/utils') const { assert } = require('../helpers/assert') -const { getCurrentBlockTimestamp } = require('../helpers/blockchain') +const { getCurrentBlockTimestamp, EvmSnapshot } = require('../helpers/blockchain') const mocksFilePath = 'contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol' const LidoStub = artifacts.require(`${mocksFilePath}:LidoStub`) @@ -22,7 +22,7 @@ function wei(number, units = 'wei') { } contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewardsVault, ...accounts]) => { - let oracleReportSanityChecker, lidoLocatorMock, lidoMock, withdrawalQueueMock, burnerMock + let oracleReportSanityChecker, lidoLocatorMock, lidoMock, withdrawalQueueMock, burnerMock, snapshot const managersRoster = { allLimitsManagers: accounts.slice(0, 2), churnValidatorsPerDayLimitManagers: accounts.slice(2, 4), @@ -81,6 +81,13 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa from: deployer, } ) + + snapshot = new EvmSnapshot(ethers.provider) + await snapshot.make() + }) + + afterEach(async () => { + await snapshot.rollback() }) describe('getLidoLocator()', () => { @@ -220,13 +227,14 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa .maxAccountingExtraDataListItemsCount const newValue = 31 assert.notEquals(newValue, previousValue) - await oracleReportSanityChecker.setMaxAccountingExtraDataListItemsCount(newValue, { + const tx = await oracleReportSanityChecker.setMaxAccountingExtraDataListItemsCount(newValue, { from: managersRoster.maxAccountingExtraDataListItemsCountManagers[0], }) assert.equals( (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount, newValue ) + assert.emits(tx, 'MaxAccountingExtraDataListItemsCountSet', { maxAccountingExtraDataListItemsCount: newValue }) }) it('set maxNodeOperatorsPerExtraDataItemCount', async () => { @@ -234,13 +242,71 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa .maxNodeOperatorsPerExtraDataItemCount const newValue = 33 assert.notEquals(newValue, previousValue) - await oracleReportSanityChecker.setMaxNodeOperatorsPerExtraDataItemCount(newValue, { + const tx = await oracleReportSanityChecker.setMaxNodeOperatorsPerExtraDataItemCount(newValue, { from: managersRoster.maxNodeOperatorsPerExtraDataItemCountManagers[0], }) assert.equals( (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItemCount, newValue ) + assert.emits(tx, 'MaxNodeOperatorsPerExtraDataItemCountSet', { maxNodeOperatorsPerExtraDataItemCount: newValue }) + }) + + it('set one-off CL balance decrease', async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).oneOffCLBalanceDecreaseBPLimit + const newValue = 3 + assert.notEquals(newValue, previousValue) + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setOneOffCLBalanceDecreaseBPLimit(newValue, { + from: deployer, + }), + deployer, + 'ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE' + ) + const tx = await oracleReportSanityChecker.setOneOffCLBalanceDecreaseBPLimit(newValue, { + from: managersRoster.oneOffCLBalanceDecreaseLimitManagers[0], + }) + assert.equals((await oracleReportSanityChecker.getOracleReportLimits()).oneOffCLBalanceDecreaseBPLimit, newValue) + assert.emits(tx, 'OneOffCLBalanceDecreaseBPLimitSet', { oneOffCLBalanceDecreaseBPLimit: newValue }) + }) + + it('set annual balance increase', async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).annualBalanceIncreaseBPLimit + const newValue = 9 + assert.notEquals(newValue, previousValue) + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setAnnualBalanceIncreaseBPLimit(newValue, { + from: deployer, + }), + deployer, + 'ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE' + ) + const tx = await oracleReportSanityChecker.setAnnualBalanceIncreaseBPLimit(newValue, { + from: managersRoster.annualBalanceIncreaseLimitManagers[0], + }) + assert.equals((await oracleReportSanityChecker.getOracleReportLimits()).annualBalanceIncreaseBPLimit, newValue) + assert.emits(tx, 'AnnualBalanceIncreaseBPLimitSet', { annualBalanceIncreaseBPLimit: newValue }) + }) + + it('set simulated share rate deviation', async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).simulatedShareRateDeviationBPLimit + const newValue = 7 + assert.notEquals(newValue, previousValue) + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setSimulatedShareRateDeviationBPLimit(newValue, { + from: deployer, + }), + deployer, + 'SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE' + ) + const tx = await oracleReportSanityChecker.setSimulatedShareRateDeviationBPLimit(newValue, { + from: managersRoster.shareRateDeviationLimitManagers[0], + }) + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).simulatedShareRateDeviationBPLimit, + newValue + ) + assert.emits(tx, 'SimulatedShareRateDeviationBPLimitSet', { simulatedShareRateDeviationBPLimit: newValue }) }) }) @@ -808,4 +874,56 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa assert.equals(sharesToBurn, '39960039960039960039960') // ETH(1000000 - 961000. / 1.001) }) }) + + describe('churn limit', () => { + it('setChurnValidatorsPerDayLimit works', async () => { + const oldChurnLimit = defaultLimitsList.churnValidatorsPerDayLimit + await oracleReportSanityChecker.checkExitedValidatorsRatePerDay(oldChurnLimit) + await assert.reverts( + oracleReportSanityChecker.checkExitedValidatorsRatePerDay(oldChurnLimit + 1), + `ExitedValidatorsLimitExceeded(${oldChurnLimit}, ${oldChurnLimit + 1})` + ) + assert.equals((await oracleReportSanityChecker.getOracleReportLimits()).churnValidatorsPerDayLimit, oldChurnLimit) + + const newChurnLimit = 30 + assert.notEquals(newChurnLimit, oldChurnLimit) + + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setChurnValidatorsPerDayLimit(newChurnLimit, { from: deployer }), + deployer, + 'CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE' + ) + + const tx = await oracleReportSanityChecker.setChurnValidatorsPerDayLimit(newChurnLimit, { + from: managersRoster.churnValidatorsPerDayLimitManagers[0], + }) + + assert.emits(tx, 'ChurnValidatorsPerDayLimitSet', { churnValidatorsPerDayLimit: newChurnLimit }) + assert.equals((await oracleReportSanityChecker.getOracleReportLimits()).churnValidatorsPerDayLimit, newChurnLimit) + + await oracleReportSanityChecker.checkExitedValidatorsRatePerDay(newChurnLimit) + await assert.reverts( + oracleReportSanityChecker.checkExitedValidatorsRatePerDay(newChurnLimit + 1), + `ExitedValidatorsLimitExceeded(${newChurnLimit}, ${newChurnLimit + 1})` + ) + }) + + it('checkAccountingOracleReport: churnLimit works', async () => { + const churnLimit = defaultLimitsList.churnValidatorsPerDayLimit + assert.equals((await oracleReportSanityChecker.getOracleReportLimits()).churnValidatorsPerDayLimit, churnLimit) + + await oracleReportSanityChecker.checkAccountingOracleReport( + ...Object.values({ ...correctLidoOracleReport, postCLValidators: churnLimit }) + ) + await assert.reverts( + oracleReportSanityChecker.checkAccountingOracleReport( + ...Object.values({ + ...correctLidoOracleReport, + postCLValidators: churnLimit + 1, + }) + ), + `IncorrectAppearedValidators(${churnLimit + 1})` + ) + }) + }) }) From 6516bb88c6741ddafd32019cea4b99c930740394 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 21 Mar 2023 22:54:04 +0300 Subject: [PATCH 54/66] test: almost full coverage for sanity checker --- .../oracle-report-sanity-checker.test.js | 209 ++++++++++++++---- 1 file changed, 169 insertions(+), 40 deletions(-) diff --git a/test/0.8.9/oracle-report-sanity-checker.test.js b/test/0.8.9/oracle-report-sanity-checker.test.js index 9d740a13c..dd04aef3f 100644 --- a/test/0.8.9/oracle-report-sanity-checker.test.js +++ b/test/0.8.9/oracle-report-sanity-checker.test.js @@ -129,6 +129,13 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa assert.notEquals(limitsBefore.requestTimestampMargin, newLimitsList.requestTimestampMargin) assert.notEquals(limitsBefore.maxPositiveTokenRebase, newLimitsList.maxPositiveTokenRebase) + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setOracleReportLimits(Object.values(newLimitsList), { + from: deployer, + }), + deployer, + 'ALL_LIMITS_MANAGER_ROLE' + ) await oracleReportSanityChecker.setOracleReportLimits(Object.values(newLimitsList), { from: managersRoster.allLimitsManagers[0], }) @@ -161,7 +168,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa it('reverts with error IncorrectWithdrawalsVaultBalance() when actual withdrawal vault balance is less than passed', async () => { const currentWithdrawalVaultBalance = await ethers.provider.getBalance(withdrawalVault) - await assert.revertsWithCustomError( + await assert.reverts( oracleReportSanityChecker.checkAccountingOracleReport( ...Object.values({ ...correctLidoOracleReport, withdrawalVaultBalance: currentWithdrawalVaultBalance.add(1) }) ), @@ -171,7 +178,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa it('reverts with error IncorrectELRewardsVaultBalance() when actual el rewards vault balance is less than passed', async () => { const currentELRewardsVaultBalance = await ethers.provider.getBalance(elRewardsVault) - await assert.revertsWithCustomError( + await assert.reverts( oracleReportSanityChecker.checkAccountingOracleReport( ...Object.values({ ...correctLidoOracleReport, elRewardsVaultBalance: currentELRewardsVaultBalance.add(1) }) ), @@ -186,7 +193,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa const withdrawalVaultBalance = wei(500, 'eth') const unifiedPostCLBalance = postCLBalance + withdrawalVaultBalance const oneOffCLBalanceDecreaseBP = (maxBasisPoints * (preCLBalance - unifiedPostCLBalance)) / preCLBalance - await assert.revertsWithCustomError( + await assert.reverts( oracleReportSanityChecker.checkAccountingOracleReport( ...Object.values({ ...correctLidoOracleReport, @@ -207,7 +214,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa const timeElapsed = BigInt(correctLidoOracleReport.timeElapsed) const annualBalanceIncrease = (secondsInOneYear * maxBasisPoints * (postCLBalance - preCLBalance)) / preCLBalance / timeElapsed - await assert.revertsWithCustomError( + await assert.reverts( oracleReportSanityChecker.checkAccountingOracleReport( ...Object.values({ ...correctLidoOracleReport, @@ -222,36 +229,6 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa await oracleReportSanityChecker.checkAccountingOracleReport(...Object.values(correctLidoOracleReport)) }) - it('set maxAccountingExtraDataListItemsCount', async () => { - const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()) - .maxAccountingExtraDataListItemsCount - const newValue = 31 - assert.notEquals(newValue, previousValue) - const tx = await oracleReportSanityChecker.setMaxAccountingExtraDataListItemsCount(newValue, { - from: managersRoster.maxAccountingExtraDataListItemsCountManagers[0], - }) - assert.equals( - (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount, - newValue - ) - assert.emits(tx, 'MaxAccountingExtraDataListItemsCountSet', { maxAccountingExtraDataListItemsCount: newValue }) - }) - - it('set maxNodeOperatorsPerExtraDataItemCount', async () => { - const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()) - .maxNodeOperatorsPerExtraDataItemCount - const newValue = 33 - assert.notEquals(newValue, previousValue) - const tx = await oracleReportSanityChecker.setMaxNodeOperatorsPerExtraDataItemCount(newValue, { - from: managersRoster.maxNodeOperatorsPerExtraDataItemCountManagers[0], - }) - assert.equals( - (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItemCount, - newValue - ) - assert.emits(tx, 'MaxNodeOperatorsPerExtraDataItemCountSet', { maxNodeOperatorsPerExtraDataItemCount: newValue }) - }) - it('set one-off CL balance decrease', async () => { const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).oneOffCLBalanceDecreaseBPLimit const newValue = 3 @@ -310,7 +287,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa }) }) - describe('checkWithdrawalQueueOracleReport()', async () => { + describe('checkWithdrawalQueueOracleReport()', () => { const oldRequestId = 1 const newRequestId = 2 let oldRequestCreationTimestamp, newRequestCreationTimestamp @@ -330,7 +307,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa }) it('reverts with the error IncorrectRequestFinalization() when the creation timestamp of requestIdToFinalizeUpTo is too close to report timestamp', async () => { - await assert.revertsWithCustomError( + await assert.reverts( oracleReportSanityChecker.checkWithdrawalQueueOracleReport( ...Object.values({ ...correctWithdrawalQueueOracleReport, @@ -346,9 +323,27 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa ...Object.values(correctWithdrawalQueueOracleReport) ) }) + + it('set timestamp margin for finalization', async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).requestTimestampMargin + const newValue = 3302 + assert.notEquals(newValue, previousValue) + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setRequestTimestampMargin(newValue, { + from: deployer, + }), + deployer, + 'REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE' + ) + const tx = await oracleReportSanityChecker.setRequestTimestampMargin(newValue, { + from: managersRoster.requestTimestampMarginManagers[0], + }) + assert.equals((await oracleReportSanityChecker.getOracleReportLimits()).requestTimestampMargin, newValue) + assert.emits(tx, 'RequestTimestampMarginSet', { requestTimestampMargin: newValue }) + }) }) - describe('checkSimulatedShareRate', async () => { + describe('checkSimulatedShareRate', () => { const correctSimulatedShareRate = { postTotalPooledEther: ETH(9), postTotalShares: ETH(4), @@ -360,7 +355,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa it('reverts with error TooHighSimulatedShareRate() when reported and onchain share rate differs', async () => { const simulatedShareRate = BigInt(ETH(2.1)) * 10n ** 9n const actualShareRate = BigInt(2) * 10n ** 27n - await assert.revertsWithCustomError( + await assert.reverts( oracleReportSanityChecker.checkSimulatedShareRate( ...Object.values({ ...correctSimulatedShareRate, @@ -374,7 +369,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa it('reverts with error TooLowSimulatedShareRate() when reported and onchain share rate differs', async () => { const simulatedShareRate = BigInt(ETH(1.9)) * 10n ** 9n const actualShareRate = BigInt(2) * 10n ** 27n - await assert.revertsWithCustomError( + await assert.reverts( oracleReportSanityChecker.checkSimulatedShareRate( ...Object.values({ ...correctSimulatedShareRate, @@ -386,7 +381,7 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa }) it('reverts with error ActualShareRateIsZero() when actual share rate is zero', async () => { - await assert.revertsWithCustomError( + await assert.reverts( oracleReportSanityChecker.checkSimulatedShareRate( ...Object.values({ ...correctSimulatedShareRate, @@ -926,4 +921,138 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa ) }) }) + + describe('checkExitBusOracleReport', () => { + beforeEach(async () => { + await oracleReportSanityChecker.setOracleReportLimits(Object.values(defaultLimitsList), { + from: managersRoster.allLimitsManagers[0], + }) + }) + + it('checkExitBusOracleReport works', async () => { + const maxRequests = defaultLimitsList.maxValidatorExitRequestsPerReport + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).maxValidatorExitRequestsPerReport, + maxRequests + ) + + await oracleReportSanityChecker.checkExitBusOracleReport(maxRequests) + await assert.reverts( + oracleReportSanityChecker.checkExitBusOracleReport(maxRequests + 1), + `IncorrectNumberOfExitRequestsPerReport(${maxRequests})` + ) + }) + + it('setMaxExitRequestsPerOracleReport', async () => { + const oldMaxRequests = defaultLimitsList.maxValidatorExitRequestsPerReport + await oracleReportSanityChecker.checkExitBusOracleReport(oldMaxRequests) + await assert.reverts( + oracleReportSanityChecker.checkExitBusOracleReport(oldMaxRequests + 1), + `IncorrectNumberOfExitRequestsPerReport(${oldMaxRequests})` + ) + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).maxValidatorExitRequestsPerReport, + oldMaxRequests + ) + + const newMaxRequests = 306 + assert.notEquals(newMaxRequests, oldMaxRequests) + + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setMaxExitRequestsPerOracleReport(newMaxRequests, { from: deployer }), + deployer, + 'MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE' + ) + + const tx = await oracleReportSanityChecker.setMaxExitRequestsPerOracleReport(newMaxRequests, { + from: managersRoster.maxValidatorExitRequestsPerReportManagers[0], + }) + + assert.emits(tx, 'MaxValidatorExitRequestsPerReportSet', { maxValidatorExitRequestsPerReport: newMaxRequests }) + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).maxValidatorExitRequestsPerReport, + newMaxRequests + ) + + await oracleReportSanityChecker.checkExitBusOracleReport(newMaxRequests) + await assert.reverts( + oracleReportSanityChecker.checkExitBusOracleReport(newMaxRequests + 1), + `IncorrectNumberOfExitRequestsPerReport(${newMaxRequests})` + ) + }) + }) + + describe('extra data reporting', () => { + beforeEach(async () => { + await oracleReportSanityChecker.setOracleReportLimits(Object.values(defaultLimitsList), { + from: managersRoster.allLimitsManagers[0], + }) + }) + + it('set maxNodeOperatorsPerExtraDataItemCount', async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()) + .maxNodeOperatorsPerExtraDataItemCount + const newValue = 33 + assert.notEquals(newValue, previousValue) + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setMaxNodeOperatorsPerExtraDataItemCount(newValue, { + from: deployer, + }), + deployer, + 'MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE' + ) + const tx = await oracleReportSanityChecker.setMaxNodeOperatorsPerExtraDataItemCount(newValue, { + from: managersRoster.maxNodeOperatorsPerExtraDataItemCountManagers[0], + }) + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItemCount, + newValue + ) + assert.emits(tx, 'MaxNodeOperatorsPerExtraDataItemCountSet', { maxNodeOperatorsPerExtraDataItemCount: newValue }) + }) + + it('set maxAccountingExtraDataListItemsCount', async () => { + const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()) + .maxAccountingExtraDataListItemsCount + const newValue = 31 + assert.notEquals(newValue, previousValue) + await assert.revertsOZAccessControl( + oracleReportSanityChecker.setMaxAccountingExtraDataListItemsCount(newValue, { + from: deployer, + }), + deployer, + 'MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE' + ) + const tx = await oracleReportSanityChecker.setMaxAccountingExtraDataListItemsCount(newValue, { + from: managersRoster.maxAccountingExtraDataListItemsCountManagers[0], + }) + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount, + newValue + ) + assert.emits(tx, 'MaxAccountingExtraDataListItemsCountSet', { maxAccountingExtraDataListItemsCount: newValue }) + }) + + it('checkNodeOperatorsPerExtraDataItemCount', async () => { + const maxCount = (await oracleReportSanityChecker.getOracleReportLimits()).maxNodeOperatorsPerExtraDataItemCount + + await oracleReportSanityChecker.checkNodeOperatorsPerExtraDataItemCount(12, maxCount) + + await assert.reverts( + oracleReportSanityChecker.checkNodeOperatorsPerExtraDataItemCount(12, +maxCount + 1), + `TooManyNodeOpsPerExtraDataItem(12, ${+maxCount + 1})` + ) + }) + + it('checkAccountingExtraDataListItemsCount', async () => { + const maxCount = (await oracleReportSanityChecker.getOracleReportLimits()).maxAccountingExtraDataListItemsCount + + await oracleReportSanityChecker.checkAccountingExtraDataListItemsCount(maxCount) + + await assert.reverts( + oracleReportSanityChecker.checkAccountingExtraDataListItemsCount(maxCount + 1), + `MaxAccountingExtraDataItemsCountExceeded(${maxCount}, ${maxCount + 1})` + ) + }) + }) }) From e2fad5eacd1bd6778d651e66c9864f593b851595 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Tue, 21 Mar 2023 23:08:44 +0300 Subject: [PATCH 55/66] test: better branch coverage for sanity checker --- .../OracleReportSanityChecker.sol | 2 +- .../OracleReportSanityCheckerMocks.sol | 11 +++++++++- .../oracle-report-sanity-checker.test.js | 21 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 1329ad6eb..60fdb7be8 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -101,7 +101,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { using LimitsListUnpacker for LimitsListPacked; using PositiveTokenRebaseLimiter for TokenRebaseLimiterData; - bytes32 public constant ALL_LIMITS_MANAGER_ROLE = keccak256("LIMITS_MANAGER_ROLE"); + bytes32 public constant ALL_LIMITS_MANAGER_ROLE = keccak256("ALL_LIMITS_MANAGER_ROLE"); bytes32 public constant CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE = keccak256("CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE"); bytes32 public constant ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE = diff --git a/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol b/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol index 5556f3339..f2f5e4df7 100644 --- a/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol +++ b/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol @@ -38,10 +38,19 @@ contract WithdrawalQueueStub is IWithdrawalQueue { } contract BurnerStub { + uint256 private nonCover; + uint256 private cover; + function getSharesRequestedToBurn() external view returns ( uint256 coverShares, uint256 nonCoverShares ) { - return (0, 0); + coverShares = cover; + nonCoverShares = nonCover; + } + + function setSharesRequestedToBurn(uint256 _cover, uint256 _nonCover) external { + cover = _cover; + nonCover = _nonCover; } } diff --git a/test/0.8.9/oracle-report-sanity-checker.test.js b/test/0.8.9/oracle-report-sanity-checker.test.js index dd04aef3f..e4aacd945 100644 --- a/test/0.8.9/oracle-report-sanity-checker.test.js +++ b/test/0.8.9/oracle-report-sanity-checker.test.js @@ -186,6 +186,17 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa ) }) + it('reverts with error IncorrectSharesRequestedToBurn() when actual shares to burn is less than passed', async () => { + await burnerMock.setSharesRequestedToBurn(10, 21) + + await assert.reverts( + oracleReportSanityChecker.checkAccountingOracleReport( + ...Object.values({ ...correctLidoOracleReport, sharesRequestedToBurn: 32 }) + ), + `IncorrectSharesRequestedToBurn(31)` + ) + }) + it('reverts with error IncorrectCLBalanceDecrease() when one off CL balance decrease more than limit', async () => { const maxBasisPoints = 10_000n const preCLBalance = wei(100_000, 'eth') @@ -204,6 +215,16 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa ), `IncorrectCLBalanceDecrease(${oneOffCLBalanceDecreaseBP.toString()})` ) + + const postCLBalanceCorrect = wei(99_000, 'eth') + await oracleReportSanityChecker.checkAccountingOracleReport( + ...Object.values({ + ...correctLidoOracleReport, + preCLBalance: preCLBalance.toString(), + postCLBalance: postCLBalanceCorrect.toString(), + withdrawalVaultBalance: withdrawalVaultBalance.toString(), + }) + ) }) it('reverts with error IncorrectCLBalanceIncrease() when reported values overcome annual CL balance limit', async () => { From b1d57693d05ec2d988a9d0ef2529c43ab98c92d2 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 Mar 2023 00:16:57 +0300 Subject: [PATCH 56/66] test: more robust report-to-report transitions --- ...execution_layer_rewards_after_the_merge.test.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/scenario/execution_layer_rewards_after_the_merge.test.js b/test/scenario/execution_layer_rewards_after_the_merge.test.js index fcef6e3d8..77dd30b8d 100644 --- a/test/scenario/execution_layer_rewards_after_the_merge.test.js +++ b/test/scenario/execution_layer_rewards_after_the_merge.test.js @@ -15,7 +15,7 @@ const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const TOTAL_BASIS_POINTS = 10 ** 4 const CURATED_MODULE_ID = 1 const LIDO_INIT_BALANCE_ETH = 1 -const ONE_DAY = 1 * 24 * 60 * 60 +const ONE_DAY_WITH_MARGIN = 1 * 24 * 60 * 60 + 60 * 10 // one day and 10 minutes const ORACLE_REPORT_LIMITS_BOILERPLATE = { churnValidatorsPerDayLimit: 255, @@ -525,7 +525,7 @@ contract('Lido: merge acceptance', (addresses) => { assert.equals(oldTotalPooledEther, ETH(107.35), 'total pooled ether') // Reporting the same balance as it was before (64.35ETH => 64.35ETH) - await advanceChainTime(ONE_DAY) + await advanceChainTime(ONE_DAY_WITH_MARGIN) const { refSlot } = await consensus.getCurrentFrame() @@ -621,7 +621,7 @@ contract('Lido: merge acceptance', (addresses) => { assert.equals(oldTotalPooledEther, ETH(114.35), 'total pooled ether') // Reporting balance decrease (64.35ETH => 62.35ETH) - await advanceChainTime(ONE_DAY) + await advanceChainTime(ONE_DAY_WITH_MARGIN) const { refSlot } = await consensus.getCurrentFrame() @@ -709,7 +709,7 @@ contract('Lido: merge acceptance', (addresses) => { assert.equals(oldTotalPooledEther, ETH(117.35), 'total pooled ether') // Reporting balance decrease (62.35ETH => 59.35ETH) - await advanceChainTime(ONE_DAY) + await advanceChainTime(ONE_DAY_WITH_MARGIN) const { refSlot } = await consensus.getCurrentFrame() @@ -779,7 +779,7 @@ contract('Lido: merge acceptance', (addresses) => { assert.equals(oldTotalPooledEther, ETH(117.35), 'total pooled ether') // Reporting balance decrease (59.35ETH => 51.35ETH) - await advanceChainTime(ONE_DAY) + await advanceChainTime(ONE_DAY_WITH_MARGIN) const { refSlot } = await consensus.getCurrentFrame() @@ -870,7 +870,7 @@ contract('Lido: merge acceptance', (addresses) => { assert.equals(oldTotalPooledEther, ETH(111.35), 'total pooled ether') // Reporting balance increase (51.35ETH => 51.49ETH) - await advanceChainTime(ONE_DAY) + await advanceChainTime(ONE_DAY_WITH_MARGIN) const { refSlot } = await consensus.getCurrentFrame() @@ -977,7 +977,7 @@ contract('Lido: merge acceptance', (addresses) => { // Do multiple oracle reports to withdraw all ETH from execution layer rewards vault while (elRewardsVaultBalance > 0) { - await advanceChainTime(ONE_DAY) + await advanceChainTime(ONE_DAY_WITH_MARGIN) const currentELBalance = await web3.eth.getBalance(elRewardsVault.address) From b174a8c765eb0b74adf7ad17d9c70fa831e85cb9 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 Mar 2023 00:43:35 +0300 Subject: [PATCH 57/66] fix: review fixes --- .../0.8.9/lib/PositiveTokenRebaseLimiter.sol | 29 ++++++++------ .../OracleReportSanityChecker.sol | 10 ++--- .../PositiveTokenRebaseLimiterMock.sol | 8 ++-- lib/abi/OracleReportSanityChecker.json | 2 +- lib/abi/PositiveTokenRebaseLimiter.json | 2 +- .../positive-token-rebase-limiter.test.js | 38 +++++++++---------- 6 files changed, 47 insertions(+), 42 deletions(-) diff --git a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol index 6f5715da2..0a8ff84a3 100644 --- a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol +++ b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol @@ -25,8 +25,8 @@ import {Math256} from "../../common/lib/Math256.sol"; struct TokenRebaseLimiterData { uint256 preTotalPooledEther; // pre-rebase total pooled ether uint256 preTotalShares; // pre-rebase total shares - uint256 postTotalPooledEther; // accumulated post-rebase total pooled ether - uint256 rebaseLimit; // positive rebase limit (target value) + uint256 postTotalPooledEther; // accumulated total pooled ether when token rebase components applied + uint256 rebaseLimit; // positive rebase limit (target value) with 1e9 precision (`LIMITER_PRECISION_BASE`) } /** @@ -115,22 +115,29 @@ library PositiveTokenRebaseLimiter { } /** - * @notice raise limit using the given amount of ether + * @notice decrease total pooled ether by the given amount of ether * @param _limiterState limit repr struct + * @param _etherAmount amount of ether to decrease */ - function raiseLimit(TokenRebaseLimiterData memory _limiterState, uint256 _etherAmount) internal pure { + function decreaseEther( + TokenRebaseLimiterData memory _limiterState, uint256 _etherAmount + ) internal pure { if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return; + if (_etherAmount > _limiterState.postTotalPooledEther) revert NegativeTotalPooledEther(); + _limiterState.postTotalPooledEther -= _etherAmount; } /** - * @dev append ether and return the consumed value not exceeding the limit + * @dev increase total pooled ether up to the limit and return the consumed value (not exceeding the limit) * @param _limiterState limit repr struct * @param _etherAmount desired ether addition - * @return consumedEther allowed to add ether to not exceed the limit + * @return consumedEther appended ether still not exceeding the limit */ - function consumeLimit(TokenRebaseLimiterData memory _limiterState, uint256 _etherAmount) + function increaseEther( + TokenRebaseLimiterData memory _limiterState, uint256 _etherAmount + ) internal pure returns (uint256 consumedEther) @@ -140,13 +147,10 @@ library PositiveTokenRebaseLimiter { uint256 prevPooledEther = _limiterState.postTotalPooledEther; _limiterState.postTotalPooledEther += _etherAmount; - uint256 rebaseEtherLimit = + uint256 maxTotalPooledEther = _limiterState.preTotalPooledEther + (_limiterState.rebaseLimit * _limiterState.preTotalPooledEther) / LIMITER_PRECISION_BASE; - _limiterState.postTotalPooledEther = Math256.min( - _limiterState.postTotalPooledEther, - _limiterState.preTotalPooledEther + rebaseEtherLimit - ); + _limiterState.postTotalPooledEther = Math256.min(_limiterState.postTotalPooledEther, maxTotalPooledEther); assert(_limiterState.postTotalPooledEther >= prevPooledEther); @@ -176,4 +180,5 @@ library PositiveTokenRebaseLimiter { error TooLowTokenRebaseLimit(); error TooHighTokenRebaseLimit(); + error NegativeTotalPooledEther(); } diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 60fdb7be8..ebd7b14f1 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -368,16 +368,16 @@ contract OracleReportSanityChecker is AccessControlEnumerable { ); if (_postCLBalance < _preCLBalance) { - tokenRebaseLimiter.raiseLimit(_preCLBalance - _postCLBalance); + tokenRebaseLimiter.decreaseEther(_preCLBalance - _postCLBalance); } else { - tokenRebaseLimiter.consumeLimit(_postCLBalance - _preCLBalance); + tokenRebaseLimiter.increaseEther(_postCLBalance - _preCLBalance); } - withdrawals = tokenRebaseLimiter.consumeLimit(_withdrawalVaultBalance); - elRewards = tokenRebaseLimiter.consumeLimit(_elRewardsVaultBalance); + withdrawals = tokenRebaseLimiter.increaseEther(_withdrawalVaultBalance); + elRewards = tokenRebaseLimiter.increaseEther(_elRewardsVaultBalance); simulatedSharesToBurn = Math256.min(tokenRebaseLimiter.getSharesToBurnLimit(), _sharesRequestedToBurn); - tokenRebaseLimiter.raiseLimit(_etherToLockForWithdrawals); + tokenRebaseLimiter.decreaseEther(_etherToLockForWithdrawals); sharesToBurn = Math256.min(tokenRebaseLimiter.getSharesToBurnLimit(), _newSharesToBurnForWithdrawals + _sharesRequestedToBurn); } diff --git a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol index 1cc28af58..5aedd826e 100644 --- a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol +++ b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol @@ -38,15 +38,15 @@ contract PositiveTokenRebaseLimiterMock { return limiter.isLimitReached(); } - function raiseLimit(uint256 _etherAmount) external { + function decreaseEther(uint256 _etherAmount) external { TokenRebaseLimiterData memory limiterMemory = limiter; - limiterMemory.raiseLimit(_etherAmount); + limiterMemory.decreaseEther(_etherAmount); limiter = limiterMemory; } - function consumeLimit(uint256 _etherAmount) external returns (uint256 consumedEther) { + function increaseEther(uint256 _etherAmount) external returns (uint256 consumedEther) { TokenRebaseLimiterData memory limiterMemory = limiter; - consumedEther = limiterMemory.consumeLimit(_etherAmount); + consumedEther = limiterMemory.increaseEther(_etherAmount); limiter = limiterMemory; } diff --git a/lib/abi/OracleReportSanityChecker.json b/lib/abi/OracleReportSanityChecker.json index e92dbaba8..c58226d77 100644 --- a/lib/abi/OracleReportSanityChecker.json +++ b/lib/abi/OracleReportSanityChecker.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_lidoLocator","type":"address"},{"internalType":"address","name":"_admin","type":"address"},{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"_limitsList","type":"tuple"},{"components":[{"internalType":"address[]","name":"allLimitsManagers","type":"address[]"},{"internalType":"address[]","name":"churnValidatorsPerDayLimitManagers","type":"address[]"},{"internalType":"address[]","name":"oneOffCLBalanceDecreaseLimitManagers","type":"address[]"},{"internalType":"address[]","name":"annualBalanceIncreaseLimitManagers","type":"address[]"},{"internalType":"address[]","name":"shareRateDeviationLimitManagers","type":"address[]"},{"internalType":"address[]","name":"maxValidatorExitRequestsPerReportManagers","type":"address[]"},{"internalType":"address[]","name":"maxAccountingExtraDataListItemsCountManagers","type":"address[]"},{"internalType":"address[]","name":"maxNodeOperatorsPerExtraDataItemCountManagers","type":"address[]"},{"internalType":"address[]","name":"requestTimestampMarginManagers","type":"address[]"},{"internalType":"address[]","name":"maxPositiveTokenRebaseManagers","type":"address[]"}],"internalType":"struct OracleReportSanityChecker.ManagersRoster","name":"_managersRoster","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ActualShareRateIsZero","type":"error"},{"inputs":[{"internalType":"uint256","name":"limitPerDay","type":"uint256"},{"internalType":"uint256","name":"exitedPerDay","type":"uint256"}],"name":"ExitedValidatorsLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"churnLimit","type":"uint256"}],"name":"IncorrectAppearedValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBP","type":"uint256"}],"name":"IncorrectCLBalanceDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"annualBalanceDiff","type":"uint256"}],"name":"IncorrectCLBalanceIncrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualELRewardsVaultBalance","type":"uint256"}],"name":"IncorrectELRewardsVaultBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"churnLimit","type":"uint256"}],"name":"IncorrectExitedValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"maxAllowedValue","type":"uint256"}],"name":"IncorrectLimitValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxRequestsCount","type":"uint256"}],"name":"IncorrectNumberOfExitRequestsPerReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestCreationBlock","type":"uint256"}],"name":"IncorrectRequestFinalization","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualSharesToBurn","type":"uint256"}],"name":"IncorrectSharesRequestedToBurn","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualWithdrawalVaultBalance","type":"uint256"}],"name":"IncorrectWithdrawalsVaultBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxItemsCount","type":"uint256"},{"internalType":"uint256","name":"receivedItemsCount","type":"uint256"}],"name":"MaxAccountingExtraDataItemsCountExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"uint256","name":"actualShareRate","type":"uint256"}],"name":"TooHighSimulatedShareRate","type":"error"},{"inputs":[],"name":"TooHighTokenRebaseLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"uint256","name":"actualShareRate","type":"uint256"}],"name":"TooLowSimulatedShareRate","type":"error"},{"inputs":[],"name":"TooLowTokenRebaseLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"nodeOpsCount","type":"uint256"}],"name":"TooManyNodeOpsPerExtraDataItem","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"}],"name":"AnnualBalanceIncreaseBPLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"}],"name":"ChurnValidatorsPerDayLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"}],"name":"MaxAccountingExtraDataListItemsCountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"}],"name":"MaxNodeOperatorsPerExtraDataItemCountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"name":"MaxPositiveTokenRebaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"}],"name":"MaxValidatorExitRequestsPerReportSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"}],"name":"OneOffCLBalanceDecreaseBPLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"}],"name":"RequestTimestampMarginSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"}],"name":"SimulatedShareRateDeviationBPLimitSet","type":"event"},{"inputs":[],"name":"ALL_LIMITS_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_extraDataListItemsCount","type":"uint256"}],"name":"checkAccountingExtraDataListItemsCount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timeElapsed","type":"uint256"},{"internalType":"uint256","name":"_preCLBalance","type":"uint256"},{"internalType":"uint256","name":"_postCLBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256","name":"_preCLValidators","type":"uint256"},{"internalType":"uint256","name":"_postCLValidators","type":"uint256"}],"name":"checkAccountingOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_exitRequestsCount","type":"uint256"}],"name":"checkExitBusOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_exitedValidatorsCount","type":"uint256"}],"name":"checkExitedValidatorsRatePerDay","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_itemIndex","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorsCount","type":"uint256"}],"name":"checkNodeOperatorsPerExtraDataItemCount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_postTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_postTotalShares","type":"uint256"},{"internalType":"uint256","name":"_etherLockedOnWithdrawalQueue","type":"uint256"},{"internalType":"uint256","name":"_sharesBurntDueToWithdrawals","type":"uint256"},{"internalType":"uint256","name":"_simulatedShareRate","type":"uint256"}],"name":"checkSimulatedShareRate","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lastFinalizableRequestId","type":"uint256"},{"internalType":"uint256","name":"_reportTimestamp","type":"uint256"}],"name":"checkWithdrawalQueueOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLidoLocator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxPositiveTokenRebase","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOracleReportLimits","outputs":[{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_annualBalanceIncreaseBPLimit","type":"uint256"}],"name":"setAnnualBalanceIncreaseBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_churnValidatorsPerDayLimit","type":"uint256"}],"name":"setChurnValidatorsPerDayLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxAccountingExtraDataListItemsCount","type":"uint256"}],"name":"setMaxAccountingExtraDataListItemsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxValidatorExitRequestsPerReport","type":"uint256"}],"name":"setMaxExitRequestsPerOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxNodeOperatorsPerExtraDataItemCount","type":"uint256"}],"name":"setMaxNodeOperatorsPerExtraDataItemCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxPositiveTokenRebase","type":"uint256"}],"name":"setMaxPositiveTokenRebase","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_oneOffCLBalanceDecreaseBPLimit","type":"uint256"}],"name":"setOneOffCLBalanceDecreaseBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"_limitsList","type":"tuple"}],"name":"setOracleReportLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestTimestampMargin","type":"uint256"}],"name":"setRequestTimestampMargin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_simulatedShareRateDeviationBPLimit","type":"uint256"}],"name":"setSimulatedShareRateDeviationBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_preTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_preTotalShares","type":"uint256"},{"internalType":"uint256","name":"_preCLBalance","type":"uint256"},{"internalType":"uint256","name":"_postCLBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256","name":"_etherToLockForWithdrawals","type":"uint256"},{"internalType":"uint256","name":"_newSharesToBurnForWithdrawals","type":"uint256"}],"name":"smoothenTokenRebase","outputs":[{"internalType":"uint256","name":"withdrawals","type":"uint256"},{"internalType":"uint256","name":"elRewards","type":"uint256"},{"internalType":"uint256","name":"simulatedSharesToBurn","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","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 +[{"inputs":[{"internalType":"address","name":"_lidoLocator","type":"address"},{"internalType":"address","name":"_admin","type":"address"},{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"_limitsList","type":"tuple"},{"components":[{"internalType":"address[]","name":"allLimitsManagers","type":"address[]"},{"internalType":"address[]","name":"churnValidatorsPerDayLimitManagers","type":"address[]"},{"internalType":"address[]","name":"oneOffCLBalanceDecreaseLimitManagers","type":"address[]"},{"internalType":"address[]","name":"annualBalanceIncreaseLimitManagers","type":"address[]"},{"internalType":"address[]","name":"shareRateDeviationLimitManagers","type":"address[]"},{"internalType":"address[]","name":"maxValidatorExitRequestsPerReportManagers","type":"address[]"},{"internalType":"address[]","name":"maxAccountingExtraDataListItemsCountManagers","type":"address[]"},{"internalType":"address[]","name":"maxNodeOperatorsPerExtraDataItemCountManagers","type":"address[]"},{"internalType":"address[]","name":"requestTimestampMarginManagers","type":"address[]"},{"internalType":"address[]","name":"maxPositiveTokenRebaseManagers","type":"address[]"}],"internalType":"struct OracleReportSanityChecker.ManagersRoster","name":"_managersRoster","type":"tuple"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ActualShareRateIsZero","type":"error"},{"inputs":[{"internalType":"uint256","name":"limitPerDay","type":"uint256"},{"internalType":"uint256","name":"exitedPerDay","type":"uint256"}],"name":"ExitedValidatorsLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"churnLimit","type":"uint256"}],"name":"IncorrectAppearedValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBP","type":"uint256"}],"name":"IncorrectCLBalanceDecrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"annualBalanceDiff","type":"uint256"}],"name":"IncorrectCLBalanceIncrease","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualELRewardsVaultBalance","type":"uint256"}],"name":"IncorrectELRewardsVaultBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"churnLimit","type":"uint256"}],"name":"IncorrectExitedValidators","type":"error"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"maxAllowedValue","type":"uint256"}],"name":"IncorrectLimitValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxRequestsCount","type":"uint256"}],"name":"IncorrectNumberOfExitRequestsPerReport","type":"error"},{"inputs":[{"internalType":"uint256","name":"requestCreationBlock","type":"uint256"}],"name":"IncorrectRequestFinalization","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualSharesToBurn","type":"uint256"}],"name":"IncorrectSharesRequestedToBurn","type":"error"},{"inputs":[{"internalType":"uint256","name":"actualWithdrawalVaultBalance","type":"uint256"}],"name":"IncorrectWithdrawalsVaultBalance","type":"error"},{"inputs":[{"internalType":"uint256","name":"maxItemsCount","type":"uint256"},{"internalType":"uint256","name":"receivedItemsCount","type":"uint256"}],"name":"MaxAccountingExtraDataItemsCountExceeded","type":"error"},{"inputs":[],"name":"NegativeTotalPooledEther","type":"error"},{"inputs":[{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"uint256","name":"actualShareRate","type":"uint256"}],"name":"TooHighSimulatedShareRate","type":"error"},{"inputs":[],"name":"TooHighTokenRebaseLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"simulatedShareRate","type":"uint256"},{"internalType":"uint256","name":"actualShareRate","type":"uint256"}],"name":"TooLowSimulatedShareRate","type":"error"},{"inputs":[],"name":"TooLowTokenRebaseLimit","type":"error"},{"inputs":[{"internalType":"uint256","name":"itemIndex","type":"uint256"},{"internalType":"uint256","name":"nodeOpsCount","type":"uint256"}],"name":"TooManyNodeOpsPerExtraDataItem","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"}],"name":"AnnualBalanceIncreaseBPLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"}],"name":"ChurnValidatorsPerDayLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"}],"name":"MaxAccountingExtraDataListItemsCountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"}],"name":"MaxNodeOperatorsPerExtraDataItemCountSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"name":"MaxPositiveTokenRebaseSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"}],"name":"MaxValidatorExitRequestsPerReportSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"}],"name":"OneOffCLBalanceDecreaseBPLimitSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"}],"name":"RequestTimestampMarginSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"}],"name":"SimulatedShareRateDeviationBPLimitSet","type":"event"},{"inputs":[],"name":"ALL_LIMITS_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ANNUAL_BALANCE_INCREASE_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CHURN_VALIDATORS_PER_DAY_LIMIT_MANGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_ACCOUNTING_EXTRA_DATA_LIST_ITEMS_COUNT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_NODE_OPERATORS_PER_EXTRA_DATA_ITEM_COUNT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_POSITIVE_TOKEN_REBASE_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_VALIDATOR_EXIT_REQUESTS_PER_REPORT_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ONE_OFF_CL_BALANCE_DECREASE_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REQUEST_TIMESTAMP_MARGIN_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SHARE_RATE_DEVIATION_LIMIT_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_extraDataListItemsCount","type":"uint256"}],"name":"checkAccountingExtraDataListItemsCount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timeElapsed","type":"uint256"},{"internalType":"uint256","name":"_preCLBalance","type":"uint256"},{"internalType":"uint256","name":"_postCLBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256","name":"_preCLValidators","type":"uint256"},{"internalType":"uint256","name":"_postCLValidators","type":"uint256"}],"name":"checkAccountingOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_exitRequestsCount","type":"uint256"}],"name":"checkExitBusOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_exitedValidatorsCount","type":"uint256"}],"name":"checkExitedValidatorsRatePerDay","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_itemIndex","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorsCount","type":"uint256"}],"name":"checkNodeOperatorsPerExtraDataItemCount","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_postTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_postTotalShares","type":"uint256"},{"internalType":"uint256","name":"_etherLockedOnWithdrawalQueue","type":"uint256"},{"internalType":"uint256","name":"_sharesBurntDueToWithdrawals","type":"uint256"},{"internalType":"uint256","name":"_simulatedShareRate","type":"uint256"}],"name":"checkSimulatedShareRate","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_lastFinalizableRequestId","type":"uint256"},{"internalType":"uint256","name":"_reportTimestamp","type":"uint256"}],"name":"checkWithdrawalQueueOracleReport","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLidoLocator","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getMaxPositiveTokenRebase","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getOracleReportLimits","outputs":[{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_annualBalanceIncreaseBPLimit","type":"uint256"}],"name":"setAnnualBalanceIncreaseBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_churnValidatorsPerDayLimit","type":"uint256"}],"name":"setChurnValidatorsPerDayLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxAccountingExtraDataListItemsCount","type":"uint256"}],"name":"setMaxAccountingExtraDataListItemsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxValidatorExitRequestsPerReport","type":"uint256"}],"name":"setMaxExitRequestsPerOracleReport","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxNodeOperatorsPerExtraDataItemCount","type":"uint256"}],"name":"setMaxNodeOperatorsPerExtraDataItemCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxPositiveTokenRebase","type":"uint256"}],"name":"setMaxPositiveTokenRebase","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_oneOffCLBalanceDecreaseBPLimit","type":"uint256"}],"name":"setOneOffCLBalanceDecreaseBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"churnValidatorsPerDayLimit","type":"uint256"},{"internalType":"uint256","name":"oneOffCLBalanceDecreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"annualBalanceIncreaseBPLimit","type":"uint256"},{"internalType":"uint256","name":"simulatedShareRateDeviationBPLimit","type":"uint256"},{"internalType":"uint256","name":"maxValidatorExitRequestsPerReport","type":"uint256"},{"internalType":"uint256","name":"maxAccountingExtraDataListItemsCount","type":"uint256"},{"internalType":"uint256","name":"maxNodeOperatorsPerExtraDataItemCount","type":"uint256"},{"internalType":"uint256","name":"requestTimestampMargin","type":"uint256"},{"internalType":"uint256","name":"maxPositiveTokenRebase","type":"uint256"}],"internalType":"struct LimitsList","name":"_limitsList","type":"tuple"}],"name":"setOracleReportLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_requestTimestampMargin","type":"uint256"}],"name":"setRequestTimestampMargin","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_simulatedShareRateDeviationBPLimit","type":"uint256"}],"name":"setSimulatedShareRateDeviationBPLimit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_preTotalPooledEther","type":"uint256"},{"internalType":"uint256","name":"_preTotalShares","type":"uint256"},{"internalType":"uint256","name":"_preCLBalance","type":"uint256"},{"internalType":"uint256","name":"_postCLBalance","type":"uint256"},{"internalType":"uint256","name":"_withdrawalVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_elRewardsVaultBalance","type":"uint256"},{"internalType":"uint256","name":"_sharesRequestedToBurn","type":"uint256"},{"internalType":"uint256","name":"_etherToLockForWithdrawals","type":"uint256"},{"internalType":"uint256","name":"_newSharesToBurnForWithdrawals","type":"uint256"}],"name":"smoothenTokenRebase","outputs":[{"internalType":"uint256","name":"withdrawals","type":"uint256"},{"internalType":"uint256","name":"elRewards","type":"uint256"},{"internalType":"uint256","name":"simulatedSharesToBurn","type":"uint256"},{"internalType":"uint256","name":"sharesToBurn","type":"uint256"}],"stateMutability":"view","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/PositiveTokenRebaseLimiter.json b/lib/abi/PositiveTokenRebaseLimiter.json index 63f6b33b3..f3c22b8f6 100644 --- a/lib/abi/PositiveTokenRebaseLimiter.json +++ b/lib/abi/PositiveTokenRebaseLimiter.json @@ -1 +1 @@ -[{"inputs":[],"name":"TooHighTokenRebaseLimit","type":"error"},{"inputs":[],"name":"TooLowTokenRebaseLimit","type":"error"},{"inputs":[],"name":"LIMITER_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNLIMITED_REBASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file +[{"inputs":[],"name":"NegativeTotalPooledEther","type":"error"},{"inputs":[],"name":"TooHighTokenRebaseLimit","type":"error"},{"inputs":[],"name":"TooLowTokenRebaseLimit","type":"error"},{"inputs":[],"name":"LIMITER_PRECISION_BASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNLIMITED_REBASE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] \ No newline at end of file diff --git a/test/0.8.9/positive-token-rebase-limiter.test.js b/test/0.8.9/positive-token-rebase-limiter.test.js index ebdbe1dde..a22e84813 100644 --- a/test/0.8.9/positive-token-rebase-limiter.test.js +++ b/test/0.8.9/positive-token-rebase-limiter.test.js @@ -18,7 +18,7 @@ contract('PositiveTokenRebaseLimiter', () => { snapshot = new EvmSnapshot(ethers.provider) await snapshot.make() - addSendWithResult(limiter.consumeLimit) + addSendWithResult(limiter.increaseEther) }) afterEach(async () => { @@ -68,7 +68,7 @@ contract('PositiveTokenRebaseLimiter', () => { const preTotalShares = ETH(75) await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) - await limiter.raiseLimit(ETH(0)) + await limiter.decreaseEther(ETH(0)) const limiterValues0 = await limiter.getLimiterValues() assert.equals(limiterValues0.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues0.preTotalShares, preTotalShares) @@ -76,7 +76,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues0.rebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) - await limiter.raiseLimit(ETH(1)) + await limiter.decreaseEther(ETH(1)) const limiterValuesNeg = await limiter.getLimiterValues() assert.equals(limiterValuesNeg.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValuesNeg.preTotalShares, preTotalShares) @@ -91,7 +91,7 @@ contract('PositiveTokenRebaseLimiter', () => { const preTotalShares = ETH(750) await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) - await limiter.consumeLimit(ETH(1)) + await limiter.increaseEther(ETH(1)) assert.isFalse(await limiter.isLimitReached()) const limiterValues = await limiter.getLimiterValues() @@ -101,13 +101,13 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.rebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) - assert.equals(await limiter.consumeLimit.sendWithResult(ETH(2)), ETH(2)) + assert.equals(await limiter.increaseEther.sendWithResult(ETH(2)), ETH(2)) assert.isFalse(await limiter.isLimitReached()) - assert.equals(await limiter.consumeLimit.sendWithResult(ETH(4)), ETH(4)) + assert.equals(await limiter.increaseEther.sendWithResult(ETH(4)), ETH(4)) assert.isFalse(await limiter.isLimitReached()) - assert.equals(await limiter.consumeLimit.sendWithResult(ETH(1)), ETH(0.5)) + assert.equals(await limiter.increaseEther.sendWithResult(ETH(1)), ETH(0.5)) assert.isTrue(await limiter.isLimitReached()) assert.equals(await limiter.getSharesToBurnLimit(), 0) }) @@ -118,10 +118,10 @@ contract('PositiveTokenRebaseLimiter', () => { const preTotalShares = ETH(1000000) await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) - await limiter.raiseLimit(ETH(1)) + await limiter.decreaseEther(ETH(1)) assert.isFalse(await limiter.isLimitReached()) - assert.equals(await limiter.consumeLimit.sendWithResult(ETH(2)), ETH(2)) + assert.equals(await limiter.increaseEther.sendWithResult(ETH(2)), ETH(2)) assert.isFalse(await limiter.isLimitReached()) const limiterValues = await limiter.getLimiterValues() @@ -148,10 +148,10 @@ contract('PositiveTokenRebaseLimiter', () => { const preTotalShares = ETH(1000000) await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) - await limiter.raiseLimit(ETH(1)) + await limiter.decreaseEther(ETH(1)) assert.isFalse(await limiter.isLimitReached()) - assert.equals(await limiter.consumeLimit.sendWithResult(ETH(2)), ETH(2)) + assert.equals(await limiter.increaseEther.sendWithResult(ETH(2)), ETH(2)) assert.isFalse(await limiter.isLimitReached()) let limiterValues = await limiter.getLimiterValues() @@ -161,7 +161,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) assert.equals(limiterValues.rebaseLimit, rebaseLimit) - await limiter.raiseLimit(ETH(1)) + await limiter.decreaseEther(ETH(1)) assert.isFalse(await limiter.isLimitReached()) limiterValues = await limiter.getLimiterValues() @@ -181,17 +181,17 @@ contract('PositiveTokenRebaseLimiter', () => { await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) assert.equals(await limiter.getSharesToBurnLimit(), 0) - await limiter.raiseLimit(ETH(0)) + await limiter.decreaseEther(ETH(0)) assert.isFalse(await limiter.isLimitReached()) - await limiter.consumeLimit(ETH(0)) + await limiter.increaseEther(ETH(0)) assert.isFalse(await limiter.isLimitReached()) - await limiter.raiseLimit(ETH(1)) + await limiter.decreaseEther(ETH(1)) assert.isFalse(await limiter.isLimitReached()) - await limiter.consumeLimit(ETH(1)) + await limiter.increaseEther(ETH(1)) assert.isFalse(await limiter.isLimitReached()) - assert.equals(await limiter.consumeLimit.sendWithResult(ETH(2)), ETH(2)) + assert.equals(await limiter.increaseEther.sendWithResult(ETH(2)), ETH(2)) assert.isFalse(await limiter.isLimitReached()) const maxSharesToBurn = await limiter.getSharesToBurnLimit() @@ -204,8 +204,8 @@ contract('PositiveTokenRebaseLimiter', () => { const preTotalShares = ETH('1000000') await limiter.initLimiterState(rebaseLimit, preTotalPooledEther, preTotalShares) - await limiter.consumeLimit(ETH(1000)) - await limiter.raiseLimit(ETH(40000)) // withdrawal fulfillment + await limiter.increaseEther(ETH(1000)) + await limiter.decreaseEther(ETH(40000)) // withdrawal fulfillment assert.isFalse(await limiter.isLimitReached()) const limiterValues = await limiter.getLimiterValues() From 25a53c093f530e6315a87d1b5c95911ee6d081fe Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 Mar 2023 00:50:24 +0300 Subject: [PATCH 58/66] chore: typos --- contracts/0.4.24/nos/NodeOperatorsRegistry.sol | 8 ++++---- contracts/0.8.9/oracle/HashConsensus.sol | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol index 82153ac11..d6b5323ca 100644 --- a/contracts/0.4.24/nos/NodeOperatorsRegistry.sol +++ b/contracts/0.4.24/nos/NodeOperatorsRegistry.sol @@ -109,11 +109,11 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { /// @dev refunded keys count from dao uint8 internal constant REFUNDED_VALIDATORS_COUNT_OFFSET = 1; /// @dev extra penalty time after stuck keys resolved (refunded and/or exited) - /// @notice field is also used as flag for "half-cleaned" panlty status + /// @notice field is also used as flag for "half-cleaned" penalty status /// Operator is PENALIZED if `STUCK_VALIDATORS_COUNT > REFUNDED_VALIDATORS_COUNT` or - /// `STUCK_VALIDATORS_COUNT <= REFUNDED_VALIDATORS_COUNT && STUCK_PENALTY_END_TIMESTAMP <= refund timastamp + STUCK_PENALTY_DELAY` + /// `STUCK_VALIDATORS_COUNT <= REFUNDED_VALIDATORS_COUNT && STUCK_PENALTY_END_TIMESTAMP <= refund timestamp + STUCK_PENALTY_DELAY` /// When operator refund all stuck validators and time has pass STUCK_PENALTY_DELAY, but STUCK_PENALTY_END_TIMESTAMP not zeroed, - /// then Operator can receive reawards but can't get new deposits until the new Oracle report or `clearNodeOperatorPenalty` is called. + /// then Operator can receive rewards but can't get new deposits until the new Oracle report or `clearNodeOperatorPenalty` is called. uint8 internal constant STUCK_PENALTY_END_TIMESTAMP_OFFSET = 2; // Summary SigningKeysStats @@ -661,7 +661,7 @@ contract NodeOperatorsRegistry is AragonApp, Versioned { _updateSummaryMaxValidatorsCount(_nodeOperatorId); } - // @dev Recalculate and update the max validoator count for operator and summary stats + // @dev Recalculate and update the max validator count for operator and summary stats function _updateSummaryMaxValidatorsCount(uint256 _nodeOperatorId) internal returns (int64 maxSigningKeysDelta) { maxSigningKeysDelta = _applyNodeOperatorLimits(_nodeOperatorId); if (maxSigningKeysDelta != 0) { diff --git a/contracts/0.8.9/oracle/HashConsensus.sol b/contracts/0.8.9/oracle/HashConsensus.sol index b8a949d85..4df31ae02 100644 --- a/contracts/0.8.9/oracle/HashConsensus.sol +++ b/contracts/0.8.9/oracle/HashConsensus.sol @@ -339,7 +339,7 @@ contract HashConsensus is AccessControlEnumerable { } /// @notice Sets the duration of the interval starting at the beginning of the frame during - /// which only the selected "fast lane" subset of oracle committee memebrs can (and expected + /// which only the selected "fast lane" subset of oracle committee members can (and expected /// to) submit a report. /// /// The fast lane subset is a subset consisting of `quorum` oracles that changes on each frame. From bb943e9249b4429b85802825b9e3f67db293c609 Mon Sep 17 00:00:00 2001 From: Evgeny Taktarov Date: Wed, 22 Mar 2023 14:53:24 +0700 Subject: [PATCH 59/66] fix: add gasPrice comment --- test/0.8.9/withdrawal-queue-gas.test.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/0.8.9/withdrawal-queue-gas.test.js b/test/0.8.9/withdrawal-queue-gas.test.js index 93596d454..9700e153d 100644 --- a/test/0.8.9/withdrawal-queue-gas.test.js +++ b/test/0.8.9/withdrawal-queue-gas.test.js @@ -75,6 +75,8 @@ contract('WithdrawalQueue', ([owner, user]) => { user, { from: user, + // smap of large transactions causes local network baseFee rise in next blocks + // increase gasPrice a bit on every tx to make sure execute gasPrice: gasPrice++, gasLimit: 1000000000, }, From 5d018f53fac35dab5953dd8bde8a6dec9c398c6e Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 Mar 2023 11:49:57 +0300 Subject: [PATCH 60/66] test: full-blown unit test coverage for sanity checker --- .../OracleReportSanityChecker.sol | 10 +- .../oracle-report-sanity-checker.test.js | 147 ++++++++++++++++++ 2 files changed, 152 insertions(+), 5 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index ebd7b14f1..96ae4ba2d 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -564,14 +564,14 @@ contract OracleReportSanityChecker is AccessControlEnumerable { uint256 _postCLBalance, uint256 _timeElapsed ) internal pure { - if (_preCLBalance >= _postCLBalance) return; - // allow zero values for scratch deploy // NB: annual increase have to be large enough for scratch deploy if (_preCLBalance == 0) { _preCLBalance = DEFAULT_CL_BALANCE; } + if (_preCLBalance >= _postCLBalance) return; + if (_timeElapsed == 0) { _timeElapsed = DEFAULT_TIME_ELAPSED; } @@ -660,15 +660,15 @@ contract OracleReportSanityChecker is AccessControlEnumerable { emit ChurnValidatorsPerDayLimitSet(_newLimitsList.churnValidatorsPerDayLimit); } if (_oldLimitsList.oneOffCLBalanceDecreaseBPLimit != _newLimitsList.oneOffCLBalanceDecreaseBPLimit) { - _checkLimitValue(_newLimitsList.oneOffCLBalanceDecreaseBPLimit, type(uint16).max); + _checkLimitValue(_newLimitsList.oneOffCLBalanceDecreaseBPLimit, MAX_BASIS_POINTS); emit OneOffCLBalanceDecreaseBPLimitSet(_newLimitsList.oneOffCLBalanceDecreaseBPLimit); } if (_oldLimitsList.annualBalanceIncreaseBPLimit != _newLimitsList.annualBalanceIncreaseBPLimit) { - _checkLimitValue(_newLimitsList.annualBalanceIncreaseBPLimit, type(uint16).max); + _checkLimitValue(_newLimitsList.annualBalanceIncreaseBPLimit, MAX_BASIS_POINTS); emit AnnualBalanceIncreaseBPLimitSet(_newLimitsList.annualBalanceIncreaseBPLimit); } if (_oldLimitsList.simulatedShareRateDeviationBPLimit != _newLimitsList.simulatedShareRateDeviationBPLimit) { - _checkLimitValue(_newLimitsList.simulatedShareRateDeviationBPLimit, type(uint16).max); + _checkLimitValue(_newLimitsList.simulatedShareRateDeviationBPLimit, MAX_BASIS_POINTS); emit SimulatedShareRateDeviationBPLimitSet(_newLimitsList.simulatedShareRateDeviationBPLimit); } if (_oldLimitsList.maxValidatorExitRequestsPerReport != _newLimitsList.maxValidatorExitRequestsPerReport) { diff --git a/test/0.8.9/oracle-report-sanity-checker.test.js b/test/0.8.9/oracle-report-sanity-checker.test.js index e4aacd945..efe1bea14 100644 --- a/test/0.8.9/oracle-report-sanity-checker.test.js +++ b/test/0.8.9/oracle-report-sanity-checker.test.js @@ -286,6 +286,46 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa assert.emits(tx, 'AnnualBalanceIncreaseBPLimitSet', { annualBalanceIncreaseBPLimit: newValue }) }) + it('handles zero time passed for annual balance increase', async () => { + const preCLBalance = BigInt(correctLidoOracleReport.preCLBalance) + const postCLBalance = preCLBalance + 1000n + + await oracleReportSanityChecker.checkAccountingOracleReport( + ...Object.values({ + ...correctLidoOracleReport, + postCLBalance: postCLBalance.toString(), + timeElapsed: 0, + }) + ) + }) + + it('handles zero pre CL balance estimating balance increase', async () => { + const preCLBalance = BigInt(0) + const postCLBalance = preCLBalance + 1000n + + await oracleReportSanityChecker.checkAccountingOracleReport( + ...Object.values({ + ...correctLidoOracleReport, + preCLBalance: preCLBalance.toString(), + postCLBalance: postCLBalance.toString(), + }) + ) + }) + + it('handles zero time passed for appeared validators', async () => { + const preCLValidators = BigInt(correctLidoOracleReport.preCLValidators) + const postCLValidators = preCLValidators + 2n + + await oracleReportSanityChecker.checkAccountingOracleReport( + ...Object.values({ + ...correctLidoOracleReport, + preCLValidators: preCLValidators.toString(), + postCLValidators: postCLValidators.toString(), + timeElapsed: 0, + }) + ) + }) + it('set simulated share rate deviation', async () => { const previousValue = (await oracleReportSanityChecker.getOracleReportLimits()).simulatedShareRateDeviationBPLimit const newValue = 7 @@ -1076,4 +1116,111 @@ contract('OracleReportSanityChecker', ([deployer, admin, withdrawalVault, elRewa ) }) }) + + describe('check limit boundaries', () => { + it('values must be less or equal to MAX_BASIS_POINTS', async () => { + const MAX_BASIS_POINTS = 10000 + const INVALID_BASIS_POINTS = MAX_BASIS_POINTS + 1 + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, oneOffCLBalanceDecreaseBPLimit: INVALID_BASIS_POINTS }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_BASIS_POINTS}, ${MAX_BASIS_POINTS})` + ) + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, annualBalanceIncreaseBPLimit: 10001 }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_BASIS_POINTS}, ${MAX_BASIS_POINTS})` + ) + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, simulatedShareRateDeviationBPLimit: 10001 }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_BASIS_POINTS}, ${MAX_BASIS_POINTS})` + ) + }) + + it('values must be less or equal to type(uint16).max', async () => { + const MAX_UINT_16 = 65535 + const INVALID_VALUE = MAX_UINT_16 + 1 + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, churnValidatorsPerDayLimit: INVALID_VALUE }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_VALUE}, ${MAX_UINT_16})` + ) + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, maxValidatorExitRequestsPerReport: INVALID_VALUE }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_VALUE}, ${MAX_UINT_16})` + ) + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, maxAccountingExtraDataListItemsCount: INVALID_VALUE }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_VALUE}, ${MAX_UINT_16})` + ) + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, maxNodeOperatorsPerExtraDataItemCount: INVALID_VALUE }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_VALUE}, ${MAX_UINT_16})` + ) + }) + + it('values must be less or equals to type(uint64).max', async () => { + const MAX_UINT_64 = BigInt(2) ** 64n - 1n + const INVALID_VALUE = MAX_UINT_64 + 1n + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, requestTimestampMargin: INVALID_VALUE.toString() }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_VALUE.toString()}, ${MAX_UINT_64.toString()})` + ) + + await assert.reverts( + oracleReportSanityChecker.setOracleReportLimits( + Object.values({ ...defaultLimitsList, maxPositiveTokenRebase: INVALID_VALUE.toString() }), + { + from: managersRoster.allLimitsManagers[0], + } + ), + `IncorrectLimitValue(${INVALID_VALUE.toString()}, ${MAX_UINT_64.toString()})` + ) + }) + }) }) From b74876858991c0e3713141e5df03ee7818ad01d6 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 Mar 2023 12:01:58 +0300 Subject: [PATCH 61/66] fix: review fixes --- .../0.8.9/lib/PositiveTokenRebaseLimiter.sol | 31 ++++++++++--------- .../PositiveTokenRebaseLimiterMock.sol | 4 +-- .../positive-token-rebase-limiter.test.js | 22 ++++++------- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol index 0a8ff84a3..c462011b2 100644 --- a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol +++ b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol @@ -23,10 +23,10 @@ import {Math256} from "../../common/lib/Math256.sol"; * @dev Internal limiter representation struct (storing in memory) */ struct TokenRebaseLimiterData { - uint256 preTotalPooledEther; // pre-rebase total pooled ether - uint256 preTotalShares; // pre-rebase total shares - uint256 postTotalPooledEther; // accumulated total pooled ether when token rebase components applied - uint256 rebaseLimit; // positive rebase limit (target value) with 1e9 precision (`LIMITER_PRECISION_BASE`) + uint256 preTotalPooledEther; // pre-rebase total pooled ether + uint256 preTotalShares; // pre-rebase total shares + uint256 currentTotalPooledEther; // intermediate total pooled ether amount while token rebase is in progress + uint256 rebaseLimit; // positive rebase limit (target value) with 1e9 precision (`LIMITER_PRECISION_BASE`) } /** @@ -90,7 +90,7 @@ library PositiveTokenRebaseLimiter { // special case if (_preTotalPooledEther == 0) { _rebaseLimit = UNLIMITED_REBASE; } - limiterState.postTotalPooledEther = limiterState.preTotalPooledEther = _preTotalPooledEther; + limiterState.currentTotalPooledEther = limiterState.preTotalPooledEther = _preTotalPooledEther; limiterState.preTotalShares = _preTotalShares; limiterState.rebaseLimit = _rebaseLimit; } @@ -102,9 +102,9 @@ library PositiveTokenRebaseLimiter { */ function isLimitReached(TokenRebaseLimiterData memory _limiterState) internal pure returns (bool) { if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return false; - if (_limiterState.postTotalPooledEther < _limiterState.preTotalPooledEther) return false; + if (_limiterState.currentTotalPooledEther < _limiterState.preTotalPooledEther) return false; - uint256 accumulatedEther = _limiterState.postTotalPooledEther - _limiterState.preTotalPooledEther; + uint256 accumulatedEther = _limiterState.currentTotalPooledEther - _limiterState.preTotalPooledEther; uint256 accumulatedRebase; if (_limiterState.preTotalPooledEther > 0) { @@ -124,9 +124,9 @@ library PositiveTokenRebaseLimiter { ) internal pure { if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return; - if (_etherAmount > _limiterState.postTotalPooledEther) revert NegativeTotalPooledEther(); + if (_etherAmount > _limiterState.currentTotalPooledEther) revert NegativeTotalPooledEther(); - _limiterState.postTotalPooledEther -= _etherAmount; + _limiterState.currentTotalPooledEther -= _etherAmount; } /** @@ -144,17 +144,18 @@ library PositiveTokenRebaseLimiter { { if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return _etherAmount; - uint256 prevPooledEther = _limiterState.postTotalPooledEther; - _limiterState.postTotalPooledEther += _etherAmount; + uint256 prevPooledEther = _limiterState.currentTotalPooledEther; + _limiterState.currentTotalPooledEther += _etherAmount; uint256 maxTotalPooledEther = _limiterState.preTotalPooledEther + (_limiterState.rebaseLimit * _limiterState.preTotalPooledEther) / LIMITER_PRECISION_BASE; - _limiterState.postTotalPooledEther = Math256.min(_limiterState.postTotalPooledEther, maxTotalPooledEther); + _limiterState.currentTotalPooledEther + = Math256.min(_limiterState.currentTotalPooledEther, maxTotalPooledEther); - assert(_limiterState.postTotalPooledEther >= prevPooledEther); + assert(_limiterState.currentTotalPooledEther >= prevPooledEther); - return _limiterState.postTotalPooledEther - prevPooledEther; + return _limiterState.currentTotalPooledEther - prevPooledEther; } /** @@ -173,7 +174,7 @@ library PositiveTokenRebaseLimiter { uint256 rebaseLimitPlus1 = _limiterState.rebaseLimit + LIMITER_PRECISION_BASE; uint256 pooledEtherRate = - (_limiterState.postTotalPooledEther * LIMITER_PRECISION_BASE) / _limiterState.preTotalPooledEther; + (_limiterState.currentTotalPooledEther * LIMITER_PRECISION_BASE) / _limiterState.preTotalPooledEther; maxSharesToBurn = (_limiterState.preTotalShares * (rebaseLimitPlus1 - pooledEtherRate)) / rebaseLimitPlus1; } diff --git a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol index 5aedd826e..c3600b482 100644 --- a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol +++ b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol @@ -16,13 +16,13 @@ contract PositiveTokenRebaseLimiterMock { returns ( uint256 preTotalPooledEther, uint256 preTotalShares, - uint256 postTotalPooledEther, + uint256 currentTotalPooledEther, uint256 rebaseLimit ) { preTotalPooledEther = limiter.preTotalPooledEther; preTotalShares = limiter.preTotalShares; - postTotalPooledEther = limiter.postTotalPooledEther; + currentTotalPooledEther = limiter.currentTotalPooledEther; rebaseLimit = limiter.rebaseLimit; } diff --git a/test/0.8.9/positive-token-rebase-limiter.test.js b/test/0.8.9/positive-token-rebase-limiter.test.js index a22e84813..1674a3c81 100644 --- a/test/0.8.9/positive-token-rebase-limiter.test.js +++ b/test/0.8.9/positive-token-rebase-limiter.test.js @@ -30,7 +30,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, 0) assert.equals(limiterValues.preTotalShares, 0) - assert.equals(limiterValues.postTotalPooledEther, 0) + assert.equals(limiterValues.currentTotalPooledEther, 0) assert.equals(limiterValues.rebaseLimit, 0) assert.isTrue(await limiter.isLimitReached()) @@ -46,7 +46,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) - assert.equals(limiterValues.postTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.currentTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.rebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) @@ -72,7 +72,7 @@ contract('PositiveTokenRebaseLimiter', () => { const limiterValues0 = await limiter.getLimiterValues() assert.equals(limiterValues0.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues0.preTotalShares, preTotalShares) - assert.equals(limiterValues0.postTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues0.currentTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues0.rebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) @@ -81,7 +81,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValuesNeg.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValuesNeg.preTotalShares, preTotalShares) assert.equals(limiterValuesNeg.rebaseLimit, rebaseLimit) - assert.equals(limiterValuesNeg.postTotalPooledEther, bn(preTotalPooledEther).sub(bn(ETH(1)))) + assert.equals(limiterValuesNeg.currentTotalPooledEther, bn(preTotalPooledEther).sub(bn(ETH(1)))) assert.isFalse(await limiter.isLimitReached()) }) @@ -97,7 +97,7 @@ contract('PositiveTokenRebaseLimiter', () => { const limiterValues = await limiter.getLimiterValues() assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) - assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(1)))) + assert.equals(limiterValues.currentTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(1)))) assert.equals(limiterValues.rebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) @@ -128,13 +128,13 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) - assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) + assert.equals(limiterValues.currentTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) assert.equals(limiterValues.rebaseLimit, rebaseLimit) assert.equals(await limiter.getSharesToBurnLimit(), bn('4499977500112499437')) const preShareRate = bn(preTotalPooledEther).mul(e9).div(bn(preTotalShares)) - const postShareRate = bn(limiterValues.postTotalPooledEther) + const postShareRate = bn(limiterValues.currentTotalPooledEther) .mul(e9) .div(bn(preTotalShares).sub(await limiter.getSharesToBurnLimit())) @@ -158,7 +158,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) - assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) + assert.equals(limiterValues.currentTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) assert.equals(limiterValues.rebaseLimit, rebaseLimit) await limiter.decreaseEther(ETH(1)) @@ -167,7 +167,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) - assert.equals(limiterValues.postTotalPooledEther, preTotalPooledEther) + assert.equals(limiterValues.currentTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.rebaseLimit, rebaseLimit) assert.equals(await limiter.getSharesToBurnLimit(), bn('4999975000124999375')) @@ -212,13 +212,13 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) - assert.equals(limiterValues.postTotalPooledEther, bn(preTotalPooledEther).sub(bn(ETH(39000)))) + assert.equals(limiterValues.currentTotalPooledEther, bn(preTotalPooledEther).sub(bn(ETH(39000)))) assert.equals(limiterValues.rebaseLimit, rebaseLimit) assert.equals(await limiter.getSharesToBurnLimit(), bn('39960039960039960039960')) const preShareRate = bn(preTotalPooledEther).mul(e9).div(bn(preTotalShares)) - const postShareRate = bn(limiterValues.postTotalPooledEther) + const postShareRate = bn(limiterValues.currentTotalPooledEther) .mul(e9) .div(bn(preTotalShares).sub(await limiter.getSharesToBurnLimit())) From 56f287a73fb6dc73a7a8de8f8462148e896990ea Mon Sep 17 00:00:00 2001 From: Bogdan Kovtun Date: Wed, 22 Mar 2023 14:49:01 +0400 Subject: [PATCH 62/66] Rename ModuleOutOfGas to UnrecoverableModuleError --- contracts/0.8.9/StakingRouter.sol | 8 ++++---- lib/abi/StakingRouter.json | 2 +- .../staking-router/staking-router-keys-reporting.test.js | 2 +- test/0.8.9/staking-router/staking-router.test.js | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/contracts/0.8.9/StakingRouter.sol b/contracts/0.8.9/StakingRouter.sol index 3f6de0b3d..bc4af6023 100644 --- a/contracts/0.8.9/StakingRouter.sol +++ b/contracts/0.8.9/StakingRouter.sol @@ -54,7 +54,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version error InvalidDepositsValue(uint256 etherValue, uint256 depositsCount); error StakingModuleAddressExists(); error ArraysLengthMismatch(uint256 firstArrayLength, uint256 secondArrayLength); - error ModuleOutOfGas(); + error UnrecoverableModuleError(); enum StakingModuleStatus { Active, // deposits and rewards allowed @@ -295,7 +295,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// return an invalid value when the onRewardsMinted() reverts because of the /// "out of gas" error. Here we assume that the onRewardsMinted() method doesn't /// have reverts with empty error data except "out of gas". - if (lowLevelRevertData.length == 0) revert ModuleOutOfGas(); + if (lowLevelRevertData.length == 0) revert UnrecoverableModuleError(); emit RewardsMintedReportFailed( _stakingModuleIds[i], lowLevelRevertData @@ -555,7 +555,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// reverts because of the "out of gas" error. Here we assume that the /// onExitedAndStuckValidatorsCountsUpdated() method doesn't have reverts with /// empty error data except "out of gas". - if (lowLevelRevertData.length == 0) revert ModuleOutOfGas(); + if (lowLevelRevertData.length == 0) revert UnrecoverableModuleError(); emit ExitedAndStuckValidatorsCountsUpdateFailed( stakingModule.id, lowLevelRevertData @@ -1140,7 +1140,7 @@ contract StakingRouter is AccessControlEnumerable, BeaconChainDepositor, Version /// reverts because of the "out of gas" error. Here we assume that the /// onWithdrawalCredentialsChanged() method doesn't have reverts with /// empty error data except "out of gas". - if (lowLevelRevertData.length == 0) revert ModuleOutOfGas(); + if (lowLevelRevertData.length == 0) revert UnrecoverableModuleError(); _setStakingModuleStatus(stakingModule, StakingModuleStatus.DepositsPaused); emit WithdrawalsCredentialsChangeFailed(stakingModule.id, lowLevelRevertData); } diff --git a/lib/abi/StakingRouter.json b/lib/abi/StakingRouter.json index 0356024c0..88b6ba986 100644 --- a/lib/abi/StakingRouter.json +++ b/lib/abi/StakingRouter.json @@ -1 +1 @@ -[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"ModuleOutOfGas","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file +[{"inputs":[{"internalType":"address","name":"_depositContract","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AppAuthLidoFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"firstArrayLength","type":"uint256"},{"internalType":"uint256","name":"secondArrayLength","type":"uint256"}],"name":"ArraysLengthMismatch","type":"error"},{"inputs":[],"name":"DepositContractZeroAddress","type":"error"},{"inputs":[],"name":"DirectETHTransfer","type":"error"},{"inputs":[],"name":"EmptyWithdrawalsCredentials","type":"error"},{"inputs":[],"name":"ExitedValidatorsCountCannotDecrease","type":"error"},{"inputs":[],"name":"InvalidContractVersionIncrement","type":"error"},{"inputs":[{"internalType":"uint256","name":"etherValue","type":"uint256"},{"internalType":"uint256","name":"depositsCount","type":"uint256"}],"name":"InvalidDepositsValue","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidPublicKeysBatchLength","type":"error"},{"inputs":[{"internalType":"uint256","name":"code","type":"uint256"}],"name":"InvalidReportData","type":"error"},{"inputs":[{"internalType":"uint256","name":"actual","type":"uint256"},{"internalType":"uint256","name":"expected","type":"uint256"}],"name":"InvalidSignaturesBatchLength","type":"error"},{"inputs":[],"name":"NonZeroContractVersionOnInit","type":"error"},{"inputs":[],"name":"StakingModuleAddressExists","type":"error"},{"inputs":[],"name":"StakingModuleNotActive","type":"error"},{"inputs":[],"name":"StakingModuleNotPaused","type":"error"},{"inputs":[],"name":"StakingModuleStatusTheSame","type":"error"},{"inputs":[],"name":"StakingModuleUnregistered","type":"error"},{"inputs":[],"name":"StakingModuleWrongName","type":"error"},{"inputs":[],"name":"StakingModulesLimitExceeded","type":"error"},{"inputs":[{"internalType":"uint256","name":"expected","type":"uint256"},{"internalType":"uint256","name":"received","type":"uint256"}],"name":"UnexpectedContractVersion","type":"error"},{"inputs":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOpStuckValidatorsCount","type":"uint256"}],"name":"UnexpectedCurrentValidatorsCount","type":"error"},{"inputs":[],"name":"UnrecoverableModuleError","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ValueOver100Percent","type":"error"},{"inputs":[{"internalType":"string","name":"field","type":"string"}],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"version","type":"uint256"}],"name":"ContractVersionSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"ExitedAndStuckValidatorsCountsUpdateFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"RewardsMintedReportFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"address","name":"stakingModule","type":"address"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"address","name":"createdBy","type":"address"}],"name":"StakingModuleAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"unreportedExitedValidatorsCount","type":"uint256"}],"name":"StakingModuleExitedValidatorsIncompleteReporting","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"stakingModuleFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"treasuryFee","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleFeesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"enum StakingRouter.StakingModuleStatus","name":"status","type":"uint8"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleStatusSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"targetShare","type":"uint256"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"StakingModuleTargetShareSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"StakingRouterETHDeposited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes32","name":"withdrawalCredentials","type":"bytes32"},{"indexed":false,"internalType":"address","name":"setBy","type":"address"}],"name":"WithdrawalCredentialsSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakingModuleId","type":"uint256"},{"indexed":false,"internalType":"bytes","name":"lowLevelRevertData","type":"bytes"}],"name":"WithdrawalsCredentialsChangeFailed","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSIT_CONTRACT","outputs":[{"internalType":"contract IDepositContract","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_PRECISION_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MANAGE_WITHDRAWAL_CREDENTIALS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULES_COUNT","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_STAKING_MODULE_NAME_LENGTH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REPORT_REWARDS_MINTED_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_MANAGE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_PAUSE_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"STAKING_MODULE_RESUME_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOTAL_BASIS_POINTS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNSAFE_SET_EXITED_VALIDATORS_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"address","name":"_stakingModuleAddress","type":"address"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"addStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_depositCalldata","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getAllNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContractVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_depositsCount","type":"uint256"}],"name":"getDepositsAllocation","outputs":[{"internalType":"uint256","name":"allocated","type":"uint256"},{"internalType":"uint256[]","name":"allocations","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getLido","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256[]","name":"_nodeOperatorIds","type":"uint256[]"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_offset","type":"uint256"},{"internalType":"uint256","name":"_limit","type":"uint256"}],"name":"getNodeOperatorDigests","outputs":[{"components":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"bool","name":"isActive","type":"bool"},{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.NodeOperatorDigest[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"}],"name":"getNodeOperatorSummary","outputs":[{"components":[{"internalType":"bool","name":"isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"targetValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"refundedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"stuckPenaltyEndTimestamp","type":"uint256"},{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.NodeOperatorSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistribution","outputs":[{"internalType":"uint96","name":"modulesFee","type":"uint96"},{"internalType":"uint96","name":"treasuryFee","type":"uint96"},{"internalType":"uint256","name":"basePrecision","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingFeeAggregateDistributionE4Precision","outputs":[{"internalType":"uint16","name":"modulesFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModule","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleActiveValidatorsCount","outputs":[{"internalType":"uint256","name":"activeValidatorsCount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"}],"name":"getStakingModuleDigests","outputs":[{"components":[{"internalType":"uint256","name":"nodeOperatorsCount","type":"uint256"},{"internalType":"uint256","name":"activeNodeOperatorsCount","type":"uint256"},{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule","name":"state","type":"tuple"},{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"internalType":"struct StakingRouter.StakingModuleDigest[]","name":"digests","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModuleIds","outputs":[{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsDepositsPaused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleIsStopped","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleLastDepositBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_maxDepositsValue","type":"uint256"}],"name":"getStakingModuleMaxDepositsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleNonce","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleStatus","outputs":[{"internalType":"enum StakingRouter.StakingModuleStatus","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"getStakingModuleSummary","outputs":[{"components":[{"internalType":"uint256","name":"totalExitedValidators","type":"uint256"},{"internalType":"uint256","name":"totalDepositedValidators","type":"uint256"},{"internalType":"uint256","name":"depositableValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModuleSummary","name":"summary","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModules","outputs":[{"components":[{"internalType":"uint24","name":"id","type":"uint24"},{"internalType":"address","name":"stakingModuleAddress","type":"address"},{"internalType":"uint16","name":"stakingModuleFee","type":"uint16"},{"internalType":"uint16","name":"treasuryFee","type":"uint16"},{"internalType":"uint16","name":"targetShare","type":"uint16"},{"internalType":"uint8","name":"status","type":"uint8"},{"internalType":"string","name":"name","type":"string"},{"internalType":"uint64","name":"lastDepositAt","type":"uint64"},{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"exitedValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.StakingModule[]","name":"res","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingModulesCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getStakingRewardsDistribution","outputs":[{"internalType":"address[]","name":"recipients","type":"address[]"},{"internalType":"uint256[]","name":"stakingModuleIds","type":"uint256[]"},{"internalType":"uint96[]","name":"stakingModuleFees","type":"uint96[]"},{"internalType":"uint96","name":"totalFee","type":"uint96"},{"internalType":"uint256","name":"precisionPoints","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTotalFeeE4Precision","outputs":[{"internalType":"uint16","name":"totalFee","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getWithdrawalCredentials","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"hasStakingModule","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"},{"internalType":"address","name":"_lido","type":"address"},{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"onValidatorsCountsByNodeOperatorReportingFinished","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"pauseStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_totalShares","type":"uint256[]"}],"name":"reportRewardsMinted","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_exitedValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleExitedValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"bytes","name":"_nodeOperatorIds","type":"bytes"},{"internalType":"bytes","name":"_stuckValidatorsCounts","type":"bytes"}],"name":"reportStakingModuleStuckValidatorsCountByNodeOperator","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"}],"name":"resumeStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"enum StakingRouter.StakingModuleStatus","name":"_status","type":"uint8"}],"name":"setStakingModuleStatus","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_withdrawalCredentials","type":"bytes32"}],"name":"setWithdrawalCredentials","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_triggerUpdateFinish","type":"bool"},{"components":[{"internalType":"uint256","name":"currentModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"currentNodeOperatorStuckValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newModuleExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorExitedValidatorsCount","type":"uint256"},{"internalType":"uint256","name":"newNodeOperatorStuckValidatorsCount","type":"uint256"}],"internalType":"struct StakingRouter.ValidatorsCountsCorrection","name":"_correction","type":"tuple"}],"name":"unsafeSetExitedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_stakingModuleIds","type":"uint256[]"},{"internalType":"uint256[]","name":"_exitedValidatorsCounts","type":"uint256[]"}],"name":"updateExitedValidatorsCountByStakingModule","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"uint256","name":"_refundedValidatorsCount","type":"uint256"}],"name":"updateRefundedValidatorsCount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_targetShare","type":"uint256"},{"internalType":"uint256","name":"_stakingModuleFee","type":"uint256"},{"internalType":"uint256","name":"_treasuryFee","type":"uint256"}],"name":"updateStakingModule","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_stakingModuleId","type":"uint256"},{"internalType":"uint256","name":"_nodeOperatorId","type":"uint256"},{"internalType":"bool","name":"_isTargetLimitActive","type":"bool"},{"internalType":"uint256","name":"_targetLimit","type":"uint256"}],"name":"updateTargetValidatorsLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}] \ No newline at end of file diff --git a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js index b4f1e6cae..eaded7a53 100644 --- a/test/0.8.9/staking-router/staking-router-keys-reporting.test.js +++ b/test/0.8.9/staking-router/staking-router-keys-reporting.test.js @@ -512,7 +512,7 @@ contract('StakingRouter', ([deployer, lido, admin, stranger]) => { await assert.reverts( router.onValidatorsCountsByNodeOperatorReportingFinished({ from: admin }), - 'ModuleOutOfGas()' + 'UnrecoverableModuleError()' ) }) }) diff --git a/test/0.8.9/staking-router/staking-router.test.js b/test/0.8.9/staking-router/staking-router.test.js index d712125ca..bc5fa7565 100644 --- a/test/0.8.9/staking-router/staking-router.test.js +++ b/test/0.8.9/staking-router/staking-router.test.js @@ -357,7 +357,7 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { .on('onWithdrawalCredentialsChanged', { revert: { reason: 'outOfGas' } }) .update({ from: deployer }) - await assert.reverts(router.setWithdrawalCredentials(newWC, { from: appManager }), 'ModuleOutOfGas()') + await assert.reverts(router.setWithdrawalCredentials(newWC, { from: appManager }), 'UnrecoverableModuleError()') }) }) @@ -986,7 +986,7 @@ contract('StakingRouter', ([deployer, lido, admin, appManager, stranger]) => { await assert.reverts( router.reportRewardsMinted(stakingModuleIds, totalShares, { from: admin }), - 'ModuleOutOfGas()' + 'UnrecoverableModuleError()' ) }) }) From 1ae9d93e92ef2add057f3ef0b859291f247d006d Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 Mar 2023 13:54:14 +0300 Subject: [PATCH 63/66] fix: review fixes --- .../0.8.9/lib/PositiveTokenRebaseLimiter.sol | 36 +++++++++---------- .../OracleReportSanityChecker.sol | 8 +++++ .../PositiveTokenRebaseLimiterMock.sol | 4 +-- .../positive-token-rebase-limiter.test.js | 18 +++++----- 4 files changed, 37 insertions(+), 29 deletions(-) diff --git a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol index c462011b2..a5e6bd052 100644 --- a/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol +++ b/contracts/0.8.9/lib/PositiveTokenRebaseLimiter.sol @@ -26,7 +26,7 @@ struct TokenRebaseLimiterData { uint256 preTotalPooledEther; // pre-rebase total pooled ether uint256 preTotalShares; // pre-rebase total shares uint256 currentTotalPooledEther; // intermediate total pooled ether amount while token rebase is in progress - uint256 rebaseLimit; // positive rebase limit (target value) with 1e9 precision (`LIMITER_PRECISION_BASE`) + uint256 positiveRebaseLimit; // positive rebase limit (target value) with 1e9 precision (`LIMITER_PRECISION_BASE`) } /** @@ -39,31 +39,31 @@ struct TokenRebaseLimiterData { * dec - total shares decrease. * * ### Step 1. Calculating the allowed total pooled ether changes (preTotalShares === postTotalShares) - * Used for `PositiveTokenRebaseLimiter.consumeLimit()`, `PositiveTokenRebaseLimiter.raiseLimit()`. + * Used for `PositiveTokenRebaseLimiter.increaseEther()`, `PositiveTokenRebaseLimiter.decreaseEther()`. * * R = ((preTotalPooledEther + inc) / preTotalShares) / (preTotalPooledEther / preTotalShares) - 1 - * R = ((preTotalPooledEther + inc) / preTotalShares) * (preTotalShares / preTotalPooledEther) - 1 - * R = (preTotalPooledEther + inc) / preTotalPooledEther) - 1 - * R = inc/preTotalPooledEther + * = ((preTotalPooledEther + inc) / preTotalShares) * (preTotalShares / preTotalPooledEther) - 1 + * = (preTotalPooledEther + inc) / preTotalPooledEther) - 1 + * = inc/preTotalPooledEther * * isolating inc: * * ``` inc = R * preTotalPooledEther ``` * - * ### Step 2. Calculating the allowed to burn shares (preTotalPooledEther != postTotalPooledEther) + * ### Step 2. Calculating the allowed to burn shares (preTotalPooledEther != currentTotalPooledEther) * Used for `PositiveTokenRebaseLimiter.getSharesToBurnLimit()`. * - * R = (postTotalPooledEther / (preTotalShares - dec)) / (preTotalPooledEther / preTotalShares) - 1, - * let X = postTotalPooledEther / preTotalPooledEther + * R = (currentTotalPooledEther / (preTotalShares - dec)) / (preTotalPooledEther / preTotalShares) - 1, + * let X = currentTotalPooledEther / preTotalPooledEther * * then: - * R = X * (preTotalShares / (preTotalShares - dec)) - 1 + * R = X * (preTotalShares / (preTotalShares - dec)) - 1, or * (R+1) * (preTotalShares - dec) = X * preTotalShares * * isolating dec: * dec * (R + 1) = (R + 1 - X) * preTotalShares => * - * ``` dec = preTotalShares * (R + 1 - postTotalPooledEther/preTotalPooledEther) / (R + 1) ``` + * ``` dec = preTotalShares * (R + 1 - currentTotalPooledEther/preTotalPooledEther) / (R + 1) ``` * */ library PositiveTokenRebaseLimiter { @@ -92,7 +92,7 @@ library PositiveTokenRebaseLimiter { limiterState.currentTotalPooledEther = limiterState.preTotalPooledEther = _preTotalPooledEther; limiterState.preTotalShares = _preTotalShares; - limiterState.rebaseLimit = _rebaseLimit; + limiterState.positiveRebaseLimit = _rebaseLimit; } /** @@ -101,7 +101,7 @@ library PositiveTokenRebaseLimiter { * @return true if limit is reached */ function isLimitReached(TokenRebaseLimiterData memory _limiterState) internal pure returns (bool) { - if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return false; + if (_limiterState.positiveRebaseLimit == UNLIMITED_REBASE) return false; if (_limiterState.currentTotalPooledEther < _limiterState.preTotalPooledEther) return false; uint256 accumulatedEther = _limiterState.currentTotalPooledEther - _limiterState.preTotalPooledEther; @@ -111,7 +111,7 @@ library PositiveTokenRebaseLimiter { accumulatedRebase = accumulatedEther * LIMITER_PRECISION_BASE / _limiterState.preTotalPooledEther; } - return accumulatedRebase >= _limiterState.rebaseLimit; + return accumulatedRebase >= _limiterState.positiveRebaseLimit; } /** @@ -122,7 +122,7 @@ library PositiveTokenRebaseLimiter { function decreaseEther( TokenRebaseLimiterData memory _limiterState, uint256 _etherAmount ) internal pure { - if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return; + if (_limiterState.positiveRebaseLimit == UNLIMITED_REBASE) return; if (_etherAmount > _limiterState.currentTotalPooledEther) revert NegativeTotalPooledEther(); @@ -142,13 +142,13 @@ library PositiveTokenRebaseLimiter { pure returns (uint256 consumedEther) { - if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return _etherAmount; + if (_limiterState.positiveRebaseLimit == UNLIMITED_REBASE) return _etherAmount; uint256 prevPooledEther = _limiterState.currentTotalPooledEther; _limiterState.currentTotalPooledEther += _etherAmount; uint256 maxTotalPooledEther = _limiterState.preTotalPooledEther + - (_limiterState.rebaseLimit * _limiterState.preTotalPooledEther) / LIMITER_PRECISION_BASE; + (_limiterState.positiveRebaseLimit * _limiterState.preTotalPooledEther) / LIMITER_PRECISION_BASE; _limiterState.currentTotalPooledEther = Math256.min(_limiterState.currentTotalPooledEther, maxTotalPooledEther); @@ -168,11 +168,11 @@ library PositiveTokenRebaseLimiter { pure returns (uint256 maxSharesToBurn) { - if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return _limiterState.preTotalShares; + if (_limiterState.positiveRebaseLimit == UNLIMITED_REBASE) return _limiterState.preTotalShares; if (isLimitReached(_limiterState)) return 0; - uint256 rebaseLimitPlus1 = _limiterState.rebaseLimit + LIMITER_PRECISION_BASE; + uint256 rebaseLimitPlus1 = _limiterState.positiveRebaseLimit + LIMITER_PRECISION_BASE; uint256 pooledEtherRate = (_limiterState.currentTotalPooledEther * LIMITER_PRECISION_BASE) / _limiterState.preTotalPooledEther; diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 96ae4ba2d..218c9755c 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -376,8 +376,16 @@ contract OracleReportSanityChecker is AccessControlEnumerable { withdrawals = tokenRebaseLimiter.increaseEther(_withdrawalVaultBalance); elRewards = tokenRebaseLimiter.increaseEther(_elRewardsVaultBalance); + // determining the shares to burn limit that would have been + // in no withdrawals finalized during the report + // it's used to check later the provided `simulatedShareRate` value + // after the off-chain calculation via `eth_call` of `Lido.handleOracleReport()` + // see the step 9 of the `Lido._handleOracleReport()` simulatedSharesToBurn = Math256.min(tokenRebaseLimiter.getSharesToBurnLimit(), _sharesRequestedToBurn); + + // remove ether to lock for withdrawals from total pooled ether tokenRebaseLimiter.decreaseEther(_etherToLockForWithdrawals); + // re-evaluate shares to burn after TVL was updated due to withdrawals finalization sharesToBurn = Math256.min(tokenRebaseLimiter.getSharesToBurnLimit(), _newSharesToBurnForWithdrawals + _sharesRequestedToBurn); } diff --git a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol index c3600b482..d87ab92b0 100644 --- a/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol +++ b/contracts/0.8.9/test_helpers/PositiveTokenRebaseLimiterMock.sol @@ -17,13 +17,13 @@ contract PositiveTokenRebaseLimiterMock { uint256 preTotalPooledEther, uint256 preTotalShares, uint256 currentTotalPooledEther, - uint256 rebaseLimit + uint256 positiveRebaseLimit ) { preTotalPooledEther = limiter.preTotalPooledEther; preTotalShares = limiter.preTotalShares; currentTotalPooledEther = limiter.currentTotalPooledEther; - rebaseLimit = limiter.rebaseLimit; + positiveRebaseLimit = limiter.positiveRebaseLimit; } function initLimiterState( diff --git a/test/0.8.9/positive-token-rebase-limiter.test.js b/test/0.8.9/positive-token-rebase-limiter.test.js index 1674a3c81..f30dd9bfe 100644 --- a/test/0.8.9/positive-token-rebase-limiter.test.js +++ b/test/0.8.9/positive-token-rebase-limiter.test.js @@ -31,7 +31,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, 0) assert.equals(limiterValues.preTotalShares, 0) assert.equals(limiterValues.currentTotalPooledEther, 0) - assert.equals(limiterValues.rebaseLimit, 0) + assert.equals(limiterValues.positiveRebaseLimit, 0) assert.isTrue(await limiter.isLimitReached()) }) @@ -47,7 +47,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) assert.equals(limiterValues.currentTotalPooledEther, preTotalPooledEther) - assert.equals(limiterValues.rebaseLimit, rebaseLimit) + assert.equals(limiterValues.positiveRebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) await assert.revertsWithCustomError( @@ -73,14 +73,14 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues0.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues0.preTotalShares, preTotalShares) assert.equals(limiterValues0.currentTotalPooledEther, preTotalPooledEther) - assert.equals(limiterValues0.rebaseLimit, rebaseLimit) + assert.equals(limiterValues0.positiveRebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) await limiter.decreaseEther(ETH(1)) const limiterValuesNeg = await limiter.getLimiterValues() assert.equals(limiterValuesNeg.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValuesNeg.preTotalShares, preTotalShares) - assert.equals(limiterValuesNeg.rebaseLimit, rebaseLimit) + assert.equals(limiterValuesNeg.positiveRebaseLimit, rebaseLimit) assert.equals(limiterValuesNeg.currentTotalPooledEther, bn(preTotalPooledEther).sub(bn(ETH(1)))) assert.isFalse(await limiter.isLimitReached()) }) @@ -98,7 +98,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) assert.equals(limiterValues.currentTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(1)))) - assert.equals(limiterValues.rebaseLimit, rebaseLimit) + assert.equals(limiterValues.positiveRebaseLimit, rebaseLimit) assert.isFalse(await limiter.isLimitReached()) assert.equals(await limiter.increaseEther.sendWithResult(ETH(2)), ETH(2)) @@ -129,7 +129,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) assert.equals(limiterValues.currentTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) - assert.equals(limiterValues.rebaseLimit, rebaseLimit) + assert.equals(limiterValues.positiveRebaseLimit, rebaseLimit) assert.equals(await limiter.getSharesToBurnLimit(), bn('4499977500112499437')) @@ -159,7 +159,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) assert.equals(limiterValues.currentTotalPooledEther, bn(preTotalPooledEther).add(bn(ETH(2 - 1)))) - assert.equals(limiterValues.rebaseLimit, rebaseLimit) + assert.equals(limiterValues.positiveRebaseLimit, rebaseLimit) await limiter.decreaseEther(ETH(1)) assert.isFalse(await limiter.isLimitReached()) @@ -168,7 +168,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) assert.equals(limiterValues.currentTotalPooledEther, preTotalPooledEther) - assert.equals(limiterValues.rebaseLimit, rebaseLimit) + assert.equals(limiterValues.positiveRebaseLimit, rebaseLimit) assert.equals(await limiter.getSharesToBurnLimit(), bn('4999975000124999375')) }) @@ -213,7 +213,7 @@ contract('PositiveTokenRebaseLimiter', () => { assert.equals(limiterValues.preTotalPooledEther, preTotalPooledEther) assert.equals(limiterValues.preTotalShares, preTotalShares) assert.equals(limiterValues.currentTotalPooledEther, bn(preTotalPooledEther).sub(bn(ETH(39000)))) - assert.equals(limiterValues.rebaseLimit, rebaseLimit) + assert.equals(limiterValues.positiveRebaseLimit, rebaseLimit) assert.equals(await limiter.getSharesToBurnLimit(), bn('39960039960039960039960')) From 893d0a1c79a14680a0062e2d138f3d89f4004264 Mon Sep 17 00:00:00 2001 From: Alexey Potapkin Date: Wed, 22 Mar 2023 13:07:54 +0200 Subject: [PATCH 64/66] =?UTF-8?q?=F0=9F=92=85:=20remove=20some=20dead=20co?= =?UTF-8?q?de=20from=20MemUtils?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- contracts/common/lib/MemUtils.sol | 25 ---------- test/common/lib/mem-utils.test.sol | 74 ------------------------------ 2 files changed, 99 deletions(-) diff --git a/contracts/common/lib/MemUtils.sol b/contracts/common/lib/MemUtils.sol index 959e4ab37..0c90d9c8b 100644 --- a/contracts/common/lib/MemUtils.sol +++ b/contracts/common/lib/MemUtils.sol @@ -63,29 +63,4 @@ library MemUtils { function copyBytes(bytes memory _src, bytes memory _dst, uint256 _dstStart) internal pure { copyBytes(_src, _dst, 0, _dstStart, _src.length); } - - /** - * Calculates keccak256 over a uint256 memory array contents. - * - * keccakUint256Array(array) is a more gas-efficient equivalent - * to keccak256(abi.encodePacked(array)) since copying memory - * is avoided. - */ - function keccakUint256Array(uint256[] memory _arr) internal pure returns (bytes32 result) { - assembly { - let ptr := add(_arr, 32) - let len := mul(mload(_arr), 32) - result := keccak256(ptr, len) - } - } - - /** - * Decreases length of a uint256 memory array `_arr` by the `_trimBy` items. - */ - function trimUint256Array(uint256[] memory _arr, uint256 _trimBy) internal pure { - uint256 newLen = _arr.length - _trimBy; - assembly { - mstore(_arr, newLen) - } - } } diff --git a/test/common/lib/mem-utils.test.sol b/test/common/lib/mem-utils.test.sol index cb4e462f4..c1600f2d4 100644 --- a/test/common/lib/mem-utils.test.sol +++ b/test/common/lib/mem-utils.test.sol @@ -457,78 +457,4 @@ contract MemUtilsTest is Test { bytes32(0x2222222222222222222222222222222222222222222222222222222222222222) )); } - - /// - /// keccakUint256Array - /// - - function test_keccakUint256Array_calcs_keccak_over_a_uint_array() external pure { - uint256[] memory array = new uint256[](5); - array[0] = uint256(0x1111111111111111111111111111111111111111111111111111111111111111); - array[1] = uint256(0x2222222222222222222222222222222222222222222222222222222222222222); - array[2] = uint256(0x3333333333333333333333333333333333333333333333333333333333333333); - array[3] = uint256(0x4444444444444444444444444444444444444444444444444444444444444444); - array[4] = uint256(0x5555555555555555555555555555555555555555555555555555555555555555); - - bytes32 expected = keccak256(abi.encodePacked(array)); - bytes32 actual = MemUtils.keccakUint256Array(array); - - Assert.equal(actual, expected); - } - - function test_keccakUint256Array_calcs_keccak_over_an_empty_array() external pure { - uint256[] memory array = new uint256[](0); - - bytes32 expected = keccak256(abi.encodePacked(array)); - bytes32 actual = MemUtils.keccakUint256Array(array); - - Assert.equal(actual, expected); - } - - /// - /// trimUint256Array - /// - - function test_trimUint256Array_decreases_length_of_a_uint_array() external pure { - uint256[] memory array = new uint256[](5); - array[0] = uint256(0x1111111111111111111111111111111111111111111111111111111111111111); - array[1] = uint256(0x2222222222222222222222222222222222222222222222222222222222222222); - array[2] = uint256(0x3333333333333333333333333333333333333333333333333333333333333333); - array[3] = uint256(0x4444444444444444444444444444444444444444444444444444444444444444); - array[4] = uint256(0x5555555555555555555555555555555555555555555555555555555555555555); - - MemUtils.trimUint256Array(array, 2); - - Assert.equal(array.length, 3); - Assert.equal(array[0], uint256(0x1111111111111111111111111111111111111111111111111111111111111111)); - Assert.equal(array[1], uint256(0x2222222222222222222222222222222222222222222222222222222222222222)); - Assert.equal(array[2], uint256(0x3333333333333333333333333333333333333333333333333333333333333333)); - - Assert.equal(abi.encodePacked(array), abi.encodePacked( - bytes32(0x1111111111111111111111111111111111111111111111111111111111111111), - bytes32(0x2222222222222222222222222222222222222222222222222222222222222222), - bytes32(0x3333333333333333333333333333333333333333333333333333333333333333) - )); - } - - function test_trimUint256Array_allows_trimming_to_zero_length() external pure { - uint256[] memory array = new uint256[](3); - array[0] = uint256(0x1111111111111111111111111111111111111111111111111111111111111111); - array[1] = uint256(0x2222222222222222222222222222222222222222222222222222222222222222); - array[2] = uint256(0x3333333333333333333333333333333333333333333333333333333333333333); - - MemUtils.trimUint256Array(array, 3); - - Assert.empty(array); - } - - function test_trimUint256Array_reverts_on_trying_to_trim_by_more_than_length() external { - uint256[] memory array = new uint256[](3); - array[0] = uint256(0x1111111111111111111111111111111111111111111111111111111111111111); - array[1] = uint256(0x2222222222222222222222222222222222222222222222222222222222222222); - array[2] = uint256(0x3333333333333333333333333333333333333333333333333333333333333333); - - vm.expectRevert(); - MemUtils.trimUint256Array(array, 4); - } } From 45cd58ade94eae20cd5d0457d7a3d9a0cba3c5b5 Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 Mar 2023 14:16:14 +0300 Subject: [PATCH 65/66] chore: formatiing --- contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 218c9755c..ad1e9ea5c 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -386,7 +386,10 @@ contract OracleReportSanityChecker is AccessControlEnumerable { // remove ether to lock for withdrawals from total pooled ether tokenRebaseLimiter.decreaseEther(_etherToLockForWithdrawals); // re-evaluate shares to burn after TVL was updated due to withdrawals finalization - sharesToBurn = Math256.min(tokenRebaseLimiter.getSharesToBurnLimit(), _newSharesToBurnForWithdrawals + _sharesRequestedToBurn); + sharesToBurn = Math256.min( + tokenRebaseLimiter.getSharesToBurnLimit(), + _newSharesToBurnForWithdrawals + _sharesRequestedToBurn + ); } /// @notice Applies sanity checks to the accounting params of Lido's oracle report From 39f6ec877498a366a6d53fe7c88a822805cf300c Mon Sep 17 00:00:00 2001 From: Eugene Mamin Date: Wed, 22 Mar 2023 14:24:21 +0300 Subject: [PATCH 66/66] chore: typos --- contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index ad1e9ea5c..023c9654b 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -377,10 +377,10 @@ contract OracleReportSanityChecker is AccessControlEnumerable { elRewards = tokenRebaseLimiter.increaseEther(_elRewardsVaultBalance); // determining the shares to burn limit that would have been - // in no withdrawals finalized during the report + // if no withdrawals finalized during the report // it's used to check later the provided `simulatedShareRate` value // after the off-chain calculation via `eth_call` of `Lido.handleOracleReport()` - // see the step 9 of the `Lido._handleOracleReport()` + // see also step 9 of the `Lido._handleOracleReport()` simulatedSharesToBurn = Math256.min(tokenRebaseLimiter.getSharesToBurnLimit(), _sharesRequestedToBurn); // remove ether to lock for withdrawals from total pooled ether