Skip to content

Wormhole Solidity SDK Upgrade: v0.1.0 → v1.1.0#840

Open
dvgui wants to merge 2 commits intomainfrom
sol-sdk-upgrades
Open

Wormhole Solidity SDK Upgrade: v0.1.0 → v1.1.0#840
dvgui wants to merge 2 commits intomainfrom
sol-sdk-upgrades

Conversation

@dvgui
Copy link
Member

@dvgui dvgui commented Mar 12, 2026

Gas optimizations

Gas benchmarks — WormholeTransceiver.receiveMessage

Metric Old (SDK v0.1.0) SDK upgrade only + additional optimizations Total savings
Avg 115,292 92,984 90,342 -24,950 (-21.6%)
Median 108,961 92,304 89,274 -19,687 (-18.1%)
Max 205,031 184,592 180,403 -24,628 (-12.0%)

Deployment size: 12,757 → 10,959 bytes (-14.1%)

Infrastructure

  • Submodule updated to v1.1.0 (d80e0f6cd1)
  • remappings.txt created for wormhole-sdk/ paths
  • solc bumped from 0.8.19 to 0.8.24, via_ir changed from false to true (required by SDK's CoreBridgeLib)

Build config

  • foundry.toml — solc 0.8.24, via_ir = true
  • foundry.lock — updated SDK commit
  • remappings.txt — new file
  • lib/wormhole-solidity-sdk — submodule updated to v1.1.0

Source contracts (8 files)

  • IWormholeICoreBridge, toWormholeFormattoUniversalAddress, fromWormholeFormatfromUniversalAddress
  • BytesParsing API: all methods on bytes memory now have Mem suffix (e.g. asBytes32UncheckedasBytes32MemUnchecked)
  • checkLength signature changed from (bytes memory, uint) to (uint, uint) — now called as BytesParsing.checkLength(encoded.length, offset)
  • Import paths migrated from wormhole-solidity-sdk/ to wormhole-sdk/
  • Deployment scripts updated (DeployWormholeNttBase.sol, DeployWormholeNtt.s.sol, ParseNttConfig.sol)

Contract Changes

  • WormholeTransceiver._verifyMessage(): replaced wormhole.parseAndVerifyVM() with CoreBridgeLib.decodeAndVerifyVaaCd() (client-side VAA verification — avoids external call overhead, more efficient ECDSA recovery loop, no ABI decode of VM struct). Uses the calldata variant to avoid copying the entire VAA into memory.
  • receiveMessage calldata switch: changed bytes memorybytes calldata on both IWormholeTransceiver and WormholeTransceiver, avoiding the implicit calldata-to-memory copy of the full encoded VAA
  • Replay digest via VaaLib.calcVaaDoubleHashCd: replaced manual abi.encodePacked + double-keccak with the SDK's optimized calldata hash utility, which hashes the VAA body directly from calldata using keccak256SliceUnchecked + keccak256Word (scratch space, no memory allocation). Produces the same backward-compatible double-keccak digest as the old CoreBridge parseAndVerifyVM.
  • nttManagerMessageDigest inlined: eliminated the intermediate encodeNttManagerMessage allocation by hashing struct fields directly via abi.encodePacked(sourceChainId, m.id, m.sender, uint16(m.payload.length), m.payload) — produces identical digest
  • countSetBits constant-time popcount: replaced O(n) Kernighan loop with parallel bit counting (~6 ops, constant gas regardless of set bit count)
  • Cached transceiver index in _recordTransceiverAttestation: reads transceiverInfos[msg.sender].index once and reuses it for attestation check, bitmap set, and event emission — eliminates redundant warm SLOADs
  • Replay protection: kept existing hash-based isVAAConsumed guard. SequenceReplayProtectionLib was evaluated but not adopted — adding it alongside the hash guard (needed for upgrade safety) would increase gas costs rather than reduce them.
  • Governance.sol: same decodeAndVerifyVaaCd + VaaLib.calcVaaDoubleHashCd + calldata optimization; created ICoreBridgeGovernance interface extending ICoreBridge with governance-specific methods
  • SDK library consolidation: replaced NTT's ConfigMakers with SDK's CustomConsistencyLib (same encoding, more gas-efficient bit shifts vs abi.encodePacked). Replaced NTT's ICustomConsistencyLevel import with SDK's identical interface.

Test migration (11 test files + 1 fuzz file)

  • WormholeSimulatorWormholeOverride library (completely different API)
  • fetchPublishedMessages() + sign() + VaaLib.encode() pattern replaces old fetchSignedMessageFromLogs()
  • Emitter chain ID explicitly passed to _getSignedMessages() (matching old fetchSignedMessageFromLogs(log, chainId) behavior)
  • Replay protection tests updated to use vm.expectRevert() for TransferAlreadyCompleted (parameterized error)
  • VaaBody struct is now nested: { VaaEnvelope envelope, bytes payload } instead of flat fields

via_ir test fix

  • Fixed test_transfersOnForkedChains in NttManager.t.sol and NttManagerNoRateLimiting.t.sol. Root cause: known Foundry/Solidity via_ir optimizer bug where the Yul optimizer treats CHAINID as a pure/movable expression and may re-evaluate it after vm.chainId() has changed the value. Fix: store block.chainid in a state variable during setUp() (storage reads are not subject to CSE/rematerialization).

Summary by CodeRabbit

  • New Features

    • Added TypeScript v2.1.0 contract bindings for NttManager and WormholeTransceiver.
  • Improvements

    • Updated Wormhole SDK integration and unified address format handling across contracts and tests.
    • Upgraded Solidity toolchain to 0.8.24 with IR-enabled compilation.
    • Bumped on-chain contract versions to 2.1.0 and improved message verification, replay protection, and parsing performance.
  • Tests

    • Modernized test harnesses to use SDK override utilities and simplified VAA signing helpers.

@dvgui dvgui force-pushed the sol-sdk-upgrades branch from 08685bf to 3fa3e22 Compare March 12, 2026 18:51
@wormhole-foundation wormhole-foundation deleted a comment from coderabbitai bot Mar 12, 2026
@coderabbitai
Copy link

coderabbitai bot commented Mar 12, 2026

📝 Walkthrough

Walkthrough

This PR migrates imports from wormhole-solidity-sdk to wormhole-sdk, replaces IWormhole with ICoreBridge (and governance variant), switches address encoding from toWormholeFormat to toUniversalAddress, upgrades Solidity to 0.8.24 with via_ir enabled, refactors calldata-based VAA verification/replay protection, updates tests to use WormholeOverride, and adds v2.1.0 TypeScript contract bindings.

Changes

Cohort / File(s) Summary
SDK import & small fixes
evm/echidna/FuzzNttManager.sol, evm/remappings.txt
Adjusted imports/remappings to reference wormhole-sdk alongside existing wormhole-solidity-sdk remap; single-line import fixes.
Build config & submodule
evm/foundry.toml, evm/lib/wormhole-solidity-sdk
Bumped compiler to 0.8.24 and enabled via_ir; updated submodule pointer for wormhole-solidity-sdk.
CoreBridge / interface migration
evm/script/DeployWormholeNtt.s.sol, evm/script/helpers/DeployWormholeNttBase.sol, evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol, evm/src/wormhole/Governance.sol
Replaced IWormhole usage with ICoreBridge / ICoreBridgeGovernance, updated immutable types and constructor casts, and adjusted consistency configuration calls.
Transceiver verification & calldata refactor
evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol, evm/src/interfaces/IWormholeTransceiver.sol
Refactored VAA verification to calldata-based CoreBridgeLib.decodeAndVerifyVaaCd/VaaLib.calcVaaDoubleHashCd, changed receiveMessage and _verifyMessage to calldata, updated replay-protection and peer checks, and switched to universal address encoding.
NttManager & ManagerBase logic changes
evm/src/NttManager/NttManager.sol, evm/src/NttManager/ManagerBase.sol
Updated imports to wormhole-sdk; bumped NTT_MANAGER_VERSION to 2.1.0; switched address encoding helpers to universal forms; optimized transceiver attestation lookup by caching index and emitting event after marking attestation; minor instruction-length arg change.
Parsing / Utils adjustments
evm/script/helpers/ParseNttConfig.sol, evm/src/libraries/TransceiverStructs.sol, evm/src/libraries/ConfigMakers.sol
Removed local address-conversion helpers and imported toUniversalAddress/fromUniversalAddress from SDK; migrated bytes parsing to *MemUnchecked variants and centralized length checks.
Helpers & libraries
evm/src/libraries/TransceiverHelpers.sol
Replaced loop-based popcount with a parallel bit-counting implementation and simplified return signature.
Tests: Wormhole override / signing helper
evm/test/* (many files), evm/test/libraries/*, evm/test/wormhole/Governance.t.sol
Replaced WormholeSimulator + guardian signing with WormholeOverride/AdvancedWormholeOverride + VaaLib.encode; updated tests to use ICoreBridge/ICoreBridgeGovernance, switched to toUniversalAddress, added _getSignedMessages helper in multiple tests, simplified duplicate-send revert assertions, and updated consistency-encoding calls.
Mocks & test helpers
evm/test/mocks/MockNttManagerAdditionalPayload.sol, evm/test/libraries/TransceiverHelpers.sol, evm/test/libraries/NttManagerHelpers.sol
Imported wormhole-sdk/Utils.sol and changed mocked/test helper address encodings to universal representation.
TypeScript bindings (v2.1.0)
evm/ts/src/ethers-contracts/2_1_0/*, evm/ts/src/bindings.ts, evm/ts/src/ethers-contracts/index.ts
Added autogenerated strongly-typed ethers.js contract wrappers and factories for v2.1.0 (NttManager, WormholeTransceiver), new common typing utilities, and registered "2.1.0" in ABI versions.

Sequence Diagram(s)

sequenceDiagram
    participant Sender as Sender / Relayer
    participant Transceiver as WormholeTransceiver
    participant Core as ICoreBridge
    participant NttMgr as NttManager

    Sender->>Core: publish signed VAA (encoded bytes)
    Core->>Transceiver: deliver encodedMessage (bytes calldata)
    Transceiver->>Core: CoreBridgeLib.decodeAndVerifyVaaCd(encodedMessage)
    Core-->>Transceiver: (emitterChainId, emitterAddress, payload)
    Transceiver->>Transceiver: validate peer (getWormholePeer(emitterChainId) == emitterAddress)
    Transceiver->>Transceiver: vaaDigest = VaaLib.calcVaaDoubleHashCd(encodedMessage)
    Transceiver->>Transceiver: check/set VAA consumed (isVAAConsumed / _setVAAConsumed)
    Transceiver->>NttMgr: call attestation/receive with decoded payload
    NttMgr-->>Transceiver: acknowledge
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • evgeniko
  • fergarrui

Poem

🐰 a little rabbit in the dev tree

I hopped through imports, light and spry,
Swapped formats, let universals fly,
Calldata sings, verifications flow,
Version two-point-one begins to glow,
Cheers to bridges stitched by rabbit's eye. 🥕

🚥 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 PR title accurately summarizes the primary objective: upgrading the Wormhole Solidity SDK from v0.1.0 to v1.1.0, which is the central theme of this comprehensive changeset.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sol-sdk-upgrades

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: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
evm/src/libraries/TransceiverStructs.sol (1)

58-64: ⚠️ Potential issue | 🟡 Minor

Keep nttManagerMessageDigest aligned with the wire-format length bound.

This helper now hashes uint16(m.payload.length) without preserving the type(uint16).max guard that encodeNttManagerMessage() still enforces below. For oversized payloads, the digest helper will now succeed on a message that the encoder rejects, so the two APIs no longer agree on what constitutes a valid NttManagerMessage.

💡 Proposed fix
 function nttManagerMessageDigest(
     uint16 sourceChainId,
     NttManagerMessage memory m
 ) public pure returns (bytes32) {
+    if (m.payload.length > type(uint16).max) {
+        revert PayloadTooLong(m.payload.length);
+    }
     return keccak256(
         abi.encodePacked(sourceChainId, m.id, m.sender, uint16(m.payload.length), m.payload)
     );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@evm/src/libraries/TransceiverStructs.sol` around lines 58 - 64, The digest
helper nttManagerMessageDigest is inconsistent with encodeNttManagerMessage
because it hashes uint16(m.payload.length) without the encoder's
type(uint16).max clamp; update nttManagerMessageDigest to compute the payload
length exactly as the encoder does (i.e., clamp the length to type(uint16).max
before casting) so both functions use the same wire-format length bound; locate
nttManagerMessageDigest and mirror the encoder's min/clip logic when producing
the uint16 length value for the keccak256 input.
🧹 Nitpick comments (2)
evm/test/IntegrationStandalone.t.sol (1)

670-680: Centralize _getSignedMessages.

This helper is now copied into multiple integration tests and overlaps with IntegrationHelpers._getWormholeMessage. Pulling it into one shared utility will make future harness updates much easier to keep in sync.

🤖 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 670 - 680, Duplicate
helper _getSignedMessages should be removed and callers should use the
centralized IntegrationHelpers._getWormholeMessage instead; update
IntegrationStandalone.t.sol to delete the local _getSignedMessages function,
import or inherit IntegrationHelpers where needed, and replace all calls to
_getSignedMessages(logs, emitterChainId) with
IntegrationHelpers._getWormholeMessage(logs, emitterChainId) (or the instance
method name as defined in IntegrationHelpers) ensuring types/visibility match
and any WormholeOverride/VaaLib dependencies are resolved via the shared helper.
evm/test/IntegrationCustomConsistency.t.sol (1)

304-315: Extract _getSignedMessages into a shared test helper.

The same helper now exists here and in evm/test/Upgrades.t.sol, so any future change to the WormholeOverride/VAA signing path has to be kept in sync in multiple suites.

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

In `@evm/test/IntegrationCustomConsistency.t.sol` around lines 304 - 315,
Duplicate test helper _getSignedMessages (which calls
WormholeOverride.fetchPublishedMessages, sets
PublishedMessage.envelope.emitterChainId, calls WormholeOverride.sign and
VaaLib.encode) should be extracted to a single shared test helper; create a
shared library/contract (e.g., TestWormholeHelpers.getSignedMessages) that
accepts Vm.Log[] and uint16 emitterChainId, implements the same logic, make it
internal/view or public/view as needed for tests, then replace the duplicate
implementations in both IntegrationCustomConsistency.t.sol and Upgrades.t.sol
with calls to the shared helper so future changes to WormholeOverride/VAA
signing are centralized.
🤖 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/echidna/FuzzNttManager.sol`:
- Line 17: The import in FuzzNttManager.sol now references
"wormhole-sdk/Utils.sol" but the wormhole-solidity-sdk submodule is not
initialized so remapped path (evm/remappings.txt mapping wormhole-sdk/ ->
lib/wormhole-solidity-sdk/src/) will fail; either ensure the submodule is
initialized in CI/setup by running git submodule update --init --recursive
before compilation, or revert the import to the previously working path (e.g.,
the original wormhole-solidity-sdk import) so builds work without submodule
changes; update CI/build scripts or developer setup docs accordingly and adjust
FuzzNttManager.sol import to match whichever approach you choose.

In `@evm/lib/wormhole-solidity-sdk`:
- Line 1: The submodule at evm/lib/wormhole-solidity-sdk is pinned to the wrong
commit (d80e0f6cd16f281f3f9df435e98e823b4212b2ad) which doesn't match the
intended v1.1.0 tag (33d4895fb629aabf12e0f60740367bd03e702bd1); update the
submodule pointer so the gitlink points to the v1.1.0 tag commit (33d4895f...),
or if a different commit is intended, replace the pinned SHA with that correct
commit and update .gitmodules/commit reference and the submodule state in the
repo so the submodule resolves to the declared commit. Ensure the commit SHA in
the PR matches the upstream tag or include a clear justification in the PR
description if you intentionally pin to a non-tagged commit.

In `@evm/test/IntegrationAdditionalTransfer.t.sol`:
- Around line 226-228: The test currently uses a bare vm.expectRevert() before
calling wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) which will pass
for any revert; update this to assert the specific duplicate-delivery revert by
swapping vm.expectRevert() for vm.expectRevert(<expectedRevertBytesOrString>)
where <expectedRevertBytesOrString> is the exact revert message or encoded error
selector thrown by receiveMessage on duplicate deliveries (use the contract's
DuplicateDelivery error name or revert string encoded with
abi.encodeWithSignature/abi.encodePacked as appropriate) so the test only
succeeds if the second receiveMessage call fails for the intended
duplicate-delivery reason.

In `@evm/test/IntegrationStandalone.t.sol`:
- Around line 222-224: Replace the broad vm.expectRevert() with a revert
expectation that matches the replay error selector, e.g.
vm.expectRevert(abi.encodeWithSelector(<REPLAY_ERROR>.selector)); for the call
to wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) (and do the same for
the second occurrence around lines 344-346). Use the actual replay error
type/identifier declared in the transceiver contract (replace <REPLAY_ERROR>
with that error name) so the test asserts the revert is specifically from the
replay-path.

In `@evm/test/IntegrationWithoutRateLimiting.t.sol`:
- Around line 203-205: Replace the generic vm.expectRevert() checks with
assertions that check for the specific replay-protection revert so the test
guarantees the failure is due to duplicate delivery; for the calls to
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) (and the same pattern at
the other location), use vm.expectRevert(...) with the contract's replay error
selector or encoded revert reason (e.g.,
vm.expectRevert(abi.encodeWithSelector(ReplayAlreadyProcessed.selector)) or
vm.expectRevert(bytes("AlreadyProcessed"))), then call
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) so the test explicitly
validates the replay-protection error.

In `@evm/test/Upgrades.t.sol`:
- Around line 513-514: The test currently uses a generic vm.expectRevert()
before calling wormholeTransceiverChain2.receiveMessage(encodedVMs[0]), which
can pass for any failure; replace the generic expectRevert with a specific
revert assertion that matches the contract's replay-protection error (either the
exact revert string or the error selector/ABI-encoded bytes for the replay
error) so the test proves the second delivery hit replay protection; update the
call that precedes wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) to
use vm.expectRevert(...) with the replay-specific payload (e.g., the contract
error selector or revert message) referencing the replay protection error
defined in the transceiver contract.

---

Outside diff comments:
In `@evm/src/libraries/TransceiverStructs.sol`:
- Around line 58-64: The digest helper nttManagerMessageDigest is inconsistent
with encodeNttManagerMessage because it hashes uint16(m.payload.length) without
the encoder's type(uint16).max clamp; update nttManagerMessageDigest to compute
the payload length exactly as the encoder does (i.e., clamp the length to
type(uint16).max before casting) so both functions use the same wire-format
length bound; locate nttManagerMessageDigest and mirror the encoder's min/clip
logic when producing the uint16 length value for the keccak256 input.

---

Nitpick comments:
In `@evm/test/IntegrationCustomConsistency.t.sol`:
- Around line 304-315: Duplicate test helper _getSignedMessages (which calls
WormholeOverride.fetchPublishedMessages, sets
PublishedMessage.envelope.emitterChainId, calls WormholeOverride.sign and
VaaLib.encode) should be extracted to a single shared test helper; create a
shared library/contract (e.g., TestWormholeHelpers.getSignedMessages) that
accepts Vm.Log[] and uint16 emitterChainId, implements the same logic, make it
internal/view or public/view as needed for tests, then replace the duplicate
implementations in both IntegrationCustomConsistency.t.sol and Upgrades.t.sol
with calls to the shared helper so future changes to WormholeOverride/VAA
signing are centralized.

In `@evm/test/IntegrationStandalone.t.sol`:
- Around line 670-680: Duplicate helper _getSignedMessages should be removed and
callers should use the centralized IntegrationHelpers._getWormholeMessage
instead; update IntegrationStandalone.t.sol to delete the local
_getSignedMessages function, import or inherit IntegrationHelpers where needed,
and replace all calls to _getSignedMessages(logs, emitterChainId) with
IntegrationHelpers._getWormholeMessage(logs, emitterChainId) (or the instance
method name as defined in IntegrationHelpers) ensuring types/visibility match
and any WormholeOverride/VaaLib dependencies are resolved via the shared helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 86b96402-bfb2-47df-a267-a7951ddd1f26

📥 Commits

Reviewing files that changed from the base of the PR and between 9607ca7 and 3fa3e22.

⛔ Files ignored due to path filters (1)
  • evm/foundry.lock is excluded by !**/*.lock
📒 Files selected for processing (40)
  • evm/echidna/FuzzNttManager.sol
  • evm/foundry.toml
  • evm/lib/wormhole-solidity-sdk
  • evm/remappings.txt
  • evm/script/DeployWormholeNtt.s.sol
  • evm/script/helpers/DeployWormholeNttBase.sol
  • evm/script/helpers/ParseNttConfig.sol
  • evm/src/NttManager/ManagerBase.sol
  • evm/src/NttManager/NttManager.sol
  • evm/src/Transceiver/Transceiver.sol
  • evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol
  • evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol
  • evm/src/interfaces/IWormholeTransceiver.sol
  • evm/src/libraries/ConfigMakers.sol
  • evm/src/libraries/TransceiverHelpers.sol
  • evm/src/libraries/TransceiverStructs.sol
  • evm/src/wormhole/Governance.sol
  • evm/test/DeploymentScripts.t.sol
  • evm/test/IntegrationAdditionalTransfer.t.sol
  • evm/test/IntegrationCustomConsistency.t.sol
  • evm/test/IntegrationStandalone.t.sol
  • evm/test/IntegrationWithoutRateLimiting.t.sol
  • evm/test/NttManager.t.sol
  • evm/test/NttManagerNoRateLimiting.t.sol
  • evm/test/RateLimit.t.sol
  • evm/test/Upgrades.t.sol
  • evm/test/libraries/IntegrationHelpers.sol
  • evm/test/libraries/NttManagerHelpers.sol
  • evm/test/libraries/TransceiverHelpers.sol
  • evm/test/mocks/MockNttManagerAdditionalPayload.sol
  • evm/test/wormhole/Governance.t.sol
  • evm/ts/src/bindings.ts
  • evm/ts/src/ethers-contracts/2_1_0/NttManager.ts
  • evm/ts/src/ethers-contracts/2_1_0/WormholeTransceiver.ts
  • evm/ts/src/ethers-contracts/2_1_0/common.ts
  • evm/ts/src/ethers-contracts/2_1_0/factories/NttManager__factory.ts
  • evm/ts/src/ethers-contracts/2_1_0/factories/WormholeTransceiver__factory.ts
  • evm/ts/src/ethers-contracts/2_1_0/factories/index.ts
  • evm/ts/src/ethers-contracts/2_1_0/index.ts
  • evm/ts/src/ethers-contracts/index.ts

import "solidity-bytes-utils/BytesLib.sol";
import "../test/mocks/DummyTransceiver.sol";
import "wormhole-solidity-sdk/Utils.sol";
import "wormhole-sdk/Utils.sol";
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:

#!/bin/bash
# Verify Utils.sol exists in the SDK submodule
fd -t f "Utils.sol" lib/wormhole-solidity-sdk/src/ 2>/dev/null

# Check what symbols are exported from Utils.sol
if [ -f "lib/wormhole-solidity-sdk/src/Utils.sol" ]; then
  echo "=== Utils.sol contents ==="
  head -50 lib/wormhole-solidity-sdk/src/Utils.sol
fi

Repository: wormhole-foundation/native-token-transfers

Length of output: 68


🏁 Script executed:

#!/bin/bash
# Check lib directory structure
ls -la lib/ 2>/dev/null || echo "lib directory not found or empty"

# Check remappings.txt for relevant entries
echo "=== Checking remappings.txt ==="
cat remappings.txt 2>/dev/null || echo "remappings.txt not found"

# Search for Utils.sol anywhere in the repo
echo "=== Searching for Utils.sol ==="
fd -t f "Utils.sol" . 2>/dev/null | head -20

Repository: wormhole-foundation/native-token-transfers

Length of output: 238


🏁 Script executed:

#!/bin/bash
# Find and show FuzzNttManager.sol
echo "=== FuzzNttManager.sol location and content ==="
find . -name "FuzzNttManager.sol" -type f -exec head -25 {} \;

# Check for remapping configurations in config files
echo -e "\n=== Checking config files for remappings ==="
ls -la | grep -E "foundry|hardhat|truffle|forge"

if [ -f "foundry.toml" ]; then
  echo "=== foundry.toml ==="
  cat foundry.toml
fi

if [ -f "hardhat.config.ts" ] || [ -f "hardhat.config.js" ]; then
  echo "=== hardhat config ==="
  cat hardhat.config.* 2>/dev/null | head -50
fi

Repository: wormhole-foundation/native-token-transfers

Length of output: 1240


🏁 Script executed:

#!/bin/bash
# Check for any config files that might contain remappings
echo "=== All config files in repo ==="
find . -maxdepth 2 -name "*.toml" -o -name "*.json" | grep -E "(forge|foundry|hardhat|package|tsconfig)" | head -20

# Check evm subdirectory specifically
echo -e "\n=== evm directory config files ==="
ls -la evm/ | grep -E "toml|json"

# Check package.json if it exists
if [ -f "package.json" ]; then
  echo -e "\n=== package.json (first 80 lines) ==="
  head -80 package.json
fi

if [ -f "evm/package.json" ]; then
  echo -e "\n=== evm/package.json ==="
  cat evm/package.json
fi

# Check for forge.toml
if [ -f "evm/forge.toml" ]; then
  echo -e "\n=== evm/forge.toml ==="
  cat evm/forge.toml
fi

Repository: wormhole-foundation/native-token-transfers

Length of output: 2426


🏁 Script executed:

#!/bin/bash
echo "=== evm/foundry.toml ==="
cat evm/foundry.toml

Repository: wormhole-foundation/native-token-transfers

Length of output: 1948


🏁 Script executed:

#!/bin/bash
# Check evm package.json
if [ -f "evm/package.json" ]; then
  echo "=== evm/package.json ==="
  cat evm/package.json
else
  echo "evm/package.json not found"
fi

# Check what's in evm/lib
echo -e "\n=== evm/lib directory ==="
ls -la evm/lib/ 2>/dev/null || echo "evm/lib directory not found"

# Search for any references to wormhole-sdk in the codebase
echo -e "\n=== References to wormhole-sdk ==="
grep -r "wormhole-sdk" evm/ --include="*.sol" --include="*.json" --include="*.toml" 2>/dev/null | head -20

Repository: wormhole-foundation/native-token-transfers

Length of output: 2602


🏁 Script executed:

#!/bin/bash
# Check for wormhole-sdk as a symlink or directory
echo "=== Checking for wormhole-sdk ==="
ls -la evm/lib/ | grep wormhole

# Check if wormhole-sdk is referenced elsewhere
echo -e "\n=== Check git status for lib changes ==="
cd evm && git status lib/ 2>/dev/null | head -30

# Check what Utils.sol exports in the actual library
echo -e "\n=== Contents of wormhole-solidity-sdk/Utils.sol ==="
head -30 evm/lib/wormhole-solidity-sdk/Utils.sol 2>/dev/null || echo "File not found"

# Check if the imports are using the correct path in other files
echo -e "\n=== Sample working import from another file ==="
grep -A2 "import.*Utils" evm/src/NttManager/ManagerBase.sol | head -10

Repository: wormhole-foundation/native-token-transfers

Length of output: 500


🏁 Script executed:

#!/bin/bash
# List contents of wormhole-solidity-sdk
echo "=== Contents of evm/lib/wormhole-solidity-sdk/ ==="
find evm/lib/wormhole-solidity-sdk -type f | head -20

# Check if it's actually a symlink
echo -e "\n=== Check symlinks in evm/lib ==="
ls -la evm/lib/

# Look for wormhole-sdk anywhere
echo -e "\n=== Search for wormhole-sdk anywhere ==="
find evm -name "wormhole-sdk" -o -name "wormhole-sdk*" 2>/dev/null

# Check actual location of Utils from test
echo -e "\n=== Utils.sol in test ==="
ls -la evm/test/libraries/ 2>/dev/null | grep Utils

Repository: wormhole-foundation/native-token-transfers

Length of output: 682


🏁 Script executed:

#!/bin/bash
# Verify wormhole-solidity-sdk is truly empty
echo "=== Checking if wormhole-solidity-sdk is a git submodule ==="
cd evm && git config --file .gitmodules --get-regexp path | grep wormhole
cd -

# Check the git history to see what changed
echo -e "\n=== Recent git log for FuzzNttManager.sol ==="
cd evm && git log --oneline -n 10 echidna/FuzzNttManager.sol 2>/dev/null || echo "No git history"
cd -

# Check if there's a remapping or import alias that might help
echo -e "\n=== Check for any remapping attempts in Solidity files ==="
grep -r "remapping\|import.*as" evm/src evm/echidna --include="*.sol" 2>/dev/null | head -5

# Verify: does wormhole-sdk exist as an npm package that would be installed?
echo -e "\n=== Check root package.json for wormhole-sdk ==="
grep -i "wormhole-sdk" package.json 2>/dev/null || echo "Not found in root package.json"

Repository: wormhole-foundation/native-token-transfers

Length of output: 879


🏁 Script executed:

#!/bin/bash
# Check .gitmodules
echo "=== .gitmodules content ==="
cat .gitmodules 2>/dev/null || cat evm/.gitmodules 2>/dev/null || echo "No .gitmodules found"

# Check git submodule status
echo -e "\n=== Git submodule status ==="
git config --file .gitmodules --get-regexp . 2>/dev/null || echo "No submodules configured"

# Look at the actual commit that changed the imports
echo -e "\n=== Show the commit that changed FuzzNttManager.sol ==="
cd evm && git show --stat 3fa3e22 2>/dev/null | head -50
cd -

Repository: wormhole-foundation/native-token-transfers

Length of output: 4278


🏁 Script executed:

#!/bin/bash
# Check the full diff of the commit to understand the remapping changes
echo "=== Full diff of the upgrade commit (first 200 lines) ==="
cd evm && git show 3fa3e22 -- foundry.toml 2>/dev/null | head -100
cd -

# Also check if remappings were added in this commit
echo -e "\n=== Check if remappings.txt was added ==="
cd evm && git show 3fa3e22 -- remappings.txt 2>/dev/null || echo "No remappings.txt in this commit"
cd -

# Check the actual current state - look at full diff for FuzzNttManager.sol
echo -e "\n=== Diff for FuzzNttManager.sol import change ==="
cd evm && git show 3fa3e22 -- echidna/FuzzNttManager.sol 2>/dev/null | grep -A5 -B5 "wormhole"
cd -

Repository: wormhole-foundation/native-token-transfers

Length of output: 5327


🏁 Script executed:

#!/bin/bash
# Verify remappings.txt actually exists in current state
echo "=== Check if evm/remappings.txt exists ==="
ls -la evm/remappings.txt 2>/dev/null || echo "File does not exist in current state"

# Check git status to see if it's tracked
echo -e "\n=== Git status of remappings.txt ==="
cd evm && git ls-files remappings.txt 2>/dev/null && echo "File is tracked" || echo "File not tracked"
cd -

# Try to view the file directly
echo -e "\n=== Content of evm/remappings.txt ==="
cat evm/remappings.txt 2>/dev/null || echo "Cannot read file"

# Check git for current tracked files in evm root
echo -e "\n=== Tracked files in evm root ==="
cd evm && git ls-files | grep "^[^/]*$" | head -20
cd -

Repository: wormhole-foundation/native-token-transfers

Length of output: 558


🏁 Script executed:

#!/bin/bash
# Verify Utils.sol exists at the remapped location
echo "=== Check if Utils.sol exists at remapped location ==="
ls -la evm/lib/wormhole-solidity-sdk/src/Utils.sol 2>/dev/null || echo "File not found"

# List what's in the src directory
echo -e "\n=== Contents of evm/lib/wormhole-solidity-sdk/src/ ==="
ls -la evm/lib/wormhole-solidity-sdk/src/ 2>/dev/null | head -20

# Verify the remapping resolves correctly
echo -e "\n=== Verify remapping resolution ==="
# Check if the path referenced in remappings exists
stat evm/lib/wormhole-solidity-sdk/src/ 2>/dev/null && echo "Remapped directory exists" || echo "Remapped directory not found"

Repository: wormhole-foundation/native-token-transfers

Length of output: 277


🏁 Script executed:

#!/bin/bash
# Check git submodule status
echo "=== Git submodule status ==="
cd evm && git submodule status 2>/dev/null
cd -

# Check if submodule initialization is needed
echo -e "\n=== Submodule update status ==="
cd evm && git config --file .gitmodules --get submodule.lib/wormhole-solidity-sdk.path
cd -

# Try to understand what's in the submodule directory
echo -e "\n=== Detailed check of wormhole-solidity-sdk ==="
find evm/lib/wormhole-solidity-sdk -type f 2>/dev/null | wc -l
echo "Number of files in wormhole-solidity-sdk:"
find evm/lib/wormhole-solidity-sdk -type d 2>/dev/null | wc -l
echo "Number of directories in wormhole-solidity-sdk (empty dirs count as 0 files)"

Repository: wormhole-foundation/native-token-transfers

Length of output: 617


Critical: Submodule not initialized—remappings will fail to resolve.

The import path change to wormhole-sdk/Utils.sol depends on remappings configured in evm/remappings.txt, which maps wormhole-sdk/ to lib/wormhole-solidity-sdk/src/. However, the wormhole-solidity-sdk git submodule is not initialized in the current state (indicated by the - prefix in submodule status), meaning the src/ directory does not exist. This will cause compilation failures when Foundry/Forge attempts to resolve the import.

The remappings configuration itself is correct, but this PR requires either:

  1. Ensuring git submodule update --init --recursive runs before compilation (CI/build scripts, or developer setup instructions)
  2. Or, if submodules are intentionally avoided, updating imports to use the old wormhole-solidity-sdk/ path which is already configured
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@evm/echidna/FuzzNttManager.sol` at line 17, The import in FuzzNttManager.sol
now references "wormhole-sdk/Utils.sol" but the wormhole-solidity-sdk submodule
is not initialized so remapped path (evm/remappings.txt mapping wormhole-sdk/ ->
lib/wormhole-solidity-sdk/src/) will fail; either ensure the submodule is
initialized in CI/setup by running git submodule update --init --recursive
before compilation, or revert the import to the previously working path (e.g.,
the original wormhole-solidity-sdk import) so builds work without submodule
changes; update CI/build scripts or developer setup docs accordingly and adjust
FuzzNttManager.sol import to match whichever approach you choose.

@@ -1 +1 @@
Subproject commit b9e129e65d34827d92fceeed8c87d3ecdfc801d0
Subproject commit d80e0f6cd16f281f3f9df435e98e823b4212b2ad
Copy link

@coderabbitai coderabbitai bot Mar 12, 2026

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:

#!/bin/bash
set -euo pipefail

PINNED_SHA="d80e0f6cd16f281f3f9df435e98e823b4212b2ad"

echo "== gitlink in current tree =="
git ls-tree HEAD evm/lib/wormhole-solidity-sdk

echo
echo "== submodule entries from .gitmodules =="
git config -f .gitmodules --get-regexp '^submodule\..*\.(path|url)$'

SUBMODULE_URL="$(git config -f .gitmodules --get-regexp '^submodule\..*\.url$' | awk 'NR==1 {print $2}')"

echo
echo "== refs on upstream remote containing the pinned SHA =="
git ls-remote --tags --heads "$SUBMODULE_URL" | rg "$PINNED_SHA|refs/tags|refs/heads"

Repository: wormhole-foundation/native-token-transfers

Length of output: 4420


Pinned SHA does not match the v1.1.0 release tag.

The submodule is pinned to commit d80e0f6cd16f281f3f9df435e98e823b4212b2ad, which does not correspond to the v1.1.0 tag (33d4895fb629aabf12e0f60740367bd03e702bd1) mentioned in the PR objective. The pinned SHA also does not appear in any upstream tag or branch on the Wormhole Solidity SDK repository. Please either update the pinned commit to match the intended v1.1.0 release tag or clarify which commit should be used.

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

In `@evm/lib/wormhole-solidity-sdk` at line 1, The submodule at
evm/lib/wormhole-solidity-sdk is pinned to the wrong commit
(d80e0f6cd16f281f3f9df435e98e823b4212b2ad) which doesn't match the intended
v1.1.0 tag (33d4895fb629aabf12e0f60740367bd03e702bd1); update the submodule
pointer so the gitlink points to the v1.1.0 tag commit (33d4895f...), or if a
different commit is intended, replace the pinned SHA with that correct commit
and update .gitmodules/commit reference and the submodule state in the repo so
the submodule resolves to the declared commit. Ensure the commit SHA in the PR
matches the upstream tag or include a clear justification in the PR description
if you intentionally pin to a non-tagged commit.

Copy link
Member Author

Choose a reason for hiding this comment

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

The diff from that commit is only npm publishing scripts. Not relevant.

Copy link

Choose a reason for hiding this comment

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

@dvgui, understood — if the only delta between the pinned commit (d80e0f6cd1) and the v1.1.0 tag is npm publishing scripts, then the Solidity/EVM content is effectively identical to v1.1.0 and the pin is fine. Thanks for the clarification!


✏️ Learnings added
Learnt from: dvgui
Repo: wormhole-foundation/native-token-transfers PR: 840
File: evm/lib/wormhole-solidity-sdk:1-1
Timestamp: 2026-03-12T20:23:23.293Z
Learning: In the wormhole-foundation/native-token-transfers repo, the evm/lib/wormhole-solidity-sdk submodule is intentionally pinned to a commit slightly ahead of the v1.1.0 tag (d80e0f6cd16f281f3f9df435e98e823b4212b2ad vs tag 33d4895fb629aabf12e0f60740367bd03e702bd1). The difference between those two commits is only npm publishing scripts, which are not relevant to EVM/Solidity usage. The Solidity content is effectively equivalent to v1.1.0.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

Comment on lines 226 to 228
// Can't resubmit the same message twice
(IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]);
vm.expectRevert(
abi.encodeWithSelector(
IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep this replay check explicit.

A bare vm.expectRevert() will still pass if the second receive fails for the wrong reason, which weakens the duplicate-delivery assertion.

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

In `@evm/test/IntegrationAdditionalTransfer.t.sol` around lines 226 - 228, The
test currently uses a bare vm.expectRevert() before calling
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) which will pass for any
revert; update this to assert the specific duplicate-delivery revert by swapping
vm.expectRevert() for vm.expectRevert(<expectedRevertBytesOrString>) where
<expectedRevertBytesOrString> is the exact revert message or encoded error
selector thrown by receiveMessage on duplicate deliveries (use the contract's
DuplicateDelivery error name or revert string encoded with
abi.encodeWithSignature/abi.encodePacked as appropriate) so the test only
succeeds if the second receiveMessage call fails for the intended
duplicate-delivery reason.

Comment on lines 222 to 224
// Can't resubmit the same message twice
(IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]);
vm.expectRevert(
abi.encodeWithSelector(
IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep these replay checks explicit.

vm.expectRevert() will also pass on unrelated failures, so these assertions no longer prove the second delivery is rejected by the replay path. Please match at least the selector here.

Also applies to: 344-346

🤖 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 222 - 224, Replace the
broad vm.expectRevert() with a revert expectation that matches the replay error
selector, e.g. vm.expectRevert(abi.encodeWithSelector(<REPLAY_ERROR>.selector));
for the call to wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) (and do
the same for the second occurrence around lines 344-346). Use the actual replay
error type/identifier declared in the transceiver contract (replace
<REPLAY_ERROR> with that error name) so the test asserts the revert is
specifically from the replay-path.

Comment on lines 203 to 205
// Can't resubmit the same message twice
(IWormhole.VM memory wormholeVM,,) = wormhole.parseAndVerifyVM(encodedVMs[0]);
vm.expectRevert(
abi.encodeWithSelector(
IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep these replay checks explicit.

vm.expectRevert() will pass on any revert, so these assertions no longer guarantee the second delivery is being rejected by replay protection specifically.

Also applies to: 377-379

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

In `@evm/test/IntegrationWithoutRateLimiting.t.sol` around lines 203 - 205,
Replace the generic vm.expectRevert() checks with assertions that check for the
specific replay-protection revert so the test guarantees the failure is due to
duplicate delivery; for the calls to
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) (and the same pattern at
the other location), use vm.expectRevert(...) with the contract's replay error
selector or encoded revert reason (e.g.,
vm.expectRevert(abi.encodeWithSelector(ReplayAlreadyProcessed.selector)) or
vm.expectRevert(bytes("AlreadyProcessed"))), then call
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) so the test explicitly
validates the replay-protection error.

Comment on lines +513 to 514
vm.expectRevert();
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Keep the replay assertion specific.

vm.expectRevert() now passes on any failure path, including bad VAA verification or fork mismatches, so this test no longer proves that the second delivery hit replay protection.

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

In `@evm/test/Upgrades.t.sol` around lines 513 - 514, The test currently uses a
generic vm.expectRevert() before calling
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]), which can pass for any
failure; replace the generic expectRevert with a specific revert assertion that
matches the contract's replay-protection error (either the exact revert string
or the error selector/ABI-encoded bytes for the replay error) so the test proves
the second delivery hit replay protection; update the call that precedes
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) to use
vm.expectRevert(...) with the replay-specific payload (e.g., the contract error
selector or revert message) referencing the replay protection error defined in
the transceiver contract.

@dvgui
Copy link
Member Author

dvgui commented Mar 16, 2026

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)

🧹 Nitpick comments (2)

🤖 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/echidna/FuzzNttManager.sol`:
- Line 17: The import in FuzzNttManager.sol now references
"wormhole-sdk/Utils.sol" but the wormhole-solidity-sdk submodule is not
initialized so remapped path (evm/remappings.txt mapping wormhole-sdk/ ->
lib/wormhole-solidity-sdk/src/) will fail; either ensure the submodule is
initialized in CI/setup by running git submodule update --init --recursive
before compilation, or revert the import to the previously working path (e.g.,
the original wormhole-solidity-sdk import) so builds work without submodule
changes; update CI/build scripts or developer setup docs accordingly and adjust
FuzzNttManager.sol import to match whichever approach you choose.

In `@evm/lib/wormhole-solidity-sdk`:
- Line 1: The submodule at evm/lib/wormhole-solidity-sdk is pinned to the wrong
commit (d80e0f6cd16f281f3f9df435e98e823b4212b2ad) which doesn't match the
intended v1.1.0 tag (33d4895fb629aabf12e0f60740367bd03e702bd1); update the
submodule pointer so the gitlink points to the v1.1.0 tag commit (33d4895f...),
or if a different commit is intended, replace the pinned SHA with that correct
commit and update .gitmodules/commit reference and the submodule state in the
repo so the submodule resolves to the declared commit. Ensure the commit SHA in
the PR matches the upstream tag or include a clear justification in the PR
description if you intentionally pin to a non-tagged commit.

In `@evm/test/IntegrationAdditionalTransfer.t.sol`:
- Around line 226-228: The test currently uses a bare vm.expectRevert() before
calling wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) which will pass
for any revert; update this to assert the specific duplicate-delivery revert by
swapping vm.expectRevert() for vm.expectRevert(<expectedRevertBytesOrString>)
where <expectedRevertBytesOrString> is the exact revert message or encoded error
selector thrown by receiveMessage on duplicate deliveries (use the contract's
DuplicateDelivery error name or revert string encoded with
abi.encodeWithSignature/abi.encodePacked as appropriate) so the test only
succeeds if the second receiveMessage call fails for the intended
duplicate-delivery reason.

In `@evm/test/IntegrationStandalone.t.sol`:
- Around line 222-224: Replace the broad vm.expectRevert() with a revert
expectation that matches the replay error selector, e.g.
vm.expectRevert(abi.encodeWithSelector(<REPLAY_ERROR>.selector)); for the call
to wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) (and do the same for
the second occurrence around lines 344-346). Use the actual replay error
type/identifier declared in the transceiver contract (replace <REPLAY_ERROR>
with that error name) so the test asserts the revert is specifically from the
replay-path.

In `@evm/test/IntegrationWithoutRateLimiting.t.sol`:
- Around line 203-205: Replace the generic vm.expectRevert() checks with
assertions that check for the specific replay-protection revert so the test
guarantees the failure is due to duplicate delivery; for the calls to
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) (and the same pattern at
the other location), use vm.expectRevert(...) with the contract's replay error
selector or encoded revert reason (e.g.,
vm.expectRevert(abi.encodeWithSelector(ReplayAlreadyProcessed.selector)) or
vm.expectRevert(bytes("AlreadyProcessed"))), then call
wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) so the test explicitly
validates the replay-protection error.

In `@evm/test/Upgrades.t.sol`:
- Around line 513-514: The test currently uses a generic vm.expectRevert()
before calling wormholeTransceiverChain2.receiveMessage(encodedVMs[0]), which
can pass for any failure; replace the generic expectRevert with a specific
revert assertion that matches the contract's replay-protection error (either the
exact revert string or the error selector/ABI-encoded bytes for the replay
error) so the test proves the second delivery hit replay protection; update the
call that precedes wormholeTransceiverChain2.receiveMessage(encodedVMs[0]) to
use vm.expectRevert(...) with the replay-specific payload (e.g., the contract
error selector or revert message) referencing the replay protection error
defined in the transceiver contract.

---

Outside diff comments:
In `@evm/src/libraries/TransceiverStructs.sol`:
- Around line 58-64: The digest helper nttManagerMessageDigest is inconsistent
with encodeNttManagerMessage because it hashes uint16(m.payload.length) without
the encoder's type(uint16).max clamp; update nttManagerMessageDigest to compute
the payload length exactly as the encoder does (i.e., clamp the length to
type(uint16).max before casting) so both functions use the same wire-format
length bound; locate nttManagerMessageDigest and mirror the encoder's min/clip
logic when producing the uint16 length value for the keccak256 input.

---

Nitpick comments:
In `@evm/test/IntegrationCustomConsistency.t.sol`:
- Around line 304-315: Duplicate test helper _getSignedMessages (which calls
WormholeOverride.fetchPublishedMessages, sets
PublishedMessage.envelope.emitterChainId, calls WormholeOverride.sign and
VaaLib.encode) should be extracted to a single shared test helper; create a
shared library/contract (e.g., TestWormholeHelpers.getSignedMessages) that
accepts Vm.Log[] and uint16 emitterChainId, implements the same logic, make it
internal/view or public/view as needed for tests, then replace the duplicate
implementations in both IntegrationCustomConsistency.t.sol and Upgrades.t.sol
with calls to the shared helper so future changes to WormholeOverride/VAA
signing are centralized.

In `@evm/test/IntegrationStandalone.t.sol`:
- Around line 670-680: Duplicate helper _getSignedMessages should be removed and
callers should use the centralized IntegrationHelpers._getWormholeMessage
instead; update IntegrationStandalone.t.sol to delete the local
_getSignedMessages function, import or inherit IntegrationHelpers where needed,
and replace all calls to _getSignedMessages(logs, emitterChainId) with
IntegrationHelpers._getWormholeMessage(logs, emitterChainId) (or the instance
method name as defined in IntegrationHelpers) ensuring types/visibility match
and any WormholeOverride/VaaLib dependencies are resolved via the shared helper.

ℹ️ Review info

Findings 1-2 (FuzzNttManager import + submodule commit): Not real issues. wormhole-sdk/ is correctly remapped, and d80e0f6 IS the v1.1.0 tag.

Findings 3-6 (generic vm.expectRevert()): vm.expectRevert(bytes4) fails for parameterized errors like TransferAlreadyCompleted(bytes32) in this forge-std version. Bare vm.expectRevert() is the correct approach.
Confirmed with actual test run showing 2 failures when using selector.

nttManagerMessageDigest length clamp:

Not a real issue. The type(uint16).max check in encodeNttManagerMessage is a revert guard — if payload exceeds 65535 bytes, the encoder reverts. It never encodes a clamped value. So any valid message that passes through
encodeNttManagerMessage will have payload.length <= type(uint16).max, making uint16(m.payload.length) in the digest function identical. The digest function only operates on already-parsed messages (which passed the length check
on encode), so there's no divergence.

Nitpicks (duplicate _getSignedMessages helpers): These are refactoring suggestions, not bugs. The duplication exists because different test contracts inherit different bases and the helper is a few lines. Not worth the
abstraction overhead for test code.

None of these findings require code changes.

@dvgui dvgui requested a review from djb15 March 16, 2026 19:54
dvgui added 2 commits March 25, 2026 15:13
…ions

Migrate from wormhole-solidity-sdk v0.1.0 to v1.1.0. Replace
parseAndVerifyVM with client-side CoreBridgeLib.decodeAndVerifyVaaCd,
switch receiveMessage to calldata, use VaaLib.calcVaaDoubleHashCd for
replay protection, inline nttManagerMessageDigest, add constant-time
countSetBits popcount, cache transceiver index in attestation, and
replace ConfigMakers with SDK's CustomConsistencyLib.

receiveMessage gas: avg -21.6%, median -18.1%, max -12.0%.
Deployment size: -14.1%.

Bump NttManager and WormholeTransceiver to v2.1.0 with TS SDK bindings.
quoteDeliveryPrice passed enabledTransceivers.length to
parseTransceiverInstructions, causing index out of bounds after
removing a transceiver. Use registered count instead, matching
the correct pattern in _transferEntryPoint.
Copilot AI review requested due to automatic review settings March 25, 2026 15:13
@dvgui dvgui force-pushed the sol-sdk-upgrades branch from 5f677f0 to 2c0cfb9 Compare March 25, 2026 15:13
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Upgrades the EVM contracts and tooling to Wormhole Solidity SDK v1.1.0, enabling solc 0.8.24 + via_ir, and adopts the SDK’s newer address and VAA verification utilities to reduce gas and deployment size while keeping ABI-versioned TS bindings in sync.

Changes:

  • Upgrade build + remappings (SDK submodule rev, solc 0.8.24, via_ir = true, wormhole-sdk/ imports).
  • Refactor Wormhole message verification to CoreBridgeLib.decodeAndVerifyVaaCd + replay digest via VaaLib.calcVaaDoubleHashCd, plus calldata optimizations.
  • Migrate tests/scripts and TS ethers bindings to ABI 2.1.0 and new WormholeOverride/VaaLib signing flow.

Reviewed changes

Copilot reviewed 40 out of 41 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
evm/ts/src/ethers-contracts/index.ts Exposes new _2_1_0 ABI bindings.
evm/ts/src/ethers-contracts/2_1_0/index.ts Adds autogenerated exports for ABI 2.1.0.
evm/ts/src/ethers-contracts/2_1_0/factories/index.ts Adds factory exports for ABI 2.1.0.
evm/ts/src/ethers-contracts/2_1_0/factories/WormholeTransceiver__factory.ts Adds ABI 2.1.0 WormholeTransceiver factory + bytecode linking.
evm/ts/src/ethers-contracts/2_1_0/common.ts Adds typechain common typings for ABI 2.1.0.
evm/ts/src/ethers-contracts/2_1_0/WormholeTransceiver.ts Adds ABI 2.1.0 WormholeTransceiver type definitions.
evm/ts/src/bindings.ts Registers ABI version 2.1.0 as newest supported.
evm/test/wormhole/Governance.t.sol Migrates to ICoreBridge + WormholeOverride/VaaLib-based VAA creation.
evm/test/mocks/MockNttManagerAdditionalPayload.sol Switches to toUniversalAddress helper.
evm/test/libraries/TransceiverHelpers.sol Updates helpers to universal addresses.
evm/test/libraries/NttManagerHelpers.sol Updates peer setup to universal addresses.
evm/test/libraries/IntegrationHelpers.sol Migrates to WormholeOverride/VaaLib and universal addresses.
evm/test/Upgrades.t.sol Migrates to WormholeOverride/VaaLib and universal addresses; adjusts replay tests.
evm/test/RateLimit.t.sol Migrates to WormholeOverride/VaaLib, BytesParsing API updates, universal addresses.
evm/test/NttManagerNoRateLimiting.t.sol Migrates to WormholeOverride/VaaLib; adds cached chainid workaround for via_ir.
evm/test/NttManager.t.sol Migrates to WormholeOverride/VaaLib; adds cached chainid workaround for via_ir.
evm/test/IntegrationWithoutRateLimiting.t.sol Migrates to WormholeOverride/VaaLib and universal addresses; adjusts replay tests.
evm/test/IntegrationStandalone.t.sol Migrates to WormholeOverride/VaaLib and universal addresses; adjusts replay tests.
evm/test/IntegrationCustomConsistency.t.sol Switches to SDK CustomConsistency + WormholeOverride/VaaLib.
evm/test/IntegrationAdditionalTransfer.t.sol Migrates to WormholeOverride/VaaLib and universal addresses; adjusts replay tests.
evm/test/DeploymentScripts.t.sol Updates deployment script tests for SDK CustomConsistency + WormholeOverride.
evm/src/wormhole/Governance.sol Replaces parseAndVerifyVM with SDK decode/verify + digest replay protection and updated BytesParsing API.
evm/src/libraries/TransceiverStructs.sol Updates BytesParsing API; inlines message digest hashing.
evm/src/libraries/TransceiverHelpers.sol Replaces popcount loop with constant-time popcount.
evm/src/libraries/ConfigMakers.sol Updates BytesParsing API usage and import path.
evm/src/interfaces/IWormholeTransceiver.sol Changes receiveMessage to bytes calldata for gas savings.
evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol Switches to ICoreBridge + SDK CustomConsistency helper + universal addresses.
evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol Implements calldata receive + SDK VAA verification and replay digest changes; bumps version string.
evm/src/Transceiver/Transceiver.sol Migrates to wormhole-sdk/Utils.sol and universal address comparisons.
evm/src/NttManager/NttManager.sol Bumps version string and migrates to universal address helpers.
evm/src/NttManager/ManagerBase.sol Caches transceiver index for attestation; adjusts instruction parsing length source.
evm/script/helpers/ParseNttConfig.sol Removes local universal address helpers; imports from SDK.
evm/script/helpers/DeployWormholeNttBase.sol Switches to ICoreBridge for messageFee() call.
evm/script/DeployWormholeNtt.s.sol Switches to ICoreBridge for chainId/decimals sanity checks.
evm/remappings.txt Adds wormhole-sdk/ remapping alongside legacy path.
evm/foundry.toml Bumps solc_version to 0.8.24 and enables via_ir.
evm/foundry.lock Updates wormhole SDK submodule revision.
evm/echidna/FuzzNttManager.sol Migrates Utils import path to wormhole-sdk/.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

vm.expectRevert() here is too broad for the replay-protection check; it will succeed for any revert. Please assert the specific IWormholeTransceiver.TransferAlreadyCompleted error with the expected VAA digest (computed from encodedVMs[0] using VaaLib.calcVaaDoubleHash*).

Suggested change
vm.expectRevert();
bytes32 vaaDigest = VaaLib.calcVaaDoubleHash(encodedVMs[0]);
vm.expectRevert(
abi.encodeWithSelector(
IWormholeTransceiver.TransferAlreadyCompleted.selector,
vaaDigest
)
);

Copilot uses AI. Check for mistakes.
IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This replay-protection assertion uses vm.expectRevert() without checking the revert data, so it won’t catch regressions where receiveMessage reverts for an unexpected reason. Prefer asserting TransferAlreadyCompleted(expectedDigest) with the digest derived from encodedVMs[0] via VaaLib.calcVaaDoubleHash*.

Suggested change
vm.expectRevert();
bytes32 expectedDigest = VaaLib.calcVaaDoubleHash(encodedVMs[0]);
vm.expectRevert(
abi.encodeWithSelector(
IWormholeTransceiver.TransferAlreadyCompleted.selector,
expectedDigest
)
);

Copilot uses AI. Check for mistakes.
IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

vm.expectRevert() is overly permissive for validating replay protection; it will pass even if receiveMessage reverts for a different reason. Consider computing the VAA digest from encodedVMs[0] (via VaaLib.calcVaaDoubleHash*) and asserting the parameterized TransferAlreadyCompleted(digest) error.

Suggested change
vm.expectRevert();
bytes32 digest = VaaLib.calcVaaDoubleHashFromEncoded(encodedVMs[0]);
vm.expectRevert(
abi.encodeWithSelector(
WormholeTransceiver.TransferAlreadyCompleted.selector,
digest
)
);

Copilot uses AI. Check for mistakes.
IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

This replay-submission check uses vm.expectRevert() without asserting the expected error selector/args, which weakens the test. Please assert IWormholeTransceiver.TransferAlreadyCompleted with the expected digest computed from encodedVMs[0] (e.g., via VaaLib.calcVaaDoubleHash*).

Suggested change
vm.expectRevert();
bytes32 digest = VaaLib.calcVaaDoubleHash(encodedVMs[0]);
vm.expectRevert(
abi.encodeWithSelector(
IWormholeTransceiver.TransferAlreadyCompleted.selector,
digest
)
);

Copilot uses AI. Check for mistakes.
IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

For the "can't resubmit" replay-protection case, vm.expectRevert() is too broad and can mask unintended revert causes. Prefer asserting the exact TransferAlreadyCompleted error with the expected digest (computed from encodedVMs[0] via VaaLib.calcVaaDoubleHash*).

Suggested change
vm.expectRevert();
vm.expectRevert(
abi.encodeWithSelector(
IWormholeTransceiver.TransferAlreadyCompleted.selector,
VaaLib.calcVaaDoubleHash(encodedVMs[0])
)
);

Copilot uses AI. Check for mistakes.
Comment on lines +62 to +64
return keccak256(
abi.encodePacked(sourceChainId, m.id, m.sender, uint16(m.payload.length), m.payload)
);
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

nttManagerMessageDigest now casts m.payload.length to uint16 without enforcing the <= type(uint16).max invariant. Previously this digest path went through encodeNttManagerMessage(m), which reverts with PayloadTooLong when the payload is oversized. To avoid silent truncation (and potential digest collisions), add the same length check (or reuse encodeNttManagerMessage).

Suggested change
return keccak256(
abi.encodePacked(sourceChainId, m.id, m.sender, uint16(m.payload.length), m.payload)
);
bytes memory encoded = encodeNttManagerMessage(m);
return keccak256(abi.encodePacked(sourceChainId, encoded));

Copilot uses AI. Check for mistakes.
IWormholeTransceiver.TransferAlreadyCompleted.selector, wormholeVM.hash
)
);
vm.expectRevert();
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

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

vm.expectRevert() without specifying the expected error data makes this replay-protection assertion pass for any revert reason. Since receiveMessage now uses the VAA double-hash digest, compute the digest from encodedVMs[0] (via the appropriate VaaLib.calcVaaDoubleHash* helper) and assert TransferAlreadyCompleted(digest) explicitly.

Suggested change
vm.expectRevert();
bytes32 digest = VaaLib.calcVaaDoubleHash(encodedVMs[0]);
vm.expectRevert(
abi.encodeWithSelector(WormholeTransceiver.TransferAlreadyCompleted.selector, digest)
);

Copilot uses AI. Check for mistakes.
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.

🧹 Nitpick comments (2)
evm/test/libraries/IntegrationHelpers.sol (1)

174-187: Rename _getWormholeMessage to match the array return.

The helper now always returns multiple encoded VAAs, so the singular name makes the call sites read like a one-message path. Renaming it to _getWormholeMessages would make the helper’s contract clearer.

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

In `@evm/test/libraries/IntegrationHelpers.sol` around lines 174 - 187, Rename the
helper function _getWormholeMessage to the plural form _getWormholeMessages and
update all references/call sites accordingly (including any tests or other
helpers) so the name matches the bytes[] return; specifically change the
function declaration for _getWormholeMessage and every invocation to
_getWormholeMessages, keeping the implementation (PublishedMessage[],
encodedVaas, WormholeOverride.fetchPublishedMessages, WormholeOverride.sign,
VaaLib.encode) unchanged.
evm/test/Upgrades.t.sol (1)

195-195: Exercise thisNttManager after the upgrade.

basicFunctionality() runs against the shared nttManagerChain1/2 fixture, so this path currently only proves that thisNttManager.upgrade(...) does not revert. A small post-upgrade assertion or local transfer flow on thisNttManager would make this test cover the special no-rate-limit upgrade path it sets up.

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

In `@evm/test/Upgrades.t.sol` at line 195, After calling basicFunctionality() and
performing thisNttManager.upgrade(...), actually exercise the upgraded instance:
invoke a simple post-upgrade flow against thisNttManager (for example mint or
transfer a token, or call a local transfer/send method and assert
balances/ownership) and assert it succeeds; alternatively assert a relevant
state change such as the peer rate limit set by thisNttManager.setPeer(chainId2,
toUniversalAddress(address(0x1)), 9, type(uint64).max) is present/readable.
Ensure the new assertions follow the upgrade call and reference thisNttManager,
basicFunctionality(), upgrade(...), and setPeer(...) so the test verifies the
no-rate-limit upgrade path rather than only checking for non-revert.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@evm/test/libraries/IntegrationHelpers.sol`:
- Around line 174-187: Rename the helper function _getWormholeMessage to the
plural form _getWormholeMessages and update all references/call sites
accordingly (including any tests or other helpers) so the name matches the
bytes[] return; specifically change the function declaration for
_getWormholeMessage and every invocation to _getWormholeMessages, keeping the
implementation (PublishedMessage[], encodedVaas,
WormholeOverride.fetchPublishedMessages, WormholeOverride.sign, VaaLib.encode)
unchanged.

In `@evm/test/Upgrades.t.sol`:
- Line 195: After calling basicFunctionality() and performing
thisNttManager.upgrade(...), actually exercise the upgraded instance: invoke a
simple post-upgrade flow against thisNttManager (for example mint or transfer a
token, or call a local transfer/send method and assert balances/ownership) and
assert it succeeds; alternatively assert a relevant state change such as the
peer rate limit set by thisNttManager.setPeer(chainId2,
toUniversalAddress(address(0x1)), 9, type(uint64).max) is present/readable.
Ensure the new assertions follow the upgrade call and reference thisNttManager,
basicFunctionality(), upgrade(...), and setPeer(...) so the test verifies the
no-rate-limit upgrade path rather than only checking for non-revert.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ed45e5d9-0b1d-46b8-85d7-07ff5df0f744

📥 Commits

Reviewing files that changed from the base of the PR and between 3fa3e22 and 2c0cfb9.

⛔ Files ignored due to path filters (1)
  • evm/foundry.lock is excluded by !**/*.lock
📒 Files selected for processing (40)
  • evm/echidna/FuzzNttManager.sol
  • evm/foundry.toml
  • evm/lib/wormhole-solidity-sdk
  • evm/remappings.txt
  • evm/script/DeployWormholeNtt.s.sol
  • evm/script/helpers/DeployWormholeNttBase.sol
  • evm/script/helpers/ParseNttConfig.sol
  • evm/src/NttManager/ManagerBase.sol
  • evm/src/NttManager/NttManager.sol
  • evm/src/Transceiver/Transceiver.sol
  • evm/src/Transceiver/WormholeTransceiver/WormholeTransceiver.sol
  • evm/src/Transceiver/WormholeTransceiver/WormholeTransceiverState.sol
  • evm/src/interfaces/IWormholeTransceiver.sol
  • evm/src/libraries/ConfigMakers.sol
  • evm/src/libraries/TransceiverHelpers.sol
  • evm/src/libraries/TransceiverStructs.sol
  • evm/src/wormhole/Governance.sol
  • evm/test/DeploymentScripts.t.sol
  • evm/test/IntegrationAdditionalTransfer.t.sol
  • evm/test/IntegrationCustomConsistency.t.sol
  • evm/test/IntegrationStandalone.t.sol
  • evm/test/IntegrationWithoutRateLimiting.t.sol
  • evm/test/NttManager.t.sol
  • evm/test/NttManagerNoRateLimiting.t.sol
  • evm/test/RateLimit.t.sol
  • evm/test/Upgrades.t.sol
  • evm/test/libraries/IntegrationHelpers.sol
  • evm/test/libraries/NttManagerHelpers.sol
  • evm/test/libraries/TransceiverHelpers.sol
  • evm/test/mocks/MockNttManagerAdditionalPayload.sol
  • evm/test/wormhole/Governance.t.sol
  • evm/ts/src/bindings.ts
  • evm/ts/src/ethers-contracts/2_1_0/NttManager.ts
  • evm/ts/src/ethers-contracts/2_1_0/WormholeTransceiver.ts
  • evm/ts/src/ethers-contracts/2_1_0/common.ts
  • evm/ts/src/ethers-contracts/2_1_0/factories/NttManager__factory.ts
  • evm/ts/src/ethers-contracts/2_1_0/factories/WormholeTransceiver__factory.ts
  • evm/ts/src/ethers-contracts/2_1_0/factories/index.ts
  • evm/ts/src/ethers-contracts/2_1_0/index.ts
  • evm/ts/src/ethers-contracts/index.ts
✅ Files skipped from review due to trivial changes (9)
  • evm/echidna/FuzzNttManager.sol
  • evm/foundry.toml
  • evm/ts/src/ethers-contracts/index.ts
  • evm/lib/wormhole-solidity-sdk
  • evm/ts/src/ethers-contracts/2_1_0/index.ts
  • evm/ts/src/ethers-contracts/2_1_0/factories/index.ts
  • evm/remappings.txt
  • evm/ts/src/ethers-contracts/2_1_0/factories/NttManager__factory.ts
  • evm/ts/src/ethers-contracts/2_1_0/factories/WormholeTransceiver__factory.ts
🚧 Files skipped from review as they are similar to previous changes (13)
  • evm/test/mocks/MockNttManagerAdditionalPayload.sol
  • evm/src/libraries/ConfigMakers.sol
  • evm/src/interfaces/IWormholeTransceiver.sol
  • evm/test/libraries/NttManagerHelpers.sol
  • evm/src/Transceiver/Transceiver.sol
  • evm/src/libraries/TransceiverHelpers.sol
  • evm/ts/src/bindings.ts
  • evm/test/RateLimit.t.sol
  • evm/test/IntegrationAdditionalTransfer.t.sol
  • evm/test/libraries/TransceiverHelpers.sol
  • evm/src/NttManager/NttManager.sol
  • evm/src/libraries/TransceiverStructs.sol
  • evm/ts/src/ethers-contracts/2_1_0/WormholeTransceiver.ts

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.

2 participants