diff --git a/contracts/0.4.24/test_helpers/LidoMock.sol b/contracts/0.4.24/test_helpers/LidoMock.sol index 9be8388ad..ab834cc4e 100644 --- a/contracts/0.4.24/test_helpers/LidoMock.sol +++ b/contracts/0.4.24/test_helpers/LidoMock.sol @@ -70,41 +70,4 @@ contract LidoMock is Lido { function burnShares(address _account, uint256 _amount) external { _burnShares(_account, _amount); } - - function handleOracleReportDirect( - // Oracle timings - uint256 _reportTimestamp, - uint256 _timeElapsed, - // CL values - uint256 _clValidators, - uint256 _clBalance, - // EL values - uint256 _withdrawalVaultBalance, - uint256 _elRewardsVaultBalance, - // Decision about withdrawals processing - uint256 _lastFinalizableRequestId, - uint256 _simulatedShareRate - ) external returns ( - uint256 totalPooledEther, - uint256 totalShares, - uint256 withdrawals, - uint256 elRewards - ) { - - OracleReportContracts memory protocolContracts = _loadOracleReportContracts(); - - return _handleOracleReport( - OracleReportedData( - _reportTimestamp, - _timeElapsed, - _clValidators, - _clBalance, - _withdrawalVaultBalance, - _elRewardsVaultBalance, - _lastFinalizableRequestId, - _simulatedShareRate - ), - protocolContracts - ); - } } diff --git a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol index 4412fbc80..98e65e4cb 100644 --- a/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol +++ b/contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol @@ -493,7 +493,7 @@ contract OracleReportSanityChecker is AccessControlEnumerable { uint256 churnLimit = (_limitsList.churnValidatorsPerDayLimit * _timeElapsed) / SECONDS_PER_DAY; - if (_appearedValidators > churnLimit) revert IncorrectAppearedValidators(churnLimit); + if (_appearedValidators > churnLimit) revert IncorrectAppearedValidators(_appearedValidators); } function _checkRequestIdToFinalizeUpTo( diff --git a/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol b/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol index f1089c569..cb83f4ecf 100644 --- a/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol +++ b/contracts/0.8.9/test_helpers/OracleReportSanityCheckerMocks.sol @@ -76,11 +76,18 @@ contract LidoLocatorStub is ILidoLocator { } contract OracleReportSanityCheckerStub { + + error SelectorNotFound(bytes4 sig, uint256 value, bytes data); + + fallback() external payable { revert SelectorNotFound(msg.sig, msg.value, msg.data); } + function checkLidoOracleReport( uint256 _timeElapsed, uint256 _preCLBalance, uint256 _postCLBalance, - uint256 _withdrawalVaultBalance + uint256 _withdrawalVaultBalance, + uint256 _preCLValidators, + uint256 _postCLValidators ) external view {} function checkWithdrawalQueueOracleReport( @@ -102,4 +109,6 @@ contract OracleReportSanityCheckerStub { elRewards = _elRewardsVaultBalance; sharesToBurnLimit = _etherToLockForWithdrawals; } -} \ No newline at end of file + + function checkAccountingExtraDataListItemsCount(uint256 _extraDataListItemsCount) external view {} +} diff --git a/test/0.4.24/lido-handle-oracle-report.test.js b/test/0.4.24/lido-handle-oracle-report.test.js new file mode 100644 index 000000000..b22df4e4a --- /dev/null +++ b/test/0.4.24/lido-handle-oracle-report.test.js @@ -0,0 +1,475 @@ +const hre = require('hardhat') +const { assert } = require('../helpers/assert') +const { ETH, toBN, genKeys } = require('../helpers/utils') +const { deployProtocol } = require('../helpers/protocol') +const { EvmSnapshot } = require('../helpers/blockchain') +const { ZERO_ADDRESS } = require('../helpers/constants') +const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') + +const ETHForwarderMock = artifacts.require('ETHForwarderMock') +const ONE_YEAR = 3600 * 24 * 365 +const ONE_DAY = 3600 * 24 + +contract('Lido: handleOracleReport', ([appManager, , , , , , , stranger, anotherStranger, depositor, operator]) => { + let deployed, snapshot, lido, treasury, voting, oracle + let curatedModule, oracleReportSanityChecker + let strangerBalanceBefore, + anotherStrangerBalanceBefore, + totalPooledEtherBefore, + curatedModuleBalanceBefore, + treasuryBalanceBefore + + before('deploy base app', async () => { + deployed = await deployProtocol({ + stakingModulesFactory: async (protocol) => { + curatedModule = await setupNodeOperatorsRegistry(protocol) + return [ + { + module: curatedModule, + name: 'Curated', + targetShares: 10000, + moduleFee: 500, + treasuryFee: 500 + } + ] + }, + depositSecurityModuleFactory: async (protocol) => { + return { address: depositor } + } + }) + + await ETHForwarderMock.new(deployed.oracle.address, { from: appManager, value: ETH(1) }) + await hre.ethers.getImpersonatedSigner(deployed.oracle.address) + + await curatedModule.addNodeOperator('1', operator, { from: deployed.voting.address }) + const keysAmount = 120 + const keys1 = genKeys(keysAmount) + await curatedModule.addSigningKeys(0, keysAmount, keys1.pubkeys, keys1.sigkeys, { from: deployed.voting.address }) + await curatedModule.setNodeOperatorStakingLimit(0, keysAmount, { from: deployed.voting.address }) + + lido = deployed.pool + treasury = deployed.treasury.address + voting = deployed.voting.address + oracle = deployed.oracle.address + oracleReportSanityChecker = deployed.oracleReportSanityChecker + + await lido.submit(ZERO_ADDRESS, { from: stranger, value: ETH(30) }) + await lido.submit(ZERO_ADDRESS, { from: anotherStranger, value: ETH(70) }) + + await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: 0 }) + + snapshot = new EvmSnapshot(hre.ethers.provider) + await snapshot.make() + }) + + beforeEach(async () => { + await updateBalancesBefore() + }) + + afterEach(async () => { + await snapshot.rollback() + }) + + const checkStat = async ({ depositedValidators, beaconValidators, beaconBalance }) => { + const stat = await lido.getBeaconStat() + assert.equals(stat.depositedValidators, depositedValidators, 'depositedValidators check') + assert.equals(stat.beaconValidators, beaconValidators, 'beaconValidators check') + assert.equals(stat.beaconBalance, beaconBalance, 'beaconBalance check') + } + + const updateBalancesBefore = async () => { + totalPooledEtherBefore = await lido.getTotalPooledEther() + strangerBalanceBefore = await lido.balanceOf(stranger) + anotherStrangerBalanceBefore = await lido.balanceOf(anotherStranger) + treasuryBalanceBefore = await lido.balanceOf(treasury) + curatedModuleBalanceBefore = await lido.balanceOf(curatedModule.address) + } + + const checkBalanceDeltas = async ({ + totalPooledEtherDiff, + treasuryBalanceDiff, + strangerBalanceDiff, + anotherStrangerBalanceDiff, + curatedModuleBalanceDiff + }) => { + assert.equals( + await lido.getTotalPooledEther(), + toBN(totalPooledEtherBefore).add(toBN(totalPooledEtherDiff)), + 'totalPooledEther check' + ) + assert.equalsDelta( + await lido.balanceOf(treasury), + toBN(treasuryBalanceBefore).add(toBN(treasuryBalanceDiff)), + 1, + 'treasury balance check' + ) + assert.equalsDelta( + await lido.balanceOf(curatedModule.address), + toBN(curatedModuleBalanceBefore).add(toBN(curatedModuleBalanceDiff)), + 1, + 'curated module balance check' + ) + assert.equalsDelta( + await lido.balanceOf(stranger), + toBN(strangerBalanceBefore).add(toBN(strangerBalanceDiff)), + 1, + 'stranger balance check' + ) + assert.equalsDelta( + await lido.balanceOf(anotherStranger), + toBN(anotherStrangerBalanceBefore).add(toBN(anotherStrangerBalanceDiff)), + 1, + 'another stranger balance check' + ) + } + + it('handleOracleReport access control', async () => { + await assert.reverts(lido.handleOracleReport(0, 0, 0, 0, 0, 0, 0, 0, { from: stranger }), 'APP_AUTH_FAILED') + }) + + it('handleOracleReport reverts whe protocol stopped', async () => { + await lido.stop({ from: deployed.voting.address }) + await assert.reverts(lido.handleOracleReport(0, 0, 0, 0, 0, 0, 0, 0, { from: stranger }), 'CONTRACT_IS_STOPPED') + }) + + it('zero report should do nothing', async () => { + await lido.handleOracleReport(0, 0, 0, 0, 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 0, beaconValidators: 0, beaconBalance: 0 }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + }) + + describe('clBalance', () => { + beforeEach(async () => { + await await lido.deposit(3, 1, '0x', { from: depositor }) + await checkStat({ depositedValidators: 3, beaconValidators: 0, beaconBalance: 0 }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + }) + + it('first report after deposit without rewards', async () => { + await lido.handleOracleReport(0, 0, 1, ETH(32), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 1, beaconBalance: ETH(32) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + }) + + it('first report after deposit with rewards', async () => { + // elapsed time set to 1000000 because of annualBalanceIncrease limited by 10000 + await lido.handleOracleReport(0, 1000000, 1, ETH(33), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 1, beaconBalance: ETH(33) }) + + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(1), + treasuryBalanceDiff: ETH(0.05), + strangerBalanceDiff: ETH(0.3 * 0.9), + anotherStrangerBalanceDiff: ETH(0.7 * 0.9), + curatedModuleBalanceDiff: ETH(0.05) + }) + }) + }) + + describe('sanity checks', async () => { + beforeEach(async () => { + await await lido.deposit(3, 1, '0x', { from: depositor }) + }) + + it('reverts on reported more than deposited', async () => { + await assert.reverts(lido.handleOracleReport(0, 0, 4, 0, 0, 0, 0, 0, { from: oracle }), 'REPORTED_MORE_DEPOSITED') + }) + + it('reverts on reported less than reported previously', async () => { + await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await assert.reverts( + lido.handleOracleReport(0, 0, 2, 0, 0, 0, 0, 0, { from: oracle }), + 'REPORTED_LESS_VALIDATORS' + ) + }) + + it('withdrawal vault balance check', async () => { + await assert.reverts( + lido.handleOracleReport(0, 0, 0, 0, 1, 0, 0, 0, { from: oracle }), + 'IncorrectWithdrawalsVaultBalance(0)' + ) + }) + + it('withdrawal vault balance check', async () => { + await assert.reverts( + lido.handleOracleReport(0, 0, 0, 0, 1, 0, 0, 0, { from: oracle }), + 'IncorrectWithdrawalsVaultBalance(0)' + ) + }) + + it('does not revert on new total balance stay the same', async () => { + await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + }) + + it('does not revert on new total balance decrease under the limit', async () => { + // set oneOffCLBalanceDecreaseBPLimit = 1% + await oracleReportSanityChecker.setOracleReportLimits( + { + churnValidatorsPerDayLimit: 255, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 10000, + shareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 1000000000, + maxAccountingExtraDataListItemsCount: 10000 + }, + { from: voting } + ) + + await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + await lido.handleOracleReport(0, 0, 3, ETH(95.04), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(95.04) }) + + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(-0.96), + treasuryBalanceDiff: ETH(0), + strangerBalanceDiff: ETH(-30 * 0.0096), + anotherStrangerBalanceDiff: toBN(ETH(0.0096)).mul(toBN(-70)).toString(), + curatedModuleBalanceDiff: ETH(0) + }) + }) + + it('reverts on new total balance decrease over the limit', async () => { + // set oneOffCLBalanceDecreaseBPLimit = 1% + await oracleReportSanityChecker.setOracleReportLimits( + { + churnValidatorsPerDayLimit: 255, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 10000, + shareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 1000000000, + maxAccountingExtraDataListItemsCount: 10000 + }, + { from: voting } + ) + + await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + await assert.reverts( + lido.handleOracleReport(0, 0, 3, ETH(95.03), 0, 0, 0, 0, { from: oracle }), + 'IncorrectCLBalanceDecrease(101)' + ) + }) + + it('does not revert on new total balance increase under the limit', async () => { + // set annualBalanceIncreaseBPLimit = 1% + await oracleReportSanityChecker.setOracleReportLimits( + { + churnValidatorsPerDayLimit: 255, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 100, + shareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 1000000000, + maxAccountingExtraDataListItemsCount: 10000 + }, + { from: voting } + ) + + await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + await lido.handleOracleReport(0, ONE_YEAR, 3, ETH(96.96), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96.96) }) + + await checkBalanceDeltas({ + totalPooledEtherDiff: ETH(0.96), + treasuryBalanceDiff: ETH(0.96 * 0.05), + strangerBalanceDiff: ETH(30 * 0.0096 * 0.9), + anotherStrangerBalanceDiff: ETH(70 * 0.0096 * 0.9), + curatedModuleBalanceDiff: ETH(0.96 * 0.05) + }) + }) + + it('reverts on new total balance increase over the limit', async () => { + // set annualBalanceIncreaseBPLimit = 1% + await oracleReportSanityChecker.setOracleReportLimits( + { + churnValidatorsPerDayLimit: 255, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 100, + shareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 1000000000, + maxAccountingExtraDataListItemsCount: 10000 + }, + { from: voting } + ) + + await lido.handleOracleReport(0, 0, 3, ETH(96), 0, 0, 0, 0, { from: oracle }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(96) }) + await checkBalanceDeltas({ + totalPooledEtherDiff: 0, + treasuryBalanceDiff: 0, + strangerBalanceDiff: 0, + anotherStrangerBalanceDiff: 0, + curatedModuleBalanceDiff: 0 + }) + await assert.reverts( + lido.handleOracleReport(0, ONE_YEAR, 3, ETH(96.97), 0, 0, 0, 0, { from: oracle }), + 'IncorrectCLBalanceIncrease(101)' + ) + }) + + it('does not revert on validators reported under limit', async () => { + await lido.submit(ZERO_ADDRESS, { from: stranger, value: ETH(3100), gasPrice: 1 }) + await lido.deposit(100, 1, '0x', { from: depositor }) + await oracleReportSanityChecker.setOracleReportLimits( + { + churnValidatorsPerDayLimit: 100, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 100, + shareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 1000000000, + maxAccountingExtraDataListItemsCount: 10000 + }, + { from: voting, gasPrice: 1 } + ) + + await lido.handleOracleReport(0, ONE_DAY, 100, ETH(3200), 0, 0, 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 100, beaconValidators: 100, beaconBalance: ETH(3200) }) + }) + + it('reverts on validators reported when over limit', async () => { + await lido.submit(ZERO_ADDRESS, { from: stranger, value: ETH(3200), gasPrice: 1 }) + await lido.deposit(101, 1, '0x', { from: depositor }) + await oracleReportSanityChecker.setOracleReportLimits( + { + churnValidatorsPerDayLimit: 100, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 100, + shareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 1000000000, + maxAccountingExtraDataListItemsCount: 10000 + }, + { from: voting, gasPrice: 1 } + ) + await assert.reverts( + lido.handleOracleReport(0, ONE_DAY, 101, ETH(3200), 0, 0, 0, 0, { from: oracle, gasPrice: 1 }), + 'IncorrectAppearedValidators(101)' + ) + }) + + it('does not smooth if report in limits', async () => { + await oracleReportSanityChecker.setOracleReportLimits( + { + churnValidatorsPerDayLimit: 100, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 10000, + shareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 10000000, + maxAccountingExtraDataListItemsCount: 10000 + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_YEAR, 3, ETH(97), 0, 0, 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(97) }) + }) + + it('does not smooth if cl balance report over limit', async () => { + await oracleReportSanityChecker.setOracleReportLimits( + { + churnValidatorsPerDayLimit: 100, + oneOffCLBalanceDecreaseBPLimit: 100, + annualBalanceIncreaseBPLimit: 10000, + shareRateDeviationBPLimit: 10000, + maxValidatorExitRequestsPerReport: 10000, + requestTimestampMargin: 0, + maxPositiveTokenRebase: 1000000, + maxAccountingExtraDataListItemsCount: 10000 + }, + { from: voting, gasPrice: 1 } + ) + await lido.handleOracleReport(0, ONE_YEAR, 3, ETH(100), 0, 0, 0, 0, { from: oracle, gasPrice: 1 }) + await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(100) }) + }) + + // it('smooths withdrawals if report in limits', async () => { + // await ETHForwarderMock.new(deployed.withdrawalVault.address, { from: appManager, value: ETH(1) }) + + // await oracleReportSanityChecker.setOracleReportLimits( + // { + // churnValidatorsPerDayLimit: 100, + // oneOffCLBalanceDecreaseBPLimit: 100, + // annualBalanceIncreaseBPLimit: 10000, + // shareRateDeviationBPLimit: 10000, + // maxValidatorExitRequestsPerReport: 10000, + // requestTimestampMargin: 0, + // maxPositiveTokenRebase: 10000000, + // maxAccountingExtraDataListItemsCount: 10000 + // }, + // { from: voting, gasPrice: 1 } + // ) + // await lido.handleOracleReport(0, ONE_YEAR, 3, ETH(96), ETH(1), 0, 0, 0, { from: oracle, gasPrice: 1 }) + // await checkStat({ depositedValidators: 3, beaconValidators: 3, beaconBalance: ETH(97) }) + // }) + }) +}) diff --git a/test/0.4.24/lido.rewards-distribution.test.js b/test/0.4.24/lido.rewards-distribution.test.js deleted file mode 100644 index 959babeae..000000000 --- a/test/0.4.24/lido.rewards-distribution.test.js +++ /dev/null @@ -1,108 +0,0 @@ -const hre = require('hardhat') - -const { ZERO_ADDRESS, bn } = require('@aragon/contract-helpers-test') - -const { EvmSnapshot } = require('../helpers/blockchain') -const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') -const { deployProtocol } = require('../helpers/protocol') -const { assert } = require('../helpers/assert') -const { pushOracleReport } = require('../helpers/oracle') - -const ModuleSolo = artifacts.require('ModuleSolo.sol') - -const ETH = (value) => web3.utils.toWei(value + '', 'ether') -contract('Lido: staking router reward distribution', ([depositor, user2]) => { - let app, oracle, curatedModule, stakingRouter, soloModule, snapshot, appManager, consensus, treasury - - - before(async () => { - const deployed = await deployProtocol({ - stakingModulesFactory: async (protocol) => { - const curatedModule = await setupNodeOperatorsRegistry(protocol, true) - const soloModule = await ModuleSolo.new(protocol.pool.address, { from: protocol.appManager.address }) - return [ - { - module: curatedModule, - name: 'Curated', - targetShares: 10000, - moduleFee: 500, - treasuryFee: 500 - }, - { - module: soloModule, - name: 'Curated', - targetShares: 5000, - moduleFee: 566, - treasuryFee: 123 - } - ] - } - }) - - app = deployed.pool - stakingRouter = deployed.stakingRouter - curatedModule = deployed.stakingModules[0] - soloModule = deployed.stakingModules[1] - consensus = deployed.consensusContract - oracle = deployed.oracle - appManager = deployed.appManager.address - treasury = deployed.treasury.address - - await curatedModule.increaseTotalSigningKeysCount(500_000, { from: appManager }) - await curatedModule.increaseDepositedSigningKeysCount(499_950, { from: appManager }) - await curatedModule.increaseVettedSigningKeysCount(499_950, { from: appManager }) - - await soloModule.setTotalVettedValidators(100, { from: appManager }) - await soloModule.setTotalDepositedValidators(10, { from: appManager }) - await soloModule.setTotalExitedValidators(0, { from: appManager }) - - snapshot = new EvmSnapshot(hre.ethers.provider) - await snapshot.make() - }) - - afterEach(async () => { - await snapshot.rollback() - }) - - it('Rewards distribution fills treasury', async () => { - const beaconBalance = ETH(1) - const { stakingModuleFees, totalFee, precisionPoints } = await stakingRouter.getStakingRewardsDistribution() - const treasuryShare = stakingModuleFees.reduce((total, share) => total.sub(share), totalFee) - const treasuryRewards = bn(beaconBalance).mul(treasuryShare).div(precisionPoints) - await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(32) }) - - const treasuryBalanceBefore = await app.balanceOf(treasury) - await pushOracleReport(consensus, oracle, 0, beaconBalance) - - const treasuryBalanceAfter = await app.balanceOf(treasury) - assert(treasuryBalanceAfter.gt(treasuryBalanceBefore)) - assert.equals(fixRound(treasuryBalanceBefore.add(treasuryRewards)), fixRound(treasuryBalanceAfter)) - }) - - it('Rewards distribution fills modules', async () => { - const beaconBalance = ETH(1) - const { recipients, stakingModuleFees, precisionPoints } = await stakingRouter.getStakingRewardsDistribution() - - await app.submit(ZERO_ADDRESS, { from: user2, value: ETH(32) }) - - const moduleBalanceBefore = [] - for (let i = 0; i < recipients.length; i++) { - moduleBalanceBefore.push(await app.balanceOf(recipients[i])) - } - - await pushOracleReport(consensus, oracle, 0, beaconBalance) - - for (let i = 0; i < recipients.length; i++) { - const moduleBalanceAfter = await app.balanceOf(recipients[i]) - const moduleRewards = bn(beaconBalance).mul(stakingModuleFees[i]).div(precisionPoints) - assert(moduleBalanceAfter.gt(moduleBalanceBefore[i])) - assert.equals(fixRound(moduleBalanceBefore[i].add(moduleRewards)), fixRound(moduleBalanceAfter)) - } - }) -}) - -function fixRound(n) { - const _fix = bn(5) // +/- 5wei - const _base = bn(10) - return n.add(_fix).div(_base).mul(_base) -} diff --git a/test/0.4.24/lido.test.js b/test/0.4.24/lido.test.js index ab6c348d6..d220c0d17 100644 --- a/test/0.4.24/lido.test.js +++ b/test/0.4.24/lido.test.js @@ -49,7 +49,7 @@ async function getTimestamp() { return block.timestamp; } -contract('Lido', ([appManager, user1, user2, user3, nobody, depositor, treasury]) => { +contract('Lido', ([appManager, , , , , , , , , , , , user1, user2, user3, nobody, depositor, treasury]) => { let app, oracle, depositContract, operators let treasuryAddress let dao @@ -470,57 +470,6 @@ contract('Lido', ([appManager, user1, user2, user3, nobody, depositor, treasury] }) }) - // TODO: check if reverts are checked in Staking Router tests and remove this test from here - it.skip('setModulesFee works', async () => { - const [curated] = await stakingRouter.getStakingModules() - - let module1 = await stakingRouter.getStakingModule(curated.id) - assertBn(module1.targetShare, 10000) - assertBn(module1.stakingModuleFee, 500) - assertBn(module1.treasuryFee, 500) - - // stakingModuleId, targetShare, stakingModuleFee, treasuryFee - await stakingRouter.updateStakingModule(module1.id, module1.targetShare, 300, 700, { from: voting }) - - module1 = await stakingRouter.getStakingModule(curated.id) - - await assertRevert( - stakingRouter.updateStakingModule(module1.id, module1.targetShare, 300, 700, { from: user1 }), - `AccessControl: account ${user1.toLowerCase()} is missing role ${await stakingRouter.STAKING_MODULE_MANAGE_ROLE()}` - ) - - await assertRevert( - stakingRouter.updateStakingModule(module1.id, module1.targetShare, 300, 700, { from: nobody }), - `AccessControl: account ${nobody.toLowerCase()} is missing role ${await stakingRouter.STAKING_MODULE_MANAGE_ROLE()}` - ) - - await assert.revertsWithCustomError( - stakingRouter.updateStakingModule(module1.id, 10001, 300, 700, { from: voting }), - 'ErrorValueOver100Percent("_targetShare")' - ) - - await assert.revertsWithCustomError( - stakingRouter.updateStakingModule(module1.id, 10000, 10001, 700, { from: voting }), - 'ErrorValueOver100Percent("_stakingModuleFee + _treasuryFee")' - ) - - await assert.revertsWithCustomError( - stakingRouter.updateStakingModule(module1.id, 10000, 300, 10001, { from: voting }), - 'ErrorValueOver100Percent("_stakingModuleFee + _treasuryFee")' - ) - - // distribution fee calculates on active keys in modules - const depositAmount = 32 - await setupNodeOperatorsForELRewardsVaultTests(user2, ETH(depositAmount)) - - const {totalFee} = await app.getFee() - assertBn(totalFee, toPrecBP(1000)) - - const distribution = await app.getFeeDistribution({ from: nobody }) - assertBn(distribution.modulesFee, toPrecBP(300)) - assertBn(distribution.treasuryFee, toPrecBP(700)) - }) - it('setWithdrawalCredentials works', async () => { await stakingRouter.setWithdrawalCredentials(pad('0x0202', 32), { from: voting }) @@ -990,7 +939,7 @@ contract('Lido', ([appManager, user1, user2, user3, nobody, depositor, treasury] await web3.eth.sendTransaction({ to: app.address, from: user3, value: ETH(33) }) await app.methods['deposit(uint256,uint256,bytes)'](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) assertBn(await depositContract.totalCalls(), 1) - await assertRevert(operators.removeSigningKey(0, 0, { from: voting }), 'KEY_WAS_USED') + await assertRevert(operators.removeSigningKey(0, 0, { from: voting }), 'OUT_OF_RANGE') assertBn(await app.getBufferedEther(), ETH(1)) diff --git a/test/0.4.24/node-operators-registry-penalty.test.js b/test/0.4.24/node-operators-registry-penalty.test.js index 98d728bba..9e08c4e92 100644 --- a/test/0.4.24/node-operators-registry-penalty.test.js +++ b/test/0.4.24/node-operators-registry-penalty.test.js @@ -1,7 +1,7 @@ const hre = require('hardhat') const { assert } = require('../helpers/assert') const { assertRevert } = require('../helpers/assertThrow') -const { toBN, padRight, shares } = require('../helpers/utils') +const { toBN, padRight, printEvents } = require('../helpers/utils') const { BN } = require('bn.js') const { AragonDAO } = require('./helpers/dao') const { EvmSnapshot } = require('../helpers/blockchain') @@ -156,7 +156,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use ]) // calls distributeRewards() inside - await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + await app.testing__distributeRewards({ from: voting }) const recipientsSharesAfter = await Promise.all([ steth.sharesOf(user1), @@ -173,10 +173,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - await app.testing_addNodeOperator('no', no1, 10, 10, 10, 0) - // calls distributeRewards() inside - await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + await app.testing__distributeRewards({ from: voting }) assert.equals(await steth.sharesOf(user1), ETH(3)) assert.equals(await steth.sharesOf(user2), ETH(7)) @@ -187,10 +185,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - await app.testing_addNodeOperator('no', no1, 10, 10, 10, 0) - // calls distributeRewards() inside - receipt = await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + receipt = await app.testing__distributeRewards({ from: voting }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(3) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(7) }) @@ -203,8 +199,6 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, totalRewardShares) - await app.testing_addNodeOperator('no', no1, 10, 10, 10, 0) - //before // operatorId | Total | Deposited | Exited | Active (deposited-exited) // 0 3 3 0 3 @@ -216,8 +210,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use //perValidatorShare 10*10^18 / 10 = 10^18 //update [operator, exited, stuck] - await app.unsafeUpdateValidatorsKeysCount(firstNodeOperator, 1, 0 , { from: voting }) - await app.unsafeUpdateValidatorsKeysCount(secondNodeOperator, 1, 0 , { from: voting }) + await app.unsafeUpdateValidatorsCount(firstNodeOperator, 1, 0 , { from: voting }) + await app.unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0 , { from: voting }) //after // operatorId | Total | Deposited | Exited | Stuck | Active (deposited-exited) @@ -230,7 +224,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use //perValidatorShare 10*10^18 / 8 = 1250000000000000000 == 1.25 * 10^18 // calls distributeRewards() inside - receipt = await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + receipt = await app.testing__distributeRewards({ from: voting }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(2*1.25) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(6*1.25) }) @@ -244,8 +238,6 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, totalRewardShares) - await app.testing_addNodeOperator('no', no1, 10, 10, 10, 0) - //before // operatorId | Total | Deposited | Exited | Active (deposited-exited) // 0 3 3 0 3 @@ -257,8 +249,8 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use //perValidatorShare 10*10^18 / 10 = 10^18 //update [operator, exited, stuck] - await app.unsafeUpdateValidatorsKeysCount(firstNodeOperator, 1, 1 , { from: voting }) - await app.unsafeUpdateValidatorsKeysCount(secondNodeOperator, 1, 0 , { from: voting }) + await app.unsafeUpdateValidatorsCount(firstNodeOperator, 1, 1 , { from: voting }) + await app.unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0 , { from: voting }) //after // operatorId | Total | Deposited | Exited | Stuck | Active (deposited-exited) @@ -272,7 +264,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use // but half goes to burner // calls distributeRewards() inside - receipt = await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + receipt = await app.testing__distributeRewards({ from: voting }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(1.25) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(6*1.25) }) @@ -284,16 +276,14 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - await app.testing_addNodeOperator('no', no1, 10, 10, 10, 0) - //update [operator, exited, stuck] - await app.unsafeUpdateValidatorsKeysCount(firstNodeOperator, 1, 1 , { from: voting }) - await app.unsafeUpdateValidatorsKeysCount(secondNodeOperator, 1, 0 , { from: voting }) + await app.unsafeUpdateValidatorsCount(firstNodeOperator, 1, 1 , { from: voting }) + await app.unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0 , { from: voting }) - await app.updateRefundedValidatorsKeysCount(firstNodeOperator, 1, { from: voting }) + await app.updateRefundedValidatorsCount(firstNodeOperator, 1, { from: voting }) // calls distributeRewards() inside - receipt = await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + receipt = await app.testing__distributeRewards({ from: voting }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(1.25) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(6*1.25) }) @@ -305,16 +295,16 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - await app.testing_addNodeOperator('no', no1, 10, 10, 10, 0) + //update [operator, exited, stuck] - await app.unsafeUpdateValidatorsKeysCount(firstNodeOperator, 1, 1, { from: voting }) - await app.unsafeUpdateValidatorsKeysCount(secondNodeOperator, 1, 0, { from: voting }) + await app.unsafeUpdateValidatorsCount(firstNodeOperator, 1, 1, { from: voting }) + await app.unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0, { from: voting }) - await app.updateRefundedValidatorsKeysCount(firstNodeOperator, 1000, { from: voting }) + await app.updateRefundedValidatorsCount(firstNodeOperator, 1000, { from: voting }) // calls distributeRewards() inside - receipt = await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + receipt = await app.testing__distributeRewards({ from: voting }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(1.25) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(6*1.25) }) @@ -326,18 +316,18 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - await app.testing_addNodeOperator('no', no1, 10, 10, 10, 0) + //update [operator, exited, stuck] - await app.unsafeUpdateValidatorsKeysCount(firstNodeOperator, 2, 2 , { from: voting }) - await app.unsafeUpdateValidatorsKeysCount(secondNodeOperator, 3, 0 , { from: voting }) + await app.unsafeUpdateValidatorsCount(firstNodeOperator, 2, 2 , { from: voting }) + await app.unsafeUpdateValidatorsCount(secondNodeOperator, 3, 0 , { from: voting }) // perValidator = ETH(10) / 5 = 2 eth - await app.updateRefundedValidatorsKeysCount(firstNodeOperator, 1, { from: voting }) + await app.updateRefundedValidatorsCount(firstNodeOperator, 1, { from: voting }) // calls distributeRewards() inside - receipt = await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + receipt = await app.testing__distributeRewards({ from: voting }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(1) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(4*2) }) @@ -349,16 +339,16 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - await app.testing_addNodeOperator('no', no1, 10, 10, 10, 0) + assert.isFalse(await app.testing_isNodeOperatorPenalized(firstNodeOperator)) //update [operator, exited, stuck] - await app.unsafeUpdateValidatorsKeysCount(firstNodeOperator, 1, 1 , { from: voting }) - await app.unsafeUpdateValidatorsKeysCount(secondNodeOperator, 1, 0 , { from: voting }) + await app.unsafeUpdateValidatorsCount(firstNodeOperator, 1, 1 , { from: voting }) + await app.unsafeUpdateValidatorsCount(secondNodeOperator, 1, 0 , { from: voting }) assert.isTrue(await app.testing_isNodeOperatorPenalized(firstNodeOperator)) - await app.updateRefundedValidatorsKeysCount(firstNodeOperator, 1, { from: voting }) + await app.updateRefundedValidatorsCount(firstNodeOperator, 1, { from: voting }) assert.isTrue(await app.testing_isNodeOperatorPenalized(firstNodeOperator)) await hre.network.provider.send('evm_increaseTime', [2 * 24 * 60 * 60 + 10]) @@ -367,7 +357,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use assert.isFalse(await app.testing_isNodeOperatorPenalized(firstNodeOperator)) // calls distributeRewards() inside - receipt = await app.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + receipt = await app.testing__distributeRewards({ from: voting }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user1, sharesAmount: ETH(2.5) }) assert.emits(receipt, 'RewardsDistributed', { rewardAddress: user2, sharesAmount: ETH(7.5) }) @@ -386,7 +376,7 @@ contract('NodeOperatorsRegistry', ([appManager, voting, user1, user2, user3, use await steth.setTotalPooledEther(ETH(100)) await steth.mintShares(app.address, ETH(10)) - await app.testing_addNodeOperator('no', no1, 55, 30, 15, 2) + // await app.testing_addNodeOperator('no', no1, 55, 30, 15, 2) // sum of (vetted[i] - exited[i]) await app.increaseTargetValidatorsCount(28) diff --git a/test/0.4.24/steth.test.js b/test/0.4.24/steth.test.js index e3e3bf026..e0382776e 100644 --- a/test/0.4.24/steth.test.js +++ b/test/0.4.24/steth.test.js @@ -286,10 +286,7 @@ contract('StETH', ([_, __, user1, user2, user3, nobody]) => { assert(await stEth.isStopped()) await assertRevert(stEth.transfer(user2, tokens(2), { from: user1 }), 'CONTRACT_IS_STOPPED') - await assertRevert(stEth.approve(user2, tokens(2), { from: user1 }), 'CONTRACT_IS_STOPPED') await assertRevert(stEth.transferFrom(user2, user3, tokens(2), { from: user1 }), 'CONTRACT_IS_STOPPED') - await assertRevert(stEth.increaseAllowance(user2, tokens(2), { from: user1 }), 'CONTRACT_IS_STOPPED') - await assertRevert(stEth.decreaseAllowance(user2, tokens(2), { from: user1 }), 'CONTRACT_IS_STOPPED') await stEth.resume({ from: user1 }) assert.equal(await stEth.isStopped(), false) diff --git a/test/0.8.9/oracle/hash-consensus-frames.js b/test/0.8.9/oracle/hash-consensus-frames.js index a7347f137..ec788d6e0 100644 --- a/test/0.8.9/oracle/hash-consensus-frames.js +++ b/test/0.8.9/oracle/hash-consensus-frames.js @@ -3,7 +3,7 @@ const { assertEvent } = require('@aragon/contract-helpers-test/src/asserts') const { assertRevert } = require('../../helpers/assertThrow') const { - INITIAL_FAST_LANE_LENGHT_SLOTS, + INITIAL_FAST_LANE_LENGTH_SLOTS, INITIAL_EPOCH, EPOCHS_PER_FRAME, SLOTS_PER_EPOCH, @@ -43,11 +43,10 @@ contract('HashConsensus', ([admin, member1, member2]) => { context('getFrameConfig', () => { before(deploy) - it('should return initial data', async () => { assert.equal(+(await consensus.getFrameConfig()).epochsPerFrame, EPOCHS_PER_FRAME) assert.equal(+(await consensus.getFrameConfig()).initialEpoch, INITIAL_EPOCH) - assert.equal(+(await consensus.getFrameConfig()).fastLaneLengthSlots, INITIAL_FAST_LANE_LENGHT_SLOTS) + assert.equal(+(await consensus.getFrameConfig()).fastLaneLengthSlots, INITIAL_FAST_LANE_LENGTH_SLOTS) }) it('should return new data', async () => { diff --git a/test/deposit.test.js b/test/deposit.test.js index e9b667f83..c5bbbc078 100644 --- a/test/deposit.test.js +++ b/test/deposit.test.js @@ -31,6 +31,13 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d const deployed = await deployProtocol({ stakingModulesFactory: async (protocol) => { const curatedModule = await setupNodeOperatorsRegistry(protocol) + + await protocol.acl.grantPermission( + protocol.stakingRouter.address, + curatedModule.address, + await curatedModule.MANAGE_NODE_OPERATOR_ROLE() + ) + return [ { module: curatedModule, @@ -58,7 +65,6 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d operators = deployed.stakingModules[0] voting = deployed.voting.address depositContract = deployed.depositContract - voting = deployed.voting.address snapshot = new EvmSnapshot(hre.ethers.provider) await snapshot.make() @@ -168,7 +174,7 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) assertBn(bn(changeEndianness(await depositContract.get_deposit_count())), 1) - await assert.reverts(operators.removeSigningKey(0, 0, { from: voting }), 'KEY_WAS_USED') + await assert.reverts(operators.removeSigningKey(0, 0, { from: voting }), 'OUT_OF_RANGE') await operators.removeSigningKey(0, 1, { from: voting }) @@ -176,8 +182,8 @@ contract('Lido with official deposit contract', ([user1, user2, user3, nobody, d await app.methods[`deposit(uint256,uint256,bytes)`](MAX_DEPOSITS, CURATED_MODULE_ID, CALLDATA, { from: depositor }) // deposit should go to second operator, as the first one got their key limits set to 1 - await assert.reverts(operators.removeSigningKey(1, 0, { from: voting }), 'KEY_WAS_USED') - await assert.reverts(operators.removeSigningKey(1, 1, { from: voting }), 'KEY_WAS_USED') + await assert.reverts(operators.removeSigningKey(1, 0, { from: voting }), 'OUT_OF_RANGE') + await assert.reverts(operators.removeSigningKey(1, 1, { from: voting }), 'OUT_OF_RANGE') assertBn(bn(changeEndianness(await depositContract.get_deposit_count())), 4) assertBn(await app.getTotalPooledEther(), ETH(133)) assertBn(await app.getBufferedEther(), ETH(5)) diff --git a/test/helpers/locator-deploy.js b/test/helpers/locator-deploy.js index 7f7ba28d7..c54ff12ce 100644 --- a/test/helpers/locator-deploy.js +++ b/test/helpers/locator-deploy.js @@ -1,3 +1,5 @@ +const LidoLocator = artifacts.require('LidoLocator') + const DUMMY_ADDRESS = '0x' + 'f'.repeat(40) const invalidButNonZeroLocatorConfig = { @@ -38,7 +40,7 @@ async function updateProxyImplementation(proxyAddress, artifactName, proxyOwner, } async function getLocatorConfig(locatorAddress) { - const LidoLocator = await artifacts.require('LidoLocator') + const locator = await LidoLocator.at(locatorAddress) const config = { accountingOracle: await locator.accountingOracle(), @@ -63,7 +65,8 @@ async function deployLocatorWithInvalidImplementation(admin) { } async function deployLocatorWithDummyAddressesImplementation(admin) { - return await deployBehindOssifiableProxy('LidoLocator', admin, [invalidButNonZeroLocatorConfig]) + const proxy = await deployBehindOssifiableProxy('LidoLocator', admin, [invalidButNonZeroLocatorConfig]) + return await LidoLocator.at(proxy.address) } ///! Not specified in configUpdate values are set to dummy non zero addresses diff --git a/test/helpers/oracle.js b/test/helpers/oracle.js index 845e4c260..45c839165 100644 --- a/test/helpers/oracle.js +++ b/test/helpers/oracle.js @@ -53,7 +53,7 @@ async function pushOracleReport(consensus, oracle, numValidators, clBalance, elR lastWithdrawalRequestIdToFinalize: 0, finalizationShareRate: 0, isBunkerMode: false, - extraDataFormat: 0, + extraDataFormat: 1, extraDataHash: ZERO_BYTES32, extraDataItemsCount: 0 } @@ -66,7 +66,7 @@ async function pushOracleReport(consensus, oracle, numValidators, clBalance, elR const oracleVersion = await oracle.getContractVersion() - await oracle.submitReportData(reportItems, oracleVersion, { from: members.addresses[0] }) + return await oracle.submitReportData(reportItems, oracleVersion, { from: members.addresses[0] }) } module.exports = { getReportDataItems, calcReportDataHash, pushOracleReport } diff --git a/test/helpers/protocol.js b/test/helpers/protocol.js index ae0f908c0..68e1c2879 100644 --- a/test/helpers/protocol.js +++ b/test/helpers/protocol.js @@ -28,6 +28,13 @@ async function deployProtocol(factories = {}, deployParams = {}) { protocol.depositContract = await protocol.factories.depositContractFactory(protocol) + protocol.burner = await protocol.factories.burnerFactory(protocol) + protocol.lidoLocator = await protocol.factories.lidoLocatorFactory(protocol) + await updateLocatorImplementation(protocol.lidoLocator.address, protocol.appManager.address, { + lido: protocol.pool.address, + burner: protocol.burner.address + }) + protocol.withdrawalCredentials = await protocol.factories.withdrawalCredentialsFactory(protocol) protocol.stakingRouter = await protocol.factories.stakingRouterFactory(protocol) protocol.stakingModules = await addStakingModules(protocol.factories.stakingModulesFactory, protocol) @@ -36,16 +43,11 @@ async function deployProtocol(factories = {}, deployParams = {}) { protocol.elRewardsVault = await protocol.factories.elRewardsVaultFactory(protocol) protocol.withdrawalVault = await protocol.factories.withdrawalVaultFactory(protocol) protocol.eip712StETH = await protocol.factories.eip712StETHFactory(protocol) - protocol.burner = await protocol.factories.burnerFactory(protocol) - - protocol.lidoLocator = await protocol.factories.lidoLocatorFactory(protocol) await updateLocatorImplementation(protocol.lidoLocator.address, protocol.appManager.address, { - lido: protocol.pool.address, depositSecurityModule: protocol.depositSecurityModule.address, elRewardsVault: protocol.elRewardsVault.address, legacyOracle: protocol.legacyOracle.address, - burner: protocol.burner.address, stakingRouter: protocol.stakingRouter.address, treasury: protocol.treasury.address, withdrawalVault: protocol.withdrawalVault.address, diff --git a/test/helpers/staking-modules.js b/test/helpers/staking-modules.js index 0c90f68f2..85e08e404 100644 --- a/test/helpers/staking-modules.js +++ b/test/helpers/staking-modules.js @@ -2,7 +2,7 @@ const { newApp } = require('./dao') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') const NodeOperatorsRegistryMock = artifacts.require('NodeOperatorsRegistryMock') -async function setupNodeOperatorsRegistry({ dao, acl, token, stakingRouter, voting, appManager }, mock = false) { +async function setupNodeOperatorsRegistry({ dao, acl, lidoLocator, stakingRouter, voting, appManager }, mock = false) { const nodeOperatorsRegistryBase = mock ? await NodeOperatorsRegistryMock.new() : await NodeOperatorsRegistry.new() const name = 'node-operators-registry-' + Math.random().toString(36).slice(2, 6) const nodeOperatorsRegistryProxyAddress = await newApp( @@ -16,7 +16,7 @@ async function setupNodeOperatorsRegistry({ dao, acl, token, stakingRouter, voti ? await NodeOperatorsRegistryMock.at(nodeOperatorsRegistryProxyAddress) : await NodeOperatorsRegistry.at(nodeOperatorsRegistryProxyAddress) - await nodeOperatorsRegistry.initialize(token.address, '0x01') + await nodeOperatorsRegistry.initialize(lidoLocator.address, '0x01') const [ NODE_OPERATOR_REGISTRY_MANAGE_SIGNING_KEYS, diff --git a/test/scenario/changing_oracles_during_epoch.js b/test/scenario/changing_oracles_during_epoch.js index 661876c8d..d041a9ba6 100644 --- a/test/scenario/changing_oracles_during_epoch.js +++ b/test/scenario/changing_oracles_during_epoch.js @@ -38,10 +38,9 @@ contract('AccountingOracle', ([appManager, voting, malicious1, malicious2, membe lastWithdrawalRequestIdToFinalize: 0, finalizationShareRate: 0, isBunkerMode: false, - extraDataFormat: 0, + extraDataFormat: 1, extraDataHash: ZERO_HASH, - extraDataItemsCount: 0, - extraDataMaxNodeOpsCountByModule: 0, + extraDataItemsCount: 0 } const BAD_DATA = { diff --git a/test/scenario/deposit_distribution.js b/test/scenario/deposit_distribution.js index 6ab7314c3..7315fae02 100644 --- a/test/scenario/deposit_distribution.js +++ b/test/scenario/deposit_distribution.js @@ -75,7 +75,7 @@ contract('StakingRouter', ([depositor, stranger1, dsm, address1, address2]) => { await web3.eth.sendTransaction({ value: sendEthForKeys, to: lido.address, from: stranger1 }) assert.equals(await lido.getBufferedEther(), sendEthForKeys) - const keysAllocation = await stakingRouter.getKeysAllocation(200) + const keysAllocation = await stakingRouter.getDepositsAllocation(200) assert.equals(keysAllocation.allocated, 200) assert.equals(keysAllocation.allocations, [100, 100]) diff --git a/test/scenario/lido_happy_path.js b/test/scenario/lido_happy_path.js index c330261f0..e112cd687 100644 --- a/test/scenario/lido_happy_path.js +++ b/test/scenario/lido_happy_path.js @@ -389,51 +389,57 @@ contract('Lido: happy path', (addresses) => { const oldTotalPooledEther = await pool.getTotalPooledEther() assertBn(oldTotalPooledEther, ETH(33 + 64), 'total pooled ether') - // Reporting 1.5-fold balance increase (64 => 96) + // Reporting 1.005-fold balance increase (64 => 64.32) to stay in limits - pushOracleReport(consensus, oracle, 2, ETH(96)) + await pushOracleReport(consensus, oracle, 2, ETH(64.32)) // Total shares increased because fee minted (fee shares added) - // shares ~= oldTotalShares + reward * oldTotalShares / (newTotalPooledEther - reward) + // shares = oldTotalShares + reward * totalFee * oldTotalShares / (newTotalPooledEther - reward * totalFee) const newTotalShares = await token.getTotalShares() - assertBn(newTotalShares, new BN('99467408585055643879'), 'total shares') + assertBn(newTotalShares, '97031905270948112819', 'total shares') // Total pooled Ether increased const newTotalPooledEther = await pool.getTotalPooledEther() - assertBn(newTotalPooledEther, ETH(33 + 96), 'total pooled ether') + assertBn(newTotalPooledEther, ETH(33 + 64.32), 'total pooled ether') // Ether2 stat reported by the pool changed correspondingly const ether2Stat = await pool.getBeaconStat() assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') - assertBn(ether2Stat.beaconBalance, ETH(96), 'remote ether2') + assertBn(ether2Stat.beaconBalance, ETH(64.32), 'remote ether2') // Buffered Ether amount didn't change assertBn(await pool.getBufferedEther(), ETH(33), 'buffered ether') // New tokens was minted to distribute fee - assertBn(await token.totalSupply(), tokens(129), 'token total supply') + assertBn(await token.totalSupply(), tokens(97.32), 'token total supply') - const reward = toBN(ETH(96 - 64)) + const reward = toBN(ETH(64.32 - 64)) const mintedAmount = new BN(totalFeePoints).mul(reward).divn(10000) // Token user balances increased - assertBn(await token.balanceOf(user1), new BN('3890721649484536082'), 'user1 tokens') - assertBn(await token.balanceOf(user2), new BN('38907216494845360824'), 'user2 tokens') - assertBn(await token.balanceOf(user3), new BN('83002061855670103092'), 'user3 tokens') + + assertBn(await token.balanceOf(user1), new BN('3008907216494845360'), 'user1 tokens') + assertBn(await token.balanceOf(user2), new BN('30089072164948453608'), 'user2 tokens') + assertBn(await token.balanceOf(user3), new BN('64190020618556701031'), 'user3 tokens') // Fee, in the form of minted tokens, was distributed between treasury, insurance fund // and node operators // treasuryTokenBalance ~= mintedAmount * treasuryFeePoints / 10000 // insuranceTokenBalance ~= mintedAmount * insuranceFeePoints / 10000 - assertBn(await token.balanceOf(treasuryAddr), new BN('1600000000000000000'), 'treasury tokens') - assertBn(await token.balanceOf(nodeOperatorsRegistry.address), new BN('1599999999999999999'), 'insurance tokens') + assert.equalsDelta(await token.balanceOf(treasuryAddr), new BN('16000000000000000'), 1, 'treasury tokens') + assert.equalsDelta( + await token.balanceOf(nodeOperatorsRegistry.address), + new BN('16000000000000000'), + 1, + 'insurance tokens' + ) // The node operators' fee is distributed between all active node operators, - // proprotional to their effective stake (the amount of Ether staked by the operator's + // proportional to their effective stake (the amount of Ether staked by the operator's // used and non-stopped validators). // // In our case, both node operators received the same fee since they have the same diff --git a/test/scenario/lido_penalties_slashing.js b/test/scenario/lido_penalties_slashing.js index 8abe83a1d..ffb5b6b0b 100644 --- a/test/scenario/lido_penalties_slashing.js +++ b/test/scenario/lido_penalties_slashing.js @@ -1,17 +1,17 @@ -const { assert } = require('chai') +const hre = require('hardhat') const { BN } = require('bn.js') const { assertBn } = require('@aragon/contract-helpers-test/src/asserts') const { getEventArgument } = require('@aragon/contract-helpers-test') -const { assertRevert } = require('../helpers/assertThrow') -const { pad, ETH, tokens } = require('../helpers/utils') -const { signDepositData } = require('../helpers/signatures') +const { assert } = require('../helpers/assert') +const { pad, ETH, tokens, toBN } = require('../helpers/utils') const { waitBlocks } = require('../helpers/blockchain') const { deployProtocol } = require('../helpers/protocol') const { setupNodeOperatorsRegistry } = require('../helpers/staking-modules') const { SLOTS_PER_FRAME, SECONDS_PER_FRAME } = require('../helpers/constants') const { pushOracleReport } = require('../helpers/oracle') const { oracleReportSanityCheckerStubFactory } = require('../helpers/factories') +const { DSMAttestMessage, DSMPauseMessage, signDepositData } = require('../helpers/signatures') const NodeOperatorsRegistry = artifacts.require('NodeOperatorsRegistry') @@ -445,9 +445,9 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const lossReported = ETH(1) awaitingUser1Balance = awaitingUser1Balance.sub(new BN(lossReported)) - // Reporting 1 ETH balance loss (61 => 60) + // Reporting 1 ETH balance loss ( total pooled 61 => 60) - await pushReport(1, ETH(60)) + await pushReport(1, ETH(28)) // Total shares stay the same because no fee shares are added @@ -463,7 +463,7 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const ether2Stat = await pool.getBeaconStat() assertBn(ether2Stat.depositedValidators, 2, 'deposited ether2') - assertBn(ether2Stat.beaconBalance, ETH(60), 'remote ether2') + assertBn(ether2Stat.beaconBalance, ETH(28), 'remote ether2') // Buffered Ether amount didn't change @@ -483,7 +483,7 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { }) it(`the oracle can't report less validators than previously`, () => { - assertRevert(pushReport(2, ETH(31))) + assert.reverts(pushReport(2, ETH(31))) }) it(`user deposits another 32 ETH to the pool`, async () => { @@ -491,34 +491,33 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { const depositAmount = ETH(32) awaitingTotalShares = awaitingTotalShares.add(new BN(depositAmount).mul(awaitingTotalShares).div(totalPooledEther)) awaitingUser1Balance = awaitingUser1Balance.add(new BN(depositAmount)) - await web3.eth.sendTransaction({ to: pool.address, from: user1, value: depositAmount }) - const block = await waitBlocks(await depositSecurityModule.getMinDepositBlockDistance()) + + await hre.network.provider.send("hardhat_mine", ['0x100']) + + const block = await web3.eth.getBlock('latest') const keysOpIndex = await nodeOperatorsRegistry.getKeysOpIndex() + + DSMAttestMessage.setMessagePrefix(await depositSecurityModule.ATTEST_MESSAGE_PREFIX()) + DSMPauseMessage.setMessagePrefix(await depositSecurityModule.PAUSE_MESSAGE_PREFIX()) + + const validAttestMessage = new DSMAttestMessage(block.number, block.hash, depositRoot, 1, keysOpIndex) + const signatures = [ - signDepositData( - await depositSecurityModule.ATTEST_MESSAGE_PREFIX(), - block.number, - block.hash, - depositRoot, - 1, - keysOpIndex, - '0x00', - guardians.privateKeys[guardians.addresses[0]] - ), - signDepositData( - await depositSecurityModule.ATTEST_MESSAGE_PREFIX(), - block.number, - block.hash, - depositRoot, - 1, - keysOpIndex, - '0x00', - guardians.privateKeys[guardians.addresses[1]] - ) + validAttestMessage.sign(guardians.privateKeys[guardians.addresses[0]]), + validAttestMessage.sign(guardians.privateKeys[guardians.addresses[1]]) ] - await depositSecurityModule.depositBufferedEther(block.number, block.hash, depositRoot, 1, keysOpIndex, '0x00', signatures) + await depositSecurityModule.depositBufferedEther( + block.number, + block.hash, + depositRoot, + 1, + keysOpIndex, + '0x', + signatures + ) + // TODO: check getBeaconStat call const ether2Stat = await pool.getBeaconStat() assertBn(ether2Stat.depositedValidators, 2, 'no validators have received the current deposit') @@ -604,7 +603,7 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { assertBn(nodeOperatorsRegistrySharesAfter, nodeOperatorsRegistrySharesBefore, `NOR stakingModule hasn't got fees`) const tenKBN = new BN(10000) - const totalFeeToDistribute = new BN(ETH(beaconBalanceIncrement)).mul(new BN(totalFeePoints)).div(tenKBN) + const totalFeeToDistribute = new BN(beaconBalanceIncrement.toString()).mul(new BN(totalFeePoints)).div(tenKBN) const totalPooledEther = await pool.getTotalPooledEther() let sharesToMint = totalFeeToDistribute @@ -642,7 +641,7 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { await pushReport(2, ETH(96)) // kicks rewards distribution - await nodeOperatorsRegistry.finishUpdatingExitedValidatorsKeysCount({ from: voting }) + await nodeOperatorsRegistry.updateExitedValidatorsCount(0, 1, { from: voting }) const nodeOperator1TokenSharesAfter = await token.sharesOf(nodeOperator1.address) const nodeOperator2TokenSharesAfter = await token.sharesOf(nodeOperator2.address) @@ -658,5 +657,4 @@ contract('Lido: penalties, slashing, operator stops', (addresses) => { `second node operator gained shares under fee distribution` ) }) - }) diff --git a/test/scenario/lido_rewards_distribution_math.js b/test/scenario/lido_rewards_distribution_math.js index 0d29ca8c6..c226b8ede 100644 --- a/test/scenario/lido_rewards_distribution_math.js +++ b/test/scenario/lido_rewards_distribution_math.js @@ -76,11 +76,11 @@ contract('Lido: rewards distribution math', (addresses) => { } async function reportBeacon(validatorsCount, balance) { + const tx = await pushOracleReport(consensus, oracle, validatorsCount, balance) await ethers.provider.send('evm_increaseTime', [SECONDS_PER_FRAME + 1000]) await ethers.provider.send('evm_mine') - await pushOracleReport(consensus, oracle, validatorsCount, balance) - await ethers.provider.send('evm_increaseTime', [SECONDS_PER_FRAME + 1000]) - await ethers.provider.send('evm_mine') + + return tx } before(async () => { @@ -127,8 +127,6 @@ contract('Lido: rewards distribution math', (addresses) => { const withdrawalCredentials = pad('0x0202', 32) - console.log(voting) - await stakingRouter.setWithdrawalCredentials(withdrawalCredentials, { from: voting }) }) @@ -196,7 +194,10 @@ contract('Lido: rewards distribution math', (addresses) => { it(`the first deposit gets deployed`, async () => { const [curated] = await stakingRouter.getStakingModules() - const block = await web3.eth.getBlock('latest') + await ethers.provider.send('evm_increaseTime', [SECONDS_PER_FRAME *2]) + await ethers.provider.send('evm_mine') + const block = await ethers.provider.getBlock('latest') + const keysOpIndex = await nodeOperatorsRegistry.getKeysOpIndex() DSMAttestMessage.setMessagePrefix(await depositSecurityModule.ATTEST_MESSAGE_PREFIX()) @@ -244,15 +245,19 @@ contract('Lido: rewards distribution math', (addresses) => { const nodeOperator1TokenBefore = await token.balanceOf(operator_1) // for some reason there's nothing in this receipt's log, so we're not going to use it - const [{ receipt }, deltas] = await getSharesTokenDeltas( - () => reportBeacon(1, reportingValue), - treasuryAddr, - nodeOperatorsRegistry.address - ) - const [treasuryTokenDelta, treasurySharesDelta, nodeOperatorsRegistryTokenDelta, nodeOperatorsRegistrySharesDelta] = - deltas + const treasuryBalanceBefore = await pool.balanceOf(treasuryAddr) + const nodeOperatorsRegistryBalanceBefore = await pool.balanceOf(nodeOperatorsRegistry.address) + const treasurySharesBefore = await pool.sharesOf(treasuryAddr) + const nodeOperatorsRegistrySharesBefore = await pool.sharesOf(nodeOperatorsRegistry.address) - const { reportedMintAmount, tos, values } = await readLastPoolEventLog() + const receipt = await reportBeacon(1, reportingValue) + + const treasuryTokenDelta = (await pool.balanceOf(treasuryAddr)) - treasuryBalanceBefore + const treasurySharesDelta = (await pool.sharesOf(treasuryAddr)) - treasurySharesBefore + const nodeOperatorsRegistryTokenDelta = + (await pool.balanceOf(nodeOperatorsRegistry.address)) - nodeOperatorsRegistryBalanceBefore + const nodeOperatorsRegistrySharesDelta = + (await pool.sharesOf(nodeOperatorsRegistry.address)) - nodeOperatorsRegistrySharesBefore const awaitedDeltas = await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 1) const { @@ -263,15 +268,28 @@ contract('Lido: rewards distribution math', (addresses) => { treasuryFeeToMint } = awaitedDeltas - assertBn(nodeOperatorsRegistrySharesDelta, nodeOperatorsSharesToMint, 'nodeOperator1 shares are correct') - assertBn(treasurySharesDelta, treasurySharesToMint, 'treasury shares are correct') + assert.equals(nodeOperatorsRegistrySharesDelta, nodeOperatorsSharesToMint, 'nodeOperator1 shares are correct') + assert.equals(treasurySharesDelta, treasurySharesToMint, 'treasury shares are correct') + assert.equalsDelta(nodeOperatorsFeeToMint, treasuryTokenDelta, 1, 'reported the expected total fee') + assert.equalsDelta(nodeOperatorsRegistryTokenDelta, nodeOperatorsFeeToMint, 1, 'treasury shares are correct') - assertBn(treasuryFeeToMint.add(nodeOperatorsFeeToMint), reportedMintAmount, 'reported the expected total fee') + assert.emits(receipt, 'Transfer', { + to: nodeOperatorsRegistry.address, + value: nodeOperatorsFeeToMint + }) + assert.emits(receipt, 'Transfer', { + to: treasuryAddr, + value: nodeOperatorsFeeToMint + }) + assert.emits(receipt, 'TransferShares', { + to: nodeOperatorsRegistry.address, + sharesValue: nodeOperatorsSharesToMint + }) + assert.emits(receipt, 'TransferShares', { + to: treasuryAddr, + sharesValue: treasurySharesToMint + }) - assert.equal(tos[0], nodeOperatorsRegistry.address, 'second transfer to node operator') - assertBn(values[0], nodeOperatorsFeeToMint, 'operator transfer amount is correct') - assert.equal(tos[1], treasuryAddr, 'third transfer to treasury address') - assertBn(values[1], treasuryFeeToMint, 'treasury transfer amount is correct') assertBn( await token.balanceOf(user1), // 32 staked 2 buffered 1 profit @@ -435,37 +453,49 @@ contract('Lido: rewards distribution math', (addresses) => { const reportingValue = ETH(32 + 1 + 32 + profitAmountEth) const prevTotalShares = await pool.getTotalShares() - const [{ valuesBefore, valuesAfter }, deltas] = await getSharesTokenDeltas( - () => reportBeacon(2, reportingValue), - treasuryAddr, - user1, - user2 - ) - - const [ - treasuryTokenDelta, - treasurySharesDelta, - user1TokenDelta, - user1SharesDelta, - user2TokenDelta, - user2SharesDelta - ] = deltas - const { reportedMintAmount, tos, values } = await readLastPoolEventLog() + const treasuryBalanceBefore = await pool.balanceOf(treasuryAddr) + const nodeOperatorsRegistryBalanceBefore = await pool.balanceOf(nodeOperatorsRegistry.address) + const treasurySharesBefore = await pool.sharesOf(treasuryAddr) + const nodeOperatorsRegistrySharesBefore = await pool.sharesOf(nodeOperatorsRegistry.address) - const { sharesToMint, nodeOperatorsSharesToMint, treasurySharesToMint, nodeOperatorsFeeToMint, treasuryFeeToMint } = - await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 2) + const receipt = await reportBeacon(2, reportingValue) + const treasuryTokenDelta = (await pool.balanceOf(treasuryAddr)) - treasuryBalanceBefore + const treasurySharesDelta = (await pool.sharesOf(treasuryAddr)) - treasurySharesBefore + const nodeOperatorsRegistryTokenDelta = + (await pool.balanceOf(nodeOperatorsRegistry.address)) - nodeOperatorsRegistryBalanceBefore + const nodeOperatorsRegistrySharesDelta = + (await pool.sharesOf(nodeOperatorsRegistry.address)) - nodeOperatorsRegistrySharesBefore - // events are ok - assert.equal(tos[0], nodeOperatorsRegistry.address, 'second transfer to staking router curated module') - assert.equal(tos[1], treasuryAddr, 'third transfer to treasury address') + const awaitedDeltas = await getAwaitedFeesSharesTokensDeltas(profitAmount, prevTotalShares, 2) + const { + totalFeeToDistribute, + nodeOperatorsSharesToMint, + treasurySharesToMint, + nodeOperatorsFeeToMint, + treasuryFeeToMint + } = awaitedDeltas - // TODO merge: 1 wei - // assertBn(values[0], nodeOperatorsFeeToMint, 'operator transfer amount is correct') - assertBn(values[1], treasuryFeeToMint, 'treasury transfer amount is correct') + assert.equals(nodeOperatorsRegistrySharesDelta, nodeOperatorsSharesToMint, 'nodeOperator1 shares are correct') + assert.equals(treasurySharesDelta, treasurySharesToMint, 'treasury shares are correct') + assert.equalsDelta(nodeOperatorsFeeToMint, treasuryTokenDelta, 1, 'reported the expected total fee') + assert.equalsDelta(nodeOperatorsRegistryTokenDelta, nodeOperatorsFeeToMint, 1, 'treasury shares are correct') - // TODO merge: 1 wei - // const totalFeeToMint = nodeOperatorsFeeToMint.add(treasuryFeeToMint) - // assertBn(totalFeeToMint, reportedMintAmount, 'reported the expected total fee') + assert.emits(receipt, 'Transfer', { + to: nodeOperatorsRegistry.address, + value: nodeOperatorsFeeToMint + }) + assert.emits(receipt, 'Transfer', { + to: treasuryAddr, + value: nodeOperatorsFeeToMint + }) + assert.emits(receipt, 'TransferShares', { + to: nodeOperatorsRegistry.address, + sharesValue: nodeOperatorsSharesToMint + }) + assert.emits(receipt, 'TransferShares', { + to: treasuryAddr, + sharesValue: treasurySharesToMint + }) // TODO merge: 1 wei // assertBn(await token.balanceOf(nodeOperatorsRegistry.address), nodeOperatorsFeeToMint, 'nodeOperatorsRegistry balance = fee') @@ -723,7 +753,15 @@ contract('Lido: rewards distribution math', (addresses) => { const totalFeeToDistribute = new BN(profitAmount).mul(new BN(totalFeePoints)).div(tenKBN) + const sharesToMintSol = new BN(profitAmount) + .mul(new BN(totalFeePoints)) + .mul(prevTotalShares) + .div(totalPooledEther.mul(tenKBN).sub(new BN(profitAmount).mul(new BN(totalFeePoints)))) + const sharesToMint = totalFeeToDistribute.mul(prevTotalShares).div(totalPooledEther.sub(totalFeeToDistribute)) + + assert.equals(sharesToMintSol, sharesToMint) + const nodeOperatorsSharesToMint = sharesToMint.mul(new BN(nodeOperatorsFeePoints)).div(tenKBN) const treasurySharesToMint = sharesToMint.sub(nodeOperatorsSharesToMint)