-
Notifications
You must be signed in to change notification settings - Fork 195
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #914 from lidofinance/pausable-vault-hub
feat: make VaultHub pausable
- Loading branch information
Showing
8 changed files
with
399 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
|
||
// See contracts/COMPILERS.md | ||
pragma solidity 0.8.25; | ||
|
||
import {PausableUntil} from "contracts/common/utils/PausableUntil.sol"; | ||
import {AccessControlEnumerableUpgradeable} from "contracts/openzeppelin/5.0.2/upgradeable/access/extensions/AccessControlEnumerableUpgradeable.sol"; | ||
|
||
/** | ||
* @title PausableUntilWithRoles | ||
* @notice a `PausableUntil` implementation using OpenZeppelin's `AccessControlEnumerableUpgradeable` | ||
* @dev the inheriting contract must use `whenNotPaused` modifier from `PausableUntil` to block some functions on pause | ||
*/ | ||
abstract contract PausableUntilWithRoles is PausableUntil, AccessControlEnumerableUpgradeable { | ||
/// @notice role that allows to pause the contract | ||
bytes32 public constant PAUSE_ROLE = keccak256("PausableUntilWithRoles.PauseRole"); | ||
/// @notice role that allows to resume the contract | ||
bytes32 public constant RESUME_ROLE = keccak256("PausableUntilWithRoles.ResumeRole"); | ||
|
||
/** | ||
* @notice Resume the contract | ||
* @dev Reverts if contracts is not paused | ||
* @dev Reverts if sender has no `RESUME_ROLE` | ||
*/ | ||
function resume() external onlyRole(RESUME_ROLE) { | ||
_resume(); | ||
} | ||
|
||
/** | ||
* @notice Pause the contract for a specified period | ||
* @param _duration pause duration in seconds (use `PAUSE_INFINITELY` for unlimited) | ||
* @dev Reverts if contract is already paused | ||
* @dev Reverts if sender has no `PAUSE_ROLE` | ||
* @dev Reverts if zero duration is passed | ||
*/ | ||
function pauseFor(uint256 _duration) external onlyRole(PAUSE_ROLE) { | ||
_pauseFor(_duration); | ||
} | ||
|
||
/** | ||
* @notice Pause the contract until a specified timestamp | ||
* @param _pauseUntilInclusive the last second to pause until inclusive | ||
* @dev Reverts if the timestamp is in the past | ||
* @dev Reverts if sender has no `PAUSE_ROLE` | ||
* @dev Reverts if contract is already paused | ||
*/ | ||
function pauseUntil(uint256 _pauseUntilInclusive) external onlyRole(PAUSE_ROLE) { | ||
_pauseUntil(_pauseUntilInclusive); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-FileCopyrightText: 2023 Lido <info@lido.fi>, Aragon | ||
// SPDX-License-Identifier: MIT | ||
|
||
// solhint-disable-next-line lido/fixed-compiler-version | ||
pragma solidity ^0.8.9; | ||
|
||
library UnstructuredStorage { | ||
function getStorageBool(bytes32 position) internal view returns (bool data) { | ||
assembly { data := sload(position) } | ||
} | ||
|
||
function getStorageAddress(bytes32 position) internal view returns (address data) { | ||
assembly { data := sload(position) } | ||
} | ||
|
||
function getStorageBytes32(bytes32 position) internal view returns (bytes32 data) { | ||
assembly { data := sload(position) } | ||
} | ||
|
||
function getStorageUint256(bytes32 position) internal view returns (uint256 data) { | ||
assembly { data := sload(position) } | ||
} | ||
|
||
function setStorageBool(bytes32 position, bool data) internal { | ||
assembly { sstore(position, data) } | ||
} | ||
|
||
function setStorageAddress(bytes32 position, address data) internal { | ||
assembly { sstore(position, data) } | ||
} | ||
|
||
function setStorageBytes32(bytes32 position, bytes32 data) internal { | ||
assembly { sstore(position, data) } | ||
} | ||
|
||
function setStorageUint256(bytes32 position, uint256 data) internal { | ||
assembly { sstore(position, data) } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// SPDX-FileCopyrightText: 2025 Lido <info@lido.fi> | ||
// SPDX-License-Identifier: GPL-3.0 | ||
// solhint-disable-next-line lido/fixed-compiler-version | ||
pragma solidity ^0.8.9; | ||
|
||
import {UnstructuredStorage} from "contracts/common/lib/UnstructuredStorage.sol"; | ||
|
||
/** | ||
* @title PausableUntil | ||
* @notice allows to pause the contract for a specific duration or indefinitely | ||
*/ | ||
abstract contract PausableUntil { | ||
using UnstructuredStorage for bytes32; | ||
|
||
/// Contract resume/pause control storage slot | ||
bytes32 internal constant RESUME_SINCE_TIMESTAMP_POSITION = keccak256("lido.PausableUntil.resumeSinceTimestamp"); | ||
/// Special value for the infinite pause | ||
uint256 public constant PAUSE_INFINITELY = type(uint256).max; | ||
|
||
/// @notice Emitted when paused by the `pauseFor` or `pauseUntil` call | ||
event Paused(uint256 duration); | ||
/// @notice Emitted when resumed by the `resume` call | ||
event Resumed(); | ||
|
||
error ZeroPauseDuration(); | ||
error PausedExpected(); | ||
error ResumedExpected(); | ||
error PauseUntilMustBeInFuture(); | ||
|
||
/// @notice Reverts if paused | ||
modifier whenResumed() { | ||
_checkResumed(); | ||
_; | ||
} | ||
|
||
/// @notice Returns whether the contract is paused | ||
function isPaused() public view returns (bool) { | ||
return block.timestamp < RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); | ||
} | ||
|
||
/// @notice Returns one of: | ||
/// - PAUSE_INFINITELY if paused infinitely returns | ||
/// - the timestamp when the contract get resumed if paused for specific duration | ||
/// - some timestamp in past if not paused | ||
function getResumeSinceTimestamp() external view returns (uint256) { | ||
return RESUME_SINCE_TIMESTAMP_POSITION.getStorageUint256(); | ||
} | ||
|
||
function _checkPaused() internal view { | ||
if (!isPaused()) { | ||
revert PausedExpected(); | ||
} | ||
} | ||
|
||
function _checkResumed() internal view { | ||
if (isPaused()) { | ||
revert ResumedExpected(); | ||
} | ||
} | ||
|
||
function _resume() internal { | ||
_checkPaused(); | ||
RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(block.timestamp); | ||
emit Resumed(); | ||
} | ||
|
||
function _pauseFor(uint256 _duration) internal { | ||
_checkResumed(); | ||
if (_duration == 0) revert ZeroPauseDuration(); | ||
|
||
uint256 resumeSince; | ||
if (_duration == PAUSE_INFINITELY) { | ||
resumeSince = PAUSE_INFINITELY; | ||
} else { | ||
resumeSince = block.timestamp + _duration; | ||
} | ||
_setPausedState(resumeSince); | ||
} | ||
|
||
function _pauseUntil(uint256 _pauseUntilInclusive) internal { | ||
_checkResumed(); | ||
if (_pauseUntilInclusive < block.timestamp) revert PauseUntilMustBeInFuture(); | ||
|
||
uint256 resumeSince; | ||
if (_pauseUntilInclusive != PAUSE_INFINITELY) { | ||
resumeSince = _pauseUntilInclusive + 1; | ||
} else { | ||
resumeSince = PAUSE_INFINITELY; | ||
} | ||
_setPausedState(resumeSince); | ||
} | ||
|
||
function _setPausedState(uint256 _resumeSince) internal { | ||
RESUME_SINCE_TIMESTAMP_POSITION.setStorageUint256(_resumeSince); | ||
if (_resumeSince == PAUSE_INFINITELY) { | ||
emit Paused(PAUSE_INFINITELY); | ||
} else { | ||
emit Paused(_resumeSince - block.timestamp); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.