From b79b698d90e4ce9bbc5d788cbece90a5ad0c6b3e Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Feb 2023 01:47:58 +0700 Subject: [PATCH 1/7] test: one more handy helper for AccountingOracle tests --- ...counting-oracle-submit-report-data.test.js | 51 ++++++++----------- 1 file changed, 20 insertions(+), 31 deletions(-) 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 7b7080894..2bd58158f 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 @@ -94,7 +94,20 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra await consensus.advanceTimeToNextFrameStart() await consensus.submitReport(newReportFields.refSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) - return newReportItems + return { + newReportFields, + newReportItems, + reportHash + } + } + + async function prepareNextReportInNextFrame(newReportFields) { + const { refSlot } = await consensus.getCurrentFrame() + const next = await prepareNextReport({ + ...newReportFields, + refSlot: +refSlot + SLOTS_PER_FRAME + }) + return next } context('deploying', () => { @@ -165,21 +178,8 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra it('should should allow calling if correct ref slot', async () => { await consensus.setTime(deadline) - const { refSlot } = await consensus.getCurrentFrame() - - const nextRefSlot = +refSlot + SLOTS_PER_FRAME - - const newReportFields = { - ...reportFields, - refSlot: nextRefSlot - } - const reportItems = getReportDataItems(newReportFields) - - const reportHash = calcReportDataHash(reportItems) - await consensus.advanceTimeToNextFrameStart() - await consensus.submitReport(nextRefSlot, reportHash, CONSENSUS_VERSION, { from: member1 }) - - const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) + const { newReportFields, newReportItems } = await prepareNextReportInNextFrame({ ...reportFields }) + const tx = await oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }) assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: newReportFields.refSlot } }) }) }) @@ -241,16 +241,11 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra beforeEach(deploy) it('should revert if incorrect stakingModuleIdsWithNewlyExitedValidators order', async () => { - const { refSlot } = await consensus.getCurrentFrame() - - const nextRefSlot = +refSlot + SLOTS_PER_FRAME - const newReportFields = { + const { newReportItems } = await prepareNextReportInNextFrame({ ...reportFields, - refSlot: nextRefSlot, stakingModuleIdsWithNewlyExitedValidators: [2, 1], numExitedValidatorsByStakingModule: [3, 4] - } - const newReportItems = await prepareNextReport(newReportFields) + }) await assert.reverts( oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }), @@ -259,17 +254,11 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra }) it('should should allow calling if correct extra data list moduleId', async () => { - const { refSlot } = await consensus.getCurrentFrame() - - const nextRefSlot = +refSlot + SLOTS_PER_FRAME - - const newReportFields = { + const { newReportFields, newReportItems } = await prepareNextReportInNextFrame({ ...reportFields, - refSlot: nextRefSlot, stakingModuleIdsWithNewlyExitedValidators: [1, 2], numExitedValidatorsByStakingModule: [3, 4] - } - const newReportItems = await prepareNextReport(newReportFields) + }) const tx = await oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }) assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: newReportFields.refSlot } }) From 3336c7e0998511bfe932b6beeb713ff87637b841 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Feb 2023 01:59:00 +0700 Subject: [PATCH 2/7] test: cleaner event assertion in AccountingOracle tests --- ...accounting-oracle-submit-report-data.test.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) 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 2bd58158f..02510c7a7 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 @@ -1,5 +1,4 @@ const { assert } = require('../../helpers/assert') -const { assertEvent } = require('@aragon/contract-helpers-test/src/asserts') const { e9, e18, e27 } = require('../../helpers/utils') const { @@ -153,7 +152,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra await consensus.setTime(deadline) const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) }) }) @@ -180,7 +179,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra await consensus.setTime(deadline) const { newReportFields, newReportItems } = await prepareNextReportInNextFrame({ ...reportFields }) const tx = await oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: newReportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: newReportFields.refSlot }) }) }) @@ -216,7 +215,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra const { refSlot } = await consensus.getCurrentFrame() const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) const newConsensusVersion = CONSENSUS_VERSION + 1 const nextRefSlot = +refSlot + SLOTS_PER_FRAME @@ -233,7 +232,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra await consensus.submitReport(newReportFields.refSlot, newReportHash, newConsensusVersion, { from: member1 }) const txNewVersion = await oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }) - assertEvent(txNewVersion, 'ProcessingStarted', { expectedArgs: { refSlot: newReportFields.refSlot } }) + assert.emits(txNewVersion, 'ProcessingStarted', { refSlot: newReportFields.refSlot }) }) }) @@ -261,7 +260,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra }) const tx = await oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: newReportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: newReportFields.refSlot }) }) }) @@ -283,7 +282,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra it('submits if data successfully pass hash validation', async () => { const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) }) }) @@ -326,7 +325,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra assert.equals((await mockLido.getLastCall_handleOracleReport()).callCount, 0) await consensus.setTime(deadline) const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) const lastOracleReportToLido = await mockLido.getLastCall_handleOracleReport() @@ -350,7 +349,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra assert.equals((await mockStakingRouter.lastCall_updateExitedKeysByModule()).callCount, 0) await consensus.setTime(deadline) const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) - assertEvent(tx, 'ProcessingStarted', { expectedArgs: { refSlot: reportFields.refSlot } }) + assert.emits(tx, 'ProcessingStarted', { refSlot: reportFields.refSlot }) const lastOracleReportToStakingRouter = await mockStakingRouter.lastCall_updateExitedKeysByModule() From 06e550d957b9da6e27a5ef8f56e005e4a1c6a189 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Feb 2023 02:03:23 +0700 Subject: [PATCH 3/7] test: AccountingOracle.submitReportData does not calling StakingRouter.updateExitedKeysByModule if lists of exited validators is empty --- .../accounting-oracle-submit-report-data.test.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) 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 02510c7a7..421dbc6f5 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 @@ -345,7 +345,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra assert.equals(lastOracleReportToLido.finalizationShareRate, reportFields.finalizationShareRate) }) - it('should call updateExitedValidatorsCountByStakingModule on stakingRouter', async () => { + it('should call updateExitedValidatorsCountByStakingModule on StakingRouter', async () => { assert.equals((await mockStakingRouter.lastCall_updateExitedKeysByModule()).callCount, 0) await consensus.setTime(deadline) const tx = await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) @@ -358,6 +358,18 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra assert.equals(lastOracleReportToStakingRouter.exitedKeysCounts, reportFields.numExitedValidatorsByStakingModule) }) + it('does not calling StakingRouter.updateExitedKeysByModule if lists of exited validators is empty', async () => { + const { newReportItems, newReportFields } = await prepareNextReportInNextFrame({ + ...reportFields, + stakingModuleIdsWithNewlyExitedValidators: [], + numExitedValidatorsByStakingModule: [] + }) + const tx = await oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }) + assert.emits(tx, 'ProcessingStarted', { refSlot: newReportFields.refSlot }) + const lastOracleReportToStakingRouter = await mockStakingRouter.lastCall_updateExitedKeysByModule() + assert.equals(lastOracleReportToStakingRouter.callCount, 0) + }) + it('should call handleConsensusLayerReport on legacyOracle', async () => { await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) const lastCall = await mockLegacyOracle.lastCall__handleConsensusLayerReport() From 21f9409a0aadf8cac7075635b8d7575160fad548 Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Feb 2023 02:04:33 +0700 Subject: [PATCH 4/7] test: AccountingOracle.submitReportData reverts with InvalidExitedValidatorsData if counts of stakingModuleIds and numExitedValidatorsByStakingModule does not match --- .../accounting-oracle-submit-report-data.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) 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 421dbc6f5..ddef76a2f 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 @@ -318,6 +318,18 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) }) + + it('reverts with InvalidExitedValidatorsData if counts of stakingModuleIds and numExitedValidatorsByStakingModule does not match', async () => { + const { newReportItems } = await prepareNextReportInNextFrame({ + ...reportFields, + stakingModuleIdsWithNewlyExitedValidators: [2, 1], + numExitedValidatorsByStakingModule: [3] + }) + await assert.reverts( + oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }), + 'InvalidExitedValidatorsData()' + ) + }) }) context('delivers the data to corresponded contracts', () => { From bde7cd3a33b696d3317b72b8105fd31793de143f Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Feb 2023 02:17:38 +0700 Subject: [PATCH 5/7] test: more test cases for scenarios when AccountingOracle.submitReportData reverts with InvalidExitedValidatorsData --- ...counting-oracle-submit-report-data.test.js | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) 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 ddef76a2f..0f7eaad75 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 @@ -239,7 +239,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra context('enforces module ids sorting order', () => { beforeEach(deploy) - it('should revert if incorrect stakingModuleIdsWithNewlyExitedValidators order', async () => { + it('should revert if incorrect stakingModuleIdsWithNewlyExitedValidators order (when next number in list is less than previous)', async () => { const { newReportItems } = await prepareNextReportInNextFrame({ ...reportFields, stakingModuleIdsWithNewlyExitedValidators: [2, 1], @@ -252,6 +252,19 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra ) }) + it('should revert if incorrect stakingModuleIdsWithNewlyExitedValidators order (when next number in list is less than previous)', async () => { + const { newReportItems } = await prepareNextReportInNextFrame({ + ...reportFields, + stakingModuleIdsWithNewlyExitedValidators: [1, 1], + numExitedValidatorsByStakingModule: [3, 4] + }) + + await assert.reverts( + oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }), + 'InvalidExitedValidatorsData()' + ) + }) + it('should should allow calling if correct extra data list moduleId', async () => { const { newReportFields, newReportItems } = await prepareNextReportInNextFrame({ ...reportFields, @@ -322,7 +335,7 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra it('reverts with InvalidExitedValidatorsData if counts of stakingModuleIds and numExitedValidatorsByStakingModule does not match', async () => { const { newReportItems } = await prepareNextReportInNextFrame({ ...reportFields, - stakingModuleIdsWithNewlyExitedValidators: [2, 1], + stakingModuleIdsWithNewlyExitedValidators: [1, 2], numExitedValidatorsByStakingModule: [3] }) await assert.reverts( @@ -330,6 +343,18 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra 'InvalidExitedValidatorsData()' ) }) + + it('reverts with InvalidExitedValidatorsData if any record for number of exited validators equals 0', async () => { + const { newReportItems } = await prepareNextReportInNextFrame({ + ...reportFields, + stakingModuleIdsWithNewlyExitedValidators: [1, 2], + numExitedValidatorsByStakingModule: [3, 0] + }) + await assert.reverts( + oracle.submitReportData(newReportItems, oracleVersion, { from: member1 }), + 'InvalidExitedValidatorsData()' + ) + }) }) context('delivers the data to corresponded contracts', () => { From 12baa2799d6e7e87ab3d8b683aa3c9abe11d4afc Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Feb 2023 03:07:47 +0700 Subject: [PATCH 6/7] test: AccountingOracle.submitReportData exited validarors count boundaries scenarios --- ...counting-oracle-submit-report-data.test.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) 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 0f7eaad75..d1a105fd2 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 @@ -355,6 +355,47 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra 'InvalidExitedValidatorsData()' ) }) + + 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 + // during calculation `exitedValidatorsPerDay` rate in AccountingOracle:612 + const totalExitedValidators = reportFields.numExitedValidatorsByStakingModule.reduce( + (sum, curr) => sum + curr, + 0 + ) + const exitingRateLimit = totalExitedValidators - 1 + await oracleReportSanityChecker.setChurnValidatorsPerDayLimit(exitingRateLimit) + assert.equals( + (await oracleReportSanityChecker.getOracleReportLimits()).churnValidatorsPerDayLimit, + exitingRateLimit + ) + await assert.reverts( + oracle.submitReportData(reportItems, oracleVersion, { from: member1 }), + `ExitedValidatorsLimitExceeded(${exitingRateLimit}, ${totalExitedValidators})` + ) + }) }) context('delivers the data to corresponded contracts', () => { From 53be6333c300357c69d1f4781dbf8102d72a1dda Mon Sep 17 00:00:00 2001 From: Dmitrii Podlesnyi Date: Tue, 14 Feb 2023 03:24:53 +0700 Subject: [PATCH 7/7] test: AccountingOracle.submitReportData fills ExtraDataProcessingState after call --- .../AccountingOracleTimeTravellable.sol | 4 +++ ...counting-oracle-submit-report-data.test.js | 26 ++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/contracts/0.8.9/test_helpers/oracle/AccountingOracleTimeTravellable.sol b/contracts/0.8.9/test_helpers/oracle/AccountingOracleTimeTravellable.sol index 7847b8417..e25ffa93c 100644 --- a/contracts/0.8.9/test_helpers/oracle/AccountingOracleTimeTravellable.sol +++ b/contracts/0.8.9/test_helpers/oracle/AccountingOracleTimeTravellable.sol @@ -30,4 +30,8 @@ contract AccountingOracleTimeTravellable is AccountingOracle, ITimeProvider { address consensus = CONSENSUS_CONTRACT_POSITION.getStorageAddress(); return ITimeProvider(consensus).getTime(); } + + function getExtraDataProcessingState() external view returns (ExtraDataProcessingState memory) { + return _storageExtraDataProcessingState().value; + } } 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 d1a105fd2..ea0c5851a 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 @@ -12,7 +12,8 @@ const { EXTRA_DATA_FORMAT_LIST, SLOTS_PER_FRAME, SECONDS_PER_SLOT, - GENESIS_TIME + GENESIS_TIME, + ZERO_HASH } = require('./accounting-oracle-deploy.test') contract('AccountingOracle', ([admin, account1, account2, member1, member2, stranger]) => { @@ -491,5 +492,28 @@ contract('AccountingOracle', ([admin, account1, account2, member1, member2, stra ) }) }) + + context('ExtraDataProcessingState', () => { + it('should be empty from start', async () => { + const data = await oracle.getExtraDataProcessingState() + assert.equals(data.refSlot, '0') + assert.equals(data.dataFormat, '0') + assert.equals(data.itemsCount, '0') + assert.equals(data.itemsProcessed, '0') + assert.equals(data.lastSortingKey, '0') + assert.equals(data.dataHash, ZERO_HASH) + }) + + it('should be filled with report data after submitting', async () => { + await oracle.submitReportData(reportItems, oracleVersion, { from: member1 }) + const data = await oracle.getExtraDataProcessingState() + assert.equals(data.refSlot, reportFields.refSlot) + assert.equals(data.dataFormat, reportFields.extraDataFormat) + assert.equals(data.itemsCount, reportFields.extraDataItemsCount) + assert.equals(data.itemsProcessed, '0') + assert.equals(data.lastSortingKey, '0') + assert.equals(data.dataHash, reportFields.extraDataHash) + }) + }) }) })