Commit 358a343a authored by smartcontracts's avatar smartcontracts Committed by GitHub

fix(ct): DeputyPauseModule audit fixes (#13594)

Includes a number of low/informational fixes from the audits of
the DeputyPauseModule.

- Additional validation on the DeputyGuardianModule.
- Small additional comments.
- Proper encoding of EIP-712 signature digest.
parent 58e3da1f
...@@ -8,6 +8,7 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; ...@@ -8,6 +8,7 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
interface IDeputyPauseModule is ISemver { interface IDeputyPauseModule is ISemver {
error DeputyPauseModule_InvalidDeputy(); error DeputyPauseModule_InvalidDeputy();
error DeputyPauseModule_InvalidDeputyGuardianModule();
error DeputyPauseModule_ExecutionFailed(string); error DeputyPauseModule_ExecutionFailed(string);
error DeputyPauseModule_SuperchainNotPaused(); error DeputyPauseModule_SuperchainNotPaused();
error DeputyPauseModule_Unauthorized(); error DeputyPauseModule_Unauthorized();
......
...@@ -299,6 +299,11 @@ ...@@ -299,6 +299,11 @@
"name": "DeputyPauseModule_InvalidDeputy", "name": "DeputyPauseModule_InvalidDeputy",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "DeputyPauseModule_InvalidDeputyGuardianModule",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "DeputyPauseModule_NonceAlreadyUsed", "name": "DeputyPauseModule_NonceAlreadyUsed",
......
...@@ -184,8 +184,8 @@ ...@@ -184,8 +184,8 @@
"sourceCodeHash": "0x17236a91c4171ae9525eae0e59fa65bb2dc320d62677cfc7d7eb942f182619fb" "sourceCodeHash": "0x17236a91c4171ae9525eae0e59fa65bb2dc320d62677cfc7d7eb942f182619fb"
}, },
"src/safe/DeputyPauseModule.sol": { "src/safe/DeputyPauseModule.sol": {
"initCodeHash": "0x55f0b6c1a102114b8c30157e7295da7058ba8d1b93f7d4070028286968611279", "initCodeHash": "0xa3b7bf0c93b41f39ebc18a81322b90127a633d684ae9f86c2f2a1c48fe7f1372",
"sourceCodeHash": "0xec92b5ccbd3ee337897464546ccb8306fca245bbb6ce4b0941d86bc82e0f4cfe" "sourceCodeHash": "0xfbe7f5733aa57de7557605e40e03e9708fbdec872bc8739c551905c4b1e1b61b"
}, },
"src/safe/LivenessGuard.sol": { "src/safe/LivenessGuard.sol": {
"initCodeHash": "0xc8e29e8b12f423c8cd229a38bc731240dd815d96f1b0ab96c71494dde63f6a81", "initCodeHash": "0xc8e29e8b12f423c8cd229a38bc731240dd815d96f1b0ab96c71494dde63f6a81",
......
...@@ -22,9 +22,12 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol"; ...@@ -22,9 +22,12 @@ import { ISuperchainConfig } from "interfaces/L1/ISuperchainConfig.sol";
/// Superchain-wide pause functionality. Significantly simplifies the process of triggering /// Superchain-wide pause functionality. Significantly simplifies the process of triggering
/// a Superchain-wide pause without changing the existing security model. /// a Superchain-wide pause without changing the existing security model.
contract DeputyPauseModule is ISemver, EIP712 { contract DeputyPauseModule is ISemver, EIP712 {
/// @notice Error message for invalid deputy. /// @notice Error message for deputy being invalid.
error DeputyPauseModule_InvalidDeputy(); error DeputyPauseModule_InvalidDeputy();
/// @notice Error message for the DeputyGuardianModule being invalid.
error DeputyPauseModule_InvalidDeputyGuardianModule();
/// @notice Error message for unauthorized calls. /// @notice Error message for unauthorized calls.
error DeputyPauseModule_Unauthorized(); error DeputyPauseModule_Unauthorized();
...@@ -83,8 +86,8 @@ contract DeputyPauseModule is ISemver, EIP712 { ...@@ -83,8 +86,8 @@ contract DeputyPauseModule is ISemver, EIP712 {
mapping(bytes32 => bool) public usedNonces; mapping(bytes32 => bool) public usedNonces;
/// @notice Semantic version. /// @notice Semantic version.
/// @custom:semver 1.0.0-beta.1 /// @custom:semver 1.0.0-beta.2
string public constant version = "1.0.0-beta.1"; string public constant version = "1.0.0-beta.2";
/// @param _foundationSafe Address of the Foundation Safe. /// @param _foundationSafe Address of the Foundation Safe.
/// @param _deputyGuardianModule Address of the DeputyGuardianModule used by the SC Safe. /// @param _deputyGuardianModule Address of the DeputyGuardianModule used by the SC Safe.
...@@ -101,7 +104,7 @@ contract DeputyPauseModule is ISemver, EIP712 { ...@@ -101,7 +104,7 @@ contract DeputyPauseModule is ISemver, EIP712 {
EIP712("DeputyPauseModule", "1") EIP712("DeputyPauseModule", "1")
{ {
_setDeputy(_deputy, _deputySignature); _setDeputy(_deputy, _deputySignature);
deputyGuardianModule = _deputyGuardianModule; _setDeputyGuardianModule(_deputyGuardianModule);
FOUNDATION_SAFE = _foundationSafe; FOUNDATION_SAFE = _foundationSafe;
SUPERCHAIN_CONFIG = _superchainConfig; SUPERCHAIN_CONFIG = _superchainConfig;
} }
...@@ -146,11 +149,13 @@ contract DeputyPauseModule is ISemver, EIP712 { ...@@ -146,11 +149,13 @@ contract DeputyPauseModule is ISemver, EIP712 {
/// @notice Sets the DeputyGuardianModule. /// @notice Sets the DeputyGuardianModule.
/// @param _deputyGuardianModule DeputyGuardianModule address. /// @param _deputyGuardianModule DeputyGuardianModule address.
function setDeputyGuardianModule(IDeputyGuardianModule _deputyGuardianModule) external { function setDeputyGuardianModule(IDeputyGuardianModule _deputyGuardianModule) external {
// Can only be called by the Foundation Safe itself.
if (msg.sender != address(FOUNDATION_SAFE)) { if (msg.sender != address(FOUNDATION_SAFE)) {
revert DeputyPauseModule_NotFromSafe(); revert DeputyPauseModule_NotFromSafe();
} }
deputyGuardianModule = _deputyGuardianModule;
emit DeputyGuardianModuleSet(_deputyGuardianModule); // Set the DeputyGuardianModule.
_setDeputyGuardianModule(_deputyGuardianModule);
} }
/// @notice Calls the Foundation Safe's `execTransactionFromModuleReturnData()` function with /// @notice Calls the Foundation Safe's `execTransactionFromModuleReturnData()` function with
...@@ -161,7 +166,7 @@ contract DeputyPauseModule is ISemver, EIP712 { ...@@ -161,7 +166,7 @@ contract DeputyPauseModule is ISemver, EIP712 {
/// @param _signature ECDSA signature. /// @param _signature ECDSA signature.
function pause(bytes32 _nonce, bytes memory _signature) external { function pause(bytes32 _nonce, bytes memory _signature) external {
// Verify the signature. // Verify the signature.
bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(PAUSE_MESSAGE_TYPEHASH, PauseMessage(_nonce)))); bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(PAUSE_MESSAGE_TYPEHASH, _nonce)));
if (ECDSA.recover(digest, _signature) != deputy) { if (ECDSA.recover(digest, _signature) != deputy) {
revert DeputyPauseModule_Unauthorized(); revert DeputyPauseModule_Unauthorized();
} }
...@@ -175,6 +180,7 @@ contract DeputyPauseModule is ISemver, EIP712 { ...@@ -175,6 +180,7 @@ contract DeputyPauseModule is ISemver, EIP712 {
usedNonces[_nonce] = true; usedNonces[_nonce] = true;
// Attempt to trigger the call. // Attempt to trigger the call.
// Will succeed if the DeputyGuardianModule has no code.
(bool success, bytes memory returnData) = FOUNDATION_SAFE.execTransactionFromModuleReturnData( (bool success, bytes memory returnData) = FOUNDATION_SAFE.execTransactionFromModuleReturnData(
address(deputyGuardianModule), 0, abi.encodeCall(IDeputyGuardianModule.pause, ()), Enum.Operation.Call address(deputyGuardianModule), 0, abi.encodeCall(IDeputyGuardianModule.pause, ()), Enum.Operation.Call
); );
...@@ -198,8 +204,7 @@ contract DeputyPauseModule is ISemver, EIP712 { ...@@ -198,8 +204,7 @@ contract DeputyPauseModule is ISemver, EIP712 {
/// @param _deputySignature Deputy signature. /// @param _deputySignature Deputy signature.
function _setDeputy(address _deputy, bytes memory _deputySignature) internal { function _setDeputy(address _deputy, bytes memory _deputySignature) internal {
// Check that the deputy signature is valid. // Check that the deputy signature is valid.
bytes32 digest = bytes32 digest = _hashTypedDataV4(keccak256(abi.encode(DEPUTY_AUTH_MESSAGE_TYPEHASH, _deputy)));
_hashTypedDataV4(keccak256(abi.encode(DEPUTY_AUTH_MESSAGE_TYPEHASH, DeputyAuthMessage(_deputy))));
if (ECDSA.recover(digest, _deputySignature) != _deputy) { if (ECDSA.recover(digest, _deputySignature) != _deputy) {
revert DeputyPauseModule_InvalidDeputy(); revert DeputyPauseModule_InvalidDeputy();
} }
...@@ -210,4 +215,26 @@ contract DeputyPauseModule is ISemver, EIP712 { ...@@ -210,4 +215,26 @@ contract DeputyPauseModule is ISemver, EIP712 {
// Emit the DeputySet event. // Emit the DeputySet event.
emit DeputySet(_deputy); emit DeputySet(_deputy);
} }
/// @notice Internal function to set the DeputyGuardianModule.
/// @param _deputyGuardianModule DeputyGuardianModule address.
function _setDeputyGuardianModule(IDeputyGuardianModule _deputyGuardianModule) internal {
// Cannot set the DeputyGuardianModule to be the Foundation Safe. This prevents the
// Foundation Safe from unexpectedly triggering itself if there's some important function
// that happens to have the "pause()" selector.
if (address(_deputyGuardianModule) == address(FOUNDATION_SAFE)) {
revert DeputyPauseModule_InvalidDeputyGuardianModule();
}
// Make sure that the DeputyGuardianModule actually has code.
if (address(_deputyGuardianModule).code.length == 0) {
revert DeputyPauseModule_InvalidDeputyGuardianModule();
}
// Set the DeputyGuardianModule.
deputyGuardianModule = _deputyGuardianModule;
// Emit the DeputyGuardianModuleSet event.
emit DeputyGuardianModuleSet(_deputyGuardianModule);
}
} }
...@@ -646,6 +646,14 @@ contract DeputyPauseModule_SetDeputyGuardianModule_Test is DeputyPauseModule_Tes ...@@ -646,6 +646,14 @@ contract DeputyPauseModule_SetDeputyGuardianModule_Test is DeputyPauseModule_Tes
function testFuzz_setDeputyGuardianModule_fromSafe_succeeds(address _newModule) external { function testFuzz_setDeputyGuardianModule_fromSafe_succeeds(address _newModule) external {
vm.assume(_newModule != address(0)); vm.assume(_newModule != address(0));
vm.assume(_newModule != address(deputyGuardianModule)); vm.assume(_newModule != address(deputyGuardianModule));
vm.assume(_newModule != address(foundationSafeInstance.safe));
assumeNotPrecompile(_newModule);
// Write code to the module address if it has no code.
// vm.assume would throw out too many inputs.
if (_newModule.code.length == 0) {
vm.etch(_newModule, hex"FF");
}
// Set the new DeputyGuardianModule // Set the new DeputyGuardianModule
vm.expectEmit(address(deputyPauseModule)); vm.expectEmit(address(deputyPauseModule));
...@@ -674,4 +682,22 @@ contract DeputyPauseModule_SetDeputyGuardianModule_TestFail is DeputyPauseModule ...@@ -674,4 +682,22 @@ contract DeputyPauseModule_SetDeputyGuardianModule_TestFail is DeputyPauseModule
// Make sure DeputyGuardianModule has not changed // Make sure DeputyGuardianModule has not changed
assertEq(address(deputyPauseModule.deputyGuardianModule()), address(deputyGuardianModule)); assertEq(address(deputyPauseModule.deputyGuardianModule()), address(deputyGuardianModule));
} }
/// @notice Tests that setDeputyGuardianModule() reverts when the DeputyGuardianModule has no code.
/// @param _newModule The new DeputyGuardianModule.
function testFuzz_setDeputyGuardianModule_hasNoCode_reverts(address _newModule) external {
vm.assume(_newModule.code.length == 0);
// Expect a revert.
vm.expectRevert(IDeputyPauseModule.DeputyPauseModule_InvalidDeputyGuardianModule.selector);
vm.prank(address(foundationSafeInstance.safe));
deputyPauseModule.setDeputyGuardianModule(IDeputyGuardianModule(_newModule));
}
/// @notice Tests that setDeputyGuardianModule() reverts when the DeputyGuardianModule is the Foundation Safe.
function test_setDeputyGuardianModule_isFoundationSafe_reverts() external {
vm.expectRevert(IDeputyPauseModule.DeputyPauseModule_InvalidDeputyGuardianModule.selector);
vm.prank(address(foundationSafeInstance.safe));
deputyPauseModule.setDeputyGuardianModule(IDeputyGuardianModule(address(foundationSafeInstance.safe)));
}
} }
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