Commit 843a6600 authored by Maurelian's avatar Maurelian Committed by GitHub

ctb: Add DeputyGuardianModule (#10201)

* ctb: Add DeputyGuardianModule

* ctb: Add Guardian actions on the Portal to deputy guardian module

* ctb: Bubble up returndata from module on errors

* ctb: move deputy guardian check to internal function

* ctb: Add snapshots

* ctb: Use encodeCall

* ctb: Fix natspec typos

* ctb: Add events to DeputyGuardianModule

* ctb: use custom errors in DeputyGuardianModule

* ctb: Update snapshots

* Reuse events from OptimismPortal

* Add test_noPortalCollisions_succeeds

* ctb: Refactor to getContractFunctionAbis with path and excludes arguments

* ctb: Import PortalErrors.sol for Unauthorized

* ctb: Fix function name and whitespace

* ctb: Fix test visibility
parent a4c47fe1
......@@ -144,13 +144,23 @@ library ForgeArtifacts {
}
/// @notice Returns the function ABIs of all L1 contracts.
function getL1ContractFunctionAbis() internal returns (Abi[] memory abis_) {
function getContractFunctionAbis(
string memory path,
string memory excludes
)
internal
returns (Abi[] memory abis_)
{
string[] memory command = new string[](3);
command[0] = Executables.bash;
command[1] = "-c";
command[2] = string.concat(
Executables.find,
" src/{L1,governance,universal/ProxyAdmin.sol} -type f -exec basename {} \\;",
" ",
path,
" -type f ",
bytes(excludes).length > 0 ? string.concat(" ! -name ", excludes, " ") : "",
"-exec basename {} \\;",
" | ",
Executables.sed,
" 's/\\.[^.]*$//'",
......@@ -164,7 +174,7 @@ library ForgeArtifacts {
for (uint256 i; i < contractNames.length; i++) {
string memory contractName = contractNames[i];
string[] memory methodIdentifiers = ForgeArtifacts.getMethodIdentifiers(contractName);
string[] memory methodIdentifiers = getMethodIdentifiers(contractName);
abis_[i].contractName = contractName;
abis_[i].entries = new AbiEntry[](methodIdentifiers.length);
for (uint256 j; j < methodIdentifiers.length; j++) {
......
......@@ -87,6 +87,10 @@
"initCodeHash": "0xd62e193d89b1661d34031227a45ce1eade9c2a89b0bd7f362f511d03cceef294",
"sourceCodeHash": "0xa304b4b556162323d69662b4dd9a1d073d55ec661494465489bb67f1e465e7b3"
},
"src/Safe/DeputyGuardianModule.sol": {
"initCodeHash": "0x8f6adc162587ac7150045c0cf4671f23e0453417a4b7006e39eb8cb58052dc58",
"sourceCodeHash": "0x8ebf09555561d475ec51c681033b8c567281f40f310ad47312b00710f6394d34"
},
"src/Safe/LivenessGuard.sol": {
"initCodeHash": "0x16ec47f0888391638814047a1735dbac849b48e256b2e20182bbb3186d950a3c",
"sourceCodeHash": "0x9633cea9b66077e222f470439fe3e9a31f3e33b4f7a5618374c44310fd234b24"
......
[
{
"inputs": [
{
"internalType": "contract Safe",
"name": "_safe",
"type": "address"
},
{
"internalType": "contract SuperchainConfig",
"name": "_superchainConfig",
"type": "address"
},
{
"internalType": "address",
"name": "_deputyGuardian",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "contract OptimismPortal2",
"name": "_portal",
"type": "address"
},
{
"internalType": "contract IDisputeGame",
"name": "_game",
"type": "address"
}
],
"name": "blacklistDisputeGame",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "deputyGuardian",
"outputs": [
{
"internalType": "address",
"name": "deputyGuardian_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "pause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "safe",
"outputs": [
{
"internalType": "contract Safe",
"name": "safe_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract OptimismPortal2",
"name": "_portal",
"type": "address"
},
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint32"
}
],
"name": "setRespectedGameType",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "superchainConfig",
"outputs": [
{
"internalType": "contract SuperchainConfig",
"name": "superchainConfig_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "unpause",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "contract IDisputeGame",
"name": "game",
"type": "address"
}
],
"name": "DisputeGameBlacklisted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "string",
"name": "identifier",
"type": "string"
}
],
"name": "Paused",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "GameType",
"name": "gameType",
"type": "uint32"
}
],
"name": "RespectedGameTypeSet",
"type": "event"
},
{
"anonymous": false,
"inputs": [],
"name": "Unpaused",
"type": "event"
},
{
"inputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"name": "ExecutionFailed",
"type": "error"
},
{
"inputs": [],
"name": "Unauthorized",
"type": "error"
}
]
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Safe } from "safe-contracts/Safe.sol";
import { Enum } from "safe-contracts/common/Enum.sol";
import { SuperchainConfig } from "src/L1/SuperchainConfig.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
import { IDisputeGame } from "src/dispute/interfaces/IDisputeGame.sol";
import { ISemver } from "src/universal/ISemver.sol";
import { Unauthorized } from "src/libraries/PortalErrors.sol";
import "src/libraries/DisputeTypes.sol";
/// @title DeputyGuardianModule
/// @notice This module is intended to be enabled on the Security Council Safe, which will own the Guardian role in the
/// SuperchainConfig contract. The DeputyGuardianModule should allow a Deputy Guardian to administer any of the
/// actions that the Guardian is authorized to take. The security council can revoke the Deputy Guardian's
/// authorization at any time by disabling this module.
contract DeputyGuardianModule is ISemver {
/// @notice Error message for failed transaction execution
error ExecutionFailed(string);
/// @notice Emitted when the SuperchainConfig is paused
event Paused(string identifier);
/// @notice Emitted when the SuperchainConfig is unpaused
event Unpaused();
/// @notice Emitted when a DisputeGame is blacklisted
event DisputeGameBlacklisted(IDisputeGame game);
/// @notice Emitted when the respected game type is set
event RespectedGameTypeSet(GameType gameType);
/// @notice The Safe contract instance
Safe internal immutable SAFE;
/// @notice The SuperchainConfig's address
SuperchainConfig internal immutable SUPERCHAIN_CONFIG;
/// @notice The deputy guardian's address
address internal immutable DEPUTY_GUARDIAN;
/// @notice Semantic version.
/// @custom:semver 1.0.0
string public constant version = "1.0.0";
// Constructor to initialize the Safe and baseModule instances
constructor(Safe _safe, SuperchainConfig _superchainConfig, address _deputyGuardian) {
SAFE = _safe;
SUPERCHAIN_CONFIG = _superchainConfig;
DEPUTY_GUARDIAN = _deputyGuardian;
}
/// @notice Getter function for the Safe contract instance
/// @return safe_ The Safe contract instance
function safe() public view returns (Safe safe_) {
safe_ = SAFE;
}
/// @notice Getter function for the SuperchainConfig's address
/// @return superchainConfig_ The SuperchainConfig's address
function superchainConfig() public view returns (SuperchainConfig superchainConfig_) {
superchainConfig_ = SUPERCHAIN_CONFIG;
}
/// @notice Getter function for the deputy guardian's address
/// @return deputyGuardian_ The deputy guardian's address
function deputyGuardian() public view returns (address deputyGuardian_) {
deputyGuardian_ = DEPUTY_GUARDIAN;
}
/// @notice Internal function to ensure that only the deputy guardian can call certain functions.
function _onlyDeputyGuardian() internal view {
if (msg.sender != DEPUTY_GUARDIAN) {
revert Unauthorized();
}
}
/// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments
/// necessary to call `pause()` on the `SuperchainConfig` contract.
/// Only the deputy guardian can call this function.
function pause() external {
_onlyDeputyGuardian();
bytes memory data = abi.encodeCall(SUPERCHAIN_CONFIG.pause, ("Deputy Guardian"));
(bool success, bytes memory returnData) =
SAFE.execTransactionFromModuleReturnData(address(SUPERCHAIN_CONFIG), 0, data, Enum.Operation.Call);
if (!success) {
revert ExecutionFailed(string(returnData));
}
emit Paused("Deputy Guardian");
}
/// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments
/// necessary to call `unpause()` on the `SuperchainConfig` contract.
/// Only the deputy guardian can call this function.
function unpause() external {
_onlyDeputyGuardian();
bytes memory data = abi.encodeCall(SUPERCHAIN_CONFIG.unpause, ());
(bool success, bytes memory returnData) =
SAFE.execTransactionFromModuleReturnData(address(SUPERCHAIN_CONFIG), 0, data, Enum.Operation.Call);
if (!success) {
revert ExecutionFailed(string(returnData));
}
emit Unpaused();
}
/// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments
/// necessary to call `blacklistDisputeGame()` on the `OptimismPortal2` contract.
/// Only the deputy guardian can call this function.
/// @param _portal The `OptimismPortal2` contract instance.
/// @param _game The `IDisputeGame` contract instance.
function blacklistDisputeGame(OptimismPortal2 _portal, IDisputeGame _game) external {
_onlyDeputyGuardian();
bytes memory data = abi.encodeCall(OptimismPortal2.blacklistDisputeGame, (_game));
(bool success, bytes memory returnData) =
SAFE.execTransactionFromModuleReturnData(address(_portal), 0, data, Enum.Operation.Call);
if (!success) {
revert ExecutionFailed(string(returnData));
}
emit DisputeGameBlacklisted(_game);
}
/// @notice Calls the Security Council Safe's `execTransactionFromModuleReturnData()`, with the arguments
/// necessary to call `setRespectedGameType()` on the `OptimismPortal2` contract.
/// Only the deputy guardian can call this function.
/// @param _portal The `OptimismPortal2` contract instance.
/// @param _gameType The `GameType` to set as the respected game type.
function setRespectedGameType(OptimismPortal2 _portal, GameType _gameType) external {
_onlyDeputyGuardian();
bytes memory data = abi.encodeCall(OptimismPortal2.setRespectedGameType, (_gameType));
(bool success, bytes memory returnData) =
SAFE.execTransactionFromModuleReturnData(address(_portal), 0, data, Enum.Operation.Call);
if (!success) {
revert ExecutionFailed(string(returnData));
}
emit RespectedGameTypeSet(_gameType);
}
}
......@@ -2,7 +2,6 @@
pragma solidity ^0.8.15;
import { CommonTest } from "test/setup/CommonTest.sol";
import { Abi, AbiEntry } from "scripts/ForgeArtifacts.sol";
import { Executables } from "scripts/Executables.sol";
import { console2 as console } from "forge-std/console2.sol";
import { ProtocolVersions } from "src/L1/ProtocolVersions.sol";
......@@ -10,7 +9,7 @@ import { OptimismPortal } from "src/L1/OptimismPortal.sol";
import { OptimismPortal2 } from "src/L1/OptimismPortal2.sol";
import { SystemConfig } from "src/L1/SystemConfig.sol";
import { DataAvailabilityChallenge } from "src/L1/DataAvailabilityChallenge.sol";
import { ForgeArtifacts } from "scripts/ForgeArtifacts.sol";
import { ForgeArtifacts, Abi, AbiEntry } from "scripts/ForgeArtifacts.sol";
/// @title Specification_Test
/// @dev Specifies common security properties of entrypoints to L1 contracts, including authorization and
......@@ -476,7 +475,7 @@ contract Specification_Test is CommonTest {
/// @notice Ensures that there's an auth spec for every L1 contract function.
function testContractAuth() public {
Abi[] memory abis = ForgeArtifacts.getL1ContractFunctionAbis();
Abi[] memory abis = ForgeArtifacts.getContractFunctionAbis("src/{L1,governance,universal/ProxyAdmin.sol}", "");
for (uint256 i = 0; i < abis.length; i++) {
string memory contractName = abis[i].contractName;
......
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