-
A user calls a smart contract
OAppon the source chain and pays a fee to send a crosschain message to theEndpoint. -
The
Endpointcheck the validity of the crosschain message and assigns each job to theOAppconfiguredDVNs(Decentralized Verifier Networks) andExecutorto execute the crosschain message. -
The
DVNsverify 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 theEndpointon the destination chain. -
After the message has been inserted in the Endpoint’s message channel, the
ExecutorcallsEndpoint.lzReceiveto trigger the execution of the crosschain message on the destination chain. -
The
Endpointcalls the payableReceiverOApp.lzReceiveto pass the message and execute the internal receive logic. You can modify the internal execution logic insideReceiverOApp._lzReceiveto trigger any intended outcome from the crosschain message.
Send Overview
TheOApp calls EndpointV2.send to send the crosschain message and pays a fee to each configured DVN and Executor.
EndpointV2.sol
Inside thesend call:
-
emit event to each
DVNandExecutoraccording to theOAppsend configuration for the crosschain message. Also calculate and record the fee that should be paid to eachDVNandExecutor. -
check whether the fees the user is willing to pay can cover the fees required by the
DVNsandExecutor. -
transfer fee to
_sendLibrary(which records fee allocation).
_send call:
-
get the
nonceof this packet according to the path: [sender, destination chain, receiver]. -
generate
guidof the packet (global unique identifier). -
get the
_sendLibraryof the OApp (OApp can set their specific send library of each destination chain). -
call
_sendLibraryto emit events to notifyExecutorandDVNs, also calculate and record thefeethat should be paid to each.
guid is generated using the following parameters:
SendUln302.sol
Next, the message is handled by theOApp selected Send Library. For example, SendUln302.send:
-
pay workers (
DVNsandExecutor) and treasury. In the send process, thefeeis not directly paid to the workers, but recorded in the send library (SendUln302.sol) for workers to claim later. -
call
DVNsandExecutor’s contract to emit event to notify them to send crosschain message.
SendUln302._payWorkers, the contract:
-
splits options to get
executorOptions(Executor) andvalidationOptions(DVN). -
get the
OAppsetExecutorand correspondingmaxMessageSize(If not set, then a defaultmaxMessageSizeof 10000 bytes is used), and checks that the size of the message to send is less than than the max. -
calls
_payExecutorto assign job to correspondingExecutorand record the fee paid. -
calls
_payVerifierto assign job to specifiedDVNsand record fee paid.
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.
SendUln302._payVerifier:
-
calculate
payloadHashandpayload, which will be used to emit event to notifyDVNto send the crosschain message.-
payloadHashis a digest including information about the version and path of the crosschain message; -
payloadincludes information of theguidand the body of the crosschain message.
-
-
get the sender
OAppconfig about whichDVNsto use. -
assign job for each
DVN, including both required and optional.
SendUln302._assignJobs:
-
call each required and optional
DVNto notify them to verify the crosschain message on the destination chain. -
update each
DVN’s fee. -
return the
totalFeeused by allDVNs.
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.
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.
Send Limitations
Max Message Bytes Size
ThemaxMessageSize 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 theExecutorFeeLib._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:
Verification Workflow
After the crosschain message has been sent on the source chain (event has been emitted to notifyDVNs 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.
Commit Verification
After theOApp’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.
_verifyAndReclaimStorage verifies that the required and optional DVNs have submitted witness.
Insert Hash to Endpoint’s Message Channel
Inside theverify:
-
check
msg.senderis validReceiveLibraryconfigured by theOApp. -
get the
lazyNonceof the OApp. -
check the crosschain message path is valid for the
receiver. -
check the message represented by the
noncehas 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.
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.
_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).
_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 theEndpointwill clear the payload hash of the nonce after successful execution), it cannot be executed again.
_inbound inserts the message into the channel (inboundPayloadHash).
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.lzReceiveto execute the message.
_clearPayload:
-
update the
lazyInboundNonce. -
verify payload provided by
Executor. - delete message in the channel to prevent double execution.
OApp Execution
By default, theOApp standard inherits OAppReceiver which implements lzReceive called by Endpoint to execute message.
-
check
msg.senderisEndpoint. - check the path is valid.
-
call internal
_lzReceiveto execute logic (developer should override to add specific use).
_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.
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).