Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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 @@ -14,6 +14,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/',
'@rari-capital/solmate/=lib/solmate',
'@eth-optimism-superchain-registry/=lib/superchain-registry/',
'@solady/=lib/optimism/packages/contracts-bedrock/lib/solady/src/',
Expand Down
144 changes: 144 additions & 0 deletions src/improvements/template/AddGameTypeTemplate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
// 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/improvements/tasks/types/OPCMTaskBase.sol";
import {SuperchainAddressRegistry} from "src/improvements/SuperchainAddressRegistry.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 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";
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) internal override {
super._templateSetup(taskConfigFilePath);
string memory tomlContent = vm.readFile(taskConfigFilePath);

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

// Load OPCM address.
OPCM = tomlContent.readAddress(".addresses.OPCM");
vm.label(OPCM, "OPCM");
}

/// @notice Write the calls that you want to execute for the task.
function _build() 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);
for (uint256 i = 0; i < chains.length; i++) {
uint256 chainId = chains[i].chainId;
configs[i] = _toAddGameInput(cfg[chainId]);
}

// Delegatecall the OPCM.addGameType() function.
(bool success,) = OPCM.delegatecall(abi.encodeCall(IOPContractsManager.addGameType, configs));
require(success, "AddGameType: failed to add game type");
}

/// @notice This method performs all validations and assertions that verify the calls executed as expected.
function _validate(VmSafe.AccountAccess[] memory, Action[] memory) 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)));

// 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 pure override returns (address[] memory) {
address[] memory codeExceptions = new address[](0);
return codeExceptions;
}

/// @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 @@ -23,6 +23,7 @@ import {TransferL2PAOFromL1} from "src/improvements/template/TransferL2PAOFromL1
import {DisableModule} from "src/improvements/template/DisableModule.sol";
import {Action} from "src/libraries/MultisigTypes.sol";
import {GnosisSafeApproveHash} from "src/improvements/template/GnosisSafeApproveHash.sol";
import {AddGameTypeTemplate} from "src/improvements/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 @@ -364,6 +365,34 @@ contract RegressionTest is Test {
_assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign);
}

/// @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";

// Call data generated by manually running the AddGameType template at block 8383837 on Sepolia.
string memory expectedCallData =
"0x82ad56cb000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000fbceed4de885645fbded164910e10f52febfab350000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002441661a2e900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b538000000000000000000000000189abaaaa82dfc015a588a7dbad6f13b1d3485bc000000000000000000000000cdfdc692a53b4ae9f81e0aebd26107da4a71db84000000000000000000000000000000000000000000000000000000000000000003682932cec7ce0a3874b19675a6bbc923054a7b321efc7d3835187b172494b60000000000000000000000000000000000000000000000000000000000000049000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000002a300000000000000000000000000000000000000000000000000000000000049d40000000000000000000000000000000000000000000000000011c37937e080000000000000000000000000000f027f4a985560fb13324e943edf55ad6f1d15dc1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000147468697320697320612073616c74206d6978657200000000000000000000000000000000000000000000000000000000000000000000000000000000";

MultisigTask multisigTask = new AddGameTypeTemplate();
address foundationChildMultisig = 0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B;
MultisigTask.Action[] memory actions =
_setupAndSimulateRun(taskConfigFilePath, 8383837, "sepolia", multisigTask, foundationChildMultisig);

_assertCallData(multisigTask, actions, expectedCallData);

// Data to sign generated by manually running the AddGameType template at block 8383837 on Sepolia.
string[] memory expectedDataToSign = new string[](2);
// Foundation
expectedDataToSign[0] =
"0x190137e1f5dd3b92a004a23589b741196c8a214629d4ea3a690ec8e41ae45c689cbb195e8071f7d2fcb4120c11aec197007f1b41900d82f62741d1a58c134f350549";
// Security council
expectedDataToSign[1] =
"0x1901be081970e9fc104bd1ea27e375cd21ec7bb1eec56bfe43347c3e36c5d27b853304e459124b117bdf49183bbb2c639b7fde05b9ec9d15f554c8f1837ade2ad463";
_assertDataToSignNestedMultisig(multisigTask, actions, expectedDataToSign);
}

/// @notice Expected call data and data to sign generated by manually running the TransferL2PAOFromL1 template at block 22447773 on mainnet.
/// Simulate from task directory (test/tasks/example/eth/008-transfer-l2pao) with:
/// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path $(pwd)/.env --justfile ../../../../../src/improvements/nested.just 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"