For the complete documentation index, see llms.txt. This page is also available as Markdown.

56615 sc high inconsistent handler address representation in transceivermanager leads to permanent freezing of incoming transfers

Submitted on Oct 18th 2025 at 12:51:16 UTC by @Ambitious_DyDx for Audit Comp | Folks Finance: Wormhole NTT on Algorand

  • Report ID: #56615

  • Report Type: Smart Contract

  • Report severity: High

  • Target: https://github.com/Folks-Finance/algorand-ntt-contracts/blob/main/ntt_contracts/transceiver/TransceiverManager.py

  • Impacts:

    • Permanent freezing of funds

Description

Brief/Intro

The TransceiverManager contract incorrectly converts the handler_address (a 32-byte UniversalAddress) to a UInt64 app ID using BytesUtils.safe_convert_bytes32_to_uint64, assuming a padded UInt64 format, while the system consistently uses full 32-byte Algorand app addresses derived from sha512_256("appID" + itob(app_id)). This mismatch causes attestation failures during attestation_received (e.g., unsafe conversion errors or unknown handler assertions), preventing attestations from being recorded. As a result, incoming cross-chain messages never reach the required threshold for execution in MessageHandler, permanently freezing funds transferred to Algorand (tokens locked/burned on source chains but never minted/unlocked on Algorand).

Vulnerability Details

In the NTT framework, the MessageReceived.handler_address field is a UniversalAddress (Bytes32) representing the destination contract. For Algorand destinations, this should be the 32-byte app address (public key-like, derived via SHA512/256 hashing of the app ID). However, in TransceiverManager.attestation_received:

message_handler = BytesUtils.safe_convert_bytes32_to_uint64(message.handler_address.copy())

This assumes handler_address is a 32-byte value with the last 8 bytes as the UInt64 app ID (padded with 24 zeros), allowing safe extraction. But the code populates handler_address with the full 32-byte hashed address:

  • In NttManager._transfer: handler_address = ntt_manager_peer.peer_contract (set via set_ntt_manager_peer as 32-byte peer address).

  • source_address = UniversalAddress.from_bytes(Global.current_application_address.bytes) (32-byte address).

When receiving a message from a non-Algorand chain (where peers are set to 32-byte addresses), the conversion in attestation_received either fails with "Unsafe conversion of bytes32 to uint64" (if not padded) or yields an incorrect UInt64. Subsequent checks like _check_message_handler_known(message_handler) or _check_transceiver_configured(message_handler, transceiver) fail due to the invalid ID, halting attestation recording. The num_attestations box remains at 0, blocking threshold attainment.

Even if attestations were forced, MessageHandler.execute_message checks:

This expects the full 32-byte address, so using padded IDs for peers would mismatch here, still preventing execution.

This breaks all incoming transfers to Algorand, as messages from other chains use 32-byte addresses.

Impact Details

Exploitation (or normal use) results in permanent freezing of funds: tokens are burned/locked on the source chain during outbound transfers, but incoming messages to Algorand cannot be attested or executed, preventing minting/unlocking on the destination. This affects all cross-chain inflows to Algorand-integrated NTT tokens, leading to total loss of transferred value (protocol insolvency for affected assets if unrecoverable). Matches the in-scope impact "Permanent freezing of funds" (Critical severity).

References

  • TransceiverManager.attestation_received: https://github.com/Folks-Finance/algorand-ntt-contracts/blob/main/contracts/transceiver/TransceiverManager.py (lines relevant to conversion and checks).

  • NttManager._transfer: https://github.com/Folks-Finance/algorand-ntt-contracts/blob/main/contracts/ntt_manager/NttManager.py (handler_address setting).

  • MessageHandler.execute_message: https://github.com/Folks-Finance/algorand-ntt-contracts/blob/main/contracts/transceiver/MessageHandler.py (address mismatch check).

  • UniversalAddress definition: https://github.com/Folks-Finance/algorand-ntt-contracts/blob/main/contracts/types.py.

  • Algorand app address derivation: https://developer.algorand.org/docs/get-details/dapps/smart-contracts/apps/specify/#application-address.

Proof of Concept

Proof of Concept

Add to algorand-ntt-contracts/tests/transceiver/TransceiverManager.test.ts:

Run:

Output:

The test passes by confirming the unsafe conversion error, proving attestation failure on realistic 32-byte handler addresses.

Recommendation

Refactor TransceiverManager to use a key derived from the full UniversalAddress (e.g., keccak256(handler_address)) for storage and checks, eliminating UInt64 conversion. Store configurations (e.g., transceivers, paused status) under this key. In attestation_received, compute the key from message.handler_address without conversion. Update peer setting/docs to ensure consistent 32-byte address use. If app ID is needed elsewhere, add a registry mapping addresses to IDs.

Was this helpful?