Operators
Deploying new dispute games with OPCM

Deploying new dispute games with OPCM

This guide provides instructions on how to deploy new dispute games to an OP Stack chain using the OPCM (OP Contracts Manager). This process is particularly relevant for teams looking to upgrade their chains to support permissionless dispute games.

Prerequisites

Before you begin, ensure that:

Understanding dispute games

The OP Stack uses two types of dispute games:

  • Permissioned dispute game: Limited to specific proposer and challenger addresses
  • Permissionless dispute game: Open to anyone to propose or challenge
⚠️

In the Permissioned dispute game (PDG), the challenger role is a protocol-level permission assigned to specific addresses, allowing them to initiate or respond to disputes. This role is distinct from the op-challenger service, which is an off-chain monitoring service responsible for automatically detecting discrepancies and submitting challenges. While the op-challenger service typically operates using an address that has been assigned the challenger role, the protocol-level role itself can be independently assigned, regardless of whether the op-challenger service is in use. Refer to the OP Stack configurability spec (opens in a new tab) for more details.

All chains deployed with op-deployer only initially include the permissioned dispute game. This guide explains how to add the permissionless game.

The addGameType function

The OPCM contract contains an addGameType function that handles the deployment of new dispute games. This function:

  1. Deploys a new dispute game implementation
  2. Optionally deploys a new DelayedWETH (opens in a new tab) contract
  3. Registers the game with the DisputeGameFactory
  4. Sets the initial bond amount

1. Finding the correct OPCM instance

Each OP Contracts release has its own OPCM instance. You must use the OPCM corresponding exactly to your chain's contract version. For example, if your system uses contracts version v3.0.0, you must use the OPCM for v3.0.0.

To find the correct OPCM address:

These registry files contain mappings between OP contract versions and their corresponding OPCM addresses.

2. Preparing the addGameType Call

The addGameType function expects an array of AddGameInput structs. Here is the structure:

struct AddGameInput {
    string saltMixer;
    ISystemConfig systemConfig;
    IProxyAdmin proxyAdmin;
    IDelayedWETH delayedWETH;
    GameType disputeGameType;
    Claim disputeAbsolutePrestate;
    uint256 disputeMaxGameDepth;
    uint256 disputeSplitDepth;
    Duration disputeClockExtension;
    Duration disputeMaxClockDuration;
    uint256 initialBond;
    IBigStepper vm;
    bool permissioned;
}

Key parameters explained:

  • saltMixer: A string used to create unique contract addresses
  • systemConfig: The address of your chain's SystemConfig contract
  • proxyAdmin: The Address of your chain's ProxyAdmin contract
  • delayedWETH: The Address of the DelayedWETH contract (use zero address to deploy a new one)
  • disputeGameType: For permissionless games, use GameTypes.CANNON
  • disputeAbsolutePrestate: The absolute prestate hash for the game
  • permissioned: Set to false for a permissionless game

For a permissionless game, you'll generally want to mirror most parameters from your existing permissioned game, but set permissioned to false and use the GameType CANNON.

  1. Execute the addGameType function The following is a template for calling the addGameType function using Forge's cast:

The most recommended way is to use a script to execute this call, rather than manual execution.

# Retrieve existing values from chain for reference
# Get permissioned game implementation
PERMISSIONED_GAME=$(cast call --rpc-url $RPC_URL $DISPUTE_GAME_FACTORY "gameImpls(uint32)" $PERMISSIONED_GAME_TYPE)
 
# Retrieve parameters from existing permissioned game
ABSOLUTE_PRESTATE=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "absolutePrestate()")
MAX_GAME_DEPTH=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "maxGameDepth()")
SPLIT_DEPTH=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "splitDepth()")
CLOCK_EXTENSION=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "clockExtension()")
MAX_CLOCK_DURATION=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "maxClockDuration()")
VM=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "vm()")
ANCHOR_STATE_REGISTRY=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "anchorStateRegistry()")
L2_CHAIN_ID=$(cast call --rpc-url $RPC_URL $PERMISSIONED_GAME "l2ChainId()")
 
# Create call data for addGameType function
# Note: Set delayedWETH to 0x0 to deploy a new one
CALLDATA=$(cast calldata "addGameType((string,address,address,address,uint32,bytes32,uint256,uint256,uint64,uint64,uint256,address,bool)[])" \
"[(\
\"unique_salt_mixer\",\
$SYSTEM_CONFIG,\
$PROXY_ADMIN,\
0x0000000000000000000000000000000000000000,\
$CANNON_GAME_TYPE,\
$ABSOLUTE_PRESTATE,\
$MAX_GAME_DEPTH,\
$SPLIT_DEPTH,\
$CLOCK_EXTENSION,\
$MAX_CLOCK_DURATION,\
$INITIAL_BOND,\
$VM,\
false\
)]")
 
# Execute the transaction
cast send --rpc-url $RPC_URL --private-key $PRIVATE_KEY $OPCM_ADDRESS $CALLDATA
  1. Setting the respected game type After deploying the permissionless dispute game, you'll need to update the respectedGameType in the OptimismPortal to start using it. For detailed instructions on setting the respected game type and migrating your chain from permissioned to permissionless fault proofs, refer to the migrating to permissionless fault proofs guide.