Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions contracts/evmx/watcher/Configurations.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ pragma solidity ^0.8.21;
import "../interfaces/IConfigurations.sol";
import "../../utils/common/Errors.sol";
import "../helpers/AddressResolverUtil.sol";
import "solady/auth/Ownable.sol";

import "../../utils/AccessControl.sol";
import "../../utils/common/Converters.sol";
import "../../utils/common/Structs.sol";
import "solady/utils/ECDSA.sol";
Expand Down Expand Up @@ -46,7 +45,7 @@ abstract contract ConfigurationsStorage is IWatcher {
/// @title Configurations
/// @notice Configuration contract for the Watcher Precompile system
/// @dev Handles the mapping between networks, plugs, and app gateways for payload execution
abstract contract Configurations is ConfigurationsStorage, Ownable, AddressResolverUtil {
abstract contract Configurations is ConfigurationsStorage, AccessControl, AddressResolverUtil {
/// @notice Emitted when a new plug is configured for an app gateway
/// @param appGatewayId The id of the app gateway
/// @param chainSlug The identifier of the destination network
Expand Down
22 changes: 19 additions & 3 deletions contracts/evmx/watcher/Watcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ import {IFeesManager} from "../interfaces/IFeesManager.sol";
import {IPromise} from "../interfaces/IPromise.sol";
import {IERC20} from "../interfaces/IERC20.sol";
import "../../utils/common/IdUtils.sol";
import "../../utils/Pausable.sol";
import {PAUSER_ROLE, UNPAUSER_ROLE} from "../../utils/common/AccessRoles.sol";
import "solady/utils/LibCall.sol";

/// @title Watcher
/// @notice Minimal request → payloads container with no batch/auction logic.
/// @dev Lives alongside existing Watcher without modifying current code.
contract Watcher is Initializable, Configurations {
contract Watcher is Initializable, Configurations, Pausable {
using LibCall for address;

uint256 public nextPayloadCount;
Expand Down Expand Up @@ -70,7 +72,7 @@ contract Watcher is Initializable, Configurations {

/// @notice Submit a request containing a single payload. No batches/auctions.
/// @dev Deploys promise via asyncDeployer and stores payload directly.
function executePayload() external returns (address asyncPromise) {
function executePayload() external whenNotPaused returns (address asyncPromise) {
if (latestAppGateway != msg.sender) revert AppGatewayMismatch();
if (
!feesManager__().isCreditSpendable(
Expand Down Expand Up @@ -113,7 +115,7 @@ contract Watcher is Initializable, Configurations {
emit PayloadSubmitted(_payloads[currentPayloadId]);
}

function resolvePayload(WatcherMultiCallParams memory params_) external {
function resolvePayload(WatcherMultiCallParams memory params_) external whenNotPaused {
_validateSignature(address(this), params_.data, params_.nonce, params_.signature);
(PromiseReturnData memory resolvedPromise, uint256 feesUsed) = abi.decode(params_.data, (PromiseReturnData, uint256));

Expand Down Expand Up @@ -317,4 +319,18 @@ contract Watcher is Initializable, Configurations {
) external view returns (uint256) {
return precompiles[callType_].getPrecompileFees(precompileData_);
}

////////////////////////////////////////////////////////
////////////////////// Pausable ////////////////////////
////////////////////////////////////////////////////////

/// @notice Pause the contract (only pauser role)
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}

/// @notice Unpause the contract (only unpauser role)
function unpause() external onlyRole(UNPAUSER_ROLE) {
_unpause();
}
}
22 changes: 19 additions & 3 deletions contracts/protocol/Socket.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import "./SocketUtils.sol";

import {WRITE} from "../utils/common/Constants.sol";
import {getVerificationInfo} from "../utils/common/IdUtils.sol";
import "../utils/Pausable.sol";
import {PAUSER_ROLE, UNPAUSER_ROLE} from "../utils/common/AccessRoles.sol";

/**
* @title Socket
* @dev Socket is an abstract contract that inherits from SocketUtils and SocketConfig and
* provides functionality for payload execution, verification, and management of payload execution status
*/
contract Socket is SocketUtils {
contract Socket is SocketUtils, Pausable {
using LibCall for address;

// mapping of payload id to execution status
Expand Down Expand Up @@ -59,7 +61,7 @@ contract Socket is SocketUtils {
function execute(
ExecuteParams calldata executeParams_,
TransmissionParams calldata transmissionParams_
) external payable returns (bool, bytes memory) {
) external payable whenNotPaused returns (bool, bytes memory) {
// check if the deadline has passed
if (executeParams_.deadline < block.timestamp) revert DeadlinePassed();

Expand Down Expand Up @@ -216,7 +218,7 @@ contract Socket is SocketUtils {
address plug_,
uint256 value_,
bytes calldata data_
) internal returns (bytes32 payloadId) {
) internal whenNotPaused returns (bytes32 payloadId) {
(uint64 switchboardId, address switchboardAddress) = _verifyPlugSwitchboard(plug_);
bytes memory plugOverrides = IPlug(plug_).overrides();

Expand Down Expand Up @@ -269,4 +271,18 @@ contract Socket is SocketUtils {
receive() external payable {
revert("Socket does not accept ETH");
}

////////////////////////////////////////////////////////
////////////////////// Pausable ////////////////////////
////////////////////////////////////////////////////////

/// @notice Pause the contract (only pauser role)
function pause() external onlyRole(PAUSER_ROLE) {
_pause();
}

/// @notice Unpause the contract (only unpauser role)
function unpause() external onlyRole(UNPAUSER_ROLE) {
_unpause();
}
}
67 changes: 67 additions & 0 deletions contracts/utils/Pausable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.21;

/**
* @title Pausable
* @dev Base contract that provides pausable functionality
* @notice This contract can be inherited to add pause/unpause capabilities
* @dev Uses a dedicated storage slot to avoid storage collisions
*/
abstract contract Pausable {
/// @notice Storage slot for pausable state
bytes32 private constant STORAGE_SLOT = keccak256("socket.storage.Pausable");

/// @notice Thrown when the contract is paused
error ContractPaused();

/// @notice Event emitted when contract is paused
event Paused();

/// @notice Event emitted when contract is unpaused
event Unpaused();

/// @notice Returns the paused state of the contract
function paused() public view returns (bool) {
bytes32 slot = STORAGE_SLOT;
bool result;
assembly {
result := sload(slot)
}
return result;
}

/// @notice Modifier to check if contract is not paused
modifier whenNotPaused() {
if (paused()) revert ContractPaused();
_;
}

/// @notice Internal function to pause the contract
function _pause() internal {
bytes32 slot = STORAGE_SLOT;
bool current;
assembly {
current := sload(slot)
}
if (current) return;
assembly {
sstore(slot, 1)
}
emit Paused();
}

/// @notice Internal function to unpause the contract
function _unpause() internal {
bytes32 slot = STORAGE_SLOT;
bool current;
assembly {
current := sload(slot)
}
if (!current) return;
assembly {
sstore(slot, 0)
}
emit Unpaused();
}
}

4 changes: 4 additions & 0 deletions contracts/utils/common/AccessRoles.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,7 @@ bytes32 constant SWITCHBOARD_DISABLER_ROLE = keccak256("SWITCHBOARD_DISABLER_ROL
bytes32 constant FEE_MANAGER_ROLE = keccak256("FEE_MANAGER_ROLE");
// used by oracle to update minimum message value fees
bytes32 constant FEE_UPDATER_ROLE = keccak256("FEE_UPDATER_ROLE");

bytes32 constant PAUSER_ROLE = keccak256("PAUSER_ROLE");

bytes32 constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE");
Loading