Skip to content
Open
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
1 change: 1 addition & 0 deletions output/hyperevm-l1-staker-op-config-bundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"chainId":"999","safeAddress":"0xf27128a5b064e8d97edaa60d24bfa2fd1eec26eb","meta": { "txBuilderVersion": "1.16.5" }, "transactions": [{"to":"0xe6a3bcd93e9d56e5252db47ff512629fd3cc9b54","value":"0","data":"0x3400288b000000000000000000000000000000000000000000000000000000000000759f0000000000000000000000003f1bdae959ced680e434fe201861e97976ea4a8f"},{"to":"0x3a73033c0b1407574c76bdbac67f126f6b4a9aa9","value":"0","data":"0x6dbd9f90000000000000000000000000e6a3bcd93e9d56e5252db47ff512629fd3cc9b54000000000000000000000000fd76d9cb0bac839725ab79127e7411fe71b1e3ca000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000759f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000008e49ef1dfae17e547ca0e7526ffda81fbaca810a000000000000000000000000c097ab8cd7b053326dfe9fb3e3a31a0cce3b526f0000000000000000000000000000000000000000000000000000000000000000"},{"to":"0x3a73033c0b1407574c76bdbac67f126f6b4a9aa9","value":"0","data":"0x6dbd9f90000000000000000000000000e6a3bcd93e9d56e5252db47ff512629fd3cc9b540000000000000000000000007cacbe439ead55fa1c22790330b12835c6884a91000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000759f0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001e00000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000020000000000000000000000008e49ef1dfae17e547ca0e7526ffda81fbaca810a000000000000000000000000c097ab8cd7b053326dfe9fb3e3a31a0cce3b526f0000000000000000000000000000000000000000000000000000000000000000"}]}
122 changes: 122 additions & 0 deletions script/oapp/ConfigureL1OAppStakerForOP.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "forge-std/StdJson.sol";

import {ILayerZeroEndpointV2} from "lib/LayerZero-v2/packages/layerzero-v2/evm/protocol/contracts/interfaces/ILayerZeroEndpointV2.sol";
import {IMessageLibManager, SetConfigParam} from "lib/LayerZero-v2/packages/layerzero-v2/evm/protocol/contracts/interfaces/IMessageLibManager.sol";
import {UlnConfig} from "lib/LayerZero-v2/packages/layerzero-v2/evm/messagelib/contracts/uln/UlnBase.sol";

import {GnosisHelpers} from "../utils/GnosisHelpers.sol";
import {L1BeHYPEOAppStaker} from "../../src/L1BeHYPEOAppStaker.sol";

/**
* @title ConfigureL1OAppStakerForOP
* @notice Generates a Gnosis Safe Transaction Builder JSON bundle for the HyperEVM
* controller (current L1BeHYPEOAppStaker owner) to peer the already-deployed
* L1BeHYPEOAppStaker with the new OP-side L2BeHYPEOAppStaker, and configure
* the HyperEVM endpoint send/receive DVNs for the OP route.
*
* The bundle is simulated on the current fork to surface any reverts before
* signing the multisig transaction.
*
* Bundle (executed by the L1 staker owner Safe):
* 1. L1BeHYPEOAppStaker.setPeer(OP_EID, L2BeHYPEOAppStaker)
* 2. endpoint.setConfig on send302 with [Nethermind, LayerZero] DVNs
* 3. endpoint.setConfig on receive302 with [Nethermind, LayerZero] DVNs
*
* forge script script/oapp/ConfigureL1OAppStakerForOP.s.sol:ConfigureL1OAppStakerForOP \
* --rpc-url $HYPEREVM_RPC \
* -vvvv
*/
contract ConfigureL1OAppStakerForOP is GnosisHelpers {
using stdJson for string;

string constant OUTPUT_PATH = "output/hyperevm-l1-staker-op-config-bundle.json";
string constant CHAIN_ID = "999";

function run() external {
string memory cfg = vm.readFile("config/production.json");

address l1Staker = cfg.readAddress(".layerZero.L1BeHYPEOAppStaker");
// L2 staker is expected to land at the same CREATE3 address on OP as Scroll
// (per script/oapp/DeployAndConfigureOApps.s.sol).
address l2Staker = cfg.readAddress(".layerZero.L2BeHYPEOAppStaker");
uint32 optimismEid = uint32(cfg.readUint(".layerZero.optimism.eid"));

address endpoint = cfg.readAddress(".layerZero.hyperEVM.endpoint");
address sendLib = cfg.readAddress(".layerZero.hyperEVM.send302");
address receiveLib = cfg.readAddress(".layerZero.hyperEVM.receive302");
address nevermindDvn = cfg.readAddress(".layerZero.hyperEVM.nevermindDvn");
address layerZeroDvn = cfg.readAddress(".layerZero.hyperEVM.layerZeroDvn");

address safeAddress = L1BeHYPEOAppStaker(payable(l1Staker)).owner();

// --- 1. setPeer calldata ---
bytes memory setPeerData = abi.encodeWithSignature(
"setPeer(uint32,bytes32)",
optimismEid,
bytes32(uint256(uint160(l2Staker)))
);

// --- 2 & 3. setConfig calldata (send + receive) ---
address[] memory requiredDVNs = new address[](2);
if (layerZeroDvn > nevermindDvn) {
requiredDVNs[0] = nevermindDvn;
requiredDVNs[1] = layerZeroDvn;
} else {
requiredDVNs[0] = layerZeroDvn;
requiredDVNs[1] = nevermindDvn;
}

UlnConfig memory ulnConfig = UlnConfig({
confirmations: 30,
requiredDVNCount: 2,
optionalDVNCount: 0,
optionalDVNThreshold: 0,
requiredDVNs: requiredDVNs,
optionalDVNs: new address[](0)
});

SetConfigParam[] memory params = new SetConfigParam[](1);
params[0] = SetConfigParam(optimismEid, 2, abi.encode(ulnConfig));

bytes memory setConfigSendData = abi.encodeWithSelector(
IMessageLibManager.setConfig.selector,
l1Staker,
sendLib,
params
);

bytes memory setConfigReceiveData = abi.encodeWithSelector(
IMessageLibManager.setConfig.selector,
l1Staker,
receiveLib,
params
);

// --- Build Gnosis JSON bundle ---
string memory safeHex = addressToHex(safeAddress);
string memory l1StakerHex = addressToHex(l1Staker);
string memory endpointHex = addressToHex(endpoint);

string memory bundle = string.concat(
_getGnosisHeader(CHAIN_ID, safeHex),
_getGnosisTransaction(l1StakerHex, iToHex(setPeerData), "0", false),
_getGnosisTransaction(endpointHex, iToHex(setConfigSendData), "0", false),
_getGnosisTransaction(endpointHex, iToHex(setConfigReceiveData), "0", true)
);

vm.writeFile(OUTPUT_PATH, bundle);

// --- Simulate execution on fork ---
executeGnosisTransactionBundle(OUTPUT_PATH);

// --- Verify ---
bytes32 peer = L1BeHYPEOAppStaker(payable(l1Staker)).peers(optimismEid);
require(
peer == bytes32(uint256(uint160(l2Staker))),
"Peer not set correctly"
);
}
}
6 changes: 5 additions & 1 deletion script/oapp/DeployAndConfigureOApps.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,11 @@ contract DeployAndConfigureOApps is Script, Utils {
cfg.readAddress(".layerZero.optimism.layerZeroDvn"),
address(l2BeHYPEOAppStaker)
);


address opController = cfg.readAddress(".roles.optimismController");
L2BeHYPEOAppStaker(l2BeHYPEOAppStaker).setDelegate(opController);
L2BeHYPEOAppStaker(l2BeHYPEOAppStaker).transferOwnership(opController);

} else {
L1BeHYPEOAppStaker l1Impl = new L1BeHYPEOAppStaker(cfg.readAddress(".layerZero.hyperEVM.endpoint"));
l1BeHYPEOAppStaker = _deployL1OAppStaker(address(l1Impl), owner);
Expand Down
79 changes: 79 additions & 0 deletions script/oapp/OAppStakerOptimismVerification.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {Test, console} from "forge-std/Test.sol";
import {ContractCodeChecker} from "../utils/ContractCodeChecker.sol";
import {L2BeHYPEOAppStaker} from "../../src/L2BeHYPEOAppStaker.sol";
import "forge-std/StdJson.sol";

interface IEndpointV2 {
function delegates(address oapp) external view returns (address);
}

interface IOAppCore {
function peers(uint32 eid) external view returns (bytes32);
}

/**
* @title OAppStakerOptimismVerification
* @notice Bytecode verification for the L2BeHYPEOAppStaker deployed on Optimism.
*
* forge test --match-contract OAppStakerOptimismVerification -vvv
*/
contract OAppStakerOptimismVerification is ContractCodeChecker, Test {
using stdJson for string;

bytes32 constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

function test_verifyL2BeHYPEOAppStakerOnOptimism() public {
vm.createSelectFork("https://mainnet.optimism.io");

string memory config = vm.readFile("config/production.json");

address l2StakerProxy = config.readAddress(".layerZero.L2BeHYPEOAppStaker");
address optimismEndpoint = config.readAddress(".layerZero.optimism.endpoint");
address expectedController = config.readAddress(".roles.optimismController");
uint32 hyperEVMEid = uint32(config.readUint(".layerZero.hyperEVM.eid"));

console.log("=== L2BeHYPEOAppStaker on Optimism Verification ===");
console.log("Proxy:", l2StakerProxy);

// --- Implementation Bytecode ---
console.log("--- Verifying Implementation Bytecode ---");
address impl = _getImplementationAddress(l2StakerProxy);
console.log("Implementation:", impl);

address localImpl = address(new L2BeHYPEOAppStaker(optimismEndpoint));
verifyContractByteCodeMatch(impl, localImpl);

// --- Config & Roles ---
L2BeHYPEOAppStaker staker = L2BeHYPEOAppStaker(payable(l2StakerProxy));

assertEq(staker.owner(), expectedController, "owner != optimismController");
console.log("Owner is optimismController");

assertEq(
IEndpointV2(optimismEndpoint).delegates(l2StakerProxy),
expectedController,
"delegate != optimismController"
);
console.log("LZ delegate is optimismController");

// Verify peer is set to L1 staker on HyperEVM
address l1Staker = config.readAddress(".layerZero.L1BeHYPEOAppStaker");
bytes32 expectedPeer = bytes32(uint256(uint160(l1Staker)));
assertEq(
IOAppCore(l2StakerProxy).peers(hyperEVMEid),
expectedPeer,
"HyperEVM peer not set correctly"
);
console.log("HyperEVM peer verified");

console.log("=== L2BeHYPEOAppStaker Optimism Verification Complete ===");
}

function _getImplementationAddress(address proxy) internal view returns (address) {
bytes32 slotValue = vm.load(proxy, IMPLEMENTATION_SLOT);
return address(uint160(uint256(slotValue)));
}
}
Loading