Skip to content

Commit 6ce61d1

Browse files
authored
add comments to oapp-solana (#1578)
1 parent aa21531 commit 6ce61d1

11 files changed

Lines changed: 125 additions & 62 deletions

File tree

examples/oapp-solana/contracts/MyOApp.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,9 @@ contract MyOApp is OApp, OAppOptionsType3 {
2828
string calldata _string,
2929
bytes calldata _options
3030
) external payable returns (MessagingReceipt memory receipt) {
31-
bytes memory _payload = abi.encodePacked(abi.encode(uint256(bytes(_string).length)), bytes(_string));
31+
bytes memory _message = abi.encodePacked(abi.encode(uint256(bytes(_string).length)), bytes(_string));
3232
bytes memory options = combineOptions(_dstEid, StringMsgCodec.VANILLA_TYPE, _options);
33-
receipt = _lzSend(_dstEid, _payload, options, MessagingFee(msg.value, 0), payable(msg.sender));
33+
receipt = _lzSend(_dstEid, _message, options, MessagingFee(msg.value, 0), payable(msg.sender));
3434
}
3535

3636
/**

examples/oapp-solana/docs/compose-implementation.md

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ Handle compose_msg in `lz_receive`:
117117
pub use lz_receive_types::*;
118118
pub use quote_send::*;
119119
pub use set_peer_config::*;
120-
pub use set_peer_config::*;
121120
```
122121

123122
### Modify `programs/my_oapp/src/instructions/quote_send.rs` to take in `compose_msg`
@@ -324,13 +323,13 @@ and appended it to the payload:
324323
```solidity
325324
function send(
326325
uint32 _dstEid,
327-
string calldata _message,
326+
string calldata _string,
328327
+ bytes calldata _composeMsg,
329328
bytes calldata _options
330329
) external payable returns (MessagingReceipt memory receipt) {
331-
bytes memory _payload = abi.encodePacked(
332-
abi.encode(uint256(bytes(_message).length)),
333-
bytes(_message),
330+
bytes memory _message = abi.encodePacked(
331+
abi.encode(uint256(bytes(_string).length)),
332+
bytes(_string),
334333
+ _composeMsg
335334
);
336335
+ uint8 msgType = _composeMsg.length > 0 ? StringMsgCodec.COMPOSED_TYPE : StringMsgCodec.VANILLA_TYPE;
@@ -339,18 +338,17 @@ and appended it to the payload:
339338
// ...
340339
function quote(
341340
uint32 _dstEid,
342-
string calldata _message,
341+
string calldata _string,
343342
+ bytes calldata _composeMsg,
344343
bytes calldata _options,
345344
bool _payInLzToken
346345
) public view returns (MessagingFee memory fee) {
347-
bytes memory payload = abi.encodePacked(
348-
abi.encode(uint256(bytes(_message).length)),
349-
bytes(_message),
350-
bytes(_message)
346+
bytes memory _message = abi.encodePacked(
347+
abi.encode(uint256(bytes(_string).length)),
348+
bytes(_string),
351349
+ _composeMsg
352350
);
353-
+ uint8 msgType = _composeMsg.length > 0 ? StringMsgCodec.COMPOSED_TYPE : StringMsgCodec.VANILLA_TYPE;
351+
+ uint8 msgType = _composeMsg.length > 0 ? StringMsgCodec.COMPOSED_TYPE : StringMsgCodec.VANILLA_TYPE;
354352
- bytes memory options = combineOptions(_dstEid, StringMsgCodec.VANILLA_TYPE, _options);
355353
+ bytes memory options = combineOptions(_dstEid, msgType, _options);
356354
// ...
@@ -361,10 +359,11 @@ and appended it to the payload:
361359
address /*_executor*/,
362360
bytes calldata /*_extraData*/
363361
) internal override {
364-
_ (string memory stringValue) = StringMsgCodec.decode(payload);
365-
+ (string memory stringValue, ) = StringMsgCodec.decode(payload); // TODO: use the last value from .decode()
362+
_ (string memory stringValue) = StringMsgCodec.decode(payload);
363+
+ (string memory stringValue, bytes memory composeMsg) = StringMsgCodec.decode(payload);
366364
data = stringValue;
367-
+ // TODO: process _composeMsg
365+
+ // if necessary, encode the composeMsg further
366+
+ endpoint.sendCompose(toAddress, _guid, 0 /* the index of the composed message*/, composeMsg);
368367
}
369368
```
370369

examples/oapp-solana/programs/my_oapp/src/instructions/init_store.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ pub struct InitStore<'info> {
1010
init,
1111
payer = payer,
1212
space = Store::SIZE,
13-
seeds = [STORE_SEED],
13+
seeds = [STORE_SEED], // You can namespace this further if your program manages multiple stores.
14+
// e.g. If there can be a store for each user, you can use something like:
15+
// seeds = [STORE_SEED, &user.key().as_ref()]
1416
bump
1517
)]
1618
pub store: Account<'info, Store>,
@@ -33,12 +35,16 @@ impl InitStore<'_> {
3335
ctx.accounts.store.admin = params.admin;
3436
ctx.accounts.store.bump = ctx.bumps.store;
3537
ctx.accounts.store.endpoint_program = params.endpoint;
36-
ctx.accounts.store.string = "Nothing received yet.".to_string();
37-
3838
ctx.accounts.lz_receive_types_accounts.store = ctx.accounts.store.key();
39+
// the above lines are required for all OApp implementations
40+
41+
// the line below is specific to this string-passing example
42+
ctx.accounts.store.string = "Nothing received yet.".to_string();
3943

40-
// calling endpoint cpi
44+
// Prepare the delegate address for the OApp registration.
4145
let register_params = RegisterOAppParams { delegate: ctx.accounts.store.admin };
46+
47+
// The Store PDA 'signs' CPI to the Endpoint program to register the OApp.
4248
let seeds: &[&[u8]] = &[STORE_SEED, &[ctx.accounts.store.bump]];
4349
oapp::endpoint_cpi::register_oapp(
4450
ENDPOINT_ID,

examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive.rs

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ use oapp::{
1212
#[derive(Accounts)]
1313
#[instruction(params: LzReceiveParams)]
1414
pub struct LzReceive<'info> {
15+
/// OApp Store PDA. This account represents the "address" of your OApp on
16+
/// Solana and can contain any state relevant to your application.
17+
/// Customize the fields in `Store` as needed.
1518
#[account(mut, seeds = [STORE_SEED], bump = store.bump)]
1619
pub store: Account<'info, Store>,
20+
/// Peer config PDA for the sending chain. Ensures `params.sender` can only be the allowed peer from that remote chain.
1721
#[account(
1822
seeds = [PEER_SEED, &store.key().to_bytes(), &params.src_eid.to_be_bytes()],
1923
bump = peer.bump,
@@ -24,10 +28,15 @@ pub struct LzReceive<'info> {
2428

2529
impl LzReceive<'_> {
2630
pub fn apply(ctx: &mut Context<LzReceive>, params: &LzReceiveParams) -> Result<()> {
27-
let seeds: &[&[u8]] =
28-
&[STORE_SEED, &[ctx.accounts.store.bump]];
29-
// the first 9 accounts are for clear()
31+
// The OApp Store PDA is used to sign the CPI to the Endpoint program.
32+
let seeds: &[&[u8]] = &[STORE_SEED, &[ctx.accounts.store.bump]];
33+
34+
// The first Clear::MIN_ACCOUNTS_LEN accounts were returned by
35+
// `lz_receive_types` and are required for Endpoint::clear
3036
let accounts_for_clear = &ctx.remaining_accounts[0..Clear::MIN_ACCOUNTS_LEN];
37+
// Call the Endpoint::clear CPI to clear the message from the Endpoint program.
38+
// This is necessary to ensure the message is processed only once and to
39+
// prevent replays.
3140
let _ = oapp::endpoint_cpi::clear(
3241
ENDPOINT_ID,
3342
ctx.accounts.store.key(),
@@ -43,6 +52,7 @@ impl LzReceive<'_> {
4352
},
4453
)?;
4554

55+
// From here on, you can process the message as needed by your use case.
4656
let string_value = msg_codec::decode(&params.message)?;
4757
let store = &mut ctx.accounts.store;
4858
store.string = string_value;

examples/oapp-solana/programs/my_oapp/src/instructions/lz_receive_types.rs

Lines changed: 15 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,43 +2,42 @@ use crate::*;
22
use oapp::endpoint_cpi::{get_accounts_for_clear, LzAccount};
33
use oapp::{endpoint::ID as ENDPOINT_ID, LzReceiveParams};
44

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

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

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

32+
// Accounts used directly by `lz_receive`
3433
let mut accounts = vec![
35-
// count
34+
// store (mutable)
3635
LzAccount { pubkey: store, is_signer: false, is_writable: true },
37-
// peer
36+
// peer (read-only)
3837
LzAccount { pubkey: peer, is_signer: false, is_writable: false }
3938
];
4039

41-
// append the accounts for the clear ix
40+
// Append the additional accounts required for `Endpoint::clear`
4241
let accounts_for_clear = get_accounts_for_clear(
4342
ENDPOINT_ID,
4443
&store,

examples/oapp-solana/programs/my_oapp/src/instructions/quote_send.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ pub struct QuoteSend<'info> {
2222
#[account(seeds = [ENDPOINT_SEED], bump = endpoint.bump, seeds::program = ENDPOINT_ID)]
2323
pub endpoint: Account<'info, EndpointSettings>,
2424
}
25+
2526
impl<'info> QuoteSend<'info> {
2627
pub fn apply(ctx: &Context<QuoteSend>, params: &QuoteSendParams) -> Result<MessagingFee> {
28+
// Encode the payload for quoting
2729
let message = msg_codec::encode(&params.message);
2830

29-
// calling endpoint cpi
31+
// Ask the Endpoint how much a send would cost
3032
let quote_params = QuoteParams {
3133
sender: ctx.accounts.store.key(),
3234
dst_eid: params.dst_eid,

examples/oapp-solana/programs/my_oapp/src/instructions/send.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,23 @@ pub struct Send<'info> {
1515
],
1616
bump = peer.bump
1717
)]
18+
/// Configuration for the destination chain. Holds the peer address and any
19+
/// enforced messaging options.
1820
pub peer: Account<'info, PeerConfig>,
1921
#[account(seeds = [STORE_SEED], bump = store.bump)]
22+
/// OApp Store PDA that signs the send instruction
2023
pub store: Account<'info, Store>,
2124
#[account(seeds = [ENDPOINT_SEED], bump = endpoint.bump, seeds::program = ENDPOINT_ID)]
2225
pub endpoint: Account<'info, EndpointSettings>,
2326
}
2427
impl<'info> Send<'info> {
2528
pub fn apply(ctx: &mut Context<Send>, params: &SendMessageParams) -> Result<()> {
29+
// Serialize the message according to our codec
2630
let message = msg_codec::encode(&params.message);
31+
// Prepare the seeds for the OApp Store PDA, which is used to sign the CPI call to the Endpoint program.
2732
let seeds: &[&[u8]] = &[STORE_SEED, &[ctx.accounts.store.bump]];
2833

29-
// calling endpoint cpi
34+
// Prepare the SendParams for the Endpoint::send CPI call.
3035
let send_params = SendParams {
3136
dst_eid: params.dst_eid,
3237
receiver: ctx.accounts.peer.peer_address,
@@ -39,9 +44,10 @@ impl<'info> Send<'info> {
3944
native_fee: params.native_fee,
4045
lz_token_fee: params.lz_token_fee,
4146
};
47+
// Call the Endpoint::send CPI to send the message.
4248
oapp::endpoint_cpi::send(
4349
ENDPOINT_ID,
44-
ctx.accounts.store.key(),
50+
ctx.accounts.store.key(), // payer/signer derived from seeds
4551
ctx.remaining_accounts,
4652
seeds,
4753
send_params,

examples/oapp-solana/programs/my_oapp/src/instructions/set_peer_config.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
use crate::*;
22
use anchor_lang::prelude::*;
33

4+
// PeerConfig PDAs are used to store configuration for each remote chain
5+
// For each remote chain, a PeerConfig PDA is created with the remote EID as part of the seed
6+
// The PDA holds the peer address and any enforced options for messaging
7+
48
#[derive(Accounts)]
59
#[instruction(params: SetPeerConfigParams)]
610
pub struct SetPeerConfig<'info> {
711
#[account(mut, address = store.admin)]
12+
/// Admin of the OApp store
813
pub admin: Signer<'info>,
914
#[account(
1015
init_if_needed,
@@ -13,14 +18,17 @@ pub struct SetPeerConfig<'info> {
1318
seeds = [PEER_SEED, &store.key().to_bytes(), &params.remote_eid.to_be_bytes()],
1419
bump
1520
)]
21+
/// Peer configuration PDA for a specific remote chain
1622
pub peer: Account<'info, PeerConfig>,
1723
#[account(seeds = [STORE_SEED], bump = store.bump)]
24+
/// Store PDA of this OApp
1825
pub store: Account<'info, Store>,
1926
pub system_program: Program<'info, System>,
2027
}
2128

2229
impl SetPeerConfig<'_> {
2330
pub fn apply(ctx: &mut Context<SetPeerConfig>, params: &SetPeerConfigParams) -> Result<()> {
31+
// Update or create the peer config PDA
2432
match params.config.clone() {
2533
PeerConfigParam::PeerAddress(peer_address) => {
2634
ctx.accounts.peer.peer_address = peer_address;
@@ -32,6 +40,7 @@ impl SetPeerConfig<'_> {
3240
ctx.accounts.peer.enforced_options.send_and_call = send_and_call;
3341
},
3442
}
43+
// Store the PDA bump for later validation
3544
ctx.accounts.peer.bump = ctx.bumps.peer;
3645
Ok(())
3746
}
@@ -46,5 +55,6 @@ pub struct SetPeerConfigParams {
4655
#[derive(Clone, AnchorSerialize, AnchorDeserialize)]
4756
pub enum PeerConfigParam {
4857
PeerAddress([u8; 32]),
58+
/// Optionally enforce specific send options for this peer
4959
EnforcedOptions { send: Vec<u8>, send_and_call: Vec<u8> },
5060
}

examples/oapp-solana/programs/my_oapp/src/lib.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,31 @@ use oapp::{endpoint::MessagingFee, endpoint_cpi::LzAccount, LzReceiveParams};
99
use solana_helper::program_id_from_env;
1010
use state::*;
1111

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

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

2125
#[program]
2226
pub mod my_oapp {
2327
use super::*;
2428

29+
// ============================== Initializers ==============================
30+
// 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.
2531
pub fn init_store(mut ctx: Context<InitStore>, params: InitStoreParams) -> Result<()> {
2632
InitStore::apply(&mut ctx, &params)
2733
}
2834

2935
// ============================== Admin ==============================
30-
36+
// admin instruction to set or update cross-chain peer configuration parameters.
3137
pub fn set_peer_config(
3238
mut ctx: Context<SetPeerConfig>,
3339
params: SetPeerConfigParams,
@@ -36,19 +42,22 @@ pub mod my_oapp {
3642
}
3743

3844
// ============================== Public ==============================
39-
45+
// public instruction returning the estimated MessagingFee for sending a message.
4046
pub fn quote_send(ctx: Context<QuoteSend>, params: QuoteSendParams) -> Result<MessagingFee> {
4147
QuoteSend::apply(&ctx, &params)
4248
}
4349

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

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

60+
// handler that returns the list of accounts required to execute lz_receive
5261
pub fn lz_receive_types(
5362
ctx: Context<LzReceiveTypes>,
5463
params: LzReceiveParams,

0 commit comments

Comments
 (0)