11import { expect } from "chai" ;
2+ import { ContractTransactionReceipt , ZeroAddress } from "ethers" ;
23import { ethers } from "hardhat" ;
34
45import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers" ;
6+ import { setBalance } from "@nomicfoundation/hardhat-network-helpers" ;
57
68import { Dashboard , StakingVault } from "typechain-types" ;
79
8- import { MAX_UINT256 } from "lib" ;
10+ import { MAX_UINT256 , ONE_GWEI } from "lib" ;
911import {
1012 changeTier ,
1113 createVaultWithDashboard ,
1214 DEFAULT_TIER_PARAMS ,
15+ finalizeWQViaElVault ,
1316 getProtocolContext ,
17+ getReportTimeElapsed ,
1418 ProtocolContext ,
1519 report ,
1620 reportVaultDataWithProof ,
@@ -36,9 +40,13 @@ describe("Integration: Vault with bad debt", () => {
3640
3741 before ( async ( ) => {
3842 ctx = await getProtocolContext ( ) ;
39- const { lido, stakingVaultFactory, vaultHub } = ctx . contracts ;
43+ const { lido, stakingVaultFactory, vaultHub, elRewardsVault } = ctx . contracts ;
4044 originalSnapshot = await Snapshot . take ( ) ;
4145
46+ await waitNextAvailableReportTime ( ctx ) ;
47+ await finalizeWQViaElVault ( ctx ) ;
48+ await setBalance ( elRewardsVault . address , 0 ) ;
49+
4250 [ , owner , nodeOperator , otherOwner , daoAgent ] = await ethers . getSigners ( ) ;
4351 await setupLidoForVaults ( ctx ) ;
4452
@@ -74,6 +82,12 @@ describe("Integration: Vault with bad debt", () => {
7482 await vaultHub . connect ( await ctx . getSigner ( "agent" ) ) . grantRole ( await vaultHub . BAD_DEBT_MASTER_ROLE ( ) , daoAgent ) ;
7583 } ) ;
7684
85+ const getFirstEvent = ( receipt : ContractTransactionReceipt , eventName : string ) => {
86+ const events = ctx . getEvents ( receipt , eventName ) ;
87+ expect ( events . length ) . to . be . greaterThan ( 0 ) ;
88+ return events [ 0 ] ;
89+ } ;
90+
7791 beforeEach ( async ( ) => ( snapshot = await Snapshot . take ( ) ) ) ;
7892 afterEach ( async ( ) => await Snapshot . restore ( snapshot ) ) ;
7993 after ( async ( ) => await Snapshot . restore ( originalSnapshot ) ) ;
@@ -233,4 +247,95 @@ describe("Integration: Vault with bad debt", () => {
233247 expect ( await vaultHub . badDebtToInternalize ( ) ) . to . be . equal ( 0n ) ;
234248 } ) ;
235249 } ) ;
250+
251+ describe ( "Report simulation (accounting)" , ( ) => {
252+ it ( "simulateOracleReport result matches handleOracleReport while bad debt" , async ( ) => {
253+ const { lido, hashConsensus, accounting, elRewardsVault, withdrawalVault, withdrawalQueue, vaultHub } =
254+ ctx . contracts ;
255+
256+ const clRebase = ether ( "50" ) ;
257+ const elRewards = ether ( "100" ) ;
258+ const withdrawalVaultBalance = ether ( "100" ) ;
259+ const withdrawalRequestAmount = ether ( "20" ) ;
260+
261+ await lido . connect ( otherOwner ) . submit ( ZeroAddress , { value : withdrawalRequestAmount } ) ;
262+ await lido . connect ( otherOwner ) . approve ( withdrawalQueue . address , withdrawalRequestAmount ) ;
263+ await withdrawalQueue . connect ( otherOwner ) . requestWithdrawals ( [ withdrawalRequestAmount ] , otherOwner . address ) ;
264+ const withdrawalRequestId = await withdrawalQueue . getLastRequestId ( ) ;
265+
266+ await setBalance ( elRewardsVault . address , elRewards ) ;
267+ await setBalance ( withdrawalVault . address , withdrawalVaultBalance ) ;
268+
269+ const badDebtShares =
270+ ( await dashboard . liabilityShares ( ) ) - ( await lido . getSharesByPooledEth ( await dashboard . totalValue ( ) ) ) ;
271+ await vaultHub . connect ( daoAgent ) . internalizeBadDebt ( stakingVault , badDebtShares ) ;
272+
273+ const refSlot = ( await hashConsensus . getCurrentFrame ( ) ) . refSlot ;
274+ const { genesisTime, secondsPerSlot } = await hashConsensus . getChainConfig ( ) ;
275+ const reportTimestamp = genesisTime + refSlot * secondsPerSlot ;
276+ const { timeElapsed } = await getReportTimeElapsed ( ctx ) ;
277+
278+ const params = { clDiff : clRebase , reportElVault : true , reportWithdrawalsVault : true , dryRun : true } ;
279+ const { data : reportData } = await report ( ctx , params ) ;
280+
281+ const externalSharesBefore = await lido . getExternalShares ( ) ;
282+ const totalSharesBefore = await lido . getTotalShares ( ) ;
283+ const internalSharesBefore = totalSharesBefore - externalSharesBefore ;
284+
285+ const elRewardsBalanceBefore = await ethers . provider . getBalance ( elRewardsVault ) ;
286+ const withdrawalVaultBalanceBefore = await ethers . provider . getBalance ( withdrawalVault ) ;
287+
288+ const simulated = await accounting . simulateOracleReport ( {
289+ timestamp : reportTimestamp ,
290+ timeElapsed,
291+ clValidators : reportData . numValidators ,
292+ clBalance : BigInt ( reportData . clBalanceGwei ) * ONE_GWEI ,
293+ withdrawalVaultBalance : reportData . withdrawalVaultBalance ,
294+ elRewardsVaultBalance : reportData . elRewardsVaultBalance ,
295+ sharesRequestedToBurn : reportData . sharesRequestedToBurn ,
296+ withdrawalFinalizationBatches : reportData . withdrawalFinalizationBatches ,
297+ simulatedShareRate : reportData . simulatedShareRate ,
298+ } ) ;
299+
300+ const { reportTx } = await report ( ctx , { ...params , dryRun : false } ) ;
301+
302+ const reportTxReceipt = await reportTx ! . wait ( ) ;
303+ const tokenRebasedEvent = getFirstEvent ( reportTxReceipt ! , "TokenRebased" ) ;
304+
305+ expect ( simulated . preTotalShares ) . to . equal ( tokenRebasedEvent . args . preTotalShares ) ;
306+ expect ( simulated . preTotalPooledEther ) . to . equal ( tokenRebasedEvent . args . preTotalEther ) ;
307+ expect ( simulated . postTotalShares ) . to . equal ( tokenRebasedEvent . args . postTotalShares ) ;
308+ expect ( simulated . postTotalPooledEther ) . to . equal ( tokenRebasedEvent . args . postTotalEther ) ;
309+
310+ const externalSharesAfter = await lido . getExternalShares ( ) ;
311+ const totalSharesAfter = await lido . getTotalShares ( ) ;
312+ const totalPooledEtherAfter = await lido . getTotalPooledEther ( ) ;
313+
314+ const elRewardsBalanceAfter = await ethers . provider . getBalance ( elRewardsVault ) ;
315+ const withdrawalVaultBalanceAfter = await ethers . provider . getBalance ( withdrawalVault ) ;
316+
317+ expect ( elRewardsBalanceBefore - simulated . elRewardsVaultTransfer ) . to . equal ( elRewardsBalanceAfter ) ;
318+ expect ( withdrawalVaultBalanceBefore - simulated . withdrawalsVaultTransfer ) . to . equal ( withdrawalVaultBalanceAfter ) ;
319+
320+ const [ withdrawalRequestData ] = await withdrawalQueue . getWithdrawalStatus ( [ withdrawalRequestId ] ) ;
321+ const actualBadDebtInternalized = externalSharesBefore - externalSharesAfter ;
322+
323+ expect ( simulated . etherToFinalizeWQ ) . to . equal ( withdrawalRequestAmount ) ;
324+ expect ( simulated . etherToFinalizeWQ ) . to . equal ( withdrawalRequestData . amountOfStETH ) ;
325+ expect ( simulated . sharesToFinalizeWQ ) . to . equal ( withdrawalRequestData . amountOfShares ) ;
326+ expect ( simulated . sharesToBurnForWithdrawals ) . to . equal ( withdrawalRequestData . amountOfShares ) ;
327+ expect ( simulated . totalSharesToBurn ) . to . equal ( totalSharesBefore - totalSharesAfter + simulated . sharesToMintAsFees ) ;
328+
329+ expect ( simulated . postInternalShares ) . to . equal ( totalSharesAfter - externalSharesAfter ) ;
330+ expect ( simulated . postInternalShares ) . to . equal (
331+ internalSharesBefore - simulated . totalSharesToBurn + simulated . sharesToMintAsFees + actualBadDebtInternalized ,
332+ ) ;
333+
334+ expect ( simulated . postInternalEther ) . to . equal ( totalPooledEtherAfter - ( await lido . getExternalEther ( ) ) ) ;
335+ expect ( simulated . sharesToMintAsFees ) . to . equal ( tokenRebasedEvent . args . sharesMintedAsFees ) ;
336+
337+ const elRewardsReceived = ctx . getEvents ( reportTxReceipt ! , "ELRewardsReceived" ) ;
338+ expect ( simulated . elRewardsVaultTransfer ) . to . equal ( elRewardsReceived [ 0 ] . args . amount ) ;
339+ } ) ;
340+ } ) ;
236341} ) ;
0 commit comments