Skip to content

Commit 184dc4d

Browse files
committed
feat: add integration test to check report simulation while bad debt
1 parent 5211a7e commit 184dc4d

File tree

1 file changed

+107
-2
lines changed

1 file changed

+107
-2
lines changed

test/integration/vaults/bad-debt.integration.ts

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { expect } from "chai";
2+
import { ContractTransactionReceipt, ZeroAddress } from "ethers";
23
import { ethers } from "hardhat";
34

45
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
6+
import { setBalance } from "@nomicfoundation/hardhat-network-helpers";
57

68
import { Dashboard, StakingVault } from "typechain-types";
79

8-
import { MAX_UINT256 } from "lib";
10+
import { MAX_UINT256, ONE_GWEI } from "lib";
911
import {
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

Comments
 (0)