@layerzerolabs/protocol-stellar-v2 0.2.19 → 0.2.20

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 (125) hide show
  1. package/.turbo/turbo-build.log +245 -222
  2. package/.turbo/turbo-lint.log +77 -70
  3. package/.turbo/turbo-test.log +1385 -1221
  4. package/Cargo.lock +13 -3
  5. package/Cargo.toml +2 -0
  6. package/contracts/ERROR_SPEC.md +8 -1
  7. package/contracts/common-macros/src/contract_ttl.rs +18 -7
  8. package/contracts/common-macros/src/lib.rs +4 -4
  9. package/contracts/common-macros/src/tests/contract_ttl.rs +1 -1
  10. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__contract_ttl__snapshot_generated_contractimpl_code.snap +2 -1
  11. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +7 -12
  12. package/contracts/common-macros/src/upgradeable.rs +15 -21
  13. package/contracts/message-libs/uln-302/src/events.rs +4 -0
  14. package/contracts/message-libs/uln-302/src/send_uln.rs +22 -6
  15. package/contracts/message-libs/uln-302/src/tests/send_uln302/send.rs +38 -64
  16. package/contracts/oapps/counter/Cargo.toml +1 -0
  17. package/contracts/oapps/counter/integration_tests/setup_uln.rs +1 -1
  18. package/contracts/oapps/oapp/src/tests/test_oapp_core.rs +113 -65
  19. package/contracts/oapps/oapp/src/tests/test_oapp_options_type3.rs +111 -82
  20. package/contracts/oapps/oapp/src/tests/test_oapp_receiver.rs +293 -65
  21. package/contracts/oapps/oapp/src/tests/test_oapp_sender.rs +331 -56
  22. package/contracts/oapps/oft/src/extensions/oft_fee.rs +18 -2
  23. package/contracts/oapps/oft/src/extensions/pausable.rs +19 -4
  24. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +52 -28
  25. package/contracts/oapps/oft/src/oft.rs +29 -41
  26. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +3 -25
  27. package/contracts/oapps/oft-core/integration-tests/setup.rs +2 -2
  28. package/contracts/oapps/oft-core/src/oft_core.rs +247 -207
  29. package/contracts/oapps/oft-core/src/tests/test_utils.rs +4 -4
  30. package/contracts/upgrader/src/lib.rs +30 -57
  31. package/contracts/upgrader/src/tests/test_data/test_upgradeable_contract1.wasm +0 -0
  32. package/contracts/upgrader/src/tests/test_data/test_upgradeable_contract2.wasm +0 -0
  33. package/contracts/upgrader/src/tests/test_upgrader.rs +44 -35
  34. package/contracts/utils/src/buffer_reader.rs +1 -0
  35. package/contracts/utils/src/errors.rs +3 -1
  36. package/contracts/utils/src/tests/upgradeable.rs +372 -175
  37. package/contracts/utils/src/ttl_configurable.rs +3 -3
  38. package/contracts/utils/src/upgradeable.rs +48 -23
  39. package/contracts/workers/dvn/Cargo.toml +1 -0
  40. package/contracts/workers/dvn/src/auth.rs +12 -42
  41. package/contracts/workers/dvn/src/dvn.rs +16 -31
  42. package/contracts/workers/dvn/src/errors.rs +0 -1
  43. package/contracts/workers/dvn/src/interfaces/dvn.rs +35 -0
  44. package/contracts/workers/dvn/src/lib.rs +4 -3
  45. package/contracts/workers/dvn/src/tests/auth.rs +1 -1
  46. package/contracts/workers/dvn/src/tests/dvn.rs +19 -15
  47. package/contracts/workers/dvn/src/tests/multisig/set_threshold.rs +2 -4
  48. package/contracts/workers/dvn/src/tests/multisig/verify_signatures.rs +1 -3
  49. package/contracts/workers/dvn/src/tests/setup.rs +5 -9
  50. package/contracts/workers/dvn-fee-lib/Cargo.toml +1 -1
  51. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +3 -5
  52. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +2 -3
  53. package/contracts/workers/executor/Cargo.toml +1 -0
  54. package/contracts/workers/executor/src/executor.rs +15 -26
  55. package/contracts/workers/executor-fee-lib/Cargo.toml +2 -1
  56. package/contracts/workers/executor-fee-lib/src/executor_fee_lib.rs +63 -5
  57. package/contracts/workers/executor-fee-lib/src/executor_option.rs +28 -1
  58. package/contracts/workers/executor-fee-lib/src/lib.rs +3 -0
  59. package/contracts/workers/executor-fee-lib/src/tests/executor_fee_lib.rs +701 -0
  60. package/contracts/workers/executor-fee-lib/src/tests/executor_option.rs +370 -0
  61. package/contracts/workers/executor-fee-lib/src/tests/mod.rs +4 -0
  62. package/contracts/workers/executor-fee-lib/src/tests/setup.rs +60 -0
  63. package/contracts/workers/executor-helper/src/lib.rs +3 -0
  64. package/contracts/workers/executor-helper/src/tests/executor_helper.rs +184 -0
  65. package/contracts/workers/executor-helper/src/tests/mod.rs +2 -0
  66. package/contracts/workers/executor-helper/src/tests/setup.rs +366 -0
  67. package/contracts/workers/fee-lib-interfaces/Cargo.toml +14 -0
  68. package/contracts/workers/{worker/src/interfaces/mod.rs → fee-lib-interfaces/src/lib.rs} +4 -3
  69. package/contracts/workers/price-feed/Cargo.toml +2 -1
  70. package/contracts/workers/price-feed/src/events.rs +1 -1
  71. package/contracts/workers/price-feed/src/lib.rs +3 -0
  72. package/contracts/workers/price-feed/src/price_feed.rs +6 -12
  73. package/contracts/workers/price-feed/src/storage.rs +1 -1
  74. package/contracts/workers/price-feed/src/tests/mod.rs +2 -0
  75. package/contracts/workers/price-feed/src/tests/price_feed.rs +869 -0
  76. package/contracts/workers/price-feed/src/tests/setup.rs +70 -0
  77. package/contracts/workers/price-feed/src/types.rs +1 -1
  78. package/contracts/workers/worker/src/errors.rs +0 -3
  79. package/contracts/workers/worker/src/lib.rs +0 -2
  80. package/contracts/workers/worker/src/storage.rs +32 -29
  81. package/contracts/workers/worker/src/tests/setup.rs +1 -7
  82. package/contracts/workers/worker/src/tests/worker.rs +50 -42
  83. package/contracts/workers/worker/src/worker.rs +49 -58
  84. package/package.json +3 -3
  85. package/sdk/.turbo/turbo-test.log +220 -218
  86. package/sdk/dist/generated/bml.d.ts +12 -4
  87. package/sdk/dist/generated/bml.js +8 -6
  88. package/sdk/dist/generated/counter.d.ts +12 -4
  89. package/sdk/dist/generated/counter.js +8 -6
  90. package/sdk/dist/generated/dvn.d.ts +404 -365
  91. package/sdk/dist/generated/dvn.js +55 -53
  92. package/sdk/dist/generated/dvn_fee_lib.d.ts +224 -268
  93. package/sdk/dist/generated/dvn_fee_lib.js +22 -53
  94. package/sdk/dist/generated/endpoint.d.ts +12 -4
  95. package/sdk/dist/generated/endpoint.js +8 -6
  96. package/sdk/dist/generated/executor.d.ts +370 -326
  97. package/sdk/dist/generated/executor.js +47 -44
  98. package/sdk/dist/generated/executor_fee_lib.d.ts +258 -302
  99. package/sdk/dist/generated/executor_fee_lib.js +21 -52
  100. package/sdk/dist/generated/executor_helper.d.ts +26 -190
  101. package/sdk/dist/generated/executor_helper.js +22 -27
  102. package/sdk/dist/generated/layerzero_view.d.ts +1271 -0
  103. package/sdk/dist/generated/layerzero_view.js +294 -0
  104. package/sdk/dist/generated/oft.d.ts +49 -40
  105. package/sdk/dist/generated/oft.js +25 -23
  106. package/sdk/dist/generated/price_feed.d.ts +225 -269
  107. package/sdk/dist/generated/price_feed.js +22 -53
  108. package/sdk/dist/generated/sml.d.ts +12 -4
  109. package/sdk/dist/generated/sml.js +8 -6
  110. package/sdk/dist/generated/treasury.d.ts +12 -4
  111. package/sdk/dist/generated/treasury.js +8 -6
  112. package/sdk/dist/generated/uln302.d.ts +12 -4
  113. package/sdk/dist/generated/uln302.js +10 -8
  114. package/sdk/dist/generated/upgrader.d.ts +189 -18
  115. package/sdk/dist/generated/upgrader.js +84 -4
  116. package/sdk/dist/index.d.ts +1 -0
  117. package/sdk/dist/index.js +2 -0
  118. package/sdk/package.json +1 -1
  119. package/sdk/src/index.ts +3 -0
  120. package/sdk/test/oft-sml.test.ts +4 -4
  121. package/sdk/test/upgrader.test.ts +2 -3
  122. package/tools/ts-bindings-gen/src/main.rs +2 -1
  123. /package/contracts/workers/{worker/src/interfaces → fee-lib-interfaces/src}/dvn_fee_lib.rs +0 -0
  124. /package/contracts/workers/{worker/src/interfaces → fee-lib-interfaces/src}/executor_fee_lib.rs +0 -0
  125. /package/contracts/workers/{worker/src/interfaces → fee-lib-interfaces/src}/price_feed.rs +0 -0
@@ -1,15 +1,16 @@
1
1
  //! OFT - traits and implementations for Omnichain Fungible Tokens.
2
2
  //!
3
3
  //! This module provides:
4
- //! - `OFTInternal`: Internal methods NOT exposed as contract entrypoints (`__debit`, `__credit`, etc.)
4
+ //! - `OFTInternal`: Internal methods NOT exposed as contract entrypoints (`__debit`, `__credit`, `__initialize_oft`, etc.)
5
5
  //! - `OFTCore`: Public methods exposed as contract entrypoints (using `#[contracttrait]`)
6
- //! - Core functions: `initialize_oft`, `lz_receive`, and other helpers
6
+ //! - `impl_oft_lz_receive!`: Macro to implement `LzReceiveInternal` with default OFT receive logic
7
+ //! - Helper functions: `lz_receive` for handling incoming cross-chain transfers
7
8
  //!
8
9
  //! ## Usage
9
10
  //!
10
11
  //! ```ignore
11
- //! use oapp_macros::{oapp, oapp_options_type3};
12
- //! use oft_core::{OFTInternal, OFTCore, initialize_oft};
12
+ //! use oapp_macros::oapp;
13
+ //! use oft_core::{OFTInternal, OFTCore, impl_oft_lz_receive};
13
14
  //!
14
15
  //! #[oapp]
15
16
  //! pub struct MyOFT;
@@ -17,7 +18,7 @@
17
18
  //! #[contractimpl]
18
19
  //! impl MyOFT {
19
20
  //! pub fn __constructor(env: &Env, token: &Address, owner: &Address, endpoint: &Address, delegate: &Option<Address>) {
20
- //! initialize_oft::<Self>(env, owner, token, endpoint, delegate, 6)
21
+ //! Self::__initialize_oft(env, owner, token, endpoint, delegate, 6)
21
22
  //! }
22
23
  //! }
23
24
  //!
@@ -38,6 +39,9 @@
38
39
  //! oft_core::oft_types::mint_burn::credit::<Self>(env, to, amount_ld, src_eid)
39
40
  //! }
40
41
  //! }
42
+ //!
43
+ //! // LzReceiveInternal - use the macro for default OFT receive logic
44
+ //! impl_oft_lz_receive!(MyOFT);
41
45
  //! ```
42
46
 
43
47
  use crate::{
@@ -50,14 +54,14 @@ use crate::{
50
54
  utils as oft_utils,
51
55
  };
52
56
  use common_macros::contract_trait;
53
- use endpoint_v2::{MessagingComposerClient, MessagingFee, MessagingReceipt, Origin};
57
+ use endpoint_v2::{MessagingComposerClient, MessagingFee, MessagingReceipt};
54
58
  use oapp::{
55
59
  oapp_core::{initialize_oapp, OAppCore},
56
60
  oapp_options_type3::OAppOptionsType3,
57
61
  oapp_receiver::OAppReceiver,
58
62
  oapp_sender::OAppSenderInternal,
59
63
  };
60
- use soroban_sdk::{assert_with_error, token::TokenClient, vec, Address, Bytes, BytesN, Env, Vec};
64
+ use soroban_sdk::{assert_with_error, token::TokenClient, vec, Address, Bytes, Env, Vec};
61
65
  use utils::{option_ext::OptionExt, ownable::OwnableInitializer};
62
66
 
63
67
  // ===========================================================================
@@ -78,11 +82,47 @@ use utils::{option_ext::OptionExt, ownable::OwnableInitializer};
78
82
  /// impl OFTInternal for MyOFT { ... }
79
83
  /// ```
80
84
  ///
81
- /// Internal OFT trait for token debit/credit operations.
82
- ///
83
- /// This trait contains only the internal token operations. The OApp supertraits
84
- /// are on `OFTCore` instead, keeping this trait focused on token logic.
85
- pub trait OFTInternal: OAppCore + OAppOptionsType3 {
85
+ /// This trait extends all OApp supertraits and contains both the token operations
86
+ /// and the internal sending logic. `OFTCore` serves only as an entrypoint wrapper.
87
+ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOptionsType3 + OwnableInitializer {
88
+ // =========================================================================
89
+ // Initialization
90
+ // =========================================================================
91
+
92
+ /// Initializes the OFT (Omnichain Fungible Token) contract.
93
+ ///
94
+ /// Sets up the OApp infrastructure and configures decimal conversion for cross-chain transfers.
95
+ /// The `shared_decimals` parameter defines the common decimal precision used across all chains,
96
+ /// enabling consistent token amounts regardless of each chain's native token decimals.
97
+ ///
98
+ /// # Arguments
99
+ /// * `owner` - The address that will own this OFT contract
100
+ /// * `token` - The underlying token contract address (must implement SEP-41 token interface)
101
+ /// * `endpoint` - The LayerZero endpoint address for cross-chain messaging
102
+ /// * `delegate` - Optional delegate address for endpoint configuration permissions
103
+ /// * `shared_decimals` - The shared decimal precision for cross-chain compatibility (must be <= local decimals)
104
+ ///
105
+ /// # Panics
106
+ /// * `OFTError::InvalidLocalDecimals` - If the token's local decimals are less than `shared_decimals`
107
+ fn __initialize_oft(
108
+ env: &Env,
109
+ owner: &Address,
110
+ token: &Address,
111
+ endpoint: &Address,
112
+ delegate: &Option<Address>,
113
+ shared_decimals: u32,
114
+ ) {
115
+ // Initialize OApp (includes owner initialization)
116
+ initialize_oapp::<Self>(env, owner, endpoint, delegate);
117
+
118
+ let local_decimals = TokenClient::new(env, token).decimals();
119
+ assert_with_error!(env, local_decimals >= shared_decimals, OFTError::InvalidLocalDecimals);
120
+
121
+ // Initialize OFT storage
122
+ OFTStorage::set_token(env, token);
123
+ OFTStorage::set_decimal_conversion_rate(env, &10_i128.pow(local_decimals - shared_decimals));
124
+ }
125
+
86
126
  // =========================================================================
87
127
  // Required Methods (no defaults - user MUST implement)
88
128
  // =========================================================================
@@ -114,19 +154,158 @@ pub trait OFTInternal: OAppCore + OAppOptionsType3 {
114
154
  // Optional Methods (have defaults - override as needed)
115
155
  // =========================================================================
116
156
 
117
- /// Calculates debit amounts without executing (view function).
118
- fn __debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> OFTReceipt {
119
- debit_view(env, amount_ld, min_amount_ld, dst_eid)
157
+ // ----- Quote Methods -----
158
+
159
+ /// Quotes an OFT transfer without executing.
160
+ ///
161
+ /// The default implementation has no send limits (`min_amount_ld: 0`, `max_amount_ld: i128::MAX`)
162
+ /// and no fee details (empty vector). Override this method to implement custom limits or fees.
163
+ ///
164
+ /// # Arguments
165
+ /// * `send_param` - The send parameters to quote
166
+ ///
167
+ /// # Returns
168
+ /// A tuple of (transfer limits, fee details, estimated receipt)
169
+ fn __quote_oft(env: &Env, send_param: &SendParam) -> (OFTLimit, Vec<OFTFeeDetail>, OFTReceipt) {
170
+ let limit = OFTLimit { min_amount_ld: 0, max_amount_ld: i128::MAX };
171
+ let fee_details = vec![env];
172
+ let oft_receipt = Self::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
173
+ (limit, fee_details, oft_receipt)
174
+ }
175
+
176
+ /// Quotes the LayerZero messaging fee for a send operation.
177
+ ///
178
+ /// # Arguments
179
+ /// * `from` - The address initiating the transfer
180
+ /// * `send_param` - The send parameters to quote
181
+ /// * `pay_in_zro` - Whether to pay the fee in ZRO token
182
+ ///
183
+ /// # Returns
184
+ /// The messaging fee required for the transfer
185
+ fn __quote_send(env: &Env, from: &Address, send_param: &SendParam, pay_in_zro: bool) -> MessagingFee {
186
+ let OFTReceipt { amount_received_ld, .. } =
187
+ Self::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
188
+
189
+ let (msg, options) = Self::__build_msg_and_options(env, from, send_param, amount_received_ld);
190
+ Self::__quote(env, send_param.dst_eid, &msg, &options, pay_in_zro)
191
+ }
192
+
193
+ // ----- Send Method -----
194
+
195
+ /// Executes a cross-chain token transfer via LayerZero.
196
+ ///
197
+ /// Debits tokens from the `from` address, builds the message, and sends via the endpoint.
198
+ /// The `from` address must be authenticated.
199
+ ///
200
+ /// # Arguments
201
+ /// * `from` - The address sending tokens (must authorize)
202
+ /// * `send_param` - The send parameters (destination, recipient, amount, options)
203
+ /// * `fee` - The messaging fee to pay
204
+ /// * `refund_address` - The address to refund excess fees to
205
+ ///
206
+ /// # Returns
207
+ /// A tuple of (messaging receipt, OFT receipt with amounts)
208
+ fn __send(
209
+ env: &Env,
210
+ from: &Address,
211
+ send_param: &SendParam,
212
+ fee: &MessagingFee,
213
+ refund_address: &Address,
214
+ ) -> (MessagingReceipt, OFTReceipt) {
215
+ from.require_auth();
216
+
217
+ let oft_receipt = Self::__debit(env, from, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
218
+
219
+ let (msg, options) = Self::__build_msg_and_options(env, from, send_param, oft_receipt.amount_received_ld);
220
+ let msg_receipt = Self::__lz_send(env, send_param.dst_eid, &msg, &options, from, fee, refund_address);
221
+
222
+ OFTSent {
223
+ guid: msg_receipt.guid.clone(),
224
+ dst_eid: send_param.dst_eid,
225
+ from: from.clone(),
226
+ amount_sent_ld: oft_receipt.amount_sent_ld,
227
+ amount_received_ld: oft_receipt.amount_received_ld,
228
+ }
229
+ .publish(env);
230
+
231
+ (msg_receipt, oft_receipt)
232
+ }
233
+
234
+ // ----- View/Helper Methods -----
235
+
236
+ /// Simulates a debit operation without executing, used for quoting.
237
+ ///
238
+ /// The default implementation does not charge any fee, so `amount_sent_ld` equals
239
+ /// `amount_received_ld` (after dust removal). Override this method to implement
240
+ /// custom fee logic.
241
+ ///
242
+ /// # Arguments
243
+ /// * `amount_ld` - The amount of tokens to send in local decimals
244
+ /// * `min_amount_ld` - The minimum amount to send in local decimals (slippage protection)
245
+ /// * `dst_eid` - The destination chain ID (unused in default implementation)
246
+ ///
247
+ /// # Returns
248
+ /// `OFTReceipt` containing the amount sent and amount received after dust removal
249
+ ///
250
+ /// # Panics
251
+ /// * `OFTError::SlippageExceeded` - If `amount_received_ld` is less than `min_amount_ld`
252
+ fn __debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, _dst_eid: u32) -> OFTReceipt {
253
+ let conversion_rate = Self::__decimal_conversion_rate(env);
254
+ let amount_sent_ld = oft_utils::remove_dust(amount_ld, conversion_rate);
255
+ let amount_received_ld = amount_sent_ld;
256
+
257
+ assert_with_error!(env, amount_received_ld >= min_amount_ld, OFTError::SlippageExceeded);
258
+
259
+ OFTReceipt { amount_sent_ld, amount_received_ld }
120
260
  }
121
261
 
122
262
  /// Builds OFT message and combines options for cross-chain transfer.
263
+ ///
264
+ /// # Arguments
265
+ /// * `from` - The address initiating the transfer
266
+ /// * `send_param` - The send parameters including destination, recipient, and options
267
+ /// * `receive_amount_ld` - The amount to be received in local decimals (after dust removal)
268
+ ///
269
+ /// # Returns
270
+ /// A tuple of (encoded message, combined options)
123
271
  fn __build_msg_and_options(
124
272
  env: &Env,
125
273
  from: &Address,
126
- send_param: &oft_core::types::SendParam,
274
+ send_param: &SendParam,
127
275
  receive_amount_ld: i128,
128
276
  ) -> (Bytes, Bytes) {
129
- build_msg_and_options::<Self>(env, from, send_param, receive_amount_ld)
277
+ let has_compose = !send_param.compose_msg.is_empty();
278
+ let conversion_rate = Self::__decimal_conversion_rate(env);
279
+ let (msg, _) = OFTMessage {
280
+ send_to: send_param.to.clone(),
281
+ amount_sd: oft_utils::to_sd(env, receive_amount_ld, conversion_rate),
282
+ compose_from: if has_compose { Some(oft_utils::address_payload(from)) } else { None },
283
+ compose_msg: if has_compose { Some(send_param.compose_msg.clone()) } else { None },
284
+ }
285
+ .encode(env);
286
+ let msg_type = if has_compose { types::SEND_AND_CALL } else { types::SEND };
287
+
288
+ (msg, Self::combine_options(env, send_param.dst_eid, msg_type, &send_param.extra_options))
289
+ }
290
+
291
+ // ----- Storage Accessors -----
292
+
293
+ /// Retrieves the token address associated with this OFT.
294
+ fn __token(env: &Env) -> Address {
295
+ OFTStorage::token(env).unwrap_or_panic(env, OFTError::NotInitialized)
296
+ }
297
+
298
+ /// Retrieves the decimal conversion rate used for cross-chain normalization.
299
+ fn __decimal_conversion_rate(env: &Env) -> i128 {
300
+ OFTStorage::decimal_conversion_rate(env).unwrap_or_panic(env, OFTError::NotInitialized)
301
+ }
302
+
303
+ /// Retrieves the shared decimals used for cross-chain normalization.
304
+ fn __shared_decimals(env: &Env) -> u32 {
305
+ let token = Self::__token(env);
306
+ let local_decimals = TokenClient::new(env, &token).decimals();
307
+ let conversion_rate = Self::__decimal_conversion_rate(env);
308
+ local_decimals - conversion_rate.ilog10()
130
309
  }
131
310
  }
132
311
 
@@ -139,13 +318,12 @@ pub trait OFTInternal: OAppCore + OAppOptionsType3 {
139
318
  /// This trait is marked with `#[contracttrait]` so all its methods are exposed as
140
319
  /// contract entrypoints. Users implement this with `#[contractimpl(contracttrait)]`.
141
320
  ///
142
- /// Internal methods like `__debit`, `__credit`, `__debit_view`, and `__build_msg_and_options`
143
- /// are in the `OFTInternal` trait and are NOT exposed as contract entrypoints.
321
+ /// This trait only exposes entrypoints. All internal logic is in `OFTInternal`.
144
322
  #[contract_trait(client_name = "OFTClient")]
145
- pub trait OFTCore: OFTInternal + OAppReceiver + OAppSenderInternal + OAppOptionsType3 {
323
+ pub trait OFTCore: OFTInternal {
146
324
  /// Retrieves the token address associated with this OFT.
147
325
  fn token(env: &soroban_sdk::Env) -> soroban_sdk::Address {
148
- token(env)
326
+ Self::__token(env)
149
327
  }
150
328
 
151
329
  /// Returns OFT version as (major, minor).
@@ -155,12 +333,12 @@ pub trait OFTCore: OFTInternal + OAppReceiver + OAppSenderInternal + OAppOptions
155
333
 
156
334
  /// Retrieves the shared decimals used for cross-chain normalization.
157
335
  fn shared_decimals(env: &soroban_sdk::Env) -> u32 {
158
- shared_decimals(env)
336
+ Self::__shared_decimals(env)
159
337
  }
160
338
 
161
339
  /// Retrieves the decimal conversion rate used for cross-chain normalization.
162
340
  fn decimal_conversion_rate(env: &soroban_sdk::Env) -> i128 {
163
- decimal_conversion_rate(env)
341
+ Self::__decimal_conversion_rate(env)
164
342
  }
165
343
 
166
344
  /// Whether a separate token approval is required before sending.
@@ -182,218 +360,80 @@ pub trait OFTCore: OFTInternal + OAppReceiver + OAppSenderInternal + OAppOptions
182
360
  env: &soroban_sdk::Env,
183
361
  send_param: &oft_core::types::SendParam,
184
362
  ) -> (oft_core::types::OFTLimit, soroban_sdk::Vec<oft_core::types::OFTFeeDetail>, oft_core::types::OFTReceipt) {
185
- quote_oft::<Self>(env, send_param)
363
+ Self::__quote_oft(env, send_param)
186
364
  }
187
365
 
188
366
  /// Quotes a send operation including LayerZero messaging fees.
189
367
  fn quote_send(
190
368
  env: &soroban_sdk::Env,
191
- sender: &soroban_sdk::Address,
369
+ from: &soroban_sdk::Address,
192
370
  send_param: &oft_core::types::SendParam,
193
371
  pay_in_zro: bool,
194
372
  ) -> endpoint_v2::MessagingFee {
195
- quote_send::<Self>(env, sender, send_param, pay_in_zro)
373
+ Self::__quote_send(env, from, send_param, pay_in_zro)
196
374
  }
197
375
 
198
376
  /// Sends tokens cross-chain to another endpoint.
199
377
  ///
200
- /// Sender must be authenticated.
378
+ /// From must be authenticated.
201
379
  ///
202
380
  /// # Returns
203
381
  /// (MessagingReceipt, OFTReceipt)
204
382
  fn send(
205
383
  env: &soroban_sdk::Env,
206
- sender: &soroban_sdk::Address,
384
+ from: &soroban_sdk::Address,
207
385
  send_param: &oft_core::types::SendParam,
208
386
  fee: &endpoint_v2::MessagingFee,
209
387
  refund_address: &soroban_sdk::Address,
210
388
  ) -> (endpoint_v2::MessagingReceipt, oft_core::types::OFTReceipt) {
211
- send::<Self>(env, sender, send_param, fee, refund_address)
389
+ Self::__send(env, from, send_param, fee, refund_address)
212
390
  }
213
391
  }
214
392
 
215
393
  // ===========================================================================
216
- // Initialization
217
- // ===========================================================================
218
-
219
- /// Initializes the OFT (Omnichain Fungible Token) contract.
220
- ///
221
- /// Sets up the OApp infrastructure and configures decimal conversion for cross-chain transfers.
222
- /// The `shared_decimals` parameter defines the common decimal precision used across all chains,
223
- /// enabling consistent token amounts regardless of each chain's native token decimals.
224
- ///
225
- /// # Arguments
226
- /// * `owner` - The address that will own this OFT contract
227
- /// * `token` - The underlying token contract address (must implement SEP-41 token interface)
228
- /// * `endpoint` - The LayerZero endpoint address for cross-chain messaging
229
- /// * `delegate` - Optional delegate address for endpoint configuration permissions
230
- /// * `shared_decimals` - The shared decimal precision for cross-chain compatibility (must be <= local decimals)
231
- ///
232
- /// # Panics
233
- /// * `OFTError::InvalidLocalDecimals` - If the token's local decimals are less than `shared_decimals`
234
- pub fn initialize_oft<T: OFTCore + OwnableInitializer>(
235
- env: &Env,
236
- owner: &Address,
237
- token: &Address,
238
- endpoint: &Address,
239
- delegate: &Option<Address>,
240
- shared_decimals: u32,
241
- ) {
242
- // Initialize OApp (includes owner initialization)
243
- initialize_oapp::<T>(env, owner, endpoint, delegate);
244
-
245
- let local_decimals = TokenClient::new(env, token).decimals();
246
- assert_with_error!(env, local_decimals >= shared_decimals, OFTError::InvalidLocalDecimals);
247
-
248
- // Initialize OFT storage
249
- OFTStorage::set_token(env, token);
250
- OFTStorage::set_decimal_conversion_rate(env, &10_i128.pow(local_decimals - shared_decimals));
251
- }
252
-
253
- // ===========================================================================
254
- // Implementation Functions
394
+ // LzReceive Handler (called by OAppReceiver)
255
395
  // ===========================================================================
256
396
 
257
- /// Simulates a debit operation without executing, used for quoting.
397
+ /// Implements `LzReceiveInternal` for an OFT contract using the default OFT receive logic.
258
398
  ///
259
- /// # Arguments
260
- /// * `amount_ld` - The amount of tokens to send in local decimals
261
- /// * `min_amount_ld` - The minimum amount to send in local decimals (slippage protection)
262
- /// * `dst_eid` - The destination chain ID (unused in default implementation)
399
+ /// This macro generates the boilerplate `LzReceiveInternal` implementation that delegates
400
+ /// to `oft_core::lz_receive`, which handles decoding the OFT message, crediting tokens
401
+ /// to the recipient, and optionally queuing compose messages.
263
402
  ///
264
- /// # Returns
265
- /// `OFTReceipt` containing the amount sent and amount received after dust removal
266
- pub fn debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, _dst_eid: u32) -> OFTReceipt {
267
- let conversion_rate = decimal_conversion_rate(env);
268
- let amount_sent_ld = oft_utils::remove_dust(amount_ld, conversion_rate);
269
- let amount_received_ld = amount_sent_ld;
270
-
271
- assert_with_error!(env, amount_received_ld >= min_amount_ld, OFTError::SlippageExceeded);
272
-
273
- OFTReceipt { amount_sent_ld, amount_received_ld }
274
- }
275
-
276
- /// Builds the OFT message payload and combines enforced options for cross-chain transfer.
403
+ /// # Usage
277
404
  ///
278
- /// # Arguments
279
- /// * `from` - The address initiating the transfer
280
- /// * `send_param` - The send parameters including destination, recipient, and options
281
- /// * `receive_amount_ld` - The amount to be received in local decimals (after dust removal)
282
- ///
283
- /// # Returns
284
- /// A tuple of (encoded message, combined options)
285
- pub fn build_msg_and_options<T: OAppOptionsType3>(
286
- env: &Env,
287
- from: &Address,
288
- send_param: &SendParam,
289
- receive_amount_ld: i128,
290
- ) -> (Bytes, Bytes) {
291
- let has_compose = !send_param.compose_msg.is_empty();
292
- let conversion_rate = decimal_conversion_rate(env);
293
- let (msg, _) = OFTMessage {
294
- send_to: send_param.to.clone(),
295
- amount_sd: oft_utils::to_sd(env, receive_amount_ld, conversion_rate),
296
- compose_from: if has_compose { Some(oft_utils::address_payload(from)) } else { None },
297
- compose_msg: if has_compose { Some(send_param.compose_msg.clone()) } else { None },
298
- }
299
- .encode(env);
300
- let msg_type = if has_compose { types::SEND_AND_CALL } else { types::SEND };
301
-
302
- (msg, T::combine_options(env, send_param.dst_eid, msg_type, &send_param.extra_options))
303
- }
304
-
305
- /// Quotes an OFT transfer without executing.
306
- ///
307
- /// # Arguments
308
- /// * `send_param` - The send parameters to quote
309
- ///
310
- /// # Returns
311
- /// A tuple of (transfer limits, fee details, estimated receipt)
312
- pub fn quote_oft<T: OFTInternal>(env: &Env, send_param: &SendParam) -> (OFTLimit, Vec<OFTFeeDetail>, OFTReceipt) {
313
- let limit = OFTLimit { min_amount_ld: 0, max_amount_ld: i128::MAX }; // No limits in default implementation
314
- let fee_details = vec![env]; // No fee details in default implementation
315
- let oft_receipt = T::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
316
- (limit, fee_details, oft_receipt)
317
- }
318
-
319
- /// Quotes the LayerZero messaging fee for a send operation.
320
- ///
321
- /// # Arguments
322
- /// * `from` - The address initiating the transfer
323
- /// * `send_param` - The send parameters to quote
324
- /// * `pay_in_zro` - Whether to pay the fee in ZRO token
325
- ///
326
- /// # Returns
327
- /// The messaging fee required for the transfer
328
- pub fn quote_send<T: OFTCore>(env: &Env, from: &Address, send_param: &SendParam, pay_in_zro: bool) -> MessagingFee {
329
- let OFTReceipt { amount_received_ld, .. } =
330
- T::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
331
-
332
- let (msg, options) = build_msg_and_options::<T>(env, from, send_param, amount_received_ld);
333
- T::__quote(env, send_param.dst_eid, &msg, &options, pay_in_zro)
334
- }
335
-
336
- /// Executes a cross-chain token transfer via LayerZero.
405
+ /// ```ignore
406
+ /// use oft_core::impl_oft_lz_receive;
337
407
  ///
338
- /// Debits tokens from the `from` address, builds the message, and sends via the endpoint.
339
- /// The `from` address must be authenticated.
408
+ /// #[oapp]
409
+ /// pub struct MyOFT;
340
410
  ///
341
- /// # Arguments
342
- /// * `from` - The address sending tokens (must authorize)
343
- /// * `send_param` - The send parameters (destination, recipient, amount, options)
344
- /// * `fee` - The messaging fee to pay
345
- /// * `refund_address` - The address to refund excess fees to
411
+ /// impl OFTInternal for MyOFT {
412
+ /// // ... implement __debit and __credit ...
413
+ /// }
346
414
  ///
347
- /// # Returns
348
- /// A tuple of (messaging receipt, OFT receipt with amounts)
349
- pub fn send<T: OFTCore>(
350
- env: &Env,
351
- from: &Address,
352
- send_param: &SendParam,
353
- fee: &MessagingFee,
354
- refund_address: &Address,
355
- ) -> (MessagingReceipt, OFTReceipt) {
356
- from.require_auth();
357
-
358
- let oft_receipt = T::__debit(env, from, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
359
-
360
- let (msg, options) = build_msg_and_options::<T>(env, from, send_param, oft_receipt.amount_received_ld);
361
- let msg_receipt = T::__lz_send(env, send_param.dst_eid, &msg, &options, from, fee, refund_address);
362
-
363
- OFTSent {
364
- guid: msg_receipt.guid.clone(),
365
- dst_eid: send_param.dst_eid,
366
- from: from.clone(),
367
- amount_sent_ld: oft_receipt.amount_sent_ld,
368
- amount_received_ld: oft_receipt.amount_received_ld,
369
- }
370
- .publish(env);
371
-
372
- (msg_receipt, oft_receipt)
373
- }
374
-
375
- /// Retrieves the decimal conversion rate used for cross-chain normalization.
376
- pub fn decimal_conversion_rate(env: &Env) -> i128 {
377
- OFTStorage::decimal_conversion_rate(env).unwrap_or_panic(env, OFTError::NotInitialized)
378
- }
379
-
380
- /// Retrieves the token address associated with this OFT.
381
- pub fn token(env: &Env) -> Address {
382
- OFTStorage::token(env).unwrap_or_panic(env, OFTError::NotInitialized)
383
- }
384
-
385
- /// Retrieves the shared decimals used for cross-chain normalization.
386
- pub fn shared_decimals(env: &Env) -> u32 {
387
- let token = token(env);
388
- let local_decimals = TokenClient::new(env, &token).decimals();
389
- let conversion_rate = decimal_conversion_rate(env);
390
- local_decimals - conversion_rate.ilog10()
415
+ /// // Instead of manually implementing LzReceiveInternal:
416
+ /// impl_oft_lz_receive!(MyOFT);
417
+ /// ```
418
+ #[macro_export]
419
+ macro_rules! impl_oft_lz_receive {
420
+ ($contract:ty) => {
421
+ impl oapp::oapp_receiver::LzReceiveInternal for $contract {
422
+ fn __lz_receive(
423
+ env: &soroban_sdk::Env,
424
+ origin: &endpoint_v2::Origin,
425
+ guid: &soroban_sdk::BytesN<32>,
426
+ message: &soroban_sdk::Bytes,
427
+ extra_data: &soroban_sdk::Bytes,
428
+ executor: &soroban_sdk::Address,
429
+ value: i128,
430
+ ) {
431
+ oft_core::lz_receive::<Self>(env, executor, origin, guid, message, extra_data, value)
432
+ }
433
+ }
434
+ };
391
435
  }
392
436
 
393
- // ===========================================================================
394
- // LzReceive Handler (called by OAppReceiver)
395
- // ===========================================================================
396
-
397
437
  /// Handles incoming cross-chain OFT transfer from LayerZero endpoint.
398
438
  ///
399
439
  /// Credits tokens to the recipient and optionally queues a compose message.
@@ -406,18 +446,18 @@ pub fn shared_decimals(env: &Env) -> u32 {
406
446
  /// * `extra_data` - Additional data (unused in default implementation)
407
447
  /// * `value` - The native token value sent with the message (unused in default implementation)
408
448
  pub fn lz_receive<T: OFTInternal>(
409
- env: &Env,
410
- _executor: &Address,
411
- origin: &Origin,
412
- guid: &BytesN<32>,
413
- message: &Bytes,
414
- _extra_data: &Bytes,
449
+ env: &soroban_sdk::Env,
450
+ _executor: &soroban_sdk::Address,
451
+ origin: &endpoint_v2::Origin,
452
+ guid: &soroban_sdk::BytesN<32>,
453
+ message: &soroban_sdk::Bytes,
454
+ _extra_data: &soroban_sdk::Bytes,
415
455
  _value: i128,
416
456
  ) {
417
457
  let oft_msg = OFTMessage::decode(message);
418
458
  let send_to = oft_utils::resolve_address(env, &oft_msg.send_to);
419
459
 
420
- let conversion_rate = decimal_conversion_rate(env);
460
+ let conversion_rate = T::__decimal_conversion_rate(env);
421
461
  let amount_received_ld =
422
462
  T::__credit(env, &send_to, oft_utils::to_ld(oft_msg.amount_sd, conversion_rate), origin.src_eid);
423
463
 
@@ -106,7 +106,7 @@ pub fn create_origin(src_eid: u32, sender: &BytesN<32>, nonce: u64) -> Origin {
106
106
  mod test_mint_burn_oft {
107
107
  use crate::{
108
108
  self as oft_core,
109
- oft_core::{initialize_oft, lz_receive, OFTCore, OFTInternal},
109
+ oft_core::{lz_receive, OFTCore, OFTInternal},
110
110
  types::OFTReceipt,
111
111
  };
112
112
  use endpoint_v2::Origin;
@@ -133,7 +133,7 @@ mod test_mint_burn_oft {
133
133
  delegate: &Option<Address>,
134
134
  shared_decimals: u32,
135
135
  ) {
136
- initialize_oft::<Self>(env, owner, token, endpoint, delegate, shared_decimals)
136
+ Self::__initialize_oft(env, owner, token, endpoint, delegate, shared_decimals)
137
137
  }
138
138
  }
139
139
 
@@ -174,7 +174,7 @@ pub use test_mint_burn_oft::TestMintBurnOFT;
174
174
  mod test_lock_unlock_oft {
175
175
  use crate::{
176
176
  self as oft_core,
177
- oft_core::{initialize_oft, lz_receive, OFTCore, OFTInternal},
177
+ oft_core::{lz_receive, OFTCore, OFTInternal},
178
178
  types::OFTReceipt,
179
179
  };
180
180
  use endpoint_v2::Origin;
@@ -194,7 +194,7 @@ mod test_lock_unlock_oft {
194
194
  delegate: &Option<Address>,
195
195
  shared_decimals: u32,
196
196
  ) {
197
- initialize_oft::<Self>(env, owner, token, endpoint, delegate, shared_decimals)
197
+ Self::__initialize_oft(env, owner, token, endpoint, delegate, shared_decimals)
198
198
  }
199
199
  }
200
200