-
Notifications
You must be signed in to change notification settings - Fork 6
feat: add legacy OFT DVN upgrade scripts (3/3 Frax+Horizen+L0) #133
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dhruvinparikh
wants to merge
1
commit into
FraxFinance:master
Choose a base branch
from
dhruvinparikh:ops/fix-legacy-dvns
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
scripts/ops/fix/FixLegacyDVNs/1_SetBlockSendLibLegacy.s.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
183
scripts/ops/fix/FixLegacyDVNs/2_FixDVNsAndRestoreSendLibLegacy.s.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
82
scripts/ops/fix/FixLegacyDVNs/FixLegacyDVNsInherited.s.sol
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); | ||
| } | ||
| } | ||
29 changes: 29 additions & 0 deletions
29
scripts/ops/fix/FixLegacyDVNs/txs/1776661025-1_SetBlockSendLibLegacy-LFRAX-1088.json
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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" | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FixLegacyDVNsInheritedcallsIOAppCore(_oft).peers(_eid)but does not importIOAppCore. This compiles only because leaf scripts importIOAppCore, and will break if another script importsFixLegacyDVNsInheritedwithout also importingIOAppCore. ImportIOAppCorein this file to make the dependency explicit and self-contained.