@layerzerolabs/oapp-solana-contracts 1.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Anchor.toml +23 -0
- package/Cargo.toml +26 -0
- package/README.md +181 -0
- package/idls/endpoint.json +3520 -0
- package/macros/Cargo.toml +23 -0
- package/macros/src/composer.rs +43 -0
- package/macros/src/instructions/composer/lz_compose.rs +17 -0
- package/macros/src/instructions/composer/lz_compose_types_info.rs +29 -0
- package/macros/src/instructions/composer/lz_compose_types_v2.rs +17 -0
- package/macros/src/instructions/composer/mod.rs +23 -0
- package/macros/src/instructions/mod.rs +4 -0
- package/macros/src/instructions/oapp/accept_default_admin_transfer.rs +50 -0
- package/macros/src/instructions/oapp/get_enforced_options.rs +57 -0
- package/macros/src/instructions/oapp/get_peer.rs +55 -0
- package/macros/src/instructions/oapp/init_enforced_options.rs +87 -0
- package/macros/src/instructions/oapp/is_compose_msg_sender.rs +44 -0
- package/macros/src/instructions/oapp/lz_receive.rs +33 -0
- package/macros/src/instructions/oapp/lz_receive_types_info.rs +28 -0
- package/macros/src/instructions/oapp/lz_receive_types_v2.rs +17 -0
- package/macros/src/instructions/oapp/mod.rs +41 -0
- package/macros/src/instructions/oapp/next_nonce.rs +52 -0
- package/macros/src/instructions/oapp/set_enforced_options.rs +101 -0
- package/macros/src/instructions/oapp/set_peer.rs +74 -0
- package/macros/src/lib.rs +81 -0
- package/macros/src/oapp.rs +86 -0
- package/macros/src/state_types.rs +79 -0
- package/package.json +61 -0
- package/rust-toolchain.toml +3 -0
- package/rustfmt.toml +23 -0
- package/src/common.rs +156 -0
- package/src/endpoint_cpi.rs +386 -0
- package/src/errors.rs +16 -0
- package/src/events.rs +26 -0
- package/src/lib.rs +82 -0
- package/src/lz_compose_types_v2.rs +88 -0
- package/src/lz_receive_types_v2.rs +147 -0
- package/src/oapp_info.rs +278 -0
- package/src/options.rs +147 -0
- package/src/types.rs +114 -0
- package/src/utils.rs +14 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
//! CPI helpers for invoking the LayerZero Endpoint program.
|
|
2
|
+
//!
|
|
3
|
+
//! Each function in this module is a thin wrapper that:
|
|
4
|
+
//! 1. Validates the caller-supplied authority key matches the expected account in the accounts
|
|
5
|
+
//! slice.
|
|
6
|
+
//! 2. Validates that `accounts[0]` is the Endpoint program itself (checked against
|
|
7
|
+
//! `endpoint_program`) and that enough accounts were supplied.
|
|
8
|
+
//! 3. Invokes the corresponding Endpoint instruction, signing with the OApp's PDA seeds.
|
|
9
|
+
//!
|
|
10
|
+
//! **Important:** The `accounts` slice must follow the field declaration order of the
|
|
11
|
+
//! corresponding `cpi::accounts::*` struct, with `endpoint_program` prepended at index 0.
|
|
12
|
+
//! See each wrapper's "Accounts layout" section for the exact positional order.
|
|
13
|
+
//!
|
|
14
|
+
//! These wrappers are the primary interface OApp programs use to interact with the
|
|
15
|
+
//! LayerZero Endpoint for cross-chain messaging operations.
|
|
16
|
+
//!
|
|
17
|
+
//! The `endpoint` IDL (`idls/endpoint.json`) was obtained with:
|
|
18
|
+
//!
|
|
19
|
+
//! ```text
|
|
20
|
+
//! anchor idl fetch 76y77prsiCMvXMjuoZ5VRrhG5qYBrUMYTE5WgHqgjEn6 --provider.cluster mainnet
|
|
21
|
+
//! ```
|
|
22
|
+
|
|
23
|
+
use crate::endpoint::{
|
|
24
|
+
self,
|
|
25
|
+
cpi::accounts::{Clear, ClearCompose, Quote, RegisterOapp, Send, SendCompose, SetDelegate},
|
|
26
|
+
types::{
|
|
27
|
+
ClearComposeParams, ClearParams, MessagingFee, MessagingReceipt, QuoteParams,
|
|
28
|
+
RegisterOAppParams, SendComposeParams, SendParams, SetDelegateParams,
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
use anchor_lang::{
|
|
32
|
+
error,
|
|
33
|
+
prelude::{AccountInfo, CpiContext, ErrorCode, Key, Pubkey, Result},
|
|
34
|
+
require, require_keys_eq, ToAccountInfos, ToAccountMetas,
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
pub const REGISTER_OAPP_MIN_ACCOUNTS_LEN: usize = 7;
|
|
38
|
+
pub const SET_DELEGATE_MIN_ACCOUNTS_LEN: usize = 5;
|
|
39
|
+
pub const SEND_MIN_ACCOUNTS_LEN: usize = 10;
|
|
40
|
+
pub const QUOTE_MIN_ACCOUNTS_LEN: usize = 7;
|
|
41
|
+
pub const CLEAR_MIN_ACCOUNTS_LEN: usize = 8;
|
|
42
|
+
pub const SEND_COMPOSE_MIN_ACCOUNTS_LEN: usize = 7;
|
|
43
|
+
pub const CLEAR_COMPOSE_MIN_ACCOUNTS_LEN: usize = 5;
|
|
44
|
+
|
|
45
|
+
fn validate(accounts: &[AccountInfo], endpoint_program: Pubkey, min_len: usize) -> Result<()> {
|
|
46
|
+
require!(accounts.len() >= min_len, ErrorCode::AccountNotEnoughKeys);
|
|
47
|
+
require_keys_eq!(accounts[0].key(), endpoint_program, ErrorCode::InvalidProgramId);
|
|
48
|
+
Ok(())
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
fn make_cpi_ctx<'info, T>(
|
|
52
|
+
accounts: &[AccountInfo<'info>],
|
|
53
|
+
cpi_accounts: T,
|
|
54
|
+
min_len: usize,
|
|
55
|
+
) -> CpiContext<'info, 'info, 'info, 'info, T>
|
|
56
|
+
where
|
|
57
|
+
T: ToAccountMetas + ToAccountInfos<'info>,
|
|
58
|
+
{
|
|
59
|
+
let mut ctx = CpiContext::new(accounts[0].key(), cpi_accounts);
|
|
60
|
+
if accounts.len() > min_len {
|
|
61
|
+
ctx = ctx.with_remaining_accounts(accounts[min_len..].to_vec());
|
|
62
|
+
}
|
|
63
|
+
ctx
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/// Registers the OApp with the LayerZero Endpoint.
|
|
67
|
+
///
|
|
68
|
+
/// Must be called once during program initialization to establish the OApp's identity
|
|
69
|
+
/// with the Endpoint. After registration, the Endpoint recognizes this program as a
|
|
70
|
+
/// valid sender/receiver of cross-chain messages.
|
|
71
|
+
///
|
|
72
|
+
/// # Accounts layout
|
|
73
|
+
/// The `accounts` slice must follow the `RegisterOapp` struct field order:
|
|
74
|
+
///
|
|
75
|
+
/// 0. `endpoint_program`
|
|
76
|
+
/// 1. `payer`
|
|
77
|
+
/// 2. `oapp`
|
|
78
|
+
/// 3. `oapp_registry`
|
|
79
|
+
/// 4. `system_program`
|
|
80
|
+
/// 5. `event_authority`
|
|
81
|
+
/// 6. `program`
|
|
82
|
+
/// 7. remaining accounts (variable length)
|
|
83
|
+
///
|
|
84
|
+
/// # Accounts validation
|
|
85
|
+
/// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
|
|
86
|
+
/// - `accounts[2]` must match the provided `oapp` key (the OApp PDA).
|
|
87
|
+
pub fn register_oapp(
|
|
88
|
+
endpoint_program: Pubkey,
|
|
89
|
+
oapp: Pubkey,
|
|
90
|
+
accounts: &[AccountInfo],
|
|
91
|
+
seeds: &[&[u8]],
|
|
92
|
+
params: RegisterOAppParams,
|
|
93
|
+
) -> Result<()> {
|
|
94
|
+
validate(accounts, endpoint_program, REGISTER_OAPP_MIN_ACCOUNTS_LEN)?;
|
|
95
|
+
require_keys_eq!(accounts[2].key(), oapp, ErrorCode::ConstraintAddress);
|
|
96
|
+
|
|
97
|
+
let cpi_accounts = RegisterOapp {
|
|
98
|
+
payer: accounts[1].clone(),
|
|
99
|
+
oapp: accounts[2].clone(),
|
|
100
|
+
oapp_registry: accounts[3].clone(),
|
|
101
|
+
system_program: accounts[4].clone(),
|
|
102
|
+
event_authority: accounts[5].clone(),
|
|
103
|
+
program: accounts[6].clone(),
|
|
104
|
+
};
|
|
105
|
+
endpoint::cpi::register_oapp(
|
|
106
|
+
make_cpi_ctx(accounts, cpi_accounts, REGISTER_OAPP_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
|
|
107
|
+
params,
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// Sets a delegate address authorized to configure the OApp's Endpoint settings.
|
|
112
|
+
///
|
|
113
|
+
/// The delegate can manage messaging libraries, executors, and other Endpoint-level
|
|
114
|
+
/// configuration on behalf of the OApp admin.
|
|
115
|
+
///
|
|
116
|
+
/// # Accounts layout
|
|
117
|
+
/// The `accounts` slice must follow the `SetDelegate` struct field order:
|
|
118
|
+
///
|
|
119
|
+
/// 0. `endpoint_program`
|
|
120
|
+
/// 1. `oapp`
|
|
121
|
+
/// 2. `oapp_registry`
|
|
122
|
+
/// 3. `event_authority`
|
|
123
|
+
/// 4. `program`
|
|
124
|
+
/// 5. remaining accounts (variable length)
|
|
125
|
+
///
|
|
126
|
+
/// # Accounts validation
|
|
127
|
+
/// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
|
|
128
|
+
/// - `accounts[1]` must match the provided `oapp` key.
|
|
129
|
+
pub fn set_delegate(
|
|
130
|
+
endpoint_program: Pubkey,
|
|
131
|
+
oapp: Pubkey,
|
|
132
|
+
accounts: &[AccountInfo],
|
|
133
|
+
seeds: &[&[u8]],
|
|
134
|
+
params: SetDelegateParams,
|
|
135
|
+
) -> Result<()> {
|
|
136
|
+
validate(accounts, endpoint_program, SET_DELEGATE_MIN_ACCOUNTS_LEN)?;
|
|
137
|
+
require_keys_eq!(accounts[1].key(), oapp, ErrorCode::ConstraintAddress);
|
|
138
|
+
|
|
139
|
+
let cpi_accounts = SetDelegate {
|
|
140
|
+
oapp: accounts[1].clone(),
|
|
141
|
+
oapp_registry: accounts[2].clone(),
|
|
142
|
+
event_authority: accounts[3].clone(),
|
|
143
|
+
program: accounts[4].clone(),
|
|
144
|
+
};
|
|
145
|
+
endpoint::cpi::set_delegate(
|
|
146
|
+
make_cpi_ctx(accounts, cpi_accounts, SET_DELEGATE_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
|
|
147
|
+
params,
|
|
148
|
+
)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/// Sends a cross-chain message through the LayerZero Endpoint.
|
|
152
|
+
///
|
|
153
|
+
/// This is the core outbound messaging function. The Endpoint assigns a nonce,
|
|
154
|
+
/// emits a packet event for DVNs to verify, and charges the messaging fee.
|
|
155
|
+
///
|
|
156
|
+
/// # Accounts layout
|
|
157
|
+
/// The `accounts` slice must follow the `Send` struct field order:
|
|
158
|
+
///
|
|
159
|
+
/// 0. `endpoint_program`
|
|
160
|
+
/// 1. `sender`
|
|
161
|
+
/// 2. `send_library_program`
|
|
162
|
+
/// 3. `send_library_config`
|
|
163
|
+
/// 4. `default_send_library_config`
|
|
164
|
+
/// 5. `send_library_info`
|
|
165
|
+
/// 6. `endpoint`
|
|
166
|
+
/// 7. `nonce`
|
|
167
|
+
/// 8. `event_authority`
|
|
168
|
+
/// 9. `program`
|
|
169
|
+
/// 10. remaining accounts (variable length)
|
|
170
|
+
///
|
|
171
|
+
/// # Accounts validation
|
|
172
|
+
/// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
|
|
173
|
+
/// - `accounts[1]` must match the provided `sender` key.
|
|
174
|
+
/// - The `sender` key must appear exactly once in the accounts slice to prevent duplicate-account
|
|
175
|
+
/// exploits that could manipulate fee deduction.
|
|
176
|
+
///
|
|
177
|
+
/// # Returns
|
|
178
|
+
/// A [`MessagingReceipt`] containing the assigned nonce and fee breakdown.
|
|
179
|
+
pub fn send(
|
|
180
|
+
endpoint_program: Pubkey,
|
|
181
|
+
sender: Pubkey,
|
|
182
|
+
accounts: &[AccountInfo],
|
|
183
|
+
seeds: &[&[u8]],
|
|
184
|
+
params: SendParams,
|
|
185
|
+
) -> Result<MessagingReceipt> {
|
|
186
|
+
validate(accounts, endpoint_program, SEND_MIN_ACCOUNTS_LEN)?;
|
|
187
|
+
require_keys_eq!(accounts[1].key(), sender, ErrorCode::ConstraintAddress);
|
|
188
|
+
require!(
|
|
189
|
+
accounts.iter().filter(|acc| acc.key == &sender).count() <= 1,
|
|
190
|
+
ErrorCode::ConstraintAddress
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
let cpi_accounts = Send {
|
|
194
|
+
sender: accounts[1].clone(),
|
|
195
|
+
send_library_program: accounts[2].clone(),
|
|
196
|
+
send_library_config: accounts[3].clone(),
|
|
197
|
+
default_send_library_config: accounts[4].clone(),
|
|
198
|
+
send_library_info: accounts[5].clone(),
|
|
199
|
+
endpoint: accounts[6].clone(),
|
|
200
|
+
nonce: accounts[7].clone(),
|
|
201
|
+
event_authority: accounts[8].clone(),
|
|
202
|
+
program: accounts[9].clone(),
|
|
203
|
+
};
|
|
204
|
+
let rtn = endpoint::cpi::send(
|
|
205
|
+
make_cpi_ctx(accounts, cpi_accounts, SEND_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
|
|
206
|
+
params,
|
|
207
|
+
)?;
|
|
208
|
+
Ok(rtn.get())
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// Queries the Endpoint for the fee required to send a cross-chain message.
|
|
212
|
+
///
|
|
213
|
+
/// This is a read-only CPI — no PDA signer seeds are needed. Call this before
|
|
214
|
+
/// [`send`] to determine the [`MessagingFee`] the caller must pay.
|
|
215
|
+
///
|
|
216
|
+
/// # Accounts layout
|
|
217
|
+
/// The `accounts` slice must follow the `Quote` struct field order:
|
|
218
|
+
///
|
|
219
|
+
/// 0. `endpoint_program`
|
|
220
|
+
/// 1. `send_library_program`
|
|
221
|
+
/// 2. `send_library_config`
|
|
222
|
+
/// 3. `default_send_library_config`
|
|
223
|
+
/// 4. `send_library_info`
|
|
224
|
+
/// 5. `endpoint`
|
|
225
|
+
/// 6. `nonce`
|
|
226
|
+
/// 7. remaining accounts (variable length)
|
|
227
|
+
///
|
|
228
|
+
/// # Accounts validation
|
|
229
|
+
/// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
|
|
230
|
+
pub fn quote(
|
|
231
|
+
endpoint_program: Pubkey,
|
|
232
|
+
accounts: &[AccountInfo],
|
|
233
|
+
params: QuoteParams,
|
|
234
|
+
) -> Result<MessagingFee> {
|
|
235
|
+
validate(accounts, endpoint_program, QUOTE_MIN_ACCOUNTS_LEN)?;
|
|
236
|
+
|
|
237
|
+
let cpi_accounts = Quote {
|
|
238
|
+
send_library_program: accounts[1].clone(),
|
|
239
|
+
send_library_config: accounts[2].clone(),
|
|
240
|
+
default_send_library_config: accounts[3].clone(),
|
|
241
|
+
send_library_info: accounts[4].clone(),
|
|
242
|
+
endpoint: accounts[5].clone(),
|
|
243
|
+
nonce: accounts[6].clone(),
|
|
244
|
+
};
|
|
245
|
+
let result =
|
|
246
|
+
endpoint::cpi::quote(make_cpi_ctx(accounts, cpi_accounts, QUOTE_MIN_ACCOUNTS_LEN), params)?;
|
|
247
|
+
Ok(result.get())
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/// Marks an inbound message as received by clearing it from the Endpoint.
|
|
251
|
+
///
|
|
252
|
+
/// Called during `lz_receive` processing to acknowledge that the OApp has consumed the
|
|
253
|
+
/// message payload for a given (srcEid, sender, nonce) tuple. This advances the
|
|
254
|
+
/// inbound nonce and allows subsequent messages to be delivered.
|
|
255
|
+
///
|
|
256
|
+
/// # Accounts layout
|
|
257
|
+
/// The `accounts` slice must follow the `Clear` struct field order:
|
|
258
|
+
///
|
|
259
|
+
/// 0. `endpoint_program`
|
|
260
|
+
/// 1. `signer`
|
|
261
|
+
/// 2. `oapp_registry`
|
|
262
|
+
/// 3. `nonce`
|
|
263
|
+
/// 4. `payload_hash`
|
|
264
|
+
/// 5. `endpoint`
|
|
265
|
+
/// 6. `event_authority`
|
|
266
|
+
/// 7. `program`
|
|
267
|
+
/// 8. remaining accounts (variable length)
|
|
268
|
+
///
|
|
269
|
+
/// # Accounts validation
|
|
270
|
+
/// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
|
|
271
|
+
/// - `accounts[1]` is the `signer` — pinned to `receiver`. The OApp PDA path signs via `seeds`; the
|
|
272
|
+
/// delegate path requires the delegate to already be a signer. The Endpoint accepts either the
|
|
273
|
+
/// OApp PDA (`params.receiver`) or the registered delegate as `signer`.
|
|
274
|
+
///
|
|
275
|
+
/// # Returns
|
|
276
|
+
/// The payload hash (`[u8; 32]`) of the cleared message.
|
|
277
|
+
pub fn clear(
|
|
278
|
+
endpoint_program: Pubkey,
|
|
279
|
+
receiver: Pubkey,
|
|
280
|
+
accounts: &[AccountInfo],
|
|
281
|
+
seeds: &[&[u8]],
|
|
282
|
+
params: ClearParams,
|
|
283
|
+
) -> Result<[u8; 32]> {
|
|
284
|
+
validate(accounts, endpoint_program, CLEAR_MIN_ACCOUNTS_LEN)?;
|
|
285
|
+
require_keys_eq!(accounts[1].key(), receiver, ErrorCode::ConstraintAddress);
|
|
286
|
+
|
|
287
|
+
let cpi_accounts = Clear {
|
|
288
|
+
signer: accounts[1].clone(),
|
|
289
|
+
oapp_registry: accounts[2].clone(),
|
|
290
|
+
nonce: accounts[3].clone(),
|
|
291
|
+
payload_hash: accounts[4].clone(),
|
|
292
|
+
endpoint: accounts[5].clone(),
|
|
293
|
+
event_authority: accounts[6].clone(),
|
|
294
|
+
program: accounts[7].clone(),
|
|
295
|
+
};
|
|
296
|
+
let result = endpoint::cpi::clear(
|
|
297
|
+
make_cpi_ctx(accounts, cpi_accounts, CLEAR_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
|
|
298
|
+
params,
|
|
299
|
+
)?;
|
|
300
|
+
Ok(result.get())
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/// Sends a composed message to another program on the same chain.
|
|
304
|
+
///
|
|
305
|
+
/// Composition allows an OApp to trigger follow-up logic in a separate program after
|
|
306
|
+
/// receiving a cross-chain message. The Endpoint stores the composed message hash,
|
|
307
|
+
/// and the target program's `lz_compose` handler is invoked to process it.
|
|
308
|
+
///
|
|
309
|
+
/// # Accounts layout
|
|
310
|
+
/// The `accounts` slice must follow the `SendCompose` struct field order:
|
|
311
|
+
///
|
|
312
|
+
/// 0. `endpoint_program`
|
|
313
|
+
/// 1. `from`
|
|
314
|
+
/// 2. `payer`
|
|
315
|
+
/// 3. `compose_message`
|
|
316
|
+
/// 4. `system_program`
|
|
317
|
+
/// 5. `event_authority`
|
|
318
|
+
/// 6. `program`
|
|
319
|
+
/// 7. remaining accounts (variable length)
|
|
320
|
+
///
|
|
321
|
+
/// # Accounts validation
|
|
322
|
+
/// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
|
|
323
|
+
/// - `accounts[1]` must match the provided `from` key (the sending OApp PDA).
|
|
324
|
+
pub fn send_compose(
|
|
325
|
+
endpoint_program: Pubkey,
|
|
326
|
+
from: Pubkey,
|
|
327
|
+
accounts: &[AccountInfo],
|
|
328
|
+
seeds: &[&[u8]],
|
|
329
|
+
params: SendComposeParams,
|
|
330
|
+
) -> Result<()> {
|
|
331
|
+
validate(accounts, endpoint_program, SEND_COMPOSE_MIN_ACCOUNTS_LEN)?;
|
|
332
|
+
require_keys_eq!(accounts[1].key(), from, ErrorCode::ConstraintAddress);
|
|
333
|
+
|
|
334
|
+
let cpi_accounts = SendCompose {
|
|
335
|
+
from: accounts[1].clone(),
|
|
336
|
+
payer: accounts[2].clone(),
|
|
337
|
+
compose_message: accounts[3].clone(),
|
|
338
|
+
system_program: accounts[4].clone(),
|
|
339
|
+
event_authority: accounts[5].clone(),
|
|
340
|
+
program: accounts[6].clone(),
|
|
341
|
+
};
|
|
342
|
+
endpoint::cpi::send_compose(
|
|
343
|
+
make_cpi_ctx(accounts, cpi_accounts, SEND_COMPOSE_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
|
|
344
|
+
params,
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/// Clears (acknowledges) a composed message after processing.
|
|
349
|
+
///
|
|
350
|
+
/// Called by the target program's `lz_compose` handler after it has consumed the
|
|
351
|
+
/// composed message. This marks the composition as complete in the Endpoint.
|
|
352
|
+
///
|
|
353
|
+
/// # Accounts layout
|
|
354
|
+
/// The `accounts` slice must follow the `ClearCompose` struct field order:
|
|
355
|
+
///
|
|
356
|
+
/// 0. `endpoint_program`
|
|
357
|
+
/// 1. `to`
|
|
358
|
+
/// 2. `compose_message`
|
|
359
|
+
/// 3. `event_authority`
|
|
360
|
+
/// 4. `program`
|
|
361
|
+
/// 5. remaining accounts (variable length)
|
|
362
|
+
///
|
|
363
|
+
/// # Accounts validation
|
|
364
|
+
/// - `accounts[0]` must equal the caller-supplied `endpoint_program` parameter.
|
|
365
|
+
/// - `accounts[1]` must match the provided `to` key (the receiving program's PDA).
|
|
366
|
+
pub fn clear_compose(
|
|
367
|
+
endpoint_program: Pubkey,
|
|
368
|
+
to: Pubkey,
|
|
369
|
+
accounts: &[AccountInfo],
|
|
370
|
+
seeds: &[&[u8]],
|
|
371
|
+
params: ClearComposeParams,
|
|
372
|
+
) -> Result<()> {
|
|
373
|
+
validate(accounts, endpoint_program, CLEAR_COMPOSE_MIN_ACCOUNTS_LEN)?;
|
|
374
|
+
require_keys_eq!(accounts[1].key(), to, ErrorCode::ConstraintAddress);
|
|
375
|
+
|
|
376
|
+
let cpi_accounts = ClearCompose {
|
|
377
|
+
to: accounts[1].clone(),
|
|
378
|
+
compose_message: accounts[2].clone(),
|
|
379
|
+
event_authority: accounts[3].clone(),
|
|
380
|
+
program: accounts[4].clone(),
|
|
381
|
+
};
|
|
382
|
+
endpoint::cpi::clear_compose(
|
|
383
|
+
make_cpi_ctx(accounts, cpi_accounts, CLEAR_COMPOSE_MIN_ACCOUNTS_LEN).with_signer(&[seeds]),
|
|
384
|
+
params,
|
|
385
|
+
)
|
|
386
|
+
}
|
package/src/errors.rs
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
use anchor_lang::error_code;
|
|
2
|
+
|
|
3
|
+
/// Custom error codes for OApp operations
|
|
4
|
+
#[error_code]
|
|
5
|
+
pub enum OAppError {
|
|
6
|
+
#[msg("Invalid address lookup table")]
|
|
7
|
+
InvalidAddressLookupTable,
|
|
8
|
+
#[msg("Invalid options")]
|
|
9
|
+
InvalidOptions,
|
|
10
|
+
#[msg("Invalid options length")]
|
|
11
|
+
InvalidOptionsLength,
|
|
12
|
+
#[msg("No peer set for the given EID")]
|
|
13
|
+
NoPeer,
|
|
14
|
+
#[msg("TLV update would change entry size")]
|
|
15
|
+
TlvLengthMismatch,
|
|
16
|
+
}
|
package/src/events.rs
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
use anchor_lang::prelude::*;
|
|
2
|
+
|
|
3
|
+
/// Emitted when a peer is set for a destination endpoint.
|
|
4
|
+
///
|
|
5
|
+
/// `oapp` is the OApp-store PDA this peer entry belongs to. A single OApp
|
|
6
|
+
/// program manages many independent OApp-store instances (mirroring how the
|
|
7
|
+
/// SPL Token program manages many `Mint` accounts), so the event carries the
|
|
8
|
+
/// store pubkey to let off-chain consumers attribute each change to the right
|
|
9
|
+
/// instance.
|
|
10
|
+
#[event]
|
|
11
|
+
pub struct PeerSet {
|
|
12
|
+
pub oapp: Pubkey,
|
|
13
|
+
pub eid: u32,
|
|
14
|
+
pub peer: [u8; 32],
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Emitted when enforced options are set for a destination endpoint and message type.
|
|
18
|
+
///
|
|
19
|
+
/// See [`PeerSet`] for the rationale of the `oapp` field.
|
|
20
|
+
#[event]
|
|
21
|
+
pub struct EnforcedOptionsSet {
|
|
22
|
+
pub oapp: Pubkey,
|
|
23
|
+
pub eid: u32,
|
|
24
|
+
pub msg_type: u16,
|
|
25
|
+
pub options: Vec<u8>,
|
|
26
|
+
}
|
package/src/lib.rs
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
//! LayerZero OApp SDK for Solana.
|
|
2
|
+
//!
|
|
3
|
+
//! Provides the core building blocks for Solana programs that integrate with the
|
|
4
|
+
//! LayerZero V2 cross-chain messaging protocol as an Omnichain Application (OApp).
|
|
5
|
+
//!
|
|
6
|
+
//! # What's included
|
|
7
|
+
//!
|
|
8
|
+
//! - **PDA seeds** – canonical constants for deriving all protocol-related accounts (peers,
|
|
9
|
+
//! enforced options, nonces, payload hashes, etc.).
|
|
10
|
+
//! - **Endpoint CPI helpers** – thin wrappers around cross-program invocations to the LayerZero
|
|
11
|
+
//! Endpoint program (send, quote, compose, etc.).
|
|
12
|
+
//! - **Account macros** – re-exported from [`oapp_macros`] to generate boilerplate Anchor account
|
|
13
|
+
//! structs for standard OApp instructions.
|
|
14
|
+
//! - **Types & traits** – shared parameter / return structs, the [`OAppState`] trait for PDA signer
|
|
15
|
+
//! seeds, and messaging option builders.
|
|
16
|
+
|
|
17
|
+
pub mod common;
|
|
18
|
+
pub mod endpoint_cpi;
|
|
19
|
+
pub mod events;
|
|
20
|
+
pub mod lz_compose_types_v2;
|
|
21
|
+
pub mod lz_receive_types_v2;
|
|
22
|
+
pub mod oapp_info;
|
|
23
|
+
pub mod options;
|
|
24
|
+
pub mod types;
|
|
25
|
+
pub mod utils;
|
|
26
|
+
|
|
27
|
+
mod errors;
|
|
28
|
+
use anchor_lang::prelude::*;
|
|
29
|
+
// `idls/endpoint.json` is vendored here because `declare_program!` reads the
|
|
30
|
+
// IDL at compile time, and downstream consumers of this crate cannot rely on
|
|
31
|
+
// a workspace-relative path to the Endpoint program's own build output.
|
|
32
|
+
declare_program!(endpoint);
|
|
33
|
+
|
|
34
|
+
pub use errors::*;
|
|
35
|
+
|
|
36
|
+
// Re-export procedural macros so consumers can `use oapp::oapp`
|
|
37
|
+
pub use oapp_macros::*;
|
|
38
|
+
|
|
39
|
+
// OApp state & config
|
|
40
|
+
pub const OAPP_SEED: &[u8] = b"OApp";
|
|
41
|
+
pub const OAPP_INFO_SEED: &[u8] = b"OAppInfo";
|
|
42
|
+
pub const PEER_SEED: &[u8] = b"Peer";
|
|
43
|
+
pub const ENFORCED_OPTIONS_SEED: &[u8] = b"EnforcedOptions";
|
|
44
|
+
|
|
45
|
+
// Messaging
|
|
46
|
+
pub const LZ_COMPOSE_TYPES_SEED: &[u8] = b"LzComposeTypes";
|
|
47
|
+
pub const LZ_RECEIVE_TYPES_SEED: &[u8] = b"LzReceiveTypes";
|
|
48
|
+
pub const NONCE_SEED: &[u8] = b"Nonce";
|
|
49
|
+
pub const PENDING_NONCE_SEED: &[u8] = b"PendingNonce";
|
|
50
|
+
pub const PAYLOAD_HASH_SEED: &[u8] = b"PayloadHash";
|
|
51
|
+
pub const COMPOSED_MESSAGE_HASH_SEED: &[u8] = b"ComposedMessageHash";
|
|
52
|
+
|
|
53
|
+
// Endpoint & library config
|
|
54
|
+
pub const ENDPOINT_SEED: &[u8] = b"Endpoint";
|
|
55
|
+
pub const MESSAGE_LIB_SEED: &[u8] = b"MessageLib";
|
|
56
|
+
pub const SEND_LIBRARY_CONFIG_SEED: &[u8] = b"SendLibraryConfig";
|
|
57
|
+
pub const RECEIVE_LIBRARY_CONFIG_SEED: &[u8] = b"ReceiveLibraryConfig";
|
|
58
|
+
|
|
59
|
+
// Event
|
|
60
|
+
pub const EVENT_SEED: &[u8] = b"__event_authority";
|
|
61
|
+
|
|
62
|
+
// PDA seed helpers below use `Box::leak` to produce the `&'static [&'static [u8]]`
|
|
63
|
+
// shape required by Anchor's `#[account(seeds = ...)]` attribute. Each call leaks
|
|
64
|
+
// ~64 bytes. **SVM-only**: harmless on-chain because the BPF VM allocator is torn
|
|
65
|
+
// down at the end of every transaction, so leaks never accumulate. Do not call
|
|
66
|
+
// these from long-running off-chain Rust (native test runners, indexers, CLIs) —
|
|
67
|
+
// derive PDAs with stack-local arrays in those contexts instead.
|
|
68
|
+
|
|
69
|
+
/// PDA seeds for an OApp's `lz_receive_types_accounts` PDA.
|
|
70
|
+
/// Seed shape: [LZ_RECEIVE_TYPES_SEED, oapp_key]
|
|
71
|
+
pub fn lz_receive_types_seeds(oapp_key: &Pubkey) -> &'static [&'static [u8]] {
|
|
72
|
+
let key_bytes: &'static [u8; 32] = Box::leak(Box::new(oapp_key.to_bytes()));
|
|
73
|
+
Box::leak(Box::new([LZ_RECEIVE_TYPES_SEED, key_bytes.as_slice()]))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Trait for OApp state types.
|
|
77
|
+
///
|
|
78
|
+
/// Defines the minimal interface for PDA signer seeds used in CPI invocations.
|
|
79
|
+
/// `N` is the number of seed segments (e.g. 3 for `[PREFIX, key, bump]`).
|
|
80
|
+
pub trait OAppState<const N: usize> {
|
|
81
|
+
fn signer_seeds(&self) -> [&[u8]; N];
|
|
82
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
use crate::{common::AccountMetaRef, COMPOSED_MESSAGE_HASH_SEED, EVENT_SEED};
|
|
2
|
+
use anchor_lang::prelude::*;
|
|
3
|
+
use solana_keccak_hasher::hash;
|
|
4
|
+
|
|
5
|
+
/// Version identifier returned by `lz_compose_types_info` for the V2 flow.
|
|
6
|
+
pub const LZ_COMPOSE_TYPES_VERSION: u8 = 2;
|
|
7
|
+
|
|
8
|
+
/// Payload returned from `lz_compose_types_info` when the version is V2.
|
|
9
|
+
///
|
|
10
|
+
/// The executor uses these accounts to construct the follow-up
|
|
11
|
+
/// `lz_compose_types_v2` call.
|
|
12
|
+
#[derive(AnchorSerialize, AnchorDeserialize, Clone, Debug)]
|
|
13
|
+
pub struct LzComposeTypesV2Accounts {
|
|
14
|
+
pub accounts: Vec<Pubkey>,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/// Output of the lz_compose_types_v2 instruction.
|
|
18
|
+
///
|
|
19
|
+
/// This structure enables the multi-instruction execution model where composers
|
|
20
|
+
/// can define multiple instructions to be executed atomically by the Executor.
|
|
21
|
+
/// The Executor constructs a single transaction containing all returned instructions.
|
|
22
|
+
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
|
23
|
+
pub struct LzComposeTypesV2Result {
|
|
24
|
+
/// The version of context account
|
|
25
|
+
pub context_version: u8,
|
|
26
|
+
/// ALTs required for this execution context
|
|
27
|
+
/// Used by the Executor to resolve AltIndex references in AccountMetaRef
|
|
28
|
+
pub alts: Vec<Pubkey>,
|
|
29
|
+
/// The complete list of instructions required for LzCompose execution
|
|
30
|
+
/// MUST include exactly one LzCompose instruction
|
|
31
|
+
/// MAY include additional Standard instructions for preprocessing/postprocessing
|
|
32
|
+
/// Instructions are executed in the order returned
|
|
33
|
+
pub instructions: Vec<ComposeInstruction>,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/// The list of instructions that can be executed in the LzCompose transaction.
|
|
37
|
+
#[derive(AnchorSerialize, AnchorDeserialize, Clone)]
|
|
38
|
+
pub enum ComposeInstruction {
|
|
39
|
+
/// The main LzCompose instruction (exactly one required per transaction)
|
|
40
|
+
/// This instruction processes the incoming composed message
|
|
41
|
+
LzCompose {
|
|
42
|
+
/// Account list for the lz_compose instruction
|
|
43
|
+
/// Uses AddressLocator for flexible address resolution
|
|
44
|
+
accounts: Vec<AccountMetaRef>,
|
|
45
|
+
},
|
|
46
|
+
/// Arbitrary custom instruction for preprocessing/postprocessing
|
|
47
|
+
Standard {
|
|
48
|
+
/// Target program ID for the custom instruction
|
|
49
|
+
program_id: Pubkey,
|
|
50
|
+
/// Account list for the custom instruction
|
|
51
|
+
accounts: Vec<AccountMetaRef>,
|
|
52
|
+
/// Instruction data payload
|
|
53
|
+
data: Vec<u8>,
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// V2 version of get_accounts_for_clear_compose that returns AccountMetaRef
|
|
58
|
+
pub fn get_accounts_for_clear_compose(
|
|
59
|
+
endpoint_program: Pubkey,
|
|
60
|
+
from: &Pubkey,
|
|
61
|
+
to: &Pubkey,
|
|
62
|
+
guid: &[u8; 32],
|
|
63
|
+
index: u16,
|
|
64
|
+
composed_message: &[u8],
|
|
65
|
+
) -> Vec<AccountMetaRef> {
|
|
66
|
+
let (composed_message_account, _) = Pubkey::find_program_address(
|
|
67
|
+
&[
|
|
68
|
+
COMPOSED_MESSAGE_HASH_SEED,
|
|
69
|
+
&from.to_bytes(),
|
|
70
|
+
&to.to_bytes(),
|
|
71
|
+
&guid[..],
|
|
72
|
+
&index.to_be_bytes(),
|
|
73
|
+
&hash(composed_message).to_bytes(),
|
|
74
|
+
],
|
|
75
|
+
&endpoint_program,
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
let (event_authority_account, _) =
|
|
79
|
+
Pubkey::find_program_address(&[EVENT_SEED], &endpoint_program);
|
|
80
|
+
|
|
81
|
+
vec![
|
|
82
|
+
AccountMetaRef { pubkey: endpoint_program.into(), is_writable: false },
|
|
83
|
+
AccountMetaRef { pubkey: (*to).into(), is_writable: false },
|
|
84
|
+
AccountMetaRef { pubkey: composed_message_account.into(), is_writable: true },
|
|
85
|
+
AccountMetaRef { pubkey: event_authority_account.into(), is_writable: false },
|
|
86
|
+
AccountMetaRef { pubkey: endpoint_program.into(), is_writable: false },
|
|
87
|
+
]
|
|
88
|
+
}
|