diff --git a/packages/layerzero-v2/evm/messagelib/contracts/Executor.sol b/packages/layerzero-v2/evm/messagelib/contracts/Executor.sol index 5420833..7eb75d6 100644 --- a/packages/layerzero-v2/evm/messagelib/contracts/Executor.sol +++ b/packages/layerzero-v2/evm/messagelib/contracts/Executor.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.20; import { ReentrancyGuardUpgradeable } from "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; import { Proxied } from "hardhat-deploy/solc_0.8/proxy/Proxied.sol"; -import { ILayerZeroEndpointV2, Origin } from "@layerzerolabs/lz-evm-protocol-v2/contracts/interfaces/ILayerZeroEndpointV2.sol"; +import { 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 "./uln/uln301/interfaces/IUltraLightNode301.sol"; @@ -13,6 +13,50 @@ import { IExecutor } from "./interfaces/IExecutor.sol"; import { IExecutorFeeLib } from "./interfaces/IExecutorFeeLib.sol"; import { WorkerUpgradeable } from "./upgradeable/WorkerUpgradeable.sol"; +interface ILayerZeroEndpointV2 { + function eid() external view returns (uint32); + + function lzReceive( + Origin calldata _origin, + address _receiver, + bytes32 _guid, + bytes calldata _message, + bytes calldata _extraData + ) external payable; + + function lzReceiveAlert( + Origin calldata _origin, + address _receiver, + bytes32 _guid, + uint256 _gas, + uint256 _value, + bytes calldata _message, + bytes calldata _extraData, + bytes calldata _reason + ) external; + + function lzCompose( + address _from, + address _to, + bytes32 _guid, + uint16 _index, + bytes calldata _message, + bytes calldata _extraData + ) external payable; + + function lzComposeAlert( + address _from, + address _to, + bytes32 _guid, + uint16 _index, + uint256 _gas, + uint256 _value, + bytes calldata _message, + bytes calldata _extraData, + bytes calldata _reason + ) external; +} + contract Executor is WorkerUpgradeable, ReentrancyGuardUpgradeable, Proxied, IExecutor { using PacketV1Codec for bytes; @@ -49,10 +93,11 @@ contract Executor is WorkerUpgradeable, ReentrancyGuardUpgradeable, Proxied, IEx for (uint256 i = 0; i < _params.length; i++) { DstConfigParam memory param = _params[i]; dstConfig[param.dstEid] = DstConfig( - param.baseGas, + param.lzReceiveBaseGas, param.multiplierBps, param.floorMarginUSD, - param.nativeCap + param.nativeCap, + param.lzComposeBaseGas ); } emit DstConfigSet(_params); @@ -83,6 +128,66 @@ contract Executor is WorkerUpgradeable, ReentrancyGuardUpgradeable, Proxied, IEx IUltraLightNode301(receiveUln301).commitVerification(_packet, _gasLimit); } + function execute302(ExecutionParams calldata _executionParams) external payable onlyRole(ADMIN_ROLE) nonReentrant { + try + ILayerZeroEndpointV2(endpoint).lzReceive{ value: msg.value, gas: _executionParams.gasLimit }( + _executionParams.origin, + _executionParams.receiver, + _executionParams.guid, + _executionParams.message, + _executionParams.extraData + ) + { + // do nothing + } catch (bytes memory reason) { + ILayerZeroEndpointV2(endpoint).lzReceiveAlert( + _executionParams.origin, + _executionParams.receiver, + _executionParams.guid, + _executionParams.gasLimit, + msg.value, + _executionParams.message, + _executionParams.extraData, + reason + ); + } + } + + function compose302( + address _from, + address _to, + bytes32 _guid, + uint16 _index, + bytes calldata _message, + bytes calldata _extraData, + uint256 _gasLimit + ) external payable onlyRole(ADMIN_ROLE) nonReentrant { + try + ILayerZeroEndpointV2(endpoint).lzCompose{ value: msg.value, gas: _gasLimit }( + _from, + _to, + _guid, + _index, + _message, + _extraData + ) + { + // do nothing + } catch (bytes memory reason) { + ILayerZeroEndpointV2(endpoint).lzComposeAlert( + _from, + _to, + _guid, + _index, + _gasLimit, + msg.value, + _message, + _extraData, + reason + ); + } + } + function nativeDropAndExecute302( NativeDropParams[] calldata _nativeDropParams, uint256 _nativeDropGasLimit, @@ -97,14 +202,28 @@ contract Executor is WorkerUpgradeable, ReentrancyGuardUpgradeable, Proxied, IEx ); uint256 value = msg.value - spent; - // ignore the execution result + try ILayerZeroEndpointV2(endpoint).lzReceive{ value: value, gas: _executionParams.gasLimit }( _executionParams.origin, _executionParams.receiver, _executionParams.guid, _executionParams.message, _executionParams.extraData - ); + ) + { + // do nothing + } catch (bytes memory reason) { + ILayerZeroEndpointV2(endpoint).lzReceiveAlert( + _executionParams.origin, + _executionParams.receiver, + _executionParams.guid, + _executionParams.gasLimit, + value, + _executionParams.message, + _executionParams.extraData, + reason + ); + } } // --- Message Lib --- @@ -113,7 +232,7 @@ contract Executor is WorkerUpgradeable, ReentrancyGuardUpgradeable, Proxied, IEx address _sender, uint256 _calldataSize, bytes calldata _options - ) external onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) returns (uint256 fee) { + ) external onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) whenNotPaused returns (uint256 fee) { IExecutorFeeLib.FeeParams memory params = IExecutorFeeLib.FeeParams( priceFeed, _dstEid, diff --git a/packages/layerzero-v2/evm/messagelib/contracts/ExecutorFeeLib.sol b/packages/layerzero-v2/evm/messagelib/contracts/ExecutorFeeLib.sol index bfbf101..c74d191 100644 --- a/packages/layerzero-v2/evm/messagelib/contracts/ExecutorFeeLib.sol +++ b/packages/layerzero-v2/evm/messagelib/contracts/ExecutorFeeLib.sol @@ -32,11 +32,12 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { IExecutor.DstConfig calldata _dstConfig, bytes calldata _options ) external returns (uint256 fee) { - if (_dstConfig.baseGas == 0) revert Executor_EidNotSupported(_params.dstEid); + if (_dstConfig.lzReceiveBaseGas == 0) revert Executor_EidNotSupported(_params.dstEid); (uint256 totalDstAmount, uint256 totalGas) = _decodeExecutorOptions( _isV1Eid(_params.dstEid), - _dstConfig.baseGas, + _dstConfig.lzReceiveBaseGas, + _dstConfig.lzComposeBaseGas, _dstConfig.nativeCap, _options ); @@ -49,19 +50,10 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { 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 - ); + uint16 multiplierBps = _dstConfig.multiplierBps == 0 ? _params.defaultMultiplierBps : _dstConfig.multiplierBps; + + fee = _applyPremiumToGas(totalGasFee, multiplierBps, _dstConfig.floorMarginUSD, nativePriceUSD); + fee += _convertAndApplyPremiumToValue(totalDstAmount, priceRatio, priceRatioDenominator, multiplierBps); } // ================================ View ================================ @@ -70,11 +62,12 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { IExecutor.DstConfig calldata _dstConfig, bytes calldata _options ) external view returns (uint256 fee) { - if (_dstConfig.baseGas == 0) revert Executor_EidNotSupported(_params.dstEid); + if (_dstConfig.lzReceiveBaseGas == 0) revert Executor_EidNotSupported(_params.dstEid); (uint256 totalDstAmount, uint256 totalGas) = _decodeExecutorOptions( _isV1Eid(_params.dstEid), - _dstConfig.baseGas, + _dstConfig.lzReceiveBaseGas, + _dstConfig.lzComposeBaseGas, _dstConfig.nativeCap, _options ); @@ -86,26 +79,18 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { 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 - ); + uint16 multiplierBps = _dstConfig.multiplierBps == 0 ? _params.defaultMultiplierBps : _dstConfig.multiplierBps; + + fee = _applyPremiumToGas(totalGasFee, multiplierBps, _dstConfig.floorMarginUSD, nativePriceUSD); + fee += _convertAndApplyPremiumToValue(totalDstAmount, priceRatio, priceRatioDenominator, multiplierBps); } // ================================ Internal ================================ // @dev decode executor options into dstAmount and totalGas function _decodeExecutorOptions( bool _v1Eid, - uint64 _baseGas, + uint64 _lzReceiveBaseGas, + uint64 _lzComposeBaseGas, uint128 _nativeCap, bytes calldata _options ) internal pure returns (uint256 dstAmount, uint256 totalGas) { @@ -115,8 +100,9 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { uint256 cursor = 0; bool ordered = false; - totalGas = _baseGas; + totalGas = _lzReceiveBaseGas; // lz receive only called once + bool v1Eid = _v1Eid; // stack too deep uint256 lzReceiveGas; while (cursor < _options.length) { (uint8 optionType, bytes calldata option, uint256 newCursor) = _options.nextExecutorOption(cursor); @@ -126,7 +112,7 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { (uint128 gas, uint128 value) = ExecutorOptions.decodeLzReceiveOption(option); // endpoint v1 does not support lzReceive with value - if (_v1Eid && value > 0) revert Executor_UnsupportedOptionType(optionType); + if (v1Eid && value > 0) revert Executor_UnsupportedOptionType(optionType); dstAmount += value; lzReceiveGas += gas; @@ -135,11 +121,16 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { dstAmount += nativeDropAmount; } else if (optionType == ExecutorOptions.OPTION_TYPE_LZCOMPOSE) { // endpoint v1 does not support lzCompose - if (_v1Eid) revert Executor_UnsupportedOptionType(optionType); + if (v1Eid) revert Executor_UnsupportedOptionType(optionType); (, uint128 gas, uint128 value) = ExecutorOptions.decodeLzComposeOption(option); + if (gas == 0) revert Executor_ZeroLzComposeGasProvided(); + dstAmount += value; - totalGas += gas; + // lz compose can be called multiple times, based on unique index + // to simplify the quoting, we add lzComposeBaseGas for each lzComposeOption received + // if the same index has multiple compose options, the gas will be added multiple times + totalGas += gas + _lzComposeBaseGas; } else if (optionType == ExecutorOptions.OPTION_TYPE_ORDERED_EXECUTION) { ordered = true; } else { @@ -158,14 +149,11 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { function _applyPremiumToGas( uint256 _fee, - uint16 _bps, - uint16 _defaultBps, + uint16 _multiplierBps, uint128 _marginUSD, uint128 _nativePriceUSD ) internal view returns (uint256) { - uint16 multiplierBps = _bps == 0 ? _defaultBps : _bps; - - uint256 feeWithMultiplier = (_fee * multiplierBps) / 10000; + uint256 feeWithMultiplier = (_fee * _multiplierBps) / 10000; if (_nativePriceUSD == 0 || _marginUSD == 0) { return feeWithMultiplier; @@ -179,10 +167,10 @@ contract ExecutorFeeLib is Ownable, IExecutorFeeLib { uint256 _value, uint128 _ratio, uint128 _denom, - uint16 _defaultBps + uint16 _multiplierBps ) internal pure returns (uint256 fee) { if (_value > 0) { - fee = (((_value * _ratio) / _denom) * _defaultBps) / 10000; + fee = (((_value * _ratio) / _denom) * _multiplierBps) / 10000; } } diff --git a/packages/layerzero-v2/evm/messagelib/contracts/interfaces/IExecutor.sol b/packages/layerzero-v2/evm/messagelib/contracts/interfaces/IExecutor.sol index 70d150a..4ce6a73 100644 --- a/packages/layerzero-v2/evm/messagelib/contracts/interfaces/IExecutor.sol +++ b/packages/layerzero-v2/evm/messagelib/contracts/interfaces/IExecutor.sol @@ -10,17 +10,19 @@ import { ILayerZeroExecutor } from "./ILayerZeroExecutor.sol"; interface IExecutor is IWorker, ILayerZeroExecutor { struct DstConfigParam { uint32 dstEid; - uint64 baseGas; + uint64 lzReceiveBaseGas; + uint64 lzComposeBaseGas; uint16 multiplierBps; uint128 floorMarginUSD; uint128 nativeCap; } struct DstConfig { - uint64 baseGas; // for verifying / fixed calldata overhead + uint64 lzReceiveBaseGas; uint16 multiplierBps; uint128 floorMarginUSD; // uses priceFeed PRICE_RATIO_DENOMINATOR uint128 nativeCap; + uint64 lzComposeBaseGas; } struct ExecutionParams { @@ -40,5 +42,5 @@ interface IExecutor is IWorker, ILayerZeroExecutor { event DstConfigSet(DstConfigParam[] params); event NativeDropApplied(Origin origin, uint32 dstEid, address oapp, NativeDropParams[] params, bool[] success); - function dstConfig(uint32 _dstEid) external view returns (uint64, uint16, uint128, uint128); + function dstConfig(uint32 _dstEid) external view returns (uint64, uint16, uint128, uint128, uint64); } diff --git a/packages/layerzero-v2/evm/messagelib/contracts/interfaces/IExecutorFeeLib.sol b/packages/layerzero-v2/evm/messagelib/contracts/interfaces/IExecutorFeeLib.sol index 92de5b0..1e8a0cd 100644 --- a/packages/layerzero-v2/evm/messagelib/contracts/interfaces/IExecutorFeeLib.sol +++ b/packages/layerzero-v2/evm/messagelib/contracts/interfaces/IExecutorFeeLib.sol @@ -18,6 +18,7 @@ interface IExecutorFeeLib { error Executor_UnsupportedOptionType(uint8 optionType); error Executor_InvalidExecutorOptions(uint256 cursor); error Executor_ZeroLzReceiveGasProvided(); + error Executor_ZeroLzComposeGasProvided(); error Executor_EidNotSupported(uint32 eid); function getFeeOnSend( diff --git a/packages/layerzero-v2/evm/messagelib/test/ExecutorFeeLib.t.sol b/packages/layerzero-v2/evm/messagelib/test/ExecutorFeeLib.t.sol index 395322c..ab991e4 100644 --- a/packages/layerzero-v2/evm/messagelib/test/ExecutorFeeLib.t.sol +++ b/packages/layerzero-v2/evm/messagelib/test/ExecutorFeeLib.t.sol @@ -42,7 +42,7 @@ contract ExecutorFeeLibTest is Test { priceFeed = new PriceFeedMock(); executorFeeLib = new ExecutorFeeLib(1e18); priceFeed.setup(gasFee, priceRatio, nativePriceUSD); - config = IExecutor.DstConfig(baseGas, multiplierBps, floorMarginUSD, nativeDropCap); + config = IExecutor.DstConfig(baseGas, multiplierBps, floorMarginUSD, nativeDropCap, 0); } function test_getFee_noOptions_revert() public { @@ -77,7 +77,7 @@ contract ExecutorFeeLibTest is Test { } function test_getFee_lzReceiveOption_defaultMultiplier() public { - config = IExecutor.DstConfig(baseGas, 0, 0, nativeDropCap); + config = IExecutor.DstConfig(baseGas, 0, 0, nativeDropCap, 0); uint256 dstFee = (dstAmount * priceRatio) / priceFeed.getPriceRatioDenominator(); uint256 expected = ((gasFee + dstFee) * defaultMultiplierBps) / 10000; @@ -99,7 +99,7 @@ contract ExecutorFeeLibTest is Test { } function test_getFee_lzReceiveOption_specificMultiplier() public { - config = IExecutor.DstConfig(baseGas, multiplierBps, 0, nativeDropCap); + config = IExecutor.DstConfig(baseGas, multiplierBps, 0, nativeDropCap, 0); uint256 dstFee = (dstAmount * priceRatio) / priceFeed.getPriceRatioDenominator(); uint256 expected = ((gasFee + dstFee) * multiplierBps) / 10000; @@ -285,7 +285,7 @@ contract ExecutorFeeLibTest is Test { calldataSize, defaultMultiplierBps ); - config = IExecutor.DstConfig(baseGas, defaultMultiplierBps, 0, 0); + config = IExecutor.DstConfig(baseGas, defaultMultiplierBps, 0, 0, 0); bytes memory executorOption = abi.encodePacked(OPTION_TYPE_LZRECEIVE, dstGas, uint128(0)); uint256 actual = executorFeeLib.getFee( @@ -307,7 +307,7 @@ contract ExecutorFeeLibTest is Test { calldataSize, defaultMultiplierBps ); - config = IExecutor.DstConfig(baseGas, defaultMultiplierBps, 0, 0); + config = IExecutor.DstConfig(baseGas, defaultMultiplierBps, 0, 0, 0); bytes memory executorOption = abi.encodePacked(OPTION_TYPE_LZRECEIVE, dstGas, uint128(0)); uint256 actual = executorFeeLib.getFeeOnSend( diff --git a/packages/layerzero-v2/evm/messagelib/test/util/Setup.sol b/packages/layerzero-v2/evm/messagelib/test/util/Setup.sol index 8d0e69f..a25272a 100644 --- a/packages/layerzero-v2/evm/messagelib/test/util/Setup.sol +++ b/packages/layerzero-v2/evm/messagelib/test/util/Setup.sol @@ -168,7 +168,8 @@ library Setup { IExecutor.DstConfigParam[] memory dstConfigParams = new IExecutor.DstConfigParam[](1); dstConfigParams[0] = IExecutor.DstConfigParam({ dstEid: remoteEid, - baseGas: 5000, + lzReceiveBaseGas: 5000, + lzComposeBaseGas: 0, multiplierBps: 10000, floorMarginUSD: 1e10, nativeCap: 1 gwei @@ -180,7 +181,8 @@ library Setup { IExecutor.DstConfigParam[] memory dstConfigParams = new IExecutor.DstConfigParam[](1); dstConfigParams[0] = IExecutor.DstConfigParam({ dstEid: remoteEid, - baseGas: 5000, + lzReceiveBaseGas: 5000, + lzComposeBaseGas: 0, multiplierBps: 10000, floorMarginUSD: 1e10, nativeCap: 1 gwei diff --git a/packages/layerzero-v2/evm/oapp/test/TestHelper.sol b/packages/layerzero-v2/evm/oapp/test/TestHelper.sol index b22a4d0..c9ada1a 100644 --- a/packages/layerzero-v2/evm/oapp/test/TestHelper.sol +++ b/packages/layerzero-v2/evm/oapp/test/TestHelper.sol @@ -180,7 +180,8 @@ contract TestHelper is Test, OptionsHelper { // executor config dstConfigParams[j] = IExecutor.DstConfigParam({ dstEid: dstEid, - baseGas: 5000, + lzReceiveBaseGas: 5000, + lzComposeBaseGas: 0, multiplierBps: 10000, floorMarginUSD: 1e10, nativeCap: executorValueCap