Skip to content

feat(lz-bridge-gw): add isReceiveEnabled flag for gateway replacements - #231

Merged
zeroxnoodle merged 4 commits into
lz-bridge-upgradefrom
feature/lz-bridge-is-receive-enabled
Apr 16, 2026
Merged

zeroxnoodle merged 4 commits into
lz-bridge-upgradefrom
feature/lz-bridge-is-receive-enabled

Conversation

@Yurii3721

@Yurii3721 Yurii3721 commented Apr 14, 2026 •

Copy link
Copy Markdown
Contributor

Allow lzReceive() to continue accepting in-flight messages after disable(), preventing stuck messages during gateway replacements. The flag is gated to emergency/admin, reset automatically by disable() (for an emergency shutdown), and has no effect when the gateway is enabled.

Summary by CodeRabbit

  • Documentation

    • Added gateway upgrade notes explaining the new receive-control flag and its role during gateway replacements.
  • New Features

    • Receive control separated from send/enable state so message reception can be toggled independently.
    • Admin/emergency-facing control to manually set receive-enabled state.
  • Tests

    • Extensive tests covering receive-control behavior, access rules, enable/disable interactions, and message retry scenarios.

Allow lzReceive() to continue accepting in-flight messages after
disable(), preventing stuck messages during gateway replacements.
The flag is gated to emergency/admin, reset automatically by disable(),
and has no effect when the gateway is enabled.
@coderabbitai

coderabbitai Bot commented Apr 14, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@Yurii3721 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 54 minutes and 43 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 54 minutes and 43 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 7a6cfe9c-ebb3-494f-81c8-072d383b7512

📥 Commits

Reviewing files that changed from the base of the PR and between 28d481c and ae0d24e.

📒 Files selected for processing (1)
  • src/policies/bridge/LZBridgeGateway.sol
📝 Walkthrough

Walkthrough

Added an independent receive-gating flag isReceiveEnabled to LZBridgeGateway, managed by enable/disable hooks and manually adjustable via setIsReceiveEnabled() (restricted to emergency/admin). lzReceive() now checks this flag at runtime instead of using onlyEnabled, enabling staged gateway replacements and delivery continuity.

Changes

Cohort / File(s) Summary
Interface Extensions
src/policies/interfaces/ILZBridgeGateway.sol
Added errors for receive-gating, IsReceiveEnabledSet(bool) event, setIsReceiveEnabled(bool) admin setter, and isReceiveEnabled() view.
Core Implementation
src/policies/bridge/LZBridgeGateway.sol
Added public isReceiveEnabled, internal _setIsReceiveEnabled(), enabler hooks _enable()/_disable() to flip the flag, admin setIsReceiveEnabled() with access checks, and replaced onlyEnabled gating in lzReceive() with an explicit isReceiveEnabled runtime check.
Documentation
documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md
New notes describing isReceiveEnabled semantics, automatic hook behavior, manual setter restrictions, and the recommended operational sequence for gateway replacements.
Tests — New & Modified
src/test/policies/bridge/LZBridgeGateway/...
Added/updated tests covering: setIsReceiveEnabled access, state transitions, idempotency, lzReceive behavior when receive is disabled/enabled independently, retrying failed deliveries, enable/disable interactions, and view assertions for initial/post-enable state. (See multiple test files under the LZBridgeGateway test directory.)
Core Tests — Specific
src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_BurnAndSend.t.sol
Added test asserting burnAndSend reverts when bridge is disabled even if receive is enabled.
Cleanup
src/proposals/LZBridgeSecurityUpgradeProposal.sol
Removed two unused imports.

Sequence Diagram(s)

sequenceDiagram
    participant OCG as OCG Proposal
    participant OldGW as Old Gateway
    participant NewGW as New Gateway
    participant DAO as DAO MS
    participant Kernel as Kernel

    OCG->>Kernel: Propose disable old gateway (stop sending)
    Kernel->>OldGW: disable()  -- enabler hook sets isReceiveEnabled = false
    OCG->>Kernel: Propose enable receive on OldGW and enable NewGW
    Kernel->>OldGW: setIsReceiveEnabled(true) / enable()  -- receive allowed
    Kernel->>NewGW: enable()  -- new gateway active
    DAO->>DAO: Reconfigure non-canonical chains (no delivery interruption to OldGW)
    Kernel->>Kernel: Deactivate OldGW in Kernel (old gateway removed)
    Note right of OldGW: After deactivation, explicit setIsReceiveEnabled(false) not required
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • LZ Bridge Upgrade #220: Touches the same bridge gateway interfaces and behavior; likely overlapping changes around LZBridgeGateway receive/enable semantics.

Suggested reviewers

  • 0xJem

Poem

🐰 A little rabbit whispers through the gate,
Toggling flags so messages don't wait,
Old hops pause while new ones stride,
Admins guide the flow with gentle pride,
Hopping safe where packets glide! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: introducing an isReceiveEnabled flag for managing gateway replacements, which aligns with the core functionality added across the LZBridgeGateway implementation and its tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/lz-bridge-is-receive-enabled

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@0xJem

0xJem commented Apr 15, 2026

Copy link
Copy Markdown
Member

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 15, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

vm.prank(caller_);
gateway.disable(bytes(""));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

validate that isReceiveEnabled() is true if re-enabled:

  • after disable, then enable
  • after disable, then set receive enabled, then enable

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added.

vm.prank(address(endpointSetup.endpointList[1]));
gateway2.lzReceive(origin, bytes32(0), bytes(""), address(0), bytes(""));
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

additional test

  1. disabled
  2. set receive enabled
  3. enable

receive still works

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added.

);
}

function test_isReceiveEnabled_defaultsFalse() external view {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

IMO if enabled, isReceiveEnabled should also be true (otherwise it's confusing)

@Yurii3721 Yurii3721 Apr 16, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

For simplicity, the isReceivedEnabled flag was implemented like this: it’s ignored when isEnabled is true and only checked when !isEnabled.

Since that behavior isn’t intuitive, the flag now works like a regular flag. _enable and _disable are overridden to automatically turn isReceivedEnabled on or off, so lzReceive no longer checks isEnabled (because _enable/_disable always toggle isReceivedEnabled). admin/emergency can call isReceivedEnabled(true) after disable() for gateway replacements. setIsReceivedEnabled is forbidden if isEnabled.

  enable()/disable() now manage isReceiveEnabled automatically via
  _enable/_disable overrides, so lzReceive checks only isReceiveEnabled
  without referencing isEnabled. Add ReceiveNotEnabled error for lzReceive
  distinct from the onlyEnabled modifier used by burnAndSend.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/policies/interfaces/ILZBridgeGateway.sol (1)

242-243: Clarify NatSpec for isReceiveEnabled().

The comment "Whether receiving is allowed while the gateway is disabled" is accurate for the primary use case but could be clearer, since isReceiveEnabled is also true when the gateway is fully enabled. Consider:

-    /// `@notice` Whether receiving is allowed while the gateway is disabled.
+    /// `@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).
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/policies/interfaces/ILZBridgeGateway.sol` around lines 242 - 243, Update
the NatSpec for the isReceiveEnabled() function to explicitly state its two
conditions: return true when the gateway is fully enabled and also return true
when the gateway is disabled but receiving is still permitted; reference the
function name isReceiveEnabled() in the comment and replace the current line
with a clearer description like “Returns true if receiving is permitted — either
because the gateway is fully enabled or because receiving is explicitly allowed
while the gateway is disabled.”
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/policies/bridge/LZBridgeGateway.sol`:
- Around line 328-334: The setIsReceiveEnabled function currently allows
toggling isReceiveEnabled while the gateway is enabled, which can block inbound
messages; modify setIsReceiveEnabled (and/or _setIsReceiveEnabled) to prevent
changing isReceiveEnabled when isEnabled == true by adding a guard that reverts
if isEnabled is true (introduce a new revert like
LZBridgeGateway_CannotToggleReceiveWhileEnabled or reuse an existing appropriate
error) so the receive flag cannot affect the enabled gateway path.

---

Nitpick comments:
In `@src/policies/interfaces/ILZBridgeGateway.sol`:
- Around line 242-243: Update the NatSpec for the isReceiveEnabled() function to
explicitly state its two conditions: return true when the gateway is fully
enabled and also return true when the gateway is disabled but receiving is still
permitted; reference the function name isReceiveEnabled() in the comment and
replace the current line with a clearer description like “Returns true if
receiving is permitted — either because the gateway is fully enabled or because
receiving is explicitly allowed while the gateway is disabled.”
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85b5ddb3-f652-4af5-8d23-cd754c3f491e

📥 Commits

Reviewing files that changed from the base of the PR and between 750ab4f and f8b8c86.

📒 Files selected for processing (10)
  • documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md
  • src/policies/bridge/LZBridgeGateway.sol
  • src/policies/interfaces/ILZBridgeGateway.sol
  • src/proposals/LZBridgeSecurityUpgradeProposal.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_BurnAndSend.t.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_LzReceive.t.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_RetryingFailedMessages.t.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_SetIsReceiveEnabled.t.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_View.t.sol
💤 Files with no reviewable changes (1)
  • src/proposals/LZBridgeSecurityUpgradeProposal.sol

Comment thread src/policies/bridge/LZBridgeGateway.sol

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol (1)

31-39: Align new tests with repo test-structure conventions (given* + branching-tree naming).

The newly added test names and setup style don’t follow the repository’s required test pattern. Please migrate these to test_given<Condition>_<Action>_<ExpectedResult>() and move reusable setup into given* modifiers/helpers.

As per coding guidelines: "src/test/**/*.t.sol: Use given* modifiers for state setup in tests" and "Follow branching tree naming for tests: test_given<Condition>_<Action>_<ExpectedResult>()".

Also applies to: 41-58, 84-100

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol`
around lines 31 - 39, Rename and restructure the tests to follow the repo
convention: convert test_enable_setsIsReceiveEnabledTrue to
test_givenGatewayDisabled_enable_setsIsReceiveEnabledTrue and move the repeated
setup into a reusable given modifier/helper (e.g., givenGatewayDisabled) that
performs vm.startPrank(admin), gateway.disable(...), and leaves prank context
active; update corresponding tests at the other ranges (lines noted) similarly
(e.g., test_givenGatewayEnabled_disable_setsIsReceiveEnabledFalse) and replace
inline setup with calls to the given* modifier/helper, ensuring vm.stopPrank()
is called only in a shared teardown or at the end of each test if not handled by
the helper.
documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md (1)

10-14: Convert the “Expected usage” sequence into a checkable TODO runbook.

This section is an execution plan, but it is not trackable in checklist form. Please switch to checkbox items so upgrade progress can be audited during rollout.

📝 Suggested doc update
-### 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.
+### Upgrade TODO checklist
+
+- [ ] **OCG proposal** calls `oldGateway.disable("")`, then `oldGateway.setIsReceiveEnabled(true)`, and enables the new gateway.
+- [ ] **DAO MS** reconfigures non-canonical chains while in-flight messages continue arriving at the old gateway.
+- [ ] **Deactivate old gateway in the Kernel** once operations complete.

As per coding guidelines: "**/*.md: When planning a new feature, write the plan to disk in Markdown format and always include a TODO list that can be checked off".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md` around lines 10 - 14,
Convert the "Expected usage" numbered sequence into a checkable TODO runbook by
replacing the three numbered steps with Markdown checkbox items that mirror each
action and verification point: include checkboxes for "OCG proposal: call
oldGateway.disable(\"\")", "OCG: call oldGateway.setIsReceiveEnabled(true) and
enable new gateway", "DAO MS: reconfigure non-canonical chains and verify
in-flight messages continue to arrive at old gateway", and "Kernel: deactivate
old gateway (no need to call setIsReceiveEnabled(false))"; add brief acceptance
criteria or verification notes for each checkbox (e.g., new gateway enabled,
messages delivered, deactivation confirmed) so rollout progress can be audited.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md`:
- Around line 10-14: Convert the "Expected usage" numbered sequence into a
checkable TODO runbook by replacing the three numbered steps with Markdown
checkbox items that mirror each action and verification point: include
checkboxes for "OCG proposal: call oldGateway.disable(\"\")", "OCG: call
oldGateway.setIsReceiveEnabled(true) and enable new gateway", "DAO MS:
reconfigure non-canonical chains and verify in-flight messages continue to
arrive at old gateway", and "Kernel: deactivate old gateway (no need to call
setIsReceiveEnabled(false))"; add brief acceptance criteria or verification
notes for each checkbox (e.g., new gateway enabled, messages delivered,
deactivation confirmed) so rollout progress can be audited.

In
`@src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol`:
- Around line 31-39: Rename and restructure the tests to follow the repo
convention: convert test_enable_setsIsReceiveEnabledTrue to
test_givenGatewayDisabled_enable_setsIsReceiveEnabledTrue and move the repeated
setup into a reusable given modifier/helper (e.g., givenGatewayDisabled) that
performs vm.startPrank(admin), gateway.disable(...), and leaves prank context
active; update corresponding tests at the other ranges (lines noted) similarly
(e.g., test_givenGatewayEnabled_disable_setsIsReceiveEnabledFalse) and replace
inline setup with calls to the given* modifier/helper, ensuring vm.stopPrank()
is called only in a shared teardown or at the end of each test if not handled by
the helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 456b52b9-911a-4274-8681-e495783f0d0c

📥 Commits

Reviewing files that changed from the base of the PR and between f8b8c86 and 28d481c.

📒 Files selected for processing (5)
  • documentation/lz-bridge/GATEWAY_UPGRADE_NOTES.md
  • src/policies/interfaces/ILZBridgeGateway.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_EnableDisable.t.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_RetryingFailedMessages.t.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_SetIsReceiveEnabled.t.sol
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_RetryingFailedMessages.t.sol
  • src/policies/interfaces/ILZBridgeGateway.sol
  • src/test/policies/bridge/LZBridgeGateway/LZBridgeGateway_SetIsReceiveEnabled.t.sol

@zeroxnoodle
zeroxnoodle merged commit 843f56b into lz-bridge-upgrade Apr 16, 2026
12 checks passed
@zeroxnoodle
zeroxnoodle deleted the feature/lz-bridge-is-receive-enabled branch April 16, 2026 11:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants