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
2 changes: 1 addition & 1 deletion scripts/L0Config.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"sendLib302": "0xbB2Ea70C9E858123480642Cf96acbcCE1372dCe1"
},
{
"RPC": "https://metis-mainnet.public.blastapi.io",
"RPC": "https://andromeda.metis.io/?owner=1088",
"chainid": 1088,
"delegate": "0xF4A4F32732F9B2fB84Ee28c58616946F3bF80F7d",
"dvnHorizen": "0x32d4F92437454829b3Fe7BEBfeCE5D0523DEb475",
Expand Down
81 changes: 81 additions & 0 deletions scripts/ops/fix/FixLegacyDVNs/1_SetBlockSendLibLegacy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.19;

import "./FixLegacyDVNsInherited.s.sol";
import {IOAppCore} from "@fraxfinance/layerzero-v2-upgradeable/oapp/contracts/oapp/interfaces/IOAppCore.sol";

interface IEndpointV2BlockLib {
function blockedLibrary() external view returns (address);
function getSendLibrary(address _sender, uint32 _dstEid) external view returns (address lib);
}

/// @title Step 1: Block send lib for all legacy OFTs
/// @notice Sets the send library to the blocked library for all legacy OFTs on each source chain,
/// preventing any OFT message flow. This must be executed FIRST before DVN config changes.
/// @dev Run per-chain: modify the chainId filter in run() to target a specific source chain.
/// Generates one Safe TX batch JSON per source chain.
contract SetBlockSendLibLegacy is FixLegacyDVNsInherited {
using stdJson for string;
using Strings for uint256;

function filename() public view override returns (string memory) {
string memory root = vm.projectRoot();
root = string.concat(root, "/scripts/ops/fix/FixLegacyDVNs/txs/");
string memory name = string.concat((block.timestamp).toString(), "-1_SetBlockSendLibLegacy-");
name = string.concat(name, currentOftName, "-");
name = string.concat(name, simulateConfig.chainid.toString());
name = string.concat(name, ".json");
return string.concat(root, name);
}

function run() public override {
for (uint256 o = 0; o < legacyOftAddresses.length; o++) {
currentOftName = legacyOftNames[o];
for (uint256 i = 0; i < legacyConfigs.length; i++) {
for (uint256 j = 0; j < legacyChainIds.length; j++) {
if (legacyConfigs[i].chainid == legacyChainIds[j]) {
blockSendLibsForOft(legacyConfigs[i], legacyOftAddresses[o]);
}
}
}
}
}

function blockSendLibsForOft(L0Config memory _config, address _oft) public simulateAndWriteTxs(_config) {
address blockedLibrary = IEndpointV2BlockLib(_config.endpoint).blockedLibrary();

for (uint256 d = 0; d < legacyChainIds.length; d++) {
// Skip self
if (legacyChainIds[d] == _config.chainid) continue;

L0Config memory dstConfig = findLegacyConfig(legacyChainIds[d]);

// Skip if peer is not set
if (!hasPeer(_oft, uint32(dstConfig.eid))) continue;

// Check if already blocked
address existingSendLib = IEndpointV2BlockLib(_config.endpoint).getSendLibrary(
_oft,
uint32(dstConfig.eid)
);
if (existingSendLib == blockedLibrary) continue;

// Set send lib to blocked library
bytes memory data = abi.encodeCall(
IMessageLibManager.setSendLibrary,
(_oft, uint32(dstConfig.eid), blockedLibrary)
);
(bool success,) = _config.endpoint.call(data);
require(success, "Failed to set blocked send library");

serializedTxs.push(
SerializedTx({
name: "SetBlockSendLib",
to: _config.endpoint,
value: 0,
data: data
})
);
}
}
}
183 changes: 183 additions & 0 deletions scripts/ops/fix/FixLegacyDVNs/2_FixDVNsAndRestoreSendLibLegacy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.19;

import "./FixLegacyDVNsInherited.s.sol";
import {IOAppCore} from "@fraxfinance/layerzero-v2-upgradeable/oapp/contracts/oapp/interfaces/IOAppCore.sol";
import {SetConfigParam, IMessageLibManager} from "@fraxfinance/layerzero-v2-upgradeable/protocol/contracts/interfaces/IMessageLibManager.sol";
import {Constant} from "@fraxfinance/layerzero-v2-upgradeable/messagelib/test/util/Constant.sol";
import {UlnConfig} from "@fraxfinance/layerzero-v2-upgradeable/messagelib/contracts/uln/UlnBase.sol";
import {Arrays} from "@openzeppelin-5/contracts/utils/Arrays.sol";

error LZ_SameValue();

/// @title Step 2+3: Set DVN config and restore send lib for legacy OFTs
/// @notice For Ethereum, Blast, Base: sets 3/3 DVN config (Frax + Horizen + L0) on both
/// send and receive ULN libraries, then restores the send library to the proper sendLib302.
/// For Metis: keeps existing 2/2 DVN config (no Frax DVN available), only restores send lib.
/// @dev This script should be executed AFTER Step 1 (block send lib) has been confirmed on-chain.
/// Steps 2 and 3 are batched together so they execute atomically in a single Safe TX batch.
contract FixDVNsAndRestoreSendLibLegacy is FixLegacyDVNsInherited {
using stdJson for string;
using Strings for uint256;

struct LegacyDvnStack {
address frax;
address horizen;
address lz;
}

function filename() public view override returns (string memory) {
string memory root = vm.projectRoot();
root = string.concat(root, "/scripts/ops/fix/FixLegacyDVNs/txs/");
string memory name = string.concat((block.timestamp).toString(), "-2_FixDVNsAndRestoreSendLibLegacy-");
name = string.concat(name, currentOftName, "-");
name = string.concat(name, simulateConfig.chainid.toString());
name = string.concat(name, ".json");
return string.concat(root, name);
}

function run() public override {
for (uint256 o = 0; o < legacyOftAddresses.length; o++) {
currentOftName = legacyOftNames[o];
for (uint256 i = 0; i < legacyConfigs.length; i++) {
for (uint256 j = 0; j < legacyChainIds.length; j++) {
if (legacyConfigs[i].chainid == legacyChainIds[j]) {
fixDVNsAndRestoreSendLibForOft(legacyConfigs[i], legacyOftAddresses[o]);
}
}
}
}
}

function fixDVNsAndRestoreSendLibForOft(L0Config memory _config, address _oft) public simulateAndWriteTxs(_config) {
for (uint256 d = 0; d < legacyChainIds.length; d++) {
// Skip self
if (legacyChainIds[d] == _config.chainid) continue;

L0Config memory dstConfig = findLegacyConfig(legacyChainIds[d]);

// Only set DVN config if BOTH source and destination have Frax DVN
bool bothHaveFraxDvn = hasFraxDvn(_config.chainid) && hasFraxDvn(legacyChainIds[d]);

// Skip if peer is not set
if (!hasPeer(_oft, uint32(dstConfig.eid))) continue;

// --- Step 2: Set DVN config (only for pathways where both sides have Frax DVN) ---
if (bothHaveFraxDvn) {
_setDVNConfig(_oft, _config, dstConfig);
}

// --- Step 3: Restore send library ---
_restoreSendLib(_oft, _config, dstConfig);
}
}

/// @notice Set 3/3 DVN config (Frax + Horizen + L0) on both sendLib and receiveLib
function _setDVNConfig(
address _oft,
L0Config memory _srcConfig,
L0Config memory _dstConfig
) internal {
// Build the desired DVN array from the dvn config files
address[] memory desiredDVNs = _craftLegacyDvnStack(_srcConfig.chainid, _dstConfig.chainid);

// Set config on send library
_setUlnConfig(_oft, _srcConfig, _dstConfig, _srcConfig.sendLib302, desiredDVNs);

// Set config on receive library
_setUlnConfig(_oft, _srcConfig, _dstConfig, _srcConfig.receiveLib302, desiredDVNs);
}

function _setUlnConfig(
address _oft,
L0Config memory _srcConfig,
L0Config memory _dstConfig,
address _lib,
address[] memory _desiredDVNs
) internal {
UlnConfig memory desiredUlnConfig;
desiredUlnConfig.requiredDVNCount = uint8(_desiredDVNs.length);
desiredUlnConfig.requiredDVNs = _desiredDVNs;

SetConfigParam[] memory params = new SetConfigParam[](1);
params[0] = SetConfigParam({
eid: uint32(_dstConfig.eid),
configType: Constant.CONFIG_TYPE_ULN,
config: abi.encode(desiredUlnConfig)
});

bytes memory data = abi.encodeCall(
IMessageLibManager.setConfig,
(_oft, _lib, params)
);
(bool success,) = _srcConfig.endpoint.call(data);
require(success, "Failed to setConfig DVNs");

serializedTxs.push(
SerializedTx({
name: "setConfig",
to: _srcConfig.endpoint,
value: 0,
data: data
})
);
}

/// @notice Restore send library from blocked to sendLib302
function _restoreSendLib(
address _oft,
L0Config memory _srcConfig,
L0Config memory _dstConfig
) internal {
bytes memory data = abi.encodeCall(
IMessageLibManager.setSendLibrary,
(_oft, uint32(_dstConfig.eid), _srcConfig.sendLib302)
);
(bool success, bytes memory returnData) = _srcConfig.endpoint.call(data);

if (!success) {
if (returnData.length >= 4) {
bytes4 errorSelector;
assembly {
errorSelector := mload(add(returnData, 32))
}

if (errorSelector != LZ_SameValue.selector) {
revert("Failed to restore send library");
}
}
}

serializedTxs.push(
SerializedTx({
name: "SetSendLib",
to: _srcConfig.endpoint,
value: 0,
data: data
})
);
}

/// @notice Build the sorted 3-DVN array (Frax + Horizen + L0) from dvn config files.
/// Reads config/dvn/{srcChainId}.json -> key {dstChainId}.
function _craftLegacyDvnStack(
uint256 _srcChainId,
uint256 _dstChainId
) internal returns (address[] memory desiredDVNs) {
// Load DVN stack from config file (uses the existing SetDVNs.getDvnStack pattern)
DvnStack memory srcStack = getDvnStack(_srcChainId, _dstChainId);

// For legacy upgrade we expect exactly: frax + horizen + lz
require(srcStack.frax != address(0), "Frax DVN missing in src config");
require(srcStack.horizen != address(0), "Horizen DVN missing in src config");
require(srcStack.lz != address(0), "L0 DVN missing in src config");

desiredDVNs = new address[](3);
desiredDVNs[0] = srcStack.frax;
desiredDVNs[1] = srcStack.horizen;
desiredDVNs[2] = srcStack.lz;

// Sort ascending (required by LayerZero ULN)
desiredDVNs = Arrays.sort(desiredDVNs);
}
}
82 changes: 82 additions & 0 deletions scripts/ops/fix/FixLegacyDVNs/FixLegacyDVNsInherited.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: ISC
pragma solidity ^0.8.19;

import "scripts/DeployFraxOFTProtocol/DeployFraxOFTProtocol.s.sol";

/// @title FixLegacyDVNsInherited
/// @notice Base contract for legacy OFT DVN upgrade scripts.
/// Legacy OFTs live on 4 chains: Ethereum (1), Metis (1088), Blast (81457), Base (8453).
/// All 6 legacy OFTs share the same address across all 4 chains.
/// Metis does NOT have a Frax DVN, so it stays at 2/2 (Horizen + L0).
contract FixLegacyDVNsInherited is DeployFraxOFTProtocol {
using OptionsBuilder for bytes;
using stdJson for string;
using Strings for uint256;

/// @notice The 4 legacy chain IDs
uint256[] public legacyChainIds;

/// @notice The 6 legacy OFT addresses (same on all chains)
address[] public legacyOftAddresses;

/// @notice The 6 legacy OFT names (parallel to legacyOftAddresses)
string[] public legacyOftNames;

/// @notice Currently processing OFT name (set before simulateAndWriteTxs calls)
string public currentOftName;

/// @notice Legacy chain IDs that get the Frax DVN upgrade (excludes Metis)
uint256[] public fraxDvnChainIds;

constructor() {
// Legacy chains
legacyChainIds.push(1); // Ethereum
legacyChainIds.push(1088); // Metis
legacyChainIds.push(81457); // Blast
legacyChainIds.push(8453); // Base

// 6 legacy OFTs (same address on all 4 chains)
legacyOftAddresses.push(0x909DBdE1eBE906Af95660033e478D59EFe831fED); // LFRAX
legacyOftAddresses.push(0xe4796cCB6bB5DE2290C417Ac337F2b66CA2E770E); // sFRAX
legacyOftAddresses.push(0xF010a7c8877043681D59AD125EbF575633505942); // frxETH
legacyOftAddresses.push(0x1f55a02A049033E3419a8E2975cF3F572F4e6E9A); // sfrxETH
legacyOftAddresses.push(0x23432452B720C80553458496D4D9d7C5003280d0); // FRAX
legacyOftAddresses.push(0x6Eca253b102D41B6B69AC815B9CC6bD47eF1979d); // FPI

// Parallel names array
legacyOftNames.push("LFRAX");
legacyOftNames.push("sFRAX");
legacyOftNames.push("frxETH");
legacyOftNames.push("sfrxETH");
legacyOftNames.push("FRAX");
legacyOftNames.push("FPI");

// Chains that will get 3/3 DVN (Frax DVN added) — Metis excluded
fraxDvnChainIds.push(1); // Ethereum
fraxDvnChainIds.push(81457); // Blast
fraxDvnChainIds.push(8453); // Base
}

/// @notice Find legacy L0Config by chain ID from legacyConfigs array
function findLegacyConfig(uint256 _chainId) internal view returns (L0Config memory) {
for (uint256 i = 0; i < legacyConfigs.length; i++) {
if (legacyConfigs[i].chainid == _chainId) {
return legacyConfigs[i];
}
}
revert(string.concat("Legacy config not found for chainId: ", _chainId.toString()));
}

/// @notice Check if a chain has Frax DVN support
function hasFraxDvn(uint256 _chainId) internal view returns (bool) {
for (uint256 i = 0; i < fraxDvnChainIds.length; i++) {
if (fraxDvnChainIds[i] == _chainId) return true;
}
return false;
}

function hasPeer(address _oft, uint32 _eid) internal view returns (bool) {
bytes32 peer = IOAppCore(_oft).peers(_eid);
return peer != bytes32(0);
}
Comment on lines +78 to +81
Copy link

Copilot AI Apr 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FixLegacyDVNsInherited calls IOAppCore(_oft).peers(_eid) but does not import IOAppCore. This compiles only because leaf scripts import IOAppCore, and will break if another script imports FixLegacyDVNsInherited without also importing IOAppCore. Import IOAppCore in this file to make the dependency explicit and self-contained.

Copilot uses AI. Check for mistakes.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"chainId": 1088,
"createdAt": 1776661025000,
"meta": {
"description": "",
"name": "Transactions Batch"
},
"transactions": [
{
"data": "0x9535ff30000000000000000000000000909dbde1ebe906af95660033e478d59efe831fed00000000000000000000000000000000000000000000000000000000000075950000000000000000000000001ccbf0db9c192d969de57e25b3ff09a25bb1d862",
"operation": "0",
"to": "0x1a44076050125825900e736c501f859c50fE728c",
"value": "0"
},
{
"data": "0x9535ff30000000000000000000000000909dbde1ebe906af95660033e478d59efe831fed00000000000000000000000000000000000000000000000000000000000076230000000000000000000000001ccbf0db9c192d969de57e25b3ff09a25bb1d862",
"operation": "0",
"to": "0x1a44076050125825900e736c501f859c50fE728c",
"value": "0"
},
{
"data": "0x9535ff30000000000000000000000000909dbde1ebe906af95660033e478d59efe831fed00000000000000000000000000000000000000000000000000000000000075e80000000000000000000000001ccbf0db9c192d969de57e25b3ff09a25bb1d862",
"operation": "0",
"to": "0x1a44076050125825900e736c501f859c50fE728c",
"value": "0"
}
],
"version": "1.0"
}
Loading
Loading