> ## Documentation Index
> Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
> Use this file to discover all available pages before exploring further.

# LayerZero V2 EVM Protocol Overview

> Overview of EVM Protocol Overview on LayerZero V2. Learn the architecture, features, and how to get started building. LayerZero enables secure crosschain...

1. A user calls a smart contract `OApp` on the source chain and pays a fee to send a crosschain message to the `Endpoint`.

2. The `Endpoint` check the validity of the crosschain message and assigns each job to the `OApp` configured `DVNs` (Decentralized Verifier Networks) and `Executor` to execute the crosschain message.

3. The `DVNs` verify the message on the destination chain. After the required and optional DVNs have verified the message, the message is to be inserted (committed) in the message channel of the `Endpoint` on the destination chain.

4. After the message has been inserted in the Endpoint's message channel, the `Executor` calls `Endpoint.lzReceive` to trigger the execution of the crosschain message on the destination chain.

5. The `Endpoint` calls the payable `ReceiverOApp.lzReceive` to pass the message and execute the internal receive logic. You can modify the internal execution logic inside `ReceiverOApp._lzReceive` to trigger any intended outcome from the crosschain message.

<br />

<Tip>
  You can find all of the above contracts by visiting [**Supported Chains**](../../deployments/deployed-contracts) and [**Supported DVNs**](../../deployments/dvn-addresses).
</Tip>

### Send Overview

The `OApp` calls `EndpointV2.send` to send the crosschain message and pays a fee to each configured `DVN` and `Executor`.

#### EndpointV2.sol

Inside the `send` call:

* emit event to each `DVN` and `Executor` according to the `OApp` send configuration for the crosschain message. Also calculate and record the fee that should be paid to each `DVN` and `Executor`.

* check whether the fees the user is willing to pay can cover the fees required by the `DVNs` and `Executor`.

* transfer fee to `_sendLibrary` (which records fee allocation).

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/EndpointV2.sol

address public lzToken;

struct MessagingParams {
    uint32 dstEid; // destination chain endpoint id
    bytes32 receiver; // receiver on destination chain
    bytes message; // crosschain message
    bytes options; // settings for executor and dvn
    bool payInLzToken; // whether to pay in ZRO token
}

struct MessagingReceipt {
    bytes32 guid; // unique identifier for the message
    uint64 nonce; // message nonce
    MessagingFee fee; // the message fee paid
}

/// @dev MESSAGING STEP 1 - OApp need to transfer the fees to the endpoint before sending the message
/// @param _params the messaging parameters
/// @param _refundAddress the address to refund both the native and lzToken
function send(
    MessagingParams calldata _params,
    address _refundAddress
) external payable sendContext(_params.dstEid, msg.sender) returns (MessagingReceipt memory) {
    if (_params.payInLzToken && lzToken == address(0x0)) revert Errors.LZ_LzTokenUnavailable();

    // send message
    (MessagingReceipt memory receipt, address _sendLibrary) = _send(msg.sender, _params);

    // OApp can simulate with 0 native value it will fail with error including the required fee, which can be provided in the actual call
    // this trick can be used to avoid the need to write the quote() function
    // however, without the quote view function it will be hard to compose an oapp on chain
    uint256 suppliedNative = _suppliedNative();
    uint256 suppliedLzToken = _suppliedLzToken(_params.payInLzToken);

    // check fee sender has provided enough fee
    _assertMessagingFee(receipt.fee, suppliedNative, suppliedLzToken);

    // handle lz token fees to _sendLibrary
    _payToken(lzToken, receipt.fee.lzTokenFee, suppliedLzToken, _sendLibrary, _refundAddress);

    // handle native fees to _sendLibrary
    _payNative(receipt.fee.nativeFee, suppliedNative, _sendLibrary, _refundAddress);

    return receipt;
}

/// @dev Assert the required fees and the supplied fees are enough
function _assertMessagingFee(
    MessagingFee memory _required,
    uint256 _suppliedNativeFee,
    uint256 _suppliedLzTokenFee
) internal pure {
    if (_required.nativeFee > _suppliedNativeFee || _required.lzTokenFee > _suppliedLzTokenFee) {
        revert Errors.LZ_InsufficientFee(
            _required.nativeFee,
            _suppliedNativeFee,
            _required.lzTokenFee,
            _suppliedLzTokenFee
        );
    }
}

// pay lzToken
function _payToken(
    address _token,
    uint256 _required,
    uint256 _supplied,
    address _receiver,
    address _refundAddress
) internal {
    if (_required > 0) {
        Transfer.token(_token, _receiver, _required);
    }
    if (_required < _supplied) {
        unchecked {
            // refund the excess
            Transfer.token(_token, _refundAddress, _supplied - _required);
        }
    }
}

// pay native token
function _payNative(
    uint256 _required,
    uint256 _supplied,
    address _receiver,
    address _refundAddress
) internal virtual {
    if (_required > 0) {
        Transfer.native(_receiver, _required);
    }
    if (_required < _supplied) {
        unchecked {
            // refund the excess
            Transfer.native(_refundAddress, _supplied - _required);
        }
    }
}
```

Inside the internal `_send` call:

* get the `nonce` of this packet according to the path: **\[sender, destination chain, receiver]**.

* generate `guid` of the packet (global unique identifier).

* get the `_sendLibrary` of the OApp (OApp can set their specific send library of each destination chain).

* call `_sendLibrary` to emit events to notify `Executor` and `DVNs`, also calculate and record the `fee` that should be paid to each.

```solidity wrap theme={null}
//  LayerZero/V2/protocol/contracts/EndpointV2.sol

mapping(address sender => mapping(uint32 dstEid => mapping(bytes32 receiver => uint64 nonce))) public outboundNonce;

/// @dev increase and return the next outbound nonce
function _outbound(address _sender, uint32 _dstEid, bytes32 _receiver) internal returns (uint64 nonce) {
    unchecked {
        nonce = ++outboundNonce[_sender][_dstEid][_receiver];
    }
}

address private constant DEFAULT_LIB = address(0);
mapping(uint32 dstEid => address lib) public defaultSendLibrary;

/// @notice The Send Library is the Oapp specified library that will be used to send the message to the destination
/// endpoint. If the Oapp does not specify a Send Library, the default Send Library will be used.
/// @dev If the Oapp does not have a selected Send Library, this function will resolve to the default library
/// configured by LayerZero
/// @return lib address of the Send Library
/// @param _sender The address of the Oapp that is sending the message
/// @param _dstEid The destination endpoint id
function getSendLibrary(address _sender, uint32 _dstEid) public view returns (address lib) {
    lib = sendLibrary[_sender][_dstEid];
    if (lib == DEFAULT_LIB) {
        lib = defaultSendLibrary[_dstEid];
        if (lib == address(0x0)) revert Errors.LZ_DefaultSendLibUnavailable();
    }
}

struct MessagingFee {
    uint256 nativeFee;
    uint256 lzTokenFee;
}

/// @dev internal function for sending the messages used by all external send methods
/// @param _sender the address of the application sending the message to the destination chain
/// @param _params the messaging parameters
function _send(
    address _sender,
    MessagingParams calldata _params
) internal returns (MessagingReceipt memory, address) {
    // get the correct outbound nonce
    uint64 latestNonce = _outbound(_sender, _params.dstEid, _params.receiver);

    // construct the packet with a GUID
    Packet memory packet = Packet({
        nonce: latestNonce,
        srcEid: eid,
        sender: _sender,
        dstEid: _params.dstEid,
        receiver: _params.receiver,
        guid: GUID.generate(latestNonce, eid, _sender, _params.dstEid, _params.receiver),
        message: _params.message
    });

    // get the send library by sender and dst eid
    address _sendLibrary = getSendLibrary(_sender, _params.dstEid);

    // messageLib always returns encodedPacket with guid
    (MessagingFee memory fee, bytes memory encodedPacket) = ISendLib(_sendLibrary).send(
        packet,
        _params.options,
        _params.payInLzToken
    );

    // Emit packet information for DVNs, Executors, and any other offchain infrastructure to only listen
    // for this one event to perform their actions.
    emit PacketSent(encodedPacket, _params.options, _sendLibrary);

    return (MessagingReceipt(packet.guid, latestNonce, fee), _sendLibrary);
}
```

The `guid` is generated using the following parameters:

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/libs/GUID.sol
function generate(
    uint64 _nonce,
    uint32 _srcEid,
    address _sender,
    uint32 _dstEid,
    bytes32 _receiver
) internal pure returns (bytes32) {
    return keccak256(abi.encodePacked(_nonce, _srcEid, _sender.toBytes32(), _dstEid, _receiver));
}
```

#### SendUln302.sol

Next, the message is handled by the `OApp` selected Send Library. For example, `SendUln302.send`:

* pay workers (`DVNs` and `Executor`) and treasury. In the send process, the `fee` is not directly paid to the workers, but recorded in the send library (`SendUln302.sol`) for workers to claim later.

* call `DVNs` and `Executor`'s contract to emit event to notify them to send crosschain message.

```solidity wrap theme={null}
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.sol

struct Packet {
    uint64 nonce;
    uint32 srcEid;
    address sender;
    uint32 dstEid;
    bytes32 receiver;
    bytes32 guid;
    bytes message;
}

function send(
    Packet calldata _packet,
    bytes calldata _options,
    bool _payInLzToken
) public virtual onlyEndpoint returns (MessagingFee memory, bytes memory) {
		// assign job to Executor and DVN, calculate fees
    (bytes memory encodedPacket, uint256 totalNativeFee) = _payWorkers(_packet, _options);

		// calculate and pay the treasury fee, if enabled
    (uint256 treasuryNativeFee, uint256 lzTokenFee) = _payTreasury(
        _packet.sender,
        _packet.dstEid,
        totalNativeFee,
        _payInLzToken
    );
    totalNativeFee += treasuryNativeFee;

    return (MessagingFee(totalNativeFee, lzTokenFee), encodedPacket);
}
```

Inside the `SendUln302._payWorkers`, the contract:

* splits options to get `executorOptions` (`Executor`) and `validationOptions` (`DVN`).

* get the `OApp` set `Executor` and corresponding `maxMessageSize` (If not set, then a default `maxMessageSize` of 10000 bytes is used), and checks that the size of the message to send is less than than the max.

* calls `_payExecutor` to assign job to corresponding `Executor` and record the fee paid.

* calls `_payVerifier` to assign job to specified `DVNs` and record fee paid.

```solidity wrap theme={null}
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.sol

/// 1/ handle executor
/// 2/ handle other workers
function _payWorkers(
    Packet calldata _packet,
    bytes calldata _options
) internal returns (bytes memory encodedPacket, uint256 totalNativeFee) {
    // split workers options
    (bytes memory executorOptions, WorkerOptions[] memory validationOptions) = _splitOptions(_options);

    // handle executor
    ExecutorConfig memory config = getExecutorConfig(_packet.sender, _packet.dstEid);
    uint256 msgSize = _packet.message.length;
    _assertMessageSize(msgSize, config.maxMessageSize);
    totalNativeFee += _payExecutor(config.executor, _packet.dstEid, _packet.sender, msgSize, executorOptions);

    // handle other workers
    (uint256 verifierFee, bytes memory packetBytes) = _payVerifier(_packet, validationOptions); //for ULN, it will be dvns
    totalNativeFee += verifierFee;

    encodedPacket = packetBytes;
}

// @dev get the executor config and if not set, return the default config
function getExecutorConfig(address _oapp, uint32 _remoteEid) public view returns (ExecutorConfig memory rtnConfig) {
    ExecutorConfig storage defaultConfig = executorConfigs[DEFAULT_CONFIG][_remoteEid];
    ExecutorConfig storage customConfig = executorConfigs[_oapp][_remoteEid];

    uint32 maxMessageSize = customConfig.maxMessageSize;
    rtnConfig.maxMessageSize = maxMessageSize != 0 ? maxMessageSize : defaultConfig.maxMessageSize;

    address executor = customConfig.executor;
    rtnConfig.executor = executor != address(0x0) ? executor : defaultConfig.executor;
}

function _assertMessageSize(uint256 _actual, uint256 _max) internal pure {
    if (_actual > _max) revert LZ_MessageLib_InvalidMessageSize(_actual, _max);
}
```

Inside the `SendUln302._payExecutor`:

* calls `Executor` (default or set by OApp) to assign job and calculate the fee needed.

* record the `Executor`’s fee inside the send library.

```solidity wrap theme={null}

// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.sol
function _payExecutor(
    address _executor,
    uint32 _dstEid,
    address _sender,
    uint256 _msgSize,
    bytes memory _executorOptions
) internal returns (uint256 executorFee) {
    executorFee = ILayerZeroExecutor(_executor).assignJob(_dstEid, _sender, _msgSize, _executorOptions);
    if (executorFee > 0) {
        fees[_executor] += executorFee;
    }
    emit ExecutorFeePaid(_executor, executorFee);
}
```

Inside the `SendUln302._payVerifier`:

* calculate `payloadHash` and `payload`, which will be used to emit event to notify `DVN` to send the crosschain message.

  * `payloadHash` is a digest including information about the version and path of the crosschain message;

  * `payload` includes information of the `guid` and the body of the crosschain message.

* get the sender `OApp` config about which `DVNs` to use.

* assign job for each `DVN`, including both required and optional.

```solidity wrap theme={null}
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.sol
function _payVerifier(
    Packet calldata _packet,
    WorkerOptions[] memory _options
) internal override returns (uint256 otherWorkerFees, bytes memory encodedPacket) {
    (otherWorkerFees, encodedPacket) = _payDVNs(fees, _packet, _options);
}

struct WorkerOptions {
    uint8 workerId;
    bytes options;
}

// accumulated fees for workers and treasury
mapping(address worker => uint256) public fees;

struct AssignJobParam {
    uint32 dstEid;
    bytes packetHeader;
    bytes32 payloadHash;
    uint64 confirmations; // source chain block confirmations before message being verified on the destination
    address sender;
}

struct UlnConfig {
    uint64 confirmations;
    // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas
    uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default)
    uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default)
    uint8 optionalDVNThreshold; // (0, optionalDVNCount]
    address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs
    address[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs
}

/// ---------- pay and assign jobs ----------
function _payDVNs(
    mapping(address => uint256) storage _fees,
    Packet memory _packet,
    WorkerOptions[] memory _options
) internal returns (uint256 totalFee, bytes memory encodedPacket) {
		// calculate packetHeader and payload
    bytes memory packetHeader = PacketV1Codec.encodePacketHeader(_packet);
    bytes memory payload = PacketV1Codec.encodePayload(_packet);
    bytes32 payloadHash = keccak256(payload);
    uint32 dstEid = _packet.dstEid;
    address sender = _packet.sender;

    // get user’s config about DVN
    UlnConfig memory config = getUlnConfig(sender, dstEid);

    // if options is not empty, it must be dvn options
    bytes memory dvnOptions = _options.length == 0 ? bytes("") : _options[0].options;
    uint256[] memory dvnFees;

    // assign job for each DVN includes those required and optional
    (totalFee, dvnFees) = _assignJobs(
        _fees,
        config,
        ILayerZeroDVN.AssignJobParam(dstEid, packetHeader, payloadHash, config.confirmations, sender),
        dvnOptions
    );
    encodedPacket = abi.encodePacked(packetHeader, payload);

    emit DVNFeePaid(config.requiredDVNs, config.optionalDVNs, dvnFees);
}
```

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/messagelib/libs/PacketV1Codec.sol
function encodePacketHeader(Packet memory _packet) internal pure returns (bytes memory) {
    return
        abi.encodePacked(
            PACKET_VERSION,
            _packet.nonce,
            _packet.srcEid,
            _packet.sender.toBytes32(),
            _packet.dstEid,
            _packet.receiver
        );
}

function encodePayload(Packet memory _packet) internal pure returns (bytes memory) {
    return abi.encodePacked(_packet.guid, _packet.message);
}
```

Inside the `SendUln302._assignJobs`:

* call each required and optional `DVN` to notify them to verify the crosschain message on the destination chain.

* update each `DVN`'s fee.

* return the `totalFee` used by all `DVNs`.

```solidity wrap theme={null}
// LayerZero/V2/messagelib/contracts/uln/uln302/SendUln302.sol
function _assignJobs(
    mapping(address => uint256) storage _fees,
    UlnConfig memory _ulnConfig,
    ILayerZeroDVN.AssignJobParam memory _param,
    bytes memory dvnOptions
) internal returns (uint256 totalFee, uint256[] memory dvnFees) {
    (bytes[] memory optionsArray, uint8[] memory dvnIds) = DVNOptions.groupDVNOptionsByIdx(dvnOptions);

    uint8 dvnsLength = _ulnConfig.requiredDVNCount + _ulnConfig.optionalDVNCount;
    dvnFees = new uint256[](dvnsLength);
    for (uint8 i = 0; i < dvnsLength; ++i) {
        address dvn = i < _ulnConfig.requiredDVNCount
            ? _ulnConfig.requiredDVNs[i]
            : _ulnConfig.optionalDVNs[i - _ulnConfig.requiredDVNCount];

        bytes memory options = "";
        for (uint256 j = 0; j < dvnIds.length; ++j) {
            if (dvnIds[j] == i) {
                options = optionsArray[j];
                break;
            }
        }

        dvnFees[i] = ILayerZeroDVN(dvn).assignJob(_param, options);
        if (dvnFees[i] > 0) {
            _fees[dvn] += dvnFees[i];
            totalFee += dvnFees[i];
        }
    }
}
```

#### Assign Job to Executor

`Executor.assignJob` calls `ExecutorFeeLib.getFeeOnSend` to calculate the fee that should be paid to the `Executor`, and emit an event to notify.

In the `ExecutorFeeLib.getFeeOnSend`, it will check the `msg.value` specified by the message sender and enforce that it should be smaller than the `DstConfig.nativeCap` of the destination chain. This is because the supply of native tokens (e.g., Ether) must be maintained by the `Executor`, and is not controlled by the OApp unless running a custom `Executor`.

```solidity wrap theme={null}
// LayerZero/V2/messagelib/contracts/Executor.sol
struct FeeParams {
    address priceFeed;
    uint32 dstEid;
    address sender;
    uint256 calldataSize;
    uint16 defaultMultiplierBps;
}

struct DstConfig {
    uint64 baseGas; // for verifying / fixed calldata overhead
    uint16 multiplierBps;
    uint128 floorMarginUSD; // uses priceFeed PRICE_RATIO_DENOMINATOR
    uint128 nativeCap; // maximum native gas token cap
}

function assignJob(
    uint32 _dstEid,
    address _sender,
    uint256 _calldataSize,
    bytes calldata _options
) external onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_sender) returns (uint256 fee) {
    IExecutorFeeLib.FeeParams memory params = IExecutorFeeLib.FeeParams(
        priceFeed,
        _dstEid,
        _sender,
        _calldataSize,
        defaultMultiplierBps
    );
    fee = IExecutorFeeLib(workerFeeLib).getFeeOnSend(params, dstConfig[_dstEid], _options);
}
```

#### Assign Job to DVNs

`DVN.assignJob` calls `DVNFeeLib.getFeeOnSend` to calculate the fee that should be paid to the `DVNs`, and emit events to notify them.

```solidity wrap theme={null}

// LayerZero/V2/messagelib/contracts/uln/dvn/DVN.sol
/// @dev for ULN301, ULN302 and more to assign job
/// @dev dvn network can reject job from _sender by adding/removing them from allowlist/denylist
/// @param _param assign job param
/// @param _options dvn options
function assignJob(
    AssignJobParam calldata _param,
    bytes calldata _options
) external payable onlyRole(MESSAGE_LIB_ROLE) onlyAcl(_param.sender) returns (uint256 totalFee) {
    IDVNFeeLib.FeeParams memory feeParams = IDVNFeeLib.FeeParams(
        priceFeed,
        _param.dstEid,
        _param.confirmations,
        _param.sender,
        quorum,
        defaultMultiplierBps
    );
    totalFee = IDVNFeeLib(workerFeeLib).getFeeOnSend(feeParams, dstConfig[_param.dstEid], _options);
}
```

### Send Limitations

#### Max Message Bytes Size

The `maxMessageSize` depends on the Send Library. In `SendUln302`, the default max is 10000 bytes, but this value can be configured per OApp.

#### Max Native Gas Token Requests

In the `ExecutorFeeLib._decodeExecutorOptions`, it limits the maximum native gas token amount that can be requested from the `Executor` for the destination chain transaction.

This config is set in `Executor.dstConfig`:

```solidity wrap theme={null}
// LayerZero/V2/messagelib/contracts/Executor.sol
struct DstConfig {
    uint64 baseGas; // for verifying / fixed calldata overhead
    uint16 multiplierBps;
    uint128 floorMarginUSD; // uses priceFeed PRICE_RATIO_DENOMINATOR
    uint128 nativeCap; // maximum native gas token amount to request from Executor for destination chain transaction
}
```

### Verification Workflow

After the crosschain message has been sent on the source chain (event has been emitted to notify `DVNs` and `Executor`), `DVN` will first verify the message on the destination chain, after which `Executor` will execute the message.

#### DVN Verification

`DVNs` call `ReceiveUln302.verify` to submit their witness of the source crosschain message using the `_payloadHash`.

```solidity wrap theme={null}
// LayerZero/V2/messagelib/contracts/uln/ReceiveUlnBase.sol
function verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) external {
    _verify(_packetHeader, _payloadHash, _confirmations);
}

mapping(bytes32 headerHash => mapping(bytes32 payloadHash => mapping(address dvn => Verification)))
    public hashLookup;
function _verify(bytes calldata _packetHeader, bytes32 _payloadHash, uint64 _confirmations) internal {
    hashLookup[keccak256(_packetHeader)][_payloadHash][msg.sender] = Verification(true, _confirmations);
    emit PayloadVerified(msg.sender, _packetHeader, _confirmations, _payloadHash);
}
```

#### Commit Verification

After the `OApp`'s required `DVNs` have all verified, and the threshold of optional `DVNs` has been reached, `ReceiveUln302.commitVerification` can be called by any address to commit the verification to the `Endpoint`'s message channel.

```solidity wrap theme={null}
// LayerZero/V2/messagelib/contracts/uln/uln302/ReceiveUln302.sol

struct UlnConfig {
    uint64 confirmations;
    // we store the length of required DVNs and optional DVNs instead of using DVN.length directly to save gas
    uint8 requiredDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default)
    uint8 optionalDVNCount; // 0 indicate DEFAULT, NIL_DVN_COUNT indicate NONE (to override the value of default)
    uint8 optionalDVNThreshold; // (0, optionalDVNCount]
    address[] requiredDVNs; // no duplicates. sorted an an ascending order. allowed overlap with optionalDVNs
    address[] optionalDVNs; // no duplicates. sorted an an ascending order. allowed overlap with requiredDVNs
}

/// @dev dont need to check endpoint verifiable here to save gas, as it will reverts if not verifiable.
function commitVerification(bytes calldata _packetHeader, bytes32 _payloadHash) external {
		// check packet header validity
    _assertHeader(_packetHeader, localEid);

    // decode the receiver and source Endpoint Id
    address receiver = _packetHeader.receiverB20();
    uint32 srcEid = _packetHeader.srcEid();

		// get receiver's config
    UlnConfig memory config = getUlnConfig(receiver, srcEid);
    _verifyAndReclaimStorage(config, keccak256(_packetHeader), _payloadHash);

    Origin memory origin = Origin(srcEid, _packetHeader.sender(), _packetHeader.nonce());

    // call endpoint to verify payload hash
    // endpoint will revert if nonce <= lazyInboundNonce
    ILayerZeroEndpointV2(endpoint).verify(origin, receiver, _payloadHash);
}

function _assertHeader(bytes calldata _packetHeader, uint32 _localEid) internal pure {
    // assert packet header is of right size 81
    if (_packetHeader.length != 81) revert LZ_ULN_InvalidPacketHeader();
    // assert packet header version is the same as ULN
    if (_packetHeader.version() != PacketV1Codec.PACKET_VERSION) revert LZ_ULN_InvalidPacketVersion();
    // assert the packet is for this endpoint
    if (_packetHeader.dstEid() != _localEid) revert LZ_ULN_InvalidEid();
}
```

`_verifyAndReclaimStorage` verifies that the required and optional `DVNs` have submitted witness.

```solidity wrap theme={null}
function _verifyAndReclaimStorage(UlnConfig memory _config, bytes32 _headerHash, bytes32 _payloadHash) internal {
    if (!_checkVerifiable(_config, _headerHash, _payloadHash)) {
        revert LZ_ULN_Verifying();
    }

    // iterate the required DVNs
    if (_config.requiredDVNCount > 0) {
        for (uint8 i = 0; i < _config.requiredDVNCount; ++i) {
            delete hashLookup[_headerHash][_payloadHash][_config.requiredDVNs[i]];
        }
    }

    // iterate the optional DVNs
    if (_config.optionalDVNCount > 0) {
        for (uint8 i = 0; i < _config.optionalDVNCount; ++i) {
            delete hashLookup[_headerHash][_payloadHash][_config.optionalDVNs[i]];
        }
    }
}
```

#### Insert Hash to Endpoint's Message Channel

Inside the `verify`:

* check `msg.sender` is valid `ReceiveLibrary` configured by the `OApp`.

* get the `lazyNonce` of the OApp.

* check the crosschain message path is valid for the `receiver`.

* check the message represented by the `nonce` has not been executed before.

* insert the message into the `Endpoint`'s message channel.

`lazyNonce` is the latest executed message’s `nonce`. To execute a transaction, LayerZero requires all messages before the current message has been verified. So all messages before the message with `lazyNonce` has been verified.

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/EndpointV2.sol

/// @dev configured receive library verifies a message
/// @param _origin a struct holding the srcEid, nonce, and sender of the message
/// @param _receiver the receiver of the message
/// @param _payloadHash the payload hash of the message
function verify(Origin calldata _origin, address _receiver, bytes32 _payloadHash) external {
		// check msg.sender is valid ReceiveLibrary configured by the OApp
    if (!isValidReceiveLibrary(_receiver, _origin.srcEid, msg.sender)) revert Errors.LZ_InvalidReceiveLibrary();

		// get the lazynonce
    uint64 lazyNonce = lazyInboundNonce[_receiver][_origin.srcEid][_origin.sender];

    // check whether path is valid
    if (!_initializable(_origin, _receiver, lazyNonce)) revert Errors.LZ_PathNotInitializable();

    // check the nonce/msg hasn't been executed before
    if (!_verifiable(_origin, _receiver, lazyNonce)) revert Errors.LZ_PathNotVerifiable();

    // insert the message into the message channel
    _inbound(_receiver, _origin.srcEid, _origin.sender, _origin.nonce, _payloadHash);
    emit PacketVerified(_origin, _receiver, _payloadHash);
}
```

`isValidReceiveLibrary` checks whether the `ReceiveLib` is the expected `ReceiveLib` of the `receiver`. If not, then check whether there has been a `Timeout` set for the current `ReceiveLib`.

`Timeout` is used to help improve the UX of updating a `ReceiveLib`. For example, if `OApp` decides to switch the `ReceiveLib`, it can update the address on the destination chain, but some crosschain messages may already be in-flight and not inserted in the destination chain Endpoint's message channel before the switch. Those messages depend on the previous `ReceiveLib`, so `Timeout` provides a grace period to ensure already in-flight messages have successful execution.

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/EndpointV2.sol

/// @dev called when the endpoint checks if the msgLib attempting to verify the msg is the configured msgLib of the Oapp
/// @dev this check provides the ability for Oapp to lock in a trusted msgLib
/// @dev it will fist check if the msgLib is the currently configured one. then check if the msgLib is the one in grace period of msgLib versioning upgrade
function isValidReceiveLibrary(
    address _receiver,
    uint32 _srcEid,
    address _actualReceiveLib
) public view returns (bool) {
    // early return true if the _actualReceiveLib is the currently configured one
    (address expectedReceiveLib, bool isDefault) = getReceiveLibrary(_receiver, _srcEid);
    if (_actualReceiveLib == expectedReceiveLib) {
        return true;
    }

    // check the timeout condition otherwise
    // if the Oapp is using defaultReceiveLibrary, use the default Timeout config
    // otherwise, use the Timeout configured by the Oapp
    Timeout memory timeout = isDefault
        ? defaultReceiveLibraryTimeout[_srcEid]
        : receiveLibraryTimeout[_receiver][_srcEid];

    // requires the _actualReceiveLib to be the same as the one in grace period and the grace period has not expired
    // block.number is uint256 so timeout.expiry must > 0, which implies a non-ZERO value
    if (timeout.lib == _actualReceiveLib && timeout.expiry > block.number) {
        // timeout lib set and has not expired
        return true;
    }

    // returns false by default
    return false;
}

/// @dev the receiveLibrary can be lazily resolved that if not set it will point to the default configured by LayerZero
function getReceiveLibrary(address _receiver, uint32 _srcEid) public view returns (address lib, bool isDefault) {
    lib = receiveLibrary[_receiver][_srcEid];
    if (lib == DEFAULT_LIB) {
        lib = defaultReceiveLibrary[_srcEid];
        if (lib == address(0x0)) revert Errors.LZ_DefaultReceiveLibUnavailable();
        isDefault = true;
    }
}
```

`_initializable` is used to check whether the crosschain message path is valid for the `receiver`. `_lazyInboundNonce` greater than 0 suggests a message has already been executed successfully, so no need to call `_receiver` to check the path again, which helps save gas.

Otherwise, call `_receiver.allowInitializePath` to check (the `OApp` standard inherits `OAppReceiver` which has already implemented `allowInitializePath`).

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/EndpointV2.sol

function _initializable(
    Origin calldata _origin,
    address _receiver,
    uint64 _lazyInboundNonce
) internal view returns (bool) {
    return
        _lazyInboundNonce > 0 || // allowInitializePath already checked
        ILayerZeroReceiver(_receiver).allowInitializePath(_origin);
}
```

```solidity wrap theme={null}
// LayerZero/V2/oapp/contracts/oapp/OAppReceiver.sol

/**
 * @notice Checks if the path initialization is allowed based on the provided origin.
 * @param origin The origin information containing the source endpoint and sender address.
 * @return Whether the path has been initialized.
 *
 * @dev This indicates to the endpoint that the OApp has enabled msgs for this particular path to be received.
 * @dev This defaults to assuming if a peer has been set, its initialized.
 * Can be overridden by the OApp if there is other logic to determine this.
 */
function allowInitializePath(Origin calldata origin) public view virtual returns (bool) {
    return peers[origin.srcEid] == origin.sender;
}
```

`_verifiable` checks that the nonce / message has not been executed before.

* If `_origin.nonce` > `_lazyInboundNonce`, then the nonce / message has not been executed before, otherwise `_lazyInboundNonce` ≥ `_origin.nonce`.

* If `_origin.nonce` ≤ `_lazyInboundNonce`, then the nonce / message has been verified. If the payload hash is empty, which means the nonce / message has been executed (because the `Endpoint` will clear the payload hash of the nonce after successful execution), it cannot be executed again.

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/EndpointV2.sol
function _verifiable(
    Origin calldata _origin,
    address _receiver,
    uint64 _lazyInboundNonce
) internal view returns (bool) {
    return
        _origin.nonce > _lazyInboundNonce || // either initializing an empty slot or reverifying
        inboundPayloadHash[_receiver][_origin.srcEid][_origin.sender][_origin.nonce] != EMPTY_PAYLOAD_HASH; // only allow reverifying if it hasn't been executed
}
```

`_inbound` inserts the message into the channel (`inboundPayloadHash`).

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/MessagingChannel.sol

/// @dev inbound won't update the nonce eagerly to allow unordered verification
/// @dev instead, it will update the nonce lazily when the message is received
/// @dev messages can only be cleared in order to preserve censorship-resistance
function _inbound(
    address _receiver,
    uint32 _srcEid,
    bytes32 _sender,
    uint64 _nonce,
    bytes32 _payloadHash
) internal {
    if (_payloadHash == EMPTY_PAYLOAD_HASH) revert Errors.LZ_InvalidPayloadHash();
    inboundPayloadHash[_receiver][_srcEid][_sender][_nonce] = _payloadHash;
}
```

### Receive Workflow

#### Endpoint Execution

After the crosschain message has been inserted into the channel (`Endpoint.inboundPayloadHash`), `Executor` will try to call `Endpoint.lzReceive` to execute the message.

* clear the payload first to prevent reentrancy and double execution.

* call `ILayerZeroReceiver.lzReceive` to execute the message.

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/EndpointV2.sol

struct Origin {
    uint32 srcEid;
    bytes32 sender;
    uint64 nonce;
}

/// @dev execute a verified message to the designated receiver
/// @dev the execution provides the execution context (caller, extraData) to the receiver. the receiver can optionally assert the caller and validate the untrusted extraData
/// @dev cant reentrant because the payload is cleared before execution
/// @param _origin the origin of the message
/// @param _receiver the receiver of the message
/// @param _guid the guid of the message
/// @param _message the message
/// @param _extraData the extra data provided by the executor. this data is untrusted and should be validated.
function lzReceive(
    Origin calldata _origin,
    address _receiver,
    bytes32 _guid,
    bytes calldata _message,
    bytes calldata _extraData
) external payable {
    // clear the payload first to prevent reentrancy, and then execute the message
    _clearPayload(_receiver, _origin.srcEid, _origin.sender, _origin.nonce, abi.encodePacked(_guid, _message));
    ILayerZeroReceiver(_receiver).lzReceive{ value: msg.value }(_origin, _guid, _message, msg.sender, _extraData);
    emit PacketDelivered(_origin, _receiver);
}
```

Inside the `_clearPayload`:

* update the `lazyInboundNonce`.

* verify payload provided by `Executor`.

* delete message in the channel to prevent double execution.

```solidity wrap theme={null}
// LayerZero/V2/protocol/contracts/EndpointV2.sol


/// @dev calling this function will clear the stored message and increment the lazyInboundNonce to the provided nonce
/// @dev if a lot of messages are queued, the messages can be cleared with a smaller step size to prevent OOG
/// @dev NOTE: this function does not change inboundNonce, it only changes the lazyInboundNonce up to the provided nonce
function _clearPayload(
    address _receiver,
    uint32 _srcEid,
    bytes32 _sender,
    uint64 _nonce,
    bytes memory _payload
) internal returns (bytes32 actualHash) {
    uint64 currentNonce = lazyInboundNonce[_receiver][_srcEid][_sender];
    if (_nonce > currentNonce) {
        unchecked {
            // try to lazily update the inboundNonce till the _nonce
            for (uint64 i = currentNonce + 1; i <= _nonce; ++i) {
                if (!_hasPayloadHash(_receiver, _srcEid, _sender, i)) revert Errors.LZ_InvalidNonce(i);
            }
            lazyInboundNonce[_receiver][_srcEid][_sender] = _nonce;
        }
    }

    // check the hash of the payload to verify the executor has given the proper payload that has been verified
    actualHash = keccak256(_payload);
    bytes32 expectedHash = inboundPayloadHash[_receiver][_srcEid][_sender][_nonce];
    if (expectedHash != actualHash) revert Errors.LZ_PayloadHashNotFound(expectedHash, actualHash);

    // remove it from the storage
    delete inboundPayloadHash[_receiver][_srcEid][_sender][_nonce];
}
```

#### OApp Execution

By default, the `OApp` standard inherits `OAppReceiver` which implements `lzReceive` called by `Endpoint` to execute message.

* check `msg.sender` is `Endpoint`.

* check the path is valid.

* call internal `_lzReceive` to execute logic (developer should override to add specific use).

```solidity wrap theme={null}
// LayerZero/V2/oapp/contracts/oapp/OAppReceiver.sol

/**
 * @dev Entry point for receiving messages or packets from the endpoint.
 * @param _origin The origin information containing the source endpoint and sender address.
 *  - srcEid: The source chain endpoint ID.
 *  - sender: The sender address on the src chain.
 *  - nonce: The nonce of the message.
 * @param _guid The unique identifier for the received LayerZero message.
 * @param _message The payload of the received message.
 * @param _executor The address of the executor for the received message.
 * @param _extraData Additional arbitrary data provided by the corresponding executor.
 *
 * @dev Entry point for receiving msg/packet from the LayerZero endpoint.
 */
function lzReceive(
    Origin calldata _origin,
    bytes32 _guid,
    bytes calldata _message,
    address _executor,
    bytes calldata _extraData
) public payable virtual {
    // Ensures that only the endpoint can attempt to lzReceive() messages to this OApp.
    if (address(endpoint) != msg.sender) revert OnlyEndpoint(msg.sender);

    // Ensure that the sender matches the expected peer for the source endpoint.
    if (_getPeerOrRevert(_origin.srcEid) != _origin.sender) revert OnlyPeer(_origin.srcEid, _origin.sender);

    // Call the internal OApp implementation of lzReceive.
    _lzReceive(_origin, _guid, _message, _executor, _extraData);
}

/**
 * @dev Internal function to implement lzReceive logic without needing to copy the basic parameter validation.
 */
function _lzReceive(
    Origin calldata _origin,
    bytes32 _guid,
    bytes calldata _message,
    address _executor,
    bytes calldata _extraData
) internal virtual;
```

In the original `_getPeerOrRevert` implementation, it can only assign one valid `sender` for each source chain, but developers can override this to allow multiple `senders` on one source chain.

```solidity wrap theme={null}
// LayerZero/V2/oapp/contracts/oapp/OAppCore.sol

/**
 * @notice Internal function to get the peer address associated with a specific endpoint; reverts if NOT set.
 * ie. the peer is set to bytes32(0).
 * @param _eid The endpoint ID.
 * @return peer The address of the peer associated with the specified endpoint.
 */
function _getPeerOrRevert(uint32 _eid) internal view virtual returns (bytes32) {
    bytes32 peer = peers[_eid];
    if (peer == bytes32(0)) revert NoPeer(_eid);
    return peer;
}

```

<Info>
  Developers should also override `OAppReceiver.allowInitializePath` so that the message can be successfully inserted into the `Endpoint`'s message channel (the Endpoint will call to check whether the path is valid).
</Info>

<Tip>
  Special thanks to community member [**SennHanami**](https://x.com/HanamiSenn) for their contribution to this documentation page. You can read their full deep-dive at: [**Decode LayerZero V2**](https://senn.fun/decode-layerzero-v2#af1b7e67e9ad48c5aae184928aa4d209).
</Tip>
