diff --git a/output/hyperevm-l1-staker-op-config-bundle.json b/output/hyperevm-l1-staker-op-config-bundle.json new file mode 100644 index 0000000..6625184 --- /dev/null +++ b/output/hyperevm-l1-staker-op-config-bundle.json @@ -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"}]} \ No newline at end of file diff --git a/script/oapp/ConfigureL1OAppStakerForOP.s.sol b/script/oapp/ConfigureL1OAppStakerForOP.s.sol new file mode 100644 index 0000000..fd85356 --- /dev/null +++ b/script/oapp/ConfigureL1OAppStakerForOP.s.sol @@ -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" + ); + } +} diff --git a/script/oapp/DeployAndConfigureOApps.s.sol b/script/oapp/DeployAndConfigureOApps.s.sol index 434b709..51ae597 100644 --- a/script/oapp/DeployAndConfigureOApps.s.sol +++ b/script/oapp/DeployAndConfigureOApps.s.sol @@ -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); diff --git a/script/oapp/OAppStakerOptimismVerification.s.sol b/script/oapp/OAppStakerOptimismVerification.s.sol new file mode 100644 index 0000000..b37bd3a --- /dev/null +++ b/script/oapp/OAppStakerOptimismVerification.s.sol @@ -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))); + } +}