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
2 changes: 1 addition & 1 deletion script/20260706-deploy-tokens-ethereum.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,7 @@ contract DeployTokensEthereum is Script {
// pins, NOT `LibProdDeployCurrent`: Ethereum's bootstrap shipped the
// audited 0.1.1 set, and the 0.1.1 unified deployer is what wires
// new vault proxies onto the chain's IN-USE production beacons (the
// 0.1.1 set pinned via `LibProdBeaconsEthereum` /
// 0.1.1 set pinned via `LibProdBeacons0_1_1` /
// `LibBeaconInvariants`). The current tag's deployer is a different
// Zoltu address that (a) is not deployed on Ethereum and (b) would
// wire tokens onto a different, unadopted beacon set even if it were.
Expand Down
6 changes: 3 additions & 3 deletions script/20260716-migrate-beacon-owners-ethereum.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ pragma solidity =0.8.25;
import {Script} from "forge-std-1.16.1/src/Script.sol";
import {console2} from "forge-std-1.16.1/src/console2.sol";
import {Ownable} from "@openzeppelin-contracts-5.6.1/access/Ownable.sol";
import {LibProdBeaconsEthereum} from "../src/lib/LibProdBeaconsEthereum.sol";
import {LibProdBeacons0_1_1} from "../src/lib/LibProdBeacons0_1_1.sol";
import {LibProdDeployV1} from "../src/lib/LibProdDeployV1.sol";
import {LibSafeInvariants} from "../src/lib/LibSafeInvariants.sol";
import {LibBeaconInvariants} from "../src/lib/LibBeaconInvariants.sol";
Expand Down Expand Up @@ -39,8 +39,8 @@ contract MigrateBeaconOwnersEthereum is Script {
function run() external {
address safe = LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_ETHEREUM;
require(safe != address(0), "Ethereum token-owner Safe not pinned");
address[3] memory beaconList = LibProdBeaconsEthereum.beacons();
address[3] memory implList = LibProdBeaconsEthereum.implementations();
address[3] memory beaconList = LibProdBeacons0_1_1.beacons();
address[3] memory implList = LibProdBeacons0_1_1.implementations();

// Pre-flight: every beacon is deployed, is the OZ UpgradeableBeacon,
// is still owned by the deploy EOA, and points at its pinned impl.
Expand Down
77 changes: 77 additions & 0 deletions script/20260722-migrate-beacon-owners-hyperevm.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Script} from "forge-std-1.16.1/src/Script.sol";
import {console2} from "forge-std-1.16.1/src/console2.sol";
import {Ownable} from "@openzeppelin-contracts-5.6.1/access/Ownable.sol";
import {LibProdBeacons0_1_1} from "../src/lib/LibProdBeacons0_1_1.sol";
import {LibProdDeployV1} from "../src/lib/LibProdDeployV1.sol";
import {LibSafeInvariants} from "../src/lib/LibSafeInvariants.sol";
import {LibBeaconInvariants} from "../src/lib/LibBeaconInvariants.sol";

/// @title MigrateBeaconOwnersHyperEvm
/// @notice **EXECUTED 2026-07-24** (broadcast from the deploy EOA's CLI;
/// all three beacons verified Safe-owned with implementations unchanged).
/// Transfers ownership of the three ST0x production
/// beacons on **HyperEVM** (chain id 999) from the deploy EOA
/// (`LibProdDeployV1.BEACON_INITIAL_OWNER`, rainlang.eth) to the HyperEVM
/// token-owner Safe (`LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_HYPEREVM`).
/// The HyperEVM leg of the same migration executed for Base (#253) and
/// Ethereum (`20260716-migrate-beacon-owners-ethereum`) — HyperEVM
/// bootstraps at 0.1.1, so its beacons are the SAME deterministic addresses
/// as Ethereum's (`LibProdBeacons0_1_1`).
///
/// @dev This is a **deploy-EOA broadcast**, not a Safe artifact: the beacons
/// come up EOA-owned from the beacon-set deployer constructors, so the
/// migration is the EOA calling `transferOwnership`. Broadcast as the EOA:
///
/// forge script script/20260722-migrate-beacon-owners-hyperevm.s.sol \
/// --rpc-url hyperevm --legacy --broadcast --private-key <EOA key>
///
/// (`--legacy`: HyperEVM's RPC rejects forge's EIP-1559 `eth_feeHistory`
/// fee-estimation ranges.)
///
/// Ordering (RAI-1511): run AFTER the 0.1.1 impl suites are deployed on
/// HyperEVM (the beacons don't exist before) and AFTER the HyperEVM Safe
/// pin hydrates, and BEFORE the token deploy — the token-deploy pre-flight
/// hard-gates on `assertProdBeaconsOwnedByChainSafe`. The beacons back no
/// vaults yet at that point, so this is pure ownership hand-off with no
/// live-vault risk.
contract MigrateBeaconOwnersHyperEvm is Script {
/// @notice Pre-flight every beacon against the EOA-owned state, broadcast
/// the three `transferOwnership` calls to the HyperEVM Safe, then
/// re-assert every beacon against the Safe-owned state. Implementations
/// are asserted unchanged across the transfer.
function run() external {
address safe = LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_HYPEREVM;
require(safe != address(0), "HyperEVM token-owner Safe not pinned");
require(block.chainid == LibSafeInvariants.HYPEREVM_CHAIN_ID, "not HyperEVM - wrong --rpc-url");
address[3] memory beaconList = LibProdBeacons0_1_1.beacons();
address[3] memory implList = LibProdBeacons0_1_1.implementations();

// Pre-flight: every beacon is deployed, is the OZ UpgradeableBeacon,
// is still owned by the deploy EOA, and points at its pinned impl.
// Reverts with the relevant typed error on the first drift, before any
// broadcast happens.
for (uint256 i = 0; i < beaconList.length; i++) {
LibBeaconInvariants.assertBeaconInvariants(beaconList[i], LibProdDeployV1.BEACON_INITIAL_OWNER, implList[i]);
}

// Broadcast the ownership transfers from the EOA — three separate
// transactions, one per beacon.
vm.startBroadcast();
for (uint256 i = 0; i < beaconList.length; i++) {
Ownable(beaconList[i]).transferOwnership(safe);
}
vm.stopBroadcast();

// Post-state: every beacon is now Safe-owned, implementations
// unchanged.
for (uint256 i = 0; i < beaconList.length; i++) {
LibBeaconInvariants.assertBeaconInvariants(beaconList[i], safe, implList[i]);
}

console2.log("Transferred ownership of 3 HyperEVM beacons to:", vm.toString(safe));
}
}
11 changes: 8 additions & 3 deletions src/lib/LibBeaconInvariants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ pragma solidity ^0.8.25;

import {IBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/IBeacon.sol";
import {LibProdBeaconsBase} from "./LibProdBeaconsBase.sol";
import {LibProdBeaconsEthereum} from "./LibProdBeaconsEthereum.sol";
import {LibProdBeacons0_1_1} from "./LibProdBeacons0_1_1.sol";
import {LibSafeInvariants} from "./LibSafeInvariants.sol";

/// @notice Minimal `Ownable`-like surface used to read a beacon's owner.
Expand Down Expand Up @@ -184,7 +184,7 @@ library LibBeaconInvariants {
/// chain's production tokens point at them — only the implementations they
/// serve are upgraded — so "which beacons is production running on" is
/// per-chain pinned state. Each chain's set lives in its own lib
/// (`LibProdBeaconsBase` / `LibProdBeaconsEthereum`, same shape and index
/// (`LibProdBeaconsBase` / `LibProdBeacons0_1_1`, same shape and index
/// order); this map only dispatches by chain id.
/// @param chainId The active chain id (`block.chainid`).
/// @return The chain's three in-use beacon addresses.
Expand All @@ -193,7 +193,12 @@ library LibBeaconInvariants {
return LibProdBeaconsBase.beacons();
}
if (chainId == LibSafeInvariants.ETHEREUM_CHAIN_ID) {
return LibProdBeaconsEthereum.beacons();
return LibProdBeacons0_1_1.beacons();
}
if (chainId == LibSafeInvariants.HYPEREVM_CHAIN_ID) {
// HyperEVM bootstraps at 0.1.1 too — the deterministic beacon
// set resolves to the SAME addresses as Ethereum's.
return LibProdBeacons0_1_1.beacons();
}
revert UnsupportedChainForProdBeacons(chainId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ pragma solidity ^0.8.25;
import {IST0xVaultBeaconSet} from "../interface/IST0xVaultBeaconSet.sol";
import {LibProdDeployV4} from "../generated/LibProdDeployV4.sol";

/// @title LibProdBeaconsEthereum
/// @notice The three ST0x production beacons on **Ethereum mainnet** and the
/// implementations they point at — every address traced to the generated
/// `0_1_1` pins rather than re-pasted as fresh literals.
/// @dev Ethereum bootstrapped fresh at the **0.1.1** release, so its beacons
/// and impls are the `0_1_1` deployment. Two principles keep this lib free of
/// @title LibProdBeacons0_1_1
/// @notice The three ST0x production beacons of the deterministic **0.1.1**
/// deployment and the implementations they point at — every address traced
/// to the generated `0_1_1` pins rather than re-pasted as fresh literals.
/// The whole set is Zoltu-deterministic, so these are the SAME addresses on
/// every chain that bootstraps at 0.1.1 (Ethereum mainnet; HyperEVM per
/// RAI-1511).
/// @dev A 0.1.1-bootstrap chain's beacons and impls are the `0_1_1`
/// deployment. Two principles keep this lib free of
/// pasted addresses:
///
/// 1. **Implementations are chain-agnostic.** They are deployed
Expand All @@ -36,7 +39,10 @@ import {LibProdDeployV4} from "../generated/LibProdDeployV4.sol";
/// Ethereum migration (`20260716-migrate-beacon-owners-ethereum`) transfers
/// them to `LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_ETHEREUM`, mirroring what
/// `MigrateBeaconOwners` already did for Base's beacons.
library LibProdBeaconsEthereum {
// The version-suffixed name mirrors the generated `0_1_1` pin naming that
// this lib exists to trace; CapWords would obscure the version.
// slither-disable-next-line naming-convention
library LibProdBeacons0_1_1 {
/// @notice The three production beacons, in a fixed order (receipt,
/// receipt vault, wrapped token vault) — index-aligned with
/// `implementations()`. The receipt / receipt-vault beacons are read from
Expand Down
6 changes: 3 additions & 3 deletions src/lib/LibProdBeaconsBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {LibProdDeployV4} from "../generated/LibProdDeployV4.sol";
/// @title LibProdBeaconsBase
/// @notice The three ST0x production beacons on **Base** and the
/// implementations they point at — the Base counterpart of
/// `LibProdBeaconsEthereum`, same shape and index order so per-chain
/// `LibProdBeacons0_1_1`, same shape and index order so per-chain
/// consumers dispatch to one lib per chain instead of hand-assembling
/// either side.
/// @dev Base's production tokens run on the **V1-generation** beacon
Expand All @@ -27,7 +27,7 @@ import {LibProdDeployV4} from "../generated/LibProdDeployV4.sol";
library LibProdBeaconsBase {
/// @notice The three production beacons, in a fixed order (receipt,
/// receipt vault, wrapped token vault) — index-aligned with
/// `implementations()` and with `LibProdBeaconsEthereum.beacons()`.
/// `implementations()` and with `LibProdBeacons0_1_1.beacons()`.
/// @return The three Base beacon addresses.
function beacons() internal pure returns (address[3] memory) {
return [
Expand All @@ -39,7 +39,7 @@ library LibProdBeaconsBase {

/// @notice The implementation each beacon points at, index-aligned with
/// `beacons()`. Referenced from the generated `0_1_1` impl pins — the
/// same deterministic addresses `LibProdBeaconsEthereum.implementations()`
/// same deterministic addresses `LibProdBeacons0_1_1.implementations()`
/// resolves, because implementation parity across chains is the goal.
/// @return The three implementation addresses.
function implementations() internal pure returns (address[3] memory) {
Expand Down
6 changes: 3 additions & 3 deletions test/src/concrete/deploy/EthereumBeaconOwnership.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pragma solidity =0.8.25;

import {Test} from "forge-std-1.16.1/src/Test.sol";
import {LibProdBeaconsEthereum} from "../../../../src/lib/LibProdBeaconsEthereum.sol";
import {LibProdBeacons0_1_1} from "../../../../src/lib/LibProdBeacons0_1_1.sol";
import {LibProdDeployV1} from "../../../../src/lib/LibProdDeployV1.sol";
import {LibSafeInvariants} from "../../../../src/lib/LibSafeInvariants.sol";
import {LibBeaconInvariants} from "../../../../src/lib/LibBeaconInvariants.sol";
Expand Down Expand Up @@ -31,8 +31,8 @@ contract EthereumBeaconOwnershipTest is Test {
}

vm.createSelectFork(LibStoxDeployNetworks.ETHEREUM);
address[3] memory beacons = LibProdBeaconsEthereum.beacons();
address[3] memory impls = LibProdBeaconsEthereum.implementations();
address[3] memory beacons = LibProdBeacons0_1_1.beacons();
address[3] memory impls = LibProdBeacons0_1_1.implementations();
for (uint256 i = 0; i < beacons.length; i++) {
LibBeaconInvariants.assertBeaconInvariants(beacons[i], safe, impls[i]);
}
Expand Down
37 changes: 37 additions & 0 deletions test/src/concrete/deploy/HyperEvmBeaconOwnership.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-License-Identifier: LicenseRef-DCL-1.0
// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd
pragma solidity =0.8.25;

import {Test} from "forge-std-1.16.1/src/Test.sol";
import {LibProdBeacons0_1_1} from "../../../../src/lib/LibProdBeacons0_1_1.sol";
import {LibSafeInvariants} from "../../../../src/lib/LibSafeInvariants.sol";
import {LibBeaconInvariants} from "../../../../src/lib/LibBeaconInvariants.sol";
import {LibStoxDeployNetworks} from "../../../../src/lib/LibStoxDeployNetworks.sol";

/// @title HyperEvmBeaconOwnershipTest
/// @notice The forcing function for the HyperEVM beacon-ownership migration
/// (`20260722-migrate-beacon-owners-hyperevm`), mirroring
/// `EthereumBeaconOwnershipTest`: every chain's production beacons must be
/// owned by that chain's token-owner Safe. RED from the moment the 0.1.1
/// impl suites land on HyperEVM (beacons come up EOA-owned) until the
/// migration runs; green thereafter, catching later ownership drift.
///
/// @dev The invariant runs unconditionally: the HyperEVM token-owner Safe is
/// pinned in `LibSafeInvariants`, and CI supplies `HYPEREVM_RPC_URL` to the
/// shared rainix test workflow from the `RPC_URL_HYPEREVM_FORK` secret, so the
/// fork always resolves.
contract HyperEvmBeaconOwnershipTest is Test {
/// Every HyperEVM beacon is owned by the HyperEVM token-owner Safe (with
/// the OZ beacon codehash + its pinned impl unchanged). RED until the
/// migration transfers ownership from the deploy EOA to the Safe.
function testHyperEvmBeaconsAreSafeOwned() external {
address safe = LibSafeInvariants.STOX_TOKEN_OWNER_SAFE_HYPEREVM;

vm.createSelectFork(LibStoxDeployNetworks.HYPEREVM);
address[3] memory beacons = LibProdBeacons0_1_1.beacons();
address[3] memory impls = LibProdBeacons0_1_1.implementations();
for (uint256 i = 0; i < beacons.length; i++) {
LibBeaconInvariants.assertBeaconInvariants(beacons[i], safe, impls[i]);
}
}
}
4 changes: 2 additions & 2 deletions test/src/lib/LibBeaconInvariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
} from "../../../src/lib/LibBeaconInvariants.sol";
import {LibSafeInvariants} from "../../../src/lib/LibSafeInvariants.sol";
import {LibProdBeaconsBase} from "../../../src/lib/LibProdBeaconsBase.sol";
import {LibProdBeaconsEthereum} from "../../../src/lib/LibProdBeaconsEthereum.sol";
import {LibProdBeacons0_1_1} from "../../../src/lib/LibProdBeacons0_1_1.sol";
import {LibProdDeployV1} from "../../../src/lib/LibProdDeployV1.sol";
import {LibStoxDeployNetworks} from "../../../src/lib/LibStoxDeployNetworks.sol";
import {LibBeaconInvariantsHarness} from "./LibBeaconInvariantsHarness.sol";
Expand Down Expand Up @@ -135,7 +135,7 @@ contract LibBeaconInvariantsTest is Test {
vm.createSelectFork(LibStoxDeployNetworks.ETHEREUM);
harness = new LibBeaconInvariantsHarness();
address[3] memory beacons = harness.callProdBeaconsForChainId(LibSafeInvariants.ETHEREUM_CHAIN_ID);
address[3] memory expected = LibProdBeaconsEthereum.beacons();
address[3] memory expected = LibProdBeacons0_1_1.beacons();
assertEq(beacons[0], expected[0], "receipt beacon");
assertEq(beacons[1], expected[1], "receipt vault beacon");
assertEq(beacons[2], expected[2], "wrapped vault beacon");
Expand Down
Loading