-
Notifications
You must be signed in to change notification settings - Fork 280
[INT-295] Feature Move Oapp example move vm decoding #1585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
079695f
6d69496
2bcd718
fea0b63
99d5161
7cf5134
b20792d
c3a6dfb
a101b83
1afd8fb
e45c4d6
2eb12f8
d1ae147
51efec8
d584ae1
40527e3
9d32c14
599dc1e
08b767c
0455dd6
5a33bf4
c07d128
e3c2b7d
f5f2ce7
7a3a2a0
b9a90a5
3aba4bc
379fafc
f01ca5e
4069f91
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| --- | ||
| "@layerzerolabs/oft-adapter-aptos-move-example": patch | ||
| "@layerzerolabs/mint-burn-oft-adapter-example": patch | ||
| "@layerzerolabs/native-oft-adapter-example": patch | ||
| "@layerzerolabs/oft-adapter-initia-example": patch | ||
| "@layerzerolabs/oapp-aptos-example": patch | ||
| "@layerzerolabs/oft-hyperliquid-example": patch | ||
| "@layerzerolabs/oft-upgradeable-example": patch | ||
| "@layerzerolabs/oft-aptos-move-example": patch | ||
| "@layerzerolabs/onft721-zksync-example": patch | ||
| "@layerzerolabs/oft-adapter-example": patch | ||
| "@layerzerolabs/oft-initia-example": patch | ||
| "@layerzerolabs/oft-solana-example": patch | ||
| "@layerzerolabs/omni-call-example": patch | ||
| "@layerzerolabs/oft-alt-example": patch | ||
| "@layerzerolabs/onft721-example": patch | ||
| "@layerzerolabs/oapp-example": patch | ||
| "@layerzerolabs/oft-example": patch | ||
| --- | ||
|
|
||
| Updating license from UNLICENSED to MIT. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| "@layerzerolabs/oapp-aptos-example": patch | ||
| --- | ||
|
|
||
| Adding param encoding and decoding to Move OApp example. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,34 +1,31 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.22; | ||
|
|
||
| import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||
| import { OApp, MessagingFee, Origin } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; | ||
| import { MessagingReceipt } from "@layerzerolabs/oapp-evm/contracts/oapp/OAppSender.sol"; | ||
| import { OApp, Origin, MessagingFee } from "@layerzerolabs/oapp-evm/contracts/oapp/OApp.sol"; | ||
| import { OAppOptionsType3 } from "@layerzerolabs/oapp-evm/contracts/oapp/libs/OAppOptionsType3.sol"; | ||
| import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
|
||
| contract MyOApp is OApp, OAppOptionsType3 { | ||
| constructor(address _endpoint, address _delegate) OApp(_endpoint, _delegate) Ownable(_delegate) {} | ||
| /// @notice Last string received from any remote chain | ||
| address public address1; | ||
| address public address2; | ||
| uint256 public num; | ||
|
|
||
| string public data = "Nothing received yet."; | ||
| uint256 public counter = 0; | ||
| /// @notice The only Message Type in use for this OApp: sending an arbitrary string. | ||
| /// Different message types can be assigned different enforced options per destination endpoint ID. | ||
| uint16 public constant SEND = 1; | ||
|
|
||
| /** | ||
| * @notice Sends a message from the source chain to a destination chain. | ||
| * @param _dstEid The endpoint ID of the destination chain. | ||
| * @param _message The message string to be sent. | ||
| * @param _options Additional options for message execution. | ||
| * @dev Encodes the message as bytes and sends it using the `_lzSend` internal function. | ||
| * @return receipt A `MessagingReceipt` struct containing details of the message sent. | ||
| */ | ||
| function send( | ||
| uint32 _dstEid, | ||
| string memory _message, | ||
| bytes calldata _options | ||
| ) external payable returns (MessagingReceipt memory receipt) { | ||
| bytes memory _payload = abi.encode(_message); | ||
| receipt = _lzSend(_dstEid, _payload, _options, MessagingFee(msg.value, 0), payable(msg.sender)); | ||
| } | ||
| /// @notice Initialize with Endpoint V2 and owner address | ||
| /// @param _endpoint The local chain's LayerZero Endpoint V2 address | ||
| /// @param _owner The address permitted to configure this OApp | ||
| constructor(address _endpoint, address _owner) OApp(_endpoint, _owner) Ownable(_owner) {} | ||
|
|
||
| // ────────────────────────────────────────────────────────────────────────────── | ||
| // 0. (Optional) Quote business logic | ||
| // | ||
| // Example: Get a quote from the Endpoint for a cost estimate of sending a message. | ||
| // Replace this to mirror your own send business logic. | ||
| // ────────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| /** | ||
| * @notice Quotes the gas needed to pay for the full omnichain transaction in native gas or ZRO token. | ||
|
|
@@ -38,35 +35,87 @@ contract MyOApp is OApp, OAppOptionsType3 { | |
| * @param _payInLzToken Whether to return fee in ZRO token. | ||
| * @return fee A `MessagingFee` struct containing the calculated gas fee in either the native token or ZRO token. | ||
| */ | ||
| function quote( | ||
| function quoteSend( | ||
| uint32 _dstEid, | ||
| string memory _message, | ||
| bytes memory _options, | ||
| string calldata _message, | ||
| bytes calldata _options, | ||
| bool _payInLzToken | ||
| ) public view returns (MessagingFee memory fee) { | ||
| bytes memory payload = abi.encode(_message); | ||
| fee = _quote(_dstEid, payload, _options, _payInLzToken); | ||
| bytes memory _message = abi.encode(_message); | ||
| // combineOptions (from OAppOptionsType3) merges enforced options set by the contract owner | ||
| // with any additional execution options provided by the caller | ||
| fee = _quote(_dstEid, _message, combineOptions(_dstEid, SEND, _options), _payInLzToken); | ||
| } | ||
|
|
||
| /** | ||
| * @dev Internal function override to handle incoming messages from another chain. | ||
| * @dev _origin A struct containing information about the message sender. | ||
| * @dev _guid A unique global packet identifier for the message. | ||
| * @dev payload The encoded message payload being received. | ||
| * | ||
| * @dev The following params are unused in the current implementation of the OApp. | ||
| * @dev _executor The address of the Executor responsible for processing the message. | ||
| * @dev _extraData Arbitrary data appended by the Executor to the message. | ||
| * | ||
| * Increments the counter. | ||
| */ | ||
| // ────────────────────────────────────────────────────────────────────────────── | ||
| // 1. Send business logic | ||
| // | ||
| // Example: send a simple string to a remote chain. Replace this with your | ||
| // own state-update logic, then encode whatever data your application needs. | ||
| // ────────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| /// @notice Send a string to a remote OApp on another chain | ||
| /// @param _dstEid Destination Endpoint ID (uint32) | ||
| /// @param _message The string to send | ||
| /// @param _options Execution options for gas on the destination (bytes) | ||
| function send(uint32 _dstEid, string calldata _message, bytes calldata _options) external payable { | ||
| // 1. (Optional) Update any local state here. | ||
| // e.g., record that a message was "sent": | ||
| // sentCount += 1; | ||
|
|
||
| // 2. Encode any data structures you wish to send into bytes | ||
| // You can use abi.encode, abi.encodePacked, or directly splice bytes | ||
| // if you know the format of your data structures | ||
| bytes memory _message = abi.encode(_message); | ||
|
|
||
| // 3. Call OAppSender._lzSend to package and dispatch the cross-chain message | ||
| // - _dstEid: remote chain's Endpoint ID | ||
| // - _message: ABI-encoded string | ||
| // - _options: combined execution options (enforced + caller-provided) | ||
| // - MessagingFee(msg.value, 0): pay all gas as native token; no ZRO | ||
| // - payable(msg.sender): refund excess gas to caller | ||
| // | ||
| // combineOptions (from OAppOptionsType3) merges enforced options set by the contract owner | ||
| // with any additional execution options provided by the caller | ||
| _lzSend( | ||
| _dstEid, | ||
| _message, | ||
| combineOptions(_dstEid, SEND, _options), | ||
| MessagingFee(msg.value, 0), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is an anti pattern to force users to use native gas for paying fee and hardcoding it in the code. This makes it impossible in future to pay with ZRO token, I don't think we want to encourage users to follow this pattern and disable it in their OApps they are building on top of this example. Instead, send method should accept MessagingFee struct, like OFTCore: https://github.com/LayerZero-Labs/devtools/blob/main/packages/oft-evm/contracts/OFTCore.sol#L177
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is taken directly from the EVM OApp example (that devrel wrote) I'm just updating these files with their implementation. If you want me to change it across all examples I can do that, but it should be a separate PR. |
||
| payable(msg.sender) | ||
| ); | ||
| } | ||
|
|
||
| // ────────────────────────────────────────────────────────────────────────────── | ||
| // 2. Receive business logic | ||
| // | ||
| // Override _lzReceive to decode the incoming bytes and apply your logic. | ||
| // The base OAppReceiver.lzReceive ensures: | ||
| // • Only the LayerZero Endpoint can call this method | ||
| // • The sender is a registered peer (peers[srcEid] == origin.sender) | ||
| // ────────────────────────────────────────────────────────────────────────────── | ||
|
|
||
| /// @notice Invoked by OAppReceiver when EndpointV2.lzReceive is called | ||
| /// @dev _origin Metadata (source chain, sender address, nonce) | ||
| /// @dev _guid Global unique ID for tracking this message | ||
| /// @param _message ABI-encoded bytes (the string we sent earlier) | ||
| /// @dev _executor Executor address that delivered the message | ||
| /// @dev _extraData Additional data from the Executor (unused by the LayerZero executor) | ||
| function _lzReceive( | ||
| Origin calldata /*_origin*/, | ||
| bytes32 /*_guid*/, | ||
| bytes calldata /*payload*/, | ||
| bytes calldata _message, | ||
| address /*_executor*/, | ||
| bytes calldata /*_extraData*/ | ||
| ) internal override { | ||
| counter += 1; | ||
| // 1. Decode the incoming bytes into a string | ||
| // You can use abi.decode, abi.decodePacked, or directly splice bytes | ||
| // if you know the format of your data structures | ||
| (address1, address2, num) = abi.decode(_message, (address, address, uint256)); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. does it work if you are currently encoding a "string" on send: and on receive you are decoding "address, address, uint"?: I did a Foundry small test and I think if you currently do abi.encode offchain, pass it onchain and then do abi.encode again in Solidity before sending it results in wrong data: result: Expected: Could you create a test case in Foundry for sending messages from EVM to EVM using this OApp Solidity code and double check if the encoding and decoding logic is consistent and works?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sounds good. Putting this on pause until sla-tracker work is finished. |
||
|
|
||
| // 2. Apply your custom logic. In this example, store it in `lastMessage`. | ||
| // 3. (Optional) Trigger further on-chain actions. | ||
| // e.g., emit an event, mint tokens, call another contract, etc. | ||
| // emit MessageReceived(_origin.srcEid, _message); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| import { Aptos, AptosConfig, Network } from '@aptos-labs/ts-sdk' | ||
|
|
||
| /** | ||
| * A utility script to verify cross-chain message delivery by checking the last received values. | ||
| * The values are updated each time a message is successfully received by the OApp, | ||
| * providing a simple way to confirm that cross-chain communication is working as expected. | ||
| */ | ||
| async function main() { | ||
| const config = new AptosConfig({ network: Network.TESTNET }) | ||
| const aptos = new Aptos(config) | ||
| const oappAddress = '<your-oapp-address>' | ||
|
|
||
| console.log('Fetching receive values...') | ||
| console.log('---') | ||
|
|
||
| const [counterResult, address1Result, address2Result, numberResult, rawMessageResult] = await Promise.all([ | ||
| aptos.view({ | ||
| payload: { | ||
| function: `${oappAddress}::oapp::get_counter_value`, | ||
| typeArguments: [], | ||
| }, | ||
| }), | ||
| aptos.view({ | ||
| payload: { | ||
| function: `${oappAddress}::oapp::get_decoded_address1`, | ||
| typeArguments: [], | ||
| }, | ||
| }), | ||
| aptos.view({ | ||
| payload: { | ||
| function: `${oappAddress}::oapp::get_decoded_address2`, | ||
| typeArguments: [], | ||
| }, | ||
| }), | ||
| aptos.view({ | ||
| payload: { | ||
| function: `${oappAddress}::oapp::get_decoded_number`, | ||
| typeArguments: [], | ||
| }, | ||
| }), | ||
| aptos.view({ | ||
| payload: { | ||
| function: `${oappAddress}::oapp::get_raw_message`, | ||
| typeArguments: [], | ||
| }, | ||
| }), | ||
| ]) | ||
|
|
||
| console.log('- Counter:', counterResult[0]) | ||
| console.log('- Address 1:', address1Result[0]) | ||
| console.log('- Address 2:', address2Result[0]) | ||
| console.log('- Number:', numberResult[0]) | ||
| console.log('- Raw Message:', rawMessageResult[0]) | ||
| console.log('---') | ||
| } | ||
|
|
||
| main().catch(console.error) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this comment seems inaccurate
it mentions "last string received"
while below there are three variables: address, address, uint256 and none of them is string
maybe better to change comment to "last data received"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is taken directly from the EVM OApp example (that devrel wrote) I'm just updating these files with their implementation. If you want me to change it across all examples I can do that, but it should be a separate PR.