Skip to content

Conversation

@tHeMaskedMan981
Copy link
Collaborator

@tHeMaskedMan981 tHeMaskedMan981 commented Nov 6, 2025

Summary by CodeRabbit

Release Notes

  • New Features
    • Introduced pause/unpause controls across core protocol operations
    • Administrators can now temporarily halt transaction execution and payload processing
    • Enables operational flexibility for maintenance and emergency response scenarios

@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

Walkthrough

This PR introduces pausable functionality across key entry points by implementing a new Pausable utility contract. The Watcher and Socket contracts now inherit from Pausable, with critical functions (executePayload, resolvePayload, execute, _sendPayload) guarded by whenNotPaused modifiers. Owner-controlled pause/unpause functions enable temporary halt of contract operations.

Changes

Cohort / File(s) Change Summary
Pausable Utility Implementation
contracts/utils/Pausable.sol
New abstract contract implementing pause/unpause pattern with paused state variable, ContractPaused error, Paused/Unpaused events, whenNotPaused modifier, and internal _pause()/_unpause() functions
Watcher Contract Updates
contracts/evmx/watcher/Watcher.sol
Added Pausable inheritance, applied whenNotPaused guards to executePayload and resolvePayload, added owner-only pause/unpause functions
Socket Contract Updates
contracts/protocol/Socket.sol
Added Pausable inheritance, applied whenNotPaused guards to execute (external) and _sendPayload (internal), added owner-only pause/unpause functions

Sequence Diagram

sequenceDiagram
    participant User
    participant Contract as Socket/Watcher
    participant Pausable
    
    User->>Contract: Call execute/executePayload
    Contract->>Pausable: Check whenNotPaused modifier
    alt paused = true
        Pausable-->>Contract: Revert with ContractPaused()
        Contract-->>User: Transaction reverted
    else paused = false
        Pausable-->>Contract: Proceed
        Contract->>Contract: Execute function logic
        Contract-->>User: Return result
    end
    
    Note over Contract: Owner calls pause()
    Contract->>Pausable: _pause()
    Pausable->>Pausable: paused = true
    Pausable->>Pausable: emit Paused()
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

  • Key areas for attention:
    • Verify internal _sendPayload modifier placement doesn't inadvertently gate allowed internal calls during emergency operations
    • Confirm pause state persists across transactions and doesn't create race conditions between pause calls and pending transactions
    • Check that Socket.execute and _sendPayload guards don't break existing payload routing logic when contract is unpaused

Poem

🔒 The guardian awakes with a pause,
No payload flows, no requests enthrall,
Owner's command, pause without cause,
When duty resumes, the contracts stand tall.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding pausable functionality to Watcher and Socket contracts.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/pausable

Comment @coderabbitai help to get the list of available commands and usage tips.

@arthcp
Copy link
Contributor

arthcp commented Nov 6, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Nov 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 933900b and 5e81fe7.

📒 Files selected for processing (3)
  • contracts/evmx/watcher/Watcher.sol (4 hunks)
  • contracts/protocol/Socket.sol (4 hunks)
  • contracts/utils/Pausable.sol (1 hunks)

Comment on lines +14 to +38
bool public paused;

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

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

/// @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 {
paused = true;
emit Paused();
}

/// @notice Internal function to unpause the contract
function _unpause() internal {
paused = false;
emit Unpaused();
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Fix storage layout regression before shipping.
bool public paused lives in the new base contract. When Watcher (upgradeable via Initializable) and Socket inherit this, the storage slot for paused is inserted ahead of all existing state. On upgrade, every variable defined in the derived contracts shifts down one slot: e.g. nextPayloadCount in Watcher now reads from an empty slot (zero) while the old value becomes the new paused flag (likely true), corrupting state and bricking the system. This should store the pause flag in an unstructured storage slot (or another proven-safe mechanism) so the existing layout stays untouched.

Apply this diff to keep the pause flag in a dedicated storage slot:

+library PausableStorage {
+    bytes32 internal constant STORAGE_SLOT = keccak256("socket.storage.Pausable");
+
+    struct Layout {
+        bool paused;
+    }
+
+    function layout() internal pure returns (Layout storage l) {
+        bytes32 slot = STORAGE_SLOT;
+        assembly {
+            l.slot := slot
+        }
+    }
+}
+
 abstract contract Pausable {
-    /// @notice Thrown when the contract is paused
     error ContractPaused();
 
-    /// @notice Paused state
-    bool public paused;
+    function paused() public view returns (bool) {
+        return PausableStorage.layout().paused;
+    }
 
     /// @notice Event emitted when contract is paused
     event Paused();
 
     /// @notice Event emitted when contract is unpaused
     event Unpaused();
 
     /// @notice Modifier to check if contract is not paused
     modifier whenNotPaused() {
-        if (paused) revert ContractPaused();
+        if (paused()) revert ContractPaused();
         _;
     }
 
     /// @notice Internal function to pause the contract
     function _pause() internal {
-        paused = true;
+        PausableStorage.Layout storage store = PausableStorage.layout();
+        if (store.paused) return;
+        store.paused = true;
         emit Paused();
     }
 
     /// @notice Internal function to unpause the contract
     function _unpause() internal {
-        paused = false;
+        PausableStorage.Layout storage store = PausableStorage.layout();
+        if (!store.paused) return;
+        store.paused = false;
         emit Unpaused();
     }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
bool public paused;
/// @notice Event emitted when contract is paused
event Paused();
/// @notice Event emitted when contract is unpaused
event Unpaused();
/// @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 {
paused = true;
emit Paused();
}
/// @notice Internal function to unpause the contract
function _unpause() internal {
paused = false;
emit Unpaused();
}
library PausableStorage {
bytes32 internal constant STORAGE_SLOT = keccak256("socket.storage.Pausable");
struct Layout {
bool paused;
}
function layout() internal pure returns (Layout storage l) {
bytes32 slot = STORAGE_SLOT;
assembly {
l.slot := slot
}
}
}
abstract contract Pausable {
error ContractPaused();
function paused() public view returns (bool) {
return PausableStorage.layout().paused;
}
/// @notice Event emitted when contract is paused
event Paused();
/// @notice Event emitted when contract is unpaused
event Unpaused();
/// @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 {
PausableStorage.Layout storage store = PausableStorage.layout();
if (store.paused) return;
store.paused = true;
emit Paused();
}
/// @notice Internal function to unpause the contract
function _unpause() internal {
PausableStorage.Layout storage store = PausableStorage.layout();
if (!store.paused) return;
store.paused = false;
emit Unpaused();
}
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants