-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Switch to new verification framework.
- Loading branch information
Showing
1 changed file
with
38 additions
and
165 deletions.
There are no files selected for viewing
203 changes: 38 additions & 165 deletions
203
tasks/sep/030-revert-mt-cannon/NestedSignFromJson.s.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} |