diff --git a/package.json b/package.json index a8711c17c..6588b91fc 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,8 @@ "typecheck": "tsc --noEmit", "prepare": "husky", "abis:extract": "hardhat abis:extract", - "verify:deployed": "hardhat verify:deployed" + "verify:deployed": "hardhat verify:deployed", + "postinstall": "husky" }, "lint-staged": { "./**/*.ts": [ diff --git a/test/0.4.24/contracts/Burner__MockForAccounting.sol b/test/0.4.24/contracts/Burner__MockForAccounting.sol index 6e7aa40f5..a8a3bd36d 100644 --- a/test/0.4.24/contracts/Burner__MockForAccounting.sol +++ b/test/0.4.24/contracts/Burner__MockForAccounting.sol @@ -4,24 +4,24 @@ pragma solidity 0.4.24; contract Burner__MockForAccounting { - event StETHBurnRequested( + event Mock__StETHBurnRequested( bool indexed isCover, address indexed requestedBy, uint256 amountOfStETH, uint256 amountOfShares ); - event Mock__CommitSharesToBurnWasCalled(); + event Mock__CommitSharesToBurnWasCalled(uint256 sharesToBurn); function requestBurnShares(address, uint256 _sharesAmountToBurn) external { // imitating share to steth rate 1:2 uint256 _stETHAmount = _sharesAmountToBurn * 2; - emit StETHBurnRequested(false, msg.sender, _stETHAmount, _sharesAmountToBurn); + emit Mock__StETHBurnRequested(false, msg.sender, _stETHAmount, _sharesAmountToBurn); } function commitSharesToBurn(uint256 _sharesToBurn) external { _sharesToBurn; - emit Mock__CommitSharesToBurnWasCalled(); + emit Mock__CommitSharesToBurnWasCalled(_sharesToBurn); } } diff --git a/test/0.4.24/contracts/LidoExecutionLayerRewardsVault__MockForLidoAccounting.sol b/test/0.4.24/contracts/LidoExecutionLayerRewardsVault__MockForLidoAccounting.sol deleted file mode 100644 index 0dc35aa7d..000000000 --- a/test/0.4.24/contracts/LidoExecutionLayerRewardsVault__MockForLidoAccounting.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// for testing purposes only - -pragma solidity 0.4.24; - -contract LidoExecutionLayerRewardsVault__MockForLidoAccounting { - event Mock__RewardsWithdrawn(); - - function withdrawRewards(uint256 _maxAmount) external returns (uint256 amount) { - // emitting mock event to test that the function was in fact called - emit Mock__RewardsWithdrawn(); - return _maxAmount; - } -} diff --git a/test/0.4.24/contracts/WithdrawalVault__MockForLidoAccounting.sol b/test/0.4.24/contracts/WithdrawalVault__MockForLidoAccounting.sol deleted file mode 100644 index fccca7ecd..000000000 --- a/test/0.4.24/contracts/WithdrawalVault__MockForLidoAccounting.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// for testing purposes only - -pragma solidity 0.4.24; - -contract WithdrawalVault__MockForLidoAccounting { - event Mock__WithdrawalsWithdrawn(); - - function withdrawWithdrawals(uint256 _amount) external { - _amount; - - // emitting mock event to test that the function was in fact called - emit Mock__WithdrawalsWithdrawn(); - } -} diff --git a/test/0.4.24/lido/lido.accounting.test.ts b/test/0.4.24/lido/lido.accounting.test.ts index 719b7d97b..c3fdbab17 100644 --- a/test/0.4.24/lido/lido.accounting.test.ts +++ b/test/0.4.24/lido/lido.accounting.test.ts @@ -1,45 +1,44 @@ import { expect } from "chai"; -import { BigNumberish } from "ethers"; import { ethers } from "hardhat"; import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; import { ACL, + Burner__MockForAccounting, + Burner__MockForAccounting__factory, Lido, - LidoExecutionLayerRewardsVault__MockForLidoAccounting, - LidoExecutionLayerRewardsVault__MockForLidoAccounting__factory, + LidoLocator, + LidoLocator__factory, StakingRouter__MockForLidoAccounting, StakingRouter__MockForLidoAccounting__factory, - WithdrawalVault__MockForLidoAccounting, - WithdrawalVault__MockForLidoAccounting__factory, + WithdrawalQueue__MockForAccounting, + WithdrawalQueue__MockForAccounting__factory, } from "typechain-types"; +import { ether, getNextBlockTimestamp, impersonate } from "lib"; + import { deployLidoDao } from "test/deploy"; describe("Lido:accounting", () => { let deployer: HardhatEthersSigner; - let accounting: HardhatEthersSigner; - // let stethWhale: HardhatEthersSigner; let stranger: HardhatEthersSigner; - let withdrawalQueue: HardhatEthersSigner; let lido: Lido; let acl: ACL; - // let locator: LidoLocator; + let locator: LidoLocator; - let elRewardsVault: LidoExecutionLayerRewardsVault__MockForLidoAccounting; - let withdrawalVault: WithdrawalVault__MockForLidoAccounting; let stakingRouter: StakingRouter__MockForLidoAccounting; + let withdrawalQueue: WithdrawalQueue__MockForAccounting; + let burner: Burner__MockForAccounting; beforeEach(async () => { - // [deployer, accounting, stethWhale, stranger, withdrawalQueue] = await ethers.getSigners(); - [deployer, accounting, stranger, withdrawalQueue] = await ethers.getSigners(); + [deployer, stranger] = await ethers.getSigners(); - [elRewardsVault, stakingRouter, withdrawalVault] = await Promise.all([ - new LidoExecutionLayerRewardsVault__MockForLidoAccounting__factory(deployer).deploy(), + [stakingRouter, withdrawalQueue, burner] = await Promise.all([ new StakingRouter__MockForLidoAccounting__factory(deployer).deploy(), - new WithdrawalVault__MockForLidoAccounting__factory(deployer).deploy(), + new WithdrawalQueue__MockForAccounting__factory(deployer).deploy(), + new Burner__MockForAccounting__factory(deployer).deploy(), ]); ({ lido, acl } = await deployLidoDao({ @@ -47,21 +46,16 @@ describe("Lido:accounting", () => { initialized: true, locatorConfig: { withdrawalQueue, - elRewardsVault, - withdrawalVault, stakingRouter, - accounting, + burner, }, })); - - // locator = LidoLocator__factory.connect(await lido.getLidoLocator(), deployer); + locator = LidoLocator__factory.connect(await lido.getLidoLocator(), deployer); await acl.createPermission(deployer, lido, await lido.RESUME_ROLE(), deployer); await acl.createPermission(deployer, lido, await lido.PAUSE_ROLE(), deployer); await acl.createPermission(deployer, lido, await lido.UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE(), deployer); await lido.resume(); - - lido = lido.connect(accounting); }); context("processClStateUpdate", async () => { @@ -75,6 +69,8 @@ describe("Lido:accounting", () => { }); it("Updates beacon stats", async () => { + const accountingSigner = await impersonate(await locator.accounting(), ether("100.0")); + lido = lido.connect(accountingSigner); await expect( lido.processClStateUpdate( ...args({ @@ -87,13 +83,13 @@ describe("Lido:accounting", () => { .withArgs(0n, 0n, 100n); }); - type ArgsTuple = [BigNumberish, BigNumberish, BigNumberish, BigNumberish]; + type ArgsTuple = [bigint, bigint, bigint, bigint]; interface Args { - reportTimestamp: BigNumberish; - preClValidators: BigNumberish; - postClValidators: BigNumberish; - postClBalance: BigNumberish; + reportTimestamp: bigint; + preClValidators: bigint; + postClValidators: bigint; + postClBalance: bigint; } function args(overrides?: Partial): ArgsTuple { @@ -119,26 +115,59 @@ describe("Lido:accounting", () => { ); }); - type ArgsTuple = [ - BigNumberish, - BigNumberish, - BigNumberish, - BigNumberish, - BigNumberish, - BigNumberish, - BigNumberish, - BigNumberish, - ]; + it("Updates buffered ether", async () => { + const initialBufferedEther = await lido.getBufferedEther(); + const ethToLock = 1n; + + // assert that the buffer has enough eth to lock for withdrawals + // should have some eth from the initial 0xdead holder + expect(initialBufferedEther).greaterThanOrEqual(ethToLock); + await withdrawalQueue.mock__prefinalizeReturn(ethToLock, 0n); + + const accountingSigner = await impersonate(await locator.accounting(), ether("100.0")); + lido = lido.connect(accountingSigner); + + await lido.collectRewardsAndProcessWithdrawals(...args({ etherToLockOnWithdrawalQueue: ethToLock })); + expect(await lido.getBufferedEther()).to.equal(initialBufferedEther - ethToLock); + }); + + it("Emits an `ETHDistributed` event", async () => { + const reportTimestamp = await getNextBlockTimestamp(); + const preClBalance = 0n; + const clBalance = 1n; + const withdrawals = 0n; + const elRewards = 0n; + const bufferedEther = await lido.getBufferedEther(); + + const totalFee = 1000; + const precisionPoints = 10n ** 20n; + await stakingRouter.mock__getStakingRewardsDistribution([], [], [], totalFee, precisionPoints); + + const accountingSigner = await impersonate(await locator.accounting(), ether("100.0")); + lido = lido.connect(accountingSigner); + await expect( + lido.collectRewardsAndProcessWithdrawals( + ...args({ + reportTimestamp, + reportClBalance: clBalance, + }), + ), + ) + .to.emit(lido, "ETHDistributed") + .withArgs(reportTimestamp, preClBalance, clBalance, withdrawals, elRewards, bufferedEther); + }); + + type ArgsTuple = [bigint, bigint, bigint, bigint, bigint, bigint, bigint, bigint]; interface Args { - reportTimestamp: BigNumberish; - reportClBalance: BigNumberish; - adjustedPreCLBalance: BigNumberish; - withdrawalsToWithdraw: BigNumberish; - elRewardsToWithdraw: BigNumberish; - lastWithdrawalRequestToFinalize: BigNumberish; - simulatedShareRate: BigNumberish; - etherToLockOnWithdrawalQueue: BigNumberish; + reportTimestamp: bigint; + reportClBalance: bigint; + adjustedPreCLBalance: bigint; + withdrawalsToWithdraw: bigint; + elRewardsToWithdraw: bigint; + lastWithdrawalRequestToFinalize: bigint; + simulatedShareRate: bigint; + etherToLockOnWithdrawalQueue: bigint; } function args(overrides?: Partial): ArgsTuple { @@ -155,465 +184,4 @@ describe("Lido:accounting", () => { }) as ArgsTuple; } }); - - // TODO: [@tamtamchik] restore tests - context.skip("handleOracleReport", () => { - // it("Update CL validators count if reported more", async () => { - // let depositedValidators = 100n; - // await lido.connect(deployer).unsafeChangeDepositedValidators(depositedValidators); - // - // // first report, 100 validators - // await lido.handleOracleReport( - // ...report({ - // clValidators: depositedValidators, - // }), - // ); - // - // const slot = streccak("lido.Lido.beaconValidators"); - // const lidoAddress = await lido.getAddress(); - // - // let clValidatorsPosition = await getStorageAt(lidoAddress, slot); - // expect(clValidatorsPosition).to.equal(depositedValidators); - // - // depositedValidators = 101n; - // await lido.connect(deployer).unsafeChangeDepositedValidators(depositedValidators); - // - // // second report, 101 validators - // await lido.handleOracleReport( - // ...report({ - // clValidators: depositedValidators, - // }), - // ); - // - // clValidatorsPosition = await getStorageAt(lidoAddress, slot); - // expect(clValidatorsPosition).to.equal(depositedValidators); - // }); - // - // it("Reverts if the `checkAccountingOracleReport` sanity check fails", async () => { - // await oracleReportSanityChecker.mock__checkAccountingOracleReportReverts(true); - // - // await expect(lido.handleOracleReport(...report())).to.be.reverted; - // }); - // - // it("Reverts if the `checkWithdrawalQueueOracleReport` sanity check fails", async () => { - // await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).to.be.reverted; - // }); - // - // it("Does not revert if the `checkWithdrawalQueueOracleReport` sanity check fails but no withdrawal batches were reported", async () => { - // await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); - // await withdrawalQueue.mock__isPaused(true); - // - // await expect(lido.handleOracleReport(...report())).not.to.be.reverted; - // }); - // - // it("Does not revert if the `checkWithdrawalQueueOracleReport` sanity check fails but `withdrawalQueue` is paused", async () => { - // await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); - // await withdrawalQueue.mock__isPaused(true); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).not.to.be.reverted; - // }); - // - // it("Does not emit `StETHBurnRequested` if there are no shares to burn", async () => { - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).not.to.emit(burner, "StETHBurnRequested"); - // }); - // - // it("Emits `StETHBurnRequested` if there are shares to burn", async () => { - // const sharesToBurn = 1n; - // const isCover = false; - // const steth = 1n * 2n; // imitating 1:2 rate, see Burner `mock__prefinalizeReturn` - // - // await withdrawalQueue.mock__prefinalizeReturn(0n, sharesToBurn); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ) - // .to.emit(burner, "StETHBurnRequested") - // .withArgs(isCover, await lido.getAddress(), steth, sharesToBurn); - // }); - // - // it("Withdraws ether from `ElRewardsVault` if EL rewards are greater than 0 as returned from `smoothenTokenRebase`", async () => { - // const withdrawals = 0n; - // const elRewards = 1n; - // const simulatedSharesToBurn = 0n; - // const sharesToBurn = 0n; - // - // await oracleReportSanityChecker.mock__smoothenTokenRebaseReturn( - // withdrawals, - // elRewards, - // simulatedSharesToBurn, - // sharesToBurn, - // ); - // - // // `Mock__RewardsWithdrawn` event is only emitted on the mock to verify - // // that `ElRewardsVault.withdrawRewards` was actually called - // await expect(lido.handleOracleReport(...report())).to.emit(elRewardsVault, "Mock__RewardsWithdrawn"); - // }); - // - // it("Withdraws ether from `WithdrawalVault` if withdrawals are greater than 0 as returned from `smoothenTokenRebase`", async () => { - // const withdrawals = 1n; - // const elRewards = 0n; - // const simulatedSharesToBurn = 0n; - // const sharesToBurn = 0n; - // - // await oracleReportSanityChecker.mock__smoothenTokenRebaseReturn( - // withdrawals, - // elRewards, - // simulatedSharesToBurn, - // sharesToBurn, - // ); - // - // // `Mock__WithdrawalsWithdrawn` event is only emitted on the mock to verify - // // that `WithdrawalVault.withdrawWithdrawals` was actually called - // await expect(lido.handleOracleReport(...report())).to.emit(withdrawalVault, "Mock__WithdrawalsWithdrawn"); - // }); - // - // it("Finalizes withdrawals if there is ether to lock on `WithdrawalQueue` as returned from `prefinalize`", async () => { - // const ethToLock = ether("10.0"); - // await withdrawalQueue.mock__prefinalizeReturn(ethToLock, 0n); - // // top up buffer via submit - // await lido.submit(ZeroAddress, { value: ethToLock }); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n, 2n], - // }), - // ), - // ).to.emit(withdrawalQueue, "WithdrawalsFinalized"); - // }); - // - // it("Updates buffered ether", async () => { - // const initialBufferedEther = await lido.getBufferedEther(); - // const ethToLock = 1n; - // - // // assert that the buffer has enough eth to lock for withdrawals - // // should have some eth from the initial 0xdead holder - // expect(initialBufferedEther).greaterThanOrEqual(ethToLock); - // await withdrawalQueue.mock__prefinalizeReturn(ethToLock, 0n); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).to.not.be.reverted; - // - // expect(await lido.getBufferedEther()).to.equal(initialBufferedEther - ethToLock); - // }); - // - // it("Emits an `ETHDistributed` event", async () => { - // const reportTimestamp = await getNextBlockTimestamp(); - // const preClBalance = 0n; - // const clBalance = 1n; - // const withdrawals = 0n; - // const elRewards = 0n; - // const bufferedEther = await lido.getBufferedEther(); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // reportTimestamp: reportTimestamp, - // clBalance, - // }), - // ), - // ) - // .to.emit(lido, "ETHDistributed") - // .withArgs(reportTimestamp, preClBalance, clBalance, withdrawals, elRewards, bufferedEther); - // }); - // - // it("Burns shares if there are shares to burn as returned from `smoothenTokenRebaseReturn`", async () => { - // const sharesRequestedToBurn = 1n; - // - // await oracleReportSanityChecker.mock__smoothenTokenRebaseReturn(0n, 0n, 0n, sharesRequestedToBurn); - // - // // set up steth whale, in case we need to send steth to other accounts - // await setBalance(stethWhale.address, ether("101.0")); - // await lido.connect(stethWhale).submit(ZeroAddress, { value: ether("100.0") }); - // // top up Burner with steth to burn - // await lido.connect(stethWhale).transferShares(burner, sharesRequestedToBurn); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // sharesRequestedToBurn, - // }), - // ), - // ) - // .to.emit(burner, "Mock__CommitSharesToBurnWasCalled") - // .and.to.emit(lido, "SharesBurnt") - // .withArgs(await burner.getAddress(), sharesRequestedToBurn, sharesRequestedToBurn, sharesRequestedToBurn); - // }); - // - // it("Reverts if the number of reward recipients does not match the number of module fees as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { - // // one recipient - // const recipients = [certainAddress("lido:handleOracleReport:single-recipient")]; - // const modulesIds = [1n, 2n]; - // // but two module fees - // const moduleFees = [500n, 500n]; - // const totalFee = 1000; - // const precisionPoints = 10n ** 20n; - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // recipients, - // modulesIds, - // moduleFees, - // totalFee, - // precisionPoints, - // ); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: 1n, // made 1 wei of profit, trigers reward processing - // }), - // ), - // ).to.be.revertedWith("WRONG_RECIPIENTS_INPUT"); - // }); - // - // it("Reverts if the number of module ids does not match the number of module fees as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { - // const recipients = [ - // certainAddress("lido:handleOracleReport:recipient1"), - // certainAddress("lido:handleOracleReport:recipient2"), - // ]; - // // one module id - // const modulesIds = [1n]; - // // but two module fees - // const moduleFees = [500n, 500n]; - // const totalFee = 1000; - // const precisionPoints = 10n ** 20n; - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // recipients, - // modulesIds, - // moduleFees, - // totalFee, - // precisionPoints, - // ); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: 1n, // made 1 wei of profit, trigers reward processing - // }), - // ), - // ).to.be.revertedWith("WRONG_MODULE_IDS_INPUT"); - // }); - // - // it("Does not mint and transfer any shares if the total fee is zero as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { - // // single staking module - // const recipients = [certainAddress("lido:handleOracleReport:recipient")]; - // const modulesIds = [1n]; - // const moduleFees = [500n]; - // // fee is 0 - // const totalFee = 0; - // const precisionPoints = 10n ** 20n; - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // recipients, - // modulesIds, - // moduleFees, - // totalFee, - // precisionPoints, - // ); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: 1n, - // }), - // ), - // ) - // .not.to.emit(lido, "Transfer") - // .and.not.to.emit(lido, "TransferShares") - // .and.not.to.emit(stakingRouter, "Mock__MintedRewardsReported"); - // }); - // - // it("Mints shares to itself and then transfers them to recipients if there are fees to distribute as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { - // // initially, before any rebases, one share costs one steth - // expect(await lido.getPooledEthByShares(ether("1.0"))).to.equal(ether("1.0")); - // // thus, the total supply of steth should equal the total number of shares - // expect(await lido.getTotalPooledEther()).to.equal(await lido.getTotalShares()); - // - // // mock a single staking module with 5% fee with the total protocol fee of 10% - // const stakingModule = { - // address: certainAddress("lido:handleOracleReport:staking-module"), - // id: 1n, - // fee: 5n * 10n ** 18n, // 5% - // }; - // - // const totalFee = 10n * 10n ** 18n; // 10% - // const precisionPoints = 100n * 10n ** 18n; // 100% - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // [stakingModule.address], - // [stakingModule.id], - // [stakingModule.fee], - // totalFee, - // precisionPoints, - // ); - // - // const clBalance = ether("1.0"); - // - // const expectedSharesToMint = - // (clBalance * totalFee * (await lido.getTotalShares())) / - // (((await lido.getTotalPooledEther()) + clBalance) * precisionPoints - clBalance * totalFee); - // - // const expectedModuleRewardInShares = expectedSharesToMint / (totalFee / stakingModule.fee); - // const expectedTreasuryCutInShares = expectedSharesToMint - expectedModuleRewardInShares; - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: ether("1.0"), // 1 ether of profit - // }), - // ), - // ) - // .to.emit(lido, "TransferShares") - // .withArgs(ZeroAddress, stakingModule.address, expectedModuleRewardInShares) - // .and.to.emit(lido, "TransferShares") - // .withArgs(ZeroAddress, await lido.getTreasury(), expectedTreasuryCutInShares) - // .and.to.emit(stakingRouter, "Mock__MintedRewardsReported"); - // - // expect(await lido.balanceOf(stakingModule.address)).to.equal( - // await lido.getPooledEthByShares(expectedModuleRewardInShares), - // ); - // - // expect(await lido.balanceOf(await lido.getTreasury())).to.equal( - // await lido.getPooledEthByShares(expectedTreasuryCutInShares), - // ); - // - // // now one share should cost 1.9 steth (10% was distributed as rewards) - // expect(await lido.getPooledEthByShares(ether("1.0"))).to.equal(ether("1.9")); - // }); - // - // it("Transfers all new shares to treasury if the module fee is zero as returned `StakingRouter.getStakingRewardsDistribution`", async () => { - // // initially, before any rebases, one share costs one steth - // expect(await lido.getPooledEthByShares(ether("1.0"))).to.equal(ether("1.0")); - // // thus, the total supply of steth should equal the total number of shares - // expect(await lido.getTotalPooledEther()).to.equal(await lido.getTotalShares()); - // - // // mock a single staking module with 0% fee with the total protocol fee of 10% - // const stakingModule = { - // address: certainAddress("lido:handleOracleReport:staking-module"), - // id: 1n, - // fee: 0n, - // }; - // - // const totalFee = 10n * 10n ** 18n; // 10% - // const precisionPoints = 100n * 10n ** 18n; // 100% - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // [stakingModule.address], - // [stakingModule.id], - // [stakingModule.fee], - // totalFee, - // precisionPoints, - // ); - // - // const clBalance = ether("1.0"); - // - // const expectedSharesToMint = - // (clBalance * totalFee * (await lido.getTotalShares())) / - // (((await lido.getTotalPooledEther()) + clBalance) * precisionPoints - clBalance * totalFee); - // - // const expectedModuleRewardInShares = 0n; - // const expectedTreasuryCutInShares = expectedSharesToMint; - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: ether("1.0"), // 1 ether of profit - // }), - // ), - // ) - // .and.to.emit(lido, "TransferShares") - // .withArgs(ZeroAddress, await lido.getTreasury(), expectedTreasuryCutInShares) - // .and.to.emit(stakingRouter, "Mock__MintedRewardsReported"); - // - // expect(await lido.balanceOf(stakingModule.address)).to.equal( - // await lido.getPooledEthByShares(expectedModuleRewardInShares), - // ); - // - // expect(await lido.balanceOf(await lido.getTreasury())).to.equal( - // await lido.getPooledEthByShares(expectedTreasuryCutInShares), - // ); - // - // // now one share should cost 1.9 steth (10% was distributed as rewards) - // expect(await lido.getPooledEthByShares(ether("1.0"))).to.equal(ether("1.9")); - // }); - // - // it("Relays the report data to `PostTokenRebaseReceiver`", async () => { - // await expect(lido.handleOracleReport(...report())).to.emit( - // postTokenRebaseReceiver, - // "Mock__PostTokenRebaseHandled", - // ); - // }); - // - // it("Does not relay the report data to `PostTokenRebaseReceiver` if the locator returns zero address", async () => { - // const lidoLocatorAddress = await lido.getLidoLocator(); - // - // // Change the locator implementation to support zero address - // await updateLidoLocatorImplementation(lidoLocatorAddress, {}, "LidoLocator__MutableMock", deployer); - // const locatorMutable = await ethers.getContractAt("LidoLocator__MutableMock", lidoLocatorAddress, deployer); - // await locatorMutable.mock___updatePostTokenRebaseReceiver(ZeroAddress); - // - // expect(await locator.postTokenRebaseReceiver()).to.equal(ZeroAddress); - // - // const accountingOracleAddress = await locator.accountingOracle(); - // const accountingOracle = await impersonate(accountingOracleAddress, ether("1000.0")); - // - // await expect(lido.connect(accountingOracle).handleOracleReport(...report())).not.to.emit( - // postTokenRebaseReceiver, - // "Mock__PostTokenRebaseHandled", - // ); - // }); - // - // it("Reverts if there are withdrawal batches submitted and `checkSimulatedShareRate` fails", async () => { - // await oracleReportSanityChecker.mock__checkSimulatedShareRateReverts(true); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).to.be.reverted; - // }); - // - // it("Does not revert if there are no withdrawal batches submitted but `checkSimulatedShareRate` fails", async () => { - // await oracleReportSanityChecker.mock__checkSimulatedShareRateReverts(true); - // - // await expect(lido.handleOracleReport(...report())).not.to.be.reverted; - // }); - // - // it("Returns post-rebase state", async () => { - // const postRebaseState = await lido.handleOracleReport.staticCall(...report()); - // - // expect(postRebaseState).to.deep.equal([await lido.getTotalPooledEther(), await lido.getTotalShares(), 0n, 0n]); - // }); - }); }); diff --git a/test/0.8.9/accounting.handleOracleReport.test.ts b/test/0.8.9/accounting.handleOracleReport.test.ts index 540bb98b2..c62d65af0 100644 --- a/test/0.8.9/accounting.handleOracleReport.test.ts +++ b/test/0.8.9/accounting.handleOracleReport.test.ts @@ -1,652 +1,401 @@ -// import { expect } from "chai"; -// import { BigNumberish, ZeroAddress } from "ethers"; -// import { ethers } from "hardhat"; -// -// import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; -// import { getStorageAt, setBalance } from "@nomicfoundation/hardhat-network-helpers"; -// -// import { -// ACL, -// Burner__MockForAccounting, -// Lido, -// LidoExecutionLayerRewardsVault__MockForLidoAccounting, -// LidoLocator, -// OracleReportSanityChecker__MockForAccounting, -// PostTokenRebaseReceiver__MockForAccounting, -// StakingRouter__MockForLidoAccounting, -// WithdrawalQueue__MockForAccounting, -// WithdrawalVault__MockForLidoAccounting, -// } from "typechain-types"; -// -// import { certainAddress, ether, getNextBlockTimestamp, impersonate, streccak } from "lib"; -// -// import { deployLidoDao, updateLidoLocatorImplementation } from "test/deploy"; -// import { Snapshot } from "test/suite"; - -// TODO: improve coverage -// TODO: more math-focused tests -// TODO: [@tamtamchik] restore tests -describe.skip("Accounting.sol:report", () => { - // let deployer: HardhatEthersSigner; - // let accountingOracle: HardhatEthersSigner; - // let stethWhale: HardhatEthersSigner; - // let stranger: HardhatEthersSigner; - // - // let lido: Lido; - // let acl: ACL; - // let locator: LidoLocator; - // let withdrawalQueue: WithdrawalQueue__MockForAccounting; - // let oracleReportSanityChecker: OracleReportSanityChecker__MockForAccounting; - // let burner: Burner__MockForAccounting; - // let elRewardsVault: LidoExecutionLayerRewardsVault__MockForLidoAccounting; - // let withdrawalVault: WithdrawalVault__MockForLidoAccounting; - // let stakingRouter: StakingRouter__MockForLidoAccounting; - // let postTokenRebaseReceiver: PostTokenRebaseReceiver__MockForAccounting; - // - // let originalState: string; - // - // before(async () => { - // [deployer, accountingOracle, stethWhale, stranger] = await ethers.getSigners(); - // - // [ - // burner, - // elRewardsVault, - // oracleReportSanityChecker, - // postTokenRebaseReceiver, - // stakingRouter, - // withdrawalQueue, - // withdrawalVault, - // ] = await Promise.all([ - // ethers.deployContract("Burner__MockForAccounting"), - // ethers.deployContract("LidoExecutionLayerRewardsVault__MockForLidoAccounting"), - // ethers.deployContract("OracleReportSanityChecker__MockForAccounting"), - // ethers.deployContract("PostTokenRebaseReceiver__MockForAccounting"), - // ethers.deployContract("StakingRouter__MockForLidoAccounting"), - // ethers.deployContract("WithdrawalQueue__MockForAccounting"), - // ethers.deployContract("WithdrawalVault__MockForLidoAccounting"), - // ]); - // - // ({ lido, acl } = await deployLidoDao({ - // rootAccount: deployer, - // initialized: true, - // locatorConfig: { - // accountingOracle, - // oracleReportSanityChecker, - // withdrawalQueue, - // burner, - // elRewardsVault, - // withdrawalVault, - // stakingRouter, - // postTokenRebaseReceiver, - // }, - // })); - // - // locator = await ethers.getContractAt("LidoLocator", await lido.getLidoLocator(), deployer); - // - // await acl.createPermission(deployer, lido, await lido.RESUME_ROLE(), deployer); - // await acl.createPermission(deployer, lido, await lido.PAUSE_ROLE(), deployer); - // await acl.createPermission(deployer, lido, await lido.UNSAFE_CHANGE_DEPOSITED_VALIDATORS_ROLE(), deployer); - // await lido.resume(); - // - // lido = lido.connect(accountingOracle); - // }); - // - // beforeEach(async () => (originalState = await Snapshot.take())); - // - // afterEach(async () => await Snapshot.restore(originalState)); - // - // context("handleOracleReport", () => { - // it("Reverts when the contract is stopped", async () => { - // await lido.connect(deployer).stop(); - // await expect(lido.handleOracleReport(...report())).to.be.revertedWith("CONTRACT_IS_STOPPED"); - // }); - // - // it("Reverts if the caller is not `AccountingOracle`", async () => { - // await expect(lido.connect(stranger).handleOracleReport(...report())).to.be.revertedWith("APP_AUTH_FAILED"); - // }); - // - // it("Reverts if the report timestamp is in the future", async () => { - // const nextBlockTimestamp = await getNextBlockTimestamp(); - // const invalidReportTimestamp = nextBlockTimestamp + 1n; - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // reportTimestamp: invalidReportTimestamp, - // }), - // ), - // ).to.be.revertedWith("INVALID_REPORT_TIMESTAMP"); - // }); - // - // it("Reverts if the number of reported validators is greater than what is stored on the contract", async () => { - // const depositedValidators = 100n; - // await lido.connect(deployer).unsafeChangeDepositedValidators(depositedValidators); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clValidators: depositedValidators + 1n, - // }), - // ), - // ).to.be.revertedWith("REPORTED_MORE_DEPOSITED"); - // }); - // - // it("Reverts if the number of reported CL validators is less than what is stored on the contract", async () => { - // const depositedValidators = 100n; - // await lido.connect(deployer).unsafeChangeDepositedValidators(depositedValidators); - // - // // first report, 100 validators - // await lido.handleOracleReport( - // ...report({ - // clValidators: depositedValidators, - // }), - // ); - // - // // first report, 99 validators - // await expect( - // lido.handleOracleReport( - // ...report({ - // clValidators: depositedValidators - 1n, - // }), - // ), - // ).to.be.revertedWith("REPORTED_LESS_VALIDATORS"); - // }); - // - // it("Update CL validators count if reported more", async () => { - // let depositedValidators = 100n; - // await lido.connect(deployer).unsafeChangeDepositedValidators(depositedValidators); - // - // // first report, 100 validators - // await lido.handleOracleReport( - // ...report({ - // clValidators: depositedValidators, - // }), - // ); - // - // const slot = streccak("lido.Lido.beaconValidators"); - // const lidoAddress = await lido.getAddress(); - // - // let clValidatorsPosition = await getStorageAt(lidoAddress, slot); - // expect(clValidatorsPosition).to.equal(depositedValidators); - // - // depositedValidators = 101n; - // await lido.connect(deployer).unsafeChangeDepositedValidators(depositedValidators); - // - // // second report, 101 validators - // await lido.handleOracleReport( - // ...report({ - // clValidators: depositedValidators, - // }), - // ); - // - // clValidatorsPosition = await getStorageAt(lidoAddress, slot); - // expect(clValidatorsPosition).to.equal(depositedValidators); - // }); - // - // it("Reverts if the `checkAccountingOracleReport` sanity check fails", async () => { - // await oracleReportSanityChecker.mock__checkAccountingOracleReportReverts(true); - // - // await expect(lido.handleOracleReport(...report())).to.be.reverted; - // }); - // - // it("Reverts if the `checkWithdrawalQueueOracleReport` sanity check fails", async () => { - // await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).to.be.reverted; - // }); - // - // it("Does not revert if the `checkWithdrawalQueueOracleReport` sanity check fails but no withdrawal batches were reported", async () => { - // await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); - // await withdrawalQueue.mock__isPaused(true); - // - // await expect(lido.handleOracleReport(...report())).not.to.be.reverted; - // }); - // - // it("Does not revert if the `checkWithdrawalQueueOracleReport` sanity check fails but `withdrawalQueue` is paused", async () => { - // await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); - // await withdrawalQueue.mock__isPaused(true); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).not.to.be.reverted; - // }); - // - // it("Does not emit `StETHBurnRequested` if there are no shares to burn", async () => { - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).not.to.emit(burner, "StETHBurnRequested"); - // }); - // - // it("Emits `StETHBurnRequested` if there are shares to burn", async () => { - // const sharesToBurn = 1n; - // const isCover = false; - // const steth = 1n * 2n; // imitating 1:2 rate, see Burner `mock__prefinalizeReturn` - // - // await withdrawalQueue.mock__prefinalizeReturn(0n, sharesToBurn); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ) - // .to.emit(burner, "StETHBurnRequested") - // .withArgs(isCover, await lido.getAddress(), steth, sharesToBurn); - // }); - // - // it("Withdraws ether from `ElRewardsVault` if EL rewards are greater than 0 as returned from `smoothenTokenRebase`", async () => { - // const withdrawals = 0n; - // const elRewards = 1n; - // const simulatedSharesToBurn = 0n; - // const sharesToBurn = 0n; - // - // await oracleReportSanityChecker.mock__smoothenTokenRebaseReturn( - // withdrawals, - // elRewards, - // simulatedSharesToBurn, - // sharesToBurn, - // ); - // - // // `Mock__RewardsWithdrawn` event is only emitted on the mock to verify - // // that `ElRewardsVault.withdrawRewards` was actually called - // await expect(lido.handleOracleReport(...report())).to.emit(elRewardsVault, "Mock__RewardsWithdrawn"); - // }); - // - // it("Withdraws ether from `WithdrawalVault` if withdrawals are greater than 0 as returned from `smoothenTokenRebase`", async () => { - // const withdrawals = 1n; - // const elRewards = 0n; - // const simulatedSharesToBurn = 0n; - // const sharesToBurn = 0n; - // - // await oracleReportSanityChecker.mock__smoothenTokenRebaseReturn( - // withdrawals, - // elRewards, - // simulatedSharesToBurn, - // sharesToBurn, - // ); - // - // // `Mock__WithdrawalsWithdrawn` event is only emitted on the mock to verify - // // that `WithdrawalVault.withdrawWithdrawals` was actually called - // await expect(lido.handleOracleReport(...report())).to.emit(withdrawalVault, "Mock__WithdrawalsWithdrawn"); - // }); - // - // it("Finalizes withdrawals if there is ether to lock on `WithdrawalQueue` as returned from `prefinalize`", async () => { - // const ethToLock = ether("10.0"); - // await withdrawalQueue.mock__prefinalizeReturn(ethToLock, 0n); - // // top up buffer via submit - // await lido.submit(ZeroAddress, { value: ethToLock }); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n, 2n], - // }), - // ), - // ).to.emit(withdrawalQueue, "WithdrawalsFinalized"); - // }); - // - // it("Updates buffered ether", async () => { - // const initialBufferedEther = await lido.getBufferedEther(); - // const ethToLock = 1n; - // - // // assert that the buffer has enough eth to lock for withdrawals - // // should have some eth from the initial 0xdead holder - // expect(initialBufferedEther).greaterThanOrEqual(ethToLock); - // await withdrawalQueue.mock__prefinalizeReturn(ethToLock, 0n); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).to.not.be.reverted; - // - // expect(await lido.getBufferedEther()).to.equal(initialBufferedEther - ethToLock); - // }); - // - // it("Emits an `ETHDistributed` event", async () => { - // const reportTimestamp = await getNextBlockTimestamp(); - // const preClBalance = 0n; - // const clBalance = 1n; - // const withdrawals = 0n; - // const elRewards = 0n; - // const bufferedEther = await lido.getBufferedEther(); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // reportTimestamp: reportTimestamp, - // clBalance, - // }), - // ), - // ) - // .to.emit(lido, "ETHDistributed") - // .withArgs(reportTimestamp, preClBalance, clBalance, withdrawals, elRewards, bufferedEther); - // }); - // - // it("Burns shares if there are shares to burn as returned from `smoothenTokenRebaseReturn`", async () => { - // const sharesRequestedToBurn = 1n; - // - // await oracleReportSanityChecker.mock__smoothenTokenRebaseReturn(0n, 0n, 0n, sharesRequestedToBurn); - // - // // set up steth whale, in case we need to send steth to other accounts - // await setBalance(stethWhale.address, ether("101.0")); - // await lido.connect(stethWhale).submit(ZeroAddress, { value: ether("100.0") }); - // // top up Burner with steth to burn - // await lido.connect(stethWhale).transferShares(burner, sharesRequestedToBurn); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // sharesRequestedToBurn, - // }), - // ), - // ) - // .to.emit(burner, "Mock__CommitSharesToBurnWasCalled") - // .and.to.emit(lido, "SharesBurnt") - // .withArgs(await burner.getAddress(), sharesRequestedToBurn, sharesRequestedToBurn, sharesRequestedToBurn); - // }); - // - // it("Reverts if the number of reward recipients does not match the number of module fees as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { - // // one recipient - // const recipients = [certainAddress("lido:handleOracleReport:single-recipient")]; - // const modulesIds = [1n, 2n]; - // // but two module fees - // const moduleFees = [500n, 500n]; - // const totalFee = 1000; - // const precisionPoints = 10n ** 20n; - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // recipients, - // modulesIds, - // moduleFees, - // totalFee, - // precisionPoints, - // ); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: 1n, // made 1 wei of profit, trigers reward processing - // }), - // ), - // ).to.be.revertedWith("WRONG_RECIPIENTS_INPUT"); - // }); - // - // it("Reverts if the number of module ids does not match the number of module fees as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { - // const recipients = [ - // certainAddress("lido:handleOracleReport:recipient1"), - // certainAddress("lido:handleOracleReport:recipient2"), - // ]; - // // one module id - // const modulesIds = [1n]; - // // but two module fees - // const moduleFees = [500n, 500n]; - // const totalFee = 1000; - // const precisionPoints = 10n ** 20n; - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // recipients, - // modulesIds, - // moduleFees, - // totalFee, - // precisionPoints, - // ); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: 1n, // made 1 wei of profit, trigers reward processing - // }), - // ), - // ).to.be.revertedWith("WRONG_MODULE_IDS_INPUT"); - // }); - // - // it("Does not mint and transfer any shares if the total fee is zero as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { - // // single staking module - // const recipients = [certainAddress("lido:handleOracleReport:recipient")]; - // const modulesIds = [1n]; - // const moduleFees = [500n]; - // // fee is 0 - // const totalFee = 0; - // const precisionPoints = 10n ** 20n; - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // recipients, - // modulesIds, - // moduleFees, - // totalFee, - // precisionPoints, - // ); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: 1n, - // }), - // ), - // ) - // .not.to.emit(lido, "Transfer") - // .and.not.to.emit(lido, "TransferShares") - // .and.not.to.emit(stakingRouter, "Mock__MintedRewardsReported"); - // }); - // - // it("Mints shares to itself and then transfers them to recipients if there are fees to distribute as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { - // // initially, before any rebases, one share costs one steth - // expect(await lido.getPooledEthByShares(ether("1.0"))).to.equal(ether("1.0")); - // // thus, the total supply of steth should equal the total number of shares - // expect(await lido.getTotalPooledEther()).to.equal(await lido.getTotalShares()); - // - // // mock a single staking module with 5% fee with the total protocol fee of 10% - // const stakingModule = { - // address: certainAddress("lido:handleOracleReport:staking-module"), - // id: 1n, - // fee: 5n * 10n ** 18n, // 5% - // }; - // - // const totalFee = 10n * 10n ** 18n; // 10% - // const precisionPoints = 100n * 10n ** 18n; // 100% - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // [stakingModule.address], - // [stakingModule.id], - // [stakingModule.fee], - // totalFee, - // precisionPoints, - // ); - // - // const clBalance = ether("1.0"); - // - // const expectedSharesToMint = - // (clBalance * totalFee * (await lido.getTotalShares())) / - // (((await lido.getTotalPooledEther()) + clBalance) * precisionPoints - clBalance * totalFee); - // - // const expectedModuleRewardInShares = expectedSharesToMint / (totalFee / stakingModule.fee); - // const expectedTreasuryCutInShares = expectedSharesToMint - expectedModuleRewardInShares; - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: ether("1.0"), // 1 ether of profit - // }), - // ), - // ) - // .to.emit(lido, "TransferShares") - // .withArgs(ZeroAddress, stakingModule.address, expectedModuleRewardInShares) - // .and.to.emit(lido, "TransferShares") - // .withArgs(ZeroAddress, await lido.getTreasury(), expectedTreasuryCutInShares) - // .and.to.emit(stakingRouter, "Mock__MintedRewardsReported"); - // - // expect(await lido.balanceOf(stakingModule.address)).to.equal( - // await lido.getPooledEthByShares(expectedModuleRewardInShares), - // ); - // - // expect(await lido.balanceOf(await lido.getTreasury())).to.equal( - // await lido.getPooledEthByShares(expectedTreasuryCutInShares), - // ); - // - // // now one share should cost 1.9 steth (10% was distributed as rewards) - // expect(await lido.getPooledEthByShares(ether("1.0"))).to.equal(ether("1.9")); - // }); - // - // it("Transfers all new shares to treasury if the module fee is zero as returned `StakingRouter.getStakingRewardsDistribution`", async () => { - // // initially, before any rebases, one share costs one steth - // expect(await lido.getPooledEthByShares(ether("1.0"))).to.equal(ether("1.0")); - // // thus, the total supply of steth should equal the total number of shares - // expect(await lido.getTotalPooledEther()).to.equal(await lido.getTotalShares()); - // - // // mock a single staking module with 0% fee with the total protocol fee of 10% - // const stakingModule = { - // address: certainAddress("lido:handleOracleReport:staking-module"), - // id: 1n, - // fee: 0n, - // }; - // - // const totalFee = 10n * 10n ** 18n; // 10% - // const precisionPoints = 100n * 10n ** 18n; // 100% - // - // await stakingRouter.mock__getStakingRewardsDistribution( - // [stakingModule.address], - // [stakingModule.id], - // [stakingModule.fee], - // totalFee, - // precisionPoints, - // ); - // - // const clBalance = ether("1.0"); - // - // const expectedSharesToMint = - // (clBalance * totalFee * (await lido.getTotalShares())) / - // (((await lido.getTotalPooledEther()) + clBalance) * precisionPoints - clBalance * totalFee); - // - // const expectedModuleRewardInShares = 0n; - // const expectedTreasuryCutInShares = expectedSharesToMint; - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // clBalance: ether("1.0"), // 1 ether of profit - // }), - // ), - // ) - // .and.to.emit(lido, "TransferShares") - // .withArgs(ZeroAddress, await lido.getTreasury(), expectedTreasuryCutInShares) - // .and.to.emit(stakingRouter, "Mock__MintedRewardsReported"); - // - // expect(await lido.balanceOf(stakingModule.address)).to.equal( - // await lido.getPooledEthByShares(expectedModuleRewardInShares), - // ); - // - // expect(await lido.balanceOf(await lido.getTreasury())).to.equal( - // await lido.getPooledEthByShares(expectedTreasuryCutInShares), - // ); - // - // // now one share should cost 1.9 steth (10% was distributed as rewards) - // expect(await lido.getPooledEthByShares(ether("1.0"))).to.equal(ether("1.9")); - // }); - // - // it("Relays the report data to `PostTokenRebaseReceiver`", async () => { - // await expect(lido.handleOracleReport(...report())).to.emit( - // postTokenRebaseReceiver, - // "Mock__PostTokenRebaseHandled", - // ); - // }); - // - // it("Does not relay the report data to `PostTokenRebaseReceiver` if the locator returns zero address", async () => { - // const lidoLocatorAddress = await lido.getLidoLocator(); - // - // // Change the locator implementation to support zero address - // await updateLidoLocatorImplementation(lidoLocatorAddress, {}, "LidoLocator__MockMutable", deployer); - // const locatorMutable = await ethers.getContractAt("LidoLocator__MockMutable", lidoLocatorAddress, deployer); - // await locatorMutable.mock___updatePostTokenRebaseReceiver(ZeroAddress); - // - // expect(await locator.postTokenRebaseReceiver()).to.equal(ZeroAddress); - // - // const accountingOracleAddress = await locator.accountingOracle(); - // const accountingOracleSigner = await impersonate(accountingOracleAddress, ether("1000.0")); - // - // await expect(lido.connect(accountingOracleSigner).handleOracleReport(...report())).not.to.emit( - // postTokenRebaseReceiver, - // "Mock__PostTokenRebaseHandled", - // ); - // }); - // - // it("Reverts if there are withdrawal batches submitted and `checkSimulatedShareRate` fails", async () => { - // await oracleReportSanityChecker.mock__checkSimulatedShareRateReverts(true); - // - // await expect( - // lido.handleOracleReport( - // ...report({ - // withdrawalFinalizationBatches: [1n], - // }), - // ), - // ).to.be.reverted; - // }); - // - // it("Does not revert if there are no withdrawal batches submitted but `checkSimulatedShareRate` fails", async () => { - // await oracleReportSanityChecker.mock__checkSimulatedShareRateReverts(true); - // - // await expect(lido.handleOracleReport(...report())).not.to.be.reverted; - // }); - // - // it("Returns post-rebase state", async () => { - // const postRebaseState = await lido.handleOracleReport.staticCall(...report()); - // - // expect(postRebaseState).to.deep.equal([await lido.getTotalPooledEther(), await lido.getTotalShares(), 0n, 0n]); - // }); - // }); -}); +import { expect } from "chai"; +import { ZeroAddress } from "ethers"; +import { ethers } from "hardhat"; + +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; + +import { + Accounting, + Burner__MockForAccounting, + Burner__MockForAccounting__factory, + IPostTokenRebaseReceiver, + Lido__MockForAccounting, + Lido__MockForAccounting__factory, + LidoLocator, + OracleReportSanityChecker__MockForAccounting, + OracleReportSanityChecker__MockForAccounting__factory, + PostTokenRebaseReceiver__MockForAccounting__factory, + StakingRouter__MockForLidoAccounting, + StakingRouter__MockForLidoAccounting__factory, + WithdrawalQueue__MockForAccounting, + WithdrawalQueue__MockForAccounting__factory, +} from "typechain-types"; +import { ReportValuesStruct } from "typechain-types/contracts/0.8.9/oracle/AccountingOracle.sol/IReportReceiver"; + +import { certainAddress, ether, impersonate } from "lib"; + +import { deployLidoLocator, updateLidoLocatorImplementation } from "test/deploy"; + +describe("Accounting.sol:report", () => { + let deployer: HardhatEthersSigner; + + let accounting: Accounting; + let postTokenRebaseReceiver: IPostTokenRebaseReceiver; + let locator: LidoLocator; + + let lido: Lido__MockForAccounting; + let stakingRouter: StakingRouter__MockForLidoAccounting; + let oracleReportSanityChecker: OracleReportSanityChecker__MockForAccounting; + let withdrawalQueue: WithdrawalQueue__MockForAccounting; + let burner: Burner__MockForAccounting; + + beforeEach(async () => { + [deployer] = await ethers.getSigners(); + + [lido, stakingRouter, oracleReportSanityChecker, postTokenRebaseReceiver, withdrawalQueue, burner] = + await Promise.all([ + new Lido__MockForAccounting__factory(deployer).deploy(), + new StakingRouter__MockForLidoAccounting__factory(deployer).deploy(), + new OracleReportSanityChecker__MockForAccounting__factory(deployer).deploy(), + new PostTokenRebaseReceiver__MockForAccounting__factory(deployer).deploy(), + new WithdrawalQueue__MockForAccounting__factory(deployer).deploy(), + new Burner__MockForAccounting__factory(deployer).deploy(), + ]); + + locator = await deployLidoLocator( + { + lido, + stakingRouter, + oracleReportSanityChecker, + postTokenRebaseReceiver, + withdrawalQueue, + burner, + }, + deployer, + ); + + const accountingImpl = await ethers.deployContract("Accounting", [locator, lido], deployer); + const accountingProxy = await ethers.deployContract( + "OssifiableProxy", + [accountingImpl, deployer, new Uint8Array()], + deployer, + ); + accounting = await ethers.getContractAt("Accounting", accountingProxy, deployer); + await updateLidoLocatorImplementation(await locator.getAddress(), { accounting }); + await accounting.initialize(deployer); + + const accountingOracleSigner = await impersonate(await locator.accountingOracle(), ether("100.0")); + accounting = accounting.connect(accountingOracleSigner); + }); + + context("handleOracleReport", () => { + it("Update CL validators count if reported more", async () => { + let depositedValidators = 100n; + await lido.setMockedDepositedValidators(depositedValidators); + + // first report, 100 validators + await accounting.handleOracleReport( + report({ + clValidators: depositedValidators, + }), + ); + expect(await lido.reportClValidators()).to.equal(depositedValidators); + + depositedValidators = 101n; + await lido.setMockedDepositedValidators(depositedValidators); + + // second report, 101 validators + await accounting.handleOracleReport( + report({ + clValidators: depositedValidators, + }), + ); + expect(await lido.reportClValidators()).to.equal(depositedValidators); + }); + + it("Reverts if the `checkAccountingOracleReport` sanity check fails", async () => { + await oracleReportSanityChecker.mock__checkAccountingOracleReportReverts(true); + + await expect(accounting.handleOracleReport(report())).to.be.revertedWithCustomError( + oracleReportSanityChecker, + "CheckAccountingOracleReportReverts", + ); + }); + + it("Reverts if the `checkWithdrawalQueueOracleReport` sanity check fails", async () => { + await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); + await expect( + accounting.handleOracleReport( + report({ + withdrawalFinalizationBatches: [1n], + }), + ), + ).to.be.revertedWithCustomError(oracleReportSanityChecker, "CheckWithdrawalQueueOracleReportReverts"); + }); + + it("Does not revert if the `checkWithdrawalQueueOracleReport` sanity check fails but no withdrawal batches were reported", async () => { + await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); + await withdrawalQueue.mock__isPaused(true); + + await expect(accounting.handleOracleReport(report())).not.to.be.reverted; + }); + + /// NOTE: This test is not applicable to the current implementation (Accounting's _checkAccountingOracleReport() checks for checkWithdrawalQueueOracleReport() + /// explicitly in case _report.withdrawalFinalizationBatches.length > 0 + // it("Does not revert if the `checkWithdrawalQueueOracleReport` sanity check fails but `withdrawalQueue` is paused", async () => { + // await oracleReportSanityChecker.mock__checkWithdrawalQueueOracleReportReverts(true); + // await withdrawalQueue.mock__isPaused(true); + + // await expect(accounting.handleOracleReport(report({ withdrawalFinalizationBatches: [1n] }))).not.to.be.reverted; + // }); + + it("Does not emit `StETHBurnRequested` if there are no shares to burn", async () => { + await expect( + accounting.handleOracleReport( + report({ + withdrawalFinalizationBatches: [1n], + }), + ), + ).not.to.emit(burner, "Mock__StETHBurnRequested"); + }); + + it("Emits `StETHBurnRequested` if there are shares to burn", async () => { + const sharesToBurn = 1n; + const isCover = false; + const steth = 1n * 2n; // imitating 1:2 rate, see Burner `mock__prefinalizeReturn` + + await withdrawalQueue.mock__prefinalizeReturn(0n, sharesToBurn); + + await expect( + accounting.handleOracleReport( + report({ + withdrawalFinalizationBatches: [1n], + }), + ), + ) + .to.emit(burner, "Mock__StETHBurnRequested") + .withArgs(isCover, await accounting.getAddress(), steth, sharesToBurn); + }); + + it("ensures that `Lido.collectRewardsAndProcessWithdrawals` is called from `Accounting`", async () => { + // `Mock__CollectRewardsAndProcessWithdrawals` event is only emitted on the mock to verify + // that `Lido.collectRewardsAndProcessWithdrawals` was actually called + await expect(accounting.handleOracleReport(report())).to.emit(lido, "Mock__CollectRewardsAndProcessWithdrawals"); + }); + + it("Burns shares if there are shares to burn as returned from `smoothenTokenRebaseReturn`", async () => { + const sharesRequestedToBurn = 1n; + await oracleReportSanityChecker.mock__smoothenTokenRebaseReturn(0n, 0n, 0n, sharesRequestedToBurn); -// function report(overrides?: Partial): ReportTuple { -// return Object.values({ -// reportTimestamp: 0n, -// timeElapsed: 0n, -// clValidators: 0n, -// clBalance: 0n, -// withdrawalVaultBalance: 0n, -// elRewardsVaultBalance: 0n, -// sharesRequestedToBurn: 0n, -// withdrawalFinalizationBatches: [], -// simulatedShareRate: 0n, -// ...overrides, -// }) as ReportTuple; -// } - -// interface Report { -// reportTimestamp: BigNumberish; -// timeElapsed: BigNumberish; -// clValidators: BigNumberish; -// clBalance: BigNumberish; -// withdrawalVaultBalance: BigNumberish; -// elRewardsVaultBalance: BigNumberish; -// sharesRequestedToBurn: BigNumberish; -// withdrawalFinalizationBatches: BigNumberish[]; -// simulatedShareRate: BigNumberish; -// } -// -// type ReportTuple = [ -// BigNumberish, -// BigNumberish, -// BigNumberish, -// BigNumberish, -// BigNumberish, -// BigNumberish, -// BigNumberish, -// BigNumberish[], -// BigNumberish, -// ]; + await expect( + accounting.handleOracleReport( + report({ + sharesRequestedToBurn, + }), + ), + ) + .to.emit(burner, "Mock__CommitSharesToBurnWasCalled") + .withArgs(sharesRequestedToBurn); + // TODO: SharesBurnt event is not emitted anymore because of the mock implementation + // .and.to.emit(lido, "SharesBurnt") + // .withArgs(await burner.getAddress(), sharesRequestedToBurn, sharesRequestedToBurn, sharesRequestedToBurn); + }); + + it("Reverts if the number of reward recipients does not match the number of module fees as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { + // one recipient + const recipients = [certainAddress("lido:handleOracleReport:single-recipient")]; + const modulesIds = [1n, 2n]; + // but two module fees + const moduleFees = [500n, 500n]; + const totalFee = 1000; + const precisionPoints = 10n ** 20n; + + await stakingRouter.mock__getStakingRewardsDistribution( + recipients, + modulesIds, + moduleFees, + totalFee, + precisionPoints, + ); + + await expect( + accounting.handleOracleReport( + report({ + clBalance: 1n, // made 1 wei of profit, trigers reward processing + }), + ), + ) + .to.be.revertedWithCustomError(accounting, "UnequalArrayLengths") + .withArgs(1, 2); + }); + + it("Reverts if the number of module ids does not match the number of module fees as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { + const recipients = [ + certainAddress("lido:handleOracleReport:recipient1"), + certainAddress("lido:handleOracleReport:recipient2"), + ]; + // one module id + const modulesIds = [1n]; + // but two module fees + const moduleFees = [500n, 500n]; + const totalFee = 1000; + const precisionPoints = 10n ** 20n; + + await stakingRouter.mock__getStakingRewardsDistribution( + recipients, + modulesIds, + moduleFees, + totalFee, + precisionPoints, + ); + + await expect( + accounting.handleOracleReport( + report({ + clBalance: 1n, // made 1 wei of profit, trigers reward processing + }), + ), + ) + .to.be.revertedWithCustomError(accounting, "UnequalArrayLengths") + .withArgs(1, 2); + }); + + it("Does not mint and transfer any shares if the total fee is zero as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { + // single staking module + const recipients = [certainAddress("lido:handleOracleReport:recipient")]; + const modulesIds = [1n]; + const moduleFees = [500n]; + // fee is 0 + const totalFee = 0; + const precisionPoints = 10n ** 20n; + + await stakingRouter.mock__getStakingRewardsDistribution( + recipients, + modulesIds, + moduleFees, + totalFee, + precisionPoints, + ); + + await expect( + accounting.handleOracleReport( + report({ + clBalance: 1n, + }), + ), + ).not.to.emit(stakingRouter, "Mock__MintedRewardsReported"); + }); + + it("Mints shares to itself and then transfers them to recipients if there are fees to distribute as returned from `StakingRouter.getStakingRewardsDistribution`", async () => { + // mock a single staking module with 5% fee with the total protocol fee of 10% + const stakingModule = { + address: certainAddress("lido:handleOracleReport:staking-module"), + id: 1n, + fee: 5n * 10n ** 18n, // 5% + }; + + const totalFee = 10n * 10n ** 18n; // 10% + const precisionPoints = 100n * 10n ** 18n; // 100% + + await stakingRouter.mock__getStakingRewardsDistribution( + [stakingModule.address], + [stakingModule.id], + [stakingModule.fee], + totalFee, + precisionPoints, + ); + + const clBalance = ether("1.0"); + const expectedSharesToMint = + (clBalance * totalFee * (await lido.getTotalShares())) / + (((await lido.getTotalPooledEther()) + clBalance) * precisionPoints - clBalance * totalFee); + + const expectedModuleRewardInShares = expectedSharesToMint / (totalFee / stakingModule.fee); + const expectedTreasuryCutInShares = expectedSharesToMint - expectedModuleRewardInShares; + + await expect( + accounting.handleOracleReport( + report({ + clBalance: ether("1.0"), // 1 ether of profit + }), + ), + ) + .to.emit(lido, "TransferShares") + .withArgs(ZeroAddress, stakingModule.address, expectedModuleRewardInShares) + .and.to.emit(lido, "TransferShares") + .withArgs(ZeroAddress, await locator.treasury(), expectedTreasuryCutInShares) + .and.to.emit(stakingRouter, "Mock__MintedRewardsReported"); + }); + + it("Transfers all new shares to treasury if the module fee is zero as returned `StakingRouter.getStakingRewardsDistribution`", async () => { + // mock a single staking module with 0% fee with the total protocol fee of 10% + const stakingModule = { + address: certainAddress("lido:handleOracleReport:staking-module"), + id: 1n, + fee: 0n, + }; + + const totalFee = 10n * 10n ** 18n; // 10% + const precisionPoints = 100n * 10n ** 18n; // 100% + + await stakingRouter.mock__getStakingRewardsDistribution( + [stakingModule.address], + [stakingModule.id], + [stakingModule.fee], + totalFee, + precisionPoints, + ); + + const clBalance = ether("1.0"); + + const expectedSharesToMint = + (clBalance * totalFee * (await lido.getTotalShares())) / + (((await lido.getTotalPooledEther()) + clBalance) * precisionPoints - clBalance * totalFee); + + const expectedTreasuryCutInShares = expectedSharesToMint; + + await expect( + accounting.handleOracleReport( + report({ + clBalance: ether("1.0"), // 1 ether of profit + }), + ), + ) + .and.to.emit(lido, "TransferShares") + .withArgs(ZeroAddress, await locator.treasury(), expectedTreasuryCutInShares) + .and.to.emit(stakingRouter, "Mock__MintedRewardsReported"); + }); + + it("Relays the report data to `PostTokenRebaseReceiver`", async () => { + await expect(accounting.handleOracleReport(report())).to.emit( + postTokenRebaseReceiver, + "Mock__PostTokenRebaseHandled", + ); + }); + + it("Does not relay the report data to `PostTokenRebaseReceiver` if the locator returns zero address", async () => { + const lidoLocatorAddress = await locator.getAddress(); + + // Change the locator implementation to support zero address + await updateLidoLocatorImplementation(lidoLocatorAddress, {}, "LidoLocator__MockMutable", deployer); + const locatorMutable = await ethers.getContractAt("LidoLocator__MockMutable", lidoLocatorAddress, deployer); + await locatorMutable.mock___updatePostTokenRebaseReceiver(ZeroAddress); + + expect(await locator.postTokenRebaseReceiver()).to.equal(ZeroAddress); + + const accountingOracleAddress = await locator.accountingOracle(); + const accountingOracle = await impersonate(accountingOracleAddress, ether("1000.0")); + + await expect(accounting.connect(accountingOracle).handleOracleReport(report())).not.to.emit( + postTokenRebaseReceiver, + "Mock__PostTokenRebaseHandled", + ); + }); + + function report(overrides?: Partial): ReportValuesStruct { + return { + timestamp: 0n, + timeElapsed: 0n, + clValidators: 0n, + clBalance: 0n, + withdrawalVaultBalance: 0n, + elRewardsVaultBalance: 0n, + sharesRequestedToBurn: 0n, + withdrawalFinalizationBatches: [], + vaultValues: [], + netCashFlows: [], + ...overrides, + }; + } + }); +}); diff --git a/test/0.8.9/contracts/LidoLocator__MockMutable.sol b/test/0.8.9/contracts/LidoLocator__MockMutable.sol index ead0d44e1..e102d2a4d 100644 --- a/test/0.8.9/contracts/LidoLocator__MockMutable.sol +++ b/test/0.8.9/contracts/LidoLocator__MockMutable.sol @@ -3,7 +3,9 @@ pragma solidity 0.8.9; -contract LidoLocator__MockMutable { +import {ILidoLocator} from "../../../contracts/common/interfaces/ILidoLocator.sol"; + +contract LidoLocator__MockMutable is ILidoLocator { struct Config { address accountingOracle; address depositSecurityModule; @@ -19,6 +21,8 @@ contract LidoLocator__MockMutable { address withdrawalQueue; address withdrawalVault; address oracleDaemonConfig; + address accounting; + address wstETH; } error ZeroAddress(); @@ -37,6 +41,8 @@ contract LidoLocator__MockMutable { address public immutable withdrawalQueue; address public immutable withdrawalVault; address public immutable oracleDaemonConfig; + address public immutable accounting; + address public immutable wstETH; /** * @notice declare service locations @@ -58,25 +64,22 @@ contract LidoLocator__MockMutable { withdrawalQueue = _assertNonZero(_config.withdrawalQueue); withdrawalVault = _assertNonZero(_config.withdrawalVault); oracleDaemonConfig = _assertNonZero(_config.oracleDaemonConfig); + accounting = _assertNonZero(_config.accounting); + wstETH = _assertNonZero(_config.wstETH); } function coreComponents() external view returns (address, address, address, address, address, address) { return (elRewardsVault, oracleReportSanityChecker, stakingRouter, treasury, withdrawalQueue, withdrawalVault); } - function oracleReportComponentsForLido() - external - view - returns (address, address, address, address, address, address, address) - { + function oracleReportComponents() external view returns (address, address, address, address, address, address) { return ( accountingOracle, - elRewardsVault, oracleReportSanityChecker, burner, withdrawalQueue, - withdrawalVault, - postTokenRebaseReceiver + postTokenRebaseReceiver, + stakingRouter ); } diff --git a/test/0.8.9/contracts/Lido__MockForAccounting.sol b/test/0.8.9/contracts/Lido__MockForAccounting.sol new file mode 100644 index 000000000..fc0c95582 --- /dev/null +++ b/test/0.8.9/contracts/Lido__MockForAccounting.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: UNLICENSED +// for testing purposes only + +pragma solidity 0.8.9; + +contract Lido__MockForAccounting { + uint256 public depositedValidatorsValue; + uint256 public reportClValidators; + uint256 public reportClBalance; + + // Emitted when validators number delivered by the oracle + event CLValidatorsUpdated(uint256 indexed reportTimestamp, uint256 preCLValidators, uint256 postCLValidators); + event Mock__CollectRewardsAndProcessWithdrawals( + uint256 _reportTimestamp, + uint256 _reportClBalance, + uint256 _principalCLBalance, + uint256 _withdrawalsToWithdraw, + uint256 _elRewardsToWithdraw, + uint256 _lastWithdrawalRequestToFinalize, + uint256 _withdrawalsShareRate, + uint256 _etherToLockOnWithdrawalQueue + ); + /** + * @notice An executed shares transfer from `sender` to `recipient`. + * + * @dev emitted in pair with an ERC20-defined `Transfer` event. + */ + event TransferShares(address indexed from, address indexed to, uint256 sharesValue); + + function setMockedDepositedValidators(uint256 _amount) external { + depositedValidatorsValue = _amount; + } + + function getBeaconStat() + external + view + returns (uint256 depositedValidators, uint256 beaconValidators, uint256 beaconBalance) + { + depositedValidators = depositedValidatorsValue; + beaconValidators = reportClValidators; + beaconBalance = 0; + } + + function getTotalPooledEther() external view returns (uint256) { + return 3201000000000000000000; + } + + function getTotalShares() external view returns (uint256) { + return 1000000000000000000; + } + + function getExternalShares() external view returns (uint256) { + return 0; + } + + function getExternalEther() external view returns (uint256) { + return 0; + } + + function collectRewardsAndProcessWithdrawals( + uint256 _reportTimestamp, + uint256 _reportClBalance, + uint256 _adjustedPreCLBalance, + uint256 _withdrawalsToWithdraw, + uint256 _elRewardsToWithdraw, + uint256 _lastWithdrawalRequestToFinalize, + uint256 _simulatedShareRate, + uint256 _etherToLockOnWithdrawalQueue + ) external { + emit Mock__CollectRewardsAndProcessWithdrawals( + _reportTimestamp, + _reportClBalance, + _adjustedPreCLBalance, + _withdrawalsToWithdraw, + _elRewardsToWithdraw, + _lastWithdrawalRequestToFinalize, + _simulatedShareRate, + _etherToLockOnWithdrawalQueue + ); + } + + function emitTokenRebase( + uint256 _reportTimestamp, + uint256 _timeElapsed, + uint256 _preTotalShares, + uint256 _preTotalEther, + uint256 _postTotalShares, + uint256 _postTotalEther, + uint256 _sharesMintedAsFees + ) external {} + + /** + * @notice Process CL related state changes as a part of the report processing + * @dev All data validation was done by Accounting and OracleReportSanityChecker + * @param _reportTimestamp timestamp of the report + * @param _preClValidators number of validators in the previous CL state (for event compatibility) + * @param _reportClValidators number of validators in the current CL state + * @param _reportClBalance total balance of the current CL state + */ + function processClStateUpdate( + uint256 _reportTimestamp, + uint256 _preClValidators, + uint256 _reportClValidators, + uint256 _reportClBalance + ) external { + reportClValidators = _reportClValidators; + reportClBalance = _reportClBalance; + + emit CLValidatorsUpdated(_reportTimestamp, _preClValidators, _reportClValidators); + } + + function mintShares(address _recipient, uint256 _sharesAmount) external { + emit TransferShares(address(0), _recipient, _sharesAmount); + } +} diff --git a/test/0.8.9/contracts/OracleReportSanityChecker__Mock.sol b/test/0.8.9/contracts/OracleReportSanityChecker__Mock.sol deleted file mode 100644 index a3ff27f95..000000000 --- a/test/0.8.9/contracts/OracleReportSanityChecker__Mock.sol +++ /dev/null @@ -1,60 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -// for testing purposes only - -pragma solidity 0.8.9; - -contract OracleReportSanityChecker__Mock { - error SelectorNotFound(bytes4 sig, uint256 value, bytes data); - - fallback() external payable { - revert SelectorNotFound(msg.sig, msg.value, msg.data); - } - - function checkAccountingOracleReport( - uint256 _timeElapsed, - uint256 _preCLBalance, - uint256 _postCLBalance, - uint256 _withdrawalVaultBalance, - uint256 _elRewardsVaultBalance, - uint256 _sharesRequestedToBurn, - uint256 _preCLValidators, - uint256 _postCLValidators - ) external view {} - - function checkWithdrawalQueueOracleReport( - uint256[] calldata _withdrawalFinalizationBatches, - uint256 _reportTimestamp - ) external view {} - - function checkSimulatedShareRate( - uint256 _postTotalPooledEther, - uint256 _postTotalShares, - uint256 _etherLockedOnWithdrawalQueue, - uint256 _sharesBurntDueToWithdrawals, - uint256 _simulatedShareRate - ) external view {} - - function smoothenTokenRebase( - uint256, - uint256, - uint256, - uint256, - uint256 _withdrawalVaultBalance, - uint256 _elRewardsVaultBalance, - uint256, - uint256 _etherToLockForWithdrawals, - uint256 - ) - external - view - returns (uint256 withdrawals, uint256 elRewards, uint256 simulatedSharesToBurn, uint256 sharesToBurn) - { - withdrawals = _withdrawalVaultBalance; - elRewards = _elRewardsVaultBalance; - - simulatedSharesToBurn = 0; - sharesToBurn = _etherToLockForWithdrawals; - } - - function checkAccountingExtraDataListItemsCount(uint256 _extraDataListItemsCount) external view {} -} diff --git a/test/0.4.24/contracts/OracleReportSanityChecker__MockForAccounting.sol b/test/0.8.9/contracts/OracleReportSanityChecker__MockForAccounting.sol similarity index 77% rename from test/0.4.24/contracts/OracleReportSanityChecker__MockForAccounting.sol rename to test/0.8.9/contracts/OracleReportSanityChecker__MockForAccounting.sol index aeb260b7e..5575d6ca6 100644 --- a/test/0.4.24/contracts/OracleReportSanityChecker__MockForAccounting.sol +++ b/test/0.8.9/contracts/OracleReportSanityChecker__MockForAccounting.sol @@ -1,18 +1,20 @@ // SPDX-License-Identifier: UNLICENSED // for testing purposes only -pragma solidity 0.4.24; +pragma solidity 0.8.9; contract OracleReportSanityChecker__MockForAccounting { bool private checkAccountingOracleReportReverts; bool private checkWithdrawalQueueOracleReportReverts; - bool private checkSimulatedShareRateReverts; uint256 private _withdrawals; uint256 private _elRewards; uint256 private _simulatedSharesToBurn; uint256 private _sharesToBurn; + error CheckAccountingOracleReportReverts(); + error CheckWithdrawalQueueOracleReportReverts(); + function checkAccountingOracleReport( uint256 _timeElapsed, uint256 _preCLBalance, @@ -23,14 +25,14 @@ contract OracleReportSanityChecker__MockForAccounting { uint256 _preCLValidators, uint256 _postCLValidators ) external view { - if (checkAccountingOracleReportReverts) revert(); + if (checkAccountingOracleReportReverts) revert CheckAccountingOracleReportReverts(); } function checkWithdrawalQueueOracleReport( uint256 _lastFinalizableRequestId, uint256 _reportTimestamp ) external view { - if (checkWithdrawalQueueOracleReportReverts) revert(); + if (checkWithdrawalQueueOracleReportReverts) revert CheckWithdrawalQueueOracleReportReverts(); } function smoothenTokenRebase( @@ -54,16 +56,6 @@ contract OracleReportSanityChecker__MockForAccounting { sharesToBurn = _sharesToBurn; } - function checkSimulatedShareRate( - uint256 _postTotalPooledEther, - uint256 _postTotalShares, - uint256 _etherLockedOnWithdrawalQueue, - uint256 _sharesBurntDueToWithdrawals, - uint256 _simulatedShareRate - ) external view { - if (checkSimulatedShareRateReverts) revert(); - } - // mocking function mock__checkAccountingOracleReportReverts(bool reverts) external { @@ -74,10 +66,6 @@ contract OracleReportSanityChecker__MockForAccounting { checkWithdrawalQueueOracleReportReverts = reverts; } - function mock__checkSimulatedShareRateReverts(bool reverts) external { - checkSimulatedShareRateReverts = reverts; - } - function mock__smoothenTokenRebaseReturn( uint256 withdrawals, uint256 elRewards, diff --git a/test/0.8.9/contracts/oracle/OracleReportSanityCheckerMocks.sol b/test/0.8.9/contracts/oracle/OracleReportSanityCheckerMocks.sol deleted file mode 100644 index 3fe1a880a..000000000 --- a/test/0.8.9/contracts/oracle/OracleReportSanityCheckerMocks.sol +++ /dev/null @@ -1,159 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Lido -// SPDX-License-Identifier: GPL-3.0 -// for testing purposes only - -pragma solidity 0.8.9; - -import {IWithdrawalQueue} from "contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol"; - -contract LidoStub { - uint256 private _shareRate = 1 ether; - - function getSharesByPooledEth(uint256 _sharesAmount) external view returns (uint256) { - return (_shareRate * _sharesAmount) / 1 ether; - } - - function setShareRate(uint256 _value) external { - _shareRate = _value; - } -} - -contract WithdrawalQueueStub is IWithdrawalQueue { - mapping(uint256 => uint256) private _timestamps; - - function setRequestTimestamp(uint256 _requestId, uint256 _timestamp) external { - _timestamps[_requestId] = _timestamp; - } - - function getWithdrawalStatus( - uint256[] calldata _requestIds - ) external view returns (WithdrawalRequestStatus[] memory statuses) { - statuses = new WithdrawalRequestStatus[](_requestIds.length); - for (uint256 i; i < _requestIds.length; ++i) { - statuses[i].timestamp = _timestamps[_requestIds[i]]; - } - } -} - -contract BurnerStub { - uint256 private nonCover; - uint256 private cover; - - function getSharesRequestedToBurn() external view returns (uint256 coverShares, uint256 nonCoverShares) { - coverShares = cover; - nonCoverShares = nonCover; - } - - function setSharesRequestedToBurn(uint256 _cover, uint256 _nonCover) external { - cover = _cover; - nonCover = _nonCover; - } -} - -interface ILidoLocator { - function lido() external view returns (address); - - function burner() external view returns (address); - - function withdrawalVault() external view returns (address); - - function withdrawalQueue() external view returns (address); -} - -contract LidoLocatorStub is ILidoLocator { - address private immutable LIDO; - address private immutable WITHDRAWAL_VAULT; - address private immutable WITHDRAWAL_QUEUE; - address private immutable EL_REWARDS_VAULT; - address private immutable BURNER; - - constructor( - address _lido, - address _withdrawalVault, - address _withdrawalQueue, - address _elRewardsVault, - address _burner - ) { - LIDO = _lido; - WITHDRAWAL_VAULT = _withdrawalVault; - WITHDRAWAL_QUEUE = _withdrawalQueue; - EL_REWARDS_VAULT = _elRewardsVault; - BURNER = _burner; - } - - function lido() external view returns (address) { - return LIDO; - } - - function withdrawalQueue() external view returns (address) { - return WITHDRAWAL_QUEUE; - } - - function withdrawalVault() external view returns (address) { - return WITHDRAWAL_VAULT; - } - - function elRewardsVault() external view returns (address) { - return EL_REWARDS_VAULT; - } - - function burner() external view returns (address) { - return BURNER; - } -} - -contract OracleReportSanityCheckerStub { - error SelectorNotFound(bytes4 sig, uint256 value, bytes data); - - fallback() external payable { - revert SelectorNotFound(msg.sig, msg.value, msg.data); - } - - function checkAccountingOracleReport( - uint256 _timeElapsed, - uint256 _preCLBalance, - uint256 _postCLBalance, - uint256 _withdrawalVaultBalance, - uint256 _elRewardsVaultBalance, - uint256 _sharesRequestedToBurn, - uint256 _preCLValidators, - uint256 _postCLValidators - ) external view {} - - function checkWithdrawalQueueOracleReport( - uint256[] calldata _withdrawalFinalizationBatches, - uint256 _reportTimestamp - ) external view {} - - function checkSimulatedShareRate( - uint256 _postTotalPooledEther, - uint256 _postTotalShares, - uint256 _etherLockedOnWithdrawalQueue, - uint256 _sharesBurntDueToWithdrawals, - uint256 _simulatedShareRate - ) external view {} - - function smoothenTokenRebase( - uint256, - uint256, - uint256, - uint256, - uint256 _withdrawalVaultBalance, - uint256 _elRewardsVaultBalance, - uint256, - uint256 _etherToLockForWithdrawals, - uint256 - ) - external - view - returns (uint256 withdrawals, uint256 elRewards, uint256 simulatedSharesToBurn, uint256 sharesToBurn) - { - withdrawals = _withdrawalVaultBalance; - elRewards = _elRewardsVaultBalance; - - simulatedSharesToBurn = 0; - sharesToBurn = _etherToLockForWithdrawals; - } - - function checkExtraDataItemsCountPerTransaction(uint256 _extraDataListItemsCount) external view {} -}