diff --git a/documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md b/documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md new file mode 100644 index 000000000..f916083ec --- /dev/null +++ b/documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md @@ -0,0 +1,14 @@ +# Gateway Upgrade Notes + +## `isReceiveEnabled` + +The `LZBridgeGateway.isReceiveEnabled` flag is provided for use during future gateway replacements, so that a disabled old gateway can continue delivering in-flight LZ messages instead of reverting them. + +- Managed automatically by `enable()` (sets `true`) and `disable()` (sets `false`). +- Can be set manually via `setIsReceiveEnabled()`, gated to `emergency` / `admin` roles. + +### Expected usage + +1. **OCG proposal** calls `oldGateway.disable("")` then `oldGateway.setIsReceiveEnabled(true)` (and enables the new gateway). The old gateway can no longer send but still delivers incoming messages. +2. **DAO MS** reconfigures non-canonical chains at its own pace; in-flight messages continue to arrive at the old gateway. +3. **Old gateway is deactivated in the Kernel** once operations are complete. Calling `setIsReceiveEnabled(false)` is not required — Kernel deactivation is sufficient. diff --git a/src/policies/bridge/LZBridgeGateway.sol b/src/policies/bridge/LZBridgeGateway.sol index d5667f34c..10843605b 100644 --- a/src/policies/bridge/LZBridgeGateway.sol +++ b/src/policies/bridge/LZBridgeGateway.sol @@ -103,6 +103,9 @@ contract LZBridgeGateway is /// @inheritdoc ILZBridgeGateway uint256 public override bridgedSupply; + /// @inheritdoc ILZBridgeGateway + bool public override isReceiveEnabled; + /// @inheritdoc ILZBridgeGateway mapping(uint32 eid_ => bytes32) public override peers; @@ -174,6 +177,18 @@ contract LZBridgeGateway is return (1, 0); } + // ========= POLICY ENABLER ========= // + + /// @dev Sets isReceiveEnabled to true when the gateway is enabled. + function _enable(bytes calldata) internal override { + if (!isReceiveEnabled) _setIsReceiveEnabled(true); + } + + /// @dev Resets isReceiveEnabled to false when the gateway is disabled. + function _disable(bytes calldata) internal override { + if (isReceiveEnabled) _setIsReceiveEnabled(false); + } + // ========= OHM BRIDGING ========= // /// @inheritdoc ILZBridgeGateway @@ -263,7 +278,7 @@ contract LZBridgeGateway is /// @inheritdoc ILayerZeroReceiver /// @dev Reverts if: - /// - The gateway is not enabled. + /// - Receiving is not enabled (isReceiveEnabled is false). /// - The caller is not the LayerZero endpoint. /// - The origin sender is not the configured peer for the source endpoint ID. /// - No peer is configured for the source endpoint ID. @@ -277,7 +292,8 @@ contract LZBridgeGateway is bytes calldata message_, address, bytes calldata - ) external payable override onlyEnabled { + ) external payable override { + if (!isReceiveEnabled) revert LZBridgeGateway_ReceiveNotEnabled(); if (msg.sender != LZ_ENDPOINT) revert LZBridgeGateway_OnlyEndpoint(); if (_getPeerOrRevert(origin_.srcEid) != origin_.sender) revert LZBridgeGateway_OnlyPeer(origin_.srcEid, origin_.sender); @@ -305,6 +321,20 @@ contract LZBridgeGateway is emit PeerSet(eid_, peer_); } + /// @inheritdoc ILZBridgeGateway + /// @dev Reverts if: + /// - The caller does not have the emergency or admin role. + /// - The gateway is currently enabled. + /// - The value is already in the desired state. + function setIsReceiveEnabled( + bool isReceiveEnabled_ + ) external override onlyEmergencyOrAdminRole { + if (isEnabled) revert LZBridgeGateway_ReceiveControlOnlyWhenDisabled(); + if (isReceiveEnabled == isReceiveEnabled_) + revert LZBridgeGateway_ReceiveAlreadyInDesiredState(); + _setIsReceiveEnabled(isReceiveEnabled_); + } + /// @inheritdoc ILZBridgeGateway /// @dev Reverts if: /// - The caller does not have the bridge_admin or admin role. @@ -534,6 +564,11 @@ contract LZBridgeGateway is // ========= PRIVATE FUNCTIONS ========= // + function _setIsReceiveEnabled(bool isReceiveEnabled_) private { + isReceiveEnabled = isReceiveEnabled_; + emit IsReceiveEnabledSet(isReceiveEnabled_); + } + /// @notice Decodes the message type from the payload and routes to the appropriate handler. function _decodeAndRoute(uint32 srcEid_, bytes32 guid_, bytes calldata payload_) private { if (payload_.length < _MIN_PAYLOAD_LENGTH) revert LZBridgeGateway_InvalidPayload(); diff --git a/src/policies/interfaces/ILZBridgeGateway.sol b/src/policies/interfaces/ILZBridgeGateway.sol index 346594f46..e493f87a8 100644 --- a/src/policies/interfaces/ILZBridgeGateway.sol +++ b/src/policies/interfaces/ILZBridgeGateway.sol @@ -49,6 +49,15 @@ interface ILZBridgeGateway is IVersioned, ILZEndpointV2Admin { /// @param options The invalid options bytes. error LZBridgeGateway_InvalidOptions(bytes options); + /// @notice Thrown when lzReceive is called while receiving is disabled. + error LZBridgeGateway_ReceiveNotEnabled(); + + /// @notice Thrown when setIsReceiveEnabled is called while the gateway is enabled. + error LZBridgeGateway_ReceiveControlOnlyWhenDisabled(); + + /// @notice Thrown when setIsReceiveEnabled is called with the current value. + error LZBridgeGateway_ReceiveAlreadyInDesiredState(); + // ========= EVENTS ========= // /// @notice Emitted when OHM is burned and sent to another chain. @@ -94,6 +103,10 @@ interface ILZBridgeGateway is IVersioned, ILZEndpointV2Admin { /// @param enforcedOptions The enforced option parameters. event EnforcedOptionsSet(EnforcedOptionParam[] enforcedOptions); + /// @notice Emitted when the isReceiveEnabled flag is changed. + /// @param isReceiveEnabled The new value. + event IsReceiveEnabledSet(bool isReceiveEnabled); + // ========= CORE FUNCTIONS ========= // /// @notice Burns OHM held by the gateway and sends a bridge message to a destination chain. @@ -141,6 +154,15 @@ interface ILZBridgeGateway is IVersioned, ILZEndpointV2Admin { /// @param peer_ The peer (remote gateway) address (bytes32 or `bytes32(0)` to clear). function setPeer(uint32 eid_, bytes32 peer_) external; + /// @notice Sets whether the gateway can receive messages. + /// @dev Only callable by the emergency or admin role. + /// Managed automatically by enable()/disable(), but can be set manually + /// to allow receiving while the gateway is disabled + /// (e.g. during gateway replacements, to deliver in-flight messages). + /// + /// @param isReceiveEnabled_ The desired state. + function setIsReceiveEnabled(bool isReceiveEnabled_) external; + /// @notice Sets the delegate on the LayerZero endpoint. /// @dev Only callable by the bridge_admin or admin role. /// @@ -220,6 +242,12 @@ interface ILZBridgeGateway is IVersioned, ILZEndpointV2Admin { bytes calldata extraOptions_ ) external view returns (bytes memory); + /// @notice Whether lzReceive() can process incoming messages. + /// @dev Automatically set to true by enable() and false by disable(). + /// Can be manually set via setIsReceiveEnabled() to allow receiving + /// while the gateway is otherwise disabled (e.g., during gateway replacements). + function isReceiveEnabled() external view returns (bool); + /// @notice Returns whether this is the canonical (mainnet) chain. // solhint-disable-next-line func-name-mixedcase function IS_CANONICAL() external view returns (bool); diff --git a/src/proposals/LZBridgeSecurityUpgradeProposal.sol b/src/proposals/LZBridgeSecurityUpgradeProposal.sol index d25151fb3..827de0ea9 100644 --- a/src/proposals/LZBridgeSecurityUpgradeProposal.sol +++ b/src/proposals/LZBridgeSecurityUpgradeProposal.sol @@ -15,12 +15,10 @@ import {ProposalScript} from "src/proposals/ProposalScript.sol"; import {LZConfigLib} from "src/libraries/LZConfigLib.sol"; // Interfaces -import {EnforcedOptionParam} from "@lz-oapp-evm-0.4.1/oapp/interfaces/IOAppOptionsType3.sol"; import {ExecutorConfig} from "@lz-evm-messagelib-v2-3.0.162/SendLibBase.sol"; import {UlnConfig} from "@lz-evm-messagelib-v2-3.0.162/uln/UlnBase.sol"; import {ILayerZeroEndpointV2} from "@lz-evm-protocol-v2-3.0.162/interfaces/ILayerZeroEndpointV2.sol"; import {IEndpointV2State} from "src/interfaces/layerzero/IEndpointV2State.sol"; -import {SetConfigParam} from "@lz-evm-protocol-v2-3.0.162/interfaces/IMessageLibManager.sol"; // Constants import {ADMIN_ROLE} from "src/policies/utils/RoleDefinitions.sol"; @@ -31,7 +29,6 @@ import {RolesAdmin} from "src/policies/RolesAdmin.sol"; import {ROLESv1} from "src/modules/ROLES/ROLES.v1.sol"; import {LZBridgeGateway} from "src/policies/bridge/LZBridgeGateway.sol"; import {LZBridgeActivator} from "src/proposals/LZBridgeActivator.sol"; -import {ILZBridgeGateway} from "src/policies/interfaces/ILZBridgeGateway.sol"; import {PolicyEnabler} from "src/policies/utils/PolicyEnabler.sol"; /// @notice OCG proposal for the LayerZero Bridge Security Upgrade. diff --git a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_BurnAndSend.t.sol b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_BurnAndSend.t.sol index fbb713275..4e742be39 100644 --- a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_BurnAndSend.t.sol +++ b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_BurnAndSend.t.sol @@ -549,6 +549,26 @@ contract LZBridgeGatewayTests_BurnAndSend is LZBridgeGatewayTestBase { vm.stopPrank(); } + function test_burnAndSend_revertsIfBridgeDisabledAndReceiveEnabled() external { + vm.startPrank(admin); + gateway.disable(bytes("")); + gateway.setIsReceiveEnabled(true); + vm.stopPrank(); + + vm.startPrank(facilitator); + ohm.transfer(address(gateway), 100e9); + + vm.expectRevert(abi.encodeWithSelector(IEnabler.NotEnabled.selector)); + gateway.burnAndSend{value: 1 ether}( + NONCANONICAL_EID, + recipient, + 100e9, + payable(facilitator), + bytes("") + ); + vm.stopPrank(); + } + function testFuzz_burnAndSend_revertsIfNotBridgeFacilitator(address caller_) external { vm.assume(caller_ != facilitator); diff --git a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol index 76d6c0127..866f6fbe2 100644 --- a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol +++ b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: AGPL-3.0 pragma solidity >=0.8.30; +import {Vm} from "forge-std/Vm.sol"; + import {LZBridgeGatewayTestBase} from "src/test/policies/bridge/LZBridgeGateway/LZBridgeGatewayTestBase.sol"; // Interfaces import {IEnabler} from "src/periphery/interfaces/IEnabler.sol"; +import {ILZBridgeGateway} from "src/policies/interfaces/ILZBridgeGateway.sol"; import {IPolicyAdmin} from "src/policies/interfaces/utils/IPolicyAdmin.sol"; // Constants @@ -25,6 +28,35 @@ contract LZBridgeGatewayTests_EnableDisable is LZBridgeGatewayTestBase { vm.stopPrank(); } + function test_enable_setsIsReceiveEnabledTrue() external { + vm.startPrank(admin); + gateway.disable(bytes("")); + assertFalse(gateway.isReceiveEnabled(), "isReceiveEnabled should be false after disable"); + + gateway.enable(bytes("")); + assertTrue(gateway.isReceiveEnabled(), "enable should set isReceiveEnabled to true"); + vm.stopPrank(); + } + + function test_enable_skipsIsReceiveEnabledEventIfAlreadyTrue() external { + vm.startPrank(admin); + gateway.disable(bytes("")); + gateway.setIsReceiveEnabled(true); + assertTrue(gateway.isReceiveEnabled(), "isReceiveEnabled should be true after manual set"); + + // enable() should NOT emit IsReceiveEnabledSet because it is already true + vm.recordLogs(); + gateway.enable(bytes("")); + + Vm.Log[] memory logs = vm.getRecordedLogs(); + bytes32 eventSig = ILZBridgeGateway.IsReceiveEnabledSet.selector; + for (uint256 i = 0; i < logs.length; ++i) { + assertTrue(logs[i].topics[0] != eventSig, "Should not emit IsReceiveEnabledSet"); + } + assertTrue(gateway.isReceiveEnabled(), "isReceiveEnabled should remain true after enable"); + vm.stopPrank(); + } + function test_enable_revertsIfAlreadyEnabled() external { vm.expectRevert(abi.encodeWithSelector(IEnabler.NotDisabled.selector)); vm.prank(admin); @@ -49,6 +81,24 @@ contract LZBridgeGatewayTests_EnableDisable is LZBridgeGatewayTestBase { assertFalse(gateway.isEnabled(), "Should be disabled"); } + function test_disable_setsIsReceiveEnabledFalse() external { + assertTrue(gateway.isReceiveEnabled(), "isReceiveEnabled should be true after setUp"); + + vm.startPrank(admin); + + // Enable receive while disabled + gateway.disable(bytes("")); + gateway.setIsReceiveEnabled(true); + assertTrue(gateway.isReceiveEnabled(), "isReceiveEnabled should be true"); + + // Re-enable, then disable again, isReceiveEnabled should be reset + gateway.enable(bytes("")); + gateway.disable(bytes("")); + assertFalse(gateway.isReceiveEnabled(), "disable should reset isReceiveEnabled"); + + vm.stopPrank(); + } + function test_disable_revertsIfAlreadyDisabled() external { vm.prank(admin); gateway.disable(bytes("")); diff --git a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_LzReceive.t.sol b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_LzReceive.t.sol index e38785e65..68b867611 100644 --- a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_LzReceive.t.sol +++ b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_LzReceive.t.sol @@ -5,7 +5,6 @@ import {LZBridgeGatewayTestBase} from "src/test/policies/bridge/LZBridgeGateway/ // Interfaces import {Origin} from "@lz-evm-protocol-v2-3.0.162/interfaces/ILayerZeroEndpointV2.sol"; -import {IEnabler} from "src/periphery/interfaces/IEnabler.sol"; import {ILZBridgeGateway} from "src/policies/interfaces/ILZBridgeGateway.sol"; // Libraries @@ -52,6 +51,36 @@ contract LZBridgeGatewayTests_LzReceive is LZBridgeGatewayTestBase { ); } + function test_lzReceive_succeedsWhenBridgeDisabledButReceiveEnabled() external { + // Disable gateway2 but allow receiving + vm.startPrank(admin); + gateway2.disable(bytes("")); + gateway2.setIsReceiveEnabled(true); + vm.stopPrank(); + + assertFalse(gateway2.isEnabled(), "isEnabled should be false"); + assertTrue(gateway2.isReceiveEnabled(), "isReceiveEnabled should be true"); + + // Send from canonical — gateway2 should still receive + _sendCanonicalToNonCanonical(recipient, 1000e9); + + assertEq(ohm.balanceOf(recipient), 1000e9, "Recipient should receive OHM despite disabled"); + } + + function test_lzReceive_succeedsAfterBridgeDisableAndReceiveEnable() external { + vm.startPrank(admin); + gateway2.disable(bytes("")); + gateway2.setIsReceiveEnabled(true); + gateway2.enable(bytes("")); + vm.stopPrank(); + + assertTrue(gateway2.isReceiveEnabled(), "isReceiveEnabled should be true after re-enable"); + + _sendCanonicalToNonCanonical(recipient, 1000e9); + + assertEq(ohm.balanceOf(recipient), 1000e9, "Recipient should receive OHM"); + } + function testFuzz_lzReceive_revertsIfNotEndpoint(address caller_) external { vm.assume(caller_ != address(endpointSetup.endpointList[1])); @@ -207,7 +236,7 @@ contract LZBridgeGatewayTests_LzReceive is LZBridgeGatewayTestBase { gateway2.lzReceive(origin, bytes32(0), bytes(""), address(0), bytes("")); } - function test_lzReceive_revertsIfNotEnabled() external { + function test_lzReceive_revertsIfBridgeDisabledAndReceiveEnabledFalse() external { vm.prank(admin); gateway2.disable(bytes("")); @@ -217,7 +246,9 @@ contract LZBridgeGatewayTests_LzReceive is LZBridgeGatewayTestBase { nonce: 1 }); - vm.expectRevert(abi.encodeWithSelector(IEnabler.NotEnabled.selector)); + vm.expectRevert( + abi.encodeWithSelector(ILZBridgeGateway.LZBridgeGateway_ReceiveNotEnabled.selector) + ); vm.prank(address(endpointSetup.endpointList[1])); gateway2.lzReceive(origin, bytes32(0), bytes(""), address(0), bytes("")); } diff --git a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_RetryingFailedMessages.t.sol b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_RetryingFailedMessages.t.sol index 86a30f295..18a5d1181 100644 --- a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_RetryingFailedMessages.t.sol +++ b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_RetryingFailedMessages.t.sol @@ -137,8 +137,9 @@ contract LZBridgeGatewayTests_RetryingFailedMessages is LZBridgeGatewayTestBase_ // ========== DISABLED BRIDGE -> RE-ENABLE -> RETRY ========== // /// @notice Destination gateway disabled. LZBridgeGateway.lzReceive() reverts - /// with NotEnabled (onlyEnabled modifier). Endpoint _clearPayload rolls - /// back, payload hash stays in storage. Re-enable and retry succeeds. + /// with ReceiveNotEnabled (isReceiveEnabled is false). Endpoint + /// _clearPayload rolls back, payload hash stays in storage. Re-enable + /// and retry succeeds. function test_lzReceive_disabledBridgeSoReEnableAndRetry() external { uint256 amount = 5000e9; @@ -183,6 +184,37 @@ contract LZBridgeGatewayTests_RetryingFailedMessages is LZBridgeGatewayTestBase_ assertEq(ohm.balanceOf(recipient), amount, "Recipient should receive OHM after retry"); } + // ========== GATEWAY DISABLED -> SET RECEIVE ENABLED -> RETRY ========== // + + /// @notice Gateway disabled after send. lzReceive() reverts with + /// ReceiveNotEnabled. Admin sets isReceiveEnabled, retry succeeds. + function test_lzReceive_gatewayDisabledSoSetReceiveEnabledAndRetry() external { + // 1. Send from canonical, packet enters mock queue + bytes memory packetBytes = _sendCanonicalNoDeliver(1000e9); + + // 2. Disable destination gateway (isReceiveEnabled becomes false) + vm.prank(admin); + gateway2.disable(bytes("")); + assertFalse(gateway2.isReceiveEnabled(), "Receiving should be disabled"); + + // 3. Verify packet in endpoint (hash stored) + _verifyOnly(packetBytes); + + // 4. Delivery fails (isReceiveEnabled == false) + bool delivered = _tryDeliverPacket(packetBytes); + assertFalse(delivered, "Delivery should fail while receiving is disabled"); + assertEq(ohm.balanceOf(recipient), 0, "Recipient should have no OHM yet"); + + // 5. Admin enables receiving without re-enabling the gateway + vm.prank(admin); + gateway2.setIsReceiveEnabled(true); + + // 6. Retry delivery succeeds + _manualDeliver(packetBytes, 1); + + assertEq(ohm.balanceOf(recipient), 1000e9, "Recipient should receive OHM after retry"); + } + // ========== INSUFFICIENT GAS IN ENFORCED OPTIONS ========== // /// @notice Enforced options encode too little gas. EndpointV2.lzReceive() runs diff --git a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_SetIsReceiveEnabled.t.sol b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_SetIsReceiveEnabled.t.sol new file mode 100644 index 000000000..21277a04f --- /dev/null +++ b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_SetIsReceiveEnabled.t.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: AGPL-3.0 +pragma solidity >=0.8.30; + +import {LZBridgeGatewayTestBase} from "src/test/policies/bridge/LZBridgeGateway/LZBridgeGatewayTestBase.sol"; + +// Interfaces +import {ILZBridgeGateway} from "src/policies/interfaces/ILZBridgeGateway.sol"; +import {IPolicyAdmin} from "src/policies/interfaces/utils/IPolicyAdmin.sol"; + +/// @dev setIsReceiveEnabled access control and state transitions. +contract LZBridgeGatewayTests_SetIsReceiveEnabled is LZBridgeGatewayTestBase { + function test_setIsReceiveEnabled() external { + vm.prank(admin); + gateway.disable(bytes("")); + + vm.expectEmit(); + emit ILZBridgeGateway.IsReceiveEnabledSet(true); + + vm.prank(admin); + gateway.setIsReceiveEnabled(true); + + assertTrue(gateway.isReceiveEnabled(), "isReceiveEnabled should be true"); + } + + function test_setIsReceiveEnabled_disables() external { + vm.startPrank(admin); + gateway.disable(bytes("")); + gateway.setIsReceiveEnabled(true); + vm.stopPrank(); + + vm.expectEmit(); + emit ILZBridgeGateway.IsReceiveEnabledSet(false); + + vm.prank(admin); + gateway.setIsReceiveEnabled(false); + + assertFalse(gateway.isReceiveEnabled(), "isReceiveEnabled should be false"); + } + + function test_setIsReceiveEnabled_revertsIfAlreadyInDesiredState() external { + // Disable first (setIsReceiveEnabled only works when disabled) + vm.prank(admin); + gateway.disable(bytes("")); + // isReceiveEnabled is false after disable; setting false should revert + vm.expectRevert( + abi.encodeWithSelector( + ILZBridgeGateway.LZBridgeGateway_ReceiveAlreadyInDesiredState.selector + ) + ); + vm.prank(admin); + gateway.setIsReceiveEnabled(false); + } + + function test_setIsReceiveEnabled_revertsIfBridgeEnabled() external { + vm.expectRevert( + abi.encodeWithSelector( + ILZBridgeGateway.LZBridgeGateway_ReceiveControlOnlyWhenDisabled.selector + ) + ); + vm.prank(admin); + gateway.setIsReceiveEnabled(false); + } + + function testFuzz_setIsReceiveEnabled_revertsIfNotAdminOrEmergency(address caller_) external { + vm.assume(caller_ != admin); + + vm.prank(admin); + gateway.disable(bytes("")); + + vm.expectRevert(abi.encodeWithSelector(IPolicyAdmin.NotAuthorised.selector)); + vm.prank(caller_); + gateway.setIsReceiveEnabled(true); + } +} diff --git a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_View.t.sol b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_View.t.sol index 1eb260647..991e3ceba 100644 --- a/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_View.t.sol +++ b/src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_View.t.sol @@ -9,6 +9,10 @@ import {Origin} from "@lz-evm-protocol-v2-3.0.162/interfaces/ILayerZeroEndpointV // Libraries import {LZConfigLib} from "src/libraries/LZConfigLib.sol"; +// Contracts +import {Actions, Kernel} from "src/Kernel.sol"; +import {LZBridgeGateway} from "src/policies/bridge/LZBridgeGateway.sol"; + /// @dev Public view function verification. contract LZBridgeGatewayTests_View is LZBridgeGatewayTestBase { function test_allowInitializePath_validPeer() external view { @@ -84,6 +88,31 @@ contract LZBridgeGatewayTests_View is LZBridgeGatewayTestBase { ); } + function test_isReceiveEnabled_defaultsFalse() external { + LZBridgeGateway freshGateway = new LZBridgeGateway( + kernel, + address(endpointSetup.endpointList[0]), + true + ); + assertFalse(freshGateway.isReceiveEnabled(), "isReceiveEnabled should default to false"); + } + + function test_isReceiveEnabled_returnsTrueAfterBridgeEnabling() external { + LZBridgeGateway freshGateway = new LZBridgeGateway( + kernel, + address(endpointSetup.endpointList[0]), + true + ); + kernel.executeAction(Actions.ActivatePolicy, address(freshGateway)); + + assertFalse(freshGateway.isReceiveEnabled(), "Should be false before enable"); + + vm.prank(admin); + freshGateway.enable(bytes("")); + + assertTrue(freshGateway.isReceiveEnabled(), "isReceiveEnabled should be true after enable"); + } + function test_getAmountCanBeSent() external { // Configure rate limit: 10_000e9 over 1 hour uint192 limit = 10_000e9;