Skip to content

Commit 29ad611

Browse files
authored
Merge pull request #444 from rsksmart/feature/FLY-2232
Feature/fly 2232
2 parents 32fbadf + 0fe3c05 commit 29ad611

14 files changed

Lines changed: 885 additions & 129 deletions

script/tasks/PauseSystem.s.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ contract PauseSystem is Script, AddressResolver {
138138
_verifyAllContractsUseRegistry(registry);
139139

140140
vm.startBroadcast();
141-
registry.pause(reason);
141+
registry.setPauseLevel(IPauseRegistry.PauseLevel.Soft, reason);
142142
vm.stopBroadcast();
143143

144144
console.log(
@@ -157,7 +157,7 @@ contract PauseSystem is Script, AddressResolver {
157157
_verifyAllContractsUseRegistry(registry);
158158

159159
vm.startBroadcast();
160-
registry.unpause();
160+
registry.setPauseLevel(IPauseRegistry.PauseLevel.None, "");
161161
vm.stopBroadcast();
162162

163163
console.log(

src/CollateralManagement.sol

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -78,22 +78,28 @@ contract CollateralManagementContract is
7878
}
7979

8080
/// @inheritdoc ICollateralManagement
81-
function addPegInCollateralTo(address addr) external onlyRole(COLLATERAL_ADDER) whenNotPaused payable override {
81+
function addPegInCollateralTo(address addr) external onlyRole(COLLATERAL_ADDER) whenNotSoftPaused payable override {
8282
_addPegInCollateralTo(addr, msg.value);
8383
}
8484

8585
/// @inheritdoc ICollateralManagement
86-
function addPegInCollateral() external whenNotPaused onlyRegisteredForPegIn(msg.sender) payable override {
86+
function addPegInCollateral() external whenNotSoftPaused onlyRegisteredForPegIn(msg.sender) payable override {
8787
_addPegInCollateralTo(msg.sender, msg.value);
8888
}
8989

9090
/// @inheritdoc ICollateralManagement
91-
function addPegOutCollateralTo(address addr) external whenNotPaused onlyRole(COLLATERAL_ADDER) payable override {
91+
function addPegOutCollateralTo(address addr)
92+
external
93+
whenNotSoftPaused
94+
onlyRole(COLLATERAL_ADDER)
95+
payable
96+
override
97+
{
9298
_addPegOutCollateralTo(addr, msg.value);
9399
}
94100

95101
/// @inheritdoc ICollateralManagement
96-
function addPegOutCollateral() external whenNotPaused onlyRegisteredForPegOut(msg.sender) payable override {
102+
function addPegOutCollateral() external whenNotSoftPaused onlyRegisteredForPegOut(msg.sender) payable override {
97103
_addPegOutCollateralTo(msg.sender, msg.value);
98104
}
99105

@@ -153,6 +159,8 @@ contract CollateralManagementContract is
153159
}
154160

155161
/// @inheritdoc ICollateralManagement
162+
/// @dev Intentionally not paused: slashing must remain available so PegIn/PegOut can enforce penalties
163+
/// when they call this during registerPegIn/refundPegOut; a separate pause would cause reverts.
156164
function slashPegInCollateral(
157165
address punisher,
158166
Quotes.PegInQuote calldata quote,
@@ -177,6 +185,8 @@ contract CollateralManagementContract is
177185
}
178186

179187
/// @inheritdoc ICollateralManagement
188+
/// @dev Intentionally not paused: slashing must remain available so PegIn/PegOut can enforce penalties
189+
/// when they call this during registerPegIn/refundPegOut; a separate pause would cause reverts.
180190
function slashPegOutCollateral(
181191
address punisher,
182192
Quotes.PegOutQuote calldata quote,
@@ -201,27 +211,27 @@ contract CollateralManagementContract is
201211
}
202212

203213
/// @inheritdoc ICollateralManagement
204-
function withdrawCollateral() external nonReentrant override {
214+
function withdrawCollateral() external nonReentrant whenNotHardPaused override {
205215
_withdrawCollateralTo(payable(msg.sender));
206216
}
207217

208218
/// @inheritdoc ICollateralManagement
209-
function withdrawCollateral(address payable to) external nonReentrant override {
219+
function withdrawCollateral(address payable to) external nonReentrant whenNotHardPaused override {
210220
_withdrawCollateralTo(to);
211221
}
212222

213223
/// @inheritdoc ICollateralManagement
214-
function withdrawRewards() external nonReentrant override {
224+
function withdrawRewards() external nonReentrant whenNotHardPaused override {
215225
_withdrawRewardsTo(payable(msg.sender));
216226
}
217227

218228
/// @inheritdoc ICollateralManagement
219-
function withdrawRewards(address payable to) external nonReentrant override {
229+
function withdrawRewards(address payable to) external nonReentrant whenNotHardPaused override {
220230
_withdrawRewardsTo(to);
221231
}
222232

223233
/// @inheritdoc ICollateralManagement
224-
function resign() external override {
234+
function resign() external whenNotHardPaused override {
225235
address providerAddress = msg.sender;
226236
if (_resignationBlockNum[providerAddress] != 0) revert AlreadyResigned(providerAddress);
227237
if (_pegInCollateral[providerAddress] < 1 && _pegOutCollateral[providerAddress] < 1) {
@@ -308,7 +318,8 @@ contract CollateralManagementContract is
308318
address providerAddress = msg.sender;
309319
uint256 resignationBlock = _resignationBlockNum[providerAddress];
310320
if (resignationBlock < 1) revert NotResigned(providerAddress);
311-
if (block.number - resignationBlock < _resignDelayInBlocks) {
321+
uint256 pauseBlocks = pauseRegistry().computePauseOverlapBlocks(resignationBlock, block.number);
322+
if (block.number - resignationBlock - pauseBlocks < _resignDelayInBlocks) {
312323
revert ResignationDelayNotMet(providerAddress, resignationBlock, _resignDelayInBlocks);
313324
}
314325

src/EmergencyPause/EmergencyPause.sol

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import {IPauseRegistry} from "../interfaces/IPauseRegistry.sol";
99
import {Flyover} from "../libraries/Flyover.sol";
1010

1111
/// @notice Base contract for Flyover contracts that delegate pause state to a central PauseRegistry.
12-
/// pauseStatus() and whenNotPaused read from the registry. Pause/unpause are done only on the registry.
12+
/// pauseStatus() and pause-level checks read from the registry. Pause/unpause are done only on the registry.
13+
/// Uses two modifiers:
14+
/// - whenNotSoftPaused(): blocks at level >= 1 (soft and hard pause)
15+
/// - whenNotHardPaused(): blocks at level >= 2 (hard pause only)
1316
/// Uses namespaced storage; no AccessControl (children that need roles inherit it separately).
1417
abstract contract EmergencyPause is Initializable, IPausable {
1518

@@ -23,9 +26,23 @@ abstract contract EmergencyPause is Initializable, IPausable {
2326
bytes32 private constant _EMERGENCY_PAUSE_STORAGE =
2427
0x9231f352ae2e78fc5cd04a185b8fc917dd5cf9947923b7000e25955769a61f00;
2528

26-
/// @notice Modifier that reverts if the system is paused (reads from PauseRegistry)
27-
modifier whenNotPaused() {
28-
if (_getEmergencyPauseStorage().pauseRegistry.paused()) {
29+
/// @notice Reverts at pause level >= 1 (soft pause: no new business)
30+
modifier whenNotSoftPaused() {
31+
if (
32+
_getEmergencyPauseStorage().pauseRegistry.pauseLevel() >=
33+
IPauseRegistry.PauseLevel.Soft
34+
) {
35+
revert Flyover.EnforcedPause();
36+
}
37+
_;
38+
}
39+
40+
/// @notice Reverts at pause level >= 2 (hard pause: full freeze)
41+
modifier whenNotHardPaused() {
42+
if (
43+
_getEmergencyPauseStorage().pauseRegistry.pauseLevel() >=
44+
IPauseRegistry.PauseLevel.Hard
45+
) {
2946
revert Flyover.EnforcedPause();
3047
}
3148
_;

src/FlyoverDiscovery.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ contract FlyoverDiscovery is
6666
string calldata apiBaseUrl,
6767
bool status,
6868
Flyover.ProviderType providerType
69-
) external payable whenNotPaused returns (uint) {
69+
) external payable whenNotSoftPaused returns (uint) {
7070

7171
_validateRegistration(name, apiBaseUrl, providerType, msg.sender, msg.value);
7272

@@ -103,7 +103,7 @@ contract FlyoverDiscovery is
103103
}
104104

105105
/// @inheritdoc IFlyoverDiscovery
106-
function updateProvider(string calldata name, string calldata apiBaseUrl) external whenNotPaused {
106+
function updateProvider(string calldata name, string calldata apiBaseUrl) external whenNotHardPaused {
107107
if (bytes(name).length < 1 || bytes(apiBaseUrl).length < 1) revert InvalidProviderData(name, apiBaseUrl);
108108
address providerAddress = msg.sender;
109109
uint providerId = _providerIdByAddress[providerAddress];

src/PauseRegistry.sol

Lines changed: 160 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini
1010
import {IPauseRegistry} from "./interfaces/IPauseRegistry.sol";
1111

1212
/// @title PauseRegistry
13-
/// @notice Centralized registry for pause state; all Flyover contracts read from here
14-
/// @dev Only accounts with PAUSER_ROLE can pause/unpause. Uses namespaced storage.
13+
/// @notice Centralized registry for pause state; all Flyover contracts read from here.
14+
/// @dev Level 0 = normal, 1 = soft (no new business), 2 = hard (full freeze). Uses namespaced storage.
1515
contract PauseRegistry is
1616
Initializable,
1717
AccessControlDefaultAdminRulesUpgradeable,
@@ -20,8 +20,17 @@ contract PauseRegistry is
2020
/// @custom:storage-location erc7201:rsk.flyover.PauseRegistry
2121
struct PauseRegistryStorage {
2222
bool paused;
23+
IPauseRegistry.PauseLevel pauseLevel;
2324
uint64 pauseTimestamp;
2425
string pauseReason;
26+
HardPause[] hardPauses;
27+
}
28+
29+
struct HardPause {
30+
uint64 startTimestamp;
31+
uint64 endTimestamp; // 0 while ongoing
32+
uint64 startBlock;
33+
uint64 endBlock; // 0 while ongoing
2534
}
2635

2736
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
@@ -46,28 +55,35 @@ contract PauseRegistry is
4655
}
4756

4857
/// @inheritdoc IPauseRegistry
49-
function pause(string calldata reason) external onlyRole(PAUSER_ROLE) {
50-
PauseRegistryStorage storage $ = _getPauseRegistryStorage();
51-
if ($.paused) return;
52-
$.paused = true;
53-
$.pauseReason = reason;
54-
$.pauseTimestamp = uint64(block.timestamp);
55-
emit EmergencyPaused(msg.sender, reason);
58+
function setPauseLevel(
59+
IPauseRegistry.PauseLevel level,
60+
string calldata reason
61+
) external onlyRole(PAUSER_ROLE) {
62+
_setPauseLevelWithReason(level, reason);
5663
}
5764

58-
/// @inheritdoc IPauseRegistry
59-
function unpause() external onlyRole(PAUSER_ROLE) {
65+
function _setPauseLevelWithReason(
66+
IPauseRegistry.PauseLevel level,
67+
string memory reason
68+
) internal {
6069
PauseRegistryStorage storage $ = _getPauseRegistryStorage();
61-
if (!$.paused) return;
62-
$.paused = false;
63-
$.pauseReason = "";
64-
$.pauseTimestamp = 0;
65-
emit EmergencyUnpaused(msg.sender);
70+
bool wasPaused = $.pauseLevel != IPauseRegistry.PauseLevel.None;
71+
if (level != IPauseRegistry.PauseLevel.None) {
72+
$.pauseReason = reason;
73+
}
74+
_setPauseLevel(level);
75+
bool isPaused = level != IPauseRegistry.PauseLevel.None;
76+
if (!wasPaused && isPaused) {
77+
emit EmergencyPaused(msg.sender, $.pauseReason);
78+
} else if (wasPaused && !isPaused) {
79+
emit EmergencyUnpaused(msg.sender);
80+
}
6681
}
6782

6883
/// @inheritdoc IPauseRegistry
6984
function paused() external view returns (bool) {
70-
return _getPauseRegistryStorage().paused;
85+
PauseRegistryStorage storage $ = _getPauseRegistryStorage();
86+
return $.pauseLevel != IPauseRegistry.PauseLevel.None;
7187
}
7288

7389
/// @inheritdoc IPauseRegistry
@@ -77,7 +93,132 @@ contract PauseRegistry is
7793
returns (bool isPaused, string memory reason, uint64 since)
7894
{
7995
PauseRegistryStorage storage $ = _getPauseRegistryStorage();
80-
return ($.paused, $.pauseReason, $.pauseTimestamp);
96+
isPaused = $.pauseLevel != IPauseRegistry.PauseLevel.None;
97+
reason = $.pauseReason;
98+
since = $.pauseTimestamp;
99+
}
100+
101+
/// @inheritdoc IPauseRegistry
102+
function pauseLevel() external view returns (IPauseRegistry.PauseLevel) {
103+
return _getPauseRegistryStorage().pauseLevel;
104+
}
105+
106+
/// @inheritdoc IPauseRegistry
107+
function hardPausesCount() external view returns (uint256) {
108+
return _getPauseRegistryStorage().hardPauses.length;
109+
}
110+
111+
/// @inheritdoc IPauseRegistry
112+
function hardPauses(uint256 index)
113+
external
114+
view
115+
returns (
116+
uint64 startTimestamp,
117+
uint64 endTimestamp,
118+
uint64 startBlock,
119+
uint64 endBlock
120+
)
121+
{
122+
HardPause storage p = _getPauseRegistryStorage().hardPauses[index];
123+
return (p.startTimestamp, p.endTimestamp, p.startBlock, p.endBlock);
124+
}
125+
126+
/// @inheritdoc IPauseRegistry
127+
function computePauseOverlap(uint256 startTimestamp, uint256 endTimestamp)
128+
external
129+
view
130+
returns (uint256 totalPauseTime)
131+
{
132+
HardPause[] storage pauses = _getPauseRegistryStorage().hardPauses;
133+
uint256 n = pauses.length;
134+
for (uint256 i = n; i > 0;) {
135+
unchecked {
136+
--i;
137+
}
138+
HardPause storage p = pauses[i];
139+
uint64 pEnd = p.endTimestamp;
140+
if (pEnd != 0 && !(pEnd > startTimestamp)) break;
141+
uint256 effectiveStart = startTimestamp;
142+
if (p.startTimestamp > effectiveStart) effectiveStart = p.startTimestamp;
143+
uint256 effectiveEnd = endTimestamp;
144+
uint256 pEndOrNow = pEnd == 0 ? block.timestamp : pEnd;
145+
if (pEndOrNow < effectiveEnd) effectiveEnd = pEndOrNow;
146+
if (effectiveEnd > effectiveStart) {
147+
totalPauseTime += effectiveEnd - effectiveStart;
148+
}
149+
}
150+
}
151+
152+
/// @inheritdoc IPauseRegistry
153+
function computePauseOverlapBlocks(uint256 startBlock, uint256 endBlock)
154+
external
155+
view
156+
returns (uint256 totalPauseBlocks)
157+
{
158+
HardPause[] storage pauses = _getPauseRegistryStorage().hardPauses;
159+
uint256 n = pauses.length;
160+
for (uint256 i = n; i > 0;) {
161+
unchecked {
162+
--i;
163+
}
164+
HardPause storage p = pauses[i];
165+
uint64 pEndBlock = p.endBlock;
166+
if (pEndBlock != 0 && !(pEndBlock > startBlock)) break;
167+
uint256 effectiveStart = startBlock;
168+
if (p.startBlock > effectiveStart) effectiveStart = p.startBlock;
169+
uint256 effectiveEnd = endBlock;
170+
uint256 pEndOrNow = pEndBlock == 0 ? block.number : pEndBlock;
171+
if (pEndOrNow < effectiveEnd) effectiveEnd = pEndOrNow;
172+
if (effectiveEnd > effectiveStart) {
173+
totalPauseBlocks += effectiveEnd - effectiveStart;
174+
}
175+
}
176+
}
177+
178+
function _setPauseLevel(IPauseRegistry.PauseLevel level) internal {
179+
PauseRegistryStorage storage $ = _getPauseRegistryStorage();
180+
IPauseRegistry.PauseLevel prev = IPauseRegistry.PauseLevel($.pauseLevel);
181+
$.pauseLevel = level;
182+
$.paused = (level != IPauseRegistry.PauseLevel.None);
183+
if (
184+
prev == IPauseRegistry.PauseLevel.None &&
185+
level != IPauseRegistry.PauseLevel.None
186+
) {
187+
$.pauseTimestamp = uint64(block.timestamp);
188+
} else if (
189+
prev != IPauseRegistry.PauseLevel.None &&
190+
level == IPauseRegistry.PauseLevel.None
191+
) {
192+
$.pauseTimestamp = 0;
193+
$.pauseReason = "";
194+
}
195+
196+
if (
197+
prev == IPauseRegistry.PauseLevel.Hard &&
198+
level != IPauseRegistry.PauseLevel.Hard
199+
) {
200+
HardPause[] storage pauses = $.hardPauses;
201+
uint256 len = pauses.length;
202+
if (len > 0) {
203+
HardPause storage last = pauses[len - 1];
204+
if (last.endTimestamp == 0) {
205+
last.endTimestamp = uint64(block.timestamp);
206+
last.endBlock = uint64(block.number);
207+
}
208+
}
209+
} else if (
210+
prev != IPauseRegistry.PauseLevel.Hard &&
211+
level == IPauseRegistry.PauseLevel.Hard
212+
) {
213+
$.hardPauses.push(
214+
HardPause({
215+
startTimestamp: uint64(block.timestamp),
216+
endTimestamp: 0,
217+
startBlock: uint64(block.number),
218+
endBlock: 0
219+
})
220+
);
221+
}
81222
}
82223

83224
function _getPauseRegistryStorage()
@@ -89,4 +230,5 @@ contract PauseRegistry is
89230
$.slot := _PAUSE_REGISTRY_STORAGE
90231
}
91232
}
233+
92234
}

0 commit comments

Comments
 (0)