Skip to content

Commit 015eedf

Browse files
committed
feat: redeem tests
1 parent d01960c commit 015eedf

File tree

1 file changed

+58
-21
lines changed

1 file changed

+58
-21
lines changed

test/integration/core/redeems-reserve.failureModes.integration.ts

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -752,10 +752,8 @@ describe("Integration: Redeems reserve — failure modes", () => {
752752
const { lido, withdrawalQueue } = ctx.contracts;
753753
await setupVault(ether("1000"), 500n);
754754

755-
// Transfer stETH to stranger for redeem
756755
await lido.connect(holder).transfer(stranger.address, ether("20"), { gasPrice: 0 });
757756

758-
// Enter bunker mode via large negative CL rebase (skipWithdrawals must be false for bunker flag)
759757
await resetCLBalanceDecreaseWindow(ctx);
760758
await reportWithEffectiveClDiff(ctx, ether("-1"), { excludeVaultsBalances: true });
761759
expect(await withdrawalQueue.isBunkerModeActive()).to.equal(true);
@@ -767,19 +765,50 @@ describe("Integration: Redeems reserve — failure modes", () => {
767765
vault.connect(stranger).redeem(ether("1"), stranger.address, { gasPrice: 0 }),
768766
).to.be.revertedWithCustomError(vault, "BunkerMode");
769767

770-
// Exit bunker mode via neutral report
768+
// Exit bunker → redeem works again
771769
await reportWithEffectiveClDiff(ctx, 0n, { excludeVaultsBalances: true });
772770
expect(await withdrawalQueue.isBunkerModeActive()).to.equal(false);
773-
774-
// Vault survived bunker — still reconciled and at target
775771
await assertVaultReconciled();
776772
await assertVaultAtTarget();
777773

778-
// Redeem works again
779774
const { etherAmount } = await redeemWithReceipt(stranger, ether("1"), stranger.address);
780775
expect(etherAmount).to.be.gt(0n);
781776
});
782777

778+
it("9.2 Bunker A/B: share rate after negative rebase — same with and without prior redeem", async () => {
779+
const { lido } = ctx.contracts;
780+
781+
// Scenario A: no redeem → negative rebase
782+
await setupVault(ether("1000"), 500n);
783+
await resetCLBalanceDecreaseWindow(ctx);
784+
await reportWithEffectiveClDiff(ctx, ether("-1"), { excludeVaultsBalances: true, skipWithdrawals: true });
785+
const rateA = await lido.getPooledEthByShares(ether("1"));
786+
const tpeA = await lido.getTotalPooledEther();
787+
const sharesA = await lido.getTotalShares();
788+
789+
// Restore snapshot and run Scenario B: redeem → same negative rebase
790+
await Snapshot.restore(testSnapshot);
791+
testSnapshot = await Snapshot.take();
792+
793+
await setupVault(ether("1000"), 500n);
794+
await redeemWithReceipt(holder, ether("30"), holder.address);
795+
await resetCLBalanceDecreaseWindow(ctx);
796+
await reportWithEffectiveClDiff(ctx, ether("-1"), { excludeVaultsBalances: true, skipWithdrawals: true });
797+
const rateB = await lido.getPooledEthByShares(ether("1"));
798+
const tpeB = await lido.getTotalPooledEther();
799+
const sharesB = await lido.getTotalShares();
800+
801+
// B has less TPE and shares (30 ETH redeemed + burned)
802+
expect(tpeB).to.be.lt(tpeA);
803+
expect(sharesB).to.be.lt(sharesA);
804+
805+
// Rate should be approximately the same — redeem is rate-neutral
806+
// Deviation bounded: redeemedEther/IE × rate_change ≈ 30/24000 × 0.004% ≈ negligible
807+
const rateDiff = rateA > rateB ? rateA - rateB : rateB - rateA;
808+
const deviationBP = (rateDiff * 10000n) / rateA;
809+
expect(deviationBP).to.be.lte(1n); // < 1 BP
810+
});
811+
783812
// ═══════════════════════════════════════════════════════════════════════
784813
// 10. Negative Rebase — Target Recalculation
785814
// ═══════════════════════════════════════════════════════════════════════
@@ -885,35 +914,43 @@ describe("Integration: Redeems reserve — failure modes", () => {
885914
// 13. Insurance Burn — rate impact on redeem
886915
// ═══════════════════════════════════════════════════════════════════════
887916

888-
it("13.1 Insurance burn increases rate — redeemer gets more ETH per stETH", async () => {
889-
const { lido } = ctx.contracts;
917+
it("13.1 Insurance burn via report increases rate — vault and reserve unaffected", async () => {
918+
const { lido, burner } = ctx.contracts;
890919
await setupVault(ether("1000"), 500n);
891920

892921
const vaultBefore = await ethers.provider.getBalance(await vault.getAddress());
893922
const trackedBefore = await lido.getRedeemsReserveVaultEth();
894923
const targetBefore = await lido.getRedeemsReserveTarget();
895924
const rateBefore = await lido.getPooledEthByShares(ether("1"));
896925

897-
const burnStETH = ether("5");
898-
const sharesToBurn = await lido.getSharesByPooledEth(burnStETH);
899-
const burnerAddr = await ctx.contracts.locator.burner();
900-
await lido.connect(holder).transfer(burnerAddr, burnStETH, { gasPrice: 0 });
901-
const burnerSigner = await impersonate(burnerAddr, ether("10"));
902-
await lido.connect(burnerSigner).burnShares(sharesToBurn);
926+
// Request cover burn via standard Burner path
927+
const agent = await ctx.getSigner("agent");
928+
const coverRole = await burner.REQUEST_BURN_SHARES_ROLE();
929+
if (!(await burner.hasRole(coverRole, holder.address))) {
930+
await burner.connect(agent).grantRole(coverRole, holder.address);
931+
}
932+
const coverStETH = ether("5");
933+
const coverShares = await lido.getSharesByPooledEth(coverStETH);
934+
await lido.connect(holder).approve(await burner.getAddress(), coverStETH, { gasPrice: 0 });
935+
await burner.connect(holder).requestBurnSharesForCover(holder.address, coverShares);
936+
937+
const [coverPending] = await burner.getSharesRequestedToBurn();
938+
expect(coverPending).to.equal(coverShares);
939+
940+
// Report burns cover shares → rate increases (same ETH, fewer shares)
941+
await report(ctx, reportOpts);
942+
943+
const [coverAfter] = await burner.getSharesRequestedToBurn();
944+
expect(coverAfter).to.equal(0n);
903945

904946
const rateAfter = await lido.getPooledEthByShares(ether("1"));
905947
expect(rateAfter).to.be.gt(rateBefore);
906-
// Vault balance, tracked, and target unchanged by insurance burn
948+
949+
// Vault balance, tracked, and target unchanged by cover burn
907950
expect(await ethers.provider.getBalance(await vault.getAddress())).to.equal(vaultBefore);
908951
expect(await lido.getRedeemsReserveVaultEth()).to.equal(trackedBefore);
909952
expect(await lido.getRedeemsReserveTarget()).to.equal(targetBefore);
910953

911-
// Fixed share amount gets more ETH at higher rate
912-
const probeShares = ether("1");
913-
const ethPerShareBefore = (probeShares * rateBefore) / ether("1");
914-
const ethPerShareAfter = (probeShares * rateAfter) / ether("1");
915-
expect(ethPerShareAfter).to.be.gt(ethPerShareBefore);
916-
917954
// Redeem still works at new rate
918955
const { etherAmount } = await redeemWithReceipt(holder, ether("10"), holder.address);
919956
expect(etherAmount).to.be.gt(0n);

0 commit comments

Comments
 (0)