Skip to main content
Version: Endpoint V2

Uncensorable Message Delivery (Nonce Enforcement)

LayerZero offers both unordered delivery and ordered delivery, providing developers with the flexibility to choose the most appropriate transaction ordering mechanism based on the specific requirements of their application.

Unordered Delivery

By default, the LayerZero protocol uses unordered delivery, where transactions can be executed out of order if all transactions prior have been verified.

If transactions 1 and 2 have not been verified, then transaction 3 cannot be executed until the previous nonces have been verified.

Once nonces 1, 2, 3 have been verified:

  • If nonce 2 failed to execute (due to some gas or user logic related issue), nonce 3 can still proceed and execute.

Lazy Nonce Enforcement Light Lazy Nonce Enforcement Dark

This is particularly useful in scenarios where transactions are not critically dependent on the execution of previous transactions.

Ordered Delivery

Developers can configure the OApp contract to use ordered delivery.

Strict Nonce Enforcement Light Strict Nonce Enforcement Dark

In this configuration, if you have a sequence of packets with nonces 1, 2, 3, and so on, each packet must be executed in that exact, sequential order:

  • If nonce 2 fails for any reason, it will block all subsequent transactions with higher nonces from being executed until nonce 2 is resolved.

Strict Nonce Enforcement Fail Light Strict Nonce Enforcement Fail Dark

Strict nonce enforcement can be important in scenarios where the order of transactions is critical to the integrity of the system, such as any multi-step process that needs to occur in a specific sequence to maintain consistency.

info

In these cases, strict nonce enforcement can be used to provide consistency, fairness, and censorship-resistance to maintain system integrity.

Enabling Ordered Delivery

To implement strict nonce enforcement, you need to implement the following:

  • a boolean flag orderedNonce, setting it to true in the constructor.

  • a mapping to track the maximum received nonce.

  • override _acceptNonce and nextNonce.

  • add ExecutorOrderedExecutionOption in _options when calling _lzSend.

caution

If you do not pass an ExecutorOrderedExecutionOption in your _lzSend call, the Executor will attempt to execute the message despite your application-level nonce enforcement, leading to a message revert.

Usage

Append to your Message Options an ExecutorOrderedExecutionOption in your _lzSend call:

// appends "01000104", the ExecutorOrderedExecutionOption, to your options bytes array
_options = OptionsBuilder.newOptions().addExecutorLzReceiveOption(200000, 0).addExecutorOrderedExecutionOption();

Implement strict nonce enforcement via function override:

pragma solidity ^0.8.20;

import { OApp } from "@layerzerolabs/lz-evm-oapp-v2/contracts/oapp/OApp.sol"; // Import OApp and other necessary contracts/interfaces

/**
* @title OmniChain Nonce Ordered Enforcement Example
* @dev Implements nonce ordered enforcement for your OApp.
*/
contract MyNonceEnforcementExample is OApp {
// Mapping to track the maximum received nonce for each source endpoint and sender
mapping(uint32 eid => mapping(bytes32 sender => uint64 nonce)) private receivedNonce;

/**
* @dev Constructor to initialize the omnichain contract.
* @param _endpoint Address of the LayerZero endpoint.
* @param _owner Address of the contract owner.
*/
constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) {}

/**
* @dev Public function to get the next expected nonce for a given source endpoint and sender.
* @param _srcEid Source endpoint ID.
* @param _sender Sender's address in bytes32 format.
* @return uint64 Next expected nonce.
*/
function nextNonce(uint32 _srcEid, bytes32 _sender) public view virtual override returns (uint64) {
return receivedNonce[_srcEid][_sender] + 1;
}

/**
* @dev Internal function to accept nonce from the specified source endpoint and sender.
* @param _srcEid Source endpoint ID.
* @param _sender Sender's address in bytes32 format.
* @param _nonce The nonce to be accepted.
*/
function _acceptNonce(uint32 _srcEid, bytes32 _sender, uint64 _nonce) internal virtual override {
receivedNonce[_srcEid][_sender] += 1;
require(_nonce == receivedNonce[_srcEid][_sender], "OApp: invalid nonce");
}

// @dev Override receive function to enforce strict nonce enforcement.
function _lzReceive(
Origin calldata _origin,
bytes32 _guid,
bytes calldata _message,
address _executor,
bytes calldata _extraData
) public payable virtual override {
_acceptNonce(_origin.srcEid, _origin.sender, _origin.nonce);
// Call the internal function with the correct parameters
super._lzReceive(_origin, _guid, _message, _executor, _extraData);
}
}