From dc3c28997823afd6df0ba3eff5c4a12fb4c1b7ee Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 18 Sep 2024 14:49:29 +0200 Subject: [PATCH 01/19] test: second opinion test --- .../testnets/sepolia/SecondOpinionStub.sol | 47 ++++++ .../contracts/SecondOpinionOracleMock.sol | 11 ++ test/integration/second-opinion.ts | 142 ++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 contracts/testnets/sepolia/SecondOpinionStub.sol create mode 100644 test/integration/second-opinion.ts diff --git a/contracts/testnets/sepolia/SecondOpinionStub.sol b/contracts/testnets/sepolia/SecondOpinionStub.sol new file mode 100644 index 000000000..fc77ad2f3 --- /dev/null +++ b/contracts/testnets/sepolia/SecondOpinionStub.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +// for testing purposes only + +/* See contracts/COMPILERS.md */ +pragma solidity 0.8.9; + +import {ISecondOpinionOracle} from "../../0.8.9/interfaces/ISecondOpinionOracle.sol"; + +struct StubReportData { + uint256 refSlot; + bool success; + uint256 clBalanceGwei; + uint256 withdrawalVaultBalanceWei; +} + +contract SecondOpinionStub is ISecondOpinionOracle { + + mapping(uint256 => StubReportData) reports; + + /// @notice Returns second opinion report for the given reference slot + /// @param refSlot is a reference slot to return report for + /// @return success shows whether the report was successfully generated + /// @return clBalanceGwei is a balance of the consensus layer in Gwei for the ref slot + /// @return withdrawalVaultBalanceWei is a balance of the withdrawal vault in Wei for the ref slot + /// @return totalDepositedValidators is a total number of validators deposited with Lido + /// @return totalExitedValidators is a total number of Lido validators in the EXITED state + function getReport(uint256 refSlot) + external + view + returns ( + bool success, + uint256 clBalanceGwei, + uint256 withdrawalVaultBalanceWei, + uint256 totalDepositedValidators, + uint256 totalExitedValidators + ) { + StubReportData memory report = reports[refSlot]; + if (report.refSlot == refSlot) { + return (report.success, report.clBalanceGwei, report.withdrawalVaultBalanceWei, 0, 0); + } + return (false, 0, 0, 0, 0); + } + + function addReportStub(StubReportData memory data) external { + reports[data.refSlot] = data; + } +} diff --git a/test/0.8.9/contracts/SecondOpinionOracleMock.sol b/test/0.8.9/contracts/SecondOpinionOracleMock.sol index b55b772ac..52dc687b9 100644 --- a/test/0.8.9/contracts/SecondOpinionOracleMock.sol +++ b/test/0.8.9/contracts/SecondOpinionOracleMock.sol @@ -26,6 +26,17 @@ contract SecondOpinionOracleMock is ISecondOpinionOracle { reports[refSlot] = report; } + function addPlainReport(uint256 refSlot, uint256 clBalanceGwei, uint256 withdrawalVaultBalanceWei) external { + + reports[refSlot] = Report({ + success: true, + clBalanceGwei: clBalanceGwei, + withdrawalVaultBalanceWei: withdrawalVaultBalanceWei, + numValidators: 0, + exitedValidators: 0 + }); + } + function removeReport(uint256 refSlot) external { delete reports[refSlot]; } diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts new file mode 100644 index 000000000..467c607eb --- /dev/null +++ b/test/integration/second-opinion.ts @@ -0,0 +1,142 @@ +import { expect } from "chai"; +import { ContractTransactionReceipt, TransactionResponse } from "ethers"; +import { ethers } from "hardhat"; + +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; + +import { SecondOpinionOracleMock } from "typechain-types"; + +import { ether, impersonate } from "lib"; +import { getProtocolContext, ProtocolContext } from "lib/protocol"; +import { + finalizeWithdrawalQueue, + norEnsureOperators, + report, + sdvtEnsureOperators +} from "lib/protocol/helpers"; + +import { Snapshot } from "test/suite"; + +const LIMITER_PRECISION_BASE = BigInt(10 ** 9); + +const SHARE_RATE_PRECISION = BigInt(10 ** 27); +const ONE_DAY = 86400n; +const MAX_BASIS_POINTS = 10000n; +const AMOUNT = ether("100"); +const MAX_DEPOSIT = 150n; +const CURATED_MODULE_ID = 1n; +const SIMPLE_DVT_MODULE_ID = 2n; + +const ZERO_HASH = new Uint8Array(32).fill(0); + +describe("Second opinion", () => { + let ctx: ProtocolContext; + + let ethHolder: HardhatEthersSigner; + let stEthHolder: HardhatEthersSigner; + + let snapshot: string; + let originalState: string; + + let secondOpinion: SecondOpinionOracleMock; + + before(async () => { + ctx = await getProtocolContext(); + + [stEthHolder, ethHolder] = await ethers.getSigners(); + + snapshot = await Snapshot.take(); + + const { lido, depositSecurityModule, oracleReportSanityChecker } = ctx.contracts; + + await finalizeWithdrawalQueue(ctx, stEthHolder, ethHolder); + + await norEnsureOperators(ctx, 3n, 5n); + if (ctx.flags.withSimpleDvtModule) { + await sdvtEnsureOperators(ctx, 3n, 5n); + } + + // const sepoliaDepositContractAddress = "0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D"; + // const bepoliaWhaleHolder = "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134"; + // const BEPOLIA_TO_TRANSFER = 20; + + // const bepoliaToken = await ethers.getContractAt("ISepoliaDepositContract", sepoliaDepositContractAddress); + // const bepiloaSigner = await ethers.getImpersonatedSigner(bepoliaWhaleHolder); + + // const adapterAddr = await ctx.contracts.stakingRouter.DEPOSIT_CONTRACT(); + // await bepoliaToken.connect(bepiloaSigner).transfer(adapterAddr, BEPOLIA_TO_TRANSFER); + + const dsmSigner = await impersonate(depositSecurityModule.address, AMOUNT); + await lido.connect(dsmSigner).deposit(MAX_DEPOSIT, CURATED_MODULE_ID, ZERO_HASH); + + secondOpinion = await ethers.deployContract("SecondOpinionOracleMock", []); + const soAddress = await secondOpinion.getAddress(); + console.log("second opinion address", soAddress); + + + const sanityAddr = await oracleReportSanityChecker.getAddress(); + console.log("sanityAddr", sanityAddr); + + const adminSigner = await impersonate("0xc00c0beC9F5C6b245A5c232598b3A2cc1558C3c7", AMOUNT); + await oracleReportSanityChecker.connect(adminSigner).grantRole( + await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), adminSigner.address); + + + console.log("Finish init"); + await oracleReportSanityChecker.connect(adminSigner).setSecondOpinionOracleAndCLBalanceUpperMargin( + soAddress, 74n); + + + await report(ctx, { + clDiff: ether("32") * 3n, // 32 ETH * 3 validators + clAppearedValidators: 3n, + excludeVaultsBalances: true, + }); + }); + + // beforeEach(bailOnFailure); + + beforeEach(async () => (originalState = await Snapshot.take())); + + afterEach(async () => await Snapshot.restore(originalState)); + + after(async () => await Snapshot.restore(snapshot)); // Rollback to the initial state pre deployment + + const getFirstEvent = (receipt: ContractTransactionReceipt, eventName: string) => { + const events = ctx.getEvents(receipt, eventName); + expect(events.length).to.be.greaterThan(0); + return events[0]; + }; + + it("Should account correctly with no CL rebase", async () => { + const { hashConsensus, accountingOracle } = ctx.contracts; + + const curFrame = await hashConsensus.getCurrentFrame(); + console.log('curFrame', curFrame); + + await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, 86000000000n, 0n); + await secondOpinion.addPlainReport(curFrame.refSlot, 86000000000n, 0n); + const testReport = await secondOpinion.getReport(curFrame.reportProcessingDeadlineSlot); + console.log('testReport', testReport); + + const lastProcessingRefSlotBefore = await accountingOracle.getLastProcessingRefSlot(); + console.log("lastProcessingRefSlotBefore", lastProcessingRefSlotBefore.toString()); + // Report + const params = { clDiff: ether("-10"), excludeVaultsBalances: true }; + const { reportTx } = (await report(ctx, params)) as { + reportTx: TransactionResponse; + extraDataTx: TransactionResponse; + }; + console.log("Finished report"); + + const reportTxReceipt = (await reportTx.wait()) as ContractTransactionReceipt; + + const lastProcessingRefSlotAfter = await accountingOracle.getLastProcessingRefSlot(); + console.log("lastProcessingRefSlotAfter", lastProcessingRefSlotAfter.toString()); + expect(lastProcessingRefSlotBefore).to.be.lessThan( + lastProcessingRefSlotAfter, + "LastProcessingRefSlot should be updated", + ); + + }); +}); From 5f3ce0b6c3c7249ef8ef4a2cfabdf5af97f3a434 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 19 Sep 2024 11:11:26 +0200 Subject: [PATCH 02/19] feat: remove unused stub --- .../testnets/sepolia/SecondOpinionStub.sol | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 contracts/testnets/sepolia/SecondOpinionStub.sol diff --git a/contracts/testnets/sepolia/SecondOpinionStub.sol b/contracts/testnets/sepolia/SecondOpinionStub.sol deleted file mode 100644 index fc77ad2f3..000000000 --- a/contracts/testnets/sepolia/SecondOpinionStub.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// for testing purposes only - -/* See contracts/COMPILERS.md */ -pragma solidity 0.8.9; - -import {ISecondOpinionOracle} from "../../0.8.9/interfaces/ISecondOpinionOracle.sol"; - -struct StubReportData { - uint256 refSlot; - bool success; - uint256 clBalanceGwei; - uint256 withdrawalVaultBalanceWei; -} - -contract SecondOpinionStub is ISecondOpinionOracle { - - mapping(uint256 => StubReportData) reports; - - /// @notice Returns second opinion report for the given reference slot - /// @param refSlot is a reference slot to return report for - /// @return success shows whether the report was successfully generated - /// @return clBalanceGwei is a balance of the consensus layer in Gwei for the ref slot - /// @return withdrawalVaultBalanceWei is a balance of the withdrawal vault in Wei for the ref slot - /// @return totalDepositedValidators is a total number of validators deposited with Lido - /// @return totalExitedValidators is a total number of Lido validators in the EXITED state - function getReport(uint256 refSlot) - external - view - returns ( - bool success, - uint256 clBalanceGwei, - uint256 withdrawalVaultBalanceWei, - uint256 totalDepositedValidators, - uint256 totalExitedValidators - ) { - StubReportData memory report = reports[refSlot]; - if (report.refSlot == refSlot) { - return (report.success, report.clBalanceGwei, report.withdrawalVaultBalanceWei, 0, 0); - } - return (false, 0, 0, 0, 0); - } - - function addReportStub(StubReportData memory data) external { - reports[data.refSlot] = data; - } -} From 87a29e005dc19db3b8c472fd0174b76dfa21fcbb Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 19 Sep 2024 11:15:10 +0200 Subject: [PATCH 03/19] test: improve test --- test/integration/second-opinion.ts | 52 +++++++++++------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts index 467c607eb..e610e161a 100644 --- a/test/integration/second-opinion.ts +++ b/test/integration/second-opinion.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { ContractTransactionReceipt, TransactionResponse } from "ethers"; +import { TransactionResponse } from "ethers"; import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; @@ -8,24 +8,13 @@ import { SecondOpinionOracleMock } from "typechain-types"; import { ether, impersonate } from "lib"; import { getProtocolContext, ProtocolContext } from "lib/protocol"; -import { - finalizeWithdrawalQueue, - norEnsureOperators, - report, - sdvtEnsureOperators -} from "lib/protocol/helpers"; +import { finalizeWithdrawalQueue, norEnsureOperators, report, sdvtEnsureOperators } from "lib/protocol/helpers"; import { Snapshot } from "test/suite"; -const LIMITER_PRECISION_BASE = BigInt(10 ** 9); - -const SHARE_RATE_PRECISION = BigInt(10 ** 27); -const ONE_DAY = 86400n; -const MAX_BASIS_POINTS = 10000n; const AMOUNT = ether("100"); const MAX_DEPOSIT = 150n; const CURATED_MODULE_ID = 1n; -const SIMPLE_DVT_MODULE_ID = 2n; const ZERO_HASH = new Uint8Array(32).fill(0); @@ -73,25 +62,22 @@ describe("Second opinion", () => { const soAddress = await secondOpinion.getAddress(); console.log("second opinion address", soAddress); - const sanityAddr = await oracleReportSanityChecker.getAddress(); console.log("sanityAddr", sanityAddr); - const adminSigner = await impersonate("0xc00c0beC9F5C6b245A5c232598b3A2cc1558C3c7", AMOUNT); - await oracleReportSanityChecker.connect(adminSigner).grantRole( - await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), adminSigner.address); - - - console.log("Finish init"); - await oracleReportSanityChecker.connect(adminSigner).setSecondOpinionOracleAndCLBalanceUpperMargin( - soAddress, 74n); - + const agentSigner = await ctx.getSigner("agent", AMOUNT); + await oracleReportSanityChecker + .connect(agentSigner) + .grantRole(await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), agentSigner.address); await report(ctx, { clDiff: ether("32") * 3n, // 32 ETH * 3 validators clAppearedValidators: 3n, excludeVaultsBalances: true, }); + + await oracleReportSanityChecker.connect(agentSigner).setSecondOpinionOracleAndCLBalanceUpperMargin(soAddress, 74n); + console.log("Finish init"); }); // beforeEach(bailOnFailure); @@ -102,27 +88,25 @@ describe("Second opinion", () => { after(async () => await Snapshot.restore(snapshot)); // Rollback to the initial state pre deployment - const getFirstEvent = (receipt: ContractTransactionReceipt, eventName: string) => { - const events = ctx.getEvents(receipt, eventName); - expect(events.length).to.be.greaterThan(0); - return events[0]; - }; - it("Should account correctly with no CL rebase", async () => { const { hashConsensus, accountingOracle } = ctx.contracts; const curFrame = await hashConsensus.getCurrentFrame(); - console.log('curFrame', curFrame); + console.log("curFrame", curFrame); await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, 86000000000n, 0n); - await secondOpinion.addPlainReport(curFrame.refSlot, 86000000000n, 0n); - const testReport = await secondOpinion.getReport(curFrame.reportProcessingDeadlineSlot); - console.log('testReport', testReport); + // await secondOpinion.addPlainReport(curFrame.refSlot, 86000000000n, 0n); + const testReport = await secondOpinion.getReport(curFrame.refSlot); + console.log("testReport refSlot", curFrame.refSlot, testReport); + const testReport2 = await secondOpinion.getReport(curFrame.reportProcessingDeadlineSlot); + console.log("testReport reportProcessingDeadlineSlot", curFrame.reportProcessingDeadlineSlot, testReport2); const lastProcessingRefSlotBefore = await accountingOracle.getLastProcessingRefSlot(); console.log("lastProcessingRefSlotBefore", lastProcessingRefSlotBefore.toString()); // Report const params = { clDiff: ether("-10"), excludeVaultsBalances: true }; + + // Tracing.enable(); const { reportTx } = (await report(ctx, params)) as { reportTx: TransactionResponse; extraDataTx: TransactionResponse; @@ -130,6 +114,7 @@ describe("Second opinion", () => { console.log("Finished report"); const reportTxReceipt = (await reportTx.wait()) as ContractTransactionReceipt; + console.log("reportTxReceipt", reportTxReceipt); const lastProcessingRefSlotAfter = await accountingOracle.getLastProcessingRefSlot(); console.log("lastProcessingRefSlotAfter", lastProcessingRefSlotAfter.toString()); @@ -137,6 +122,5 @@ describe("Second opinion", () => { lastProcessingRefSlotAfter, "LastProcessingRefSlot should be updated", ); - }); }); From ecf9fd0d7270907597ab9d638def69498e5012bf Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 19 Sep 2024 11:15:43 +0200 Subject: [PATCH 04/19] fix: modify accounting to enable negative rebase checker --- lib/protocol/helpers/accounting.ts | 70 ++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/lib/protocol/helpers/accounting.ts b/lib/protocol/helpers/accounting.ts index b6a9f4dec..3f48eaaed 100644 --- a/lib/protocol/helpers/accounting.ts +++ b/lib/protocol/helpers/accounting.ts @@ -17,6 +17,7 @@ import { impersonate, log, ONE_GWEI, + streccak, trace, } from "lib"; @@ -352,19 +353,62 @@ const simulateReport = async ( "El Rewards Vault Balance": formatEther(elRewardsVaultBalance), }); - const [postTotalPooledEther, postTotalShares, withdrawals, elRewards] = await lido - .connect(accountingOracleAccount) - .handleOracleReport.staticCall( - reportTimestamp, - 1n * 24n * 60n * 60n, // 1 day - beaconValidators, - clBalance, - withdrawalVaultBalance, - elRewardsVaultBalance, - 0n, - [], - 0n, - ); + // NOTE: To enable negative rebase sanity checker, the static call below should be + // replaced with advanced eth_call with stateDiff. + + // const [postTotalPooledEther1, postTotalShares1, withdrawals1, elRewards1] = await lido + // .connect(accountingOracleAccount) + // .handleOracleReport.staticCall( + // reportTimestamp, + // 1n * 24n * 60n * 60n, // 1 day + // beaconValidators, + // clBalance, + // withdrawalVaultBalance, + // elRewardsVaultBalance, + // 0n, + // [], + // 0n, + // ); + + // Step 1: Encode the function call data + const data = lido.interface.encodeFunctionData("handleOracleReport", [ + reportTimestamp, + BigInt(24 * 60 * 60), // 1 day in seconds + beaconValidators, + clBalance, + withdrawalVaultBalance, + elRewardsVaultBalance, + BigInt(0), + [], + BigInt(0), + ]); + + // Step 2: Prepare the transaction object + const transactionObject = { + to: lido.address, + from: accountingOracleAccount.address, + data: data, + }; + + // Step 3: Prepare call parameters, state diff and perform eth_call + const accountingOracleAddr = await accountingOracle.getAddress(); + const callParams = [transactionObject, "latest"]; + const LAST_PROCESSING_REF_SLOT_POSITION = streccak("lido.BaseOracle.lastProcessingRefSlot"); + const stateDiff = { + [accountingOracleAddr]: { + stateDiff: { + [LAST_PROCESSING_REF_SLOT_POSITION]: refSlot, // setting the processing refslot for the sanity checker + }, + }, + }; + + const returnData = await ethers.provider.send("eth_call", [...callParams, stateDiff]); + + // Step 4: Decode the returned data + const [[postTotalPooledEther, postTotalShares, withdrawals, elRewards]] = lido.interface.decodeFunctionResult( + "handleOracleReport", + returnData, + ); log.debug("Simulation result", { "Post Total Pooled Ether": formatEther(postTotalPooledEther), From 4ec12a84bf0fdd7a476f7aed7be121c7d1568a8c Mon Sep 17 00:00:00 2001 From: VP Date: Mon, 7 Oct 2024 14:10:19 +0200 Subject: [PATCH 05/19] test: refine second opinion test --- test/integration/second-opinion.ts | 71 ++++++++++++------------------ 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts index e610e161a..eb334151f 100644 --- a/test/integration/second-opinion.ts +++ b/test/integration/second-opinion.ts @@ -1,20 +1,21 @@ import { expect } from "chai"; -import { TransactionResponse } from "ethers"; import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { SecondOpinionOracleMock } from "typechain-types"; -import { ether, impersonate } from "lib"; +import { ether, impersonate, ONE_GWEI } from "lib"; import { getProtocolContext, ProtocolContext } from "lib/protocol"; import { finalizeWithdrawalQueue, norEnsureOperators, report, sdvtEnsureOperators } from "lib/protocol/helpers"; -import { Snapshot } from "test/suite"; +import { bailOnFailure, Snapshot } from "test/suite"; const AMOUNT = ether("100"); const MAX_DEPOSIT = 150n; const CURATED_MODULE_ID = 1n; +const INITIAL_REPORTED_BALANCE = ether("32") * 3n; // 32 ETH * 3 validators +const DIFF_AMOUNT = ether("10"); const ZERO_HASH = new Uint8Array(32).fill(0); @@ -45,25 +46,25 @@ describe("Second opinion", () => { await sdvtEnsureOperators(ctx, 3n, 5n); } - // const sepoliaDepositContractAddress = "0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D"; - // const bepoliaWhaleHolder = "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134"; - // const BEPOLIA_TO_TRANSFER = 20; + const { chainId } = await ethers.provider.getNetwork(); + // Sepolia-specific initialization + if (chainId === 11155111n) { + // Sepolia deposit contract address https://sepolia.etherscan.io/token/0x7f02c3e3c98b133055b8b348b2ac625669ed295d + const sepoliaDepositContractAddress = "0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D"; + const bepoliaWhaleHolder = "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134"; + const BEPOLIA_TO_TRANSFER = 20; - // const bepoliaToken = await ethers.getContractAt("ISepoliaDepositContract", sepoliaDepositContractAddress); - // const bepiloaSigner = await ethers.getImpersonatedSigner(bepoliaWhaleHolder); - - // const adapterAddr = await ctx.contracts.stakingRouter.DEPOSIT_CONTRACT(); - // await bepoliaToken.connect(bepiloaSigner).transfer(adapterAddr, BEPOLIA_TO_TRANSFER); + const bepoliaToken = await ethers.getContractAt("ISepoliaDepositContract", sepoliaDepositContractAddress); + const bepiloaSigner = await ethers.getImpersonatedSigner(bepoliaWhaleHolder); + const adapterAddr = await ctx.contracts.stakingRouter.DEPOSIT_CONTRACT(); + await bepoliaToken.connect(bepiloaSigner).transfer(adapterAddr, BEPOLIA_TO_TRANSFER); + } const dsmSigner = await impersonate(depositSecurityModule.address, AMOUNT); await lido.connect(dsmSigner).deposit(MAX_DEPOSIT, CURATED_MODULE_ID, ZERO_HASH); secondOpinion = await ethers.deployContract("SecondOpinionOracleMock", []); const soAddress = await secondOpinion.getAddress(); - console.log("second opinion address", soAddress); - - const sanityAddr = await oracleReportSanityChecker.getAddress(); - console.log("sanityAddr", sanityAddr); const agentSigner = await ctx.getSigner("agent", AMOUNT); await oracleReportSanityChecker @@ -71,16 +72,15 @@ describe("Second opinion", () => { .grantRole(await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), agentSigner.address); await report(ctx, { - clDiff: ether("32") * 3n, // 32 ETH * 3 validators + clDiff: INITIAL_REPORTED_BALANCE, clAppearedValidators: 3n, excludeVaultsBalances: true, }); await oracleReportSanityChecker.connect(agentSigner).setSecondOpinionOracleAndCLBalanceUpperMargin(soAddress, 74n); - console.log("Finish init"); }); - // beforeEach(bailOnFailure); + beforeEach(bailOnFailure); beforeEach(async () => (originalState = await Snapshot.take())); @@ -89,35 +89,22 @@ describe("Second opinion", () => { after(async () => await Snapshot.restore(snapshot)); // Rollback to the initial state pre deployment it("Should account correctly with no CL rebase", async () => { - const { hashConsensus, accountingOracle } = ctx.contracts; + const { hashConsensus, accountingOracle, oracleReportSanityChecker } = ctx.contracts; - const curFrame = await hashConsensus.getCurrentFrame(); - console.log("curFrame", curFrame); + // Report without second opinion is failing + await expect(report(ctx, { clDiff: -DIFF_AMOUNT, excludeVaultsBalances: true })).to.be.revertedWithCustomError( + oracleReportSanityChecker, + "NegativeRebaseFailedSecondOpinionReportIsNotReady", + ); - await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, 86000000000n, 0n); - // await secondOpinion.addPlainReport(curFrame.refSlot, 86000000000n, 0n); - const testReport = await secondOpinion.getReport(curFrame.refSlot); - console.log("testReport refSlot", curFrame.refSlot, testReport); - const testReport2 = await secondOpinion.getReport(curFrame.reportProcessingDeadlineSlot); - console.log("testReport reportProcessingDeadlineSlot", curFrame.reportProcessingDeadlineSlot, testReport2); + // Provide a second opinion + const curFrame = await hashConsensus.getCurrentFrame(); + const expectedBalance = (INITIAL_REPORTED_BALANCE - DIFF_AMOUNT) / ONE_GWEI; + await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, expectedBalance, 0n); const lastProcessingRefSlotBefore = await accountingOracle.getLastProcessingRefSlot(); - console.log("lastProcessingRefSlotBefore", lastProcessingRefSlotBefore.toString()); - // Report - const params = { clDiff: ether("-10"), excludeVaultsBalances: true }; - - // Tracing.enable(); - const { reportTx } = (await report(ctx, params)) as { - reportTx: TransactionResponse; - extraDataTx: TransactionResponse; - }; - console.log("Finished report"); - - const reportTxReceipt = (await reportTx.wait()) as ContractTransactionReceipt; - console.log("reportTxReceipt", reportTxReceipt); - + await report(ctx, { clDiff: -DIFF_AMOUNT, excludeVaultsBalances: true }); const lastProcessingRefSlotAfter = await accountingOracle.getLastProcessingRefSlot(); - console.log("lastProcessingRefSlotAfter", lastProcessingRefSlotAfter.toString()); expect(lastProcessingRefSlotBefore).to.be.lessThan( lastProcessingRefSlotAfter, "LastProcessingRefSlot should be updated", From 81eef77ccc6d36165ba64ea013714c59fc1b0dad Mon Sep 17 00:00:00 2001 From: VP Date: Mon, 7 Oct 2024 14:11:01 +0200 Subject: [PATCH 06/19] feat: refine comments --- lib/protocol/helpers/accounting.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/protocol/helpers/accounting.ts b/lib/protocol/helpers/accounting.ts index 3f48eaaed..7ff51943c 100644 --- a/lib/protocol/helpers/accounting.ts +++ b/lib/protocol/helpers/accounting.ts @@ -353,9 +353,8 @@ const simulateReport = async ( "El Rewards Vault Balance": formatEther(elRewardsVaultBalance), }); - // NOTE: To enable negative rebase sanity checker, the static call below should be + // NOTE: To enable negative rebase sanity checker, the static call below // replaced with advanced eth_call with stateDiff. - // const [postTotalPooledEther1, postTotalShares1, withdrawals1, elRewards1] = await lido // .connect(accountingOracleAccount) // .handleOracleReport.staticCall( From 47e43bd60b219cc320ff74373a3dd63b4eb88d2b Mon Sep 17 00:00:00 2001 From: VP Date: Wed, 18 Sep 2024 14:49:29 +0200 Subject: [PATCH 07/19] test: second opinion test --- .../testnets/sepolia/SecondOpinionStub.sol | 47 ++++++ .../contracts/SecondOpinionOracleMock.sol | 11 ++ test/integration/second-opinion.ts | 142 ++++++++++++++++++ 3 files changed, 200 insertions(+) create mode 100644 contracts/testnets/sepolia/SecondOpinionStub.sol create mode 100644 test/integration/second-opinion.ts diff --git a/contracts/testnets/sepolia/SecondOpinionStub.sol b/contracts/testnets/sepolia/SecondOpinionStub.sol new file mode 100644 index 000000000..fc77ad2f3 --- /dev/null +++ b/contracts/testnets/sepolia/SecondOpinionStub.sol @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: UNLICENSED +// for testing purposes only + +/* See contracts/COMPILERS.md */ +pragma solidity 0.8.9; + +import {ISecondOpinionOracle} from "../../0.8.9/interfaces/ISecondOpinionOracle.sol"; + +struct StubReportData { + uint256 refSlot; + bool success; + uint256 clBalanceGwei; + uint256 withdrawalVaultBalanceWei; +} + +contract SecondOpinionStub is ISecondOpinionOracle { + + mapping(uint256 => StubReportData) reports; + + /// @notice Returns second opinion report for the given reference slot + /// @param refSlot is a reference slot to return report for + /// @return success shows whether the report was successfully generated + /// @return clBalanceGwei is a balance of the consensus layer in Gwei for the ref slot + /// @return withdrawalVaultBalanceWei is a balance of the withdrawal vault in Wei for the ref slot + /// @return totalDepositedValidators is a total number of validators deposited with Lido + /// @return totalExitedValidators is a total number of Lido validators in the EXITED state + function getReport(uint256 refSlot) + external + view + returns ( + bool success, + uint256 clBalanceGwei, + uint256 withdrawalVaultBalanceWei, + uint256 totalDepositedValidators, + uint256 totalExitedValidators + ) { + StubReportData memory report = reports[refSlot]; + if (report.refSlot == refSlot) { + return (report.success, report.clBalanceGwei, report.withdrawalVaultBalanceWei, 0, 0); + } + return (false, 0, 0, 0, 0); + } + + function addReportStub(StubReportData memory data) external { + reports[data.refSlot] = data; + } +} diff --git a/test/0.8.9/contracts/SecondOpinionOracleMock.sol b/test/0.8.9/contracts/SecondOpinionOracleMock.sol index b55b772ac..52dc687b9 100644 --- a/test/0.8.9/contracts/SecondOpinionOracleMock.sol +++ b/test/0.8.9/contracts/SecondOpinionOracleMock.sol @@ -26,6 +26,17 @@ contract SecondOpinionOracleMock is ISecondOpinionOracle { reports[refSlot] = report; } + function addPlainReport(uint256 refSlot, uint256 clBalanceGwei, uint256 withdrawalVaultBalanceWei) external { + + reports[refSlot] = Report({ + success: true, + clBalanceGwei: clBalanceGwei, + withdrawalVaultBalanceWei: withdrawalVaultBalanceWei, + numValidators: 0, + exitedValidators: 0 + }); + } + function removeReport(uint256 refSlot) external { delete reports[refSlot]; } diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts new file mode 100644 index 000000000..467c607eb --- /dev/null +++ b/test/integration/second-opinion.ts @@ -0,0 +1,142 @@ +import { expect } from "chai"; +import { ContractTransactionReceipt, TransactionResponse } from "ethers"; +import { ethers } from "hardhat"; + +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; + +import { SecondOpinionOracleMock } from "typechain-types"; + +import { ether, impersonate } from "lib"; +import { getProtocolContext, ProtocolContext } from "lib/protocol"; +import { + finalizeWithdrawalQueue, + norEnsureOperators, + report, + sdvtEnsureOperators +} from "lib/protocol/helpers"; + +import { Snapshot } from "test/suite"; + +const LIMITER_PRECISION_BASE = BigInt(10 ** 9); + +const SHARE_RATE_PRECISION = BigInt(10 ** 27); +const ONE_DAY = 86400n; +const MAX_BASIS_POINTS = 10000n; +const AMOUNT = ether("100"); +const MAX_DEPOSIT = 150n; +const CURATED_MODULE_ID = 1n; +const SIMPLE_DVT_MODULE_ID = 2n; + +const ZERO_HASH = new Uint8Array(32).fill(0); + +describe("Second opinion", () => { + let ctx: ProtocolContext; + + let ethHolder: HardhatEthersSigner; + let stEthHolder: HardhatEthersSigner; + + let snapshot: string; + let originalState: string; + + let secondOpinion: SecondOpinionOracleMock; + + before(async () => { + ctx = await getProtocolContext(); + + [stEthHolder, ethHolder] = await ethers.getSigners(); + + snapshot = await Snapshot.take(); + + const { lido, depositSecurityModule, oracleReportSanityChecker } = ctx.contracts; + + await finalizeWithdrawalQueue(ctx, stEthHolder, ethHolder); + + await norEnsureOperators(ctx, 3n, 5n); + if (ctx.flags.withSimpleDvtModule) { + await sdvtEnsureOperators(ctx, 3n, 5n); + } + + // const sepoliaDepositContractAddress = "0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D"; + // const bepoliaWhaleHolder = "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134"; + // const BEPOLIA_TO_TRANSFER = 20; + + // const bepoliaToken = await ethers.getContractAt("ISepoliaDepositContract", sepoliaDepositContractAddress); + // const bepiloaSigner = await ethers.getImpersonatedSigner(bepoliaWhaleHolder); + + // const adapterAddr = await ctx.contracts.stakingRouter.DEPOSIT_CONTRACT(); + // await bepoliaToken.connect(bepiloaSigner).transfer(adapterAddr, BEPOLIA_TO_TRANSFER); + + const dsmSigner = await impersonate(depositSecurityModule.address, AMOUNT); + await lido.connect(dsmSigner).deposit(MAX_DEPOSIT, CURATED_MODULE_ID, ZERO_HASH); + + secondOpinion = await ethers.deployContract("SecondOpinionOracleMock", []); + const soAddress = await secondOpinion.getAddress(); + console.log("second opinion address", soAddress); + + + const sanityAddr = await oracleReportSanityChecker.getAddress(); + console.log("sanityAddr", sanityAddr); + + const adminSigner = await impersonate("0xc00c0beC9F5C6b245A5c232598b3A2cc1558C3c7", AMOUNT); + await oracleReportSanityChecker.connect(adminSigner).grantRole( + await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), adminSigner.address); + + + console.log("Finish init"); + await oracleReportSanityChecker.connect(adminSigner).setSecondOpinionOracleAndCLBalanceUpperMargin( + soAddress, 74n); + + + await report(ctx, { + clDiff: ether("32") * 3n, // 32 ETH * 3 validators + clAppearedValidators: 3n, + excludeVaultsBalances: true, + }); + }); + + // beforeEach(bailOnFailure); + + beforeEach(async () => (originalState = await Snapshot.take())); + + afterEach(async () => await Snapshot.restore(originalState)); + + after(async () => await Snapshot.restore(snapshot)); // Rollback to the initial state pre deployment + + const getFirstEvent = (receipt: ContractTransactionReceipt, eventName: string) => { + const events = ctx.getEvents(receipt, eventName); + expect(events.length).to.be.greaterThan(0); + return events[0]; + }; + + it("Should account correctly with no CL rebase", async () => { + const { hashConsensus, accountingOracle } = ctx.contracts; + + const curFrame = await hashConsensus.getCurrentFrame(); + console.log('curFrame', curFrame); + + await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, 86000000000n, 0n); + await secondOpinion.addPlainReport(curFrame.refSlot, 86000000000n, 0n); + const testReport = await secondOpinion.getReport(curFrame.reportProcessingDeadlineSlot); + console.log('testReport', testReport); + + const lastProcessingRefSlotBefore = await accountingOracle.getLastProcessingRefSlot(); + console.log("lastProcessingRefSlotBefore", lastProcessingRefSlotBefore.toString()); + // Report + const params = { clDiff: ether("-10"), excludeVaultsBalances: true }; + const { reportTx } = (await report(ctx, params)) as { + reportTx: TransactionResponse; + extraDataTx: TransactionResponse; + }; + console.log("Finished report"); + + const reportTxReceipt = (await reportTx.wait()) as ContractTransactionReceipt; + + const lastProcessingRefSlotAfter = await accountingOracle.getLastProcessingRefSlot(); + console.log("lastProcessingRefSlotAfter", lastProcessingRefSlotAfter.toString()); + expect(lastProcessingRefSlotBefore).to.be.lessThan( + lastProcessingRefSlotAfter, + "LastProcessingRefSlot should be updated", + ); + + }); +}); From 3ff99fcf02ebc1daa623867d1d5a2e85c0372794 Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 19 Sep 2024 11:11:26 +0200 Subject: [PATCH 08/19] feat: remove unused stub --- .../testnets/sepolia/SecondOpinionStub.sol | 47 ------------------- 1 file changed, 47 deletions(-) delete mode 100644 contracts/testnets/sepolia/SecondOpinionStub.sol diff --git a/contracts/testnets/sepolia/SecondOpinionStub.sol b/contracts/testnets/sepolia/SecondOpinionStub.sol deleted file mode 100644 index fc77ad2f3..000000000 --- a/contracts/testnets/sepolia/SecondOpinionStub.sol +++ /dev/null @@ -1,47 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// for testing purposes only - -/* See contracts/COMPILERS.md */ -pragma solidity 0.8.9; - -import {ISecondOpinionOracle} from "../../0.8.9/interfaces/ISecondOpinionOracle.sol"; - -struct StubReportData { - uint256 refSlot; - bool success; - uint256 clBalanceGwei; - uint256 withdrawalVaultBalanceWei; -} - -contract SecondOpinionStub is ISecondOpinionOracle { - - mapping(uint256 => StubReportData) reports; - - /// @notice Returns second opinion report for the given reference slot - /// @param refSlot is a reference slot to return report for - /// @return success shows whether the report was successfully generated - /// @return clBalanceGwei is a balance of the consensus layer in Gwei for the ref slot - /// @return withdrawalVaultBalanceWei is a balance of the withdrawal vault in Wei for the ref slot - /// @return totalDepositedValidators is a total number of validators deposited with Lido - /// @return totalExitedValidators is a total number of Lido validators in the EXITED state - function getReport(uint256 refSlot) - external - view - returns ( - bool success, - uint256 clBalanceGwei, - uint256 withdrawalVaultBalanceWei, - uint256 totalDepositedValidators, - uint256 totalExitedValidators - ) { - StubReportData memory report = reports[refSlot]; - if (report.refSlot == refSlot) { - return (report.success, report.clBalanceGwei, report.withdrawalVaultBalanceWei, 0, 0); - } - return (false, 0, 0, 0, 0); - } - - function addReportStub(StubReportData memory data) external { - reports[data.refSlot] = data; - } -} From 66f5e7ca92409f068b78acee71c8fb4c2e22235d Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 19 Sep 2024 11:15:10 +0200 Subject: [PATCH 09/19] test: improve test --- test/integration/second-opinion.ts | 52 +++++++++++------------------- 1 file changed, 18 insertions(+), 34 deletions(-) diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts index 467c607eb..e610e161a 100644 --- a/test/integration/second-opinion.ts +++ b/test/integration/second-opinion.ts @@ -1,5 +1,5 @@ import { expect } from "chai"; -import { ContractTransactionReceipt, TransactionResponse } from "ethers"; +import { TransactionResponse } from "ethers"; import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; @@ -8,24 +8,13 @@ import { SecondOpinionOracleMock } from "typechain-types"; import { ether, impersonate } from "lib"; import { getProtocolContext, ProtocolContext } from "lib/protocol"; -import { - finalizeWithdrawalQueue, - norEnsureOperators, - report, - sdvtEnsureOperators -} from "lib/protocol/helpers"; +import { finalizeWithdrawalQueue, norEnsureOperators, report, sdvtEnsureOperators } from "lib/protocol/helpers"; import { Snapshot } from "test/suite"; -const LIMITER_PRECISION_BASE = BigInt(10 ** 9); - -const SHARE_RATE_PRECISION = BigInt(10 ** 27); -const ONE_DAY = 86400n; -const MAX_BASIS_POINTS = 10000n; const AMOUNT = ether("100"); const MAX_DEPOSIT = 150n; const CURATED_MODULE_ID = 1n; -const SIMPLE_DVT_MODULE_ID = 2n; const ZERO_HASH = new Uint8Array(32).fill(0); @@ -73,25 +62,22 @@ describe("Second opinion", () => { const soAddress = await secondOpinion.getAddress(); console.log("second opinion address", soAddress); - const sanityAddr = await oracleReportSanityChecker.getAddress(); console.log("sanityAddr", sanityAddr); - const adminSigner = await impersonate("0xc00c0beC9F5C6b245A5c232598b3A2cc1558C3c7", AMOUNT); - await oracleReportSanityChecker.connect(adminSigner).grantRole( - await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), adminSigner.address); - - - console.log("Finish init"); - await oracleReportSanityChecker.connect(adminSigner).setSecondOpinionOracleAndCLBalanceUpperMargin( - soAddress, 74n); - + const agentSigner = await ctx.getSigner("agent", AMOUNT); + await oracleReportSanityChecker + .connect(agentSigner) + .grantRole(await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), agentSigner.address); await report(ctx, { clDiff: ether("32") * 3n, // 32 ETH * 3 validators clAppearedValidators: 3n, excludeVaultsBalances: true, }); + + await oracleReportSanityChecker.connect(agentSigner).setSecondOpinionOracleAndCLBalanceUpperMargin(soAddress, 74n); + console.log("Finish init"); }); // beforeEach(bailOnFailure); @@ -102,27 +88,25 @@ describe("Second opinion", () => { after(async () => await Snapshot.restore(snapshot)); // Rollback to the initial state pre deployment - const getFirstEvent = (receipt: ContractTransactionReceipt, eventName: string) => { - const events = ctx.getEvents(receipt, eventName); - expect(events.length).to.be.greaterThan(0); - return events[0]; - }; - it("Should account correctly with no CL rebase", async () => { const { hashConsensus, accountingOracle } = ctx.contracts; const curFrame = await hashConsensus.getCurrentFrame(); - console.log('curFrame', curFrame); + console.log("curFrame", curFrame); await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, 86000000000n, 0n); - await secondOpinion.addPlainReport(curFrame.refSlot, 86000000000n, 0n); - const testReport = await secondOpinion.getReport(curFrame.reportProcessingDeadlineSlot); - console.log('testReport', testReport); + // await secondOpinion.addPlainReport(curFrame.refSlot, 86000000000n, 0n); + const testReport = await secondOpinion.getReport(curFrame.refSlot); + console.log("testReport refSlot", curFrame.refSlot, testReport); + const testReport2 = await secondOpinion.getReport(curFrame.reportProcessingDeadlineSlot); + console.log("testReport reportProcessingDeadlineSlot", curFrame.reportProcessingDeadlineSlot, testReport2); const lastProcessingRefSlotBefore = await accountingOracle.getLastProcessingRefSlot(); console.log("lastProcessingRefSlotBefore", lastProcessingRefSlotBefore.toString()); // Report const params = { clDiff: ether("-10"), excludeVaultsBalances: true }; + + // Tracing.enable(); const { reportTx } = (await report(ctx, params)) as { reportTx: TransactionResponse; extraDataTx: TransactionResponse; @@ -130,6 +114,7 @@ describe("Second opinion", () => { console.log("Finished report"); const reportTxReceipt = (await reportTx.wait()) as ContractTransactionReceipt; + console.log("reportTxReceipt", reportTxReceipt); const lastProcessingRefSlotAfter = await accountingOracle.getLastProcessingRefSlot(); console.log("lastProcessingRefSlotAfter", lastProcessingRefSlotAfter.toString()); @@ -137,6 +122,5 @@ describe("Second opinion", () => { lastProcessingRefSlotAfter, "LastProcessingRefSlot should be updated", ); - }); }); From 32494492c6e26468c74120ebcaeceabb2b29b36c Mon Sep 17 00:00:00 2001 From: VP Date: Thu, 19 Sep 2024 11:15:43 +0200 Subject: [PATCH 10/19] fix: modify accounting to enable negative rebase checker --- lib/protocol/helpers/accounting.ts | 70 ++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/lib/protocol/helpers/accounting.ts b/lib/protocol/helpers/accounting.ts index b6a9f4dec..3f48eaaed 100644 --- a/lib/protocol/helpers/accounting.ts +++ b/lib/protocol/helpers/accounting.ts @@ -17,6 +17,7 @@ import { impersonate, log, ONE_GWEI, + streccak, trace, } from "lib"; @@ -352,19 +353,62 @@ const simulateReport = async ( "El Rewards Vault Balance": formatEther(elRewardsVaultBalance), }); - const [postTotalPooledEther, postTotalShares, withdrawals, elRewards] = await lido - .connect(accountingOracleAccount) - .handleOracleReport.staticCall( - reportTimestamp, - 1n * 24n * 60n * 60n, // 1 day - beaconValidators, - clBalance, - withdrawalVaultBalance, - elRewardsVaultBalance, - 0n, - [], - 0n, - ); + // NOTE: To enable negative rebase sanity checker, the static call below should be + // replaced with advanced eth_call with stateDiff. + + // const [postTotalPooledEther1, postTotalShares1, withdrawals1, elRewards1] = await lido + // .connect(accountingOracleAccount) + // .handleOracleReport.staticCall( + // reportTimestamp, + // 1n * 24n * 60n * 60n, // 1 day + // beaconValidators, + // clBalance, + // withdrawalVaultBalance, + // elRewardsVaultBalance, + // 0n, + // [], + // 0n, + // ); + + // Step 1: Encode the function call data + const data = lido.interface.encodeFunctionData("handleOracleReport", [ + reportTimestamp, + BigInt(24 * 60 * 60), // 1 day in seconds + beaconValidators, + clBalance, + withdrawalVaultBalance, + elRewardsVaultBalance, + BigInt(0), + [], + BigInt(0), + ]); + + // Step 2: Prepare the transaction object + const transactionObject = { + to: lido.address, + from: accountingOracleAccount.address, + data: data, + }; + + // Step 3: Prepare call parameters, state diff and perform eth_call + const accountingOracleAddr = await accountingOracle.getAddress(); + const callParams = [transactionObject, "latest"]; + const LAST_PROCESSING_REF_SLOT_POSITION = streccak("lido.BaseOracle.lastProcessingRefSlot"); + const stateDiff = { + [accountingOracleAddr]: { + stateDiff: { + [LAST_PROCESSING_REF_SLOT_POSITION]: refSlot, // setting the processing refslot for the sanity checker + }, + }, + }; + + const returnData = await ethers.provider.send("eth_call", [...callParams, stateDiff]); + + // Step 4: Decode the returned data + const [[postTotalPooledEther, postTotalShares, withdrawals, elRewards]] = lido.interface.decodeFunctionResult( + "handleOracleReport", + returnData, + ); log.debug("Simulation result", { "Post Total Pooled Ether": formatEther(postTotalPooledEther), From c0f4b9819930bef8a51ed47454ca05c02d82051b Mon Sep 17 00:00:00 2001 From: VP Date: Mon, 7 Oct 2024 14:10:19 +0200 Subject: [PATCH 11/19] test: refine second opinion test --- test/integration/second-opinion.ts | 71 ++++++++++++------------------ 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts index e610e161a..eb334151f 100644 --- a/test/integration/second-opinion.ts +++ b/test/integration/second-opinion.ts @@ -1,20 +1,21 @@ import { expect } from "chai"; -import { TransactionResponse } from "ethers"; import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { SecondOpinionOracleMock } from "typechain-types"; -import { ether, impersonate } from "lib"; +import { ether, impersonate, ONE_GWEI } from "lib"; import { getProtocolContext, ProtocolContext } from "lib/protocol"; import { finalizeWithdrawalQueue, norEnsureOperators, report, sdvtEnsureOperators } from "lib/protocol/helpers"; -import { Snapshot } from "test/suite"; +import { bailOnFailure, Snapshot } from "test/suite"; const AMOUNT = ether("100"); const MAX_DEPOSIT = 150n; const CURATED_MODULE_ID = 1n; +const INITIAL_REPORTED_BALANCE = ether("32") * 3n; // 32 ETH * 3 validators +const DIFF_AMOUNT = ether("10"); const ZERO_HASH = new Uint8Array(32).fill(0); @@ -45,25 +46,25 @@ describe("Second opinion", () => { await sdvtEnsureOperators(ctx, 3n, 5n); } - // const sepoliaDepositContractAddress = "0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D"; - // const bepoliaWhaleHolder = "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134"; - // const BEPOLIA_TO_TRANSFER = 20; + const { chainId } = await ethers.provider.getNetwork(); + // Sepolia-specific initialization + if (chainId === 11155111n) { + // Sepolia deposit contract address https://sepolia.etherscan.io/token/0x7f02c3e3c98b133055b8b348b2ac625669ed295d + const sepoliaDepositContractAddress = "0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D"; + const bepoliaWhaleHolder = "0xf97e180c050e5Ab072211Ad2C213Eb5AEE4DF134"; + const BEPOLIA_TO_TRANSFER = 20; - // const bepoliaToken = await ethers.getContractAt("ISepoliaDepositContract", sepoliaDepositContractAddress); - // const bepiloaSigner = await ethers.getImpersonatedSigner(bepoliaWhaleHolder); - - // const adapterAddr = await ctx.contracts.stakingRouter.DEPOSIT_CONTRACT(); - // await bepoliaToken.connect(bepiloaSigner).transfer(adapterAddr, BEPOLIA_TO_TRANSFER); + const bepoliaToken = await ethers.getContractAt("ISepoliaDepositContract", sepoliaDepositContractAddress); + const bepiloaSigner = await ethers.getImpersonatedSigner(bepoliaWhaleHolder); + const adapterAddr = await ctx.contracts.stakingRouter.DEPOSIT_CONTRACT(); + await bepoliaToken.connect(bepiloaSigner).transfer(adapterAddr, BEPOLIA_TO_TRANSFER); + } const dsmSigner = await impersonate(depositSecurityModule.address, AMOUNT); await lido.connect(dsmSigner).deposit(MAX_DEPOSIT, CURATED_MODULE_ID, ZERO_HASH); secondOpinion = await ethers.deployContract("SecondOpinionOracleMock", []); const soAddress = await secondOpinion.getAddress(); - console.log("second opinion address", soAddress); - - const sanityAddr = await oracleReportSanityChecker.getAddress(); - console.log("sanityAddr", sanityAddr); const agentSigner = await ctx.getSigner("agent", AMOUNT); await oracleReportSanityChecker @@ -71,16 +72,15 @@ describe("Second opinion", () => { .grantRole(await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), agentSigner.address); await report(ctx, { - clDiff: ether("32") * 3n, // 32 ETH * 3 validators + clDiff: INITIAL_REPORTED_BALANCE, clAppearedValidators: 3n, excludeVaultsBalances: true, }); await oracleReportSanityChecker.connect(agentSigner).setSecondOpinionOracleAndCLBalanceUpperMargin(soAddress, 74n); - console.log("Finish init"); }); - // beforeEach(bailOnFailure); + beforeEach(bailOnFailure); beforeEach(async () => (originalState = await Snapshot.take())); @@ -89,35 +89,22 @@ describe("Second opinion", () => { after(async () => await Snapshot.restore(snapshot)); // Rollback to the initial state pre deployment it("Should account correctly with no CL rebase", async () => { - const { hashConsensus, accountingOracle } = ctx.contracts; + const { hashConsensus, accountingOracle, oracleReportSanityChecker } = ctx.contracts; - const curFrame = await hashConsensus.getCurrentFrame(); - console.log("curFrame", curFrame); + // Report without second opinion is failing + await expect(report(ctx, { clDiff: -DIFF_AMOUNT, excludeVaultsBalances: true })).to.be.revertedWithCustomError( + oracleReportSanityChecker, + "NegativeRebaseFailedSecondOpinionReportIsNotReady", + ); - await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, 86000000000n, 0n); - // await secondOpinion.addPlainReport(curFrame.refSlot, 86000000000n, 0n); - const testReport = await secondOpinion.getReport(curFrame.refSlot); - console.log("testReport refSlot", curFrame.refSlot, testReport); - const testReport2 = await secondOpinion.getReport(curFrame.reportProcessingDeadlineSlot); - console.log("testReport reportProcessingDeadlineSlot", curFrame.reportProcessingDeadlineSlot, testReport2); + // Provide a second opinion + const curFrame = await hashConsensus.getCurrentFrame(); + const expectedBalance = (INITIAL_REPORTED_BALANCE - DIFF_AMOUNT) / ONE_GWEI; + await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, expectedBalance, 0n); const lastProcessingRefSlotBefore = await accountingOracle.getLastProcessingRefSlot(); - console.log("lastProcessingRefSlotBefore", lastProcessingRefSlotBefore.toString()); - // Report - const params = { clDiff: ether("-10"), excludeVaultsBalances: true }; - - // Tracing.enable(); - const { reportTx } = (await report(ctx, params)) as { - reportTx: TransactionResponse; - extraDataTx: TransactionResponse; - }; - console.log("Finished report"); - - const reportTxReceipt = (await reportTx.wait()) as ContractTransactionReceipt; - console.log("reportTxReceipt", reportTxReceipt); - + await report(ctx, { clDiff: -DIFF_AMOUNT, excludeVaultsBalances: true }); const lastProcessingRefSlotAfter = await accountingOracle.getLastProcessingRefSlot(); - console.log("lastProcessingRefSlotAfter", lastProcessingRefSlotAfter.toString()); expect(lastProcessingRefSlotBefore).to.be.lessThan( lastProcessingRefSlotAfter, "LastProcessingRefSlot should be updated", From f45f506ab6da5f8d5739d3fbe3213e25e0596dad Mon Sep 17 00:00:00 2001 From: VP Date: Mon, 7 Oct 2024 14:11:01 +0200 Subject: [PATCH 12/19] feat: refine comments --- lib/protocol/helpers/accounting.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/protocol/helpers/accounting.ts b/lib/protocol/helpers/accounting.ts index 3f48eaaed..7ff51943c 100644 --- a/lib/protocol/helpers/accounting.ts +++ b/lib/protocol/helpers/accounting.ts @@ -353,9 +353,8 @@ const simulateReport = async ( "El Rewards Vault Balance": formatEther(elRewardsVaultBalance), }); - // NOTE: To enable negative rebase sanity checker, the static call below should be + // NOTE: To enable negative rebase sanity checker, the static call below // replaced with advanced eth_call with stateDiff. - // const [postTotalPooledEther1, postTotalShares1, withdrawals1, elRewards1] = await lido // .connect(accountingOracleAccount) // .handleOracleReport.staticCall( From 2ec00ccbe37f169eec28e4eb8925a806a463d00b Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Fri, 11 Oct 2024 13:01:18 +0100 Subject: [PATCH 13/19] fix: tests --- test/integration/second-opinion.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts index eb334151f..4e71bb382 100644 --- a/test/integration/second-opinion.ts +++ b/test/integration/second-opinion.ts @@ -42,9 +42,7 @@ describe("Second opinion", () => { await finalizeWithdrawalQueue(ctx, stEthHolder, ethHolder); await norEnsureOperators(ctx, 3n, 5n); - if (ctx.flags.withSimpleDvtModule) { - await sdvtEnsureOperators(ctx, 3n, 5n); - } + await sdvtEnsureOperators(ctx, 3n, 5n); const { chainId } = await ethers.provider.getNetwork(); // Sepolia-specific initialization From a862c7a100fe31ac5d426e565cae7b8833341144 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Fri, 11 Oct 2024 16:57:55 +0100 Subject: [PATCH 14/19] fix: fix mainnet fork update for sanity checker --- .../mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/upgrade/steps/mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts b/scripts/upgrade/steps/mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts index 1d97aeca0..efb27be67 100644 --- a/scripts/upgrade/steps/mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts +++ b/scripts/upgrade/steps/mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts @@ -60,7 +60,7 @@ export async function main() { const proxyLocator = await ethers.getContractAt("OssifiableProxy", locatorAddress); const proxyAdmin = await proxyLocator.proxy__getAdmin(); - const proxyAdminSigner = await impersonate(proxyAdmin, ether("1")); + const proxyAdminSigner = await impersonate(proxyAdmin, ether("100")); await updateLidoLocatorImplementation( locatorAddress, From 071b0df661c7d5447196abbcdd6b5bd895c175b7 Mon Sep 17 00:00:00 2001 From: VP Date: Fri, 11 Oct 2024 20:06:31 +0200 Subject: [PATCH 15/19] feat: fix clBalanceOraclesErrorUpperBPLimit value --- .../mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/upgrade/steps/mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts b/scripts/upgrade/steps/mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts index 1d97aeca0..bc4621390 100644 --- a/scripts/upgrade/steps/mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts +++ b/scripts/upgrade/steps/mainnet-fork/0010-deploy-negative-rebase-sanity-checker.ts @@ -25,7 +25,7 @@ export async function main() { maxPositiveTokenRebase: 750_000, // 0.0075% initialSlashingAmountPWei: 1000, // 1 ETH = 1000 PWei inactivityPenaltiesAmountPWei: 101, // 0.101 ETH = 101 PWei - clBalanceOraclesErrorUpperBPLimit: 74, // 0.74% + clBalanceOraclesErrorUpperBPLimit: 50, // 0.5% }; // Deploy OracleReportSanityChecker From 261f90f1ab4b09737862e35e955eab6434c937ae Mon Sep 17 00:00:00 2001 From: VP Date: Fri, 11 Oct 2024 20:45:02 +0200 Subject: [PATCH 16/19] fix: clBalanceOraclesErrorUpperBPLimit value --- scripts/scratch/deployed-testnet-defaults.json | 2 +- test/0.8.9/sanityChecker/oracleReportSanityChecker.misc.test.ts | 2 +- .../oracleReportSanityChecker.negative-rebase.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/scratch/deployed-testnet-defaults.json b/scripts/scratch/deployed-testnet-defaults.json index b80aa0298..c425ca0ac 100644 --- a/scripts/scratch/deployed-testnet-defaults.json +++ b/scripts/scratch/deployed-testnet-defaults.json @@ -113,7 +113,7 @@ "maxPositiveTokenRebase": 5000000, "initialSlashingAmountPWei": 1000, "inactivityPenaltiesAmountPWei": 101, - "clBalanceOraclesErrorUpperBPLimit": 74 + "clBalanceOraclesErrorUpperBPLimit": 50 } }, "oracleDaemonConfig": { diff --git a/test/0.8.9/sanityChecker/oracleReportSanityChecker.misc.test.ts b/test/0.8.9/sanityChecker/oracleReportSanityChecker.misc.test.ts index eb84ac055..f85e8083c 100644 --- a/test/0.8.9/sanityChecker/oracleReportSanityChecker.misc.test.ts +++ b/test/0.8.9/sanityChecker/oracleReportSanityChecker.misc.test.ts @@ -38,7 +38,7 @@ describe("OracleReportSanityChecker.sol:misc", () => { maxPositiveTokenRebase: 5_000_000n, // 0.05% initialSlashingAmountPWei: 1000n, inactivityPenaltiesAmountPWei: 101n, - clBalanceOraclesErrorUpperBPLimit: 74n, // 0.74% + clBalanceOraclesErrorUpperBPLimit: 50n, // 0.5% }; const correctLidoOracleReport = { diff --git a/test/0.8.9/sanityChecker/oracleReportSanityChecker.negative-rebase.test.ts b/test/0.8.9/sanityChecker/oracleReportSanityChecker.negative-rebase.test.ts index c91ed0625..6f912b232 100644 --- a/test/0.8.9/sanityChecker/oracleReportSanityChecker.negative-rebase.test.ts +++ b/test/0.8.9/sanityChecker/oracleReportSanityChecker.negative-rebase.test.ts @@ -37,7 +37,7 @@ describe("OracleReportSanityChecker.sol:negative-rebase", () => { maxPositiveTokenRebase: 5_000_000n, // 0.05% initialSlashingAmountPWei: 1000n, // 1 ETH = 1000 PWei inactivityPenaltiesAmountPWei: 101n, // 0.101 ETH = 101 PWei - clBalanceOraclesErrorUpperBPLimit: 74n, // 0.74% + clBalanceOraclesErrorUpperBPLimit: 50n, // 0.5% }; let originalState: string; From ba3c50ecdf58451caf93e86dd316e654a9add79a Mon Sep 17 00:00:00 2001 From: VP Date: Fri, 11 Oct 2024 20:47:48 +0200 Subject: [PATCH 17/19] test: improve second opinion tests --- test/integration/second-opinion.ts | 110 +++++++++++++++++++++++++---- 1 file changed, 97 insertions(+), 13 deletions(-) diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts index 4e71bb382..b91c117ec 100644 --- a/test/integration/second-opinion.ts +++ b/test/integration/second-opinion.ts @@ -5,7 +5,7 @@ import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { SecondOpinionOracleMock } from "typechain-types"; -import { ether, impersonate, ONE_GWEI } from "lib"; +import { ether, impersonate, log, ONE_GWEI } from "lib"; import { getProtocolContext, ProtocolContext } from "lib/protocol"; import { finalizeWithdrawalQueue, norEnsureOperators, report, sdvtEnsureOperators } from "lib/protocol/helpers"; @@ -15,10 +15,14 @@ const AMOUNT = ether("100"); const MAX_DEPOSIT = 150n; const CURATED_MODULE_ID = 1n; const INITIAL_REPORTED_BALANCE = ether("32") * 3n; // 32 ETH * 3 validators -const DIFF_AMOUNT = ether("10"); const ZERO_HASH = new Uint8Array(32).fill(0); +// Diff amount is 10% of total supply +function getDiffAmount(totalSupply: bigint): bigint { + return (totalSupply / 10n / ONE_GWEI) * ONE_GWEI; +} + describe("Second opinion", () => { let ctx: ProtocolContext; @@ -29,6 +33,7 @@ describe("Second opinion", () => { let originalState: string; let secondOpinion: SecondOpinionOracleMock; + let totalSupply: bigint; before(async () => { ctx = await getProtocolContext(); @@ -69,11 +74,17 @@ describe("Second opinion", () => { .connect(agentSigner) .grantRole(await oracleReportSanityChecker.SECOND_OPINION_MANAGER_ROLE(), agentSigner.address); - await report(ctx, { - clDiff: INITIAL_REPORTED_BALANCE, - clAppearedValidators: 3n, - excludeVaultsBalances: true, - }); + let { beaconBalance } = await lido.getBeaconStat(); + // Report initial balances if TVL is zero + if (beaconBalance === 0n) { + await report(ctx, { + clDiff: INITIAL_REPORTED_BALANCE, + clAppearedValidators: 3n, + excludeVaultsBalances: true, + }); + beaconBalance = (await lido.getBeaconStat()).beaconBalance; + } + totalSupply = beaconBalance; await oracleReportSanityChecker.connect(agentSigner).setSecondOpinionOracleAndCLBalanceUpperMargin(soAddress, 74n); }); @@ -86,26 +97,99 @@ describe("Second opinion", () => { after(async () => await Snapshot.restore(snapshot)); // Rollback to the initial state pre deployment - it("Should account correctly with no CL rebase", async () => { - const { hashConsensus, accountingOracle, oracleReportSanityChecker } = ctx.contracts; + it("Should fail report without second opinion ready", async () => { + const { oracleReportSanityChecker } = ctx.contracts; - // Report without second opinion is failing - await expect(report(ctx, { clDiff: -DIFF_AMOUNT, excludeVaultsBalances: true })).to.be.revertedWithCustomError( + const reportedDiff = getDiffAmount(totalSupply); + + await expect(report(ctx, { clDiff: -reportedDiff, excludeVaultsBalances: true })).to.be.revertedWithCustomError( oracleReportSanityChecker, "NegativeRebaseFailedSecondOpinionReportIsNotReady", ); + }); + + it("Should correctly report negative rebase with second opinion", async () => { + const { hashConsensus, accountingOracle } = ctx.contracts; + + const reportedDiff = getDiffAmount(totalSupply); // Provide a second opinion const curFrame = await hashConsensus.getCurrentFrame(); - const expectedBalance = (INITIAL_REPORTED_BALANCE - DIFF_AMOUNT) / ONE_GWEI; + const expectedBalance = (totalSupply - reportedDiff) / ONE_GWEI; + await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, expectedBalance, 0n); + + const lastProcessingRefSlotBefore = await accountingOracle.getLastProcessingRefSlot(); + await report(ctx, { clDiff: -reportedDiff, excludeVaultsBalances: true }); + const lastProcessingRefSlotAfter = await accountingOracle.getLastProcessingRefSlot(); + expect(lastProcessingRefSlotBefore).to.be.lessThan( + lastProcessingRefSlotAfter, + "LastProcessingRefSlot should be updated", + ); + }); + + it("Should fail report with smaller second opinion cl balance", async () => { + const { hashConsensus, oracleReportSanityChecker } = ctx.contracts; + + const reportedDiff = getDiffAmount(totalSupply); + + const curFrame = await hashConsensus.getCurrentFrame(); + const expectedBalance = (totalSupply - reportedDiff) / ONE_GWEI - 1n; await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, expectedBalance, 0n); + await expect(report(ctx, { clDiff: -reportedDiff, excludeVaultsBalances: true })).to.be.revertedWithCustomError( + oracleReportSanityChecker, + "NegativeRebaseFailedCLBalanceMismatch", + ); + }); + + it("Should tolerate report with slightly bigger second opinion cl balance", async () => { + const { hashConsensus, accountingOracle } = ctx.contracts; + + const reportedDiff = getDiffAmount(totalSupply); + + const curFrame = await hashConsensus.getCurrentFrame(); + const expectedBalance = (totalSupply - reportedDiff) / ONE_GWEI; + // Less than 0.5% diff in balances + const correction = (expectedBalance * 4n) / 1000n; + await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, expectedBalance + correction, 0n); + log.debug("Reporting parameters", { + totalSupply, + reportedDiff, + expectedBalance, + correction, + reportedBalance: totalSupply - reportedDiff, + }); + const lastProcessingRefSlotBefore = await accountingOracle.getLastProcessingRefSlot(); - await report(ctx, { clDiff: -DIFF_AMOUNT, excludeVaultsBalances: true }); + await report(ctx, { clDiff: -reportedDiff, excludeVaultsBalances: true }); const lastProcessingRefSlotAfter = await accountingOracle.getLastProcessingRefSlot(); expect(lastProcessingRefSlotBefore).to.be.lessThan( lastProcessingRefSlotAfter, "LastProcessingRefSlot should be updated", ); }); + + it("Should fail report with significantly bigger second opinion cl balance", async () => { + const { hashConsensus, oracleReportSanityChecker } = ctx.contracts; + + const reportedDiff = getDiffAmount(totalSupply); + + const curFrame = await hashConsensus.getCurrentFrame(); + const expectedBalance = (totalSupply - reportedDiff) / ONE_GWEI; + // More than 0.5% diff in balances + const correction = (expectedBalance * 9n) / 1000n; + await secondOpinion.addPlainReport(curFrame.reportProcessingDeadlineSlot, expectedBalance + correction, 0n); + log.debug("Reporting parameters", { + totalSupply, + reportedDiff, + expectedBalance, + correction, + "expected + correction": expectedBalance + correction, + }); + + await expect(report(ctx, { clDiff: -reportedDiff, excludeVaultsBalances: true })).to.be.revertedWithCustomError( + oracleReportSanityChecker, + "NegativeRebaseFailedCLBalanceMismatch", + ); + }); }); From 7dea205ddd546a6d72d35923b20af3210c9fbf3c Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Mon, 14 Oct 2024 13:14:23 +0100 Subject: [PATCH 18/19] fix: remove PostTotalShares event checks --- test/integration/accounting.integration.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/test/integration/accounting.integration.ts b/test/integration/accounting.integration.ts index fa4579c7f..172ad9fff 100644 --- a/test/integration/accounting.integration.ts +++ b/test/integration/accounting.integration.ts @@ -207,11 +207,6 @@ describe("Accounting", () => { const { sharesRateBefore, sharesRateAfter } = shareRateFromEvent(tokenRebasedEvent[0]); expect(sharesRateBefore).to.be.lessThanOrEqual(sharesRateAfter); - const postTotalSharesEvent = ctx.getEvents(reportTxReceipt, "PostTotalShares"); - expect(postTotalSharesEvent[0].args.preTotalPooledEther).to.equal( - postTotalSharesEvent[0].args.postTotalPooledEther + amountOfETHLocked, - ); - const ethBalanceAfter = await ethers.provider.getBalance(lido.address); expect(ethBalanceBefore).to.equal(ethBalanceAfter + amountOfETHLocked); }); @@ -260,12 +255,6 @@ describe("Accounting", () => { ethDistributedEvent[0].args.postCLBalance, "ETHDistributed: CL balance differs from expected", ); - - const postTotalSharesEvent = ctx.getEvents(reportTxReceipt, "PostTotalShares"); - expect(postTotalSharesEvent[0].args.preTotalPooledEther + REBASE_AMOUNT).to.equal( - postTotalSharesEvent[0].args.postTotalPooledEther + amountOfETHLocked, - "PostTotalShares: TotalPooledEther differs from expected", - ); }); it("Should account correctly with positive CL rebase close to the limits", async () => { @@ -382,12 +371,6 @@ describe("Accounting", () => { ethDistributedEvent[0].args.postCLBalance, "ETHDistributed: CL balance has not increased", ); - - const postTotalSharesEvent = ctx.getEvents(reportTxReceipt, "PostTotalShares"); - expect(postTotalSharesEvent[0].args.preTotalPooledEther + rebaseAmount).to.equal( - postTotalSharesEvent[0].args.postTotalPooledEther + amountOfETHLocked, - "PostTotalShares: TotalPooledEther has not increased", - ); }); it("Should account correctly if no EL rewards", async () => { From 5888b1c504bbdc9968fd91ca3851262b46579623 Mon Sep 17 00:00:00 2001 From: Yuri Tkachenko Date: Mon, 14 Oct 2024 13:22:40 +0100 Subject: [PATCH 19/19] chore: fix SecondOpinionOracleMock naming --- ...dOpinionOracleMock.sol => SecondOpinionOracle__Mock.sol} | 2 +- .../oracleReportSanityChecker.negative-rebase.test.ts | 2 +- test/integration/second-opinion.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) rename test/0.8.9/contracts/{SecondOpinionOracleMock.sol => SecondOpinionOracle__Mock.sol} (96%) diff --git a/test/0.8.9/contracts/SecondOpinionOracleMock.sol b/test/0.8.9/contracts/SecondOpinionOracle__Mock.sol similarity index 96% rename from test/0.8.9/contracts/SecondOpinionOracleMock.sol rename to test/0.8.9/contracts/SecondOpinionOracle__Mock.sol index 52dc687b9..17fda805c 100644 --- a/test/0.8.9/contracts/SecondOpinionOracleMock.sol +++ b/test/0.8.9/contracts/SecondOpinionOracle__Mock.sol @@ -10,7 +10,7 @@ interface ISecondOpinionOracle { returns (bool success, uint256 clBalanceGwei, uint256 withdrawalVaultBalanceWei, uint256 numValidators, uint256 exitedValidators); } -contract SecondOpinionOracleMock is ISecondOpinionOracle { +contract SecondOpinionOracle__Mock is ISecondOpinionOracle { struct Report { bool success; diff --git a/test/0.8.9/sanityChecker/oracleReportSanityChecker.negative-rebase.test.ts b/test/0.8.9/sanityChecker/oracleReportSanityChecker.negative-rebase.test.ts index 6f912b232..4c2b84ce9 100644 --- a/test/0.8.9/sanityChecker/oracleReportSanityChecker.negative-rebase.test.ts +++ b/test/0.8.9/sanityChecker/oracleReportSanityChecker.negative-rebase.test.ts @@ -43,7 +43,7 @@ describe("OracleReportSanityChecker.sol:negative-rebase", () => { let originalState: string; const deploySecondOpinionOracle = async () => { - const secondOpinionOracle = await ethers.deployContract("SecondOpinionOracleMock"); + const secondOpinionOracle = await ethers.deployContract("SecondOpinionOracle__Mock"); const clOraclesRole = await checker.SECOND_OPINION_MANAGER_ROLE(); await checker.grantRole(clOraclesRole, deployer.address); diff --git a/test/integration/second-opinion.ts b/test/integration/second-opinion.ts index b91c117ec..75a7c0242 100644 --- a/test/integration/second-opinion.ts +++ b/test/integration/second-opinion.ts @@ -3,7 +3,7 @@ import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -import { SecondOpinionOracleMock } from "typechain-types"; +import { SecondOpinionOracle__Mock } from "typechain-types"; import { ether, impersonate, log, ONE_GWEI } from "lib"; import { getProtocolContext, ProtocolContext } from "lib/protocol"; @@ -32,7 +32,7 @@ describe("Second opinion", () => { let snapshot: string; let originalState: string; - let secondOpinion: SecondOpinionOracleMock; + let secondOpinion: SecondOpinionOracle__Mock; let totalSupply: bigint; before(async () => { @@ -66,7 +66,7 @@ describe("Second opinion", () => { const dsmSigner = await impersonate(depositSecurityModule.address, AMOUNT); await lido.connect(dsmSigner).deposit(MAX_DEPOSIT, CURATED_MODULE_ID, ZERO_HASH); - secondOpinion = await ethers.deployContract("SecondOpinionOracleMock", []); + secondOpinion = await ethers.deployContract("SecondOpinionOracle__Mock", []); const soAddress = await secondOpinion.getAddress(); const agentSigner = await ctx.getSigner("agent", AMOUNT);