diff --git a/contracts/0.8.9/oracle/ValidatorsExitBus.sol b/contracts/0.8.9/oracle/ValidatorsExitBus.sol index 8a722aa11..bc3d49d54 100644 --- a/contracts/0.8.9/oracle/ValidatorsExitBus.sol +++ b/contracts/0.8.9/oracle/ValidatorsExitBus.sol @@ -41,6 +41,8 @@ contract ValidatorsExitBus is AccessControlEnumerable { uint256 blockNumber; /// @dev Key index in exit request array uint256 lastDeliveredKeyIndex; + + // TODO: timestamp } // TODO: make type optimization struct RequestStatus { diff --git a/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol b/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol index bc65df934..b6f15cb77 100644 --- a/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol +++ b/contracts/0.8.9/oracle/ValidatorsExitBusOracle.sol @@ -418,6 +418,10 @@ contract ValidatorsExitBusOracle is BaseOracle, PausableUntil, ValidatorsExitBus } function _storeOracleExitRequestHash(bytes32 exitRequestHash, ReportData calldata report, uint256 contractVersion) internal { + if (report.exitRequestData.requestsCount == 0) { + return; + } + mapping(bytes32 => RequestStatus) storage hashes = _storageExitRequestsHashes(); RequestStatus storage request = hashes[exitRequestHash]; diff --git a/test/0.8.9/oracle/validator-exit-bus-oracle.accessControl.test.ts b/test/0.8.9/oracle/validator-exit-bus-oracle.accessControl.test.ts index 53c0e1e29..8498ac4c2 100644 --- a/test/0.8.9/oracle/validator-exit-bus-oracle.accessControl.test.ts +++ b/test/0.8.9/oracle/validator-exit-bus-oracle.accessControl.test.ts @@ -4,7 +4,7 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { HashConsensus__Harness, ValidatorsExitBus__Harness } from "typechain-types"; +import { HashConsensus__Harness, ValidatorsExitBus__Harness, WithdrawalVault__MockForVebo } from "typechain-types"; import { CONSENSUS_VERSION, de0x, numberToHex } from "lib"; @@ -22,12 +22,12 @@ describe("ValidatorsExitBusOracle.sol:accessControl", () => { let oracle: ValidatorsExitBus__Harness; let admin: HardhatEthersSigner; let originalState: string; + let withdrawalVault: WithdrawalVault__MockForVebo; let initTx: ContractTransactionResponse; let oracleVersion: bigint; let exitRequests: ExitRequest[]; let reportFields: ReportFields; - let reportItems: ReturnType; let reportHash: string; let member1: HardhatEthersSigner; @@ -42,24 +42,31 @@ describe("ValidatorsExitBusOracle.sol:accessControl", () => { valIndex: number; valPubkey: string; } + interface ExitRequestData { + requestsCount: number; + dataFormat: number; + data: string; + } interface ReportFields { consensusVersion: bigint; refSlot: bigint; - requestsCount: number; - dataFormat: number; - data: string; + exitRequestData: ExitRequestData; } - const calcValidatorsExitBusReportDataHash = (items: ReturnType) => { - const data = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,uint256,uint256,bytes)"], [items]); + const calcValidatorsExitBusReportDataHash = (items: ReportFields) => { + const exitRequestItems = [ + items.exitRequestData.requestsCount, + items.exitRequestData.dataFormat, + items.exitRequestData.data, + ]; + const exitRequestData = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,bytes)"], [exitRequestItems]); + const dataHash = ethers.keccak256(exitRequestData); + const oracleReportItems = [items.consensusVersion, items.refSlot, dataHash]; + const data = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,bytes32)"], [oracleReportItems]); return ethers.keccak256(data); }; - const getValidatorsExitBusReportDataItems = (r: ReportFields) => { - return [r.consensusVersion, r.refSlot, r.requestsCount, r.dataFormat, r.data]; - }; - const encodeExitRequestHex = ({ moduleId, nodeOpId, valIndex, valPubkey }: ExitRequest) => { const pubkeyHex = de0x(valPubkey); expect(pubkeyHex.length).to.equal(48 * 2); @@ -74,8 +81,9 @@ describe("ValidatorsExitBusOracle.sol:accessControl", () => { const deployed = await deployVEBO(admin.address); oracle = deployed.oracle; consensus = deployed.consensus; + withdrawalVault = deployed.withdrawalVault; - initTx = await initVEBO({ admin: admin.address, oracle, consensus, resumeAfterDeploy: true }); + initTx = await initVEBO({ admin: admin.address, oracle, consensus, withdrawalVault, resumeAfterDeploy: true }); oracleVersion = await oracle.getContractVersion(); @@ -92,14 +100,16 @@ describe("ValidatorsExitBusOracle.sol:accessControl", () => { reportFields = { consensusVersion: CONSENSUS_VERSION, - dataFormat: DATA_FORMAT_LIST, refSlot: refSlot, - requestsCount: exitRequests.length, - data: encodeExitRequestsDataList(exitRequests), + exitRequestData: { + dataFormat: DATA_FORMAT_LIST, + requestsCount: exitRequests.length, + data: encodeExitRequestsDataList(exitRequests), + }, }; - reportItems = getValidatorsExitBusReportDataItems(reportFields); - reportHash = calcValidatorsExitBusReportDataHash(reportItems); + // reportItems = getValidatorsExitBusReportDataItems(reportFields); + reportHash = calcValidatorsExitBusReportDataHash(reportFields); await consensus.connect(member1).submitReport(refSlot, reportHash, CONSENSUS_VERSION); await consensus.connect(member3).submitReport(refSlot, reportHash, CONSENSUS_VERSION); @@ -123,7 +133,6 @@ describe("ValidatorsExitBusOracle.sol:accessControl", () => { expect(oracleVersion).to.be.not.null; expect(exitRequests).to.be.not.null; expect(reportFields).to.be.not.null; - expect(reportItems).to.be.not.null; expect(reportHash).to.be.not.null; }); }); diff --git a/test/0.8.9/oracle/validator-exit-bus-oracle.deploy.test.ts b/test/0.8.9/oracle/validator-exit-bus-oracle.deploy.test.ts index 48dee32af..e8691852e 100644 --- a/test/0.8.9/oracle/validator-exit-bus-oracle.deploy.test.ts +++ b/test/0.8.9/oracle/validator-exit-bus-oracle.deploy.test.ts @@ -4,7 +4,12 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { HashConsensus__Harness, ValidatorsExitBus__Harness, ValidatorsExitBusOracle } from "typechain-types"; +import { + HashConsensus__Harness, + ValidatorsExitBus__Harness, + ValidatorsExitBusOracle, + WithdrawalVault__MockForVebo, +} from "typechain-types"; import { CONSENSUS_VERSION, SECONDS_PER_SLOT } from "lib"; @@ -38,12 +43,15 @@ describe("ValidatorsExitBusOracle.sol:deploy", () => { context("deployment and init finishes successfully (default setup)", async () => { let consensus: HashConsensus__Harness; let oracle: ValidatorsExitBus__Harness; + let withdrawalVault: WithdrawalVault__MockForVebo; before(async () => { const deployed = await deployVEBO(admin.address); + withdrawalVault = deployed.withdrawalVault; await initVEBO({ admin: admin.address, oracle: deployed.oracle, + withdrawalVault, consensus: deployed.consensus, }); diff --git a/test/0.8.9/oracle/validator-exit-bus-oracle.gas.test.ts b/test/0.8.9/oracle/validator-exit-bus-oracle.gas.test.ts index 44a7080ba..aa13f4688 100644 --- a/test/0.8.9/oracle/validator-exit-bus-oracle.gas.test.ts +++ b/test/0.8.9/oracle/validator-exit-bus-oracle.gas.test.ts @@ -4,7 +4,7 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { HashConsensus__Harness, ValidatorsExitBus__Harness } from "typechain-types"; +import { HashConsensus__Harness, ValidatorsExitBus__Harness, WithdrawalVault__MockForVebo } from "typechain-types"; import { trace } from "lib"; import { CONSENSUS_VERSION, de0x, numberToHex } from "lib"; @@ -31,6 +31,7 @@ describe("ValidatorsExitBusOracle.sol:gas", () => { let consensus: HashConsensus__Harness; let oracle: ValidatorsExitBus__Harness; let admin: HardhatEthersSigner; + let withdrawalVault: WithdrawalVault__MockForVebo; let oracleVersion: bigint; @@ -49,24 +50,31 @@ describe("ValidatorsExitBusOracle.sol:gas", () => { valIndex: number; valPubkey: string; } + interface ExitRequestData { + requestsCount: number; + dataFormat: number; + data: string; + } interface ReportFields { consensusVersion: bigint; refSlot: bigint; - requestsCount: number; - dataFormat: number; - data: string; + exitRequestData: ExitRequestData; } - const calcValidatorsExitBusReportDataHash = (items: ReturnType) => { - const data = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,uint256,uint256,bytes)"], [items]); + const calcValidatorsExitBusReportDataHash = (items: ReportFields) => { + const exitRequestItems = [ + items.exitRequestData.requestsCount, + items.exitRequestData.dataFormat, + items.exitRequestData.data, + ]; + const exitRequestData = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,bytes)"], [exitRequestItems]); + const dataHash = ethers.keccak256(exitRequestData); + const oracleReportItems = [items.consensusVersion, items.refSlot, dataHash]; + const data = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,bytes32)"], [oracleReportItems]); return ethers.keccak256(data); }; - const getValidatorsExitBusReportDataItems = (r: ReportFields) => { - return [r.consensusVersion, r.refSlot, r.requestsCount, r.dataFormat, r.data]; - }; - const encodeExitRequestHex = ({ moduleId, nodeOpId, valIndex, valPubkey }: ExitRequest) => { const pubkeyHex = de0x(valPubkey); expect(pubkeyHex.length).to.equal(48 * 2); @@ -81,10 +89,12 @@ describe("ValidatorsExitBusOracle.sol:gas", () => { const deployed = await deployVEBO(admin.address); oracle = deployed.oracle; consensus = deployed.consensus; + withdrawalVault = deployed.withdrawalVault; await initVEBO({ admin: admin.address, oracle, + withdrawalVault, consensus, resumeAfterDeploy: true, }); @@ -142,7 +152,6 @@ describe("ValidatorsExitBusOracle.sol:gas", () => { context(`Total requests: ${totalRequests}`, () => { let exitRequests: { requests: ExitRequest[]; requestsPerModule: number; requestsPerNodeOp: number }; let reportFields: ReportFields; - let reportItems: ReturnType; let reportHash: string; let originalState: string; @@ -171,13 +180,14 @@ describe("ValidatorsExitBusOracle.sol:gas", () => { reportFields = { consensusVersion: CONSENSUS_VERSION, refSlot: refSlot, - requestsCount: exitRequests.requests.length, - dataFormat: DATA_FORMAT_LIST, - data: encodeExitRequestsDataList(exitRequests.requests), + exitRequestData: { + requestsCount: exitRequests.requests.length, + dataFormat: DATA_FORMAT_LIST, + data: encodeExitRequestsDataList(exitRequests.requests), + }, }; - reportItems = getValidatorsExitBusReportDataItems(reportFields); - reportHash = calcValidatorsExitBusReportDataHash(reportItems); + reportHash = calcValidatorsExitBusReportDataHash(reportFields); await triggerConsensusOnHash(reportHash); }); diff --git a/test/0.8.9/oracle/validator-exit-bus-oracle.submitReportData.test.ts b/test/0.8.9/oracle/validator-exit-bus-oracle.submitReportData.test.ts index 5a79f8ab1..53d45f301 100644 --- a/test/0.8.9/oracle/validator-exit-bus-oracle.submitReportData.test.ts +++ b/test/0.8.9/oracle/validator-exit-bus-oracle.submitReportData.test.ts @@ -4,7 +4,12 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { HashConsensus__Harness, OracleReportSanityChecker, ValidatorsExitBus__Harness } from "typechain-types"; +import { + HashConsensus__Harness, + OracleReportSanityChecker, + ValidatorsExitBus__Harness, + WithdrawalVault__MockForVebo, +} from "typechain-types"; import { CONSENSUS_VERSION, de0x, numberToHex } from "lib"; @@ -25,6 +30,7 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { let oracle: ValidatorsExitBus__Harness; let admin: HardhatEthersSigner; let oracleReportSanityChecker: OracleReportSanityChecker; + let withdrawalVault: WithdrawalVault__MockForVebo; let oracleVersion: bigint; @@ -41,24 +47,31 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { valIndex: number; valPubkey: string; } + interface ExitRequestData { + requestsCount: number; + dataFormat: number; + data: string; + } interface ReportFields { consensusVersion: bigint; refSlot: bigint; - requestsCount: number; - dataFormat: number; - data: string; + exitRequestData: ExitRequestData; } - const calcValidatorsExitBusReportDataHash = (items: ReturnType) => { - const data = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,uint256,uint256,bytes)"], [items]); + const calcValidatorsExitBusReportDataHash = (items: ReportFields) => { + const exitRequestItems = [ + items.exitRequestData.requestsCount, + items.exitRequestData.dataFormat, + items.exitRequestData.data, + ]; + const exitRequestData = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,bytes)"], [exitRequestItems]); + const dataHash = ethers.keccak256(exitRequestData); + const oracleReportItems = [items.consensusVersion, items.refSlot, dataHash]; + const data = ethers.AbiCoder.defaultAbiCoder().encode(["(uint256,uint256,bytes32)"], [oracleReportItems]); return ethers.keccak256(data); }; - const getValidatorsExitBusReportDataItems = (r: ReportFields) => { - return [r.consensusVersion, r.refSlot, r.requestsCount, r.dataFormat, r.data]; - }; - const encodeExitRequestHex = ({ moduleId, nodeOpId, valIndex, valPubkey }: ExitRequest) => { const pubkeyHex = de0x(valPubkey); expect(pubkeyHex.length).to.equal(48 * 2); @@ -78,25 +91,27 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { const prepareReportAndSubmitHash = async ( requests = [{ moduleId: 5, nodeOpId: 1, valIndex: 10, valPubkey: PUBKEYS[2] }], - options = { reportFields: {} }, + options?: Partial> & { + exitRequestData?: Partial; + }, ) => { const { refSlot } = await consensus.getCurrentFrame(); - const reportData = { - consensusVersion: CONSENSUS_VERSION, - dataFormat: DATA_FORMAT_LIST, - refSlot, - requestsCount: requests.length, - data: encodeExitRequestsDataList(requests), - ...options.reportFields, + consensusVersion: options?.consensusVersion || CONSENSUS_VERSION, + refSlot: options?.refSlot || refSlot, + exitRequestData: { + requestsCount: requests.length, + dataFormat: DATA_FORMAT_LIST, + data: encodeExitRequestsDataList(requests), + ...options?.exitRequestData, + }, }; - const reportItems = getValidatorsExitBusReportDataItems(reportData); - const reportHash = calcValidatorsExitBusReportDataHash(reportItems); + const reportHash = calcValidatorsExitBusReportDataHash(reportData); await triggerConsensusOnHash(reportHash); - return { reportData, reportHash, reportItems }; + return { reportData, reportHash }; }; async function getLastRequestedValidatorIndex(moduleId: number, nodeOpId: number) { @@ -108,11 +123,13 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { oracle = deployed.oracle; consensus = deployed.consensus; oracleReportSanityChecker = deployed.oracleReportSanityChecker; + withdrawalVault = deployed.withdrawalVault; await initVEBO({ admin: admin.address, oracle, consensus, + withdrawalVault, resumeAfterDeploy: true, lastProcessingRefSlot: LAST_PROCESSING_REF_SLOT, }); @@ -182,7 +199,7 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { const dataFormatUnsupported = 0; const { reportData } = await prepareReportAndSubmitHash( [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }], - { reportFields: { dataFormat: dataFormatUnsupported } }, + { exitRequestData: { dataFormat: dataFormatUnsupported } }, ); await expect(oracle.connect(member1).submitReportData(reportData, oracleVersion)) @@ -194,7 +211,7 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { const dataFormatUnsupported = 2; const { reportData } = await prepareReportAndSubmitHash( [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }], - { reportFields: { dataFormat: dataFormatUnsupported } }, + { exitRequestData: { dataFormat: dataFormatUnsupported } }, ); await expect(oracle.connect(member1).submitReportData(reportData, oracleVersion)) @@ -215,7 +232,8 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { const { refSlot } = await consensus.getCurrentFrame(); const exitRequests = [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }]; const { reportData } = await prepareReportAndSubmitHash(exitRequests, { - reportFields: { data: encodeExitRequestsDataList(exitRequests) + "aaaaaaaaaaaaaaaaaa", refSlot }, + exitRequestData: { data: encodeExitRequestsDataList(exitRequests) + "aaaaaaaaaaaaaaaaaa" }, + refSlot, }); await expect(oracle.connect(member1).submitReportData(reportData, oracleVersion)).to.be.revertedWithCustomError( @@ -230,10 +248,10 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { const data = encodeExitRequestsDataList(exitRequests); const { reportData } = await prepareReportAndSubmitHash(exitRequests, { - reportFields: { + exitRequestData: { data: data.slice(0, data.length - 18), - refSlot, }, + refSlot, }); await expect(oracle.connect(member1).submitReportData(reportData, oracleVersion)).to.be.revertedWithCustomError( @@ -283,7 +301,7 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { it("reverts if requestsCount does not match with encoded data size", async () => { const { reportData } = await prepareReportAndSubmitHash( [{ moduleId: 5, nodeOpId: 3, valIndex: 0, valPubkey: PUBKEYS[0] }], - { reportFields: { requestsCount: 2 } }, + { exitRequestData: { requestsCount: 2 } }, ); await expect(oracle.connect(member1).submitReportData(reportData, oracleVersion)).to.be.revertedWithCustomError( @@ -375,6 +393,7 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { const requestsStep3: ExitRequest[] = []; const { reportData: reportStep3 } = await prepareReportAndSubmitHash(requestsStep3); await oracle.connect(member1).submitReportData(reportStep3, oracleVersion); + const countStep3 = await oracle.getTotalRequestsProcessed(); currentCount += requestsStep3.length; expect(countStep3).to.equal(currentCount); @@ -493,7 +512,6 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { it("reverts on stranger", async () => { const { reportData } = await prepareReportAndSubmitHash(); - await expect(oracle.connect(stranger).submitReportData(reportData, oracleVersion)).to.be.revertedWithCustomError( oracle, "SenderNotAllowed", @@ -519,7 +537,6 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { const PAUSE_INFINITELY = await oracle.PAUSE_INFINITELY(); await oracle.pauseFor(PAUSE_INFINITELY, { from: admin }); const { reportData } = await prepareReportAndSubmitHash(); - await expect(oracle.connect(member1).submitReportData(reportData, oracleVersion)).to.be.revertedWithCustomError( oracle, "ResumedExpected", @@ -554,14 +571,15 @@ describe("ValidatorsExitBusOracle.sol:submitReportData", () => { // change pubkey const reportData = { consensusVersion: CONSENSUS_VERSION, - dataFormat: DATA_FORMAT_LIST, refSlot, - requestsCount: newRequests.length, - data: encodeExitRequestsDataList(newRequests), + exitRequestData: { + requestsCount: requests.length, + dataFormat: DATA_FORMAT_LIST, + data: encodeExitRequestsDataList(newRequests), + }, }; - const reportItems = getValidatorsExitBusReportDataItems(reportData); - const changedReportHash = calcValidatorsExitBusReportDataHash(reportItems); + const changedReportHash = calcValidatorsExitBusReportDataHash(reportData); await expect(oracle.connect(member1).submitReportData(reportData, oracleVersion)) .to.be.revertedWithCustomError(oracle, "UnexpectedDataHash")