Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 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
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ remappings = [
'@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts',
'@base-contracts/=lib/base-contracts/',
'@eth-optimism-bedrock/=lib/optimism/packages/contracts-bedrock/',
'@op/=lib/optimism/packages/contracts-bedrock/',
Comment thread
Wazabie marked this conversation as resolved.
Outdated
'@rari-capital/solmate/=lib/solmate',
'@eth-optimism-superchain-registry/=lib/superchain-registry/',
'@solady/=lib/optimism/packages/contracts-bedrock/lib/solady/src/',
Expand Down
151 changes: 151 additions & 0 deletions src/template/AddGameTypeTemplate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {VmSafe} from "forge-std/Vm.sol";
import {stdToml} from "forge-std/StdToml.sol";

import {OPCMTaskBase} from "src/tasks/types/OPCMTaskBase.sol";
import {SuperchainAddressRegistry} from "src/SuperchainAddressRegistry.sol";
import {Action} from "src/libraries/MultisigTypes.sol";

//import {IDeputyGuardianModule, IOptimismPortal2} from "@op/interfaces/safe/IDeputyGuardianModule.sol";
import {GameType, Claim, Duration} from "@op/src/dispute/lib/Types.sol";
import {
IOPContractsManager,
IDisputeGameFactory,
IFaultDisputeGame,
IBigStepper,
IProxyAdmin,
IDelayedWETH,
ISystemConfig
} from "@op/interfaces/L1/IOPContractsManager.sol";

/// @title AddGameTypeTemplate
/// @notice This template is used to add a game type to the DisputeGameFactory contract.
contract AddGameTypeTemplate is OPCMTaskBase {
using stdToml for string;

/// @notice Struct that extends the original AddGameInput struct and includes the chain id.
/// Notably the fields here are also in alphabetical order, this is required because of
/// the way that Foundry parses TOML data. This MUST be kept in alphabetical order. If
/// you are adding a new field, you MUST make sure it's in order. Seriously.
struct AddGameInputWithChainId {
uint256 chainId;
IDelayedWETH delayedWETH;
Claim disputeAbsolutePrestate;
Duration disputeClockExtension;
GameType disputeGameType;
Duration disputeMaxClockDuration;
uint256 disputeMaxGameDepth;
uint256 disputeSplitDepth;
uint256 initialBond;
bool permissioned;
IProxyAdmin proxyAdmin;
string saltMixer;
ISystemConfig systemConfig;
IBigStepper vm;
}

/// @notice Mapping of chain ID to configuration for the task.
mapping(uint256 => AddGameInputWithChainId) private cfg;

/// @notice Address of the OPCM contract.
address private OPCM;

/// @notice Returns string identifiers for addresses that are expected to have their storage written to.
function _taskStorageWrites() internal pure override returns (string[] memory) {
string[] memory storageWrites = new string[](3);
storageWrites[0] = "ProxyAdminOwner";
Comment thread
Wazabie marked this conversation as resolved.
Outdated
storageWrites[1] = "OPCM";
storageWrites[2] = "DisputeGameFactoryProxy";
return storageWrites;
}

/// @notice Sets up the template with implementation configurations from a TOML file.
function _templateSetup(string memory taskConfigFilePath, address rootSafe) internal override {
super._templateSetup(taskConfigFilePath, rootSafe);
string memory tomlContent = vm.readFile(taskConfigFilePath);

// Load configuration.
AddGameInputWithChainId[] memory configs =
abi.decode(tomlContent.parseRaw(".configs"), (AddGameInputWithChainId[]));
for (uint256 i = 0; i < configs.length; i++) {
cfg[configs[i].chainId] = configs[i];
}
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.

// Load OPCM address.
OPCM = tomlContent.readAddress(".addresses.OPCM");
require(OPCM != address(0), "OPCM not set");
Comment thread
Wazabie marked this conversation as resolved.
vm.label(OPCM, "OPCM");

// Set OPCM as the target for delegatecalls.
OPCM_TARGETS = new address[](1);
OPCM_TARGETS[0] = OPCM;

}

/// @notice Write the calls that you want to execute for the task.
function _build(address) internal override {
// Iterate over the chains pull out the configs.
SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains();
IOPContractsManager.AddGameInput[] memory configs = new IOPContractsManager.AddGameInput[](chains.length);
Comment thread
Wazabie marked this conversation as resolved.
for (uint256 i = 0; i < chains.length; i++) {
uint256 chainId = chains[i].chainId;
configs[i] = _toAddGameInput(cfg[chainId]);
}
Comment thread
Wazabie marked this conversation as resolved.

// Delegatecall the OPCM.addGameType() function.
(bool success,) = OPCM.delegatecall(abi.encodeCall(IOPContractsManager.addGameType, (configs)));
require(success, "AddGameType: failed to add game type");
Comment thread
Wazabie marked this conversation as resolved.
}

/// @notice This method performs all validations and assertions that verify the calls executed as expected.
function _validate(VmSafe.AccountAccess[] memory, Action[] memory, address) internal view override {
// Iterate over the chains and validate the respected game type.
SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains();
for (uint256 i = 0; i < chains.length; i++) {
uint256 chainId = chains[i].chainId;
address factoryAddress = superchainAddrRegistry.getAddress("DisputeGameFactoryProxy", chainId);
IDisputeGameFactory factory = IDisputeGameFactory(factoryAddress);
IFaultDisputeGame game = IFaultDisputeGame(address(factory.gameImpls(cfg[chainId].disputeGameType)));
Comment thread
Wazabie marked this conversation as resolved.
Comment thread
Wazabie marked this conversation as resolved.

// Assert that everything is as expected.
assertEq(address(game.weth()), address(cfg[chainId].delayedWETH));
assertEq(game.gameType().raw(), cfg[chainId].disputeGameType.raw());
assertEq(game.absolutePrestate().raw(), cfg[chainId].disputeAbsolutePrestate.raw());
assertEq(game.maxGameDepth(), cfg[chainId].disputeMaxGameDepth);
assertEq(game.splitDepth(), cfg[chainId].disputeSplitDepth);
assertEq(game.clockExtension().raw(), cfg[chainId].disputeClockExtension.raw());
assertEq(game.maxClockDuration().raw(), cfg[chainId].disputeMaxClockDuration.raw());

// Assert that the bond is set correctly.
assertEq(factory.initBonds(cfg[chainId].disputeGameType), cfg[chainId].initialBond);
}
}

/// @notice Override to return a list of addresses that should not be checked for code length.
function _getCodeExceptions() internal view virtual override returns (address[] memory) {}
Comment thread
Wazabie marked this conversation as resolved.

/// @notice Converts the AddGameInputWithChainId struct to the AddGameInput struct.
function _toAddGameInput(AddGameInputWithChainId memory _input)
internal
pure
returns (IOPContractsManager.AddGameInput memory)
{
return IOPContractsManager.AddGameInput({
saltMixer: _input.saltMixer,
systemConfig: _input.systemConfig,
proxyAdmin: _input.proxyAdmin,
delayedWETH: _input.delayedWETH,
disputeGameType: _input.disputeGameType,
disputeAbsolutePrestate: _input.disputeAbsolutePrestate,
disputeMaxGameDepth: _input.disputeMaxGameDepth,
disputeSplitDepth: _input.disputeSplitDepth,
disputeClockExtension: _input.disputeClockExtension,
disputeMaxClockDuration: _input.disputeMaxClockDuration,
initialBond: _input.initialBond,
vm: _input.vm,
permissioned: _input.permissioned
});
}
}
29 changes: 29 additions & 0 deletions test/tasks/Regression.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import {OPCMUpgradeV220toV410} from "src/template/OPCMUpgradeV220toV410.sol";
import {OPCMUpgradeV410} from "src/template/OPCMUpgradeV410.sol";
import {OPCMUpgradeSuperchainConfigV410} from "src/template/OPCMUpgradeSuperchainConfigV410.sol";
import {L1PortalExecuteL2Call} from "src/template/L1PortalExecuteL2Call.sol";
import {AddGameTypeTemplate} from "src/template/AddGameTypeTemplate.sol";

/// @notice Ensures that simulating the task consistently produces the same call data and data to sign.
/// This guarantees determinism if a bug is introduced in the task logic, the call data or data to sign
Expand Down Expand Up @@ -900,6 +901,34 @@ contract RegressionTest is Test {
);
}

/// @notice Expected call data and data to sign generated by manually running the AddGameType template at block 8383837 on Sepolia.
/// Simulate from task directory (test/tasks/example/sep/012-add-game-type) with:
/// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path $(pwd)/.env --justfile ../../../../../src/improvements/single.just simulate <foundation|council>
function testRegressionCalldataMatches_AddGameType() public {
string memory taskConfigFilePath = "test/tasks/example/sep/012-add-game-type/config.toml";
string memory expectedCallData =
"0x82ad56cb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000fbceed4de885645fbded164910e10f52febfab350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002441661a2e900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b538000000000000000000000000189abaaaa82dfc015a588a7dbad6f13b1d3485bc000000000000000000000000cdfdc692a53b4ae9f81e0aebd26107da4a71db84000000000000000000000000000000000000000000000000000000000000000003682932cec7ce0a3874b19675a6bbc923054a7b321efc7d3835187b172494b60000000000000000000000000000000000000000000000000000000000000049000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000002a300000000000000000000000000000000000000000000000000000000000049d40000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000f027f4a985560fb13324e943edf55ad6f1d15dc1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000147468697320697320612073616c74206d6978657200000000000000000000000000000000000000000000000000000000000000000000000000000000";
MultisigTask multisigTask = new AddGameTypeTemplate();
address rootSafe = address(0x1Eb2fFc903729a0F03966B917003800b145F56E2);
address foundationChildMultisig = address(0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B);
address[] memory allSafes = MultisigTaskTestHelper.getAllSafes(rootSafe, foundationChildMultisig);

(Action[] memory actions, uint256[] memory allOriginalNonces) =
_setupAndSimulate(taskConfigFilePath, 8383837, "sepolia", multisigTask, allSafes);

_assertCallDataMatches(multisigTask, actions, allSafes, allOriginalNonces, expectedCallData);

string[] memory expectedDataToSign = new string[](2);
// Foundation
expectedDataToSign[0] =
"0x190137e1f5dd3b92a004a23589b741196c8a214629d4ea3a690ec8e41ae45c689cbb195e8071f7d2fcb4120c11aec197007f1b41900d82f62741d1a58c134f350549";
// Security council
expectedDataToSign[1] =
"0x1901be081970e9fc104bd1ea27e375cd21ec7bb1eec56bfe43347c3e36c5d27b853304e459124b117bdf49183bbb2c639b7fde05b9ec9d15f554c8f1837ade2ad463";

_assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign, MULTICALL3_ADDRESS, rootSafe);
}

/// @notice Expected call data and data to sign generated by manually running the L1PortalExecuteL2CallUpgradeGovernor template at block 23197819 on mainnet.
/// Simulate from task directory (test/tasks/example/eth/014-noop-call-optimismportal/config.toml) with:
/// just --dotenv-path $(pwd)/.env --justfile ../../../../../src/improvements/justfile simulate (foundation|council)
Expand Down
1 change: 1 addition & 0 deletions test/tasks/example/sep/012-add-game-type/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FORK_BLOCK_NUMBER=8383837
24 changes: 24 additions & 0 deletions test/tasks/example/sep/012-add-game-type/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
templateName = "AddGameTypeTemplate"

l2chains = [
{name = "OP Sepolia Testnet", chainId = 11155420},
]

[[configs]]
chainId = 11155420
saltMixer = "this is a salt mixer"
systemConfig = "0x034edD2A225f7f429A63E0f1D2084B9E0A93b538"
proxyAdmin = "0x189aBAAaa82DfC015A588A7dbaD6F13b1D3485Bc"
delayedWETH = "0xcdFdC692a53B4aE9F81E0aEBd26107Da4a71dB84"
disputeGameType = 0
disputeAbsolutePrestate = "0x03682932cec7ce0a3874b19675a6bbc923054a7b321efc7d3835187b172494b6"
disputeMaxGameDepth = 73
disputeSplitDepth = 30
disputeClockExtension = 10800
disputeMaxClockDuration = 302400
initialBond = 80000000000000000
vm = "0xF027F4A985560fb13324e943edf55ad6F1d15Dc1"
permissioned = false

[addresses]
OPCM = "0xfbceed4de885645fbded164910e10f52febfab35"
24 changes: 24 additions & 0 deletions test/tasks/mock/configs/AddGameTypeTemplate.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
templateName = "AddGameTypeTemplate"

l2chains = [
{name = "OP Sepolia Testnet", chainId = 11155420},
]

[[configs]]
chainId = 11155420
saltMixer = "this is a salt mixer"
systemConfig = "0x034edD2A225f7f429A63E0f1D2084B9E0A93b538"
proxyAdmin = "0x189aBAAaa82DfC015A588A7dbaD6F13b1D3485Bc"
delayedWETH = "0xcdFdC692a53B4aE9F81E0aEBd26107Da4a71dB84"
disputeGameType = 0
disputeAbsolutePrestate = "0x03682932cec7ce0a3874b19675a6bbc923054a7b321efc7d3835187b172494b6"
disputeMaxGameDepth = 73
disputeSplitDepth = 30
disputeClockExtension = 10800
disputeMaxClockDuration = 302400
initialBond = 80000000000000000
vm = "0xF027F4A985560fb13324e943edf55ad6F1d15Dc1"
permissioned = false

[addresses]
OPCM = "0xfbceed4de885645fbded164910e10f52febfab35"