@@ -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