@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
package/Anchor.toml ADDED
@@ -0,0 +1,23 @@
1
+ [toolchain]
2
+ anchor_version = "1.0.2"
3
+ solana_version = "3.1.10"
4
+
5
+ [features]
6
+ seeds = false
7
+ skip-lint = false
8
+ resolution = false
9
+
10
+ [programs.localnet]
11
+ counter = "Cnt11111111111111111111111111111111111111111"
12
+
13
+ [registry]
14
+ url = "https://api.apr.dev"
15
+
16
+ [provider]
17
+ cluster = "Localnet"
18
+ wallet = "~/.config/solana/id.json"
19
+
20
+ [workspace]
21
+ members = [
22
+ "macros/examples/counter",
23
+ ]
package/Cargo.toml ADDED
@@ -0,0 +1,26 @@
1
+ [package]
2
+ name = "oapp"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+
6
+ [features]
7
+ default = []
8
+ idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build", "oapp-macros/idl-build"]
9
+
10
+ [lints.rust]
11
+ unexpected_cfgs = { level = "warn", check-cfg = ['cfg(target_os, values("solana"))'] }
12
+
13
+ [dependencies]
14
+ anchor-lang = { version = "1.0.2" }
15
+ anchor-spl = { version = "1.0.2" }
16
+ oapp-macros = { path = "macros" }
17
+ solana-address-lookup-table-interface = { version = "3.0.1", features = [
18
+ "bincode",
19
+ "bytemuck",
20
+ ] }
21
+ solana-keccak-hasher = "3.1.0"
22
+ spl-type-length-value = { version = "0.9.1", features = ["derive"] }
23
+ spl-discriminator = "0.5.2"
24
+
25
+ [profile.release]
26
+ overflow-checks = true
package/README.md ADDED
@@ -0,0 +1,181 @@
1
+ # OApp Standard
2
+
3
+ ## Overview
4
+
5
+ The OApp Standard for Solana is a unified framework that standardizes how OApp programs expose instructions, how SDKs interact with them, and how account discovery is performed.
6
+
7
+ The standard consists of two parts:
8
+
9
+ 1. **OApp Interface Standard** — a formal instruction interface (`OAppInstructionSet`) that every OApp program must implement.
10
+ 2. **OApp Info** — a program-level on-chain metadata PDA (`OAppInfo`) that enables the SDK to resolve accounts for each instruction.
11
+
12
+ The appendices document the existing **LzReceiveTypes** and **LzComposeTypes**
13
+ protocols. LzReceiveTypes handles account discovery for `lz_receive` — the one
14
+ OApp instruction not covered by `OAppInfo`; LzComposeTypes handles the follow-up
15
+ compose execution on composer programs.
16
+
17
+ | Component | Detailed Spec |
18
+ | ----------------------- | --------------------------------------------------- |
19
+ | OApp Interface Standard | [`oapp-macro.md`](./docs/oapp-macro.md) |
20
+ | OApp Info | [`oapp-info.md`](./docs/oapp-info.md) |
21
+ | LzReceiveTypes | [`lz-receive-types.md`](./docs/lz-receive-types.md) |
22
+ | LzComposeTypes | [`lz-compose-types.md`](./docs/lz-compose-types.md) |
23
+
24
+ > **Operators:** if you run your own Executor, read
25
+ > [`running-an-executor.md`](./docs/running-an-executor.md). The Executor signing keypair must
26
+ > hold nothing but SOL — this applies to both `lz_receive` and `lz_compose` execution.
27
+
28
+ ---
29
+
30
+ ## Motivation
31
+
32
+ ### No Standard Interface
33
+
34
+ The original OApp design had no standard interface. There was no definition of what instructions an OApp must expose, so each OApp was built differently and SDKs had to be written per-app.
35
+
36
+ Beyond the lack of standardization, the SDK read on-chain state by directly decoding PDA account bytes rather than calling view functions. This tightly coupled the SDK to the binary layout of each account struct — any structural change required a synchronized SDK update, and a single SDK could not support multiple deployed versions simultaneously. On other VMs, this problem does not exist because state is always accessed through stable function interfaces.
37
+
38
+ The OApp Standard addresses this by defining a formal instruction interface via `anchor-trait` (`OAppInstructionSet`), including view functions for all state-reading operations. The SDK now interacts with on-chain state through these interfaces, decoupled from underlying struct layouts.
39
+
40
+ ### No Account Discovery
41
+
42
+ A standard interface tells the SDK _what_ to call, but not _how_ to call it. On Solana, every account must be declared upfront in the transaction. The SDK cannot construct a complete instruction without knowing the exact set of accounts — including PDAs, signers, and program references — for each instruction it wants to invoke.
43
+
44
+ In the old design there was no standard mechanism for this. The SDK hardcoded account lists per OApp type, which broke whenever account layouts changed between deployments.
45
+
46
+ Even with `OAppInstructionSet` standardizing the interface, account resolution still requires additional information: which version of the standard does this program implement, and what accounts does that version use? This program-level metadata is what `OAppInfo` provides.
47
+
48
+ ---
49
+
50
+ ## OApp Interface Standard
51
+
52
+ Every OApp program exposes a standard set of instructions, generated by the `#[oapp]` and `#[rbac]` attribute macros. The SDK interacts with all of these instructions uniformly.
53
+
54
+ ### OApp Instructions
55
+
56
+ Generated by the `#[oapp]` attribute macro from `oapp`.
57
+
58
+ | Instruction | View | Has Default | Description |
59
+ | ----------------------- | ---- | ----------- | ---------------------------------------------------------- |
60
+ | `set_peer` | No | Yes | Register a trusted remote OApp |
61
+ | `get_peer` | Yes | Yes | Read registered peer address |
62
+ | `init_enforced_options` | No | Yes | Create the EnforcedOptions PDA with initial options buffer |
63
+ | `set_enforced_options` | No | Yes | Update enforced send options (with resize) |
64
+ | `get_enforced_options` | Yes | Yes | Read enforced options |
65
+ | `next_nonce` | Yes | Yes | Read next inbound nonce |
66
+ | `is_compose_msg_sender` | Yes | Yes | Validate compose message sender |
67
+ | `lz_receive_types_info` | Yes | No | Return LzReceiveTypes version and accounts |
68
+ | `lz_receive_types_v2` | Yes | No | Return accounts and instructions for lz_receive |
69
+ | `lz_receive` | No | No | Execute inbound cross-chain message |
70
+
71
+ ### RBAC Instructions
72
+
73
+ Generated by the `#[rbac]` attribute macro from `rbac`. All have default implementations.
74
+
75
+ | Instruction | Description |
76
+ | ------------------------------- | ----------------------------------------------------------- |
77
+ | `begin_default_admin_transfer` | Initiate two-step admin transfer |
78
+ | `accept_default_admin_transfer` | New admin accepts the transfer |
79
+ | `grant_role` | Grant a role to an account |
80
+ | `revoke_role` | Revoke a role from an account |
81
+ | `renounce_role` | Voluntarily renounce own role |
82
+ | `get_default_admin` | View current and pending default admin (`DefaultAdminInfo`) |
83
+
84
+ > **Note:** Under `#[oapp]`, `accept_default_admin_transfer` is unconditionally overridden with an OApp-specific handler that completes the RBAC admin transfer and then CPIs `set_delegate` on the endpoint — keeping the endpoint delegate in sync with the new admin (EVM parity with `OAppCoreRBACUpgradeable`). Programs cannot override this behavior via `#[oapp_instruction]`.
85
+ >
86
+ > **Security Note (`lz_receive`):** The Endpoint does NOT validate the inbound peer. The `lz_receive` override MUST bind an `OAppPeer` PDA to `params.src_eid` via `seeds` and enforce `peer.address == params.sender` — recommended declaratively via Anchor's `constraint`. Omitting this check lets any program impersonate a registered peer. See the `#[oapp_instruction]` rustdoc for the canonical Anchor account snippet.
87
+
88
+ ### Initialization Requirements
89
+
90
+ The `#[oapp]` macro generates instruction entrypoints, but deployment initialization remains the program's responsibility. A production OApp `initialize` handler must complete these setup steps:
91
+
92
+ | Setup item | Call | Purpose |
93
+ | ----------------- | --------------------------------------- | ---------------------------------------------------------------------------- |
94
+ | RBAC admin state | `setup_default_admin!` | Initializes the first default admin and RoleMember PDA. |
95
+ | OApp metadata | `init_oapp_base!` / app-specific helper | Writes OAppInfo metadata used by the SDK for account discovery. |
96
+ | Endpoint registry | `endpoint_cpi::register_oapp(...)` | Creates the endpoint registry entry used by delegate sync and endpoint CPIs. |
97
+
98
+ The table order is the recommended initialization order. `OAppInfo` is initialized by the application, not generated by `#[oapp]`, because account sizing and app-layer TLV entries are program-specific. Programs that want shared SDK discovery should create and populate this PDA during initialization; programs that intentionally omit it are outside the standardized discovery path. Before revoking upgrade authority, confirm the intended `OAppInfo` metadata is present.
99
+
100
+ Missing `register_oapp` makes the OApp-specific `accept_default_admin_transfer` handler's `set_delegate` CPI fail, which can permanently brick admin transfers.
101
+
102
+ ### Column Definitions
103
+
104
+ - **Has Default = Yes** — the macro auto-generates a complete context struct and handler. OApp developers get correct behavior without writing any code for these instructions.
105
+ - **Has Default = No** — a required override. The developer must provide a handler via `#[oapp_instruction]`. Omitting it is a compile error.
106
+ - **View = Yes** — the instruction uses an immutable context and returns a value. The SDK calls these to read on-chain state without submitting a transaction.
107
+
108
+ ### The `#[oapp]` Macro
109
+
110
+ The `#[oapp]` attribute macro wires an OApp program module into `OAppInstructionSet`. It generates default context structs and entrypoints for all instructions where a default exists, applies RBAC enforcement on admin instructions, and enforces that all required overrides are provided at compile time.
111
+
112
+ For the full macro API, override model (Default / Override), and generated code details, see [`oapp-macro.md`](./docs/oapp-macro.md).
113
+
114
+ ### Implementation Notes
115
+
116
+ #### PDA Seed Helpers
117
+
118
+ Anchor 1.0.0 relaxed `#[account(seeds = ...)]` parsing to support more flexible PDA seed expressions, including helper calls. OApp programs should use canonical seed helper functions for non-trivial PDA shapes instead of repeating inline byte conversions in every account constraint.
119
+
120
+ ```rust
121
+ #[account(
122
+ seeds = OAppPeer::seeds(&oapp.key(), params.eid),
123
+ bump = peer.bump
124
+ )]
125
+ pub peer: Account<'info, OAppPeer>,
126
+ ```
127
+
128
+ The helper owns the exact seed layout and byte encoding:
129
+
130
+ ```rust
131
+ pub fn lz_receive_types_seeds(oapp_key: &Pubkey) -> &'static [&'static [u8]] {
132
+ let key_bytes: &'static [u8; 32] = Box::leak(Box::new(oapp_key.to_bytes()));
133
+ Box::leak(Box::new([LZ_RECEIVE_TYPES_SEED, key_bytes.as_slice()]))
134
+ }
135
+ ```
136
+
137
+ This pattern keeps account constraints readable and makes PDA layouts discoverable in one place. The `Box::leak` form is SVM-only: each invocation leaks a small allocation, which is acceptable inside an on-chain instruction because the VM allocator is discarded at the end of the transaction. Do not use these helpers from long-running off-chain Rust processes such as native test runners, indexers, or CLIs; derive those PDAs with stack-local byte arrays instead.
138
+
139
+ ---
140
+
141
+ ## OApp Info
142
+
143
+ Even with a standardized interface, the SDK needs per-program metadata to construct complete transactions. `OAppInfo` is a versioned, extensible PDA that each OApp program may publish on-chain for standardized SDK discovery, giving the SDK everything it needs in a single read.
144
+
145
+ Key concepts:
146
+
147
+ - **`OAppInfo` PDA** — one per program, derived from `[OAPP_INFO_SEED]` (`find_program_address` already namespaces the result to the calling program). Stores raw SPL TLV entries from byte 0 (no Anchor discriminator); each framework layer appends its own typed entry as a sibling.
148
+ - **`OAppBase` entry** — always written first. Carries two orthogonal version axes — `schema_version` (Borsh layout of the entry) and `idl_version` (`{ major, minor }` covering the OApp instruction set and emitted events) — plus an `app_discriminator` pointing to the optional app-layer sibling entry.
149
+ - **`lz_receive` excluded** — no default implementation; account discovery is delegated entirely to the LzReceiveTypes protocol.
150
+
151
+ For the full `OAppInfo` struct, SDK decision flow, app-specific extensions, and the relationship with LzReceiveTypes, see [`oapp-info.md`](./docs/oapp-info.md).
152
+
153
+ ---
154
+
155
+ ## Appendix: LzReceiveTypes
156
+
157
+ > LzReceiveTypes is an existing protocol that predates the OApp Standard. It is documented here for completeness, as it handles account discovery for `lz_receive` — the one instruction not covered by `OAppInfo`.
158
+
159
+ `lz_receive` is unique among OApp instructions: its accounts depend on the message content and cannot be resolved off-chain from a predefined template. The LzReceiveTypes protocol solves this by providing an on-chain instruction (`lz_receive_types_v2`) that returns the exact accounts and instructions needed for a given message.
160
+
161
+ Key concepts:
162
+
163
+ - **Two-stage execution** — the Executor first calls `lz_receive_types_v2` to discover accounts, then submits the full `lz_receive` transaction.
164
+ - **Transaction-based model (V2)** — replaces the original CPI-based model to avoid Solana's CPI depth limit of 4, enabling ABA messaging patterns.
165
+ - **`AddressLocator` enum** — compact account referencing via direct address, ALT index, payer, additional signers, or executor context account.
166
+ - **Multi-instruction support** — `lz_receive_types_v2` can return multiple instructions (preprocessing steps + `lz_receive`), executed atomically in a single transaction.
167
+ - **Versioned via `lz_receive_types_info`** — the protocol self-describes its version; the Executor reads this to determine how to interact with the OApp.
168
+
169
+ For the full protocol design, data structures, execution flow, and V2 transaction model, see [`lz-receive-types.md`](./docs/lz-receive-types.md).
170
+
171
+ ---
172
+
173
+ ## Appendix: LzComposeTypes
174
+
175
+ `lz_compose` is the follow-up execution phase for composed messages emitted by
176
+ `lz_receive`. LzComposeTypes uses the same V2 account reference and transaction
177
+ model as LzReceiveTypes, but its interface is scoped to composer programs and
178
+ `LzComposeParams`.
179
+
180
+ For the compose-specific interface and differences, see
181
+ [`lz-compose-types.md`](./docs/lz-compose-types.md).