Skip to content

Commit

Permalink
op-contracts/v1.6.0 set game implementations (#404)
Browse files Browse the repository at this point in the history
* testing

* updating the storage setter contract to the mainnet one

* updating input bundle and commenting out a post check

* adding task to ci

* fixing validation and readme

* addressing review comments

* updating readme and validation comment

* adding predeployment address links

* updating readme with more details

* address PR comments

* uncommenting the anchor state sanity test because the chain is >7days old now

* updating input bundle

* check initial bonds

* adding note about the proposer needing to be bonded prior to execution

* updating descriptions

* fixing contract name

* updating nonces

* using seb's snippet

* updating the validation file with storage slot info

* fixing nonces

* fixing whitespace

---------

Co-authored-by: Matt Solomon <[email protected]>
Co-authored-by: inphi <[email protected]>
  • Loading branch information
3 people authored Dec 20, 2024
1 parent 019264e commit 34445d0
Show file tree
Hide file tree
Showing 6 changed files with 570 additions and 1 deletion.
10 changes: 9 additions & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@ jobs:
just simulate-council
just prepare-json
just simulate-council # simulate again to make sure the json is still valid
simulate_eth_021:
docker:
- image: << pipeline.parameters.ci_builder_image >>
Expand All @@ -228,6 +228,13 @@ jobs:
steps:
- simulate_nested:
task: "eth/base-003-holocene-fp-upgrade"

just_simulate_permissionless_fp_upgrade:
docker:
- image: << pipeline.parameters.ci_builder_image >>
steps:
- simulate_nested:
task: "/eth/ink-001-permissionless-proofs"

forge_build:
docker:
Expand Down Expand Up @@ -312,3 +319,4 @@ workflows:
- simulate_eth_021
- simulate_eth_022
- simulate_base_003
- just_simulate_permissionless_fp_upgrade
16 changes: 16 additions & 0 deletions tasks/eth/ink-001-permissionless-proofs/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
ETH_RPC_URL="https://ethereum.publicnode.com"
COUNCIL_SAFE=0xc2819DC788505Aac350142A7A707BF9D03E3Bd03
FOUNDATION_SAFE=0x847B5c174615B1B7fDF770882256e2D3E95b9D92
SAFE_NONCE=7
SAFE_NONCE_0XC2819DC788505AAC350142A7A707BF9D03E3BD03=9
SAFE_NONCE_0X847B5C174615B1B7FDF770882256E2D3E95B9D92=12
L1_CHAIN_NAME="mainnet"
L2_CHAIN_NAME="ink"
SYSTEM_CONFIG="0x62c0a111929fa32cec2f76adba54c16afb6e8364"
OWNER_SAFE="0x5a0Aae59D09fccBdDb6C6CcEB07B7279367C3d2A"
PROXY_ADMIN="0xd56045e68956fce2576e680c95a4750cf8241f79"
ANCHOR_STATE_REGISTRY="0xde744491BcF6b2DD2F32146364Ea1487D75E2509"
NEW_FAULT_DISPUTE_GAME_IMPL="0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD"
NEW_PERMISSIONED_DISPUTE_GAME_IMPL="0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963"
NEW_ANCHOR_STATE_ROOT="0x5220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a"
NEW_ANCHOR_STATE_BLOCK_NUMBER=523052
204 changes: 204 additions & 0 deletions tasks/eth/ink-001-permissionless-proofs/NestedSignFromJson.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

import {NestedSignFromJson as OriginalNestedSignFromJson} from "script/NestedSignFromJson.s.sol";
import {Simulation} from "@base-contracts/script/universal/Simulation.sol";
import {console2 as console} from "forge-std/console2.sol";
import {stdJson} from "forge-std/StdJson.sol";
import {stdToml} from "forge-std/StdToml.sol";
import {Vm, VmSafe} from "forge-std/Vm.sol";
import {GnosisSafe} from "safe-contracts/GnosisSafe.sol";
import {LibString} from "solady/utils/LibString.sol";
import {Types} from "@eth-optimism-bedrock/scripts/Types.sol";
import "@eth-optimism-bedrock/src/dispute/lib/Types.sol";
import {AnchorStateRegistry} from "@eth-optimism-bedrock/src/dispute/AnchorStateRegistry.sol";
import {DisputeGameFactory} from "@eth-optimism-bedrock/src/dispute/DisputeGameFactory.sol";
import {FaultDisputeGame} from "@eth-optimism-bedrock/src/dispute/FaultDisputeGame.sol";
import {PermissionedDisputeGame} from "@eth-optimism-bedrock/src/dispute/PermissionedDisputeGame.sol";
import {SystemConfig} from "@eth-optimism-bedrock/src/L1/SystemConfig.sol";

contract NestedSignFromJson is OriginalNestedSignFromJson {
using LibString for string;

// Chains for this task.
string l1ChainName = vm.envString("L1_CHAIN_NAME");
string l2ChainName = vm.envString("L2_CHAIN_NAME");

// Safe contract for this task.
GnosisSafe ownerSafe = GnosisSafe(payable(vm.envAddress("OWNER_SAFE")));
GnosisSafe councilSafe = GnosisSafe(payable(vm.envAddress("COUNCIL_SAFE")));
GnosisSafe foundationSafe = GnosisSafe(payable(vm.envAddress("FOUNDATION_SAFE")));

// The slot used to store the livenessGuard address in GnosisSafe.
// See https://github.com/safe-global/safe-smart-account/blob/186a21a74b327f17fc41217a927dea7064f74604/contracts/base/GuardManager.sol#L30
bytes32 livenessGuardSlot = 0x4a204f620c8c5ccdca3fd54d003badd85ba500436a431f0cbda4f558c93c34c8;

SystemConfig systemConfig = SystemConfig(vm.envAddress("SYSTEM_CONFIG"));
AnchorStateRegistry anchorStateRegistryProxy = AnchorStateRegistry(vm.envAddress("ANCHOR_STATE_REGISTRY"));
address newFaultDisputeGameImpl = vm.envAddress("NEW_FAULT_DISPUTE_GAME_IMPL");
address newPermissionedDisputeGameImpl = vm.envAddress("NEW_PERMISSIONED_DISPUTE_GAME_IMPL");
bytes32 newAnchorStateRoot = vm.envBytes32("NEW_ANCHOR_STATE_ROOT");
uint256 newAnchorStateBlockNumber = vm.envUint("NEW_ANCHOR_STATE_BLOCK_NUMBER");

uint256 initBond = 0.08 ether;

// DisputeGameFactoryProxy address.
DisputeGameFactory dgfProxy;

address[] extraStorageAccessAddresses;

function setUp() public {
require(address(anchorStateRegistryProxy.disputeGameFactory()) == address(systemConfig.disputeGameFactory()), "setup-100");
dgfProxy = DisputeGameFactory(systemConfig.disputeGameFactory());
extraStorageAccessAddresses.push(address(anchorStateRegistryProxy));
_precheckAnchorStateCopy(GameType.wrap(1), GameType.wrap(0));
_precheckDisputeGameImplementation(GameType.wrap(0), newFaultDisputeGameImpl);
_precheckDisputeGameImplementation(GameType.wrap(1), newPermissionedDisputeGameImpl);
}

function getCodeExceptions() internal view override returns (address[] memory) {
// Safe owners will appear in storage in the LivenessGuard when added, and they are allowed
// to have code AND to have no code.
address[] memory securityCouncilSafeOwners = councilSafe.getOwners();

// To make sure we probably handle all signers whether or not they have code, first we count
// the number of signers that have no code.
uint256 numberOfSafeSignersWithNoCode;
for (uint256 i = 0; i < securityCouncilSafeOwners.length; i++) {
if (securityCouncilSafeOwners[i].code.length == 0) {
numberOfSafeSignersWithNoCode++;
}
}

// Then we extract those EOA addresses into a dedicated array.
uint256 trackedSignersWithNoCode;
address[] memory safeSignersWithNoCode = new address[](numberOfSafeSignersWithNoCode);
for (uint256 i = 0; i < securityCouncilSafeOwners.length; i++) {
if (securityCouncilSafeOwners[i].code.length == 0) {
safeSignersWithNoCode[trackedSignersWithNoCode] = securityCouncilSafeOwners[i];
trackedSignersWithNoCode++;
}
}

// Here we add the standard (non Safe signer) exceptions.
address[] memory shouldHaveCodeExceptions = new address[](numberOfSafeSignersWithNoCode);
// And finally, we append the Safe signer exceptions.
for (uint256 i = 0; i < safeSignersWithNoCode.length; i++) {
shouldHaveCodeExceptions[i] = safeSignersWithNoCode[i];
}

return shouldHaveCodeExceptions;
}

// _precheckDisputeGameImplementation checks that the new game being set has the same configuration as the existing
// implementation with the exception of the absolutePrestate. This is the most common scenario where the game
// implementation is upgraded to provide an updated fault proof program that supports an upcoming hard fork.
function _precheckDisputeGameImplementation(GameType _targetGameType, address _newImpl) internal view {
console.log("pre-check new game implementations", _targetGameType.raw());

FaultDisputeGame currentImpl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_targetGameType))));
// No checks are performed if there is no prior implementation.
// When deploying the first implementation, it is recommended to implement custom checks.
if (address(currentImpl) == address(0)) {
return;
}
FaultDisputeGame faultDisputeGame = FaultDisputeGame(_newImpl);
require(address(currentImpl.vm()) == address(faultDisputeGame.vm()), "10");
require(address(currentImpl.weth()) == address(faultDisputeGame.weth()), "20");
require(address(currentImpl.anchorStateRegistry()) == address(faultDisputeGame.anchorStateRegistry()), "30");
require(currentImpl.l2ChainId() == faultDisputeGame.l2ChainId(), "40");
require(currentImpl.splitDepth() == faultDisputeGame.splitDepth(), "50");
require(currentImpl.maxGameDepth() == faultDisputeGame.maxGameDepth(), "60");
require(uint64(Duration.unwrap(currentImpl.maxClockDuration())) == uint64(Duration.unwrap(faultDisputeGame.maxClockDuration())), "70");
require(uint64(Duration.unwrap(currentImpl.clockExtension())) == uint64(Duration.unwrap(faultDisputeGame.clockExtension())), "80");

if (_targetGameType.raw() == GameTypes.PERMISSIONED_CANNON.raw()) {
PermissionedDisputeGame currentPDG = PermissionedDisputeGame(address(currentImpl));
PermissionedDisputeGame permissionedDisputeGame = PermissionedDisputeGame(address(faultDisputeGame));
require(address(currentPDG.proposer()) == address(permissionedDisputeGame.proposer()), "90");
require(address(currentPDG.challenger()) == address(permissionedDisputeGame.challenger()), "100");
}
}

function _precheckAnchorStateCopy(GameType _fromType, GameType _toType) internal view {
console.log("pre-check anchor state copy", _toType.raw());

FaultDisputeGame fromImpl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_fromType))));
// Must have existing game type implementation for the source
require(address(fromImpl) != address(0), "200");
address fromRegistry = address(fromImpl.anchorStateRegistry());
require(fromRegistry != address(0), "210");

FaultDisputeGame toImpl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_toType))));
if (address(toImpl) != address(0)) {
// If there is an existing implementation, it must use the same anchor state registry.
address toRegistry = address(toImpl.anchorStateRegistry());
require(toRegistry == fromRegistry, "210");
}
}

function getAllowedStorageAccess() internal view override returns (address[] memory allowed) {
allowed = new address[](5 + extraStorageAccessAddresses.length);
allowed[0] = address(dgfProxy);
allowed[1] = address(ownerSafe);
allowed[2] = address(councilSafe);
allowed[3] = address(foundationSafe);
address livenessGuard = address(uint160(uint256(vm.load(address(councilSafe), livenessGuardSlot))));
allowed[4] = livenessGuard;

for (uint256 i = 0; i < extraStorageAccessAddresses.length; i++) {
allowed[5 + i] = extraStorageAccessAddresses[i];
}
return allowed;
}

/// @notice Checks the correctness of the deployment
function _postCheck(Vm.AccountAccess[] memory accesses, Simulation.Payload memory) internal view override {
console.log("Running post-deploy assertions");

checkStateDiff(accesses);
_postcheckAnchorStateCopy(GameType.wrap(0), newAnchorStateRoot, newAnchorStateBlockNumber);
_postcheckHasAnchorState(GameType.wrap(1));
_checkDisputeGameImplementation(GameType.wrap(0), newFaultDisputeGameImpl);
_checkDisputeGameImplementation(GameType.wrap(1), newPermissionedDisputeGameImpl);
_checkInitBonds();

console.log("All assertions passed!");
}

function _checkDisputeGameImplementation(GameType _targetGameType, address _newImpl) internal view {
console.log("check dispute game implementations", _targetGameType.raw());

require(_newImpl == address(dgfProxy.gameImpls(_targetGameType)), "check-100");
}

function _checkInitBonds() internal view {
console.log("check the initial bonds");

require(dgfProxy.initBonds(GameType.wrap(0)) == initBond, "check-bond-100");
require(dgfProxy.initBonds(GameType.wrap(1)) == initBond, "check-bond-200");
}

function _postcheckAnchorStateCopy(GameType _gameType, bytes32 _root, uint256 _l2BlockNumber) internal view {
console.log("check anchor state value", _gameType.raw());

FaultDisputeGame impl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_gameType))));
(Hash root, uint256 rootBlockNumber) = FaultDisputeGame(address(impl)).anchorStateRegistry().anchors(_gameType);

require(root.raw() == _root, "check-200");
require(rootBlockNumber == _l2BlockNumber, "check-210");
}

// @notice Checks the anchor state for the source game type still exists after re-initialization.
// The actual anchor state may have been updated since the task was defined so just assert it exists, not that
// it has a specific value.
function _postcheckHasAnchorState(GameType _gameType) internal view {
console.log("check anchor state exists", _gameType.raw());

FaultDisputeGame impl = FaultDisputeGame(address(dgfProxy.gameImpls(GameType(_gameType))));
(Hash root, uint256 rootBlockNumber) = FaultDisputeGame(address(impl)).anchorStateRegistry().anchors(_gameType);

require(root.raw() != bytes32(0), "check-300");
require(rootBlockNumber != 0, "check-310");
}
}
33 changes: 33 additions & 0 deletions tasks/eth/ink-001-permissionless-proofs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# ProxyAdminOwner - Set Dispute Game Implementation

Status: READY TO SIGN

## Objective

This task updates the fault dispute system for ink-mainnet:

* Re-initialize `AnchorStateRegistry` 0xde744491BcF6b2DD2F32146364Ea1487D75E2509 with the anchor state for game types 0 set to 0x5220f9c5ebf08e84847d542576a67a3077b6fa496235d93c557d5bd5286b431a, 523052
* Set implementation for game type 0 to 0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD in `DisputeGameFactory` 0x10d7B35078d3baabB96Dd45a9143B94be65b12CD: `setImplementation(0, 0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD)`
* Set implementation for game type 1 to 0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963 in `DisputeGameFactory` 0x10d7B35078d3baabB96Dd45a9143B94be65b12CD: `setImplementation(1, 0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963)`
* Sets the initial bonds in the `DisputeGameFactory` for game type 0 and 1 to 0.08 ETH. **Important: the proposer will now need to be bonded for the permissioned games.**

## Pre-deployments

- `FaultDisputeGame` - [0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD](https://etherscan.io/address/0x6A8eFcba5642EB15D743CBB29545BdC44D5Ad8cD)
- `PermissionedDisputeGame` - [0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963](https://etherscan.io/address/0x0A780bE3eB21117b1bBCD74cf5D7624A3a482963)

## Simulation

Please see the "Simulating and Verifying the Transaction" instructions in [NESTED.md](../../../NESTED.md).

When simulating, ensure the logs say `Using script /your/path/to/superchain-ops/tasks/ink-001-permissionless-proofs/NestedSignFromJson.s.sol`. This ensures all safety checks are run. If the default `SignFromJson.s.sol` script is shown (without the full path), something is wrong and the safety checks will not run.

Do NOT yet proceed to the "Execute the Transaction" section.

## Signing and execution

Please see the signing and execution instructions in [NESTED.md](../../../NESTED.md).

### State Validations

Please see the instructions for [validation](./VALIDATION.md).
Loading

0 comments on commit 34445d0

Please sign in to comment.