Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
123 changes: 123 additions & 0 deletions src/template/SetBatcherAndSigner.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import {VmSafe} from "forge-std/Vm.sol";
import {stdToml} from "lib/forge-std/src/StdToml.sol";

import {L2TaskBase} from "src/tasks/types/L2TaskBase.sol";
import {SuperchainAddressRegistry} from "src/SuperchainAddressRegistry.sol";
import {Action} from "src/libraries/MultisigTypes.sol";

interface ISystemConfig {
function setBatcherHash(bytes32 _batcherHash) external;
function setUnsafeBlockSigner(address _unsafeBlockSigner) external;
function batcherHash() external view returns (bytes32);
function unsafeBlockSigner() external view returns (address);
}

/// @notice Template for updating the batcher hash and unsafe block signer on SystemConfig.
/// Both calls are batched into a single Multicall3 transaction from the SystemConfig owner.
contract SetBatcherAndSigner is L2TaskBase {
using stdToml for string;

/// @notice Configuration for each chain's batcher and signer update.
struct TaskInputs {
bytes32 batcherHash;
address unsafeBlockSigner;
bool updateBatcher;
bool updateSigner;
}

/// @notice Mapping of chain ID to configuration for the task.
mapping(uint256 => TaskInputs) public cfg;

/// @notice Returns the safe address string identifier.
function safeAddressString() public pure override returns (string memory) {
return "FoundationUpgradeSafe";
}

/// @notice Returns the storage write permissions required for this task.
function _taskStorageWrites() internal pure virtual override returns (string[] memory) {
string[] memory storageWrites = new string[](2);
storageWrites[0] = "SystemConfigProxy";
storageWrites[1] = "FoundationUpgradeSafe";
Comment thread
donoso-eth marked this conversation as resolved.
Outdated
return storageWrites;
}

/// @notice Sets up the template with configuration from a TOML file.
function _templateSetup(string memory _taskConfigFilePath, address _rootSafe) internal override {
super._templateSetup(_taskConfigFilePath, _rootSafe);

string memory tomlContent = vm.readFile(_taskConfigFilePath);
SuperchainAddressRegistry.ChainInfo[] memory _chains = superchainAddrRegistry.getChains();

address batcherAddress = tomlContent.readAddress(".sequencerConfig.batcherAddress");
address unsafeBlockSigner = tomlContent.readAddress(".sequencerConfig.unsafeBlockSigner");
require(batcherAddress != address(0), "SetBatcherAndSigner: batcherAddress is zero address");
require(unsafeBlockSigner != address(0), "SetBatcherAndSigner: unsafeBlockSigner is zero address");
bytes32 batcherHash = bytes32(uint256(uint160(batcherAddress)));

for (uint256 i = 0; i < _chains.length; i++) {
uint256 chainId = _chains[i].chainId;
ISystemConfig systemConfig = ISystemConfig(superchainAddrRegistry.getAddress("SystemConfigProxy", chainId));
bool updateBatcher = batcherHash != systemConfig.batcherHash();
bool updateSigner = unsafeBlockSigner != systemConfig.unsafeBlockSigner();
require(
updateBatcher || updateSigner, "SetBatcherAndSigner: no-op (both fields already match current values)"
);
cfg[chainId] = TaskInputs({
batcherHash: batcherHash,
unsafeBlockSigner: unsafeBlockSigner,
updateBatcher: updateBatcher,
updateSigner: updateSigner
});
}
}

/// @notice Builds the batched transaction, calling only the setters for fields that actually change.
function _build(address) internal override {
SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains();
for (uint256 i = 0; i < chains.length; i++) {
uint256 chainId = chains[i].chainId;
TaskInputs memory taskInput = cfg[chainId];
address systemConfigProxy = superchainAddrRegistry.getAddress("SystemConfigProxy", chainId);
if (taskInput.updateBatcher) {
ISystemConfig(systemConfigProxy).setBatcherHash(taskInput.batcherHash);
}
if (taskInput.updateSigner) {
ISystemConfig(systemConfigProxy).setUnsafeBlockSigner(taskInput.unsafeBlockSigner);
}
}
}

/// @notice Validates that the batcher hash and unsafe block signer were updated correctly.
function _validate(VmSafe.AccountAccess[] memory, Action[] memory, address) internal view override {
SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains();
for (uint256 i = 0; i < chains.length; i++) {
uint256 chainId = chains[i].chainId;
address systemConfigProxy = superchainAddrRegistry.getAddress("SystemConfigProxy", chainId);
TaskInputs memory taskInput = cfg[chainId];
require(
ISystemConfig(systemConfigProxy).batcherHash() == taskInput.batcherHash,
"SetBatcherAndSigner: batcher hash mismatch"
);
require(
ISystemConfig(systemConfigProxy).unsafeBlockSigner() == taskInput.unsafeBlockSigner,
"SetBatcherAndSigner: unsafe block signer mismatch"
);
}
}

/// @notice The batcher and signer addresses are typically EOAs, so they won't have code.
Comment thread
donoso-eth marked this conversation as resolved.
Outdated
function _getCodeExceptions() internal view virtual override returns (address[] memory) {
SuperchainAddressRegistry.ChainInfo[] memory chains = superchainAddrRegistry.getChains();
address[] memory exceptions = new address[](chains.length * 2);
for (uint256 i = 0; i < chains.length; i++) {
uint256 chainId = chains[i].chainId;
TaskInputs memory taskInput = cfg[chainId];
exceptions[i * 2] = address(uint160(uint256(taskInput.batcherHash)));
exceptions[i * 2 + 1] = taskInput.unsafeBlockSigner;
}
return exceptions;
}
}
28 changes: 28 additions & 0 deletions test/tasks/Regression.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import {AddGameTypeTemplate} from "src/template/AddGameTypeTemplate.sol";
import {MigrateToLiveness2} from "src/template/MigrateToLiveness2.sol";
import {RevShareUpgradeAndSetup} from "src/template/RevShareUpgradeAndSetup.sol";
import {RevShareSetup} from "src/template/RevShareSetup.sol";
import {SetBatcherAndSigner} from "src/template/SetBatcherAndSigner.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 @@ -1273,4 +1274,31 @@ contract RegressionTest is Test {
rootSafe, rootSafeCalldata, expectedDataToSign, rootSafeNonce, MULTICALL3_ADDRESS
);
}

/// @notice Expected call data and data to sign generated by manually running the SetBatcherAndSigner template at block 10624099 on sepolia.
/// Simulate from task directory (test/tasks/example/sep/035-set-batcher-and-signer) with:
/// SIMULATE_WITHOUT_LEDGER=1 just --dotenv-path $(pwd)/.env --justfile ../../../../../src/justfile simulate
function testRegressionCallDataMatches_SetBatcherAndSigner() public {
string memory taskConfigFilePath = "test/tasks/example/sep/035-set-batcher-and-signer/config.toml";
// Call data generated by manually running the SetBatcherAndSigner template at block 10624099 on sepolia.
string memory expectedCallData =
"0x174dea710000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b5380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000024c9b26f610000000000000000000000001234567890abcdef1234567890abcdef1234567800000000000000000000000000000000000000000000000000000000000000000000000000000000034edd2a225f7f429a63e0f1d2084b9e0a93b538000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000002418d13918000000000000000000000000abcdef0123456789abcdef0123456789abcdef0100000000000000000000000000000000000000000000000000000000";
MultisigTask multisigTask = new SetBatcherAndSigner();
address rootSafe = address(0xDEe57160aAfCF04c34C887B5962D0a69676d3C8B); // FoundationUpgradeSafe on Sepolia
address[] memory allSafes = MultisigTaskTestHelper.getAllSafes(rootSafe);

(Action[] memory actions, uint256[] memory allOriginalNonces) =
_setupAndSimulate(taskConfigFilePath, 10624099, "sepolia", multisigTask, allSafes);

bytes memory rootSafeCalldata =
_assertCallDataMatches(multisigTask, actions, allSafes, allOriginalNonces, expectedCallData);
uint256 rootSafeNonce = allOriginalNonces[allOriginalNonces.length - 1];

string memory expectedDataToSign =
"0x190137e1f5dd3b92a004a23589b741196c8a214629d4ea3a690ec8e41ae45c689cbb801a8413c3c6a66df018408ea42aaf3d6763dae1dddaec68bd31b3833b716ab8";

_assertDataToSignSingleMultisig(
rootSafe, rootSafeCalldata, expectedDataToSign, rootSafeNonce, MULTICALL3_ADDRESS
);
}
}
1 change: 1 addition & 0 deletions test/tasks/example/sep/035-set-batcher-and-signer/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
FORK_BLOCK_NUMBER=10624099
13 changes: 13 additions & 0 deletions test/tasks/example/sep/035-set-batcher-and-signer/config.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
templateName = "SetBatcherAndSigner"

l2chains = [{name = "OP Sepolia Testnet", chainId = 11155420}]

[sequencerConfig]
batcherAddress = "0x1234567890AbcdEF1234567890aBcdef12345678"
unsafeBlockSigner = "0xAbCdEf0123456789AbCdEf0123456789AbCdEf01"

[stateOverrides]
# Override SystemConfig owner (slot 0x33) to FoundationUpgradeSafe on Sepolia
0x034edD2A225f7f429A63E0f1D2084B9E0A93b538 = [
{ key = "0x0000000000000000000000000000000000000000000000000000000000000033", value = "0x000000000000000000000000DEe57160aAfCF04c34C887B5962D0a69676d3C8B" }
]