Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/oapp-solana/contracts/MyOApp.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ contract MyOApp is OApp, OAppOptionsType3 {
string calldata _string,
bytes calldata _options
) external payable returns (MessagingReceipt memory receipt) {
bytes memory _payload = abi.encodePacked(abi.encode(uint256(bytes(_string).length)), bytes(_string));
bytes memory _message = abi.encodePacked(abi.encode(uint256(bytes(_string).length)), bytes(_string));
bytes memory options = combineOptions(_dstEid, StringMsgCodec.VANILLA_TYPE, _options);
receipt = _lzSend(_dstEid, _payload, options, MessagingFee(msg.value, 0), payable(msg.sender));
receipt = _lzSend(_dstEid, _message, options, MessagingFee(msg.value, 0), payable(msg.sender));
}

/**
Expand Down
27 changes: 13 additions & 14 deletions examples/oapp-solana/docs/compose-implementation.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,6 @@ Handle compose_msg in `lz_receive`:
pub use lz_receive_types::*;
pub use quote_send::*;
pub use set_peer_config::*;
pub use set_peer_config::*;
```

### Modify `programs/my_oapp/src/instructions/quote_send.rs` to take in `compose_msg`
Expand Down Expand Up @@ -324,13 +323,13 @@ and appended it to the payload:
```solidity
function send(
uint32 _dstEid,
string calldata _message,
string calldata _string,
+ bytes calldata _composeMsg,
bytes calldata _options
) external payable returns (MessagingReceipt memory receipt) {
bytes memory _payload = abi.encodePacked(
abi.encode(uint256(bytes(_message).length)),
bytes(_message),
bytes memory _message = abi.encodePacked(
abi.encode(uint256(bytes(_string).length)),
bytes(_string),
+ _composeMsg
);
+ uint8 msgType = _composeMsg.length > 0 ? StringMsgCodec.COMPOSED_TYPE : StringMsgCodec.VANILLA_TYPE;
Expand All @@ -339,18 +338,17 @@ and appended it to the payload:
// ...
function quote(
uint32 _dstEid,
string calldata _message,
string calldata _string,
+ bytes calldata _composeMsg,
bytes calldata _options,
bool _payInLzToken
) public view returns (MessagingFee memory fee) {
bytes memory payload = abi.encodePacked(
abi.encode(uint256(bytes(_message).length)),
bytes(_message),
bytes(_message)
bytes memory _message = abi.encodePacked(
abi.encode(uint256(bytes(_string).length)),
bytes(_string),
+ _composeMsg
);
+ uint8 msgType = _composeMsg.length > 0 ? StringMsgCodec.COMPOSED_TYPE : StringMsgCodec.VANILLA_TYPE;
+ uint8 msgType = _composeMsg.length > 0 ? StringMsgCodec.COMPOSED_TYPE : StringMsgCodec.VANILLA_TYPE;
- bytes memory options = combineOptions(_dstEid, StringMsgCodec.VANILLA_TYPE, _options);
+ bytes memory options = combineOptions(_dstEid, msgType, _options);
// ...
Expand All @@ -361,10 +359,11 @@ and appended it to the payload:
address /*_executor*/,
bytes calldata /*_extraData*/
) internal override {
_ (string memory stringValue) = StringMsgCodec.decode(payload);
+ (string memory stringValue, ) = StringMsgCodec.decode(payload); // TODO: use the last value from .decode()
_ (string memory stringValue) = StringMsgCodec.decode(payload);
+ (string memory stringValue, bytes memory composeMsg) = StringMsgCodec.decode(payload);
data = stringValue;
+ // TODO: process _composeMsg
+ // if necessary, encode the composeMsg further
+ endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ pub struct InitStore<'info> {
init,
payer = payer,
space = Store::SIZE,
seeds = [STORE_SEED],
seeds = [STORE_SEED], // You can namespace this further if your program manages multiple stores.
// e.g. If there can be a store for each user, you can use something like:
// seeds = [STORE_SEED, &user.key().as_ref()]
bump
)]
pub store: Account<'info, Store>,
Expand All @@ -33,12 +35,16 @@ impl InitStore<'_> {
ctx.accounts.store.admin = params.admin;
ctx.accounts.store.bump = ctx.bumps.store;
ctx.accounts.store.endpoint_program = params.endpoint;
ctx.accounts.store.string = "Nothing received yet.".to_string();

ctx.accounts.lz_receive_types_accounts.store = ctx.accounts.store.key();
// the above lines are required for all OApp implementations

// the line below is specific to this string-passing example
ctx.accounts.store.string = "Nothing received yet.".to_string();

// calling endpoint cpi
// Prepare the delegate address for the OApp registration.
let register_params = RegisterOAppParams { delegate: ctx.accounts.store.admin };

// The Store PDA 'signs' CPI to the Endpoint program to register the OApp.
let seeds: &[&[u8]] = &[STORE_SEED, &[ctx.accounts.store.bump]];
oapp::endpoint_cpi::register_oapp(
ENDPOINT_ID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,12 @@ use oapp::{
#[derive(Accounts)]
#[instruction(params: LzReceiveParams)]
pub struct LzReceive<'info> {
/// OApp Store PDA. This account represents the "address" of your OApp on
/// Solana and can contain any state relevant to your application.
/// Customize the fields in `Store` as needed.
#[account(mut, seeds = [STORE_SEED], bump = store.bump)]
pub store: Account<'info, Store>,
/// Peer config PDA for the sending chain. Ensures `params.sender` can only be the allowed peer from that remote chain.
#[account(
seeds = [PEER_SEED, &store.key().to_bytes(), &params.src_eid.to_be_bytes()],
bump = peer.bump,
Expand All @@ -24,10 +28,15 @@ pub struct LzReceive<'info> {

impl LzReceive<'_> {
pub fn apply(ctx: &mut Context<LzReceive>, params: &LzReceiveParams) -> Result<()> {
let seeds: &[&[u8]] =
&[STORE_SEED, &[ctx.accounts.store.bump]];
// the first 9 accounts are for clear()
// The OApp Store PDA is used to sign the CPI to the Endpoint program.
let seeds: &[&[u8]] = &[STORE_SEED, &[ctx.accounts.store.bump]];

// The first Clear::MIN_ACCOUNTS_LEN accounts were returned by
// `lz_receive_types` and are required for Endpoint::clear
let accounts_for_clear = &ctx.remaining_accounts[0..Clear::MIN_ACCOUNTS_LEN];
// Call the Endpoint::clear CPI to clear the message from the Endpoint program.
// This is necessary to ensure the message is processed only once and to
// prevent replays.
let _ = oapp::endpoint_cpi::clear(
ENDPOINT_ID,
ctx.accounts.store.key(),
Expand All @@ -43,6 +52,7 @@ impl LzReceive<'_> {
},
)?;

// From here on, you can process the message as needed by your use case.
let string_value = msg_codec::decode(&params.message)?;
let store = &mut ctx.accounts.store;
store.string = string_value;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,42 @@ use crate::*;
use oapp::endpoint_cpi::{get_accounts_for_clear, LzAccount};
use oapp::{endpoint::ID as ENDPOINT_ID, LzReceiveParams};

/// LzReceiveTypes instruction provides a list of accounts that are used in the LzReceive
/// instruction. The list of accounts required by this LzReceiveTypes instruction can be found
/// from the specific PDA account that is generated by the LZ_RECEIVE_TYPES_SEED.
/// `lz_receive_types` is queried off-chain by the Executor before calling
/// `lz_receive`. It must return **every** account that will be touched by the
/// actual `lz_receive` instruction as well as the accounts required by
/// `Endpoint::clear`.
///
/// The return order must match exactly what `lz_receive` expects or the
/// cross-program invocation will fail.
#[derive(Accounts)]
pub struct LzReceiveTypes<'info> {
#[account(seeds = [STORE_SEED], bump = store.bump)]
pub store: Account<'info, Store>,
}

impl LzReceiveTypes<'_> {
/// The list of accounts should follow the rules below:
/// 1. Include all the accounts that are used in the LzReceive instruction, including the
/// accounts that are used by the Endpoint program.
/// 2. Set the account is a signer with ZERO address if the LzReceive instruction needs a payer
/// to pay fee, like rent.
/// 3. Set the account is writable if the LzReceive instruction needs to modify the account.
pub fn apply(
ctx: &Context<LzReceiveTypes>,
params: &LzReceiveParams,
) -> Result<Vec<LzAccount>> {
// There are two accounts that are used in the LzReceive instruction,
// except those accounts for endpoint program.
// The first account is the store account, that is the fixed one.
// 1. The store PDA is always the first account and is mutable. If your
// program derives the store PDA with additional seeds, ensure the same
// seeds are used when providing the store account.
let store = ctx.accounts.store.key();

// The second account is the peer account, we find it by the params.src_eid.
// 2. The peer PDA for the remote chain needs to be retrieved, for later verification of the `params.sender`.
let peer_seeds = [PEER_SEED, &store.to_bytes(), &params.src_eid.to_be_bytes()];
let (peer, _) = Pubkey::find_program_address(&peer_seeds, ctx.program_id);

// Accounts used directly by `lz_receive`
let mut accounts = vec![
// count
// store (mutable)
LzAccount { pubkey: store, is_signer: false, is_writable: true },
// peer
// peer (read-only)
LzAccount { pubkey: peer, is_signer: false, is_writable: false }
];

// append the accounts for the clear ix
// Append the additional accounts required for `Endpoint::clear`
let accounts_for_clear = get_accounts_for_clear(
ENDPOINT_ID,
&store,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,13 @@ pub struct QuoteSend<'info> {
#[account(seeds = [ENDPOINT_SEED], bump = endpoint.bump, seeds::program = ENDPOINT_ID)]
pub endpoint: Account<'info, EndpointSettings>,
}

impl<'info> QuoteSend<'info> {
pub fn apply(ctx: &Context<QuoteSend>, params: &QuoteSendParams) -> Result<MessagingFee> {
// Encode the payload for quoting
let message = msg_codec::encode(&params.message);

// calling endpoint cpi
// Ask the Endpoint how much a send would cost
let quote_params = QuoteParams {
sender: ctx.accounts.store.key(),
dst_eid: params.dst_eid,
Expand Down
10 changes: 8 additions & 2 deletions examples/oapp-solana/programs/my_oapp/src/instructions/send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,23 @@ pub struct Send<'info> {
],
bump = peer.bump
)]
/// Configuration for the destination chain. Holds the peer address and any
/// enforced messaging options.
pub peer: Account<'info, PeerConfig>,
#[account(seeds = [STORE_SEED], bump = store.bump)]
/// OApp Store PDA that signs the send instruction
pub store: Account<'info, Store>,
#[account(seeds = [ENDPOINT_SEED], bump = endpoint.bump, seeds::program = ENDPOINT_ID)]
pub endpoint: Account<'info, EndpointSettings>,
}
impl<'info> Send<'info> {
pub fn apply(ctx: &mut Context<Send>, params: &SendMessageParams) -> Result<()> {
// Serialize the message according to our codec
let message = msg_codec::encode(&params.message);
// Prepare the seeds for the OApp Store PDA, which is used to sign the CPI call to the Endpoint program.
let seeds: &[&[u8]] = &[STORE_SEED, &[ctx.accounts.store.bump]];

// calling endpoint cpi
// Prepare the SendParams for the Endpoint::send CPI call.
let send_params = SendParams {
dst_eid: params.dst_eid,
receiver: ctx.accounts.peer.peer_address,
Expand All @@ -39,9 +44,10 @@ impl<'info> Send<'info> {
native_fee: params.native_fee,
lz_token_fee: params.lz_token_fee,
};
// Call the Endpoint::send CPI to send the message.
oapp::endpoint_cpi::send(
ENDPOINT_ID,
ctx.accounts.store.key(),
ctx.accounts.store.key(), // payer/signer derived from seeds
ctx.remaining_accounts,
seeds,
send_params,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
use crate::*;
use anchor_lang::prelude::*;

// PeerConfig PDAs are used to store configuration for each remote chain
// For each remote chain, a PeerConfig PDA is created with the remote EID as part of the seed
// The PDA holds the peer address and any enforced options for messaging

#[derive(Accounts)]
#[instruction(params: SetPeerConfigParams)]
pub struct SetPeerConfig<'info> {
#[account(mut, address = store.admin)]
/// Admin of the OApp store
pub admin: Signer<'info>,
#[account(
init_if_needed,
Expand All @@ -13,14 +18,17 @@ pub struct SetPeerConfig<'info> {
seeds = [PEER_SEED, &store.key().to_bytes(), &params.remote_eid.to_be_bytes()],
bump
)]
/// Peer configuration PDA for a specific remote chain
pub peer: Account<'info, PeerConfig>,
#[account(seeds = [STORE_SEED], bump = store.bump)]
/// Store PDA of this OApp
pub store: Account<'info, Store>,
pub system_program: Program<'info, System>,
}

impl SetPeerConfig<'_> {
pub fn apply(ctx: &mut Context<SetPeerConfig>, params: &SetPeerConfigParams) -> Result<()> {
// Update or create the peer config PDA
match params.config.clone() {
PeerConfigParam::PeerAddress(peer_address) => {
ctx.accounts.peer.peer_address = peer_address;
Expand All @@ -32,6 +40,7 @@ impl SetPeerConfig<'_> {
ctx.accounts.peer.enforced_options.send_and_call = send_and_call;
},
}
// Store the PDA bump for later validation
ctx.accounts.peer.bump = ctx.bumps.peer;
Ok(())
}
Expand All @@ -46,5 +55,6 @@ pub struct SetPeerConfigParams {
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
pub enum PeerConfigParam {
PeerAddress([u8; 32]),
/// Optionally enforce specific send options for this peer
EnforcedOptions { send: Vec<u8>, send_and_call: Vec<u8> },
}
21 changes: 15 additions & 6 deletions examples/oapp-solana/programs/my_oapp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,31 @@ use oapp::{endpoint::MessagingFee, endpoint_cpi::LzAccount, LzReceiveParams};
use solana_helper::program_id_from_env;
use state::*;

// to build in verifiable mode and using environment variable (what the README instructs), run:
// anchor build -v -e MYOAPP_ID=<OAPP_PROGRAM_ID>
// to build in normal mode and using environment, run:
// MYOAPP_ID=$PROGRAM_ID anchor build
declare_id!(anchor_lang::solana_program::pubkey::Pubkey::new_from_array(program_id_from_env!(
"MYOAPP_ID",
"41NCdrEvXhQ4mZgyJkmqYxL6A1uEmnraGj31UJ6PsXd3"
"41NCdrEvXhQ4mZgyJkmqYxL6A1uEmnraGj31UJ6PsXd3" // It's not necessary to change the ID here if you are building using environment variable
)));

const LZ_RECEIVE_TYPES_SEED: &[u8] = b"LzReceiveTypes";
const STORE_SEED: &[u8] = b"Store";
const PEER_SEED: &[u8] = b"Peer";
const LZ_RECEIVE_TYPES_SEED: &[u8] = b"LzReceiveTypes"; // The Executor relies on this exact seed to derive the LzReceiveTypes PDA. Keep it the same.
const STORE_SEED: &[u8] = b"Store"; // You are free to edit this seed.
const PEER_SEED: &[u8] = b"Peer"; // The Executor relies on this exact seed to derive the LzReceiveTypes PDA. Keep it the same.

#[program]
pub mod my_oapp {
use super::*;

// ============================== Initializers ==============================
// In this example, init_store can be called by anyone and can be called only once. Ensure you implement your own access control logic if needed.
pub fn init_store(mut ctx: Context<InitStore>, params: InitStoreParams) -> Result<()> {
InitStore::apply(&mut ctx, &params)
}

// ============================== Admin ==============================

// admin instruction to set or update cross-chain peer configuration parameters.
pub fn set_peer_config(
mut ctx: Context<SetPeerConfig>,
params: SetPeerConfigParams,
Expand All @@ -36,19 +42,22 @@ pub mod my_oapp {
}

// ============================== Public ==============================

// public instruction returning the estimated MessagingFee for sending a message.
pub fn quote_send(ctx: Context<QuoteSend>, params: QuoteSendParams) -> Result<MessagingFee> {
QuoteSend::apply(&ctx, &params)
}

// public instruction to send a message to a cross-chain peer.
pub fn send(mut ctx: Context<Send>, params: SendMessageParams) -> Result<()> {
Send::apply(&mut ctx, &params)
}

// handler for processing incoming cross-chain messages and executing the LzReceive logic
pub fn lz_receive(mut ctx: Context<LzReceive>, params: LzReceiveParams) -> Result<()> {
LzReceive::apply(&mut ctx, &params)
}

// handler that returns the list of accounts required to execute lz_receive
pub fn lz_receive_types(
ctx: Context<LzReceiveTypes>,
params: LzReceiveParams,
Expand Down
Loading