Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
7 changes: 6 additions & 1 deletion interfaces/L1/IProtocolVersions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,12 @@ interface IProtocolVersions is IProxyAdminOwnedBase, ISemver, IReinitializableBa
error ProtocolVersions_NotInitialized();
error ProtocolVersions_InsufficientNotice(uint64 timestamp);

function initialize(address _incidentResponder, uint64[] calldata _initialSchedule) external;
function initialize(
address _incidentResponder,
uint64[] calldata _initialSchedule,
uint256 _initialMinimumProtocolVersion
)
external;
function registerUpgrade(uint64 timestamp, uint256 minProtocolVersion) external returns (uint256);
function setMinimumProtocolVersion(uint256 protocolVersion) external;
function setTimestamp(uint256 id, uint64 timestamp) external;
Expand Down
8 changes: 8 additions & 0 deletions scripts/deploy/DeployConfig.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ contract DeployConfig is Script {
uint256 public operatorFeeVaultMinimumWithdrawalAmount;
uint256 public operatorFeeVaultWithdrawalNetwork;
uint256 public proofMaturityDelaySeconds;
uint256 public protocolVersionsInitialMinimumVersion;
uint256 public respectedGameType;
uint256 public sequencerFeeVaultMinimumWithdrawalAmount;
uint256 public sequencerFeeVaultWithdrawalNetwork;
Expand Down Expand Up @@ -121,13 +122,20 @@ contract DeployConfig is Script {
operatorFeeVaultMinimumWithdrawalAmount = _json.readUint("$.operatorFeeVaultMinimumWithdrawalAmount");
operatorFeeVaultWithdrawalNetwork = _json.readUint("$.operatorFeeVaultWithdrawalNetwork");
proofMaturityDelaySeconds = _json.readUintOr("$.proofMaturityDelaySeconds", 0);
_readProtocolVersionsInitialMinimumVersion(_json);
respectedGameType = _json.readUintOr("$.respectedGameType", 0);
sequencerFeeVaultMinimumWithdrawalAmount = _json.readUint("$.sequencerFeeVaultMinimumWithdrawalAmount");
sequencerFeeVaultWithdrawalNetwork = _json.readUint("$.sequencerFeeVaultWithdrawalNetwork");

_readProtocolVersionsInitialSchedule(_json);
}

function _readProtocolVersionsInitialMinimumVersion(string memory _json) internal {
uint256 minimumVersion = _json.readUintOr("$.protocolVersionsInitialMinimumVersion", 0);
require(minimumVersion <= type(uint128).max, "DeployConfig: initial minimum protocol version exceeds uint128");
protocolVersionsInitialMinimumVersion = minimumVersion;
}

/// @dev Read separately so a rerun of `read` replaces the previous schedule rather than appending
/// to it, and so an out-of-range timestamp fails loudly instead of silently truncating.
function _readProtocolVersionsInitialSchedule(string memory _json) internal {
Expand Down
14 changes: 11 additions & 3 deletions scripts/deploy/SystemDeploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -308,11 +308,16 @@ contract SystemDeploy is Script {
}),
saltMixer: "salt mixer",
gasLimit: uint64(cfg.l2GenesisBlockGasLimit()),
initialUpgradeSchedule: cfg.protocolVersionsInitialSchedule()
initialUpgradeSchedule: cfg.protocolVersionsInitialSchedule(),
initialMinimumProtocolVersion: cfg.protocolVersionsInitialMinimumVersion()
});
}

function deploy(DeployInput memory _input) public returns (DeployOutput memory output_) {
// Validate before any broadcast because a later revert cannot roll back transactions already sent by the
// script.
_assertValidOPChainInput(_input.opChainInput);

output_.superchain = _deployOrLoadSuperchain(_input);
if (_implementationsEmpty(_input.implementations)) {
output_.impls = _deployImplementations(_input.implementationsInput);
Expand Down Expand Up @@ -483,7 +488,6 @@ contract SystemDeploy is Script {
internal
returns (Types.DeployOutput memory output_, Types.Implementations memory impls_)
{
_assertValidOPChainInput(_input);
impls_ = _impls;

output_.opChainProxyAdmin = IProxyAdmin(
Expand Down Expand Up @@ -645,7 +649,8 @@ contract SystemDeploy is Script {
address(_output.protocolVersionsProxy),
_impls.protocolVersionsImpl,
abi.encodeCall(
IProtocolVersions.initialize, (_input.roles.incidentResponder, _input.initialUpgradeSchedule)
IProtocolVersions.initialize,
(_input.roles.incidentResponder, _input.initialUpgradeSchedule, _input.initialMinimumProtocolVersion)
)
);
}
Expand Down Expand Up @@ -1093,6 +1098,9 @@ contract SystemDeploy is Script {
if (_input.roles.systemConfigOwner == address(0)) revert InvalidRoleAddress("systemConfigOwner");
if (_input.roles.batcher == address(0)) revert InvalidRoleAddress("batcher");
if (_input.roles.unsafeBlockSigner == address(0)) revert InvalidRoleAddress("unsafeBlockSigner");
DeployUtils.assertValidProtocolVersionsInitialState(
_input.initialUpgradeSchedule, _input.initialMinimumProtocolVersion
);
if (Hash.unwrap(_input.startingAnchorRoot.root) == bytes32(0)) {
revert InvalidStartingAnchorRoot();
}
Expand Down
43 changes: 43 additions & 0 deletions scripts/libraries/DeployUtils.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ pragma solidity ^0.8.0;
// Scripts
import { Vm } from "lib/forge-std/src/Vm.sol";

// Interfaces
import { IProtocolVersions } from "interfaces/L1/IProtocolVersions.sol";

// Libraries
import { LibString } from "lib/solady/src/utils/LibString.sol";
import { Bytes } from "src/libraries/Bytes.sol";
Expand All @@ -12,6 +15,10 @@ library DeployUtils {
Vm internal constant vm = Vm(address(uint160(uint256(keccak256("hevm cheat code")))));

bytes32 internal constant DEFAULT_SALT = keccak256("op-stack-contract-impls-salt-v0");
/// @dev Must match ProtocolVersions.MIN_NOTICE.
uint64 internal constant PROTOCOL_VERSIONS_MIN_NOTICE = 1 hours;
/// @dev Reserves a full notice window for sequential deployment transactions to be mined before initialization.
uint64 internal constant PROTOCOL_VERSIONS_DEPLOYMENT_NOTICE_BUFFER = PROTOCOL_VERSIONS_MIN_NOTICE;

function create1(string memory _name, bytes memory _args) internal returns (address payable addr_) {
bytes memory bytecode = abi.encodePacked(vm.getCode(_name), _args);
Expand Down Expand Up @@ -91,6 +98,42 @@ library DeployUtils {
assertUniqueAddresses(_addrs);
}

/// @notice Validates imported ProtocolVersions state before a deployment script broadcasts any transactions.
function assertValidProtocolVersionsInitialState(
uint64[] memory _schedule,
uint256 _minimumProtocolVersion
)
internal
view
{
if (_minimumProtocolVersion > type(uint128).max) {
revert IProtocolVersions.ProtocolVersions_InvalidProtocolVersion();
}

uint64 currentTimestamp = uint64(block.timestamp);
uint64 minimumFutureTimestamp =
currentTimestamp + PROTOCOL_VERSIONS_MIN_NOTICE + PROTOCOL_VERSIONS_DEPLOYMENT_NOTICE_BUFFER;
uint256 previousId;
uint64 previousTimestamp;
for (uint256 id = 0; id < _schedule.length; id++) {
uint64 timestamp = _schedule[id];
if (timestamp != 0 && _minimumProtocolVersion == 0) {
revert IProtocolVersions.ProtocolVersions_InvalidProtocolVersion();
}
if (timestamp > currentTimestamp && timestamp < minimumFutureTimestamp) {
revert IProtocolVersions.ProtocolVersions_InsufficientNotice(timestamp);
}
if (timestamp == 0) continue;
if (previousTimestamp != 0 && timestamp < previousTimestamp) {
revert IProtocolVersions.ProtocolVersions_TimestampNotAfterPrevious(
id, previousId, previousTimestamp, timestamp
);
}
previousId = id;
previousTimestamp = timestamp;
}
}

/// @notice Etches a contract, labels it, and allows cheatcodes for it.
/// @param _etchTo Address of the contract to etch.
/// @param _cname The contract name (also used to label the contract). MUST be the name of both the file and the
Expand Down
3 changes: 3 additions & 0 deletions scripts/libraries/Types.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ library Types {
/// upgrade in the node's fork order, zero for unscheduled ones. Seeds
/// `ProtocolVersions` at initialization, which is the only way to enter
/// activations that are already in the past.
/// @custom:field initialMinimumProtocolVersion Packed semver required by non-zero timestamps in the initial
/// schedule.
struct DeployInput {
Roles roles;
uint32 basefeeScalar;
Expand All @@ -45,6 +47,7 @@ library Types {
string saltMixer;
uint64 gasLimit;
uint64[] initialUpgradeSchedule;
uint256 initialMinimumProtocolVersion;
}

/// @notice The full set of outputs from deploying a new OP Stack chain.
Expand Down
8 changes: 7 additions & 1 deletion scripts/multiproof/DeployDevBase.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,10 @@ abstract contract DeployDevBase is Script {
Proxy protocolVersionsProxy = new Proxy(msg.sender);
protocolVersionsProxy.upgradeToAndCall(
address(new ProtocolVersions()),
abi.encodeCall(IProtocolVersions.initialize, (address(0), cfg.protocolVersionsInitialSchedule()))
abi.encodeCall(
IProtocolVersions.initialize,
(address(0), cfg.protocolVersionsInitialSchedule(), cfg.protocolVersionsInitialMinimumVersion())
Comment thread
jackchuma marked this conversation as resolved.
)
);
protocolVersionsProxy.changeAdmin(address(proxyAdmin));

Expand Down Expand Up @@ -167,6 +170,9 @@ abstract contract DeployDevBase is Script {
function _preflight() internal virtual {
require(cfg.l2BlockTime() != 0, "l2BlockTime must be set in config");
require(cfg.l2GenesisTimestamp() != 0, "l2GenesisTimestamp must be set in config");
DeployUtils.assertValidProtocolVersionsInitialState(
cfg.protocolVersionsInitialSchedule(), cfg.protocolVersionsInitialMinimumVersion()
);
}

function _serializeExtra(string memory key) internal virtual { }
Expand Down
1 change: 1 addition & 0 deletions scripts/multiproof/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Other relevant fields:
| `multiproofGenesisOutputRoot` | Initial anchor output root |
| `multiproofGenesisBlockNumber` | Initial anchor L2 block number |
| `protocolVersionsInitialSchedule` | Hardfork activation timestamps in the node's fork order, `0` for unscheduled forks. Omit for a chain with no history; past activations cannot be added after deployment |
| `protocolVersionsInitialMinimumVersion` | Packed semver required by every non-zero initial schedule timestamp. Must be non-zero when the initial schedule contains an activation |

### Step 2: Deploy contracts

Expand Down
5 changes: 5 additions & 0 deletions snapshots/abi/ProtocolVersions.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@
"internalType": "uint64[]",
"name": "_initialSchedule",
"type": "uint64[]"
},
{
"internalType": "uint256",
"name": "_initialMinimumProtocolVersion",
"type": "uint256"
}
],
"name": "initialize",
Expand Down
4 changes: 2 additions & 2 deletions snapshots/semver-lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"sourceCodeHash": "0x811596e7486cab9ceeeb61405b9ae510d93fcbbdfa11949201a367506c53194a"
},
"src/L1/ProtocolVersions.sol:ProtocolVersions": {
"initCodeHash": "0x07398627bcea4571951b4f6fe9d0b28505cb5d896f4eb13cde870a52dfffff3e",
"sourceCodeHash": "0xac3cde345f37efbf3c3dcc481d2a5c64dfbc971f2bb1492272acdb61892684eb"
"initCodeHash": "0x58f6c7cca0659a3ea85823b67eb1b331fcfd5173007896be8a4ee8af99a4fc5a",
"sourceCodeHash": "0x0f55edce2f83f4153ebe42a50ff0509bb567182acf3dc6913ab470cc0e823a37"
},
"src/L1/SuperchainConfig.sol:SuperchainConfig": {
"initCodeHash": "0x9b1f3555b499709485d51d5d9665002c0eb1e5eb893be1fb978a30749e894858",
Expand Down
23 changes: 19 additions & 4 deletions src/L1/ProtocolVersions.sol
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable

/// @notice Thrown when an upgrade id is not registered.
error ProtocolVersions_UnknownUpgrade(uint256 id);
/// @notice Thrown when a protocol version is zero.
/// @notice Thrown when a protocol version is zero where required or exceeds 128 bits.
error ProtocolVersions_InvalidProtocolVersion();
/// @notice Thrown when modifying a timestamp whose activation has already passed.
error ProtocolVersions_ActivationAlreadyPassed(uint256 id, uint64 activationTimestamp);
Expand Down Expand Up @@ -123,28 +123,36 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable
}

/// @notice Initializes the registry by seeding the hash chain, importing any preexisting upgrade
/// schedule, and appointing the initial incidentResponder. Callable only by the
/// ProxyAdmin or its owner.
/// schedule and its minimum protocol version, and appointing the initial incidentResponder.
/// Callable only by the ProxyAdmin or its owner.
///
/// @dev `_initialSchedule` may import historical activations without MIN_NOTICE so a chain that
/// already has a hardfork history can be represented faithfully at deployment, while it is
/// still impossible for any proof game to have pinned a commitment from this registry.
/// Future activations must provide MIN_NOTICE, matching every post-initialization write path.
/// @dev Any non-zero imported timestamp requires a non-zero packed protocol version so nodes can
/// validate the schedule immediately after deployment.
///
/// @param _incidentResponder Initial incidentResponder allowed to delay activations, or address(0) to leave unset.
/// @param _initialSchedule Activation timestamps for already-known upgrades, ordered by ascending
/// upgrade id, using 0 for an upgrade that is registered but unscheduled.
/// Future timestamps must be at least MIN_NOTICE from block.timestamp.
/// Pass an empty array for a chain with no upgrade history.
/// @param _initialMinimumProtocolVersion Packed semver required by an imported activation, or 0 when
/// the initial schedule has no non-zero timestamps.
function initialize(
address _incidentResponder,
uint64[] calldata _initialSchedule
uint64[] calldata _initialSchedule,
uint256 _initialMinimumProtocolVersion
)
external
reinitializer(initVersion())
{
// Initialization transactions must come from the ProxyAdmin or its owner.
_assertOnlyProxyAdminOrProxyAdminOwner();
if (_initialMinimumProtocolVersion > type(uint128).max) {
revert ProtocolVersions_InvalidProtocolVersion();
}

// Seed the hash chain at index 0. Keeping the seed as the first array element lets
// `scheduleId` and `_refreshScheduleId` avoid an empty-registry special case, and makes a
Expand All @@ -153,6 +161,9 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable

for (uint256 id = 0; id < _initialSchedule.length; id++) {
uint64 timestamp = _initialSchedule[id];
if (timestamp != 0 && _initialMinimumProtocolVersion == 0) {
revert ProtocolVersions_InvalidProtocolVersion();
}
if (timestamp > uint64(block.timestamp) && timestamp < uint64(block.timestamp) + MIN_NOTICE) {
revert ProtocolVersions_InsufficientNotice(timestamp);
}
Expand All @@ -168,6 +179,10 @@ contract ProtocolVersions is ProxyAdminOwnedBase, Initializable, Reinitializable
// pass. With an empty import this just re-emits the seed as the current commitment.
_refreshScheduleId(0);

if (_initialMinimumProtocolVersion != 0) {
_writeMinimumProtocolVersion(_initialMinimumProtocolVersion);
}

incidentResponder = _incidentResponder;
emit IncidentResponderUpdated(address(0), _incidentResponder);
}
Expand Down
Loading
Loading