Skip to content

Commit

Permalink
Switch to new verification framework.
Browse files Browse the repository at this point in the history
  • Loading branch information
ajsutton committed Feb 5, 2025
1 parent 1598369 commit d04c75e
Showing 1 changed file with 38 additions and 165 deletions.
203 changes: 38 additions & 165 deletions tasks/sep/030-revert-mt-cannon/NestedSignFromJson.s.sol
Original file line number Diff line number Diff line change
@@ -1,184 +1,57 @@
// 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 {Vm} from "forge-std/Vm.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 "@eth-optimism-bedrock/src/dispute/lib/Types.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"));

// DisputeGameFactoryProxy address.
DisputeGameFactory dgfProxy;

address[] extraStorageAccessAddresses;

function setUp() public {
dgfProxy = DisputeGameFactory(systemConfig.disputeGameFactory());
_precheckDisputeGameImplementation(GameType.wrap(0), 0xF3CcF0C4b51D42cFe6073F0278c19A8D1900e856);
_precheckDisputeGameImplementation(GameType.wrap(1), 0xbbDBdfe37C02439764dE0e41C906e4396B5B3914);
// INSERT NEW PRE CHECKS HERE
}

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(0x69470D6970Cd2A006b84B1d4d70179c892cFCE01) == 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");
import {Simulation} from "@base-contracts/script/universal/Simulation.sol";
import {NestedSignFromJson as OriginalNestedSignFromJson} from "script/NestedSignFromJson.s.sol";
import {DisputeGameUpgrade} from "script/verification/DisputeGameUpgrade.s.sol";
import {CouncilFoundationNestedSign} from "script/verification/CouncilFoundationNestedSign.s.sol";
import {SuperchainRegistry} from "script/verification/Verification.s.sol";

contract NestedSignFromJson is OriginalNestedSignFromJson, CouncilFoundationNestedSign, DisputeGameUpgrade {
constructor()
SuperchainRegistry("sepolia", "op", "v1.8.0-rc.4")
DisputeGameUpgrade(
0x03f89406817db1ed7fd8b31e13300444652cdb0b9c509a674de43483b2f83568, // absolutePrestate
0xF3CcF0C4b51D42cFe6073F0278c19A8D1900e856, // faultDisputeGame
0xbbDBdfe37C02439764dE0e41C906e4396B5B3914 // permissionedDisputeGame
)
{}

function setUp() public view {
checkInput();
}

function checkInput() public view {
string memory inputJson;
string memory path = "/tasks/sep/030-revert-mt-cannon/input.json";
try vm.readFile(string.concat(vm.projectRoot(), path)) returns (string memory data) {
inputJson = data;
} catch {
revert(string.concat("Failed to read ", path));
}
}

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;
address inputPermissionedDisputeGame =
stdJson.readAddress(inputJson, "$.transactions[1].contractInputsValues._impl");
address inputFaultDisputeGame = stdJson.readAddress(inputJson, "$.transactions[0].contractInputsValues._impl");
require(expPermissionedDisputeGame == inputPermissionedDisputeGame, "input-pdg");
require(expFaultDisputeGame == inputFaultDisputeGame, "input-fdg");
}

/// @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);
_checkDisputeGameImplementation(GameType.wrap(0), 0xF3CcF0C4b51D42cFe6073F0278c19A8D1900e856);
_checkDisputeGameImplementation(GameType.wrap(1), 0xbbDBdfe37C02439764dE0e41C906e4396B5B3914);
// INSERT NEW POST CHECKS HERE

checkDisputeGameUpgrade();
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 getAllowedStorageAccess() internal view override returns (address[] memory) {
return allowedStorageAccess;
}

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");
function getCodeExceptions() internal view override returns (address[] memory) {
return codeExceptions;
}
}

0 comments on commit d04c75e

Please sign in to comment.