From d5ebfd8625684b34bfb4dbdc3bd9eb17706e27ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lz=2Ew=CE=94rlock=28=29?= Date: Fri, 16 Feb 2024 14:00:17 -0800 Subject: [PATCH 1/8] Add back Foundry testHelper.sol into oft example before updating it to handle Oz4/5 backwards compatability --- examples/oft/contracts/MyOFT.sol | 7 +- examples/oft/package.json | 3 +- examples/oft/test/foundry/MyOFT.t.sol | 160 ++++++++++++++++++ examples/oft/test/mocks/ERC20Mock.sol | 12 ++ .../{contracts => test}/mocks/MyOFTMock.sol | 2 +- examples/oft/test/mocks/OFTComposerMock.sol | 27 +++ examples/oft/test/mocks/OFTMock.sol | 58 +++++++ 7 files changed, 265 insertions(+), 4 deletions(-) create mode 100644 examples/oft/test/foundry/MyOFT.t.sol create mode 100644 examples/oft/test/mocks/ERC20Mock.sol rename examples/oft/{contracts => test}/mocks/MyOFTMock.sol (89%) create mode 100644 examples/oft/test/mocks/OFTComposerMock.sol create mode 100644 examples/oft/test/mocks/OFTMock.sol diff --git a/examples/oft/contracts/MyOFT.sol b/examples/oft/contracts/MyOFT.sol index 9e2628d82..fda56c432 100644 --- a/examples/oft/contracts/MyOFT.sol +++ b/examples/oft/contracts/MyOFT.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; -import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +//import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { OFT } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol"; contract MyOFT is OFT { @@ -10,5 +10,8 @@ contract MyOFT is OFT { string memory _symbol, address _lzEndpoint, address _delegate - ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {} + ) + // ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {} + OFT(_name, _symbol, _lzEndpoint, _delegate) + {} } diff --git a/examples/oft/package.json b/examples/oft/package.json index db7f05387..5be1f260c 100644 --- a/examples/oft/package.json +++ b/examples/oft/package.json @@ -35,7 +35,8 @@ "@layerzerolabs/toolbox-hardhat": "~0.1.10", "@nomicfoundation/hardhat-ethers": "^3.0.5", "@nomiclabs/hardhat-ethers": "^2.2.3", - "@openzeppelin/contracts": "^5.0.1", + "@openzeppelin/contracts": "^4.8.1", + "@openzeppelin/contracts-upgradeable": "^4.8.1", "@rushstack/eslint-patch": "^1.7.0", "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", diff --git a/examples/oft/test/foundry/MyOFT.t.sol b/examples/oft/test/foundry/MyOFT.t.sol new file mode 100644 index 000000000..2859f5428 --- /dev/null +++ b/examples/oft/test/foundry/MyOFT.t.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +// Mock imports +import { OFTMock } from "../mocks/OFTMock.sol"; +import { ERC20Mock } from "../mocks/ERC20Mock.sol"; +import { OFTComposerMock } from "../mocks/OFTComposerMock.sol"; + +// OApp imports +import { IOAppOptionsType3, EnforcedOptionParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OAppOptionsType3.sol"; +import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol"; + +// OFT imports +import { IOFT, SendParam, OFTReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/interfaces/IOFT.sol"; +import { MessagingFee, MessagingReceipt } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFTCore.sol"; +import { OFTMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTMsgCodec.sol"; +import { OFTComposeMsgCodec } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/libs/OFTComposeMsgCodec.sol"; + +// OZ imports +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +// Forge imports +import "forge-std/console.sol"; + +// DevTools imports +import { TestHelper } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelper.sol"; + +contract MyOFTTest is TestHelper { + using OptionsBuilder for bytes; + + uint32 aEid = 1; + uint32 bEid = 2; + + OFTMock aOFT; + OFTMock bOFT; + + address public userA = address(0x1); + address public userB = address(0x2); + uint256 public initialBalance = 100 ether; + + function setUp() public virtual override { + vm.deal(userA, 1000 ether); + vm.deal(userB, 1000 ether); + + super.setUp(); + setUpEndpoints(2, LibraryType.UltraLightNode); + + aOFT = OFTMock( + _deployOApp(type(OFTMock).creationCode, abi.encode("aOFT", "aOFT", address(endpoints[aEid]), address(this))) + ); + + bOFT = OFTMock( + _deployOApp(type(OFTMock).creationCode, abi.encode("bOFT", "bOFT", address(endpoints[bEid]), address(this))) + ); + + // config and wire the ofts + address[] memory ofts = new address[](2); + ofts[0] = address(aOFT); + ofts[1] = address(bOFT); + this.wireOApps(ofts); + + // mint tokens + aOFT.mint(userA, initialBalance); + bOFT.mint(userB, initialBalance); + } + + function test_constructor() public { + assertEq(aOFT.owner(), address(this)); + assertEq(bOFT.owner(), address(this)); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + + assertEq(aOFT.token(), address(aOFT)); + assertEq(bOFT.token(), address(bOFT)); + } + + function test_send_oft() public { + uint256 tokensToSend = 1 ether; + bytes memory options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0); + SendParam memory sendParam = SendParam( + bEid, + addressToBytes32(userB), + tokensToSend, + tokensToSend, + options, + "", + "" + ); + MessagingFee memory fee = aOFT.quoteSend(sendParam, false); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(userB), initialBalance); + + vm.prank(userA); + aOFT.send{ value: fee.nativeFee }(sendParam, fee, payable(address(this))); + verifyPackets(bEid, addressToBytes32(address(bOFT))); + + assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + assertEq(bOFT.balanceOf(userB), initialBalance + tokensToSend); + } + + function test_send_oft_compose_msg() public { + uint256 tokensToSend = 1 ether; + + OFTComposerMock composer = new OFTComposerMock(); + + bytes memory options = OptionsBuilder + .newOptions() + .addExecutorLzReceiveOption(200000, 0) + .addExecutorLzComposeOption(0, 500000, 0); + bytes memory composeMsg = hex"1234"; + SendParam memory sendParam = SendParam( + bEid, + addressToBytes32(address(composer)), + tokensToSend, + tokensToSend, + options, + composeMsg, + "" + ); + MessagingFee memory fee = aOFT.quoteSend(sendParam, false); + + assertEq(aOFT.balanceOf(userA), initialBalance); + assertEq(bOFT.balanceOf(address(composer)), 0); + + vm.prank(userA); + (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) = aOFT.send{ value: fee.nativeFee }( + sendParam, + fee, + payable(address(this)) + ); + verifyPackets(bEid, addressToBytes32(address(bOFT))); + + // lzCompose params + uint32 dstEid_ = bEid; + address from_ = address(bOFT); + bytes memory options_ = options; + bytes32 guid_ = msgReceipt.guid; + address to_ = address(composer); + bytes memory composerMsg_ = OFTComposeMsgCodec.encode( + msgReceipt.nonce, + aEid, + oftReceipt.amountReceivedLD, + abi.encodePacked(addressToBytes32(userA), composeMsg) + ); + this.lzCompose(dstEid_, from_, options_, guid_, to_, composerMsg_); + + assertEq(aOFT.balanceOf(userA), initialBalance - tokensToSend); + assertEq(bOFT.balanceOf(address(composer)), tokensToSend); + + assertEq(composer.from(), from_); + assertEq(composer.guid(), guid_); + assertEq(composer.message(), composerMsg_); + assertEq(composer.executor(), address(this)); + assertEq(composer.extraData(), composerMsg_); // default to setting the extraData to the message as well to test + } + + // TODO import the rest of oft tests? +} diff --git a/examples/oft/test/mocks/ERC20Mock.sol b/examples/oft/test/mocks/ERC20Mock.sol new file mode 100644 index 000000000..6ce17e418 --- /dev/null +++ b/examples/oft/test/mocks/ERC20Mock.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +contract ERC20Mock is ERC20 { + constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } +} diff --git a/examples/oft/contracts/mocks/MyOFTMock.sol b/examples/oft/test/mocks/MyOFTMock.sol similarity index 89% rename from examples/oft/contracts/mocks/MyOFTMock.sol rename to examples/oft/test/mocks/MyOFTMock.sol index 3ebb888d4..4d0001d4e 100644 --- a/examples/oft/contracts/mocks/MyOFTMock.sol +++ b/examples/oft/test/mocks/MyOFTMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; -import { MyOFT } from "../MyOFT.sol"; +import { MyOFT } from "../contracts/MyOFT.sol"; // @dev WARNING: This is for testing purposes only contract MyOFTMock is MyOFT { diff --git a/examples/oft/test/mocks/OFTComposerMock.sol b/examples/oft/test/mocks/OFTComposerMock.sol new file mode 100644 index 000000000..26354d4ad --- /dev/null +++ b/examples/oft/test/mocks/OFTComposerMock.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { IOAppComposer } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppComposer.sol"; + +contract OFTComposerMock is IOAppComposer { + // default empty values for testing a lzCompose received message + address public from; + bytes32 public guid; + bytes public message; + address public executor; + bytes public extraData; + + function lzCompose( + address _from, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata /*_extraData*/ + ) external payable { + from = _from; + guid = _guid; + message = _message; + executor = _executor; + extraData = _message; + } +} diff --git a/examples/oft/test/mocks/OFTMock.sol b/examples/oft/test/mocks/OFTMock.sol new file mode 100644 index 000000000..be066703e --- /dev/null +++ b/examples/oft/test/mocks/OFTMock.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { OFT } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol"; +import { SendParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFTCore.sol"; + +contract OFTMock is OFT { + constructor( + string memory _name, + string memory _symbol, + address _lzEndpoint, + address _delegate + ) OFT(_name, _symbol, _lzEndpoint, _delegate) {} + + function mint(address _to, uint256 _amount) public { + _mint(_to, _amount); + } + + // @dev expose internal functions for testing purposes + function debit( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) public returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + return _debit(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function debitView( + uint256 _amountToSendLD, + uint256 _minAmountToCreditLD, + uint32 _dstEid + ) public view returns (uint256 amountDebitedLD, uint256 amountToCreditLD) { + return _debitView(_amountToSendLD, _minAmountToCreditLD, _dstEid); + } + + function removeDust(uint256 _amountLD) public view returns (uint256 amountLD) { + return _removeDust(_amountLD); + } + + function toLD(uint64 _amountSD) public view returns (uint256 amountLD) { + return _toLD(_amountSD); + } + + function toSD(uint256 _amountLD) public view returns (uint64 amountSD) { + return _toSD(_amountLD); + } + + function credit(address _to, uint256 _amountToCreditLD, uint32 _srcEid) public returns (uint256 amountReceivedLD) { + return _credit(_to, _amountToCreditLD, _srcEid); + } + + function buildMsgAndOptions( + SendParam calldata _sendParam, + uint256 _amountToCreditLD + ) public view returns (bytes memory message, bytes memory options) { + return _buildMsgAndOptions(_sendParam, _amountToCreditLD); + } +} From 728cc49aaf2b8b8d123bab7ee4f7a761fc0fa409 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lz=2Ew=CE=94rlock=28=29?= Date: Fri, 16 Feb 2024 17:57:37 -0800 Subject: [PATCH 2/8] WIP: Working TestHelper.sol in OZ5 migration --- examples/oft/contracts/MyOFT.sol | 7 +- .../{test => contracts}/mocks/MyOFTMock.sol | 2 +- examples/oft/package.json | 4 +- examples/oft/test/mocks/OFTMock.sol | 3 +- .../contracts/TestHelper.sol | 43 +- .../contracts/mocks/DVNFeeLibMock.sol | 147 +++++++ .../contracts/mocks/DVNMock.sol | 351 ++++++++++++++++ .../contracts/mocks/EndpointV2Mock.sol | 376 ++++++++++++++++++ .../contracts/mocks/ExecutorFeeLibMock.sol | 197 ++++++++- .../contracts/mocks/ExecutorMock.sol | 156 ++++++++ .../contracts/mocks/MultiSigMock.sol | 108 +++++ .../contracts/mocks/PriceFeedMock.sol | 257 ++++++++++++ .../contracts/mocks/ReceiveUln302Mock.sol | 85 ++++ .../contracts/mocks/SendUln302Mock.sol | 81 +++- .../contracts/mocks/SimpleMessageLibMock.sol | 148 ++++++- .../contracts/mocks/WorkerMock.sol | 167 ++++++++ .../test-devtools-evm-foundry/package.json | 4 +- pnpm-lock.yaml | 132 +++--- 18 files changed, 2179 insertions(+), 89 deletions(-) rename examples/oft/{test => contracts}/mocks/MyOFTMock.sol (89%) create mode 100644 packages/test-devtools-evm-foundry/contracts/mocks/DVNFeeLibMock.sol create mode 100644 packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol create mode 100644 packages/test-devtools-evm-foundry/contracts/mocks/EndpointV2Mock.sol create mode 100644 packages/test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol create mode 100644 packages/test-devtools-evm-foundry/contracts/mocks/MultiSigMock.sol create mode 100644 packages/test-devtools-evm-foundry/contracts/mocks/PriceFeedMock.sol create mode 100644 packages/test-devtools-evm-foundry/contracts/mocks/ReceiveUln302Mock.sol create mode 100644 packages/test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol diff --git a/examples/oft/contracts/MyOFT.sol b/examples/oft/contracts/MyOFT.sol index fda56c432..9e2628d82 100644 --- a/examples/oft/contracts/MyOFT.sol +++ b/examples/oft/contracts/MyOFT.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; -//import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { OFT } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol"; contract MyOFT is OFT { @@ -10,8 +10,5 @@ contract MyOFT is OFT { string memory _symbol, address _lzEndpoint, address _delegate - ) - // ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {} - OFT(_name, _symbol, _lzEndpoint, _delegate) - {} + ) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {} } diff --git a/examples/oft/test/mocks/MyOFTMock.sol b/examples/oft/contracts/mocks/MyOFTMock.sol similarity index 89% rename from examples/oft/test/mocks/MyOFTMock.sol rename to examples/oft/contracts/mocks/MyOFTMock.sol index 4d0001d4e..3ebb888d4 100644 --- a/examples/oft/test/mocks/MyOFTMock.sol +++ b/examples/oft/contracts/mocks/MyOFTMock.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; -import { MyOFT } from "../contracts/MyOFT.sol"; +import { MyOFT } from "../MyOFT.sol"; // @dev WARNING: This is for testing purposes only contract MyOFTMock is MyOFT { diff --git a/examples/oft/package.json b/examples/oft/package.json index 5be1f260c..751af7e60 100644 --- a/examples/oft/package.json +++ b/examples/oft/package.json @@ -35,8 +35,8 @@ "@layerzerolabs/toolbox-hardhat": "~0.1.10", "@nomicfoundation/hardhat-ethers": "^3.0.5", "@nomiclabs/hardhat-ethers": "^2.2.3", - "@openzeppelin/contracts": "^4.8.1", - "@openzeppelin/contracts-upgradeable": "^4.8.1", + "@openzeppelin/contracts": "^5.0.1", + "@openzeppelin/contracts-upgradeable": "^5.0.1", "@rushstack/eslint-patch": "^1.7.0", "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", diff --git a/examples/oft/test/mocks/OFTMock.sol b/examples/oft/test/mocks/OFTMock.sol index be066703e..05f969d85 100644 --- a/examples/oft/test/mocks/OFTMock.sol +++ b/examples/oft/test/mocks/OFTMock.sol @@ -1,6 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.0; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; import { OFT } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol"; import { SendParam } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFTCore.sol"; @@ -10,7 +11,7 @@ contract OFTMock is OFT { string memory _symbol, address _lzEndpoint, address _delegate - ) OFT(_name, _symbol, _lzEndpoint, _delegate) {} + ) Ownable(_delegate) OFT(_name, _symbol, _lzEndpoint, _delegate) {} function mint(address _to, uint256 _amount) public { _mint(_to, _amount); diff --git a/packages/test-devtools-evm-foundry/contracts/TestHelper.sol b/packages/test-devtools-evm-foundry/contracts/TestHelper.sol index a9b467d9b..3f3a8c6cf 100644 --- a/packages/test-devtools-evm-foundry/contracts/TestHelper.sol +++ b/packages/test-devtools-evm-foundry/contracts/TestHelper.sol @@ -2,35 +2,40 @@ pragma solidity ^0.8.18; +// Forge import { Test } from "forge-std/Test.sol"; +import "forge-std/console.sol"; + +// Oz import { DoubleEndedQueue } from "@openzeppelin/contracts/utils/structs/DoubleEndedQueue.sol"; +// Msg Lib import { UlnConfig, SetDefaultUlnConfigParam } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/UlnBase.sol"; import { SetDefaultExecutorConfigParam, ExecutorConfig } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/SendLibBase.sol"; -import { ReceiveUln302 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/uln302/ReceiveUln302.sol"; -import { IDVN } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IDVN.sol"; -import { DVN, ExecuteParam } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/dvn/DVN.sol"; -import { DVNFeeLib } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/dvn/DVNFeeLib.sol"; -import { IExecutor } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/IExecutor.sol"; -import { Executor } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/Executor.sol"; -import { PriceFeed } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/PriceFeed.sol"; -import { ILayerZeroPriceFeed } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/ILayerZeroPriceFeed.sol"; -import { IReceiveUlnE2 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol"; -import { ReceiveUln302 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/uln302/ReceiveUln302.sol"; + +// Protocol import { IMessageLib } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol"; -import { EndpointV2 } from "@layerzerolabs/lz-evm-protocol-v2/contracts/EndpointV2.sol"; import { ExecutorOptions } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/ExecutorOptions.sol"; import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol"; import { Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; +// @dev oz4/5 breaking change... +import { ReceiveUln302Mock as ReceiveUln302, IReceiveUlnE2 } from "./mocks/ReceiveUln302Mock.sol"; +import { DVNMock as DVN, ExecuteParam, IDVN } from "./mocks/DVNMock.sol"; +import { DVNFeeLibMock as DVNFeeLib } from "./mocks/DVNFeeLibMock.sol"; +import { ExecutorMock as Executor, IExecutor } from "./mocks/ExecutorMock.sol"; +import { PriceFeedMock as PriceFeed, ILayerZeroPriceFeed } from "./mocks/PriceFeedMock.sol"; +import { EndpointV2Mock as EndpointV2 } from "./mocks//EndpointV2Mock.sol"; + +// OApp import { OApp } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol"; import { OptionsBuilder } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/libs/OptionsBuilder.sol"; +// Misc. Mocks import { OptionsHelper } from "./OptionsHelper.sol"; import { SendUln302Mock as SendUln302 } from "./mocks/SendUln302Mock.sol"; import { SimpleMessageLibMock } from "./mocks/SimpleMessageLibMock.sol"; -import "./mocks/ExecutorFeeLibMock.sol"; -import "forge-std/console.sol"; +import { ExecutorFeeLibMock as ExecutorFeeLib } from "./mocks/ExecutorFeeLibMock.sol"; /** * @title TestHelper @@ -94,8 +99,8 @@ contract TestHelper is Test, OptionsHelper { address[] memory signers = new address[](1); signers[0] = vm.addr(1); - PriceFeed priceFeed = new PriceFeed(); - priceFeed.initialize(address(this)); + // @dev oz4/5 breaking change... constructor init + PriceFeed priceFeed = new PriceFeed(address(this)); for (uint8 i = 0; i < _endpointNum; i++) { if (_libraryType == LibraryType.UltraLightNode) { @@ -112,7 +117,7 @@ contract TestHelper is Test, OptionsHelper { receiveLibs[i] = address(receiveUln); } - Executor executor = new Executor(); + Executor executor; DVN dvn; { address[] memory admins = new address[](1); @@ -121,8 +126,7 @@ contract TestHelper is Test, OptionsHelper { address[] memory messageLibs = new address[](2); messageLibs[0] = address(sendUln); messageLibs[1] = address(receiveUln); - - executor.initialize( + executor = new Executor( endpointAddr, address(0x0), messageLibs, @@ -130,7 +134,8 @@ contract TestHelper is Test, OptionsHelper { address(this), admins ); - ExecutorFeeLib executorLib = new ExecutorFeeLibMock(); + + ExecutorFeeLib executorLib = new ExecutorFeeLib(); executor.setWorkerFeeLib(address(executorLib)); dvn = new DVN(i + 1, messageLibs, address(priceFeed), signers, 1, admins); diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/DVNFeeLibMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/DVNFeeLibMock.sol new file mode 100644 index 000000000..53816cd6c --- /dev/null +++ b/packages/test-devtools-evm-foundry/contracts/mocks/DVNFeeLibMock.sol @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: LZBL-1.2 + +pragma solidity ^0.8.20; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol"; + +import { ILayerZeroPriceFeed } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/ILayerZeroPriceFeed.sol"; + +import { IDVN } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IDVN.sol"; +import { IDVNFeeLib } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IDVNFeeLib.sol"; +import { DVNOptions } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/libs/DVNOptions.sol"; + +contract DVNFeeLibMock is Ownable, IDVNFeeLib { + using DVNOptions for bytes; + + uint16 internal constant EXECUTE_FIXED_BYTES = 68; // encoded: funcSigHash + params -> 4 + (32 * 2) + uint16 internal constant SIGNATURE_RAW_BYTES = 65; // not encoded + // callData(updateHash) = 132 (4 + 32 * 4), padded to 32 = 160 and encoded as bytes with an 64 byte overhead = 224 + uint16 internal constant UPDATE_HASH_BYTES = 224; + + uint256 private immutable nativeDecimalsRate; + + // @dev oz4/5 breaking change... Ownable constructor + constructor(uint256 _nativeDecimalsRate) Ownable(msg.sender) { + nativeDecimalsRate = _nativeDecimalsRate; + } + + // ================================ OnlyOwner ================================ + function withdrawToken(address _token, address _to, uint256 _amount) external onlyOwner { + // transfers native if _token is address(0x0) + Transfer.nativeOrToken(_token, _to, _amount); + } + + // ========================= External ========================= + /// @dev get fee function that can change state. e.g. paying priceFeed + /// @param _params fee params + /// @param _dstConfig dst config + /// @param //_options options + function getFeeOnSend( + FeeParams calldata _params, + IDVN.DstConfig calldata _dstConfig, + bytes calldata _options + ) external payable returns (uint256) { + if (_dstConfig.gas == 0) revert DVN_EidNotSupported(_params.dstEid); + + _decodeDVNOptions(_options); // todo: validate options + + uint256 callDataSize = _getCallDataSize(_params.quorum); + + // for future versions where priceFeed charges a fee + // uint256 priceFeedFee = ILayerZeroPriceFeed(_params.priceFeed).getFee(_params.dstEid, callDataSize, _dstConfig.gas); + // (uint256 fee, , , uint128 nativePriceUSD) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeOnSend{ + // value: priceFeedFee + // }(_params.dstEid, callDataSize, _dstConfig.gas); + + (uint256 fee, , , uint128 nativePriceUSD) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeOnSend( + _params.dstEid, + callDataSize, + _dstConfig.gas + ); + + return + _applyPremium( + fee, + _dstConfig.multiplierBps, + _params.defaultMultiplierBps, + _dstConfig.floorMarginUSD, + nativePriceUSD + ); + } + + // ========================= View ========================= + /// @dev get fee view function + /// @param _params fee params + /// @param _dstConfig dst config + /// @param //_options options + function getFee( + FeeParams calldata _params, + IDVN.DstConfig calldata _dstConfig, + bytes calldata _options + ) external view returns (uint256) { + if (_dstConfig.gas == 0) revert DVN_EidNotSupported(_params.dstEid); + + _decodeDVNOptions(_options); // validate options + + uint256 callDataSize = _getCallDataSize(_params.quorum); + (uint256 fee, , , uint128 nativePriceUSD) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeByEid( + _params.dstEid, + callDataSize, + _dstConfig.gas + ); + return + _applyPremium( + fee, + _dstConfig.multiplierBps, + _params.defaultMultiplierBps, + _dstConfig.floorMarginUSD, + nativePriceUSD + ); + } + + // ========================= Internal ========================= + function _getCallDataSize(uint256 _quorum) internal pure returns (uint256) { + uint256 totalSignatureBytes = _quorum * SIGNATURE_RAW_BYTES; + if (totalSignatureBytes % 32 != 0) { + totalSignatureBytes = totalSignatureBytes - (totalSignatureBytes % 32) + 32; + } + // getFee should charge on execute(updateHash) + // totalSignatureBytesPadded also has 64 overhead for bytes + return uint256(EXECUTE_FIXED_BYTES) + UPDATE_HASH_BYTES + totalSignatureBytes + 64; + } + + function _applyPremium( + uint256 _fee, + uint16 _bps, + uint16 _defaultBps, + uint128 _marginUSD, + uint128 _nativePriceUSD + ) internal view returns (uint256) { + uint16 multiplierBps = _bps == 0 ? _defaultBps : _bps; + + uint256 feeWithMultiplier = (_fee * multiplierBps) / 10000; + if (_nativePriceUSD == 0 || _marginUSD == 0) { + return feeWithMultiplier; + } + + uint256 feeWithFloorMargin = _fee + (_marginUSD * nativeDecimalsRate) / _nativePriceUSD; + + return feeWithFloorMargin > feeWithMultiplier ? feeWithFloorMargin : feeWithMultiplier; + } + + function _decodeDVNOptions(bytes calldata _options) internal pure returns (uint256) { + uint256 cursor; + while (cursor < _options.length) { + (uint8 optionType, , uint256 newCursor) = _options.nextDVNOption(cursor); + cursor = newCursor; + revert DVN_UnsupportedOptionType(optionType); + } + if (cursor != _options.length) revert DVNOptions.DVN_InvalidDVNOptions(cursor); + + return 0; // todo: precrime fee model + } + + // send funds here to pay for price feed directly + receive() external payable {} +} diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol new file mode 100644 index 000000000..5bebc9709 --- /dev/null +++ b/packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +pragma solidity ^0.8.20; + +import { ILayerZeroUltraLightNodeV2 } from "@layerzerolabs/lz-evm-v1-0.7/contracts/interfaces/ILayerZeroUltraLightNodeV2.sol"; + +import { WorkerMock as Worker } from "./WorkerMock.sol"; + +import { MultiSigMock as MultiSig } from "./MultiSigMock.sol"; +import { IDVN } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IDVN.sol"; +import { IDVNFeeLib } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IDVNFeeLib.sol"; +import { IReceiveUlnE2 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol"; + +struct ExecuteParam { + uint32 vid; + address target; + bytes callData; + uint256 expiration; + bytes signatures; +} + +contract DVNMock is Worker, MultiSig, IDVN { + // to uniquely identify this DVN instance + // set to endpoint v1 eid if available OR endpoint v2 eid % 30_000 + uint32 public immutable vid; + + mapping(uint32 dstEid => DstConfig) public dstConfig; + mapping(bytes32 executableHash => bool used) public usedHashes; + + error DVN_OnlySelf(); + error DVN_InvalidRole(bytes32 role); + error DVN_InstructionExpired(); + error DVN_InvalidTarget(address target); + error DVN_InvalidVid(uint32 vid); + error DVN_InvalidSignatures(); + error DVN_DuplicatedHash(bytes32 executableHash); + + event VerifySignaturesFailed(uint256 idx); + event ExecuteFailed(uint256 _index, bytes _data); + event HashAlreadyUsed(ExecuteParam param, bytes32 _hash); + // same as DVNFeePaid, but for ULNv2 + event VerifierFeePaid(uint256 fee); + + // ========================= Constructor ========================= + + /// @dev DVN doesn't have a roleAdmin (address(0x0)) + /// @dev Supports all of ULNv2, ULN301, ULN302 and more + /// @param _vid unique identifier for this DVN instance + /// @param _messageLibs array of message lib addresses that are granted the MESSAGE_LIB_ROLE + /// @param _priceFeed price feed address + /// @param _signers array of signer addresses for multisig + /// @param _quorum quorum for multisig + /// @param _admins array of admin addresses that are granted the ADMIN_ROLE + constructor( + uint32 _vid, + address[] memory _messageLibs, + address _priceFeed, + address[] memory _signers, + uint64 _quorum, + address[] memory _admins + ) Worker(_messageLibs, _priceFeed, 12000, address(0x0), _admins) MultiSig(_signers, _quorum) { + vid = _vid; + } + + // ========================= Modifier ========================= + + /// @dev depending on role, restrict access to only self or admin + /// @dev ALLOWLIST, DENYLIST, MESSAGE_LIB_ROLE can only be granted/revoked by self + /// @dev ADMIN_ROLE can only be granted/revoked by admin + /// @dev reverts if not one of the above roles + /// @param _role role to check + modifier onlySelfOrAdmin(bytes32 _role) { + if (_role == ALLOWLIST || _role == DENYLIST || _role == MESSAGE_LIB_ROLE) { + // self required + if (address(this) != msg.sender) { + revert DVN_OnlySelf(); + } + } else if (_role == ADMIN_ROLE) { + // admin required + _checkRole(ADMIN_ROLE); + } else { + revert DVN_InvalidRole(_role); + } + _; + } + + modifier onlySelf() { + if (address(this) != msg.sender) { + revert DVN_OnlySelf(); + } + _; + } + + // ========================= OnlySelf ========================= + + /// @dev set signers for multisig + /// @dev function sig 0x31cb6105 + /// @param _signer signer address + /// @param _active true to add, false to remove + function setSigner(address _signer, bool _active) external onlySelf { + _setSigner(_signer, _active); + } + + /// @dev set quorum for multisig + /// @dev function sig 0x8585c945 + /// @param _quorum to set + function setQuorum(uint64 _quorum) external onlySelf { + _setQuorum(_quorum); + } + + // ========================= OnlySelf / OnlyAdmin ========================= + + /// @dev overrides AccessControl to allow self/admin to grant role' + /// @dev function sig 0x2f2ff15d + /// @param _role role to grant + /// @param _account account to grant role to + function grantRole(bytes32 _role, address _account) public override onlySelfOrAdmin(_role) { + _grantRole(_role, _account); + } + + /// @dev overrides AccessControl to allow self/admin to revoke role + /// @dev function sig 0xd547741f + /// @param _role role to revoke + /// @param _account account to revoke role from + function revokeRole(bytes32 _role, address _account) public override onlySelfOrAdmin(_role) { + _revokeRole(_role, _account); + } + + // ========================= OnlyQuorum ========================= + + /// @notice function for quorum to change admin without going through execute function + /// @dev calldata in the case is abi.encode new admin address + function quorumChangeAdmin(ExecuteParam calldata _param) external { + if (_param.expiration <= block.timestamp) { + revert DVN_InstructionExpired(); + } + if (_param.target != address(this)) { + revert DVN_InvalidTarget(_param.target); + } + if (_param.vid != vid) { + revert DVN_InvalidVid(_param.vid); + } + + // generate and validate hash + bytes32 hash = hashCallData(_param.vid, _param.target, _param.callData, _param.expiration); + (bool sigsValid, ) = verifySignatures(hash, _param.signatures); + if (!sigsValid) { + revert DVN_InvalidSignatures(); + } + if (usedHashes[hash]) { + revert DVN_DuplicatedHash(hash); + } + + usedHashes[hash] = true; + _grantRole(ADMIN_ROLE, abi.decode(_param.callData, (address))); + } + + // ========================= OnlyAdmin ========================= + + /// @param _params array of DstConfigParam + function setDstConfig(DstConfigParam[] calldata _params) external onlyRole(ADMIN_ROLE) { + for (uint256 i = 0; i < _params.length; ++i) { + DstConfigParam calldata param = _params[i]; + dstConfig[param.dstEid] = DstConfig(param.gas, param.multiplierBps, param.floorMarginUSD); + } + emit SetDstConfig(_params); + } + + /// @dev takes a list of instructions and executes them in order + /// @dev if any of the instructions fail, it will emit an error event and continue to execute the rest of the instructions + /// @param _params array of ExecuteParam, includes target, callData, expiration, signatures + function execute(ExecuteParam[] calldata _params) external onlyRole(ADMIN_ROLE) { + for (uint256 i = 0; i < _params.length; ++i) { + ExecuteParam calldata param = _params[i]; + // 1. skip if invalid vid + if (param.vid != vid) { + continue; + } + + // 2. skip if expired + if (param.expiration <= block.timestamp) { + continue; + } + + // generate and validate hash + bytes32 hash = hashCallData(param.vid, param.target, param.callData, param.expiration); + + // 3. check signatures + (bool sigsValid, ) = verifySignatures(hash, param.signatures); + if (!sigsValid) { + emit VerifySignaturesFailed(i); + continue; + } + + // 4. should check hash + bool shouldCheckHash = _shouldCheckHash(bytes4(param.callData)); + if (shouldCheckHash) { + if (usedHashes[hash]) { + emit HashAlreadyUsed(param, hash); + continue; + } else { + usedHashes[hash] = true; // prevent reentry and replay attack + } + } + + (bool success, bytes memory rtnData) = param.target.call(param.callData); + if (!success) { + if (shouldCheckHash) { + // need to unset the usedHash otherwise it cant be used + usedHashes[hash] = false; + } + // emit an event in any case + emit ExecuteFailed(i, rtnData); + } + } + } + + /// @dev to support ULNv2 + /// @dev the withdrawFee function for ULN30X is built in the Worker contract + /// @param _lib message lib address + /// @param _to address to withdraw to + /// @param _amount amount to withdraw + function withdrawFeeFromUlnV2(address _lib, address payable _to, uint256 _amount) external onlyRole(ADMIN_ROLE) { + if (!hasRole(MESSAGE_LIB_ROLE, _lib)) { + revert Worker_OnlyMessageLib(); + } + ILayerZeroUltraLightNodeV2(_lib).withdrawNative(_to, _amount); + } + + // ========================= OnlyMessageLib ========================= + + /// @dev for ULN301, ULN302 and more to assign job + /// @dev dvn network can reject job from _sender by adding/removing them from allowlist/denylist + /// @param _param assign job param + /// @param _options dvn options + function assignJob( + AssignJobParam calldata _param, + bytes calldata _options + ) external payable onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_param.sender) returns (uint256 totalFee) { + IDVNFeeLib.FeeParams memory feeParams = IDVNFeeLib.FeeParams( + priceFeed, + _param.dstEid, + _param.confirmations, + _param.sender, + quorum, + defaultMultiplierBps + ); + totalFee = IDVNFeeLib(workerFeeLib).getFeeOnSend(feeParams, dstConfig[_param.dstEid], _options); + } + + /// @dev to support ULNv2 + /// @dev dvn network can reject job from _sender by adding/removing them from allowlist/denylist + /// @param _dstEid destination EndpointId + /// @param //_outboundProofType outbound proof type + /// @param _confirmations block confirmations + /// @param _sender message sender address + function assignJob( + uint16 _dstEid, + uint16 /*_outboundProofType*/, + uint64 _confirmations, + address _sender + ) external onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) returns (uint256 totalFee) { + IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams( + priceFeed, + _dstEid, + _confirmations, + _sender, + quorum, + defaultMultiplierBps + ); + // ULNV2 does not have dvn options + totalFee = IDVNFeeLib(workerFeeLib).getFeeOnSend(params, dstConfig[_dstEid], bytes("")); + emit VerifierFeePaid(totalFee); + } + + // ========================= View ========================= + + /// @dev getFee can revert if _sender doesn't pass ACL + /// @param _dstEid destination EndpointId + /// @param _confirmations block confirmations + /// @param _sender message sender address + /// @param _options dvn options + /// @return fee fee in native amount + function getFee( + uint32 _dstEid, + uint64 _confirmations, + address _sender, + bytes calldata _options + ) external view onlyAcl(_sender) returns (uint256 fee) { + IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams( + priceFeed, + _dstEid, + _confirmations, + _sender, + quorum, + defaultMultiplierBps + ); + return IDVNFeeLib(workerFeeLib).getFee(params, dstConfig[_dstEid], _options); + } + + /// @dev to support ULNv2 + /// @dev getFee can revert if _sender doesn't pass ACL + /// @param _dstEid destination EndpointId + /// @param //_outboundProofType outbound proof type + /// @param _confirmations block confirmations + /// @param _sender message sender address + function getFee( + uint16 _dstEid, + uint16 /*_outboundProofType*/, + uint64 _confirmations, + address _sender + ) public view onlyAcl(_sender) returns (uint256 fee) { + IDVNFeeLib.FeeParams memory params = IDVNFeeLib.FeeParams( + priceFeed, + _dstEid, + _confirmations, + _sender, + quorum, + defaultMultiplierBps + ); + return IDVNFeeLib(workerFeeLib).getFee(params, dstConfig[_dstEid], bytes("")); + } + + /// @param _target target address + /// @param _callData call data + /// @param _expiration expiration timestamp + /// @return hash of above + function hashCallData( + uint32 _vid, + address _target, + bytes calldata _callData, + uint256 _expiration + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_vid, _target, _expiration, _callData)); + } + + // ========================= Internal ========================= + + /// @dev to save gas, we don't check hash for some functions (where replaying won't change the state) + /// @dev for example, some administrative functions like changing signers, the contract should check hash to double spending + /// @dev should ensure that all onlySelf functions have unique functionSig + /// @param _functionSig function signature + /// @return true if should check hash + function _shouldCheckHash(bytes4 _functionSig) internal pure returns (bool) { + // never check for these selectors to save gas + return + _functionSig != IReceiveUlnE2.verify.selector && // 0x0223536e, replaying won't change the state + _functionSig != ILayerZeroUltraLightNodeV2.updateHash.selector; // 0x704316e5, replaying will be revert at uln + } +} diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/EndpointV2Mock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/EndpointV2Mock.sol new file mode 100644 index 000000000..ebe0586b2 --- /dev/null +++ b/packages/test-devtools-evm-foundry/contracts/mocks/EndpointV2Mock.sol @@ -0,0 +1,376 @@ +// SPDX-License-Identifier: LZBL-1.2 + +pragma solidity ^0.8.20; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +// @dev oz4/5 breaking change... Ownable constructor +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { MessagingFee, MessagingParams, MessagingReceipt, Origin, ILayerZeroEndpointV2 } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; +import { ISendLib, Packet } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol"; +import { ILayerZeroReceiver } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroReceiver.sol"; +import { Errors } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Errors.sol"; +import { GUID } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/GUID.sol"; +import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol"; +import { MessagingChannel } from "@layerzerolabs/lz-evm-protocol-v2/contracts/MessagingChannel.sol"; +import { MessagingComposer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/MessagingComposer.sol"; +import { MessageLibManager } from "@layerzerolabs/lz-evm-protocol-v2/contracts/MessageLibManager.sol"; +import { MessagingContext } from "@layerzerolabs/lz-evm-protocol-v2/contracts/MessagingContext.sol"; + +// LayerZero EndpointV2 is fully backward compatible with LayerZero Endpoint(V1), but it also supports additional +// features that Endpoint(V1) does not support now and may not in the future. We have also changed some terminology +// to clarify pre-existing language that might have been confusing. +// +// The following is a list of terminology changes: +// -chainId -> eid +// - Rationale: chainId was a term we initially used to describe an endpoint on a specific chain. Since +// LayerZero supports non-EVMs we could not map the classic EVM chainIds to the LayerZero chainIds, making it +// confusing for developers. With the addition of EndpointV2 and its backward compatible nature, we would have +// two chainIds per chain that has Endpoint(V1), further confusing developers. We have decided to change the +// name to Endpoint Id, or eid, for simplicity and clarity. +// -adapterParams -> options +// -userApplication -> oapp. Omnichain Application +// -srcAddress -> sender +// -dstAddress -> receiver +// - Rationale: The sender/receiver on EVM is the address. However, on non-EVM chains, the sender/receiver could +// represented as a public key, or some other identifier. The term sender/receiver is more generic +// -payload -> message. +// - Rationale: The term payload is used in the context of a packet, which is a combination of the message and GUID +contract EndpointV2Mock is + ILayerZeroEndpointV2, + MessagingChannel, + MessageLibManager, + MessagingComposer, + MessagingContext +{ + address public lzToken; + + mapping(address oapp => address delegate) public delegates; + + /// @param _eid the unique Endpoint Id for this deploy that all other Endpoints can use to send to it + // @dev oz4/5 breaking change... Ownable constructor + constructor(uint32 _eid, address _owner) Ownable(_owner) MessagingChannel(_eid) {} + + /// @dev MESSAGING STEP 0 + /// @notice This view function gives the application built on top of LayerZero the ability to requests a quote + /// with the same parameters as they would to send their message. Since the quotes are given on chain there is a + /// race condition in which the prices could change between the time the user gets their quote and the time they + /// submit their message. If the price moves up and the user doesn't send enough funds the transaction will revert, + /// if the price goes down the _refundAddress provided by the app will be refunded the difference. + /// @param _params the messaging parameters + /// @param _sender the sender of the message + function quote(MessagingParams calldata _params, address _sender) external view returns (MessagingFee memory) { + // lzToken must be set to support payInLzToken + if (_params.payInLzToken && lzToken == address(0x0)) revert Errors.LZ_LzTokenUnavailable(); + + // get the correct outbound nonce + uint64 nonce = outboundNonce[_sender][_params.dstEid][_params.receiver] + 1; + + // construct the packet with a GUID + Packet memory packet = Packet({ + nonce: nonce, + srcEid: eid, + sender: _sender, + dstEid: _params.dstEid, + receiver: _params.receiver, + guid: GUID.generate(nonce, eid, _sender, _params.dstEid, _params.receiver), + message: _params.message + }); + + // get the send library by sender and dst eid + // use _ to avoid variable shadowing + address _sendLibrary = getSendLibrary(_sender, _params.dstEid); + + return ISendLib(_sendLibrary).quote(packet, _params.options, _params.payInLzToken); + } + + /// @dev MESSAGING STEP 1 - OApp need to transfer the fees to the endpoint before sending the message + /// @param _params the messaging parameters + /// @param _refundAddress the address to refund both the native and lzToken + function send( + MessagingParams calldata _params, + address _refundAddress + ) external payable sendContext(_params.dstEid, msg.sender) returns (MessagingReceipt memory) { + if (_params.payInLzToken && lzToken == address(0x0)) revert Errors.LZ_LzTokenUnavailable(); + + // send message + (MessagingReceipt memory receipt, address _sendLibrary) = _send(msg.sender, _params); + + // OApp can simulate with 0 native value it will fail with error including the required fee, which can be provided in the actual call + // this trick can be used to avoid the need to write the quote() function + // however, without the quote view function it will be hard to compose an oapp on chain + uint256 suppliedNative = _suppliedNative(); + uint256 suppliedLzToken = _suppliedLzToken(_params.payInLzToken); + _assertMessagingFee(receipt.fee, suppliedNative, suppliedLzToken); + + // handle lz token fees + _payToken(lzToken, receipt.fee.lzTokenFee, suppliedLzToken, _sendLibrary, _refundAddress); + + // handle native fees + _payNative(receipt.fee.nativeFee, suppliedNative, _sendLibrary, _refundAddress); + + return receipt; + } + + /// @dev internal function for sending the messages used by all external send methods + /// @param _sender the address of the application sending the message to the destination chain + /// @param _params the messaging parameters + function _send( + address _sender, + MessagingParams calldata _params + ) internal returns (MessagingReceipt memory, address) { + // get the correct outbound nonce + uint64 latestNonce = _outbound(_sender, _params.dstEid, _params.receiver); + + // construct the packet with a GUID + Packet memory packet = Packet({ + nonce: latestNonce, + srcEid: eid, + sender: _sender, + dstEid: _params.dstEid, + receiver: _params.receiver, + guid: GUID.generate(latestNonce, eid, _sender, _params.dstEid, _params.receiver), + message: _params.message + }); + + // get the send library by sender and dst eid + address _sendLibrary = getSendLibrary(_sender, _params.dstEid); + + // messageLib always returns encodedPacket with guid + (MessagingFee memory fee, bytes memory encodedPacket) = ISendLib(_sendLibrary).send( + packet, + _params.options, + _params.payInLzToken + ); + + // Emit packet information for DVNs, Executors, and any other offchain infrastructure to only listen + // for this one event to perform their actions. + emit PacketSent(encodedPacket, _params.options, _sendLibrary); + + return (MessagingReceipt(packet.guid, latestNonce, fee), _sendLibrary); + } + + /// @dev MESSAGING STEP 2 - on the destination chain + /// @dev configured receive library verifies a message + /// @param _origin a struct holding the srcEid, nonce, and sender of the message + /// @param _receiver the receiver of the message + /// @param _payloadHash the payload hash of the message + function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external { + if (!isValidReceiveLibrary(_receiver, _origin.srcEid, msg.sender)) revert Errors.LZ_InvalidReceiveLibrary(); + + uint64 lazyNonce = lazyInboundNonce[_receiver][_origin.srcEid][_origin.sender]; + if (!_initializable(_origin, _receiver, lazyNonce)) revert Errors.LZ_PathNotInitializable(); + if (!_verifiable(_origin, _receiver, lazyNonce)) revert Errors.LZ_PathNotVerifiable(); + + // insert the message into the message channel + _inbound(_receiver, _origin.srcEid, _origin.sender, _origin.nonce, _payloadHash); + emit PacketVerified(_origin, _receiver, _payloadHash); + } + + /// @dev MESSAGING STEP 3 - the last step + /// @dev execute a verified message to the designated receiver + /// @dev the execution provides the execution context (caller, extraData) to the receiver. the receiver can optionally assert the caller and validate the untrusted extraData + /// @dev cant reentrant because the payload is cleared before execution + /// @param _origin the origin of the message + /// @param _receiver the receiver of the message + /// @param _guid the guid of the message + /// @param _message the message + /// @param _extraData the extra data provided by the executor. this data is untrusted and should be validated. + function lzReceive( + Origin calldata _origin, + address _receiver, + bytes32 _guid, + bytes calldata _message, + bytes calldata _extraData + ) external payable { + // clear the payload first to prevent reentrancy, and then execute the message + _clearPayload(_receiver, _origin.srcEid, _origin.sender, _origin.nonce, abi.encodePacked(_guid, _message)); + ILayerZeroReceiver(_receiver).lzReceive{ value: msg.value }(_origin, _guid, _message, msg.sender, _extraData); + emit PacketDelivered(_origin, _receiver); + } + + /// @param _origin the origin of the message + /// @param _receiver the receiver of the message + /// @param _guid the guid of the message + /// @param _message the message + /// @param _extraData the extra data provided by the executor. + /// @param _reason the reason for failure + function lzReceiveAlert( + Origin calldata _origin, + address _receiver, + bytes32 _guid, + uint256 _gas, + uint256 _value, + bytes calldata _message, + bytes calldata _extraData, + bytes calldata _reason + ) external { + emit LzReceiveAlert(_receiver, msg.sender, _origin, _guid, _gas, _value, _message, _extraData, _reason); + } + + /// @dev Oapp uses this interface to clear a message. + /// @dev this is a PULL mode versus the PUSH mode of lzReceive + /// @dev the cleared message can be ignored by the app (effectively burnt) + /// @dev authenticated by oapp + /// @param _origin the origin of the message + /// @param _guid the guid of the message + /// @param _message the message + function clear(address _oapp, Origin calldata _origin, bytes32 _guid, bytes calldata _message) external { + _assertAuthorized(_oapp); + + bytes memory payload = abi.encodePacked(_guid, _message); + _clearPayload(_oapp, _origin.srcEid, _origin.sender, _origin.nonce, payload); + emit PacketDelivered(_origin, _oapp); + } + + /// @dev allows reconfiguration to recover from wrong configurations + /// @dev users should never approve the EndpointV2 contract to spend their non-layerzero tokens + /// @dev override this function if the endpoint is charging ERC20 tokens as native + /// @dev only owner + /// @param _lzToken the new layer zero token address + function setLzToken(address _lzToken) public virtual onlyOwner { + lzToken = _lzToken; + emit LzTokenSet(_lzToken); + } + + /// @dev recover the token sent to this contract by mistake + /// @dev only owner + /// @param _token the token to recover. if 0x0 then it is native token + /// @param _to the address to send the token to + /// @param _amount the amount to send + function recoverToken(address _token, address _to, uint256 _amount) external onlyOwner { + Transfer.nativeOrToken(_token, _to, _amount); + } + + /// @dev handling token payments on endpoint. the sender must approve the endpoint to spend the token + /// @dev internal function + /// @param _token the token to pay + /// @param _required the amount required + /// @param _supplied the amount supplied + /// @param _receiver the receiver of the token + function _payToken( + address _token, + uint256 _required, + uint256 _supplied, + address _receiver, + address _refundAddress + ) internal { + if (_required > 0) { + Transfer.token(_token, _receiver, _required); + } + if (_required < _supplied) { + unchecked { + // refund the excess + Transfer.token(_token, _refundAddress, _supplied - _required); + } + } + } + + /// @dev handling native token payments on endpoint + /// @dev override this if the endpoint is charging ERC20 tokens as native + /// @dev internal function + /// @param _required the amount required + /// @param _supplied the amount supplied + /// @param _receiver the receiver of the native token + /// @param _refundAddress the address to refund the excess to + function _payNative( + uint256 _required, + uint256 _supplied, + address _receiver, + address _refundAddress + ) internal virtual { + if (_required > 0) { + Transfer.native(_receiver, _required); + } + if (_required < _supplied) { + unchecked { + // refund the excess + Transfer.native(_refundAddress, _supplied - _required); + } + } + } + + /// @dev get the balance of the lzToken as the supplied lzToken fee if payInLzToken is true + function _suppliedLzToken(bool _payInLzToken) internal view returns (uint256 supplied) { + if (_payInLzToken) { + supplied = IERC20(lzToken).balanceOf(address(this)); + + // if payInLzToken is true, the supplied fee must be greater than 0 to prevent a race condition + // in which an oapp sending a message with lz token and the lz token is set to a new token between the tx + // being sent and the tx being mined. if the required lz token fee is 0 and the old lz token would be + // locked in the contract instead of being refunded + if (supplied == 0) revert Errors.LZ_ZeroLzTokenFee(); + } + } + + /// @dev override this if the endpoint is charging ERC20 tokens as native + function _suppliedNative() internal view virtual returns (uint256) { + return msg.value; + } + + /// @dev Assert the required fees and the supplied fees are enough + function _assertMessagingFee( + MessagingFee memory _required, + uint256 _suppliedNativeFee, + uint256 _suppliedLzTokenFee + ) internal pure { + if (_required.nativeFee > _suppliedNativeFee || _required.lzTokenFee > _suppliedLzTokenFee) { + revert Errors.LZ_InsufficientFee( + _required.nativeFee, + _suppliedNativeFee, + _required.lzTokenFee, + _suppliedLzTokenFee + ); + } + } + + /// @dev override this if the endpoint is charging ERC20 tokens as native + /// @return 0x0 if using native. otherwise the address of the native ERC20 token + function nativeToken() external view virtual returns (address) { + return address(0x0); + } + + /// @notice delegate is authorized by the oapp to configure anything in layerzero + function setDelegate(address _delegate) external { + delegates[msg.sender] = _delegate; + emit DelegateSet(msg.sender, _delegate); + } + + // ========================= Internal ========================= + function _initializable( + Origin calldata _origin, + address _receiver, + uint64 _lazyInboundNonce + ) internal view returns (bool) { + return + _lazyInboundNonce > 0 || // allowInitializePath already checked + ILayerZeroReceiver(_receiver).allowInitializePath(_origin); + } + + /// @dev bytes(0) payloadHash can never be submitted + function _verifiable( + Origin calldata _origin, + address _receiver, + uint64 _lazyInboundNonce + ) internal view returns (bool) { + return + _origin.nonce > _lazyInboundNonce || // either initializing an empty slot or reverifying + inboundPayloadHash[_receiver][_origin.srcEid][_origin.sender][_origin.nonce] != EMPTY_PAYLOAD_HASH; // only allow reverifying if it hasn't been executed + } + + /// @dev assert the caller to either be the oapp or the delegate + function _assertAuthorized(address _oapp) internal view override(MessagingChannel, MessageLibManager) { + if (msg.sender != _oapp && msg.sender != delegates[_oapp]) revert Errors.LZ_Unauthorized(); + } + + // ========================= VIEW FUNCTIONS FOR OFFCHAIN ONLY ========================= + // Not involved in any state transition function. + // ==================================================================================== + function initializable(Origin calldata _origin, address _receiver) external view returns (bool) { + return _initializable(_origin, _receiver, lazyInboundNonce[_receiver][_origin.srcEid][_origin.sender]); + } + + function verifiable(Origin calldata _origin, address _receiver) external view returns (bool) { + return _verifiable(_origin, _receiver, lazyInboundNonce[_receiver][_origin.srcEid][_origin.sender]); + } +} diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorFeeLibMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorFeeLibMock.sol index 48e5d4898..f54c81a51 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorFeeLibMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorFeeLibMock.sol @@ -1,12 +1,201 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; -import { ExecutorFeeLib } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/ExecutorFeeLib.sol"; +// @dev oz4/5 breaking change... Ownable constructor +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; -contract ExecutorFeeLibMock is ExecutorFeeLib { - constructor() ExecutorFeeLib(1e18) {} +import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol"; +import { ExecutorOptions } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/ExecutorOptions.sol"; - function _isV1Eid(uint32 /*_eid*/) internal pure override returns (bool) { +import { ILayerZeroPriceFeed } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/ILayerZeroPriceFeed.sol"; +import { IExecutor } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/IExecutor.sol"; +import { IExecutorFeeLib } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/IExecutorFeeLib.sol"; + +contract ExecutorFeeLibMock is Ownable, IExecutorFeeLib { + using ExecutorOptions for bytes; + + uint256 private immutable nativeDecimalsRate; + + // @dev oz4/5 breaking change... Ownable constructor + constructor() Ownable(msg.sender) { + nativeDecimalsRate = 1e18; + } + + // ================================ OnlyOwner ================================ + function withdrawToken(address _token, address _to, uint256 _amount) external onlyOwner { + // transfers native if _token is address(0x0) + Transfer.nativeOrToken(_token, _to, _amount); + } + + // ================================ External ================================ + function getFeeOnSend( + FeeParams calldata _params, + IExecutor.DstConfig calldata _dstConfig, + bytes calldata _options + ) external returns (uint256 fee) { + if (_dstConfig.baseGas == 0) revert Executor_EidNotSupported(_params.dstEid); + + (uint256 totalDstAmount, uint256 totalGas) = _decodeExecutorOptions( + _isV1Eid(_params.dstEid), + _dstConfig.baseGas, + _dstConfig.nativeCap, + _options + ); + + // for future versions where priceFeed charges a fee + ( + uint256 totalGasFee, + uint128 priceRatio, + uint128 priceRatioDenominator, + uint128 nativePriceUSD + ) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeOnSend(_params.dstEid, _params.calldataSize, totalGas); + + fee = _applyPremiumToGas( + totalGasFee, + _dstConfig.multiplierBps, + _params.defaultMultiplierBps, + _dstConfig.floorMarginUSD, + nativePriceUSD + ); + fee += _convertAndApplyPremiumToValue( + totalDstAmount, + priceRatio, + priceRatioDenominator, + _params.defaultMultiplierBps + ); + } + + // ================================ View ================================ + function getFee( + FeeParams calldata _params, + IExecutor.DstConfig calldata _dstConfig, + bytes calldata _options + ) external view returns (uint256 fee) { + if (_dstConfig.baseGas == 0) revert Executor_EidNotSupported(_params.dstEid); + + (uint256 totalDstAmount, uint256 totalGas) = _decodeExecutorOptions( + _isV1Eid(_params.dstEid), + _dstConfig.baseGas, + _dstConfig.nativeCap, + _options + ); + + ( + uint256 totalGasFee, + uint128 priceRatio, + uint128 priceRatioDenominator, + uint128 nativePriceUSD + ) = ILayerZeroPriceFeed(_params.priceFeed).estimateFeeByEid(_params.dstEid, _params.calldataSize, totalGas); + + fee = _applyPremiumToGas( + totalGasFee, + _dstConfig.multiplierBps, + _params.defaultMultiplierBps, + _dstConfig.floorMarginUSD, + nativePriceUSD + ); + fee += _convertAndApplyPremiumToValue( + totalDstAmount, + priceRatio, + priceRatioDenominator, + _params.defaultMultiplierBps + ); + } + + // ================================ Internal ================================ + // @dev decode executor options into dstAmount and totalGas + function _decodeExecutorOptions( + bool _v1Eid, + uint64 _baseGas, + uint128 _nativeCap, + bytes calldata _options + ) internal pure returns (uint256 dstAmount, uint256 totalGas) { + if (_options.length == 0) { + revert Executor_NoOptions(); + } + + uint256 cursor = 0; + bool ordered = false; + totalGas = _baseGas; + + uint256 lzReceiveGas; + while (cursor < _options.length) { + (uint8 optionType, bytes calldata option, uint256 newCursor) = _options.nextExecutorOption(cursor); + cursor = newCursor; + + if (optionType == ExecutorOptions.OPTION_TYPE_LZRECEIVE) { + (uint128 gas, uint128 value) = ExecutorOptions.decodeLzReceiveOption(option); + + // endpoint v1 does not support lzReceive with value + if (_v1Eid && value > 0) revert Executor_UnsupportedOptionType(optionType); + + dstAmount += value; + lzReceiveGas += gas; + } else if (optionType == ExecutorOptions.OPTION_TYPE_NATIVE_DROP) { + (uint128 nativeDropAmount, ) = ExecutorOptions.decodeNativeDropOption(option); + dstAmount += nativeDropAmount; + } else if (optionType == ExecutorOptions.OPTION_TYPE_LZCOMPOSE) { + // endpoint v1 does not support lzCompose + if (_v1Eid) revert Executor_UnsupportedOptionType(optionType); + + (, uint128 gas, uint128 value) = ExecutorOptions.decodeLzComposeOption(option); + dstAmount += value; + totalGas += gas; + } else if (optionType == ExecutorOptions.OPTION_TYPE_ORDERED_EXECUTION) { + ordered = true; + } else { + revert Executor_UnsupportedOptionType(optionType); + } + } + if (cursor != _options.length) revert Executor_InvalidExecutorOptions(cursor); + if (dstAmount > _nativeCap) revert Executor_NativeAmountExceedsCap(dstAmount, _nativeCap); + if (lzReceiveGas == 0) revert Executor_ZeroLzReceiveGasProvided(); + totalGas += lzReceiveGas; + + if (ordered) { + totalGas = (totalGas * 102) / 100; + } + } + + function _applyPremiumToGas( + uint256 _fee, + uint16 _bps, + uint16 _defaultBps, + uint128 _marginUSD, + uint128 _nativePriceUSD + ) internal view returns (uint256) { + uint16 multiplierBps = _bps == 0 ? _defaultBps : _bps; + + uint256 feeWithMultiplier = (_fee * multiplierBps) / 10000; + + if (_nativePriceUSD == 0 || _marginUSD == 0) { + return feeWithMultiplier; + } + uint256 feeWithMargin = (_marginUSD * nativeDecimalsRate) / _nativePriceUSD + _fee; + return feeWithMargin > feeWithMultiplier ? feeWithMargin : feeWithMultiplier; + } + + // includes value and nativeDrop + function _convertAndApplyPremiumToValue( + uint256 _value, + uint128 _ratio, + uint128 _denom, + uint16 _defaultBps + ) internal pure returns (uint256 fee) { + if (_value > 0) { + fee = (((_value * _ratio) / _denom) * _defaultBps) / 10000; + } + } + + function _isV1Eid(uint32 /*_eid*/) internal pure virtual returns (bool) { return false; } + + // function _isV1Eid(uint32 _eid) internal pure virtual returns (bool) { + // // v1 eid is < 30000 + // return _eid < 30000; + // } + + // send funds here to pay for price feed directly + receive() external payable {} } diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol new file mode 100644 index 000000000..c32ecdfcd --- /dev/null +++ b/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { ILayerZeroEndpointV2, Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; +import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol"; + +import { IUltraLightNode301 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/uln301/interfaces/IUltraLightNode301.sol"; +import { IExecutor } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/IExecutor.sol"; +import { IExecutorFeeLib } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/IExecutorFeeLib.sol"; + +// @dev oz4/5 breaking change... path +import { WorkerMock as Worker } from "./WorkerMock.sol"; +// @dev oz4/5 breaking change... upgradeable reentrancy +import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +contract ExecutorMock is Worker, ReentrancyGuard, IExecutor { + using PacketV1Codec for bytes; + + mapping(uint32 dstEid => DstConfig) public dstConfig; + + // endpoint v2 + address public endpoint; + uint32 public localEid; + + // endpoint v1 + address public receiveUln301; + + constructor( + address _endpoint, + address _receiveUln301, + address[] memory _messageLibs, + address _priceFeed, + address _roleAdmin, + address[] memory _admins + ) Worker(_messageLibs, _priceFeed, 12000, _roleAdmin, _admins) { + endpoint = _endpoint; + localEid = ILayerZeroEndpointV2(_endpoint).eid(); + receiveUln301 = _receiveUln301; + } + + // --- Admin --- + function setDstConfig(DstConfigParam[] memory _params) external onlyRole(ADMIN_ROLE) { + for (uint256 i = 0; i < _params.length; i++) { + DstConfigParam memory param = _params[i]; + dstConfig[param.dstEid] = DstConfig( + param.baseGas, + param.multiplierBps, + param.floorMarginUSD, + param.nativeCap + ); + } + emit DstConfigSet(_params); + } + + function nativeDrop( + Origin calldata _origin, + uint32 _dstEid, + address _oapp, + NativeDropParams[] calldata _nativeDropParams, + uint256 _nativeDropGasLimit + ) external payable onlyRole(ADMIN_ROLE) nonReentrant { + _nativeDrop(_origin, _dstEid, _oapp, _nativeDropParams, _nativeDropGasLimit); + } + + function nativeDropAndExecute301( + Origin calldata _origin, + NativeDropParams[] calldata _nativeDropParams, + uint256 _nativeDropGasLimit, + bytes calldata _packet, + uint256 _gasLimit + ) external payable onlyRole(ADMIN_ROLE) nonReentrant { + _nativeDrop(_origin, _packet.dstEid(), _packet.receiverB20(), _nativeDropParams, _nativeDropGasLimit); + IUltraLightNode301(receiveUln301).commitVerification(_packet, _gasLimit); + } + + function execute301(bytes calldata _packet, uint256 _gasLimit) external onlyRole(ADMIN_ROLE) nonReentrant { + IUltraLightNode301(receiveUln301).commitVerification(_packet, _gasLimit); + } + + function nativeDropAndExecute302( + NativeDropParams[] calldata _nativeDropParams, + uint256 _nativeDropGasLimit, + ExecutionParams calldata _executionParams + ) external payable onlyRole(ADMIN_ROLE) nonReentrant { + uint256 spent = _nativeDrop( + _executionParams.origin, + localEid, + _executionParams.receiver, + _nativeDropParams, + _nativeDropGasLimit + ); + + uint256 value = msg.value - spent; + // ignore the execution result + ILayerZeroEndpointV2(endpoint).lzReceive{ value: value, gas: _executionParams.gasLimit }( + _executionParams.origin, + _executionParams.receiver, + _executionParams.guid, + _executionParams.message, + _executionParams.extraData + ); + } + + // --- Message Lib --- + function assignJob( + uint32 _dstEid, + address _sender, + uint256 _calldataSize, + bytes calldata _options + ) external onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) returns (uint256 fee) { + IExecutorFeeLib.FeeParams memory params = IExecutorFeeLib.FeeParams( + priceFeed, + _dstEid, + _sender, + _calldataSize, + defaultMultiplierBps + ); + fee = IExecutorFeeLib(workerFeeLib).getFeeOnSend(params, dstConfig[_dstEid], _options); + } + + // --- Only ACL --- + function getFee( + uint32 _dstEid, + address _sender, + uint256 _calldataSize, + bytes calldata _options + ) external view onlyAcl(_sender) whenNotPaused returns (uint256 fee) { + IExecutorFeeLib.FeeParams memory params = IExecutorFeeLib.FeeParams( + priceFeed, + _dstEid, + _sender, + _calldataSize, + defaultMultiplierBps + ); + fee = IExecutorFeeLib(workerFeeLib).getFee(params, dstConfig[_dstEid], _options); + } + + function _nativeDrop( + Origin calldata _origin, + uint32 _dstEid, + address _oapp, + NativeDropParams[] calldata _nativeDropParams, + uint256 _nativeDropGasLimit + ) internal returns (uint256 spent) { + bool[] memory success = new bool[](_nativeDropParams.length); + for (uint256 i = 0; i < _nativeDropParams.length; i++) { + NativeDropParams memory param = _nativeDropParams[i]; + + (bool sent, ) = param.receiver.call{ value: param.amount, gas: _nativeDropGasLimit }(""); + + success[i] = sent; + spent += param.amount; + } + emit NativeDropApplied(_origin, _dstEid, _oapp, _nativeDropParams, success); + } +} diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/MultiSigMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/MultiSigMock.sol new file mode 100644 index 000000000..f0989af9b --- /dev/null +++ b/packages/test-devtools-evm-foundry/contracts/mocks/MultiSigMock.sol @@ -0,0 +1,108 @@ +// SPDX-License-Identifier: LZBL-1.2 + +pragma solidity ^0.8.20; + +import { ECDSA } from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; + +abstract contract MultiSigMock { + enum Errors { + NoError, + SignatureError, + DuplicatedSigner, + SignerNotInCommittee + } + + mapping(address signer => bool active) public signers; + uint64 public signerSize; + uint64 public quorum; + + error MultiSig_OnlySigner(); + error MultiSig_QuorumIsZero(); + error MultiSig_SignersSizeIsLessThanQuorum(uint64 signersSize, uint64 quorum); + error MultiSig_UnorderedSigners(); + error MultiSig_StateAlreadySet(address signer, bool active); + + event UpdateSigner(address _signer, bool _active); + event UpdateQuorum(uint64 _quorum); + + modifier onlySigner() { + if (!signers[msg.sender]) { + revert MultiSig_OnlySigner(); + } + _; + } + + constructor(address[] memory _signers, uint64 _quorum) { + if (_quorum == 0) { + revert MultiSig_QuorumIsZero(); + } + if (_signers.length < _quorum) { + revert MultiSig_SignersSizeIsLessThanQuorum(uint64(_signers.length), _quorum); + } + address lastSigner = address(0); + for (uint256 i = 0; i < _signers.length; i++) { + address signer = _signers[i]; + if (signer <= lastSigner) { + revert MultiSig_UnorderedSigners(); + } + signers[signer] = true; + lastSigner = signer; + } + signerSize = uint64(_signers.length); + quorum = _quorum; + } + + function _setSigner(address _signer, bool _active) internal { + if (signers[_signer] == _active) { + revert MultiSig_StateAlreadySet(_signer, _active); + } + signers[_signer] = _active; + uint64 _signerSize = _active ? signerSize + 1 : signerSize - 1; + uint64 _quorum = quorum; + if (_signerSize < _quorum) { + revert MultiSig_SignersSizeIsLessThanQuorum(_signerSize, _quorum); + } + signerSize = _signerSize; + emit UpdateSigner(_signer, _active); + } + + function _setQuorum(uint64 _quorum) internal { + if (_quorum == 0) { + revert MultiSig_QuorumIsZero(); + } + uint64 _signerSize = signerSize; + if (_signerSize < _quorum) { + revert MultiSig_SignersSizeIsLessThanQuorum(_signerSize, _quorum); + } + quorum = _quorum; + emit UpdateQuorum(_quorum); + } + + function verifySignatures(bytes32 _hash, bytes calldata _signatures) public view returns (bool, Errors) { + if (_signatures.length != uint256(quorum) * 65) { + return (false, Errors.SignatureError); + } + + bytes32 messageDigest = _getEthSignedMessageHash(_hash); + + address lastSigner = address(0); // There cannot be a signer with address 0. + for (uint256 i = 0; i < quorum; i++) { + bytes calldata signature = _signatures[i * 65:(i + 1) * 65]; + // @dev oz4/5 breaking change... return value from tryRecover + (address currentSigner, ECDSA.RecoverError error /*bytes32(signature.length)*/, ) = ECDSA.tryRecover( + messageDigest, + signature + ); + + if (error != ECDSA.RecoverError.NoError) return (false, Errors.SignatureError); + if (currentSigner <= lastSigner) return (false, Errors.DuplicatedSigner); // prevent duplicate signatures + if (!signers[currentSigner]) return (false, Errors.SignerNotInCommittee); // signature is not in committee + lastSigner = currentSigner; + } + return (true, Errors.NoError); + } + + function _getEthSignedMessageHash(bytes32 _messageHash) internal pure returns (bytes32) { + return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)); + } +} diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/PriceFeedMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/PriceFeedMock.sol new file mode 100644 index 000000000..efba712eb --- /dev/null +++ b/packages/test-devtools-evm-foundry/contracts/mocks/PriceFeedMock.sol @@ -0,0 +1,257 @@ +// SPDX-License-Identifier: LZBL-1.2 + +pragma solidity ^0.8.20; + +// @dev oz4/5 breaking change... Ownable constructor +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { ILayerZeroEndpointV2 } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; +import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol"; + +import { ILayerZeroPriceFeed } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/ILayerZeroPriceFeed.sol"; + +// PriceFeed is updated based on v1 eids +// v2 eids will fall to the convention of v1 eid + 30,000 +contract PriceFeedMock is ILayerZeroPriceFeed, Ownable { + uint128 internal PRICE_RATIO_DENOMINATOR; + + // sets pricing + mapping(address updater => bool active) public priceUpdater; + + mapping(uint32 dstEid => Price) internal _defaultModelPrice; + ArbitrumPriceExt internal _arbitrumPriceExt; + + uint128 internal _nativePriceUSD; // uses PRICE_RATIO_DENOMINATOR + + // upgrade: arbitrum compression - percentage of callDataSize after brotli compression + uint128 public ARBITRUM_COMPRESSION_PERCENT; + + ILayerZeroEndpointV2 public endpoint; + + // ============================ Constructor =================================== + + // @dev oz4/5 breaking change... Ownable constructor + constructor(address _priceUpdater) Ownable(msg.sender) { + priceUpdater[_priceUpdater] = true; + PRICE_RATIO_DENOMINATOR = 1e20; + ARBITRUM_COMPRESSION_PERCENT = 47; + } + + // ============================ Modifier ====================================== + + // owner is always approved + modifier onlyPriceUpdater() { + if (owner() != msg.sender) { + if (!priceUpdater[msg.sender]) { + revert LZ_PriceFeed_OnlyPriceUpdater(); + } + } + _; + } + + // ============================ OnlyOwner ===================================== + + function setPriceUpdater(address _addr, bool _active) external onlyOwner { + priceUpdater[_addr] = _active; + } + + function setPriceRatioDenominator(uint128 _denominator) external onlyOwner { + PRICE_RATIO_DENOMINATOR = _denominator; + } + + function setArbitrumCompressionPercent(uint128 _compressionPercent) external onlyOwner { + ARBITRUM_COMPRESSION_PERCENT = _compressionPercent; + } + + function setEndpoint(address _endpoint) external onlyOwner { + endpoint = ILayerZeroEndpointV2(_endpoint); + } + + function withdrawFee(address _to, uint256 _amount) external onlyOwner { + Transfer.native(_to, _amount); + } + + // ============================ OnlyPriceUpdater ===================================== + + function setPrice(UpdatePrice[] calldata _price) external onlyPriceUpdater { + for (uint256 i = 0; i < _price.length; i++) { + UpdatePrice calldata _update = _price[i]; + _setPrice(_update.eid, _update.price); + } + } + + function setPriceForArbitrum(UpdatePriceExt calldata _update) external onlyPriceUpdater { + _setPrice(_update.eid, _update.price); + + uint64 gasPerL2Tx = _update.extend.gasPerL2Tx; + uint32 gasPerL1CalldataByte = _update.extend.gasPerL1CallDataByte; + + _arbitrumPriceExt.gasPerL2Tx = gasPerL2Tx; + _arbitrumPriceExt.gasPerL1CallDataByte = gasPerL1CalldataByte; + } + + function setNativeTokenPriceUSD(uint128 _nativeTokenPriceUSD) external onlyPriceUpdater { + _nativePriceUSD = _nativeTokenPriceUSD; + } + + // ============================ External ===================================== + + function estimateFeeOnSend( + uint32 _dstEid, + uint256 _callDataSize, + uint256 _gas + ) external payable returns (uint256, uint128, uint128, uint128) { + uint256 fee = getFee(_dstEid, _callDataSize, _gas); + if (msg.value < fee) revert LZ_PriceFeed_InsufficientFee(msg.value, fee); + return _estimateFeeByEid(_dstEid, _callDataSize, _gas); + } + + // ============================ View ========================================== + + // get fee for calling estimateFeeOnSend + function getFee(uint32 /*_dstEid*/, uint256 /*_callDataSize*/, uint256 /*_gas*/) public pure returns (uint256) { + return 0; + } + + function getPriceRatioDenominator() external view returns (uint128) { + return PRICE_RATIO_DENOMINATOR; + } + + // NOTE: to be reverted when endpoint is in sendContext + function nativeTokenPriceUSD() external view returns (uint128) { + return _nativePriceUSD; + } + + // NOTE: to be reverted when endpoint is in sendContext + function arbitrumPriceExt() external view returns (ArbitrumPriceExt memory) { + return _arbitrumPriceExt; + } + + // NOTE: to be reverted when endpoint is in sendContext + function getPrice(uint32 _dstEid) external view returns (Price memory price) { + price = _defaultModelPrice[_dstEid]; + } + + // NOTE: to be reverted when endpoint is in sendContext + function estimateFeeByEid( + uint32 _dstEid, + uint256 _callDataSize, + uint256 _gas + ) external view returns (uint256, uint128, uint128, uint128) { + return _estimateFeeByEid(_dstEid, _callDataSize, _gas); + } + + // NOTE: to be reverted when endpoint is in sendContext + // NOTE: to support legacy + function getPrice(uint16 _dstEid) external view returns (Price memory price) { + price = _defaultModelPrice[_dstEid]; + } + + // NOTE: to be reverted when endpoint is in sendContext + // NOTE: to support legacy + function estimateFeeByChain( + uint16 _dstEid, + uint256 _callDataSize, + uint256 _gas + ) external view returns (uint256 fee, uint128 priceRatio) { + if (_dstEid == 110 || _dstEid == 10143 || _dstEid == 20143) { + return _estimateFeeWithArbitrumModel(_dstEid, _callDataSize, _gas); + } else if (_dstEid == 111 || _dstEid == 10132 || _dstEid == 20132) { + return _estimateFeeWithOptimismModel(_dstEid, _callDataSize, _gas); + } else { + return _estimateFeeWithDefaultModel(_dstEid, _callDataSize, _gas); + } + } + + // ============================ Internal ========================================== + + function _setPrice(uint32 _dstEid, Price memory _price) internal { + uint128 priceRatio = _price.priceRatio; + uint64 gasPriceInUnit = _price.gasPriceInUnit; + uint32 gasPerByte = _price.gasPerByte; + _defaultModelPrice[_dstEid] = Price(priceRatio, gasPriceInUnit, gasPerByte); + } + + function _getL1LookupId(uint32 _l2Eid) internal pure returns (uint32) { + uint32 l2Eid = _l2Eid % 30_000; + if (l2Eid == 111) { + return 101; + } else if (l2Eid == 10132) { + return 10121; // ethereum-goerli + } else if (l2Eid == 20132) { + return 20121; // ethereum-goerli + } + revert LZ_PriceFeed_UnknownL2Eid(l2Eid); + } + + function _estimateFeeWithDefaultModel( + uint32 _dstEid, + uint256 _callDataSize, + uint256 _gas + ) internal view returns (uint256 fee, uint128 priceRatio) { + Price storage remotePrice = _defaultModelPrice[_dstEid]; + + // assuming the _gas includes (1) the 21,000 overhead and (2) not the calldata gas + uint256 gasForCallData = _callDataSize * remotePrice.gasPerByte; + uint256 remoteFee = (gasForCallData + _gas) * remotePrice.gasPriceInUnit; + return ((remoteFee * remotePrice.priceRatio) / PRICE_RATIO_DENOMINATOR, remotePrice.priceRatio); + } + + function _estimateFeeByEid( + uint32 _dstEid, + uint256 _callDataSize, + uint256 _gas + ) internal view returns (uint256 fee, uint128 priceRatio, uint128 priceRatioDenominator, uint128 priceUSD) { + uint32 dstEid = _dstEid % 30_000; + if (dstEid == 110 || dstEid == 10143 || dstEid == 20143) { + (fee, priceRatio) = _estimateFeeWithArbitrumModel(dstEid, _callDataSize, _gas); + } else if (dstEid == 111 || dstEid == 10132 || dstEid == 20132) { + (fee, priceRatio) = _estimateFeeWithOptimismModel(dstEid, _callDataSize, _gas); + } else { + (fee, priceRatio) = _estimateFeeWithDefaultModel(dstEid, _callDataSize, _gas); + } + priceRatioDenominator = PRICE_RATIO_DENOMINATOR; + priceUSD = _nativePriceUSD; + } + + function _estimateFeeWithOptimismModel( + uint32 _dstEid, + uint256 _callDataSize, + uint256 _gas + ) internal view returns (uint256 fee, uint128 priceRatio) { + uint32 ethereumId = _getL1LookupId(_dstEid); + + // L1 fee + Price storage ethereumPrice = _defaultModelPrice[ethereumId]; + uint256 gasForL1CallData = (_callDataSize * ethereumPrice.gasPerByte) + 3188; // 2100 + 68 * 16 + uint256 l1Fee = gasForL1CallData * ethereumPrice.gasPriceInUnit; + + // L2 fee + Price storage optimismPrice = _defaultModelPrice[_dstEid]; + uint256 gasForL2CallData = _callDataSize * optimismPrice.gasPerByte; + uint256 l2Fee = (gasForL2CallData + _gas) * optimismPrice.gasPriceInUnit; + + uint256 l1FeeInSrcPrice = (l1Fee * ethereumPrice.priceRatio) / PRICE_RATIO_DENOMINATOR; + uint256 l2FeeInSrcPrice = (l2Fee * optimismPrice.priceRatio) / PRICE_RATIO_DENOMINATOR; + uint256 gasFee = l1FeeInSrcPrice + l2FeeInSrcPrice; + return (gasFee, optimismPrice.priceRatio); + } + + function _estimateFeeWithArbitrumModel( + uint32 _dstEid, + uint256 _callDataSize, + uint256 _gas + ) internal view returns (uint256 fee, uint128 priceRatio) { + Price storage arbitrumPrice = _defaultModelPrice[_dstEid]; + + // L1 fee + uint256 gasForL1CallData = ((_callDataSize * ARBITRUM_COMPRESSION_PERCENT) / 100) * + _arbitrumPriceExt.gasPerL1CallDataByte; + // L2 Fee + uint256 gasForL2CallData = _callDataSize * arbitrumPrice.gasPerByte; + uint256 gasFee = (_gas + _arbitrumPriceExt.gasPerL2Tx + gasForL1CallData + gasForL2CallData) * + arbitrumPrice.gasPriceInUnit; + + return ((gasFee * arbitrumPrice.priceRatio) / PRICE_RATIO_DENOMINATOR, arbitrumPrice.priceRatio); + } +} diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/ReceiveUln302Mock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/ReceiveUln302Mock.sol new file mode 100644 index 000000000..6a3f74a58 --- /dev/null +++ b/packages/test-devtools-evm-foundry/contracts/mocks/ReceiveUln302Mock.sol @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol"; +import { SetConfigParam } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol"; +import { ILayerZeroEndpointV2, Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; + +import { IReceiveUlnE2 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/interfaces/IReceiveUlnE2.sol"; +import { ReceiveUlnBase } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/ReceiveUlnBase.sol"; +import { ReceiveLibBaseE2 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/ReceiveLibBaseE2.sol"; +import { UlnConfig } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/UlnBase.sol"; + +contract ReceiveUln302Mock is IReceiveUlnE2, ReceiveUlnBase, ReceiveLibBaseE2 { + using PacketV1Codec for bytes; + + /// @dev CONFIG_TYPE_ULN=2 here to align with SendUln302/ReceiveUln302/ReceiveUln301 + uint32 internal constant CONFIG_TYPE_ULN = 2; + + error LZ_ULN_InvalidConfigType(uint32 configType); + + // @dev oz4/5 breaking change... Ownable constructor + constructor(address _endpoint) Ownable(msg.sender) ReceiveLibBaseE2(_endpoint) {} + + function supportsInterface(bytes4 _interfaceId) public view override returns (bool) { + return _interfaceId == type(IReceiveUlnE2).interfaceId || super.supportsInterface(_interfaceId); + } + + // ============================ OnlyEndpoint =================================== + + // only the ULN config on the receive side + function setConfig(address _oapp, SetConfigParam[] calldata _params) external override onlyEndpoint { + for (uint256 i = 0; i < _params.length; i++) { + SetConfigParam calldata param = _params[i]; + _assertSupportedEid(param.eid); + if (param.configType == CONFIG_TYPE_ULN) { + _setUlnConfig(param.eid, _oapp, abi.decode(param.config, (UlnConfig))); + } else { + revert LZ_ULN_InvalidConfigType(param.configType); + } + } + } + + // ============================ External =================================== + + /// @dev dont need to check endpoint verifiable here to save gas, as it will reverts if not verifiable. + function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external { + _assertHeader(_packetHeader, localEid); + + // cache these values to save gas + address receiver = _packetHeader.receiverB20(); + uint32 srcEid = _packetHeader.srcEid(); + + UlnConfig memory config = getUlnConfig(receiver, srcEid); + _verifyAndReclaimStorage(config, keccak256(_packetHeader), _payloadHash); + + Origin memory origin = Origin(srcEid, _packetHeader.sender(), _packetHeader.nonce()); + // endpoint will revert if nonce <= lazyInboundNonce + ILayerZeroEndpointV2(endpoint).verify(origin, receiver, _payloadHash); + } + + /// @dev for dvn to verify the payload + function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external { + _verify(_packetHeader, _payloadHash, _confirmations); + } + + // ============================ View =================================== + + function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view override returns (bytes memory) { + if (_configType == CONFIG_TYPE_ULN) { + return abi.encode(getUlnConfig(_oapp, _eid)); + } else { + revert LZ_ULN_InvalidConfigType(_configType); + } + } + + function isSupportedEid(uint32 _eid) external view override returns (bool) { + return _isSupportedEid(_eid); + } + + function version() external pure override returns (uint64 major, uint8 minor, uint8 endpointVersion) { + return (3, 0, 2); + } +} diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol index 5df282d69..ce181c0ce 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol @@ -1,25 +1,100 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; +// @dev oz4/5 breaking change... Ownable constructor +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +// structs import { Packet } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol"; import { MessagingFee } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; -import { SendUln302 } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/uln302/SendUln302.sol"; +import { ExecutorConfig } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/SendLibBase.sol"; +import { UlnConfig } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/UlnBase.sol"; + +// contracts +import { SendUlnBase } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/SendUlnBase.sol"; +import { SendLibBaseE2, WorkerOptions } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/SendLibBaseE2.sol"; +import { SetConfigParam } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol"; import { TestHelper } from "../TestHelper.sol"; -contract SendUln302Mock is SendUln302 { +contract SendUln302Mock is SendUlnBase, SendLibBaseE2 { // offchain packets schedule TestHelper public testHelper; + uint32 internal constant CONFIG_TYPE_EXECUTOR = 1; + uint32 internal constant CONFIG_TYPE_ULN = 2; + + error LZ_ULN_InvalidConfigType(uint32 configType); + constructor( address payable _verifyHelper, address _endpoint, uint256 _treasuryGasCap, uint256 _treasuryGasForFeeCap - ) SendUln302(_endpoint, _treasuryGasCap, _treasuryGasForFeeCap) { + ) Ownable(msg.sender) SendLibBaseE2(_endpoint, _treasuryGasCap, _treasuryGasForFeeCap) { testHelper = TestHelper(_verifyHelper); } + // ============================ OnlyEndpoint =================================== + + // on the send side the user can config both the executor and the ULN + function setConfig(address _oapp, SetConfigParam[] calldata _params) external override onlyEndpoint { + for (uint256 i = 0; i < _params.length; i++) { + SetConfigParam calldata param = _params[i]; + _assertSupportedEid(param.eid); + if (param.configType == CONFIG_TYPE_EXECUTOR) { + _setExecutorConfig(param.eid, _oapp, abi.decode(param.config, (ExecutorConfig))); + } else if (param.configType == CONFIG_TYPE_ULN) { + _setUlnConfig(param.eid, _oapp, abi.decode(param.config, (UlnConfig))); + } else { + revert LZ_ULN_InvalidConfigType(param.configType); + } + } + } + + // ============================ View =================================== + + function getConfig(uint32 _eid, address _oapp, uint32 _configType) external view override returns (bytes memory) { + if (_configType == CONFIG_TYPE_EXECUTOR) { + return abi.encode(getExecutorConfig(_oapp, _eid)); + } else if (_configType == CONFIG_TYPE_ULN) { + return abi.encode(getUlnConfig(_oapp, _eid)); + } else { + revert LZ_ULN_InvalidConfigType(_configType); + } + } + + function version() external pure override returns (uint64 major, uint8 minor, uint8 endpointVersion) { + return (3, 0, 2); + } + + function isSupportedEid(uint32 _eid) external view override returns (bool) { + return _isSupportedEid(_eid); + } + + // ============================ Internal =================================== + + function _quoteVerifier( + address _sender, + uint32 _dstEid, + WorkerOptions[] memory _options + ) internal view override returns (uint256) { + return _quoteDVNs(_sender, _dstEid, _options); + } + + function _payVerifier( + Packet calldata _packet, + WorkerOptions[] memory _options + ) internal override returns (uint256 otherWorkerFees, bytes memory encodedPacket) { + (otherWorkerFees, encodedPacket) = _payDVNs(fees, _packet, _options); + } + + function _splitOptions( + bytes calldata _options + ) internal pure override returns (bytes memory, WorkerOptions[] memory) { + return _splitUlnOptions(_options); + } + function send( Packet calldata _packet, bytes calldata _options, diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol index 32715d79f..8285e603e 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol @@ -1,19 +1,159 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.22; -import { SimpleMessageLib } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/SimpleMessageLib.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; + +import { IMessageLib, MessageLibType } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLib.sol"; +import { Packet } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol"; +import { ILayerZeroEndpointV2, MessagingFee, Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; +import { Errors } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Errors.sol"; +import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol"; +import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol"; import { TestHelper } from "../TestHelper.sol"; -contract SimpleMessageLibMock is SimpleMessageLib { +contract SimpleMessageLibMock is Ownable, ERC165 { // offchain packets schedule TestHelper public testHelper; - constructor(address payable _verifyHelper, address _endpoint) SimpleMessageLib(_endpoint, address(0x0)) { + using SafeERC20 for IERC20; + using PacketV1Codec for bytes; + + address public immutable endpoint; + address public immutable treasury; + uint32 public immutable localEid; + uint8 public constant PACKET_VERSION = 1; + + address public whitelistCaller; + + uint256 public lzTokenFee; + uint256 public nativeFee; + + bytes public defaultOption; + + error OnlyEndpoint(); + error OnlyWhitelistCaller(); + error InvalidEndpoint(address expected, address actual); + error ToIsAddressZero(); + error LzTokenIsAddressZero(); + error TransferFailed(); + + // only the endpoint can call SEND() and setConfig() + modifier onlyEndpoint() { + if (endpoint != msg.sender) { + revert OnlyEndpoint(); + } + _; + } + + // @dev oz4/5 breaking change... Ownable constructor + constructor(address payable _verifyHelper, address _endpoint) Ownable(msg.sender) { testHelper = TestHelper(_verifyHelper); + endpoint = _endpoint; + treasury = address(0x0); + localEid = ILayerZeroEndpointV2(_endpoint).eid(); + lzTokenFee = 99; + nativeFee = 100; + // defaultOption = Options.encodeLegacyOptionsType1(200000); + } + + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return interfaceId == type(IMessageLib).interfaceId || super.supportsInterface(interfaceId); + } + + // no validation logic at all + function validatePacket(bytes calldata packetBytes) external { + if (whitelistCaller != address(0x0) && msg.sender != whitelistCaller) { + revert OnlyWhitelistCaller(); + } + Origin memory origin = Origin(packetBytes.srcEid(), packetBytes.sender(), packetBytes.nonce()); + ILayerZeroEndpointV2(endpoint).verify(origin, packetBytes.receiverB20(), keccak256(packetBytes.payload())); + } + + // ------------------ onlyEndpoint ------------------ + function send( + Packet calldata _packet, + bytes memory _options, + bool _payInLzToken + ) external onlyEndpoint returns (MessagingFee memory fee, bytes memory encodedPacket, bytes memory options) { + encodedPacket = PacketV1Codec.encode(_packet); + + options = _options.length == 0 ? defaultOption : _options; + _handleMessagingParamsHook(encodedPacket, options); + + fee = MessagingFee(nativeFee, _payInLzToken ? lzTokenFee : 0); + } + + // ------------------ onlyOwner ------------------ + function setDefaultOption(bytes memory _defaultOption) external onlyOwner { + defaultOption = _defaultOption; + } + + function setMessagingFee(uint256 _nativeFee, uint256 _lzTokenFee) external onlyOwner { + nativeFee = _nativeFee; + lzTokenFee = _lzTokenFee; } - function _handleMessagingParamsHook(bytes memory _encodedPacket, bytes memory _options) internal override { + function setWhitelistCaller(address _whitelistCaller) external onlyOwner { + whitelistCaller = _whitelistCaller; + } + + function withdrawFee(address _to, uint256 _amount) external onlyOwner { + if (_to == address(0x0)) { + revert ToIsAddressZero(); + } + + address altTokenAddr = ILayerZeroEndpointV2(endpoint).nativeToken(); + + // transfers native if altTokenAddr == address(0x0) + Transfer.nativeOrToken(altTokenAddr, _to, _amount); + } + + function withdrawLzTokenFee(address _to, uint256 _amount) external onlyOwner { + if (_to == address(0x0)) { + revert ToIsAddressZero(); + } + address lzToken = ILayerZeroEndpointV2(endpoint).lzToken(); + if (lzToken == address(0x0)) { + revert LzTokenIsAddressZero(); + } + IERC20(lzToken).safeTransfer(_to, _amount); + } + + // ------------------ View ------------------ + function quote( + Packet calldata /*_packet*/, + bytes calldata /*_options*/, + bool _payInLzToken + ) external view returns (MessagingFee memory) { + return MessagingFee(nativeFee, _payInLzToken ? lzTokenFee : 0); + } + + function isSupportedEid(uint32) external pure returns (bool) { + return true; + } + + function version() external pure returns (uint64 major, uint8 minor, uint8 endpointVersion) { + return (0, 0, 2); + } + + function messageLibType() external pure returns (MessageLibType) { + return MessageLibType.SendAndReceive; + } + + // ------------------ Internal ------------------ + // function _handleMessagingParamsHook(bytes memory _encodedPacket, bytes memory _options) internal virtual {} + + function _handleMessagingParamsHook(bytes memory _encodedPacket, bytes memory _options) internal virtual { testHelper.schedulePacket(_encodedPacket, _options); } + + fallback() external payable { + revert Errors.LZ_NotImplemented(); + } + + receive() external payable {} } diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol new file mode 100644 index 000000000..3ddbe694b --- /dev/null +++ b/packages/test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol @@ -0,0 +1,167 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +// @dev oz4/5 breaking change... path +import { Pausable } from "@openzeppelin/contracts/utils/Pausable.sol"; +import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"; + +import { ISendLib } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ISendLib.sol"; +import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol"; + +import { IWorker } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/interfaces/IWorker.sol"; + +abstract contract WorkerMock is AccessControl, Pausable, IWorker { + bytes32 internal constant MESSAGE_LIB_ROLE = keccak256("MESSAGE_LIB_ROLE"); + bytes32 internal constant ALLOWLIST = keccak256("ALLOWLIST"); + bytes32 internal constant DENYLIST = keccak256("DENYLIST"); + bytes32 internal constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); + + address public workerFeeLib; + + uint64 public allowlistSize; + uint16 public defaultMultiplierBps; + address public priceFeed; + + mapping(uint32 eid => uint8[] optionTypes) internal supportedOptionTypes; + + /// @param _messageLibs array of message lib addresses that are granted the MESSAGE_LIB_ROLE + /// @param _priceFeed price feed address + /// @param _defaultMultiplierBps default multiplier for worker fee + /// @param _roleAdmin address that is granted the DEFAULT_ADMIN_ROLE (can grant and revoke all roles) + /// @param _admins array of admin addresses that are granted the ADMIN_ROLE + constructor( + address[] memory _messageLibs, + address _priceFeed, + uint16 _defaultMultiplierBps, + address _roleAdmin, + address[] memory _admins + ) { + defaultMultiplierBps = _defaultMultiplierBps; + priceFeed = _priceFeed; + + if (_roleAdmin != address(0x0)) { + _grantRole(DEFAULT_ADMIN_ROLE, _roleAdmin); // _roleAdmin can grant and revoke all roles + } + + for (uint256 i = 0; i < _messageLibs.length; ++i) { + _grantRole(MESSAGE_LIB_ROLE, _messageLibs[i]); + } + + for (uint256 i = 0; i < _admins.length; ++i) { + _grantRole(ADMIN_ROLE, _admins[i]); + } + } + + // ========================= Modifier ========================= + + modifier onlyAcl(address _sender) { + if (!hasAcl(_sender)) { + revert Worker_NotAllowed(); + } + _; + } + + /// @dev Access control list using allowlist and denylist + /// @dev 1) if one address is in the denylist -> deny + /// @dev 2) else if address in the allowlist OR allowlist is empty (allows everyone)-> allow + /// @dev 3) else deny + /// @param _sender address to check + function hasAcl(address _sender) public view returns (bool) { + if (hasRole(DENYLIST, _sender)) { + return false; + } else if (allowlistSize == 0 || hasRole(ALLOWLIST, _sender)) { + return true; + } else { + return false; + } + } + + // ========================= OnyDefaultAdmin ========================= + + /// @dev flag to pause execution of workers (if used with whenNotPaused modifier) + /// @param _paused true to pause, false to unpause + function setPaused(bool _paused) external onlyRole(DEFAULT_ADMIN_ROLE) { + if (_paused) { + _pause(); + } else { + _unpause(); + } + } + + // ========================= OnlyAdmin ========================= + + /// @param _priceFeed price feed address + function setPriceFeed(address _priceFeed) external onlyRole(ADMIN_ROLE) { + priceFeed = _priceFeed; + emit SetPriceFeed(_priceFeed); + } + + /// @param _workerFeeLib worker fee lib address + function setWorkerFeeLib(address _workerFeeLib) external onlyRole(ADMIN_ROLE) { + workerFeeLib = _workerFeeLib; + emit SetWorkerLib(_workerFeeLib); + } + + /// @param _multiplierBps default multiplier for worker fee + function setDefaultMultiplierBps(uint16 _multiplierBps) external onlyRole(ADMIN_ROLE) { + defaultMultiplierBps = _multiplierBps; + emit SetDefaultMultiplierBps(_multiplierBps); + } + + /// @dev supports withdrawing fee from ULN301, ULN302 and more + /// @param _lib message lib address + /// @param _to address to withdraw fee to + /// @param _amount amount to withdraw + function withdrawFee(address _lib, address _to, uint256 _amount) external onlyRole(ADMIN_ROLE) { + if (!hasRole(MESSAGE_LIB_ROLE, _lib)) revert Worker_OnlyMessageLib(); + ISendLib(_lib).withdrawFee(_to, _amount); + emit Withdraw(_lib, _to, _amount); + } + + /// @dev supports withdrawing token from the contract + /// @param _token token address + /// @param _to address to withdraw token to + /// @param _amount amount to withdraw + function withdrawToken(address _token, address _to, uint256 _amount) external onlyRole(ADMIN_ROLE) { + // transfers native if _token is address(0x0) + Transfer.nativeOrToken(_token, _to, _amount); + } + + function setSupportedOptionTypes(uint32 _eid, uint8[] calldata _optionTypes) external onlyRole(ADMIN_ROLE) { + supportedOptionTypes[_eid] = _optionTypes; + } + + // ========================= View Functions ========================= + function getSupportedOptionTypes(uint32 _eid) external view returns (uint8[] memory) { + return supportedOptionTypes[_eid]; + } + + // ========================= Internal Functions ========================= + + /// @dev overrides AccessControl to allow for counting of allowlistSize + /// @param _role role to grant + /// @param _account address to grant role to + function _grantRole(bytes32 _role, address _account) internal override returns (bool) { + if (_role == ALLOWLIST && !hasRole(_role, _account)) { + ++allowlistSize; + } + super._grantRole(_role, _account); + return true; + } + + /// @dev overrides AccessControl to allow for counting of allowlistSize + /// @param _role role to revoke + /// @param _account address to revoke role from + function _revokeRole(bytes32 _role, address _account) internal override returns (bool) { + if (_role == ALLOWLIST && hasRole(_role, _account)) { + --allowlistSize; + } + super._revokeRole(_role, _account); + return true; + } + + /// @dev overrides AccessControl to disable renouncing of roles + function renounceRole(bytes32 /*role*/, address /*account*/) public pure override { + revert Worker_RoleRenouncingDisabled(); + } +} diff --git a/packages/test-devtools-evm-foundry/package.json b/packages/test-devtools-evm-foundry/package.json index c08b49763..79610e81b 100644 --- a/packages/test-devtools-evm-foundry/package.json +++ b/packages/test-devtools-evm-foundry/package.json @@ -20,8 +20,8 @@ "@layerzerolabs/lz-evm-oapp-v2": "~2.1.15", "@layerzerolabs/lz-evm-protocol-v2": "~2.1.15", "@layerzerolabs/lz-evm-v1-0.7": "~2.1.15", - "@openzeppelin/contracts": "^4.9.5", - "@openzeppelin/contracts-upgradeable": "^4.9.5", + "@openzeppelin/contracts": "^5.0.0", + "@openzeppelin/contracts-upgradeable": "^5.0.0", "@types/node": "~18.18.14", "ts-node": "^10.9.2", "typescript": "^5.3.3" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5ff72cc48..7d6a648c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,16 +86,16 @@ importers: version: 2.1.15 '@layerzerolabs/lz-evm-messagelib-v2': specifier: ~2.1.15 - version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-oapp-v2': specifier: ~2.1.15 - version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-protocol-v2': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-v1-0.7': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) '@layerzerolabs/lz-v2-utilities': specifier: ~2.1.15 version: 2.1.15 @@ -182,16 +182,16 @@ importers: version: 2.1.15 '@layerzerolabs/lz-evm-messagelib-v2': specifier: ~2.1.15 - version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-oapp-v2': specifier: ~2.1.15 - version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-protocol-v2': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-v1-0.7': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) '@layerzerolabs/lz-v2-utilities': specifier: ~2.1.15 version: 2.1.15 @@ -219,6 +219,9 @@ importers: '@openzeppelin/contracts': specifier: ^5.0.1 version: 5.0.1 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.1 + version: 5.0.2(@openzeppelin/contracts@5.0.1) '@rushstack/eslint-patch': specifier: ^1.7.0 version: 1.7.0 @@ -989,22 +992,22 @@ importers: devDependencies: '@layerzerolabs/lz-evm-messagelib-v2': specifier: ~2.1.15 - version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.5)(@openzeppelin/contracts@4.9.5)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-oapp-v2': specifier: ~2.1.15 - version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.5)(@openzeppelin/contracts@4.9.5)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-protocol-v2': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.5)(@openzeppelin/contracts@4.9.5)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-v1-0.7': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.5)(@openzeppelin/contracts@4.9.5)(hardhat-deploy@0.11.45) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45) '@openzeppelin/contracts': - specifier: ^4.9.5 - version: 4.9.5 + specifier: ^5.0.0 + version: 5.0.2 '@openzeppelin/contracts-upgradeable': - specifier: ^4.9.5 - version: 4.9.5 + specifier: ^5.0.0 + version: 5.0.2(@openzeppelin/contracts@5.0.2) '@types/node': specifier: ~18.18.14 version: 18.18.14 @@ -1355,7 +1358,7 @@ importers: version: 2.1.15 '@layerzerolabs/lz-evm-messagelib-v2': specifier: ~2.1.15 - version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-v2-utilities': specifier: ~2.1.15 version: 2.1.15 @@ -1772,13 +1775,13 @@ importers: version: 2.1.15 '@layerzerolabs/lz-evm-messagelib-v2': specifier: ~2.1.15 - version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-oapp-v2': specifier: ~2.1.15 - version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-protocol-v2': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-sdk-v1': specifier: ~2.1.15 version: 2.1.15 @@ -1907,16 +1910,16 @@ importers: version: 2.1.15 '@layerzerolabs/lz-evm-messagelib-v2': specifier: ~2.1.15 - version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-oapp-v1': specifier: ~2.1.15 version: 2.1.15(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-oapp-v2': specifier: ~2.1.15 - version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-protocol-v2': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) '@layerzerolabs/lz-evm-sdk-v1': specifier: ~2.1.15 version: 2.1.15 @@ -1925,7 +1928,7 @@ importers: version: 2.1.15 '@layerzerolabs/lz-evm-v1-0.7': specifier: ~2.1.15 - version: 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) + version: 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) '@layerzerolabs/lz-v2-utilities': specifier: ~2.1.15 version: 2.1.15 @@ -3812,7 +3815,7 @@ packages: solidity-bytes-utils: 0.8.2 dev: true - /@layerzerolabs/lz-evm-messagelib-v2@2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): + /@layerzerolabs/lz-evm-messagelib-v2@2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): resolution: {integrity: sha512-4pynXXTr48myqteC67IZ6kxua88dICJxvgE82urNMJZgnScvA9V7qrYb+XR4FpD9teOmPQfX9xsXdF6XlAjBGQ==} peerDependencies: '@arbitrum/nitro-contracts': ^1.1.0 @@ -3832,15 +3835,15 @@ packages: '@axelar-network/axelar-gmp-sdk-solidity': 5.6.4 '@chainlink/contracts-ccip': 0.7.6(ethers@5.7.2) '@eth-optimism/contracts': 0.6.0(ethers@5.7.2) - '@layerzerolabs/lz-evm-protocol-v2': 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) - '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) + '@layerzerolabs/lz-evm-protocol-v2': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) '@openzeppelin/contracts': 5.0.1 - '@openzeppelin/contracts-upgradeable': 4.9.6 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.1) hardhat-deploy: 0.11.45 solidity-bytes-utils: 0.8.2 dev: true - /@layerzerolabs/lz-evm-messagelib-v2@2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): + /@layerzerolabs/lz-evm-messagelib-v2@2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): resolution: {integrity: sha512-4pynXXTr48myqteC67IZ6kxua88dICJxvgE82urNMJZgnScvA9V7qrYb+XR4FpD9teOmPQfX9xsXdF6XlAjBGQ==} peerDependencies: '@arbitrum/nitro-contracts': ^1.1.0 @@ -3860,10 +3863,10 @@ packages: '@axelar-network/axelar-gmp-sdk-solidity': 5.6.4 '@chainlink/contracts-ccip': 0.7.6(ethers@5.7.2) '@eth-optimism/contracts': 0.6.0(ethers@5.7.2) - '@layerzerolabs/lz-evm-protocol-v2': 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) - '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45) + '@layerzerolabs/lz-evm-protocol-v2': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45) '@openzeppelin/contracts': 5.0.2 - '@openzeppelin/contracts-upgradeable': 4.9.6 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.2) hardhat-deploy: 0.11.45 solidity-bytes-utils: 0.8.2 dev: true @@ -3876,7 +3879,7 @@ packages: hardhat-deploy: ^0.11.44 solidity-bytes-utils: ^0.8.0 dependencies: - '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) + '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) '@openzeppelin/contracts': 5.0.1 abi-decoder: 2.4.0 dotenv: 16.4.5 @@ -3909,7 +3912,7 @@ packages: solidity-bytes-utils: 0.8.2 dev: true - /@layerzerolabs/lz-evm-oapp-v2@2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): + /@layerzerolabs/lz-evm-oapp-v2@2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): resolution: {integrity: sha512-DHBIkerifHQFmGdGIyNDtzopLbA4Z5I/ooXQnrFKgn1qaVMIbFAMevr7JPRLBmXypHHugRgCsgbM5C+DZyjnDg==} peerDependencies: '@layerzerolabs/lz-evm-messagelib-v2': ^2.1.15 @@ -3920,11 +3923,31 @@ packages: hardhat-deploy: ^0.11.44 solidity-bytes-utils: ^0.8.0 dependencies: - '@layerzerolabs/lz-evm-messagelib-v2': 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) - '@layerzerolabs/lz-evm-protocol-v2': 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) - '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) + '@layerzerolabs/lz-evm-messagelib-v2': 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-protocol-v2': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45) '@openzeppelin/contracts': 5.0.1 - '@openzeppelin/contracts-upgradeable': 4.9.6 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.1) + hardhat-deploy: 0.11.45 + solidity-bytes-utils: 0.8.2 + dev: true + + /@layerzerolabs/lz-evm-oapp-v2@2.1.15(@layerzerolabs/lz-evm-messagelib-v2@2.1.15)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): + resolution: {integrity: sha512-DHBIkerifHQFmGdGIyNDtzopLbA4Z5I/ooXQnrFKgn1qaVMIbFAMevr7JPRLBmXypHHugRgCsgbM5C+DZyjnDg==} + peerDependencies: + '@layerzerolabs/lz-evm-messagelib-v2': ^2.1.15 + '@layerzerolabs/lz-evm-protocol-v2': ^2.1.15 + '@layerzerolabs/lz-evm-v1-0.7': ^2.1.15 + '@openzeppelin/contracts': ^4.8.1 || ^5.0.0 + '@openzeppelin/contracts-upgradeable': ^4.8.1 || ^5.0.0 + hardhat-deploy: ^0.11.44 + solidity-bytes-utils: ^0.8.0 + dependencies: + '@layerzerolabs/lz-evm-messagelib-v2': 2.1.15(@axelar-network/axelar-gmp-sdk-solidity@5.6.4)(@chainlink/contracts-ccip@0.7.6)(@eth-optimism/contracts@0.6.0)(@layerzerolabs/lz-evm-protocol-v2@2.1.15)(@layerzerolabs/lz-evm-v1-0.7@2.1.15)(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-protocol-v2': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2) + '@layerzerolabs/lz-evm-v1-0.7': 2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45) + '@openzeppelin/contracts': 5.0.2 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.2) hardhat-deploy: 0.11.45 solidity-bytes-utils: 0.8.2 dev: true @@ -3943,7 +3966,7 @@ packages: solidity-bytes-utils: 0.8.2 dev: true - /@layerzerolabs/lz-evm-protocol-v2@2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): + /@layerzerolabs/lz-evm-protocol-v2@2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): resolution: {integrity: sha512-Hp2ROjgRvx7QJrsK+LTpJv1EhXzjjKUwzMgWoyKpCIGWJAXhNTrR+SAkkcB41HbeD4LyR+NVBZmjRL1pxAGR7w==} peerDependencies: '@openzeppelin/contracts': ^4.8.1 || ^5.0.0 @@ -3952,12 +3975,12 @@ packages: solidity-bytes-utils: ^0.8.0 dependencies: '@openzeppelin/contracts': 5.0.1 - '@openzeppelin/contracts-upgradeable': 4.9.6 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.1) hardhat-deploy: 0.11.45 solidity-bytes-utils: 0.8.2 dev: true - /@layerzerolabs/lz-evm-protocol-v2@2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): + /@layerzerolabs/lz-evm-protocol-v2@2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45)(solidity-bytes-utils@0.8.2): resolution: {integrity: sha512-Hp2ROjgRvx7QJrsK+LTpJv1EhXzjjKUwzMgWoyKpCIGWJAXhNTrR+SAkkcB41HbeD4LyR+NVBZmjRL1pxAGR7w==} peerDependencies: '@openzeppelin/contracts': ^4.8.1 || ^5.0.0 @@ -3966,7 +3989,7 @@ packages: solidity-bytes-utils: ^0.8.0 dependencies: '@openzeppelin/contracts': 5.0.2 - '@openzeppelin/contracts-upgradeable': 4.9.6 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.2) hardhat-deploy: 0.11.45 solidity-bytes-utils: 0.8.2 dev: true @@ -4003,7 +4026,7 @@ packages: hardhat-deploy: 0.11.45 dev: true - /@layerzerolabs/lz-evm-v1-0.7@2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45): + /@layerzerolabs/lz-evm-v1-0.7@2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.1)(hardhat-deploy@0.11.45): resolution: {integrity: sha512-mkNESBg5gvhWa8UsR92w6V/hkpDL1RpZaFDdJ6ThotB3mdK9vrVtJkXzDtdJrj7F2ydRJ08opwjzMOATixHtpw==} peerDependencies: '@openzeppelin/contracts': 3.4.2-solc-0.7 || ^3.4.2 || ^4.0.0 || ^5.0.0 @@ -4011,11 +4034,11 @@ packages: hardhat-deploy: ^0.11.44 dependencies: '@openzeppelin/contracts': 5.0.1 - '@openzeppelin/contracts-upgradeable': 4.9.6 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.1) hardhat-deploy: 0.11.45 dev: true - /@layerzerolabs/lz-evm-v1-0.7@2.1.15(@openzeppelin/contracts-upgradeable@4.9.6)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45): + /@layerzerolabs/lz-evm-v1-0.7@2.1.15(@openzeppelin/contracts-upgradeable@5.0.2)(@openzeppelin/contracts@5.0.2)(hardhat-deploy@0.11.45): resolution: {integrity: sha512-mkNESBg5gvhWa8UsR92w6V/hkpDL1RpZaFDdJ6ThotB3mdK9vrVtJkXzDtdJrj7F2ydRJ08opwjzMOATixHtpw==} peerDependencies: '@openzeppelin/contracts': 3.4.2-solc-0.7 || ^3.4.2 || ^4.0.0 || ^5.0.0 @@ -4023,7 +4046,7 @@ packages: hardhat-deploy: ^0.11.44 dependencies: '@openzeppelin/contracts': 5.0.2 - '@openzeppelin/contracts-upgradeable': 4.9.6 + '@openzeppelin/contracts-upgradeable': 5.0.2(@openzeppelin/contracts@5.0.2) hardhat-deploy: 0.11.45 dev: true @@ -4399,8 +4422,20 @@ packages: resolution: {integrity: sha512-f7L1//4sLlflAN7fVzJLoRedrf5Na3Oal5PZfIq55NFcVZ90EpV1q5xOvL4lFvg3MNICSDr2hH0JUBxwlxcoPg==} dev: true - /@openzeppelin/contracts-upgradeable@4.9.6: - resolution: {integrity: sha512-m4iHazOsOCv1DgM7eD7GupTJ+NFVujRZt1wzddDPSVGpWdKq1SKkla5htKG7+IS4d2XOCtzkUNwRZ7Vq5aEUMA==} + /@openzeppelin/contracts-upgradeable@5.0.2(@openzeppelin/contracts@5.0.1): + resolution: {integrity: sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ==} + peerDependencies: + '@openzeppelin/contracts': 5.0.2 + dependencies: + '@openzeppelin/contracts': 5.0.1 + dev: true + + /@openzeppelin/contracts-upgradeable@5.0.2(@openzeppelin/contracts@5.0.2): + resolution: {integrity: sha512-0MmkHSHiW2NRFiT9/r5Lu4eJq5UJ4/tzlOgYXNAIj/ONkQTVnz22pLxDvp4C4uZ9he7ZFvGn3Driptn1/iU7tQ==} + peerDependencies: + '@openzeppelin/contracts': 5.0.2 + dependencies: + '@openzeppelin/contracts': 5.0.2 dev: true /@openzeppelin/contracts@3.4.2: @@ -8088,6 +8123,7 @@ packages: dependencies: is-hex-prefixed: 1.0.0 strip-hex-prefix: 1.0.0 + bundledDependencies: false /eventemitter3@4.0.4: resolution: {integrity: sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==} From 6f5146ea013a668225fb36d5c2c3f452fe79a90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lz=2Ew=CE=94rlock=28=29?= Date: Tue, 27 Feb 2024 21:03:36 -0800 Subject: [PATCH 3/8] Update all remote dependencies of monorepo to latest version --- examples/oapp/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/oapp/package.json b/examples/oapp/package.json index 1999580b8..1fd65281e 100644 --- a/examples/oapp/package.json +++ b/examples/oapp/package.json @@ -36,6 +36,7 @@ "@nomicfoundation/hardhat-ethers": "^3.0.5", "@nomiclabs/hardhat-ethers": "^2.2.3", "@openzeppelin/contracts": "^5.0.1", + "@openzeppelin/contracts-upgradeable": "^5.0.1", "@rushstack/eslint-patch": "^1.7.0", "@types/chai": "^4.3.11", "@types/mocha": "^10.0.6", From 0c057a2331e431b037d70cdd8198b7a9589ede83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lz=2Ew=CE=94rlock=28=29?= Date: Wed, 28 Feb 2024 16:19:43 -0800 Subject: [PATCH 4/8] update lockfile --- pnpm-lock.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7d6a648c3..39b4a9bfb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,9 @@ importers: '@openzeppelin/contracts': specifier: ^5.0.1 version: 5.0.1 + '@openzeppelin/contracts-upgradeable': + specifier: ^5.0.1 + version: 5.0.2(@openzeppelin/contracts@5.0.1) '@rushstack/eslint-patch': specifier: ^1.7.0 version: 1.7.0 From 5c6e4618ce16c6be937878e5b231caec02e56609 Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Thu, 29 Feb 2024 12:07:54 -0800 Subject: [PATCH 5/8] chore: Tiny slash in foundry toml --- examples/oft/foundry.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/oft/foundry.toml b/examples/oft/foundry.toml index a92e2a05b..5c8215b1c 100644 --- a/examples/oft/foundry.toml +++ b/examples/oft/foundry.toml @@ -7,6 +7,6 @@ cache_path = 'cache' libs = ['node_modules', 'node_modules/@layerzerolabs/toolbox-foundry/lib'] remappings = [ - '@layerzerolabs/=node_modules/@layerzerolabs', + '@layerzerolabs/=node_modules/@layerzerolabs/', '@openzeppelin/=node_modules/@openzeppelin/', ] From 40bfdb5f2d1c01cf60fa57db8610efe59c601197 Mon Sep 17 00:00:00 2001 From: Jan Nanista Date: Thu, 29 Feb 2024 14:04:37 -0800 Subject: [PATCH 6/8] fix: Fix foundry.toml files for examples --- examples/oapp/foundry.toml | 17 ++++++++++++++++- examples/oft/foundry.toml | 17 ++++++++++++++++- 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/examples/oapp/foundry.toml b/examples/oapp/foundry.toml index 5c8215b1c..7608c64b3 100644 --- a/examples/oapp/foundry.toml +++ b/examples/oapp/foundry.toml @@ -4,9 +4,24 @@ src = 'contracts' out = 'out' test = 'test/foundry' cache_path = 'cache' -libs = ['node_modules', 'node_modules/@layerzerolabs/toolbox-foundry/lib'] +libs = [ + # We provide a set of useful contract utilities + # in the lib directory of @layerzerolabs/toolbox-foundry: + # + # - forge-std + # - ds-test + # - solidity-bytes-utils + 'node_modules/@layerzerolabs/toolbox-foundry/lib', + 'node_modules', +] remappings = [ + # Due to a misconfiguration of solidity-bytes-utils, an outdated version + # of forge-std is being dragged in + # + # To remedy this, we'll remap the ds-test and forge-std imports to ou own versions + 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/lib/ds-test', + 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/lib/forge-std', '@layerzerolabs/=node_modules/@layerzerolabs/', '@openzeppelin/=node_modules/@openzeppelin/', ] diff --git a/examples/oft/foundry.toml b/examples/oft/foundry.toml index 5c8215b1c..7608c64b3 100644 --- a/examples/oft/foundry.toml +++ b/examples/oft/foundry.toml @@ -4,9 +4,24 @@ src = 'contracts' out = 'out' test = 'test/foundry' cache_path = 'cache' -libs = ['node_modules', 'node_modules/@layerzerolabs/toolbox-foundry/lib'] +libs = [ + # We provide a set of useful contract utilities + # in the lib directory of @layerzerolabs/toolbox-foundry: + # + # - forge-std + # - ds-test + # - solidity-bytes-utils + 'node_modules/@layerzerolabs/toolbox-foundry/lib', + 'node_modules', +] remappings = [ + # Due to a misconfiguration of solidity-bytes-utils, an outdated version + # of forge-std is being dragged in + # + # To remedy this, we'll remap the ds-test and forge-std imports to ou own versions + 'ds-test/=node_modules/@layerzerolabs/toolbox-foundry/lib/ds-test', + 'forge-std/=node_modules/@layerzerolabs/toolbox-foundry/lib/forge-std', '@layerzerolabs/=node_modules/@layerzerolabs/', '@openzeppelin/=node_modules/@openzeppelin/', ] From c545b34d955bf91ddfe6917e865abd0840abc3f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lz=2Ew=CE=94rlock=28=29?= Date: Thu, 29 Feb 2024 16:30:33 -0800 Subject: [PATCH 7/8] Change name from TestHelper -> TestHelperOz5 --- examples/oft/test/foundry/MyOFT.t.sol | 4 ++-- .../contracts/{TestHelper.sol => TestHelperOz5.sol} | 4 ++-- .../contracts/mocks/SendUln302Mock.sol | 6 +++--- .../contracts/mocks/SimpleMessageLibMock.sol | 6 +++--- 4 files changed, 10 insertions(+), 10 deletions(-) rename packages/test-devtools-evm-foundry/contracts/{TestHelper.sol => TestHelperOz5.sol} (99%) diff --git a/examples/oft/test/foundry/MyOFT.t.sol b/examples/oft/test/foundry/MyOFT.t.sol index 2859f5428..8d13dad51 100644 --- a/examples/oft/test/foundry/MyOFT.t.sol +++ b/examples/oft/test/foundry/MyOFT.t.sol @@ -23,9 +23,9 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Met import "forge-std/console.sol"; // DevTools imports -import { TestHelper } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelper.sol"; +import { TestHelperOz5 } from "@layerzerolabs/test-devtools-evm-foundry/contracts/TestHelperOz5.sol"; -contract MyOFTTest is TestHelper { +contract MyOFTTest is TestHelperOz5 { using OptionsBuilder for bytes; uint32 aEid = 1; diff --git a/packages/test-devtools-evm-foundry/contracts/TestHelper.sol b/packages/test-devtools-evm-foundry/contracts/TestHelperOz5.sol similarity index 99% rename from packages/test-devtools-evm-foundry/contracts/TestHelper.sol rename to packages/test-devtools-evm-foundry/contracts/TestHelperOz5.sol index 3f3a8c6cf..ee4380eac 100644 --- a/packages/test-devtools-evm-foundry/contracts/TestHelper.sol +++ b/packages/test-devtools-evm-foundry/contracts/TestHelperOz5.sol @@ -38,11 +38,11 @@ import { SimpleMessageLibMock } from "./mocks/SimpleMessageLibMock.sol"; import { ExecutorFeeLibMock as ExecutorFeeLib } from "./mocks/ExecutorFeeLibMock.sol"; /** - * @title TestHelper + * @title TestHelperOz5 * @notice Helper contract for setting up and managing LayerZero test environments. * @dev Extends Foundry's Test contract and provides utility functions for setting up mock endpoints and OApps. */ -contract TestHelper is Test, OptionsHelper { +contract TestHelperOz5 is Test, OptionsHelper { using OptionsBuilder for bytes; enum LibraryType { diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol index ce181c0ce..0d63a2638 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol @@ -15,11 +15,11 @@ import { SendUlnBase } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/uln/S import { SendLibBaseE2, WorkerOptions } from "@layerzerolabs/lz-evm-messagelib-v2/contracts/SendLibBaseE2.sol"; import { SetConfigParam } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/IMessageLibManager.sol"; -import { TestHelper } from "../TestHelper.sol"; +import { TestHelperOz5 } from "../TestHelperOz5.sol"; contract SendUln302Mock is SendUlnBase, SendLibBaseE2 { // offchain packets schedule - TestHelper public testHelper; + TestHelperOz5 public testHelper; uint32 internal constant CONFIG_TYPE_EXECUTOR = 1; uint32 internal constant CONFIG_TYPE_ULN = 2; @@ -32,7 +32,7 @@ contract SendUln302Mock is SendUlnBase, SendLibBaseE2 { uint256 _treasuryGasCap, uint256 _treasuryGasForFeeCap ) Ownable(msg.sender) SendLibBaseE2(_endpoint, _treasuryGasCap, _treasuryGasForFeeCap) { - testHelper = TestHelper(_verifyHelper); + testHelper = TestHelperOz5(_verifyHelper); } // ============================ OnlyEndpoint =================================== diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol index 8285e603e..fb69997ef 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol @@ -13,11 +13,11 @@ import { Errors } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Errors. import { PacketV1Codec } from "@layerzerolabs/lz-evm-protocol-v2/contracts/messagelib/libs/PacketV1Codec.sol"; import { Transfer } from "@layerzerolabs/lz-evm-protocol-v2/contracts/libs/Transfer.sol"; -import { TestHelper } from "../TestHelper.sol"; +import { TestHelperOz5 } from "../TestHelperOz5.sol"; contract SimpleMessageLibMock is Ownable, ERC165 { // offchain packets schedule - TestHelper public testHelper; + TestHelperOz5 public testHelper; using SafeERC20 for IERC20; using PacketV1Codec for bytes; @@ -51,7 +51,7 @@ contract SimpleMessageLibMock is Ownable, ERC165 { // @dev oz4/5 breaking change... Ownable constructor constructor(address payable _verifyHelper, address _endpoint) Ownable(msg.sender) { - testHelper = TestHelper(_verifyHelper); + testHelper = TestHelperOz5(_verifyHelper); endpoint = _endpoint; treasury = address(0x0); localEid = ILayerZeroEndpointV2(_endpoint).eid(); From 8974908dd19add117abffb0639438542ff1aba2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lz=2Ew=CE=94rlock=28=29?= Date: Thu, 29 Feb 2024 17:06:27 -0800 Subject: [PATCH 8/8] Update licenses inside of the testHelper mocks --- packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol | 2 +- .../contracts/mocks/ExecutorFeeLibMock.sol | 2 +- .../test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol | 2 +- .../contracts/mocks/OFTComposerMock.sol | 2 +- .../contracts/mocks/OFTInspectorMock.sol | 2 +- .../contracts/mocks/ReceiveUln302Mock.sol | 2 +- .../contracts/mocks/SendUln302Mock.sol | 2 +- .../contracts/mocks/SimpleMessageLibMock.sol | 2 +- .../test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol index 5bebc9709..cc467817b 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/DVNMock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.0; pragma solidity ^0.8.20; diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorFeeLibMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorFeeLibMock.sol index f54c81a51..73a9555e5 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorFeeLibMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorFeeLibMock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.22; // @dev oz4/5 breaking change... Ownable constructor diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol index c32ecdfcd..1027760ce 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/ExecutorMock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.0; import { ILayerZeroEndpointV2, Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/OFTComposerMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/OFTComposerMock.sol index 50609d4e2..fa0c5cbdb 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/OFTComposerMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/OFTComposerMock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.22; import { IOAppComposer } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppComposer.sol"; diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/OFTInspectorMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/OFTInspectorMock.sol index 2784b160d..ada77e75c 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/OFTInspectorMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/OFTInspectorMock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.22; import { IOAppMsgInspector } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/interfaces/IOAppMsgInspector.sol"; diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/ReceiveUln302Mock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/ReceiveUln302Mock.sol index 6a3f74a58..07d73fe94 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/ReceiveUln302Mock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/ReceiveUln302Mock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.0; import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol index 0d63a2638..d171ca9cb 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/SendUln302Mock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.22; // @dev oz4/5 breaking change... Ownable constructor diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol index fb69997ef..09f9f1703 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/SimpleMessageLibMock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.22; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; diff --git a/packages/test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol b/packages/test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol index 3ddbe694b..677854d5c 100644 --- a/packages/test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol +++ b/packages/test-devtools-evm-foundry/contracts/mocks/WorkerMock.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: LZBL-1.2 pragma solidity ^0.8.0; // @dev oz4/5 breaking change... path