@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.
Files changed (40) hide show
  1. package/Anchor.toml +23 -0
  2. package/Cargo.toml +26 -0
  3. package/README.md +181 -0
  4. package/idls/endpoint.json +3520 -0
  5. package/macros/Cargo.toml +23 -0
  6. package/macros/src/composer.rs +43 -0
  7. package/macros/src/instructions/composer/lz_compose.rs +17 -0
  8. package/macros/src/instructions/composer/lz_compose_types_info.rs +29 -0
  9. package/macros/src/instructions/composer/lz_compose_types_v2.rs +17 -0
  10. package/macros/src/instructions/composer/mod.rs +23 -0
  11. package/macros/src/instructions/mod.rs +4 -0
  12. package/macros/src/instructions/oapp/accept_default_admin_transfer.rs +50 -0
  13. package/macros/src/instructions/oapp/get_enforced_options.rs +57 -0
  14. package/macros/src/instructions/oapp/get_peer.rs +55 -0
  15. package/macros/src/instructions/oapp/init_enforced_options.rs +87 -0
  16. package/macros/src/instructions/oapp/is_compose_msg_sender.rs +44 -0
  17. package/macros/src/instructions/oapp/lz_receive.rs +33 -0
  18. package/macros/src/instructions/oapp/lz_receive_types_info.rs +28 -0
  19. package/macros/src/instructions/oapp/lz_receive_types_v2.rs +17 -0
  20. package/macros/src/instructions/oapp/mod.rs +41 -0
  21. package/macros/src/instructions/oapp/next_nonce.rs +52 -0
  22. package/macros/src/instructions/oapp/set_enforced_options.rs +101 -0
  23. package/macros/src/instructions/oapp/set_peer.rs +74 -0
  24. package/macros/src/lib.rs +81 -0
  25. package/macros/src/oapp.rs +86 -0
  26. package/macros/src/state_types.rs +79 -0
  27. package/package.json +61 -0
  28. package/rust-toolchain.toml +3 -0
  29. package/rustfmt.toml +23 -0
  30. package/src/common.rs +156 -0
  31. package/src/endpoint_cpi.rs +386 -0
  32. package/src/errors.rs +16 -0
  33. package/src/events.rs +26 -0
  34. package/src/lib.rs +82 -0
  35. package/src/lz_compose_types_v2.rs +88 -0
  36. package/src/lz_receive_types_v2.rs +147 -0
  37. package/src/oapp_info.rs +278 -0
  38. package/src/options.rs +147 -0
  39. package/src/types.rs +114 -0
  40. package/src/utils.rs +14 -0
@@ -0,0 +1,23 @@
1
+ [package]
2
+ name = "oapp-macros"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [lib]
7
+ proc-macro = true
8
+
9
+ [features]
10
+ default = []
11
+ idl-build = ["anchor-lang/idl-build"]
12
+
13
+ [dependencies]
14
+ anchor-lang = { version = "1.0.2" }
15
+ anchor-trait = { path = "../dependencies/framework-solana-anchor-trait" }
16
+ syn = { version = "2.0", features = ["full", "parsing", "extra-traits"] }
17
+ quote = "1.0"
18
+ proc-macro2 = "1.0"
19
+ darling = "0.20"
20
+ sha2 = "0.10"
21
+
22
+ [dev-dependencies]
23
+ trybuild = "1.0"
@@ -0,0 +1,43 @@
1
+ //! Top-level `#[composer]` attribute macro implementation.
2
+ //!
3
+ //! Flow: parse module, then collect → generate → assemble.
4
+ //! All three instructions are required — no attribute args needed.
5
+
6
+ use crate::instructions::composer::{ComposerCodegenContext, ComposerInstructionSet};
7
+ use anchor_trait::{generate_instructions, Assembly, InstructionOverrides, OverrideConfig};
8
+ use proc_macro2::TokenStream;
9
+ use syn::ItemMod;
10
+
11
+ const OVERRIDE_CONFIG: OverrideConfig = OverrideConfig {
12
+ attr_name: "composer_instruction",
13
+ domain: "Composer",
14
+ };
15
+
16
+ // ---------------------------------------------------------------------------
17
+ // Entry point
18
+ // ---------------------------------------------------------------------------
19
+
20
+ pub fn expand(_attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
21
+ let module = syn::parse2::<ItemMod>(item)?;
22
+
23
+ // 1. Collect overrides
24
+ let overrides = InstructionOverrides::collect(&module, OVERRIDE_CONFIG)?;
25
+
26
+ // 2. Generate instructions
27
+ let codegen_ctx = ComposerCodegenContext;
28
+ let instructions = generate_instructions::<ComposerInstructionSet>(&codegen_ctx, &overrides)?;
29
+
30
+ // 3. Assemble output
31
+ let prelude = TokenStream::new();
32
+ let extra_mod_attrs = TokenStream::new();
33
+
34
+ Ok(Assembly {
35
+ prelude,
36
+ overrides,
37
+ instructions,
38
+ extra_mod_attrs,
39
+ source_mod: module,
40
+ extend_macro_name: None,
41
+ }
42
+ .assemble())
43
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `lz_compose` instruction.
2
+ //!
3
+ //! This instruction has no default implementation — users MUST provide
4
+ //! a custom handler named `lz_compose` with `#[composer_instruction]`.
5
+
6
+ use super::ComposerCodegenContext;
7
+ use anchor_trait::InstructionSpec;
8
+ use quote::quote;
9
+
10
+ pub(super) fn spec() -> InstructionSpec<ComposerCodegenContext> {
11
+ InstructionSpec {
12
+ params_type: Some(quote!(::oapp::types::LzComposeParams)),
13
+ return_type: quote!(()),
14
+ is_view: false,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,29 @@
1
+ //! Code generator for the `lz_compose_types_info` instruction.
2
+ //!
3
+ //! Required override (no default). Users MUST provide a custom handler
4
+ //! named `lz_compose_types_info` with `#[composer_instruction]`.
5
+ //!
6
+ //! The off-chain LayerZero Executor invokes this instruction with a fixed
7
+ //! 2-account list, in this order:
8
+ //! 1. `composer_account` — the program-specific Composer store
9
+ //! 2. `lz_compose_types_accounts` PDA at `[LZ_COMPOSE_TYPES_SEED, composer_account.key()]`
10
+ //!
11
+ //! Custom handlers MUST declare an `Accounts` struct matching this shape.
12
+ //! The framework does not enforce it at compile time, so a mismatch only
13
+ //! surfaces as a runtime failure when the Executor calls the deployed program.
14
+ //!
15
+ //! This follows the same V2 account-discovery model as `lz_receive_types_info`.
16
+ //! See `docs/lz-compose-types.md` for the compose-specific differences.
17
+
18
+ use super::ComposerCodegenContext;
19
+ use anchor_trait::InstructionSpec;
20
+ use quote::quote;
21
+
22
+ pub(super) fn spec() -> InstructionSpec<ComposerCodegenContext> {
23
+ InstructionSpec {
24
+ params_type: Some(quote!(::oapp::types::LzComposeParams)),
25
+ return_type: quote!((u8, ::oapp::lz_compose_types_v2::LzComposeTypesV2Accounts)),
26
+ is_view: true,
27
+ default_impl: None,
28
+ }
29
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `lz_compose_types_v2` instruction.
2
+ //!
3
+ //! This instruction has no default implementation — users MUST provide
4
+ //! a custom handler named `lz_compose_types_v2` with `#[composer_instruction]`.
5
+
6
+ use super::ComposerCodegenContext;
7
+ use anchor_trait::InstructionSpec;
8
+ use quote::quote;
9
+
10
+ pub(super) fn spec() -> InstructionSpec<ComposerCodegenContext> {
11
+ InstructionSpec {
12
+ params_type: Some(quote!(::oapp::types::LzComposeParams)),
13
+ return_type: quote!(::oapp::lz_compose_types_v2::LzComposeTypesV2Result),
14
+ is_view: true,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,23 @@
1
+ //! Composer instruction definitions and code generation.
2
+ //!
3
+ //! Each sub-module defines a `spec()` returning the instruction's codegen descriptor.
4
+ //! `InstructionName::spec()` maps instruction variants to their specs.
5
+
6
+ mod lz_compose;
7
+ mod lz_compose_types_info;
8
+ mod lz_compose_types_v2;
9
+
10
+ use anchor_trait::declare_instructions;
11
+
12
+ /// Domain-specific context for Composer code generation.
13
+ /// Currently a unit struct — all instructions are required with no defaults.
14
+ pub(crate) struct ComposerCodegenContext;
15
+
16
+ declare_instructions! {
17
+ name = ComposerInstructionSet;
18
+ context = ComposerCodegenContext;
19
+
20
+ LzComposeTypesInfo => lz_compose_types_info,
21
+ LzComposeTypesV2 => lz_compose_types_v2,
22
+ LzCompose => lz_compose,
23
+ }
@@ -0,0 +1,4 @@
1
+ //! Instruction definitions and code generation, organized by domain.
2
+
3
+ pub(crate) mod composer;
4
+ pub(crate) mod oapp;
@@ -0,0 +1,50 @@
1
+ //! Generates an RBAC **handler override** for `accept_default_admin_transfer`
2
+ //! that also calls `set_delegate`, keeping the endpoint delegate in sync with
3
+ //! the new default admin — matching the EVM `OAppCoreRBACUpgradeable` behaviour.
4
+ //!
5
+ //! This is **not** an OApp instruction. It produces a `#[rbac_instruction]`
6
+ //! handler that reuses the RBAC-generated `StdAcceptDefaultAdminTransfer`
7
+ //! accounts struct (handler override level), so there is no duplicate struct.
8
+ //!
9
+ //! # Prerequisites
10
+ //!
11
+ //! The generated handler calls `endpoint_cpi::set_delegate`, which requires the
12
+ //! `OAppRegistry` PDA (seeds `[OAPP_SEED, oapp.key()]`) to already exist. That
13
+ //! PDA is created by `endpoint_cpi::register_oapp` — a step the `#[oapp]` macro
14
+ //! neither generates nor enforces.
15
+ //!
16
+ //! **`register_oapp` is a mandatory call in the OApp's `initialize` handler.**
17
+ //! Omitting it means `accept_default_admin_transfer` will always fail once a
18
+ //! default-admin transfer is initiated, permanently bricking the admin transfer
19
+ //! flow.
20
+
21
+ use proc_macro2::TokenStream;
22
+ use quote::quote;
23
+
24
+ /// Returns the handler function token stream to be placed inside the module.
25
+ ///
26
+ /// Uses `Context<StdAcceptDefaultAdminTransfer>` so the RBAC macro treats it
27
+ /// as a **handler override** — RBAC still generates the accounts struct, but
28
+ /// the entrypoint dispatches to this handler instead of the default `apply`.
29
+ pub(crate) fn generate() -> TokenStream {
30
+ quote! {
31
+ /// OApp override: accept admin transfer + sync endpoint delegate.
32
+ #[rbac_instruction]
33
+ pub fn accept_default_admin_transfer(ctx: &mut Context<StdAcceptDefaultAdminTransfer>) -> Result<()> {
34
+ StdAcceptDefaultAdminTransfer::apply(ctx)?;
35
+
36
+ let seeds = ctx.accounts.default_admin.signer_seeds();
37
+ ::oapp::endpoint_cpi::set_delegate(
38
+ ::oapp::endpoint::ID,
39
+ ctx.accounts.default_admin.key(), // oapp_key
40
+ &ctx.remaining_accounts,
41
+ &seeds,
42
+ ::oapp::endpoint::types::SetDelegateParams {
43
+ delegate: ctx.accounts.authority.key(),
44
+ },
45
+ )?;
46
+
47
+ Ok(())
48
+ }
49
+ }
50
+ }
@@ -0,0 +1,57 @@
1
+ //! Code generator for the `get_enforced_options` instruction.
2
+
3
+ use super::CodegenContext;
4
+ use anchor_trait::{DefaultAccounts, DefaultImpl, InstructionSpec};
5
+ use quote::{format_ident, quote};
6
+ use syn::parse_quote;
7
+
8
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
9
+ InstructionSpec {
10
+ params_type: Some(quote!(::oapp::types::GetEnforcedOptionsParams)),
11
+ return_type: quote!(Option<Vec<u8>>),
12
+ is_view: true,
13
+ default_impl: Some(DefaultImpl {
14
+ handler_ident: format_ident!("apply"),
15
+ generator: generate_default_impl,
16
+ }),
17
+ }
18
+ }
19
+
20
+ fn generate_default_impl(
21
+ ctx: &CodegenContext,
22
+ spec: &InstructionSpec<CodegenContext>,
23
+ ) -> DefaultAccounts {
24
+ let handler_ident = &spec.default_impl.as_ref().unwrap().handler_ident;
25
+ let params_type = spec.params_type.as_ref().unwrap();
26
+ let return_type = &spec.return_type;
27
+ let oapp_type = &ctx.oapp_type;
28
+
29
+ DefaultAccounts {
30
+ struct_type: parse_quote! {
31
+ #[derive(Accounts)]
32
+ #[instruction(params: #params_type)]
33
+ pub struct StdGetEnforcedOptions<'info> {
34
+ pub oapp: Account<'info, #oapp_type>,
35
+
36
+ #[account(
37
+ seeds = [
38
+ ::oapp::ENFORCED_OPTIONS_SEED,
39
+ oapp.key().as_ref(),
40
+ &params.eid.to_be_bytes(),
41
+ &params.msg_type.to_be_bytes(),
42
+ ],
43
+ bump,
44
+ )]
45
+ pub enforced_options: UncheckedAccount<'info>,
46
+ }
47
+ },
48
+ impl_block: parse_quote! {
49
+ impl StdGetEnforcedOptions<'_> {
50
+ pub fn #handler_ident(ctx: &Context<Self>, _params: &#params_type) -> Result<#return_type> {
51
+ let eo = ::oapp::utils::try_load_account::<EnforcedOptions>(&ctx.accounts.enforced_options)?;
52
+ Ok(eo.map(|eo| eo.options))
53
+ }
54
+ }
55
+ },
56
+ }
57
+ }
@@ -0,0 +1,55 @@
1
+ //! Code generator for the `get_peer` instruction.
2
+
3
+ use super::CodegenContext;
4
+ use anchor_trait::{DefaultAccounts, DefaultImpl, InstructionSpec};
5
+ use quote::{format_ident, quote};
6
+ use syn::parse_quote;
7
+
8
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
9
+ InstructionSpec {
10
+ params_type: Some(quote!(u32)),
11
+ return_type: quote!(Option<[u8; 32]>),
12
+ is_view: true,
13
+ default_impl: Some(DefaultImpl {
14
+ handler_ident: format_ident!("apply"),
15
+ generator: generate_default_impl,
16
+ }),
17
+ }
18
+ }
19
+
20
+ fn generate_default_impl(
21
+ ctx: &CodegenContext,
22
+ spec: &InstructionSpec<CodegenContext>,
23
+ ) -> DefaultAccounts {
24
+ let handler_ident = &spec.default_impl.as_ref().unwrap().handler_ident;
25
+ let params_type = spec.params_type.as_ref().unwrap();
26
+ let return_type = &spec.return_type;
27
+ let oapp_type = &ctx.oapp_type;
28
+
29
+ DefaultAccounts {
30
+ struct_type: parse_quote! {
31
+ #[derive(Accounts)]
32
+ #[instruction(eid: #params_type)]
33
+ pub struct StdGetPeer<'info> {
34
+ pub oapp: Account<'info, #oapp_type>,
35
+
36
+ #[account(
37
+ seeds = [::oapp::PEER_SEED, oapp.key().as_ref(), &eid.to_be_bytes()],
38
+ bump,
39
+ )]
40
+ pub peer: UncheckedAccount<'info>,
41
+ }
42
+ },
43
+ impl_block: parse_quote! {
44
+ impl StdGetPeer<'_> {
45
+ /// Returns `None` both when the peer PDA is uninitialized and when it
46
+ /// holds the zero address, so callers can rely on `is_some()` as an
47
+ /// existence check.
48
+ pub fn #handler_ident(ctx: &Context<Self>, _eid: &#params_type) -> Result<#return_type> {
49
+ let peer = ::oapp::utils::try_load_account::<OAppPeer>(&ctx.accounts.peer)?;
50
+ Ok(peer.and_then(|p| (p.address != [0u8; 32]).then_some(p.address)))
51
+ }
52
+ }
53
+ },
54
+ }
55
+ }
@@ -0,0 +1,87 @@
1
+ //! Code generator for the `init_enforced_options` instruction.
2
+ //!
3
+ //! Creates the EnforcedOptions PDA at the correct size for the initial options buffer.
4
+ //! Subsequent updates (including resize) use `set_enforced_options`.
5
+
6
+ use super::CodegenContext;
7
+ use anchor_trait::{DefaultAccounts, DefaultImpl, InstructionSpec};
8
+ use quote::{format_ident, quote};
9
+ use syn::parse_quote;
10
+
11
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
12
+ InstructionSpec {
13
+ params_type: Some(quote!(::oapp::types::SetEnforcedOptionsParams)),
14
+ return_type: quote!(()),
15
+ is_view: false,
16
+ default_impl: Some(DefaultImpl {
17
+ handler_ident: format_ident!("apply"),
18
+ generator: generate_default_impl,
19
+ }),
20
+ }
21
+ }
22
+
23
+ fn generate_default_impl(
24
+ ctx: &CodegenContext,
25
+ spec: &InstructionSpec<CodegenContext>,
26
+ ) -> DefaultAccounts {
27
+ let handler_ident = &spec.default_impl.as_ref().unwrap().handler_ident;
28
+ let params_type = spec.params_type.as_ref().unwrap();
29
+ let return_type = &spec.return_type;
30
+ let oapp_type = &ctx.oapp_type;
31
+ let role_type = &ctx.role_type;
32
+
33
+ DefaultAccounts {
34
+ struct_type: parse_quote! {
35
+ #[::rbac::only_role(role = #role_type::default_admin_role(), state = oapp, authority = authority)]
36
+ #[event_cpi]
37
+ #[derive(Accounts)]
38
+ #[instruction(params: #params_type)]
39
+ pub struct StdInitEnforcedOptions<'info> {
40
+ pub authority: Signer<'info>,
41
+
42
+ #[account(mut)]
43
+ pub payer: Signer<'info>,
44
+
45
+ pub oapp: Account<'info, #oapp_type>,
46
+
47
+ #[account(
48
+ init,
49
+ payer = payer,
50
+ space = EnforcedOptions::space(params.options.len()),
51
+ seeds = [
52
+ ::oapp::ENFORCED_OPTIONS_SEED,
53
+ oapp.key().as_ref(),
54
+ &params.eid.to_be_bytes(),
55
+ &params.msg_type.to_be_bytes(),
56
+ ],
57
+ bump
58
+ )]
59
+ pub enforced_options: Account<'info, EnforcedOptions>,
60
+
61
+ pub system_program: Program<'info, System>,
62
+ }
63
+ },
64
+ impl_block: parse_quote! {
65
+ impl StdInitEnforcedOptions<'_> {
66
+ pub fn #handler_ident(ctx: &mut Context<Self>, params: &#params_type) -> Result<#return_type> {
67
+ if !params.options.is_empty() {
68
+ ::oapp::options::assert_type_3(&params.options)?;
69
+ }
70
+
71
+ let enforced_options = &mut ctx.accounts.enforced_options;
72
+ enforced_options.options = params.options.clone();
73
+ enforced_options.bump = ctx.bumps.enforced_options;
74
+
75
+ emit_cpi!(::oapp::events::EnforcedOptionsSet {
76
+ oapp: ctx.accounts.oapp.key(),
77
+ eid: params.eid,
78
+ msg_type: params.msg_type,
79
+ options: params.options.clone(),
80
+ });
81
+
82
+ Ok(())
83
+ }
84
+ }
85
+ },
86
+ }
87
+ }
@@ -0,0 +1,44 @@
1
+ //! Code generator for the `is_compose_msg_sender` instruction.
2
+
3
+ use super::CodegenContext;
4
+ use anchor_trait::{DefaultAccounts, DefaultImpl, InstructionSpec};
5
+ use quote::{format_ident, quote};
6
+ use syn::parse_quote;
7
+
8
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
9
+ InstructionSpec {
10
+ params_type: Some(quote!(::oapp::types::IsComposeMsgSenderParams)),
11
+ return_type: quote!(bool),
12
+ is_view: true,
13
+ default_impl: Some(DefaultImpl {
14
+ handler_ident: format_ident!("apply"),
15
+ generator: generate_default_impl,
16
+ }),
17
+ }
18
+ }
19
+
20
+ fn generate_default_impl(
21
+ ctx: &CodegenContext,
22
+ spec: &InstructionSpec<CodegenContext>,
23
+ ) -> DefaultAccounts {
24
+ let handler_ident = &spec.default_impl.as_ref().unwrap().handler_ident;
25
+ let params_type = spec.params_type.as_ref().unwrap();
26
+ let return_type = &spec.return_type;
27
+ let oapp_type = &ctx.oapp_type;
28
+
29
+ DefaultAccounts {
30
+ struct_type: parse_quote! {
31
+ #[derive(Accounts)]
32
+ pub struct StdIsComposeMsgSender<'info> {
33
+ pub oapp: Account<'info, #oapp_type>,
34
+ }
35
+ },
36
+ impl_block: parse_quote! {
37
+ impl StdIsComposeMsgSender<'_> {
38
+ pub fn #handler_ident(ctx: &Context<Self>, params: &#params_type) -> Result<#return_type> {
39
+ Ok(params.compose_sender == ctx.accounts.oapp.key())
40
+ }
41
+ }
42
+ },
43
+ }
44
+ }
@@ -0,0 +1,33 @@
1
+ //! Code generator for the `lz_receive` instruction.
2
+ //!
3
+ //! This instruction has no default implementation — users MUST provide
4
+ //! a custom handler named `lz_receive` with `#[oapp_instruction]`.
5
+ //!
6
+ //! # Security
7
+ //!
8
+ //! The handler MUST verify that `(params.src_eid, params.sender)` corresponds
9
+ //! to a registered peer — the Endpoint does not perform this check. Bind the
10
+ //! `OAppPeer` PDA to `params.src_eid` via `seeds` and enforce
11
+ //! `peer.address == params.sender` declaratively with `constraint`:
12
+ //!
13
+ //! ```ignore
14
+ //! #[account(
15
+ //! seeds = [::oapp::PEER_SEED, oapp.key().as_ref(), &params.src_eid.to_be_bytes()],
16
+ //! bump = peer.bump,
17
+ //! constraint = peer.address == params.sender,
18
+ //! )]
19
+ //! pub peer: Account<'info, OAppPeer>,
20
+ //! ```
21
+
22
+ use super::CodegenContext;
23
+ use anchor_trait::InstructionSpec;
24
+ use quote::quote;
25
+
26
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
27
+ InstructionSpec {
28
+ params_type: Some(quote!(::oapp::types::LzReceiveParams)),
29
+ return_type: quote!(()),
30
+ is_view: false,
31
+ default_impl: None,
32
+ }
33
+ }
@@ -0,0 +1,28 @@
1
+ //! Code generator for the `lz_receive_types_info` instruction.
2
+ //!
3
+ //! Required override (no default). Users MUST provide a custom handler
4
+ //! named `lz_receive_types_info` with `#[oapp_instruction]`.
5
+ //!
6
+ //! The off-chain LayerZero Executor invokes this instruction with a fixed
7
+ //! 2-account list, in this order:
8
+ //! 1. `oapp_account` — the program-specific OApp store
9
+ //! 2. `lz_receive_types_accounts` PDA at `[LZ_RECEIVE_TYPES_SEED, oapp_account.key()]`
10
+ //!
11
+ //! Custom handlers MUST declare an `Accounts` struct matching this shape.
12
+ //! The framework does not enforce it at compile time, so a mismatch only
13
+ //! surfaces as a runtime failure when the Executor calls the deployed program.
14
+ //!
15
+ //! See: <https://docs.layerzero.network/v2/developers/solana/oapp/overview#how-lz_receive_types_v2-works>
16
+
17
+ use super::CodegenContext;
18
+ use anchor_trait::InstructionSpec;
19
+ use quote::quote;
20
+
21
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
22
+ InstructionSpec {
23
+ params_type: Some(quote!(::oapp::types::LzReceiveParams)),
24
+ return_type: quote!((u8, ::oapp::lz_receive_types_v2::LzReceiveTypesV2Accounts)),
25
+ is_view: true,
26
+ default_impl: None,
27
+ }
28
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `lz_receive_types_v2` instruction.
2
+ //!
3
+ //! This instruction has no default implementation — users MUST provide
4
+ //! a custom handler named `lz_receive_types_v2` with `#[oapp_instruction]`.
5
+
6
+ use super::CodegenContext;
7
+ use anchor_trait::InstructionSpec;
8
+ use quote::quote;
9
+
10
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
11
+ InstructionSpec {
12
+ params_type: Some(quote!(::oapp::types::LzReceiveParams)),
13
+ return_type: quote!(::oapp::lz_receive_types_v2::LzReceiveTypesV2Result),
14
+ is_view: true,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,41 @@
1
+ //! OApp instruction definitions and code generation.
2
+ //!
3
+ //! Each sub-module defines a `spec()` returning the instruction's codegen descriptor.
4
+ //! `InstructionName::spec()` maps instruction variants to their specs.
5
+
6
+ pub(crate) mod accept_default_admin_transfer;
7
+ mod get_enforced_options;
8
+ mod get_peer;
9
+ mod init_enforced_options;
10
+ mod is_compose_msg_sender;
11
+ mod lz_receive;
12
+ mod lz_receive_types_info;
13
+ mod lz_receive_types_v2;
14
+ mod next_nonce;
15
+ mod set_enforced_options;
16
+ mod set_peer;
17
+
18
+ use anchor_trait::declare_instructions;
19
+ use proc_macro2::TokenStream;
20
+
21
+ /// Domain-specific context carrying resolved types for OApp code generation.
22
+ pub(crate) struct CodegenContext {
23
+ pub role_type: TokenStream,
24
+ pub oapp_type: TokenStream,
25
+ }
26
+
27
+ declare_instructions! {
28
+ name = OAppInstructionSet;
29
+ context = CodegenContext;
30
+
31
+ SetPeer => set_peer,
32
+ GetPeer => get_peer,
33
+ InitEnforcedOptions => init_enforced_options,
34
+ SetEnforcedOptions => set_enforced_options,
35
+ GetEnforcedOptions => get_enforced_options,
36
+ NextNonce => next_nonce,
37
+ IsComposeMsgSender => is_compose_msg_sender,
38
+ LzReceiveTypesInfo => lz_receive_types_info,
39
+ LzReceiveTypesV2 => lz_receive_types_v2,
40
+ LzReceive => lz_receive,
41
+ }
@@ -0,0 +1,52 @@
1
+ //! Code generator for the `next_nonce` instruction.
2
+
3
+ use super::CodegenContext;
4
+ use anchor_trait::{DefaultAccounts, DefaultImpl, InstructionSpec};
5
+ use quote::{format_ident, quote};
6
+ use syn::parse_quote;
7
+
8
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
9
+ InstructionSpec {
10
+ params_type: Some(quote!(::oapp::types::NextNonceParams)),
11
+ return_type: quote!(u64),
12
+ is_view: true,
13
+ default_impl: Some(DefaultImpl {
14
+ handler_ident: format_ident!("apply"),
15
+ generator: generate_default_impl,
16
+ }),
17
+ }
18
+ }
19
+
20
+ fn generate_default_impl(
21
+ ctx: &CodegenContext,
22
+ spec: &InstructionSpec<CodegenContext>,
23
+ ) -> DefaultAccounts {
24
+ let handler_ident = &spec.default_impl.as_ref().unwrap().handler_ident;
25
+ let params_type = spec.params_type.as_ref().unwrap();
26
+ let return_type = &spec.return_type;
27
+ let oapp_type = &ctx.oapp_type;
28
+
29
+ DefaultAccounts {
30
+ struct_type: parse_quote! {
31
+ #[derive(Accounts)]
32
+ #[instruction(params: #params_type)]
33
+ pub struct StdNextNonce<'info> {
34
+ pub oapp: Account<'info, #oapp_type>,
35
+
36
+ #[account(
37
+ seeds = [::oapp::NONCE_SEED, oapp.key().as_ref(), &params.src_eid.to_be_bytes(), &params.sender],
38
+ bump
39
+ )]
40
+ pub nonce_account: UncheckedAccount<'info>,
41
+ }
42
+ },
43
+ impl_block: parse_quote! {
44
+ impl StdNextNonce<'_> {
45
+ pub fn #handler_ident(ctx: &Context<Self>, _params: &#params_type) -> Result<#return_type> {
46
+ // path nonce starts from 1. if 0 it means that there is no specific nonce enforcement
47
+ return Ok(0);
48
+ }
49
+ }
50
+ },
51
+ }
52
+ }