|  | 
|  | 1 | +import { expect } from "chai"; | 
|  | 2 | +import { ethers } from "hardhat"; | 
|  | 3 | + | 
|  | 4 | +import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers"; | 
|  | 5 | +import { setBalance } from "@nomicfoundation/hardhat-network-helpers"; | 
|  | 6 | + | 
|  | 7 | +import { Dashboard, StakingVault, VaultHub } from "typechain-types"; | 
|  | 8 | + | 
|  | 9 | +import { findEventsWithInterfaces } from "lib"; | 
|  | 10 | +import { | 
|  | 11 | +  createVaultWithDashboard, | 
|  | 12 | +  getProtocolContext, | 
|  | 13 | +  ProtocolContext, | 
|  | 14 | +  report, | 
|  | 15 | +  reportVaultDataWithProof, | 
|  | 16 | +  setupLidoForVaults, | 
|  | 17 | +} from "lib/protocol"; | 
|  | 18 | +import { finalizeWQViaElVault } from "lib/protocol"; | 
|  | 19 | +import { ether } from "lib/units"; | 
|  | 20 | + | 
|  | 21 | +import { Snapshot } from "test/suite"; | 
|  | 22 | + | 
|  | 23 | +describe("Integration: accounting", () => { | 
|  | 24 | +  let ctx: ProtocolContext; | 
|  | 25 | +  let snapshot: string; | 
|  | 26 | +  let originalSnapshot: string; | 
|  | 27 | + | 
|  | 28 | +  let owner: HardhatEthersSigner; | 
|  | 29 | +  let stranger: HardhatEthersSigner; | 
|  | 30 | +  let agentSigner: HardhatEthersSigner; | 
|  | 31 | +  let nodeOperator: HardhatEthersSigner; | 
|  | 32 | +  let stakingVault: StakingVault; | 
|  | 33 | +  let dashboard: Dashboard; | 
|  | 34 | +  let vaultHub: VaultHub; | 
|  | 35 | + | 
|  | 36 | +  before(async () => { | 
|  | 37 | +    ctx = await getProtocolContext(); | 
|  | 38 | +    const { stakingVaultFactory } = ctx.contracts; | 
|  | 39 | +    vaultHub = ctx.contracts.vaultHub; | 
|  | 40 | +    originalSnapshot = await Snapshot.take(); | 
|  | 41 | + | 
|  | 42 | +    [, owner, nodeOperator, stranger] = await ethers.getSigners(); | 
|  | 43 | +    await setupLidoForVaults(ctx); | 
|  | 44 | + | 
|  | 45 | +    agentSigner = await ctx.getSigner("agent"); | 
|  | 46 | + | 
|  | 47 | +    ({ stakingVault, dashboard } = await createVaultWithDashboard( | 
|  | 48 | +      ctx, | 
|  | 49 | +      stakingVaultFactory, | 
|  | 50 | +      owner, | 
|  | 51 | +      nodeOperator, | 
|  | 52 | +      nodeOperator, | 
|  | 53 | +    )); | 
|  | 54 | + | 
|  | 55 | +    dashboard = dashboard.connect(owner); | 
|  | 56 | + | 
|  | 57 | +    await dashboard.fund({ value: ether("100") }); | 
|  | 58 | +    await dashboard.mintShares(owner, await dashboard.remainingMintingCapacityShares(0n)); | 
|  | 59 | + | 
|  | 60 | +    await ctx.contracts.lido.connect(stranger).submit(owner.address, { value: ether("100") }); | 
|  | 61 | + | 
|  | 62 | +    await finalizeWQViaElVault(ctx); | 
|  | 63 | +    await reportVaultDataWithProof(ctx, stakingVault); | 
|  | 64 | + | 
|  | 65 | +    await setBalance(ctx.contracts.elRewardsVault.address, 0); | 
|  | 66 | +    await setBalance(ctx.contracts.withdrawalVault.address, 0); | 
|  | 67 | +  }); | 
|  | 68 | + | 
|  | 69 | +  beforeEach(async () => (snapshot = await Snapshot.take())); | 
|  | 70 | +  afterEach(async () => await Snapshot.restore(snapshot)); | 
|  | 71 | +  after(async () => await Snapshot.restore(originalSnapshot)); | 
|  | 72 | + | 
|  | 73 | +  context("Withdrawals: finalization with external shares", () => { | 
|  | 74 | +    it("Should finalize requests from withdrawal vault using force rebalance", async () => { | 
|  | 75 | +      const withdrawalRequestAmount = ether("10"); | 
|  | 76 | +      const { withdrawalQueue, lido } = ctx.contracts; | 
|  | 77 | +      const stakingVaultAddress = await stakingVault.getAddress(); | 
|  | 78 | + | 
|  | 79 | +      await lido.connect(owner).approve(withdrawalQueue.address, withdrawalRequestAmount); | 
|  | 80 | +      await lido.connect(stranger).approve(withdrawalQueue.address, withdrawalRequestAmount); | 
|  | 81 | + | 
|  | 82 | +      const firstRequestTx = await withdrawalQueue | 
|  | 83 | +        .connect(owner) | 
|  | 84 | +        .requestWithdrawals([withdrawalRequestAmount], owner.address); | 
|  | 85 | +      const secondRequestTx = await withdrawalQueue | 
|  | 86 | +        .connect(stranger) | 
|  | 87 | +        .requestWithdrawals([withdrawalRequestAmount], stranger.address); | 
|  | 88 | + | 
|  | 89 | +      const firstRequestReceipt = await firstRequestTx.wait(); | 
|  | 90 | +      const secondRequestReceipt = await secondRequestTx.wait(); | 
|  | 91 | + | 
|  | 92 | +      const [firstRequestEvent] = findEventsWithInterfaces(firstRequestReceipt!, "WithdrawalRequested", [ | 
|  | 93 | +        withdrawalQueue.interface, | 
|  | 94 | +      ]); | 
|  | 95 | +      const [secondRequestEvent] = findEventsWithInterfaces(secondRequestReceipt!, "WithdrawalRequested", [ | 
|  | 96 | +        withdrawalQueue.interface, | 
|  | 97 | +      ]); | 
|  | 98 | + | 
|  | 99 | +      const firstRequest = firstRequestEvent!.args.requestId; | 
|  | 100 | +      const secondRequest = secondRequestEvent!.args.requestId; | 
|  | 101 | + | 
|  | 102 | +      let [firstStatus, secondStatus] = await withdrawalQueue.getWithdrawalStatus([firstRequest, secondRequest]); | 
|  | 103 | + | 
|  | 104 | +      expect(firstStatus.isFinalized).to.be.false; | 
|  | 105 | +      expect(secondStatus.isFinalized).to.be.false; | 
|  | 106 | + | 
|  | 107 | +      // Set balance to cover only first request | 
|  | 108 | +      await setBalance(ctx.contracts.lido.address, withdrawalRequestAmount); | 
|  | 109 | + | 
|  | 110 | +      await expect(report(ctx, { clDiff: 0n })).to.be.reverted; | 
|  | 111 | + | 
|  | 112 | +      const balanceBefore = await ethers.provider.getBalance(stakingVaultAddress); | 
|  | 113 | + | 
|  | 114 | +      await vaultHub.connect(agentSigner).setLiabilitySharesTarget(stakingVaultAddress, 0n); | 
|  | 115 | +      const forceRebalanceTx = await vaultHub.connect(agentSigner).forceRebalance(stakingVaultAddress); | 
|  | 116 | + | 
|  | 117 | +      const forceRebalanceReceipt = await forceRebalanceTx.wait(); | 
|  | 118 | +      const [rebalanceEvent] = findEventsWithInterfaces(forceRebalanceReceipt!, "VaultRebalanced", [ | 
|  | 119 | +        vaultHub.interface, | 
|  | 120 | +      ]); | 
|  | 121 | +      const rebalancedValue = rebalanceEvent!.args.etherWithdrawn; | 
|  | 122 | + | 
|  | 123 | +      await report(ctx, { clDiff: 0n }); | 
|  | 124 | + | 
|  | 125 | +      const balanceAfter = await ethers.provider.getBalance(stakingVault); | 
|  | 126 | + | 
|  | 127 | +      [firstStatus, secondStatus] = await withdrawalQueue.getWithdrawalStatus([firstRequest, secondRequest]); | 
|  | 128 | + | 
|  | 129 | +      expect(firstStatus.isFinalized).to.be.true; | 
|  | 130 | +      expect(secondStatus.isFinalized).to.be.true; | 
|  | 131 | + | 
|  | 132 | +      const balanceWithdrawn = balanceBefore - balanceAfter; | 
|  | 133 | + | 
|  | 134 | +      expect(balanceWithdrawn).to.equal(rebalancedValue); | 
|  | 135 | +      expect(rebalancedValue).to.be.gte(withdrawalRequestAmount); | 
|  | 136 | +    }); | 
|  | 137 | +  }); | 
|  | 138 | +}); | 
0 commit comments