Skip to content

Commit 3d83f05

Browse files
committed
test: enhance PauseTest with soft and hard pause functionality, including new assertions and pause level management
1 parent 9afe081 commit 3d83f05

File tree

2 files changed

+262
-24
lines changed

2 files changed

+262
-24
lines changed

test/Pause.t.sol

Lines changed: 221 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -183,13 +183,13 @@ contract PauseTest is Test {
183183
.pauseStatus();
184184

185185
assertTrue(isPausedD);
186-
assertEq(reasonD, "Emergency system-wide pause");
186+
assertEq(reasonD, "");
187187
assertTrue(isPausedC);
188-
assertEq(reasonC, "Emergency system-wide pause");
188+
assertEq(reasonC, "");
189189
assertTrue(isPausedPI);
190-
assertEq(reasonPI, "Emergency system-wide pause");
190+
assertEq(reasonPI, "");
191191
assertTrue(isPausedPO);
192-
assertEq(reasonPO, "Emergency system-wide pause");
192+
assertEq(reasonPO, "");
193193
}
194194

195195
function test_CanUnpauseAllContractsSimultaneously() public {
@@ -422,26 +422,6 @@ contract PauseTest is Test {
422422
assertTrue(isPausedPO, "PegOut should be paused");
423423
}
424424

425-
function test_CanPerformEmergencyPauseWithCustomReason() public {
426-
_grantPauserRole();
427-
428-
string
429-
memory reason = "Critical security vulnerability detected - immediate pause required";
430-
431-
vm.prank(pauser);
432-
pauseRegistry.pause(reason);
433-
434-
(, string memory reasonD, ) = flyoverDiscovery.pauseStatus();
435-
(, string memory reasonC, ) = collateralManagement.pauseStatus();
436-
(, string memory reasonPI, ) = pegInContract.pauseStatus();
437-
(, string memory reasonPO, ) = pegOutContract.pauseStatus();
438-
439-
assertEq(reasonD, reason);
440-
assertEq(reasonC, reason);
441-
assertEq(reasonPI, reason);
442-
assertEq(reasonPO, reason);
443-
}
444-
445425
function test_MaintainsPauseStateAcrossMultipleOperations() public {
446426
_grantPauserRole();
447427

@@ -478,4 +458,221 @@ contract PauseTest is Test {
478458
assertTrue(isPausedPI);
479459
assertTrue(isPausedPO);
480460
}
461+
462+
// ---------- Two-level pause (soft vs hard) ----------
463+
464+
function test_SoftPause_BlocksNewBusiness_AllowsContinuations() public {
465+
_grantPauserRole();
466+
vm.prank(signers[1], signers[1]);
467+
flyoverDiscovery.register{value: 1 ether}(
468+
"LP",
469+
"http://localhost/api",
470+
true,
471+
Flyover.ProviderType.PegIn
472+
);
473+
collateralManagement.grantRole(
474+
collateralManagement.COLLATERAL_ADDER(),
475+
owner
476+
);
477+
collateralManagement.addPegInCollateralTo{value: 0.5 ether}(signers[1]);
478+
vm.prank(signers[1]);
479+
pegInContract.deposit{value: 0.5 ether}();
480+
481+
vm.prank(pauser);
482+
pauseRegistry.pause("Soft pause"); // level 1
483+
484+
assertEq(pauseRegistry.pauseLevel(), 1);
485+
486+
// New business blocked
487+
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
488+
flyoverDiscovery.register{value: 0.1 ether}(
489+
"LP2",
490+
"url",
491+
true,
492+
Flyover.ProviderType.PegIn
493+
);
494+
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
495+
vm.prank(signers[1]);
496+
pegInContract.deposit{value: 0.1 ether}();
497+
498+
// LP metadata updates are allowed during soft pause (not a new flow initiation)
499+
vm.prank(signers[1]);
500+
flyoverDiscovery.updateProvider(
501+
"LP-updated",
502+
"http://localhost/api/v2"
503+
);
504+
505+
// Continuations allowed (whenNotHardPaused passes at level 1)
506+
vm.prank(signers[1]);
507+
pegInContract.withdraw(0.3 ether);
508+
}
509+
510+
function test_HardPause_BlocksAllStateChangingIncludingContinuations()
511+
public
512+
{
513+
_grantPauserRole();
514+
vm.prank(signers[1], signers[1]);
515+
flyoverDiscovery.register{value: 1 ether}(
516+
"LP",
517+
"http://localhost/api",
518+
true,
519+
Flyover.ProviderType.PegIn
520+
);
521+
collateralManagement.grantRole(
522+
collateralManagement.COLLATERAL_ADDER(),
523+
owner
524+
);
525+
collateralManagement.addPegInCollateralTo{value: 0.5 ether}(signers[1]);
526+
vm.prank(signers[1]);
527+
pegInContract.deposit{value: 0.5 ether}();
528+
529+
vm.prank(pauser);
530+
pauseRegistry.setPauseLevel(2); // hard pause
531+
532+
assertEq(pauseRegistry.pauseLevel(), 2);
533+
534+
// New business blocked
535+
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
536+
flyoverDiscovery.register{value: 0.1 ether}(
537+
"LP2",
538+
"url",
539+
true,
540+
Flyover.ProviderType.PegIn
541+
);
542+
543+
// Metadata updates are blocked on hard pause (full freeze)
544+
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
545+
vm.prank(signers[1]);
546+
flyoverDiscovery.updateProvider(
547+
"LP-updated",
548+
"http://localhost/api/v2"
549+
);
550+
551+
// Continuations/outflows also blocked at level 2
552+
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
553+
vm.prank(signers[1]);
554+
pegInContract.withdraw(0.3 ether);
555+
556+
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
557+
vm.prank(signers[1]);
558+
collateralManagement.resign();
559+
560+
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
561+
vm.prank(signers[1]);
562+
collateralManagement.withdrawRewards();
563+
564+
vm.expectRevert(abi.encodeWithSignature("EnforcedPause()"));
565+
vm.prank(signers[1]);
566+
pegOutContract.withdraw(payable(signers[1]), 0);
567+
}
568+
569+
function test_PauseUnpause_BackwardCompatible_Level1() public {
570+
_grantPauserRole();
571+
vm.prank(pauser);
572+
pauseRegistry.pause("Reason");
573+
assertEq(pauseRegistry.pauseLevel(), 1);
574+
assertTrue(pauseRegistry.paused());
575+
(bool isPaused, , ) = pauseRegistry.pauseStatus();
576+
assertTrue(isPaused);
577+
578+
vm.prank(pauser);
579+
pauseRegistry.unpause();
580+
assertEq(pauseRegistry.pauseLevel(), 0);
581+
assertFalse(pauseRegistry.paused());
582+
}
583+
584+
// ---------- Hard-pause timer overlap ----------
585+
586+
function test_HardPause_AppendsAndClosesLog() public {
587+
_grantPauserRole();
588+
assertEq(pauseRegistry.hardPausesCount(), 0);
589+
590+
vm.prank(pauser);
591+
pauseRegistry.setPauseLevel(2);
592+
assertEq(pauseRegistry.hardPausesCount(), 1);
593+
(uint64 sTs, uint64 eTs, uint64 sBl, uint64 eBl) = pauseRegistry
594+
.hardPauses(0);
595+
assertTrue(sTs > 0 && sBl > 0);
596+
assertEq(eTs, 0);
597+
assertEq(eBl, 0);
598+
599+
vm.prank(pauser);
600+
pauseRegistry.setPauseLevel(0);
601+
assertEq(pauseRegistry.hardPausesCount(), 1);
602+
(, eTs, , eBl) = pauseRegistry.hardPauses(0);
603+
assertTrue(eTs > 0 && eBl > 0);
604+
}
605+
606+
function test_ComputePauseOverlap_AccountsForHardPauseWindow() public {
607+
_grantPauserRole();
608+
uint256 startTs = block.timestamp;
609+
610+
vm.prank(pauser);
611+
pauseRegistry.setPauseLevel(2);
612+
vm.warp(block.timestamp + 90);
613+
614+
vm.prank(pauser);
615+
pauseRegistry.setPauseLevel(0);
616+
617+
uint256 overlap = pauseRegistry.computePauseOverlap(
618+
startTs,
619+
block.timestamp
620+
);
621+
assertEq(overlap, 90);
622+
}
623+
624+
function test_ComputePauseOverlapBlocks_AccountsForHardPauseWindow()
625+
public
626+
{
627+
_grantPauserRole();
628+
uint256 startBlock = block.number;
629+
630+
vm.prank(pauser);
631+
pauseRegistry.setPauseLevel(2);
632+
vm.roll(block.number + 25);
633+
634+
vm.prank(pauser);
635+
pauseRegistry.setPauseLevel(0);
636+
637+
uint256 overlap = pauseRegistry.computePauseOverlapBlocks(
638+
startBlock,
639+
block.number
640+
);
641+
assertEq(overlap, 25);
642+
}
643+
644+
function test_ResignDelay_ExcludesHardPausedBlocks() public {
645+
_grantPauserRole();
646+
647+
vm.prank(signers[2], signers[2]);
648+
flyoverDiscovery.register{value: 1 ether}(
649+
"PauseDelay LP",
650+
"http://localhost/api",
651+
true,
652+
Flyover.ProviderType.PegIn
653+
);
654+
655+
vm.prank(signers[2]);
656+
collateralManagement.resign();
657+
uint256 resignationBlock = collateralManagement.getResignationBlock(
658+
signers[2]
659+
);
660+
661+
vm.roll(resignationBlock + 200);
662+
663+
vm.prank(pauser);
664+
pauseRegistry.setPauseLevel(2);
665+
vm.roll(block.number + 400);
666+
vm.prank(pauser);
667+
pauseRegistry.setPauseLevel(0);
668+
669+
// Effective elapsed = 200 (400 hard-pause blocks should be excluded), so delay is not met yet.
670+
vm.expectRevert();
671+
vm.prank(signers[2]);
672+
collateralManagement.withdrawCollateral();
673+
674+
vm.roll(block.number + 300);
675+
vm.prank(signers[2]);
676+
collateralManagement.withdrawCollateral();
677+
}
481678
}

test/tasks/PauseSystem.t.sol

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -309,14 +309,17 @@ contract MockPauseRegistry is IPauseRegistry {
309309
bool private _paused;
310310
string private _reason;
311311
uint64 private _since;
312+
uint8 private _level;
312313

313314
function pause(string calldata reason) external override {
315+
_level = 1;
314316
_paused = true;
315317
_reason = reason;
316318
_since = uint64(block.timestamp);
317319
}
318320

319321
function unpause() external override {
322+
_level = 0;
320323
_paused = false;
321324
_reason = "";
322325
_since = 0;
@@ -334,6 +337,44 @@ contract MockPauseRegistry is IPauseRegistry {
334337
{
335338
return (_paused, _reason, _since);
336339
}
340+
341+
function pauseLevel() external view override returns (uint8) {
342+
return _level;
343+
}
344+
345+
function setPauseLevel(uint8 level) external override {
346+
if (level > 2) revert();
347+
_level = level;
348+
_paused = (level != 0);
349+
if (level == 0) {
350+
_reason = "";
351+
_since = 0;
352+
}
353+
}
354+
355+
function hardPausesCount() external pure override returns (uint256) {
356+
return 0;
357+
}
358+
359+
function hardPauses(
360+
uint256
361+
) external pure override returns (uint64, uint64, uint64, uint64) {
362+
return (0, 0, 0, 0);
363+
}
364+
365+
function computePauseOverlap(
366+
uint256,
367+
uint256
368+
) external pure override returns (uint256) {
369+
return 0;
370+
}
371+
372+
function computePauseOverlapBlocks(
373+
uint256,
374+
uint256
375+
) external pure override returns (uint256) {
376+
return 0;
377+
}
337378
}
338379

339380
/**

0 commit comments

Comments
 (0)