@layerzerolabs/oft-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 ADDED
@@ -0,0 +1,7 @@
1
+ [toolchain]
2
+ anchor_version = "1.0.2"
3
+ solana_version = "3.1.10"
4
+
5
+ [provider]
6
+ cluster = "Localnet"
7
+ wallet = "/dev/null"
package/Cargo.toml ADDED
@@ -0,0 +1,33 @@
1
+ [package]
2
+ name = "oft"
3
+ version = "0.1.0"
4
+ description = "Reusable OFT crate for Anchor programs"
5
+ edition = "2021"
6
+
7
+ [lib]
8
+ crate-type = ["lib"]
9
+ name = "oft"
10
+
11
+ [profile.release]
12
+ overflow-checks = true
13
+
14
+ [features]
15
+ default = []
16
+ idl-build = ["anchor-lang/idl-build", "anchor-spl/idl-build"]
17
+
18
+ [lints.rust]
19
+ unexpected_cfgs = { level = "allow", check-cfg = [
20
+ "cfg(feature, values(\"anchor-debug\"))",
21
+ ] }
22
+
23
+ [dependencies]
24
+ anchor-lang = { version = "1.0.2", features = ["event-cpi"] }
25
+ anchor-spl = { version = "1.0.2" }
26
+ oft-macros = { path = "macros" }
27
+ spl-type-length-value = { version = "0.9.1", features = ["derive"] }
28
+ spl-discriminator = "0.5.2"
29
+
30
+
31
+
32
+ [dev-dependencies]
33
+ oapp = { path = "dependencies/oapp-solana-contracts" }
@@ -0,0 +1,128 @@
1
+ # This file is automatically @generated by Cargo.
2
+ # It is not intended for manual editing.
3
+ version = 4
4
+
5
+ [[package]]
6
+ name = "anchor-trait"
7
+ version = "0.1.0"
8
+ dependencies = [
9
+ "itertools",
10
+ "proc-macro2",
11
+ "quote",
12
+ "syn",
13
+ ]
14
+
15
+ [[package]]
16
+ name = "darling"
17
+ version = "0.20.11"
18
+ source = "registry+https://github.com/rust-lang/crates.io-index"
19
+ checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
20
+ dependencies = [
21
+ "darling_core",
22
+ "darling_macro",
23
+ ]
24
+
25
+ [[package]]
26
+ name = "darling_core"
27
+ version = "0.20.11"
28
+ source = "registry+https://github.com/rust-lang/crates.io-index"
29
+ checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
30
+ dependencies = [
31
+ "fnv",
32
+ "ident_case",
33
+ "proc-macro2",
34
+ "quote",
35
+ "strsim",
36
+ "syn",
37
+ ]
38
+
39
+ [[package]]
40
+ name = "darling_macro"
41
+ version = "0.20.11"
42
+ source = "registry+https://github.com/rust-lang/crates.io-index"
43
+ checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
44
+ dependencies = [
45
+ "darling_core",
46
+ "quote",
47
+ "syn",
48
+ ]
49
+
50
+ [[package]]
51
+ name = "either"
52
+ version = "1.15.0"
53
+ source = "registry+https://github.com/rust-lang/crates.io-index"
54
+ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
55
+
56
+ [[package]]
57
+ name = "fnv"
58
+ version = "1.0.7"
59
+ source = "registry+https://github.com/rust-lang/crates.io-index"
60
+ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
61
+
62
+ [[package]]
63
+ name = "ident_case"
64
+ version = "1.0.1"
65
+ source = "registry+https://github.com/rust-lang/crates.io-index"
66
+ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
67
+
68
+ [[package]]
69
+ name = "itertools"
70
+ version = "0.14.0"
71
+ source = "registry+https://github.com/rust-lang/crates.io-index"
72
+ checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285"
73
+ dependencies = [
74
+ "either",
75
+ ]
76
+
77
+ [[package]]
78
+ name = "oft-macros"
79
+ version = "0.1.0"
80
+ dependencies = [
81
+ "anchor-trait",
82
+ "darling",
83
+ "itertools",
84
+ "proc-macro2",
85
+ "quote",
86
+ "syn",
87
+ ]
88
+
89
+ [[package]]
90
+ name = "proc-macro2"
91
+ version = "1.0.106"
92
+ source = "registry+https://github.com/rust-lang/crates.io-index"
93
+ checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
94
+ dependencies = [
95
+ "unicode-ident",
96
+ ]
97
+
98
+ [[package]]
99
+ name = "quote"
100
+ version = "1.0.45"
101
+ source = "registry+https://github.com/rust-lang/crates.io-index"
102
+ checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
103
+ dependencies = [
104
+ "proc-macro2",
105
+ ]
106
+
107
+ [[package]]
108
+ name = "strsim"
109
+ version = "0.11.1"
110
+ source = "registry+https://github.com/rust-lang/crates.io-index"
111
+ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
112
+
113
+ [[package]]
114
+ name = "syn"
115
+ version = "2.0.117"
116
+ source = "registry+https://github.com/rust-lang/crates.io-index"
117
+ checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
118
+ dependencies = [
119
+ "proc-macro2",
120
+ "quote",
121
+ "unicode-ident",
122
+ ]
123
+
124
+ [[package]]
125
+ name = "unicode-ident"
126
+ version = "1.0.24"
127
+ source = "registry+https://github.com/rust-lang/crates.io-index"
128
+ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
@@ -0,0 +1,16 @@
1
+ [package]
2
+ name = "oft-macros"
3
+ version = "0.1.0"
4
+ description = "Procedural macros for OFT in Anchor programs"
5
+ edition = "2021"
6
+
7
+ [lib]
8
+ proc-macro = true
9
+
10
+ [dependencies]
11
+ darling = "0.20"
12
+ proc-macro2 = "1.0"
13
+ quote = "1.0"
14
+ syn = { version = "2.0", features = ["full", "parsing"] }
15
+ itertools = "0.14"
16
+ anchor-trait = { path = "../dependencies/framework-solana-anchor-trait" }
@@ -0,0 +1,26 @@
1
+ mod oft_type;
2
+ mod quote_oft;
3
+ mod quote_send;
4
+ mod send;
5
+ mod shared_decimals;
6
+ mod token;
7
+
8
+ use anchor_trait::declare_instructions;
9
+
10
+ /// OFT codegen context type used to bind `InstructionSpec` and `InstructionSet`.
11
+ ///
12
+ /// The current OFT instruction specs do not read shared context data, so this
13
+ /// remains empty until a generated instruction needs domain-specific inputs.
14
+ pub(crate) struct CodegenContext {}
15
+
16
+ declare_instructions! {
17
+ name = OftInstructionSet;
18
+ context = CodegenContext;
19
+
20
+ OftType => oft_type,
21
+ Token => token,
22
+ SharedDecimals => shared_decimals,
23
+ QuoteOft => quote_oft,
24
+ QuoteSend => quote_send,
25
+ Send => send,
26
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `oft_type` instruction.
2
+ //!
3
+ //! Solana: returns the OFT mode.
4
+ //! No default implementation — users MUST provide a custom handler.
5
+
6
+ use crate::instructions::CodegenContext;
7
+ use anchor_trait::InstructionSpec;
8
+ use quote::quote;
9
+
10
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
11
+ InstructionSpec {
12
+ params_type: None,
13
+ return_type: quote!(::oft::types::OftType),
14
+ is_view: true,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `quote_oft` instruction.
2
+ //!
3
+ //! EVM: IOFT.quoteOFT(SendParam) → (OFTLimit, OFTFeeDetail[], OFTReceipt)
4
+ //! No default implementation — users MUST provide a custom handler.
5
+
6
+ use crate::instructions::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!(::oft::types::QuoteOFTParams)),
13
+ return_type: quote!(::oft::types::QuoteOFTResult),
14
+ is_view: true,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `quote_send` instruction.
2
+ //!
3
+ //! EVM: IOFT.quoteSend(SendParam, bool _payInLzToken) → MessagingFee
4
+ //! No default implementation — users MUST provide a custom handler.
5
+
6
+ use crate::instructions::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!(::oft::types::QuoteSendParams)),
13
+ return_type: quote!((::oapp::endpoint::types::MessagingFee)),
14
+ is_view: true,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `send` instruction.
2
+ //!
3
+ //! EVM: IOFT.send(SendParam, MessagingFee, address _refundAddress) → (MessagingReceipt, OFTReceipt)
4
+ //! No default implementation — users MUST provide a custom handler.
5
+
6
+ use crate::instructions::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!(::oft::types::SendParams)),
13
+ return_type: quote!((::oapp::endpoint::types::MessagingReceipt, ::oft::types::OFTReceipt)),
14
+ is_view: false,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `shared_decimals` instruction.
2
+ //!
3
+ //! EVM: IOFT.sharedDecimals() → uint8
4
+ //! No default implementation — users MUST provide a custom handler.
5
+
6
+ use crate::instructions::CodegenContext;
7
+ use anchor_trait::InstructionSpec;
8
+ use quote::quote;
9
+
10
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
11
+ InstructionSpec {
12
+ params_type: None,
13
+ return_type: quote!(u8),
14
+ is_view: true,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,17 @@
1
+ //! Code generator for the `oft_token` instruction.
2
+ //!
3
+ //! EVM: IOFT.token() → address
4
+ //! No default implementation — users MUST provide a custom handler.
5
+
6
+ use crate::instructions::CodegenContext;
7
+ use anchor_trait::InstructionSpec;
8
+ use quote::quote;
9
+
10
+ pub(super) fn spec() -> InstructionSpec<CodegenContext> {
11
+ InstructionSpec {
12
+ params_type: None,
13
+ return_type: quote!(Pubkey),
14
+ is_view: true,
15
+ default_impl: None,
16
+ }
17
+ }
@@ -0,0 +1,38 @@
1
+ use proc_macro::TokenStream;
2
+
3
+ mod instructions;
4
+ mod oft;
5
+
6
+ /// Attribute macro for generating OApp program entrypoints via the new oapp2 pipeline.
7
+ ///
8
+ /// This macro is introduced as a clean-slate path and is intentionally separate
9
+ /// from `#[oapp]`.
10
+ ///
11
+ /// # Attributes
12
+ ///
13
+ /// - `state = <path>`: required. The OFT program state account type.
14
+ /// - `role_type = <path>`: required. The RBAC role enum type.
15
+ ///
16
+ /// The role enum should implement `rbac::RoleType`. With the built-in derive,
17
+ /// the `#[default]` variant is the default-admin role and must use discriminant
18
+ /// `0`.
19
+ ///
20
+ /// ```ignore
21
+ /// #[oft(state = OFTStore, role_type = crate::RoleType)]
22
+ /// mod oft_instructions {}
23
+ /// ```
24
+ #[proc_macro_attribute]
25
+ pub fn oft(attr: TokenStream, item: TokenStream) -> TokenStream {
26
+ oft::expand(attr.into(), item.into())
27
+ .unwrap_or_else(|err| err.to_compile_error())
28
+ .into()
29
+ }
30
+
31
+ /// This attribute is used to override the default OFT instruction handlers.
32
+ ///
33
+ /// The attribute itself is a no-op; `#[oft]` detects it and routes the
34
+ /// overridden handler into generated instruction entrypoints.
35
+ #[proc_macro_attribute]
36
+ pub fn oft_instruction(_attr: TokenStream, item: TokenStream) -> TokenStream {
37
+ item
38
+ }
@@ -0,0 +1,61 @@
1
+ //! Top-level `#[oft]` attribute macro implementation.
2
+ //!
3
+ //! Flow: parse attribute inputs, then collect overrides → generate instructions → assemble.
4
+
5
+ use crate::instructions::{CodegenContext, OftInstructionSet};
6
+ use anchor_trait::{generate_instructions, Assembly, InstructionOverrides, OverrideConfig};
7
+ use darling::{ast::NestedMeta, FromMeta};
8
+ use proc_macro2::TokenStream;
9
+ use quote::quote;
10
+ use syn::{ItemMod, Path};
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // Attribute parsing
14
+ // ---------------------------------------------------------------------------
15
+
16
+ #[derive(FromMeta)]
17
+ struct OFTAttr {
18
+ /// The program state account TYPE (e.g., OFTStore).
19
+ state: Path,
20
+ /// The RBAC role enum TYPE.
21
+ role_type: Path,
22
+ }
23
+
24
+ const OVERRIDE_CONFIG: OverrideConfig =
25
+ OverrideConfig { attr_name: "oft_instruction", domain: "OFT" };
26
+
27
+ // ---------------------------------------------------------------------------
28
+ // Entry point
29
+ // ---------------------------------------------------------------------------
30
+
31
+ pub fn expand(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
32
+ // Parse attribute inputs and the annotated module.
33
+ let attr_args = NestedMeta::parse_meta_list(attr)?;
34
+ let attrs = OFTAttr::from_list(&attr_args)?;
35
+ let module = syn::parse2::<ItemMod>(item)?;
36
+
37
+ let oft_type = &attrs.state;
38
+ let role_type = &attrs.role_type;
39
+
40
+ // 1. Collect overrides
41
+ let overrides = InstructionOverrides::collect(&module, OVERRIDE_CONFIG)?;
42
+
43
+ // 2. Generate instructions
44
+ let codegen_ctx = CodegenContext {};
45
+ let instructions = generate_instructions::<OftInstructionSet>(&codegen_ctx, &overrides)?;
46
+
47
+ // 3. Assemble output
48
+ let extra_mod_attrs = quote! {
49
+ #[::oapp::oapp(state = #oft_type, role_type = #role_type)]
50
+ };
51
+
52
+ Ok(Assembly {
53
+ prelude: TokenStream::new(),
54
+ overrides,
55
+ instructions,
56
+ extra_mod_attrs,
57
+ source_mod: module,
58
+ extend_macro_name: None,
59
+ }
60
+ .assemble())
61
+ }
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@layerzerolabs/oft-solana-contracts",
3
+ "version": "1.2.11",
4
+ "private": false,
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "files": [
8
+ "src",
9
+ "macros",
10
+ "Cargo.toml",
11
+ "Anchor.toml",
12
+ "rust-toolchain.toml",
13
+ "rustfmt.toml"
14
+ ],
15
+ "dependencies": {
16
+ "@layerzerolabs/oapp-solana-contracts": "1.2.11",
17
+ "@layerzerolabs/framework-solana-anchor-trait": "1.2.11"
18
+ },
19
+ "devDependencies": {
20
+ "@layerzerolabs/build-utils-rust": "1.2.11",
21
+ "@layerzerolabs/tsup-configuration": "1.2.11",
22
+ "@layerzerolabs/typescript-configuration": "1.2.11",
23
+ "@layerzerolabs/vm-tooling-solana": "1.2.11"
24
+ },
25
+ "publishConfig": {
26
+ "access": "restricted",
27
+ "registry": "https://registry.npmjs.org/"
28
+ },
29
+ "externalRepoConfig": {
30
+ "targets": [
31
+ "audit-external",
32
+ "usdai-internal"
33
+ ]
34
+ },
35
+ "implicitDependencies": {
36
+ "@layerzerolabs/framework-solana-anchor-trait": "workspace:*",
37
+ "@layerzerolabs/oapp-solana-contracts": "workspace:*"
38
+ },
39
+ "scripts": {
40
+ "build": "pnpm resolve-dependencies && pnpm exec lz-tool --script 'cargo build' anchor",
41
+ "build:native": "pnpm resolve-dependencies && pnpm exec lz-tool --docker-platform native --script 'cargo build' anchor",
42
+ "check": "pnpm exec lz-tool --script 'cargo check' anchor",
43
+ "clean": "rm -rf dependencies target",
44
+ "format": "pnpm exec lz-tool --script 'cargo +nightly-2025-06-01 fmt' anchor",
45
+ "format:check": "pnpm exec lz-tool --script 'cargo +nightly-2025-06-01 fmt -- --check' anchor",
46
+ "lint": "pnpm resolve-dependencies && pnpm exec lz-tool --script 'cargo +nightly-2025-06-01 fmt -- --check && cargo clippy --all-targets' anchor",
47
+ "resolve-dependencies": "pnpm exec build-utils-rust resolve",
48
+ "test": "pnpm test:rust",
49
+ "test:rust": "pnpm resolve-dependencies && pnpm exec lz-tool --script 'cargo test -p oft --lib --tests' anchor",
50
+ "test:snapshot:check": "pnpm exec turbo-snapshot check",
51
+ "test:snapshot:update": "pnpm exec turbo-snapshot update"
52
+ }
53
+ }
@@ -0,0 +1,3 @@
1
+ [toolchain]
2
+ channel = "1.92.0"
3
+ components = ["rustfmt", "clippy"]
package/rustfmt.toml ADDED
@@ -0,0 +1,23 @@
1
+ # Basic
2
+ hard_tabs = false
3
+ max_width = 100
4
+ use_small_heuristics = "Max"
5
+ # Imports
6
+ imports_granularity = "Crate"
7
+ reorder_imports = true
8
+ # Consistency
9
+ newline_style = "Unix"
10
+ # Format comments
11
+ comment_width = 100
12
+ wrap_comments = true
13
+ # Misc
14
+ chain_width = 80
15
+ spaces_around_ranges = false
16
+ binop_separator = "Front"
17
+ reorder_impl_items = false
18
+ match_arm_leading_pipes = "Preserve"
19
+ match_arm_blocks = true
20
+ match_block_trailing_comma = true
21
+ trailing_comma = "Vertical"
22
+ trailing_semicolon = true
23
+ use_field_init_shorthand = true
package/src/events.rs ADDED
@@ -0,0 +1,45 @@
1
+ use anchor_lang::prelude::*;
2
+
3
+ // ================================ OFT Events ================================
4
+ // EVM alignment: IOFT.sol
5
+ //
6
+ // Every event carries an `oft_store` pubkey: a single OFT program manages many
7
+ // independent OFTStore instances (one per OFT deployment, mirroring how the
8
+ // SPL Token program manages many `Mint` accounts), so events carry the store
9
+ // pubkey to let off-chain consumers attribute each event to the right instance.
10
+ //
11
+ // In OFTSent / OFTReceived, `oft_store` is appended LAST (unlike newer events
12
+ // where it is the first field). Defensive choice: these two events predated
13
+ // the field, and appending preserves the existing prefix layout in case any
14
+ // decoder relies on field order. Do not reorder.
15
+
16
+ #[event]
17
+ pub struct OFTSent {
18
+ pub guid: [u8; 32],
19
+ pub dst_eid: u32,
20
+ // `from` is the token account. `sender` is the signer/authority at send time.
21
+ // Token account authority can change later, so `sender` gives indexers a stable initiator
22
+ // record.
23
+ pub from: Pubkey,
24
+ pub amount_sent_ld: u64,
25
+ pub amount_received_ld: u64,
26
+ pub sender: Pubkey,
27
+ pub oft_store: Pubkey,
28
+ }
29
+
30
+ #[event]
31
+ pub struct OFTReceived {
32
+ pub guid: [u8; 32],
33
+ pub src_eid: u32,
34
+ pub to: Pubkey,
35
+ pub amount_received_ld: u64,
36
+ pub oft_store: Pubkey,
37
+ }
38
+
39
+ // ================================ ALT Events ================================
40
+
41
+ #[event]
42
+ pub struct AddressLookupTablesSet {
43
+ pub oft_store: Pubkey,
44
+ pub alts: Vec<Pubkey>,
45
+ }
package/src/lib.rs ADDED
@@ -0,0 +1,8 @@
1
+ pub mod events;
2
+ pub mod oft_info;
3
+ pub mod types;
4
+
5
+ // Re-export procedural macros
6
+ pub use oft_macros::*;
7
+
8
+ pub const OFT_SEED: &[u8] = b"OFT";
@@ -0,0 +1,93 @@
1
+ use anchor_lang::prelude::{borsh, AnchorDeserialize, AnchorSerialize, InitSpace};
2
+ use anchor_spl::token_interface::spl_token_metadata_interface::solana_borsh;
3
+ use spl_discriminator::SplDiscriminate;
4
+ use spl_type_length_value::SplBorshVariableLenPack;
5
+
6
+ pub const OFT_INFO_SCHEMA_VERSION: u8 = 1;
7
+
8
+ /// TLV entry type for the OFT layer.
9
+ ///
10
+ /// `OftInfo` is the OFT-family record inside the flat OAppInfo TLV container.
11
+ /// `OAppBase.app_discriminator` can route to this entry, and this entry owns the
12
+ /// `project_discriminator` route field for implementation-specific records such
13
+ /// as `ConsoleOftInfo`.
14
+ #[derive(
15
+ Clone, AnchorSerialize, AnchorDeserialize, InitSpace, SplDiscriminate, SplBorshVariableLenPack,
16
+ )]
17
+ #[discriminator_hash_input("oft::info")]
18
+ pub struct OftInfo {
19
+ /// Borsh schema version of this entry. Bumps when fields are added,
20
+ /// removed, reordered, or change type. The SDK reads this first to select
21
+ /// the correct parser for the remaining bytes.
22
+ pub schema_version: u8,
23
+ /// SPL discriminator of the implementation-specific TLV entry, if any.
24
+ pub project_discriminator: [u8; 8],
25
+ }
26
+
27
+ impl OftInfo {
28
+ /// Creates an `OftInfo` at the current schema version with the given
29
+ /// project discriminator.
30
+ pub fn new(project_discriminator: [u8; 8]) -> Self {
31
+ Self { schema_version: OFT_INFO_SCHEMA_VERSION, project_discriminator }
32
+ }
33
+ }
34
+
35
+ #[cfg(test)]
36
+ mod tests {
37
+ use super::*;
38
+ use anchor_lang::Space;
39
+ use oapp::oapp_info::{TlvSpace, TLV_HEADER_BYTES};
40
+ use spl_type_length_value::{
41
+ state::{TlvState, TlvStateBorrowed, TlvStateMut},
42
+ variable_len_pack::VariableLenPack,
43
+ };
44
+
45
+ fn write_entry(entry: OftInfo) -> Vec<u8> {
46
+ let mut buf = vec![0u8; OftInfo::TLV_SPACE];
47
+ let mut state = TlvStateMut::unpack(&mut buf[..]).unwrap();
48
+ let len = entry.get_packed_len().unwrap();
49
+ state.alloc::<OftInfo>(len, false).unwrap();
50
+ state.pack_first_variable_len_value(&entry).unwrap();
51
+ buf
52
+ }
53
+
54
+ fn read_entry(buf: &[u8]) -> OftInfo {
55
+ let state = TlvStateBorrowed::unpack(buf).unwrap();
56
+ state.get_first_variable_len_value::<OftInfo>().unwrap()
57
+ }
58
+
59
+ const TEST_DISC_A: [u8; 8] = [1, 2, 3, 4, 5, 6, 7, 8];
60
+ const TEST_DISC_B: [u8; 8] = [10, 20, 30, 40, 50, 60, 70, 80];
61
+
62
+ #[test]
63
+ fn entry_packed_len_matches_tlv_space() {
64
+ let entry = OftInfo::new(TEST_DISC_A);
65
+ assert_eq!(entry.get_packed_len().unwrap(), OftInfo::INIT_SPACE);
66
+ assert_eq!(entry.get_packed_len().unwrap() + TLV_HEADER_BYTES, OftInfo::TLV_SPACE);
67
+ }
68
+
69
+ #[test]
70
+ fn tlv_round_trip_default_versions() {
71
+ let entry = OftInfo::new(TEST_DISC_A);
72
+ let buf = write_entry(entry);
73
+ let out = read_entry(&buf);
74
+
75
+ assert_eq!(out.schema_version, OFT_INFO_SCHEMA_VERSION);
76
+ assert_eq!(out.project_discriminator, TEST_DISC_A);
77
+ }
78
+
79
+ #[test]
80
+ fn tlv_round_trip_different_discriminators() {
81
+ let entry_a = OftInfo::new(TEST_DISC_A);
82
+ let entry_b = OftInfo::new(TEST_DISC_B);
83
+
84
+ let buf_a = write_entry(entry_a);
85
+ let buf_b = write_entry(entry_b);
86
+ let out_a = read_entry(&buf_a);
87
+ let out_b = read_entry(&buf_b);
88
+
89
+ assert_eq!(out_a.project_discriminator, TEST_DISC_A);
90
+ assert_eq!(out_b.project_discriminator, TEST_DISC_B);
91
+ assert_ne!(out_a.project_discriminator, out_b.project_discriminator);
92
+ }
93
+ }
package/src/types.rs ADDED
@@ -0,0 +1,151 @@
1
+ use anchor_lang::prelude::*;
2
+
3
+ // ================================ OFT (IOFT) ================================
4
+ // EVM alignment: IOFT.sol
5
+
6
+ /// Token custody model for the OFT.
7
+ ///
8
+ /// Consuming programs store this in their runtime state; it is not part of the
9
+ /// `OftInfo` discovery entry.
10
+ #[derive(InitSpace, Clone, AnchorSerialize, AnchorDeserialize, PartialEq, Eq)]
11
+ #[borsh(use_discriminant = true)]
12
+ #[repr(u8)]
13
+ pub enum OftType {
14
+ /// Tokens are minted on receive and burned on send.
15
+ BurnMint = 0,
16
+ /// Tokens are unlocked on receive and locked on send.
17
+ LockUnlock = 1,
18
+ }
19
+
20
+ /// Struct representing OFT receipt information.
21
+ /// EVM: OFTReceipt
22
+ #[derive(Clone, AnchorSerialize, AnchorDeserialize)]
23
+ pub struct OFTReceipt {
24
+ /// Amount of tokens ACTUALLY debited from the sender in local decimals.
25
+ pub amount_sent_ld: u64,
26
+ /// Amount of tokens to be received on the remote side.
27
+ /// In non-default implementations, this COULD differ from `amount_sent_ld`.
28
+ pub amount_received_ld: u64,
29
+ }
30
+
31
+ /// Struct representing OFT limit information.
32
+ /// These amounts can change dynamically and are up to the specific OFT implementation.
33
+ /// EVM: OFTLimit
34
+ #[derive(Clone, AnchorSerialize, AnchorDeserialize)]
35
+ pub struct OFTLimit {
36
+ /// Minimum amount in local decimals that can be sent to the recipient.
37
+ pub min_amount_ld: u64,
38
+ /// Maximum amount in local decimals that can be sent to the recipient.
39
+ pub max_amount_ld: u64,
40
+ }
41
+
42
+ /// Struct representing OFT fee details.
43
+ /// Future-proof mechanism to provide a standardized way to communicate fees to things like a UI.
44
+ /// EVM: OFTFeeDetail
45
+ #[derive(Clone, AnchorSerialize, AnchorDeserialize)]
46
+ pub struct OFTFeeDetail {
47
+ /// Amount of the fee in local decimals. Negative values represent rebates.
48
+ pub fee_amount_ld: i128,
49
+ /// Description of the fee.
50
+ pub description: String,
51
+ }
52
+
53
+ /// Return type for the `quoteOFT` operation.
54
+ /// Provides limits, fee breakdown, and receipt data for an OFT quote.
55
+ /// EVM: IOFT.quoteOFT(SendParam calldata _sendParam) returns (OFTLimit, OFTFeeDetail[], OFTReceipt)
56
+ #[derive(Clone, AnchorSerialize, AnchorDeserialize)]
57
+ pub struct QuoteOFTResult {
58
+ /// The OFT limit information (min/max amounts).
59
+ pub oft_limit: OFTLimit,
60
+ /// The details of OFT fees.
61
+ pub oft_fee_details: Vec<OFTFeeDetail>,
62
+ /// The OFT receipt information.
63
+ pub oft_receipt: OFTReceipt,
64
+ }
65
+
66
+ /// Parameters for the `send()` operation.
67
+ /// Combines the EVM `SendParam` struct with `MessagingFee` fields.
68
+ /// EVM: IOFT.send(SendParam calldata _sendParam, MessagingFee calldata _fee, address
69
+ /// _refundAddress)
70
+ ///
71
+ /// Note: Unlike the EVM `SendParam`, this struct does not include an `oftCmd` field.
72
+ /// Custom OFT implementations that need OFT commands should pass them via a separate PDA account.
73
+ #[derive(Clone, AnchorSerialize, AnchorDeserialize)]
74
+ pub struct SendParams {
75
+ /// Destination endpoint ID.
76
+ pub dst_eid: u32,
77
+ /// Recipient address (bytes32).
78
+ pub to: [u8; 32],
79
+ /// Amount to send in local decimals.
80
+ pub amount_ld: u64,
81
+ /// Minimum amount to send in local decimals (slippage protection).
82
+ pub min_amount_ld: u64,
83
+ /// Additional options supplied by the caller to be used in the LayerZero message.
84
+ pub extra_options: Vec<u8>,
85
+ /// The composed message for the send() operation. `None` if no composed message.
86
+ pub compose_msg: Option<Vec<u8>>,
87
+ /// The native fee for the LayerZero message (from MessagingFee.nativeFee).
88
+ pub native_fee: u64,
89
+ /// The lzToken fee for the LayerZero message (from MessagingFee.lzTokenFee).
90
+ pub lz_token_fee: u64,
91
+ }
92
+
93
+ /// Parameters for the `quoteSend()` operation.
94
+ /// Provides a quote for the send() operation.
95
+ /// EVM: IOFT.quoteSend(SendParam calldata _sendParam, bool _payInLzToken) returns (MessagingFee)
96
+ ///
97
+ /// Note: Unlike the EVM `SendParam`, this struct does not include an `oftCmd` field.
98
+ /// Custom OFT implementations that need OFT commands should pass them via a separate PDA account.
99
+ #[derive(Clone, AnchorSerialize, AnchorDeserialize)]
100
+ pub struct QuoteSendParams {
101
+ /// Destination endpoint ID.
102
+ pub dst_eid: u32,
103
+ /// Recipient address (bytes32).
104
+ pub to: [u8; 32],
105
+ /// Amount to send in local decimals.
106
+ pub amount_ld: u64,
107
+ /// Minimum amount to send in local decimals (slippage protection).
108
+ pub min_amount_ld: u64,
109
+ /// Additional options supplied by the caller to be used in the LayerZero message.
110
+ pub extra_options: Vec<u8>,
111
+ /// The composed message for the send() operation. `None` if no composed message.
112
+ pub compose_msg: Option<Vec<u8>>,
113
+ /// Flag indicating whether the caller is paying in the LZ token.
114
+ pub pay_in_lz_token: bool,
115
+ }
116
+
117
+ /// Parameters for the `quoteOFT()` operation.
118
+ /// Describes the transfer inputs used to quote OFT limits, fees, and receipt amounts.
119
+ /// EVM: IOFT.quoteOFT(SendParam calldata _sendParam)
120
+ ///
121
+ /// Note: Unlike the EVM `SendParam`, this struct does not include an `oftCmd` field.
122
+ /// Custom OFT implementations that need OFT commands should pass them via a separate PDA account.
123
+ #[derive(Clone, AnchorSerialize, AnchorDeserialize)]
124
+ pub struct QuoteOFTParams {
125
+ /// Destination endpoint ID.
126
+ pub dst_eid: u32,
127
+ /// Recipient address (bytes32).
128
+ pub to: [u8; 32],
129
+ /// Amount to send in local decimals.
130
+ pub amount_ld: u64,
131
+ /// Minimum amount to send in local decimals (slippage protection).
132
+ pub min_amount_ld: u64,
133
+ /// Additional options supplied by the caller to be used in the LayerZero message.
134
+ pub extra_options: Vec<u8>,
135
+ /// The composed message for the send() operation. `None` if no composed message.
136
+ pub compose_msg: Option<Vec<u8>>,
137
+ /// Deprecated: retained for backward compatibility with deployed programs. Not used by the
138
+ /// quoteOFT operation.
139
+ pub pay_in_lz_token: bool,
140
+ }
141
+
142
+ #[cfg(test)]
143
+ mod tests {
144
+ use super::*;
145
+
146
+ #[test]
147
+ fn oft_type_enum_values() {
148
+ assert_eq!(OftType::BurnMint as u8, 0);
149
+ assert_eq!(OftType::LockUnlock as u8, 1);
150
+ }
151
+ }