Skip to content
Merged
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
147 changes: 147 additions & 0 deletions src/template/AddGameTypeTemplate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// 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 {GameType, Claim, Duration} from "@eth-optimism-bedrock/src/dispute/lib/Types.sol";
import {
IOPContractsManager,
IDisputeGameFactory,
IFaultDisputeGame,
IBigStepper,
IProxyAdmin,
IDelayedWETH,
ISystemConfig
} from "@eth-optimism-bedrock/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 view virtual override returns (string[] memory) {
string[] memory storageWrites = new string[](1);
storageWrites[0] = "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 @@ -898,6 +899,34 @@ contract RegressionTest is Test {
);
}

/// @notice Expected call data and data to sign generated by manually running the AddGameType template at block 9431469 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 =
"0x82ad56cb0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000003bb6437aba031afbf9cb3538fa064161e2bf2d780000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000002441661a2e900000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000004ca9608fef202216bc21d543798ec854539baad3000000000000000000000000ff9d236641962cebf9dbfb54e7b8e91f99f10db0000000000000000000000000b39c1730dff54f25f9e45667c119e0a8fee7315600000000000000000000000000000000000000000000000000000000000000000339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f910000000000000000000000000000000000000000000000000000000000000049000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000002a300000000000000000000000000000000000000000000000000000000000049d40000000000000000000000000000000000000000000000000011c37937e08000000000000000000000000000007babe08ee4d07dba236530183b24055535a7011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000147468697320697320612073616c74206d6978657200000000000000000000000000000000000000000000000000000000000000000000000000000000";
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, 9431469, "sepolia", multisigTask, allSafes);

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

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

_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
2 changes: 2 additions & 0 deletions test/tasks/example/sep/012-add-game-type/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
FORK_BLOCK_NUMBER=9431469
NESTED_SAFE_NAME_DEPTH_1=foundation
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 = "Soneium Testnet Minato", chainId = 1946},
]

[[configs]]
chainId = 1946
saltMixer = "this is a salt mixer"
systemConfig = "0x4Ca9608Fef202216bc21D543798ec854539bAAd3"
proxyAdmin = "0xff9d236641962Cebf9DBFb54E7b8e91F99f10Db0"
delayedWETH = "0xB39c1730DFF54f25F9e45667c119e0a8FeE73156"
disputeGameType = 0
disputeAbsolutePrestate = "0x0339db503776757491b9f3038bf6f1d37b7988a2f75e823fe2656c1352ef2f91"
disputeMaxGameDepth = 73
disputeSplitDepth = 30
disputeClockExtension = 10800
disputeMaxClockDuration = 302400
initialBond = 80000000000000000
vm = "0x07babe08ee4d07dba236530183b24055535a7011"
permissioned = false

[addresses]
OPCM = "0x3bb6437aba031afbf9cb3538fa064161e2bf2d78"