Commit 2f17e6b6 authored by smartcontracts's avatar smartcontracts Committed by GitHub

feat: introduce DeputyPauseModule (#13186)

Introduces the DeputyPauseModule which allows an account to act as
the Foundation Safe for the sake of triggering the Superchain-wide
pause function. Adding this module to the Foundation Safe would
remove the need for any pre-signed pause transactions and generally
simplifies the incident response process.
parent 4bf21aec
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
import { ISemver } from "interfaces/universal/ISemver.sol";
import { IDeputyGuardianModule } from "interfaces/safe/IDeputyGuardianModule.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
interface IDeputyPauseModule is ISemver {
error DeputyPauseModule_InvalidDeputy();
error DeputyPauseModule_ExecutionFailed(string);
error DeputyPauseModule_SuperchainNotPaused();
error DeputyPauseModule_Unauthorized();
error DeputyPauseModule_NonceAlreadyUsed();
error DeputyPauseModule_NotFromSafe();
error ECDSAInvalidSignature();
error ECDSAInvalidSignatureLength(uint256 length);
error ECDSAInvalidSignatureS(bytes32 s);
error InvalidShortString();
error StringTooLong(string str);
struct PauseMessage {
bytes32 nonce;
}
struct DeputyAuthMessage {
address deputy;
}
event DeputySet(address indexed deputy);
event DeputyGuardianModuleSet(IDeputyGuardianModule indexed deputyGuardianModule);
event PauseTriggered(address indexed deputy, bytes32 nonce);
event EIP712DomainChanged();
function version() external view returns (string memory);
function __constructor__(
Safe _foundationSafe,
IDeputyGuardianModule _deputyGuardianModule,
ISuperchainConfig _superchainConfig,
address _deputy,
bytes memory _deputySignature
)
external;
function foundationSafe() external view returns (Safe foundationSafe_);
function deputyGuardianModule() external view returns (IDeputyGuardianModule);
function superchainConfig() external view returns (ISuperchainConfig superchainConfig_);
function deputy() external view returns (address);
function pauseMessageTypehash() external pure returns (bytes32 pauseMessageTypehash_);
function deputyAuthMessageTypehash() external pure returns (bytes32 deputyAuthMessageTypehash_);
function usedNonces(bytes32) external view returns (bool);
function pause(bytes32 _nonce, bytes memory _signature) external;
function setDeputy(address _deputy, bytes memory _deputySignature) external;
function setDeputyGuardianModule(IDeputyGuardianModule _deputyGuardianModule) external;
function eip712Domain()
external
view
returns (
bytes1 fields,
string memory name,
string memory version,
uint256 chainId,
address verifyingContract,
bytes32 salt,
uint256[] memory extensions
);
}
...@@ -16,10 +16,10 @@ import ( ...@@ -16,10 +16,10 @@ import (
var excludeContracts = []string{ var excludeContracts = []string{
// External dependencies // External dependencies
"IERC20", "IERC721", "IERC721Enumerable", "IERC721Upgradeable", "IERC721Metadata", "IERC20", "IERC721", "IERC5267", "IERC721Enumerable", "IERC721Upgradeable", "IERC721Metadata",
"IERC165", "IERC165Upgradeable", "ERC721TokenReceiver", "ERC1155TokenReceiver", "IERC165", "IERC165Upgradeable", "ERC721TokenReceiver", "ERC1155TokenReceiver",
"ERC777TokensRecipient", "Guard", "IProxy", "Vm", "VmSafe", "IMulticall3", "ERC777TokensRecipient", "Guard", "IProxy", "Vm", "VmSafe", "IMulticall3",
"IERC721TokenReceiver", "IProxyCreationCallback", "IBeacon", "IERC721TokenReceiver", "IProxyCreationCallback", "IBeacon", "IEIP712",
// EAS // EAS
"IEAS", "ISchemaResolver", "ISchemaRegistry", "IEAS", "ISchemaResolver", "ISchemaRegistry",
......
[
{
"inputs": [
{
"internalType": "contract GnosisSafe",
"name": "_foundationSafe",
"type": "address"
},
{
"internalType": "contract IDeputyGuardianModule",
"name": "_deputyGuardianModule",
"type": "address"
},
{
"internalType": "contract ISuperchainConfig",
"name": "_superchainConfig",
"type": "address"
},
{
"internalType": "address",
"name": "_deputy",
"type": "address"
},
{
"internalType": "bytes",
"name": "_deputySignature",
"type": "bytes"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "deputy",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "deputyAuthMessageTypehash",
"outputs": [
{
"internalType": "bytes32",
"name": "deputyAuthMessageTypehash_",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "deputyGuardianModule",
"outputs": [
{
"internalType": "contract IDeputyGuardianModule",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "eip712Domain",
"outputs": [
{
"internalType": "bytes1",
"name": "fields",
"type": "bytes1"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "string",
"name": "version",
"type": "string"
},
{
"internalType": "uint256",
"name": "chainId",
"type": "uint256"
},
{
"internalType": "address",
"name": "verifyingContract",
"type": "address"
},
{
"internalType": "bytes32",
"name": "salt",
"type": "bytes32"
},
{
"internalType": "uint256[]",
"name": "extensions",
"type": "uint256[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "foundationSafe",
"outputs": [
{
"internalType": "contract GnosisSafe",
"name": "foundationSafe_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_nonce",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
}
],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "pauseMessageTypehash",
"outputs": [
{
"internalType": "bytes32",
"name": "pauseMessageTypehash_",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_deputy",
"type": "address"
},
{
"internalType": "bytes",
"name": "_deputySignature",
"type": "bytes"
}
],
"name": "setDeputy",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IDeputyGuardianModule",
"name": "_deputyGuardianModule",
"type": "address"
}
],
"name": "setDeputyGuardianModule",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "superchainConfig",
"outputs": [
{
"internalType": "contract ISuperchainConfig",
"name": "superchainConfig_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "usedNonces",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "contract IDeputyGuardianModule",
"name": "deputyGuardianModule",
"type": "address"
}
],
"name": "DeputyGuardianModuleSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "deputy",
"type": "address"
}
],
"name": "DeputySet",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "EIP712DomainChanged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "deputy",
"type": "address"
},
{
"indexed": false,
"internalType": "bytes32",
"name": "nonce",
"type": "bytes32"
}
],
"name": "PauseTriggered",
"type": "event"
},
{
"inputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"name": "DeputyPauseModule_ExecutionFailed",
"type": "error"
},
{
"inputs": [],
"name": "DeputyPauseModule_InvalidDeputy",
"type": "error"
},
{
"inputs": [],
"name": "DeputyPauseModule_NonceAlreadyUsed",
"type": "error"
},
{
"inputs": [],
"name": "DeputyPauseModule_NotFromSafe",
"type": "error"
},
{
"inputs": [],
"name": "DeputyPauseModule_SuperchainNotPaused",
"type": "error"
},
{
"inputs": [],
"name": "DeputyPauseModule_Unauthorized",
"type": "error"
},
{
"inputs": [],
"name": "ECDSAInvalidSignature",
"type": "error"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "length",
"type": "uint256"
}
],
"name": "ECDSAInvalidSignatureLength",
"type": "error"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
}
],
"name": "ECDSAInvalidSignatureS",
"type": "error"
},
{
"inputs": [],
"name": "InvalidShortString",
"type": "error"
},
{
"inputs": [
{
"internalType": "string",
"name": "str",
"type": "string"
}
],
"name": "StringTooLong",
"type": "error"
}
]
\ No newline at end of file
...@@ -183,6 +183,10 @@ ...@@ -183,6 +183,10 @@
"initCodeHash": "0x5eaf823d81995ce1f703f26e31049c54c1d4902dd9873a0b4645d470f2f459a2", "initCodeHash": "0x5eaf823d81995ce1f703f26e31049c54c1d4902dd9873a0b4645d470f2f459a2",
"sourceCodeHash": "0x17236a91c4171ae9525eae0e59fa65bb2dc320d62677cfc7d7eb942f182619fb" "sourceCodeHash": "0x17236a91c4171ae9525eae0e59fa65bb2dc320d62677cfc7d7eb942f182619fb"
}, },
"src/safe/DeputyPauseModule.sol": {
"initCodeHash": "0x55f0b6c1a102114b8c30157e7295da7058ba8d1b93f7d4070028286968611279",
"sourceCodeHash": "0xec92b5ccbd3ee337897464546ccb8306fca245bbb6ce4b0941d86bc82e0f4cfe"
},
"src/safe/LivenessGuard.sol": { "src/safe/LivenessGuard.sol": {
"initCodeHash": "0xc8e29e8b12f423c8cd229a38bc731240dd815d96f1b0ab96c71494dde63f6a81", "initCodeHash": "0xc8e29e8b12f423c8cd229a38bc731240dd815d96f1b0ab96c71494dde63f6a81",
"sourceCodeHash": "0x72b8d8d855e7af8beee29330f6cb9b9069acb32e23ce940002ec9a41aa012a16" "sourceCodeHash": "0x72b8d8d855e7af8beee29330f6cb9b9069acb32e23ce940002ec9a41aa012a16"
......
[
{
"bytes": "32",
"label": "_nameFallback",
"offset": 0,
"slot": "0",
"type": "string"
},
{
"bytes": "32",
"label": "_versionFallback",
"offset": 0,
"slot": "1",
"type": "string"
},
{
"bytes": "20",
"label": "deputy",
"offset": 0,
"slot": "2",
"type": "address"
},
{
"bytes": "20",
"label": "deputyGuardianModule",
"offset": 0,
"slot": "3",
"type": "contract IDeputyGuardianModule"
},
{
"bytes": "32",
"label": "usedNonces",
"offset": 0,
"slot": "4",
"type": "mapping(bytes32 => bool)"
}
]
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Safe
import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
import { Enum } from "safe-contracts/common/Enum.sol";
// Contracts
import { EIP712 } from "@openzeppelin/contracts-v5/utils/cryptography/EIP712.sol";
// Libraries
import { ECDSA } from "@openzeppelin/contracts-v5/utils/cryptography/ECDSA.sol";
// Interfaces
import { ISemver } from "interfaces/universal/ISemver.sol";
import { IDeputyGuardianModule } from "interfaces/safe/IDeputyGuardianModule.sol";
import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
/// @title DeputyPauseModule
/// @notice Safe Module designed to be installed in the Foundation Safe which allows a specific
/// deputy address to act as the Foundation Safe for the sake of triggering the
/// Superchain-wide pause functionality. Significantly simplifies the process of triggering
/// a Superchain-wide pause without changing the existing security model.
contract DeputyPauseModule is ISemver, EIP712 {
/// @notice Error message for invalid deputy.
error DeputyPauseModule_InvalidDeputy();
/// @notice Error message for unauthorized calls.
error DeputyPauseModule_Unauthorized();
/// @notice Error message for nonce reuse.
error DeputyPauseModule_NonceAlreadyUsed();
/// @notice Error message for failed transaction execution.
error DeputyPauseModule_ExecutionFailed(string);
/// @notice Error message for the SuperchainConfig not being paused.
error DeputyPauseModule_SuperchainNotPaused();
/// @notice Error message for the call not being from the Foundation Safe.
error DeputyPauseModule_NotFromSafe();
/// @notice Struct for the Pause action.
/// @custom:field nonce Signature nonce.
struct PauseMessage {
bytes32 nonce;
}
/// @notice Struct for the DeputyAuth action.
/// @custom:field deputy Address of the deputy account.
struct DeputyAuthMessage {
address deputy;
}
/// @notice Event emitted when the deputy address is set.
event DeputySet(address indexed deputy);
/// @notice Event emitted when the DeputyGuardianModule is set.
event DeputyGuardianModuleSet(IDeputyGuardianModule indexed deputyGuardianModule);
/// @notice Event emitted when the pause is triggered.
event PauseTriggered(address indexed deputy, bytes32 nonce);
/// @notice Foundation Safe.
Safe internal immutable FOUNDATION_SAFE;
/// @notice SuperchainConfig contract.
ISuperchainConfig internal immutable SUPERCHAIN_CONFIG;
/// @notice Typehash for the Pause action.
bytes32 internal constant PAUSE_MESSAGE_TYPEHASH = keccak256("PauseMessage(bytes32 nonce)");
/// @notice Typehash for the DeputyAuth message.
bytes32 internal constant DEPUTY_AUTH_MESSAGE_TYPEHASH = keccak256("DeputyAuthMessage(address deputy)");
/// @notice Address of the Deputy account.
address public deputy;
/// @notice Address of the DeputyGuardianModule used by the SC Safe.
IDeputyGuardianModule public deputyGuardianModule;
/// @notice Used nonces.
mapping(bytes32 => bool) public usedNonces;
/// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1
string public constant version = "1.0.0-beta.1";
/// @param _foundationSafe Address of the Foundation Safe.
/// @param _deputyGuardianModule Address of the DeputyGuardianModule used by the SC Safe.
/// @param _superchainConfig Address of the SuperchainConfig contract.
/// @param _deputy Address of the deputy account.
/// @param _deputySignature Signature from the deputy verifying that the account is an EOA.
constructor(
Safe _foundationSafe,
IDeputyGuardianModule _deputyGuardianModule,
ISuperchainConfig _superchainConfig,
address _deputy,
bytes memory _deputySignature
)
EIP712("DeputyPauseModule", "1")
{
_setDeputy(_deputy, _deputySignature);
deputyGuardianModule = _deputyGuardianModule;
FOUNDATION_SAFE = _foundationSafe;
SUPERCHAIN_CONFIG = _superchainConfig;
}
/// @notice Getter function for the Foundation Safe address.
/// @return foundationSafe_ Foundation Safe address.
function foundationSafe() public view returns (Safe foundationSafe_) {
foundationSafe_ = FOUNDATION_SAFE;
}
/// @notice Getter function for the SuperchainConfig address.
/// @return superchainConfig_ SuperchainConfig address.
function superchainConfig() public view returns (ISuperchainConfig superchainConfig_) {
superchainConfig_ = SUPERCHAIN_CONFIG;
}
/// @notice Getter function for the Pause message typehash.
/// @return pauseMessageTypehash_ Pause message typehash.
function pauseMessageTypehash() public pure returns (bytes32 pauseMessageTypehash_) {
pauseMessageTypehash_ = PAUSE_MESSAGE_TYPEHASH;
}
/// @notice Getter function for the DeputyAuth message typehash.
/// @return deputyAuthMessageTypehash_ DeputyAuth message typehash.
function deputyAuthMessageTypehash() public pure returns (bytes32 deputyAuthMessageTypehash_) {
deputyAuthMessageTypehash_ = DEPUTY_AUTH_MESSAGE_TYPEHASH;
}
/// @notice Sets the deputy address.
/// @param _deputy Deputy address.
/// @param _deputySignature Deputy signature.
function setDeputy(address _deputy, bytes memory _deputySignature) external {
// Can only be called by the Foundation Safe itself.
if (msg.sender != address(FOUNDATION_SAFE)) {
revert DeputyPauseModule_NotFromSafe();
}
// Set the deputy address.
_setDeputy(_deputy, _deputySignature);
}
/// @notice Sets the DeputyGuardianModule.
/// @param _deputyGuardianModule DeputyGuardianModule address.
function setDeputyGuardianModule(IDeputyGuardianModule _deputyGuardianModule) external {
if (msg.sender != address(FOUNDATION_SAFE)) {
revert DeputyPauseModule_NotFromSafe();
}
deputyGuardianModule = _deputyGuardianModule;
emit DeputyGuardianModuleSet(_deputyGuardianModule);
}
/// @notice Calls the Foundation Safe's `execTransactionFromModuleReturnData()` function with
/// the arguments necessary to call `pause()` on the Security Council Safe, which will
/// then cause the Security Council Safe to trigger SuperchainConfig pause.
/// Front-running this function is completely safe, it'll pause either way.
/// @param _nonce Signature nonce.
/// @param _signature ECDSA signature.
function pause(bytes32 _nonce, bytes memory _signature) external {
// Verify the signature.
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(PAUSE_MESSAGE_TYPEHASH, PauseMessage(_nonce))));
if (ECDSA.recover(digest, _signature) != deputy) {
revert DeputyPauseModule_Unauthorized();
}
// Make sure the nonce hasn't been used yet.
if (usedNonces[_nonce]) {
revert DeputyPauseModule_NonceAlreadyUsed();
}
// Mark the nonce as used.
usedNonces[_nonce] = true;
// Attempt to trigger the call.
(bool success, bytes memory returnData) = FOUNDATION_SAFE.execTransactionFromModuleReturnData(
address(deputyGuardianModule), 0, abi.encodeCall(IDeputyGuardianModule.pause, ()), Enum.Operation.Call
);
// If the call fails, revert.
if (!success) {
revert DeputyPauseModule_ExecutionFailed(string(returnData));
}
// Verify that the SuperchainConfig is now paused.
if (!SUPERCHAIN_CONFIG.paused()) {
revert DeputyPauseModule_SuperchainNotPaused();
}
// Emit that the pause was triggered.
emit PauseTriggered(deputy, _nonce);
}
/// @notice Internal function to set the deputy address.
/// @param _deputy Deputy address.
/// @param _deputySignature Deputy signature.
function _setDeputy(address _deputy, bytes memory _deputySignature) internal {
// Check that the deputy signature is valid.
bytes32 digest =
_hashTypedDataV4(keccak256(abi.encode(DEPUTY_AUTH_MESSAGE_TYPEHASH, DeputyAuthMessage(_deputy))));
if (ECDSA.recover(digest, _deputySignature) != _deputy) {
revert DeputyPauseModule_InvalidDeputy();
}
// Set the deputy address.
deputy = _deputy;
// Emit the DeputySet event.
emit DeputySet(_deputy);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
// Testing
import { CommonTest } from "test/setup/CommonTest.sol";
import { GnosisSafe as Safe } from "safe-contracts/GnosisSafe.sol";
import "test/safe-tools/SafeTestTools.sol";
// Scripts
import { DeployUtils } from "scripts/libraries/DeployUtils.sol";
// Interfaces
import { IDeputyGuardianModule } from "interfaces/safe/IDeputyGuardianModule.sol";
import { IDeputyPauseModule } from "interfaces/safe/IDeputyPauseModule.sol";
/// @title DeputyPauseModule_TestInit
/// @notice Base test setup for the DeputyPauseModule.
contract DeputyPauseModule_TestInit is CommonTest, SafeTestTools {
using SafeTestLib for SafeInstance;
event ExecutionFromModuleSuccess(address indexed);
event DeputySet(address indexed);
event DeputyGuardianModuleSet(IDeputyGuardianModule indexed);
event PauseTriggered(address indexed deputy, bytes32 nonce);
IDeputyPauseModule deputyPauseModule;
IDeputyGuardianModule deputyGuardianModule;
SafeInstance securityCouncilSafeInstance;
SafeInstance foundationSafeInstance;
address deputy;
uint256 deputyKey;
bytes deputyAuthSignature;
bytes32 constant SOME_VALID_NONCE = keccak256("some valid nonce");
bytes32 constant PAUSE_MESSAGE_TYPEHASH = keccak256("PauseMessage(bytes32 nonce)");
bytes32 constant DEPUTY_AUTH_MESSAGE_TYPEHASH = keccak256("DeputyAuthMessage(address deputy)");
/// @notice Sets up the test environment.
function setUp() public virtual override {
super.setUp();
// Set up 20 keys.
(, uint256[] memory keys) = SafeTestLib.makeAddrsAndKeys("DeputyPauseModule_test_", 20);
// Split into two sets of 10 keys.
uint256[] memory keys1 = new uint256[](10);
uint256[] memory keys2 = new uint256[](10);
for (uint256 i; i < 10; i++) {
keys1[i] = keys[i];
keys2[i] = keys[i + 10];
}
// Create a Security Council Safe with 10 owners.
securityCouncilSafeInstance = _setupSafe(keys1, 10);
// Create a Foundation Safe with 10 different owners.
foundationSafeInstance = _setupSafe(keys2, 10);
// Set the Security Council Safe as the Guardian of the SuperchainConfig.
vm.store(
address(superchainConfig),
superchainConfig.GUARDIAN_SLOT(),
bytes32(uint256(uint160(address(securityCouncilSafeInstance.safe))))
);
// Create a DeputyGuardianModule and set the Foundation Safe as the Deputy Guardian.
deputyGuardianModule = IDeputyGuardianModule(
DeployUtils.create1({
_name: "DeputyGuardianModule",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IDeputyGuardianModule.__constructor__,
(securityCouncilSafeInstance.safe, superchainConfig, address(foundationSafeInstance.safe))
)
)
})
);
// Enable the DeputyGuardianModule on the Security Council Safe.
securityCouncilSafeInstance.enableModule(address(deputyGuardianModule));
// Create the deputy for the DeputyPauseModule.
(deputy, deputyKey) = makeAddrAndKey("deputy");
// Create the deputy auth signature.
deputyAuthSignature = makeAuthSignature(getNextContract(), deputyKey, deputy);
// Create the DeputyPauseModule.
deputyPauseModule = IDeputyPauseModule(
DeployUtils.create1({
_name: "DeputyPauseModule",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IDeputyPauseModule.__constructor__,
(foundationSafeInstance.safe, deputyGuardianModule, superchainConfig, deputy, deputyAuthSignature)
)
)
})
);
// Enable the DeputyPauseModule on the Foundation Safe.
foundationSafeInstance.enableModule(address(deputyPauseModule));
}
/// @notice Generates a signature to authenticate as the deputy.
/// @param _verifyingContract The verifying contract.
/// @param _privateKey The private key to use to sign the message.
/// @param _deputy The deputy to authenticate as.
/// @return Generated signature.
function makeAuthSignature(
address _verifyingContract,
uint256 _privateKey,
address _deputy
)
internal
view
returns (bytes memory)
{
return makeAuthSignature(block.chainid, _verifyingContract, _privateKey, _deputy);
}
/// @notice Generates a signature to authenticate as the deputy.
/// @param _chainId Chain ID to use for the domain separator.
/// @param _verifyingContract The verifying contract.
/// @param _privateKey The private key to use to sign the message.
/// @param _deputy The deputy to authenticate as.
/// @return Generated signature.
function makeAuthSignature(
uint256 _chainId,
address _verifyingContract,
uint256 _privateKey,
address _deputy
)
internal
pure
returns (bytes memory)
{
bytes32 structHash = keccak256(abi.encode(DEPUTY_AUTH_MESSAGE_TYPEHASH, _deputy));
bytes32 digest = hashTypedData(_verifyingContract, _chainId, structHash);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_privateKey, digest);
return abi.encodePacked(r, s, v);
}
/// @notice Generates a signature to trigger a pause.
/// @param _verifyingContract The verifying contract.
/// @param _nonce Signature nonce.
/// @param _privateKey The private key to use to sign the message.
/// @return Generated signature.
function makePauseSignature(
address _verifyingContract,
bytes32 _nonce,
uint256 _privateKey
)
internal
view
returns (bytes memory)
{
return makePauseSignature(block.chainid, _verifyingContract, _nonce, _privateKey);
}
/// @notice Generates a signature to trigger a pause.
/// @param _chainId Chain ID to use for the domain separator.
/// @param _verifyingContract The verifying contract.
/// @param _nonce Signature nonce.
/// @param _privateKey The private key to use to sign the message.
/// @return Generated signature.
function makePauseSignature(
uint256 _chainId,
address _verifyingContract,
bytes32 _nonce,
uint256 _privateKey
)
internal
pure
returns (bytes memory)
{
bytes32 structHash = keccak256(abi.encode(PAUSE_MESSAGE_TYPEHASH, _nonce));
bytes32 digest = hashTypedData(_verifyingContract, _chainId, structHash);
(uint8 v, bytes32 r, bytes32 s) = vm.sign(_privateKey, digest);
return abi.encodePacked(r, s, v);
}
/// @notice Helper function to compute EIP-712 typed data hash
/// @param _verifyingContract The verifying contract.
/// @param _chainId Chain ID to use for the domain separator.
/// @param _structHash The struct hash.
/// @return The EIP-712 typed data hash.
function hashTypedData(
address _verifyingContract,
uint256 _chainId,
bytes32 _structHash
)
internal
pure
returns (bytes32)
{
bytes32 DOMAIN_SEPARATOR = keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256("DeputyPauseModule"),
keccak256("1"),
_chainId,
_verifyingContract
)
);
return keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, _structHash));
}
/// @notice Gets the next contract that will be created by this test contract.
/// @return Address of the next contract to be created.
function getNextContract() internal view returns (address) {
return vm.computeCreateAddress(address(this), vm.getNonce(address(this)));
}
}
/// @title DeputyPauseModule_Constructor_Test
/// @notice Tests that the constructor works.
contract DeputyPauseModule_Constructor_Test is DeputyPauseModule_TestInit {
/// @notice Tests that the constructor works.
function test_constructor_validParameters_succeeds() external {
// Create the signature.
address nextContract = getNextContract();
bytes memory signature = makeAuthSignature(nextContract, deputyKey, deputy);
// Deploy the module.
vm.expectEmit(address(nextContract));
emit DeputySet(deputy);
deputyPauseModule = IDeputyPauseModule(
DeployUtils.create1({
_name: "DeputyPauseModule",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IDeputyPauseModule.__constructor__,
(foundationSafeInstance.safe, deputyGuardianModule, superchainConfig, deputy, signature)
)
)
})
);
}
}
/// @title DeputyPauseModule_Constructor_TestFail
/// @notice Tests that the constructor fails when it should.
contract DeputyPauseModule_Constructor_TestFail is DeputyPauseModule_TestInit {
/// @notice Tests that the constructor reverts when the signature is not the deputy auth message.
function testFuzz_constructor_signatureNotNextContract_reverts(address _nextContract) external {
// Make sure that the next contract is not correct.
vm.assume(_nextContract != getNextContract());
// Create the signature.
bytes memory signature = makeAuthSignature(_nextContract, deputyKey, deputy);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_InvalidDeputy.selector));
IDeputyPauseModule(
DeployUtils.create1({
_name: "DeputyPauseModule",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IDeputyPauseModule.__constructor__,
(foundationSafeInstance.safe, deputyGuardianModule, superchainConfig, deputy, signature)
)
)
})
);
}
/// @notice Tests that the constructor reverts when the signature is not the deputy auth message.
function testFuzz_constructor_signatureNotOverDeputy_reverts(address _deputy) external {
// Make sure that the deputy is not correct.
vm.assume(_deputy != deputy);
// Create the signature.
bytes memory signature = makeAuthSignature(getNextContract(), deputyKey, _deputy);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_InvalidDeputy.selector));
IDeputyPauseModule(
DeployUtils.create1({
_name: "DeputyPauseModule",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IDeputyPauseModule.__constructor__,
(foundationSafeInstance.safe, deputyGuardianModule, superchainConfig, deputy, signature)
)
)
})
);
}
/// @notice Tests that the constructor reverts when the signature is not from the deputy.
function testFuzz_constructor_signatureNotFromDeputy_reverts(uint256 _privateKey) external {
// Make sure that the private key is not the deputy's private key.
vm.assume(_privateKey != deputyKey);
// Make sure that the private key is in the range of a valid secp256k1 private key.
_privateKey = bound(_privateKey, 1, SECP256K1_ORDER - 1);
// Create the signature.
bytes memory signature = makeAuthSignature(getNextContract(), _privateKey, deputy);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_InvalidDeputy.selector));
IDeputyPauseModule(
DeployUtils.create1({
_name: "DeputyPauseModule",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IDeputyPauseModule.__constructor__,
(foundationSafeInstance.safe, deputyGuardianModule, superchainConfig, deputy, signature)
)
)
})
);
}
/// @notice Tests that the constructor reverts when the signature uses the wrong chain ID.
function testFuzz_constructor_wrongChainId_reverts(uint256 _chainId) external {
// Make sure that the chain ID is not the current chain ID.
vm.assume(_chainId != block.chainid);
// Create the signature.
bytes memory signature = makeAuthSignature(_chainId, getNextContract(), deputyKey, deputy);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_InvalidDeputy.selector));
IDeputyPauseModule(
DeployUtils.create1({
_name: "DeputyPauseModule",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IDeputyPauseModule.__constructor__,
(foundationSafeInstance.safe, deputyGuardianModule, superchainConfig, deputy, signature)
)
)
})
);
}
/// @notice Tests that the constructor reverts when the signature is not the deputy auth message.
function test_constructor_signatureNotAuthMessage_reverts() external {
// Create the signature.
bytes memory signature = makePauseSignature(getNextContract(), bytes32(0), deputyKey);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_InvalidDeputy.selector));
IDeputyPauseModule(
DeployUtils.create1({
_name: "DeputyPauseModule",
_args: DeployUtils.encodeConstructor(
abi.encodeCall(
IDeputyPauseModule.__constructor__,
(foundationSafeInstance.safe, deputyGuardianModule, superchainConfig, deputy, signature)
)
)
})
);
}
}
/// @title DeputyPauseModule_Getters_Test
/// @notice Tests that the getters work.
contract DeputyPauseModule_Getters_Test is DeputyPauseModule_TestInit {
/// @notice Tests that the getters work.
function test_getters_works() external view {
assertEq(address(deputyPauseModule.foundationSafe()), address(foundationSafeInstance.safe));
assertEq(address(deputyPauseModule.deputyGuardianModule()), address(deputyGuardianModule));
assertEq(address(deputyPauseModule.superchainConfig()), address(superchainConfig));
assertEq(deputyPauseModule.deputy(), deputy);
assertEq(deputyPauseModule.pauseMessageTypehash(), PAUSE_MESSAGE_TYPEHASH);
assertEq(deputyPauseModule.deputyAuthMessageTypehash(), DEPUTY_AUTH_MESSAGE_TYPEHASH);
}
}
/// @title DeputyPauseModule_Pause_Test
/// @notice Tests that the pause() function works.
contract DeputyPauseModule_Pause_Test is DeputyPauseModule_TestInit {
/// @notice Tests that pause() successfully pauses when called by the deputy.
/// @param _nonce Signature nonce.
function testFuzz_pause_validParameters_succeeds(bytes32 _nonce) external {
vm.expectEmit(address(superchainConfig));
emit Paused("Deputy Guardian");
vm.expectEmit(address(securityCouncilSafeInstance.safe));
emit ExecutionFromModuleSuccess(address(deputyGuardianModule));
vm.expectEmit(address(deputyGuardianModule));
emit Paused("Deputy Guardian");
vm.expectEmit(address(foundationSafeInstance.safe));
emit ExecutionFromModuleSuccess(address(deputyPauseModule));
vm.expectEmit(address(deputyPauseModule));
emit PauseTriggered(deputy, _nonce);
// State assertions before the pause.
assertEq(deputyPauseModule.usedNonces(_nonce), false);
assertEq(superchainConfig.paused(), false);
// Trigger the pause.
bytes memory signature = makePauseSignature(address(deputyPauseModule), _nonce, deputyKey);
deputyPauseModule.pause(_nonce, signature);
// State assertions after the pause.
assertEq(deputyPauseModule.usedNonces(_nonce), true);
assertEq(superchainConfig.paused(), true);
}
/// @notice Tests that pause() succeeds when called with two different nonces if the
/// SuperchainConfig contract is not paused between calls.
/// @param _nonce1 First nonce.
/// @param _nonce2 Second nonce.
function testFuzz_pause_differentNonces_succeeds(bytes32 _nonce1, bytes32 _nonce2) external {
// Make sure that the nonces are different.
vm.assume(_nonce1 != _nonce2);
// Pause once.
bytes memory sig1 = makePauseSignature(address(deputyPauseModule), _nonce1, deputyKey);
deputyPauseModule.pause(_nonce1, sig1);
// Unpause.
vm.prank(address(securityCouncilSafeInstance.safe));
superchainConfig.unpause();
// Pause again with a different nonce.
bytes memory sig2 = makePauseSignature(address(deputyPauseModule), _nonce2, deputyKey);
deputyPauseModule.pause(_nonce2, sig2);
}
/// @notice Tests that pause() succeeds when called with two different nonces after the
/// superchain has already been paused between calls.
/// @param _nonce1 First nonce.
/// @param _nonce2 Second nonce.
function testFuzz_pause_differentNoncesAlreadyPaused_succeeds(bytes32 _nonce1, bytes32 _nonce2) external {
// Make sure that the nonces are different.
vm.assume(_nonce1 != _nonce2);
// Pause once.
bytes memory sig1 = makePauseSignature(address(deputyPauseModule), _nonce1, deputyKey);
deputyPauseModule.pause(_nonce1, sig1);
// Pause again with a different nonce.
bytes memory sig2 = makePauseSignature(address(deputyPauseModule), _nonce2, deputyKey);
deputyPauseModule.pause(_nonce2, sig2);
}
/// @notice Tests that pause() succeeds within 1 million gas.
function test_pause_withinMillionGas_succeeds() external {
bytes memory signature = makePauseSignature(address(deputyPauseModule), SOME_VALID_NONCE, deputyKey);
uint256 gasBefore = gasleft();
deputyPauseModule.pause(SOME_VALID_NONCE, signature);
uint256 gasUsed = gasBefore - gasleft();
// Ensure gas usage is within expected bounds.
// 1m is a conservative limit that means we can trigger the pause in most blocks. It would
// be prohibitively expensive to fill up blocks to prevent the pause from being triggered
// even at 1m gas for any prolonged duration. Means that we can always trigger the pause
// within a short period of time.
assertLt(gasUsed, 1000000);
}
}
/// @title DeputyPauseModule_Pause_TestFail
/// @notice Tests that the pause() function reverts when it should.
contract DeputyPauseModule_Pause_TestFail is DeputyPauseModule_TestInit {
/// @notice Tests that pause() reverts when called by an address other than the deputy.
/// @param _privateKey The private key to use to sign the message.
function testFuzz_pause_notDeputy_reverts(uint256 _privateKey) external {
// Make sure that the private key is in the range of a valid secp256k1 private key.
_privateKey = bound(_privateKey, 1, SECP256K1_ORDER - 1);
// Make sure that the private key is not the deputy's private key.
vm.assume(_privateKey != deputyKey);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_Unauthorized.selector));
bytes memory signature = makePauseSignature(address(deputyPauseModule), SOME_VALID_NONCE, _privateKey);
deputyPauseModule.pause(SOME_VALID_NONCE, signature);
}
/// @notice Tests that pause() reverts when the nonce has already been used.
/// @param _nonce Signature nonce.
function testFuzz_pause_nonceAlreadyUsed_reverts(bytes32 _nonce) external {
// Pause once.
bytes memory signature = makePauseSignature(address(deputyPauseModule), _nonce, deputyKey);
deputyPauseModule.pause(_nonce, signature);
// Unpause.
vm.prank(address(securityCouncilSafeInstance.safe));
superchainConfig.unpause();
// Expect that the nonce is now used.
assertEq(deputyPauseModule.usedNonces(_nonce), true);
// Pause again.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_NonceAlreadyUsed.selector));
deputyPauseModule.pause(_nonce, signature);
}
/// @notice Tests that pause() reverts when the signature is longer than 65 bytes.
/// @param _length The length of the malformed signature.
function testFuzz_pause_signatureTooLong_reverts(uint256 _length) external {
// Make sure signature is longer than 65 bytes.
_length = bound(_length, 66, 1000);
// Create the malformed signature.
bytes memory signature = new bytes(_length);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.ECDSAInvalidSignatureLength.selector, _length));
deputyPauseModule.pause(SOME_VALID_NONCE, signature);
}
/// @notice Tests that pause() reverts when the signature is shorter than 65 bytes.
/// @param _length The length of the malformed signature.
function testFuzz_pause_signatureTooShort_reverts(uint256 _length) external {
// Make sure signature is shorter than 65 bytes.
_length = bound(_length, 0, 64);
// Create the malformed signature.
bytes memory signature = new bytes(_length);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.ECDSAInvalidSignatureLength.selector, _length));
deputyPauseModule.pause(SOME_VALID_NONCE, signature);
}
/// @notice Tests that pause() reverts when the chain ID is not the same as the chain ID that
/// the signature was created for.
/// @param _chainId Chain ID to use for the signature.
function testFuzz_pause_wrongChainId_reverts(uint256 _chainId) external {
// Make sure that the chain ID is not the current chain ID.
vm.assume(_chainId != block.chainid);
// Signature with the wrong chain ID.
bytes memory signature = makePauseSignature(_chainId, address(deputyPauseModule), SOME_VALID_NONCE, deputyKey);
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_Unauthorized.selector));
deputyPauseModule.pause(SOME_VALID_NONCE, signature);
}
/// @notice Tests that pause() reverts when the verifying contract is not the deputy pause module.
/// @param _verifyingContract The verifying contract.
function testFuzz_pause_wrongVerifyingContract_reverts(address _verifyingContract) external {
// Make sure that the verifying contract is not the deputy pause module.
vm.assume(_verifyingContract != address(deputyPauseModule));
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_Unauthorized.selector));
bytes memory signature = makePauseSignature(_verifyingContract, SOME_VALID_NONCE, deputyKey);
deputyPauseModule.pause(SOME_VALID_NONCE, signature);
}
/// @notice Tests that the error message is returned when the call to the safe reverts.
function test_pause_targetReverts_reverts() external {
// Make sure that the SuperchainConfig pause() reverts.
vm.mockCallRevert(
address(superchainConfig),
abi.encodePacked(superchainConfig.pause.selector),
"SuperchainConfig: pause() reverted"
);
// Note that the error here will be somewhat awkwardly double-encoded because the
// DeputyGuardianModule will encode the revert message as an ExecutionFailed error and then
// the DeputyPauseModule will re-encode it as another ExecutionFailed error.
vm.expectRevert(
abi.encodeWithSelector(
IDeputyPauseModule.DeputyPauseModule_ExecutionFailed.selector,
string(
abi.encodeWithSelector(
IDeputyGuardianModule.ExecutionFailed.selector, "SuperchainConfig: pause() reverted"
)
)
)
);
bytes memory signature = makePauseSignature(address(deputyPauseModule), SOME_VALID_NONCE, deputyKey);
deputyPauseModule.pause(SOME_VALID_NONCE, signature);
}
/// @notice Tests that pause() reverts when the superchain is not in a paused state after the
/// transaction is sent.
function test_pause_superchainPauseFails_reverts() external {
// Make sure that the SuperchainConfig paused() returns false.
vm.mockCall(address(superchainConfig), abi.encodePacked(superchainConfig.paused.selector), abi.encode(false));
// Expect a revert.
vm.expectRevert(IDeputyPauseModule.DeputyPauseModule_SuperchainNotPaused.selector);
deputyPauseModule.pause(
SOME_VALID_NONCE, makePauseSignature(address(deputyPauseModule), SOME_VALID_NONCE, deputyKey)
);
}
}
/// @title DeputyPauseModule_SetDeputy_Test
/// @notice Tests that the setDeputy() function works.
contract DeputyPauseModule_SetDeputy_Test is DeputyPauseModule_TestInit {
/// @notice Tests that setDeputy() succeeds when called from the safe.
/// @param _seed Seed used to generate a private key.
function testFuzz_setDeputy_fromSafe_succeeds(bytes32 _seed) external {
(address newDeputy, uint256 newDeputyKey) = makeAddrAndKey(string(abi.encodePacked(_seed)));
// Make sure the private key is not the existing deputy's private key.
vm.assume(newDeputyKey != deputyKey);
// Sign the message.
bytes memory signature = makeAuthSignature(address(deputyPauseModule), newDeputyKey, newDeputy);
// Set the deputy address.
vm.expectEmit(address(deputyPauseModule));
emit DeputySet(newDeputy);
vm.prank(address(foundationSafeInstance.safe));
deputyPauseModule.setDeputy(newDeputy, signature);
// Assert that the deputy address has been set.
assertEq(deputyPauseModule.deputy(), newDeputy);
}
}
/// @title DeputyPauseModule_SetDeputy_TestFail
/// @notice Tests that the setDeputy() function reverts when it should.
contract DeputyPauseModule_SetDeputy_TestFail is DeputyPauseModule_TestInit {
/// @notice Tests that setDeputy() reverts when called by an address other than the safe.
function testFuzz_setDeputy_notSafe_reverts(address _sender) external {
// Make sure that the sender is not the safe.
vm.assume(_sender != address(foundationSafeInstance.safe));
// Create the key.
(address newDeputy, uint256 newDeputyKey) = makeAddrAndKey("whatever");
// Sign the message.
bytes memory signature = makeAuthSignature(address(deputyPauseModule), newDeputyKey, newDeputy);
// Expect a revert.
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_NotFromSafe.selector));
deputyPauseModule.setDeputy(newDeputy, signature);
// Make sure deputy has not changed.
assertEq(deputyPauseModule.deputy(), deputy);
}
}
/// @title DeputyPauseModule_SetDeputyGuardianModule_Test
/// @notice Tests that the setDeputyGuardianModule() function works.
contract DeputyPauseModule_SetDeputyGuardianModule_Test is DeputyPauseModule_TestInit {
/// @notice Tests that setDeputyGuardianModule() succeeds when called from the safe.
function testFuzz_setDeputyGuardianModule_fromSafe_succeeds(address _newModule) external {
vm.assume(_newModule != address(0));
vm.assume(_newModule != address(deputyGuardianModule));
// Set the new DeputyGuardianModule
vm.expectEmit(address(deputyPauseModule));
emit DeputyGuardianModuleSet(IDeputyGuardianModule(_newModule));
vm.prank(address(foundationSafeInstance.safe));
deputyPauseModule.setDeputyGuardianModule(IDeputyGuardianModule(_newModule));
// Assert that the DeputyGuardianModule has been set
assertEq(address(deputyPauseModule.deputyGuardianModule()), _newModule);
}
}
/// @title DeputyPauseModule_SetDeputyGuardianModule_TestFail
/// @notice Tests that the setDeputyGuardianModule() function reverts when it should.
contract DeputyPauseModule_SetDeputyGuardianModule_TestFail is DeputyPauseModule_TestInit {
/// @notice Tests that setDeputyGuardianModule() reverts when called by an address other than the safe.
function testFuzz_setDeputyGuardianModule_notSafe_reverts(address _sender, address _newModule) external {
vm.assume(_sender != address(foundationSafeInstance.safe));
vm.assume(_newModule != address(0));
// Expect a revert when called from non-safe address
vm.prank(_sender);
vm.expectRevert(abi.encodeWithSelector(IDeputyPauseModule.DeputyPauseModule_NotFromSafe.selector));
deputyPauseModule.setDeputyGuardianModule(IDeputyGuardianModule(_newModule));
// Make sure DeputyGuardianModule has not changed
assertEq(address(deputyPauseModule.deputyGuardianModule()), address(deputyGuardianModule));
}
}
...@@ -34,6 +34,7 @@ contract Specification_Test is CommonTest { ...@@ -34,6 +34,7 @@ contract Specification_Test is CommonTest {
SYSTEMCONFIGOWNER, SYSTEMCONFIGOWNER,
GUARDIAN, GUARDIAN,
DEPUTYGUARDIAN, DEPUTYGUARDIAN,
PAUSEDEPUTY,
MESSENGER, MESSENGER,
L1PROXYADMINOWNER, L1PROXYADMINOWNER,
GOVERNANCETOKENOWNER, GOVERNANCETOKENOWNER,
...@@ -878,6 +879,24 @@ contract Specification_Test is CommonTest { ...@@ -878,6 +879,24 @@ contract Specification_Test is CommonTest {
_addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("superchainConfig()") }); _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("superchainConfig()") });
_addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("version()") }); _addSpec({ _name: "DeputyGuardianModule", _sel: _getSel("version()") });
// DeputyPauseModule
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("version()") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("foundationSafe()") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("deputyGuardianModule()") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("superchainConfig()") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("deputy()") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("usedNonces(bytes32)") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("pauseMessageTypehash()") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("deputyAuthMessageTypehash()") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("setDeputy(address,bytes)"), _auth: Role.DEPUTYGUARDIAN });
_addSpec({
_name: "DeputyPauseModule",
_sel: _getSel("setDeputyGuardianModule(address)"),
_auth: Role.DEPUTYGUARDIAN
});
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("eip712Domain()") });
_addSpec({ _name: "DeputyPauseModule", _sel: _getSel("pause(bytes32,bytes)"), _auth: Role.PAUSEDEPUTY });
// LivenessGuard // LivenessGuard
_addSpec({ _name: "LivenessGuard", _sel: _getSel("checkAfterExecution(bytes32,bool)"), _auth: Role.COUNCILSAFE }); _addSpec({ _name: "LivenessGuard", _sel: _getSel("checkAfterExecution(bytes32,bool)"), _auth: Role.COUNCILSAFE });
_addSpec({ _addSpec({
...@@ -987,8 +1006,9 @@ contract Specification_Test is CommonTest { ...@@ -987,8 +1006,9 @@ contract Specification_Test is CommonTest {
/// @notice Ensures that the DeputyGuardian is authorized to take all Guardian actions. /// @notice Ensures that the DeputyGuardian is authorized to take all Guardian actions.
function test_deputyGuardianAuth_works() public view { function test_deputyGuardianAuth_works() public view {
assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, specsByRole[Role.GUARDIAN].length); // Additional 2 roles for the DeputyPauseModule.
assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, 5); assertEq(specsByRole[Role.GUARDIAN].length, 5);
assertEq(specsByRole[Role.DEPUTYGUARDIAN].length, specsByRole[Role.GUARDIAN].length + 2);
mapping(bytes4 => Spec) storage dgmFuncSpecs = specs["DeputyGuardianModule"]; mapping(bytes4 => Spec) storage dgmFuncSpecs = specs["DeputyGuardianModule"];
mapping(bytes4 => Spec) storage superchainConfigFuncSpecs = specs["SuperchainConfig"]; mapping(bytes4 => Spec) storage superchainConfigFuncSpecs = specs["SuperchainConfig"];
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment