Skip to content

modify ntt#830

Open
haoran1money wants to merge 1 commit intowormhole-foundation:mainfrom
1Money-Co:haoran/ntt-changes
Open

modify ntt#830
haoran1money wants to merge 1 commit intowormhole-foundation:mainfrom
1Money-Co:haoran/ntt-changes

Conversation

@haoran1money
Copy link

@haoran1money haoran1money commented Feb 28, 2026

Summary by CodeRabbit

  • New Features

    • Introduced subsidized relaying capability, allowing zero-value cross-chain transfers when subsidies are enabled
    • Added ETH receiving functionality to support subsidy funding
    • New error handling for insufficient subsidy balance
  • Tests

    • Added test coverage for subsidized relaying functionality

@coderabbitai
Copy link

coderabbitai bot commented Feb 28, 2026

📝 Walkthrough

Walkthrough

The changes introduce subsidized relaying support to the Wormhole transceiver contract. A new feature flag enables relaying costs to be subsidized from contract balance instead of requiring message senders to pay delivery fees. The contract can now receive Ether via a fallback function and conditionally computes delivery payments based on subsidy status.

Changes

Cohort / File(s) Summary
Core Subsidy Logic
evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol
Added InsufficientSubsidyBalance error and receive() payable function. Modified _quoteDeliveryPrice to return 0 when subsidies are enabled. Introduced _computeDeliveryPayment helper for centralized payment calculation. Updated _sendMessage to compute, verify, and use subsidy-aware delivery payments across multiple relayer call sites.
State Management
evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol
Added storage slot SUBSIDIZED_RELAYING_SLOT with internal accessor. Introduced public getter isSubsidizedRelayingEnabled() and owner-controlled setter setIsSubsidizedRelayingEnabled(bool) that emits state change events.
Interface Definition
evm/src/interfaces/IWormholeTransceiverState.sol
Added SetIsSubsidizedRelayingEnabled(bool isEnabled) event to track subsidy mode toggles.
Integration Tests
evm/test/IntegrationStandalone.t.sol
Added test function test_subsidizedRelaying_allowsZeroValueTransfer() that verifies zero-value transfers work when subsidized relaying is enabled and contract balance is funded.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Subsidies bloom in the blockchain spring,
No fees to pay, just relays take wing,
The contract receives with open paws,
Waiving costs is the bunny's clause!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'modify ntt' is vague and generic, using non-descriptive language that fails to convey the specific nature of changes made to the codebase. Replace with a descriptive title that highlights the main feature, such as 'Add subsidized relaying feature to WormholeTransceiver' or 'Implement subsidy-aware delivery pricing and balance validation'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
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
  • Post copyable unit tests in a comment

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

Copy link

@coderabbitai coderabbitai bot left a comment

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 (2)
evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol (1)

58-58: Consider adding a withdrawal mechanism for owner to recover subsidy funds.

The receive() function allows anyone to fund the subsidy pool, but there's no mechanism for the owner to withdraw funds. If subsidized relaying is disabled or the contract needs to be migrated, the ETH balance would be stuck.

Example withdrawal function
/// `@notice` Withdraw subsidy funds from the contract.
/// `@dev` This function is only callable by the `owner`.
/// `@param` to The address to send the funds to.
/// `@param` amount The amount of funds to withdraw.
function withdrawSubsidyFunds(address payable to, uint256 amount) external onlyOwner {
    if (amount > address(this).balance) {
        revert InsufficientSubsidyBalance(amount, address(this).balance);
    }
    (bool success,) = to.call{value: amount}("");
    require(success, "withdrawal failed");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol` at line 58,
Add an owner-only withdrawal to recover ETH sent to the contract via receive():
implement a function (e.g., withdrawSubsidyFunds) gated by onlyOwner that
accepts (address payable to, uint256 amount), checks amount <=
address(this).balance and reverts with InsufficientSubsidyBalance if not, then
performs a safe call to transfer the amount and reverts on failure; reference
the existing receive() fallback, the onlyOwner modifier, and the
InsufficientSubsidyBalance error to locate where to add and align behavior with
the contract's ownership and error patterns.
evm/test/IntegrationStandalone.t.sol (1)

300-330: Consider adding assertions to verify the subsidy was used and the transfer completed.

The test validates that the quote is zero and the transfer call succeeds, but doesn't verify:

  1. The transceiver's balance decreased (proving the subsidy was consumed)
  2. The token transfer actually completed (userA balance decreased, NttManager received tokens)
Proposed enhancement to add balance assertions
         vm.startPrank(userA);
         token1.approve(address(nttManagerChain1), sendingAmount);
+
+        uint256 transceiverBalanceBefore = address(wormholeTransceiverChain1).balance;
+        uint256 userBalanceBefore = token1.balanceOf(address(userA));
+
         nttManagerChain1.transfer(
             sendingAmount,
             chainId2,
             bytes32(uint256(uint160(userB))),
             bytes32(uint256(uint160(userA))),
             false,
             instructions
         );
+
+        // Verify subsidy was consumed (wormhole message fee paid from contract)
+        assertLt(
+            address(wormholeTransceiverChain1).balance,
+            transceiverBalanceBefore,
+            "transceiver balance should decrease"
+        );
+
+        // Verify token transfer occurred
+        assertEq(
+            token1.balanceOf(address(userA)),
+            userBalanceBefore - sendingAmount,
+            "user balance should decrease"
+        );
+
         vm.stopPrank();
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@evm/test/IntegrationStandalone.t.sol` around lines 300 - 330, Extend
test_subsidizedRelaying_allowsZeroValueTransfer to assert the subsidy was
consumed and the transfer completed: before funding, record
address(wormholeTransceiverChain1).balance and token1.balanceOf(userA) (and
token1.balanceOf(address(nttManagerChain1)) if NttManager should hold the
tokens), then after the transfer assert the transceiver balance decreased by the
gas/fee amount and userA's token balance decreased by sendingAmount while
nttManagerChain1's token balance increased accordingly; reference
wormholeTransceiverChain1, token1 (DummyToken), nttManagerChain1, userA and
userB to locate the variables and checks to add.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@evm/src/interfaces/IWormholeTransceiverState.sol`:
- Around line 34-38: The interface is missing the getter and setter declarations
for subsidized relaying and documents the wrong Topic0 hash; add function
declarations isSubsidizedRelayingEnabled() external view returns (bool) and
setIsSubsidizedRelayingEnabled(bool isEnabled) external to the interface
(matching the implementations in WormholeTransceiverState) and update the event
comment for SetIsSubsidizedRelayingEnabled(bool) to use the correct Topic0 hash
0x137fed7331b9937fa0c97e8a96a905d05e2efa75614a2b3cb247231bc6570b05.

---

Nitpick comments:
In `@evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol`:
- Line 58: Add an owner-only withdrawal to recover ETH sent to the contract via
receive(): implement a function (e.g., withdrawSubsidyFunds) gated by onlyOwner
that accepts (address payable to, uint256 amount), checks amount <=
address(this).balance and reverts with InsufficientSubsidyBalance if not, then
performs a safe call to transfer the amount and reverts on failure; reference
the existing receive() fallback, the onlyOwner modifier, and the
InsufficientSubsidyBalance error to locate where to add and align behavior with
the contract's ownership and error patterns.

In `@evm/test/IntegrationStandalone.t.sol`:
- Around line 300-330: Extend test_subsidizedRelaying_allowsZeroValueTransfer to
assert the subsidy was consumed and the transfer completed: before funding,
record address(wormholeTransceiverChain1).balance and token1.balanceOf(userA)
(and token1.balanceOf(address(nttManagerChain1)) if NttManager should hold the
tokens), then after the transfer assert the transceiver balance decreased by the
gas/fee amount and userA's token balance decreased by sendingAmount while
nttManagerChain1's token balance increased accordingly; reference
wormholeTransceiverChain1, token1 (DummyToken), nttManagerChain1, userA and
userB to locate the variables and checks to add.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fee796a and a9ceae8.

📒 Files selected for processing (4)
  • evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol
  • evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol
  • evm/src/interfaces/IWormholeTransceiverState.sol
  • evm/test/IntegrationStandalone.t.sol

Comment on lines +34 to +38
/// @notice Emitted when subsidized relaying is enabled or disabled.
/// @dev Topic0
/// 0x1df7b0a641c11c6a0b6c1d1dbd5ed0a9f2e5f9df58f9cb6d3a15d5c7c80d9f15.
/// @param isEnabled A boolean indicating whether subsidized relaying is enabled.
event SetIsSubsidizedRelayingEnabled(bool isEnabled);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Find the interface and implementation files
find . -type f -name "IWormholeTransceiverState.sol" -o -name "WormholeTransceiverState.sol" | head -20

Repository: wormhole-foundation/native-token-transfers

Length of output: 207


🏁 Script executed:

# Read the interface file to check for function declarations
cat -n ./evm/src/interfaces/IWormholeTransceiverState.sol

Repository: wormhole-foundation/native-token-transfers

Length of output: 7217


🏁 Script executed:

# Read the implementation file to check if these functions exist
cat -n ./evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol

Repository: wormhole-foundation/native-token-transfers

Length of output: 14106


🏁 Script executed:

# Verify Topic0 hash for SetIsSubsidizedRelayingEnabled(bool)
# Using python with a keccak256 library since sha3sum may not be available
python3 <<'EOF'
from Crypto.Hash import keccak
signature = "SetIsSubsidizedRelayingEnabled(bool)"
k = keccak.new(digest_bits=256)
k.update(signature.encode('utf-8'))
topic0 = k.hexdigest()
print(f"Calculated Topic0: 0x{topic0}")

# Compare with the one in the code
expected = "0x1df7b0a641c11c6a0b6c1d1dbd5ed0a9f2e5f9df58f9cb6d3a15d5c7c80d9f15"
print(f"Expected Topic0:   {expected}")
print(f"Match: {f'0x{topic0}' == expected}")
EOF

Repository: wormhole-foundation/native-token-transfers

Length of output: 204


🏁 Script executed:

# Install pycryptodome and verify Topic0 hash
pip install pycryptodome -q
python3 <<'EOF'
from Crypto.Hash import keccak
signature = "SetIsSubsidizedRelayingEnabled(bool)"
k = keccak.new(digest_bits=256)
k.update(signature.encode('utf-8'))
topic0 = k.hexdigest()
print(f"Calculated Topic0: 0x{topic0}")

# Compare with the one in the code
expected = "0x1df7b0a641c11c6a0b6c1d1dbd5ed0a9f2e5f9df58f9cb6d3a15d5c7c80d9f15"
print(f"Expected Topic0:   {expected}")
print(f"Match: {f'0x{topic0}' == expected}")
EOF

Repository: wormhole-foundation/native-token-transfers

Length of output: 270


Add getter and setter declarations to the interface AND fix the Topic0 hash.

The interface is missing isSubsidizedRelayingEnabled() getter and setIsSubsidizedRelayingEnabled(bool) setter declarations. These functions are implemented in WormholeTransceiverState.sol (lines 224-226, 294-297) but absent from the interface, breaking the pattern established by similar features like isWormholeRelayingEnabled/setIsWormholeRelayingEnabled (lines 91-95, 121-125).

More critically, the documented Topic0 hash 0x1df7b0a641c11c6a0b6c1d1dbd5ed0a9f2e5f9df58f9cb6d3a15d5c7c80d9f15 is incorrect. The correct Topic0 for SetIsSubsidizedRelayingEnabled(bool) is 0x137fed7331b9937fa0c97e8a96a905d05e2efa75614a2b3cb247231bc6570b05.

Proposed fix
     /// `@notice` Emitted when subsidized relaying is enabled or disabled.
     /// `@dev` Topic0
-    ///      0x1df7b0a641c11c6a0b6c1d1dbd5ed0a9f2e5f9df58f9cb6d3a15d5c7c80d9f15.
+    ///      0x137fed7331b9937fa0c97e8a96a905d05e2efa75614a2b3cb247231bc6570b05.
     /// `@param` isEnabled A boolean indicating whether subsidized relaying is enabled.
     event SetIsSubsidizedRelayingEnabled(bool isEnabled);
 
+    /// `@notice` Returns a boolean indicating whether subsidized relaying is enabled.
+    function isSubsidizedRelayingEnabled() external view returns (bool);
+
+    /// `@notice` Set whether subsidized relaying is enabled.
+    /// `@dev` This function is only callable by the `owner`.
+    /// `@param` isEnabled A boolean indicating whether subsidized relaying is enabled.
+    function setIsSubsidizedRelayingEnabled(bool isEnabled) external;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@evm/src/interfaces/IWormholeTransceiverState.sol` around lines 34 - 38, The
interface is missing the getter and setter declarations for subsidized relaying
and documents the wrong Topic0 hash; add function declarations
isSubsidizedRelayingEnabled() external view returns (bool) and
setIsSubsidizedRelayingEnabled(bool isEnabled) external to the interface
(matching the implementations in WormholeTransceiverState) and update the event
comment for SetIsSubsidizedRelayingEnabled(bool) to use the correct Topic0 hash
0x137fed7331b9937fa0c97e8a96a905d05e2efa75614a2b3cb247231bc6570b05.

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.

1 participant