Assessment reports>USDT OFT>Threat Model>usdtOFT TON

usdtOFT TON

The capabilities of each entity are as follows.

Permissionless

There are no assertions for the caller of the Layerzero::OP::LZ_RECEIVE_PREPARE opcode. A description of the function of the opcode is best given by the following comment in handlerOApp.fc:

;; lzReceive follows a two-phase commit pattern in TON
;; first, a permissionless call is made to lzReceivePrepare
;;      failure => permissionless retry at endpoint
;; second, the OApp performs a gas assertion and sends a clear request to the Packet
;;      failure => permissionless retry from step 1 at endpoint
;; third, the Packet locks itself and notifies the OApp to execute the request
;;      failure => OApp must unlock the Packet and retry from step 1
;; finally, the OApp clears and destroys the message
;;      failure => can be ignored, but the OApp will be blocklisted by the executor

The opcode ensures the remote peer in the user-controlled packet is correct, asserts that there is enough gas remaining to follow the receive flow, and calls Channel::OP::LZ_RECEIVE_LOCK on the caller — which is intended to be the channel contract (in LayerZero TON). If the caller is not the channel, there is no effect of the call.

Because the opcode makes no state changes, there is no harm in it being permissionless.

LayerZero channel

The LayerZero channel contract has the ability to call the following opcodes:

  • Layerzero::OP::LZ_RECEIVE_EXECUTE

  • Layerzero::OP::CHANNEL_SEND_CALLBACK

  • Layerzero::OP::BURN_CALLBACK

  • Layerzero::OP::NILIFY_CALLBACK

For information on the threat model of LayerZero, please refer to our LayerZero Endpoint V2 TON report (unreleased as of the time of this writing).

Planner

The planner has the ability to call the OP::SendCredits and OP::SetCostAsserts opcodes. This role typically handles cross-chain liquidity management by moving credits between different chains and updating the cost-related parameters used for gas and price checks.

  • OP::SendCredits. The planner can invoke this opcode to transfer credits (liquidity) across supported chains. While the planner does not hold the authority to withdraw actual USDT to an external address (that remains under the owner’s authority), it can reorder and rebalance the distribution of credits among chains. In a worst-case scenario where the planner’s key is compromised or the planner acts maliciously, it could excessively drain or redirect credits from one chain to another, resulting in local liquidity shortages or disruptions for cross-chain operations.

  • OP::SetCostAsserts. The planner can also modify cost-related assertions; raising these values too high may lead to unjustified transaction failures, whereas setting them too low could allow underpayment of fees and potentially disrupt cross-chain guarantees. When setting cost asserts, the user-input object is sanitized to ensure that only valid integer and structured fields are stored, preventing injection of malformed data structures.

Overall, the planner’s abilities facilitate dynamic adjustments to cross-chain liquidity and cost configurations. However, because the planner cannot directly withdraw USDT to an external address, there is a baseline safeguard against total liquidity theft. The ultimate power to withdraw tokens or reassign the planner resides with the owner, thereby mitigating full control risks associated with the planner role.

Owner

The owner has the ability to call the following opcodes:

  • OP::WithdrawLocal. The owner can withdraw USDT from the contract balance provided there are enough credits for the local EID (i.e., if depositLocal is called or sending bounces).

  • OP::WithdrawRemote. The owner can send USDT to an arbitrary remote address, limited by the credits for that remote EID.

  • OP::WithdrawFees. The owner can withdraw up to the feeBalance balance.

  • OP::RecoverUsdtInit and OP::RecoverUsdt. The owner can arbitrarily withdraw USDT from the contract balance after a timelock period. Note that we reported a bypass for this in Finding .

  • OP::SetPlannerAddress. Because of this, the owner inherits all of the abilities of the planner.

  • OP::SetGasAsserts. The user-input object is sanitized. This opcode allows the owner to configure the gas assertions for sending and receiving specific types of messages.

  • OP::SetFee. The user-input object is sanitized. This allows the owner to set an arbitrary fee at any time, provided it is an unsigned integer less than 100%.

The following opcodes are for managing the LayerZero OApp (handled in handlerOApp.fc). They are not specific to USDT OFT, and all functions are typically included in all OApp implementations on TON.

  • OP::DeployChannel. This deploys a LayerZero channel contract. This is typically done when intending to support a new path.

  • OP::DeployConnection. This instructs the messaging library to deploy a new, sharded LayerZero UlnConnection contract (deployUlnConnection in ulnManager) or a new SmlConnection contract (deployConnection in smlManager). This is typically done when intending to support a new path.

  • OP::ForceAbort. This aborts a pending send request. This capability is intended to prevent the send queue from being blocked if the sendRequestQueue (a DeterministicInsertionCircularQueue structure in LayerZero's channel contract) fills up for any reason. Note that when send requests are aborted, the path will not be refunded credits, and the sender will not be refunded fees or USDT on the local chain.

  • OP::Burn. This permanently discards an inbound message that the local OApp no longer intends to execute. Under the hood, the channel marks the message as "executed" (so it cannot be triggered again), effectively burning away that nonce from future processing. This is useful when a message is deemed obsolete or invalid, ensuring it never reenters the execution flow.

  • OP::Nilify. This removes an inbound message from the channel's queue without marking it as executed. This effectively frees up the message slot so it no longer blocks subsequent messages, but it does not treat it as having been processed. It is helpful in scenarios where the message must be cleared out (e.g., it became irrelevant) but not formally executed in a business-logic sense.

  • OP::SetPeer. This opcode configures valid outbound and inbound peer addresses for cross-chain interactions given an EID.

    Outbound. For each eid, the contract uses an internally maintained eidpeerAddress mapping when sending cross-chain messages, eliminating the need for hardcoded addresses and simplifying updates.

    Inbound. Upon receiving a message, the contract checks whether the sender matches the locally stored peerAddress. If they differ, the message is deemed invalid and rejected.

    This source verification is crucial for ensuring that cross-chain security-only messages from the recognized peerAddress are considered legitimate.

  • OP::SetEnforcedOptions. The setEnforcedOptions function defines mandatory option configurations for cross-chain messages associated with a specific (msgType,dstEid). These enforced options can restrict particular aspects of message handling — for example, requiring a specific options version or disallowing certain advanced features. By enforcing these settings at the OApp contract level, any subsequent logic that handles that message type must comply with these requirements, thereby ensuring that the processing flow aligns with the intended security and functional policies.

  • OP::SetLzConfig. The owner may modify the endpoint or messaging-library configuration at any time. A full description of the centralization risks of this ability is documented in our LayerZero Endpoint V2 (EVM) report.

Tentative owner

The tentative owner may accept ownership by using the OP::ClaimOwnership opcode. However, the owner must first call transferOwnership with the tentative owner's address.

Zellic © 2025Back to top ↑