@layerzerolabs/protocol-stellar-v2 0.2.20 → 0.2.22

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 (198) hide show
  1. package/.turbo/turbo-build.log +783 -802
  2. package/.turbo/turbo-lint.log +320 -157
  3. package/.turbo/turbo-test.log +1414 -1457
  4. package/Cargo.lock +109 -108
  5. package/Cargo.toml +32 -18
  6. package/contracts/common-macros/Cargo.toml +7 -7
  7. package/contracts/common-macros/src/auth.rs +18 -37
  8. package/contracts/common-macros/src/contract_ttl.rs +2 -2
  9. package/contracts/common-macros/src/lib.rs +27 -10
  10. package/contracts/common-macros/src/lz_contract.rs +38 -7
  11. package/contracts/common-macros/src/storage.rs +251 -292
  12. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_multisig_code.snap +6 -12
  13. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_ownable_code.snap +12 -17
  14. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +2 -7
  15. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +20 -9
  16. package/contracts/common-macros/src/tests/upgradeable.rs +26 -4
  17. package/contracts/common-macros/src/ttl_configurable.rs +2 -10
  18. package/contracts/common-macros/src/ttl_extendable.rs +2 -10
  19. package/contracts/common-macros/src/upgradeable.rs +56 -15
  20. package/contracts/common-macros/src/utils.rs +0 -9
  21. package/contracts/endpoint-v2/src/lib.rs +3 -2
  22. package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +2 -2
  23. package/contracts/endpoint-v2/src/tests/endpoint_v2/lz_receive_alert.rs +3 -3
  24. package/contracts/endpoint-v2/src/tests/endpoint_v2/send.rs +4 -4
  25. package/contracts/endpoint-v2/src/tests/endpoint_v2/set_delegate.rs +17 -5
  26. package/contracts/endpoint-v2/src/tests/endpoint_v2/set_zro.rs +4 -4
  27. package/contracts/endpoint-v2/src/tests/endpoint_v2/verify.rs +2 -2
  28. package/contracts/endpoint-v2/src/tests/message_lib_manager/register_library.rs +2 -2
  29. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +6 -6
  30. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +67 -37
  31. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_send_library.rs +5 -5
  32. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_receive_library.rs +44 -54
  33. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_receive_library_timeout.rs +7 -7
  34. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_send_library.rs +8 -8
  35. package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +3 -3
  36. package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +4 -4
  37. package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +3 -3
  38. package/contracts/endpoint-v2/src/tests/messaging_composer/clear_compose.rs +2 -2
  39. package/contracts/endpoint-v2/src/tests/messaging_composer/lz_compose_alert.rs +3 -3
  40. package/contracts/endpoint-v2/src/tests/messaging_composer/send_compose.rs +2 -2
  41. package/contracts/layerzero-views/Cargo.toml +0 -1
  42. package/contracts/layerzero-views/src/layerzero_view.rs +1 -13
  43. package/contracts/macro-integration-tests/Cargo.toml +5 -15
  44. package/contracts/macro-integration-tests/tests/runtime/oapp/mod.rs +48 -0
  45. package/contracts/macro-integration-tests/tests/runtime/oapp/oapp_core.rs +170 -0
  46. package/contracts/macro-integration-tests/tests/runtime/oapp/options_type3.rs +154 -0
  47. package/contracts/macro-integration-tests/tests/runtime/oapp/receiver.rs +338 -0
  48. package/contracts/macro-integration-tests/tests/runtime/oapp/sender.rs +435 -0
  49. package/contracts/macro-integration-tests/tests/runtime.rs +1 -0
  50. package/contracts/macro-integration-tests/tests/ui/oapp/fail/custom_wrong_value.rs +8 -0
  51. package/contracts/macro-integration-tests/tests/ui/oapp/fail/custom_wrong_value.stderr +5 -0
  52. package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_lz_receive_internal.rs +8 -0
  53. package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_lz_receive_internal.stderr +71 -0
  54. package/contracts/macro-integration-tests/tests/ui/oapp/fail/non_struct_input.rs +10 -0
  55. package/contracts/macro-integration-tests/tests/ui/oapp/fail/non_struct_input.stderr +5 -0
  56. package/contracts/macro-integration-tests/tests/ui/oapp/fail/unknown_custom_option.rs +8 -0
  57. package/contracts/macro-integration-tests/tests/ui/oapp/fail/unknown_custom_option.stderr +5 -0
  58. package/contracts/macro-integration-tests/tests/ui/oapp/fail/wrong_key.rs +8 -0
  59. package/contracts/macro-integration-tests/tests/ui/oapp/fail/wrong_key.stderr +5 -0
  60. package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_all.rs +38 -0
  61. package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_single_trait.rs +96 -0
  62. package/contracts/macro-integration-tests/tests/ui/oapp/pass/minimal_contract.rs +64 -0
  63. package/contracts/macro-integration-tests/tests/ui/oapp/pass/struct_with_fields.rs +46 -0
  64. package/contracts/macro-integration-tests/tests/ui/ownable/fail/only_auth_missing_env.stderr +8 -0
  65. package/contracts/macro-integration-tests/tests/ui/ownable/pass/namespacing_and_imports.rs +1 -1
  66. package/contracts/macro-integration-tests/tests/ui/ownable/pass/only_auth_env_param_variants.rs +1 -1
  67. package/contracts/macro-integration-tests/tests/ui_oapp.rs +11 -0
  68. package/contracts/message-libs/message-lib-common/Cargo.toml +0 -1
  69. package/contracts/message-libs/message-lib-common/src/errors.rs +1 -1
  70. package/contracts/message-libs/treasury/Cargo.toml +0 -2
  71. package/contracts/message-libs/treasury/src/tests/treasury_tests.rs +2 -2
  72. package/contracts/message-libs/uln-302/src/tests/receive_uln302/effective_receive_uln_config.rs +2 -2
  73. package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +2 -2
  74. package/contracts/message-libs/uln-302/src/tests/receive_uln302/verify.rs +2 -2
  75. package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_executor_config.rs +2 -2
  76. package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_send_uln_config.rs +2 -2
  77. package/contracts/message-libs/uln-302/src/tests/send_uln302/send.rs +7 -27
  78. package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_executor_configs.rs +2 -2
  79. package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +2 -2
  80. package/contracts/oapps/counter/Cargo.toml +4 -6
  81. package/contracts/oapps/counter/integration_tests/utils.rs +19 -12
  82. package/contracts/oapps/oapp/src/errors.rs +1 -1
  83. package/contracts/oapps/oapp/src/interfaces/mod.rs +3 -0
  84. package/contracts/oapps/oapp/src/interfaces/oapp_msg_inspector.rs +47 -0
  85. package/contracts/oapps/oapp/src/lib.rs +1 -0
  86. package/contracts/oapps/oapp/src/macro_tests/test_macros.rs +4 -4
  87. package/contracts/oapps/oapp/src/oapp_core.rs +5 -5
  88. package/contracts/oapps/oapp/src/oapp_options_type3.rs +12 -4
  89. package/contracts/oapps/oapp/src/oapp_receiver.rs +14 -9
  90. package/contracts/oapps/oapp/src/tests/mod.rs +4 -4
  91. package/contracts/oapps/oapp/src/tests/{test_oapp_core.rs → oapp_core.rs} +4 -4
  92. package/contracts/oapps/oapp/src/tests/{test_oapp_options_type3.rs → oapp_options_type3.rs} +3 -4
  93. package/contracts/oapps/oapp-macros/Cargo.toml +8 -4
  94. package/contracts/oapps/oapp-macros/src/generators.rs +9 -34
  95. package/contracts/oapps/oapp-macros/src/lib.rs +3 -0
  96. package/contracts/oapps/oapp-macros/src/tests/mod.rs +2 -0
  97. package/contracts/oapps/oapp-macros/src/tests/oapp.rs +88 -0
  98. package/contracts/oapps/oapp-macros/src/tests/parse_custom_impls.rs +86 -0
  99. package/contracts/oapps/oapp-macros/src/tests/snapshots/oapp_macros__tests__oapp__snapshot_generate_oapp.snap +103 -0
  100. package/contracts/oapps/oft/integration-tests/utils.rs +28 -8
  101. package/contracts/oapps/oft/src/extensions/oft_fee.rs +136 -74
  102. package/contracts/oapps/oft/src/extensions/pausable.rs +44 -10
  103. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +170 -130
  104. package/contracts/oapps/oft/src/oft.rs +19 -12
  105. package/contracts/oapps/oft/src/oft_types/lock_unlock.rs +1 -1
  106. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
  107. package/contracts/oapps/oft-core/Cargo.toml +1 -4
  108. package/contracts/oapps/oft-core/integration-tests/setup.rs +2 -2
  109. package/contracts/oapps/oft-core/integration-tests/utils.rs +21 -3
  110. package/contracts/oapps/oft-core/src/errors.rs +3 -2
  111. package/contracts/oapps/oft-core/src/events.rs +6 -0
  112. package/contracts/oapps/oft-core/src/lib.rs +1 -1
  113. package/contracts/oapps/oft-core/src/oft_core.rs +115 -60
  114. package/contracts/oapps/oft-core/src/storage.rs +7 -3
  115. package/contracts/oapps/oft-core/src/tests/mod.rs +1 -0
  116. package/contracts/oapps/oft-core/src/tests/test_decimals.rs +37 -2
  117. package/contracts/oapps/oft-core/src/tests/test_lz_receive.rs +2 -2
  118. package/contracts/oapps/oft-core/src/tests/test_msg_inspector.rs +323 -0
  119. package/contracts/oapps/oft-core/src/tests/test_send.rs +2 -2
  120. package/contracts/oapps/oft-core/src/tests/test_utils.rs +59 -14
  121. package/contracts/utils/Cargo.toml +0 -1
  122. package/contracts/utils/src/errors.rs +1 -1
  123. package/contracts/utils/src/multisig.rs +17 -8
  124. package/contracts/utils/src/ownable.rs +6 -6
  125. package/contracts/utils/src/testing_utils.rs +124 -54
  126. package/contracts/utils/src/tests/multisig.rs +12 -12
  127. package/contracts/utils/src/tests/ownable.rs +6 -6
  128. package/contracts/utils/src/tests/testing_utils.rs +50 -167
  129. package/contracts/utils/src/tests/ttl_configurable.rs +5 -5
  130. package/contracts/utils/src/tests/upgradeable.rs +1 -1
  131. package/contracts/utils/src/ttl_configurable.rs +10 -4
  132. package/contracts/utils/src/upgradeable.rs +5 -5
  133. package/contracts/workers/dvn/Cargo.toml +5 -6
  134. package/contracts/workers/dvn/src/dvn.rs +2 -12
  135. package/contracts/workers/dvn-fee-lib/Cargo.toml +1 -1
  136. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +37 -19
  137. package/contracts/workers/dvn-fee-lib/src/lib.rs +12 -2
  138. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +15 -13
  139. package/contracts/workers/executor/Cargo.toml +3 -0
  140. package/contracts/workers/executor/src/executor.rs +2 -12
  141. package/contracts/workers/executor/src/lib.rs +2 -2
  142. package/contracts/workers/executor/src/tests/auth.rs +394 -0
  143. package/contracts/workers/executor/src/tests/executor.rs +410 -0
  144. package/contracts/workers/executor/src/tests/mod.rs +3 -0
  145. package/contracts/workers/executor/src/tests/setup.rs +250 -0
  146. package/contracts/workers/executor-fee-lib/Cargo.toml +5 -0
  147. package/contracts/workers/executor-fee-lib/src/executor_fee_lib.rs +1 -12
  148. package/contracts/workers/executor-fee-lib/src/lib.rs +8 -2
  149. package/contracts/workers/executor-helper/Cargo.toml +0 -1
  150. package/contracts/workers/price-feed/Cargo.toml +5 -0
  151. package/contracts/workers/price-feed/src/lib.rs +9 -4
  152. package/contracts/workers/price-feed/src/price_feed.rs +1 -11
  153. package/contracts/workers/worker/src/errors.rs +1 -1
  154. package/contracts/workers/worker/src/tests/setup.rs +1 -1
  155. package/contracts/workers/worker/src/tests/worker.rs +55 -41
  156. package/contracts/workers/worker/src/worker.rs +34 -25
  157. package/docs/error-spec.md +55 -0
  158. package/docs/layerzero-v2-on-stellar.md +447 -0
  159. package/docs/oapp-guide.md +212 -0
  160. package/docs/oft-guide.md +314 -0
  161. package/package.json +3 -3
  162. package/sdk/.turbo/turbo-test.log +260 -257
  163. package/sdk/dist/generated/bml.d.ts +3 -3
  164. package/sdk/dist/generated/bml.js +4 -4
  165. package/sdk/dist/generated/counter.d.ts +295 -295
  166. package/sdk/dist/generated/counter.js +43 -43
  167. package/sdk/dist/generated/dvn.d.ts +91 -91
  168. package/sdk/dist/generated/dvn.js +24 -24
  169. package/sdk/dist/generated/dvn_fee_lib.d.ts +92 -92
  170. package/sdk/dist/generated/dvn_fee_lib.js +25 -25
  171. package/sdk/dist/generated/endpoint.d.ts +99 -99
  172. package/sdk/dist/generated/endpoint.js +16 -16
  173. package/sdk/dist/generated/executor.d.ts +91 -91
  174. package/sdk/dist/generated/executor.js +24 -24
  175. package/sdk/dist/generated/executor_fee_lib.d.ts +92 -92
  176. package/sdk/dist/generated/executor_fee_lib.js +25 -25
  177. package/sdk/dist/generated/executor_helper.d.ts +3 -3
  178. package/sdk/dist/generated/executor_helper.js +4 -4
  179. package/sdk/dist/generated/layerzero_view.d.ts +186 -186
  180. package/sdk/dist/generated/layerzero_view.js +35 -35
  181. package/sdk/dist/generated/oft.d.ts +366 -352
  182. package/sdk/dist/generated/oft.js +74 -79
  183. package/sdk/dist/generated/price_feed.d.ts +198 -198
  184. package/sdk/dist/generated/price_feed.js +39 -39
  185. package/sdk/dist/generated/sml.d.ts +99 -99
  186. package/sdk/dist/generated/sml.js +16 -16
  187. package/sdk/dist/generated/treasury.d.ts +99 -99
  188. package/sdk/dist/generated/treasury.js +16 -16
  189. package/sdk/dist/generated/uln302.d.ts +99 -99
  190. package/sdk/dist/generated/uln302.js +16 -16
  191. package/sdk/dist/generated/upgrader.d.ts +3 -3
  192. package/sdk/dist/generated/upgrader.js +3 -3
  193. package/sdk/package.json +1 -1
  194. package/sdk/test/suites/localnet.ts +84 -20
  195. package/contracts/ERROR_SPEC.md +0 -51
  196. package/contracts/endpoint-v2/ARCHITECTURE.md +0 -233
  197. /package/contracts/oapps/oapp/src/tests/{test_oapp_receiver.rs → oapp_receiver.rs} +0 -0
  198. /package/contracts/oapps/oapp/src/tests/{test_oapp_sender.rs → oapp_sender.rs} +0 -0
@@ -1,10 +1,9 @@
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`, `__initialize_oft`, etc.)
4
+ //! - `OFTInternal`: Internal methods NOT exposed as contract entrypoints (`__debit`, `__credit`, `__initialize_oft`, `__receive`, etc.)
5
5
  //! - `OFTCore`: Public methods exposed as contract entrypoints (using `#[contracttrait]`)
6
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
8
7
  //!
9
8
  //! ## Usage
10
9
  //!
@@ -48,14 +47,15 @@ use crate::{
48
47
  self as oft_core,
49
48
  codec::{oft_compose_msg_codec::OFTComposeMsg, oft_msg_codec::OFTMessage},
50
49
  errors::OFTError,
51
- events::{self, OFTSent},
50
+ events::{self, MsgInspectorSet, OFTSent},
52
51
  storage::OFTStorage,
53
52
  types::{self, OFTFeeDetail, OFTLimit, OFTReceipt, SendParam},
54
53
  utils as oft_utils,
55
54
  };
56
- use common_macros::contract_trait;
55
+ use common_macros::{contract_trait, only_auth};
57
56
  use endpoint_v2::{MessagingComposerClient, MessagingFee, MessagingReceipt};
58
57
  use oapp::{
58
+ interfaces::OAppMsgInspectorClient,
59
59
  oapp_core::{initialize_oapp, OAppCore},
60
60
  oapp_options_type3::OAppOptionsType3,
61
61
  oapp_receiver::OAppReceiver,
@@ -120,7 +120,7 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
120
120
 
121
121
  // Initialize OFT storage
122
122
  OFTStorage::set_token(env, token);
123
- OFTStorage::set_decimal_conversion_rate(env, &10_i128.pow(local_decimals - shared_decimals));
123
+ OFTStorage::set_decimals_diff(env, &(local_decimals - shared_decimals));
124
124
  }
125
125
 
126
126
  // =========================================================================
@@ -167,6 +167,8 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
167
167
  /// # Returns
168
168
  /// A tuple of (transfer limits, fee details, estimated receipt)
169
169
  fn __quote_oft(env: &Env, send_param: &SendParam) -> (OFTLimit, Vec<OFTFeeDetail>, OFTReceipt) {
170
+ assert_with_error!(env, send_param.amount_ld >= 0, OFTError::InvalidAmount);
171
+
170
172
  let limit = OFTLimit { min_amount_ld: 0, max_amount_ld: i128::MAX };
171
173
  let fee_details = vec![env];
172
174
  let oft_receipt = Self::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
@@ -183,6 +185,8 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
183
185
  /// # Returns
184
186
  /// The messaging fee required for the transfer
185
187
  fn __quote_send(env: &Env, from: &Address, send_param: &SendParam, pay_in_zro: bool) -> MessagingFee {
188
+ assert_with_error!(env, send_param.amount_ld >= 0, OFTError::InvalidAmount);
189
+
186
190
  let OFTReceipt { amount_received_ld, .. } =
187
191
  Self::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
188
192
 
@@ -214,6 +218,8 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
214
218
  ) -> (MessagingReceipt, OFTReceipt) {
215
219
  from.require_auth();
216
220
 
221
+ assert_with_error!(env, send_param.amount_ld >= 0, OFTError::InvalidAmount);
222
+
217
223
  let oft_receipt = Self::__debit(env, from, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
218
224
 
219
225
  let (msg, options) = Self::__build_msg_and_options(env, from, send_param, oft_receipt.amount_received_ld);
@@ -261,10 +267,13 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
261
267
 
262
268
  /// Builds OFT message and combines options for cross-chain transfer.
263
269
  ///
270
+ /// If a message inspector is set, it will be called to validate the message and options.
271
+ /// The inspector should panic if the message or options are invalid.
272
+ ///
264
273
  /// # Arguments
265
274
  /// * `from` - The address initiating the transfer
266
275
  /// * `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)
276
+ /// * `amount_receive_ld` - The amount to be received in local decimals (after dust removal)
268
277
  ///
269
278
  /// # Returns
270
279
  /// A tuple of (encoded message, combined options)
@@ -272,20 +281,26 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
272
281
  env: &Env,
273
282
  from: &Address,
274
283
  send_param: &SendParam,
275
- receive_amount_ld: i128,
284
+ amount_receive_ld: i128,
276
285
  ) -> (Bytes, Bytes) {
277
286
  let has_compose = !send_param.compose_msg.is_empty();
278
287
  let conversion_rate = Self::__decimal_conversion_rate(env);
279
288
  let (msg, _) = OFTMessage {
280
289
  send_to: send_param.to.clone(),
281
- amount_sd: oft_utils::to_sd(env, receive_amount_ld, conversion_rate),
290
+ amount_sd: oft_utils::to_sd(env, amount_receive_ld, conversion_rate),
282
291
  compose_from: if has_compose { Some(oft_utils::address_payload(from)) } else { None },
283
292
  compose_msg: if has_compose { Some(send_param.compose_msg.clone()) } else { None },
284
293
  }
285
294
  .encode(env);
286
295
  let msg_type = if has_compose { types::SEND_AND_CALL } else { types::SEND };
296
+ let options = Self::combine_options(env, send_param.dst_eid, msg_type, &send_param.extra_options);
297
+
298
+ // Optionally inspect message and options if inspector is set
299
+ if let Some(inspector) = Self::__msg_inspector(env) {
300
+ OAppMsgInspectorClient::new(env, &inspector).inspect(&env.current_contract_address(), &msg, &options);
301
+ }
287
302
 
288
- (msg, Self::combine_options(env, send_param.dst_eid, msg_type, &send_param.extra_options))
303
+ (msg, options)
289
304
  }
290
305
 
291
306
  // ----- Storage Accessors -----
@@ -297,15 +312,81 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
297
312
 
298
313
  /// Retrieves the decimal conversion rate used for cross-chain normalization.
299
314
  fn __decimal_conversion_rate(env: &Env) -> i128 {
300
- OFTStorage::decimal_conversion_rate(env).unwrap_or_panic(env, OFTError::NotInitialized)
315
+ let decimals_diff = OFTStorage::decimals_diff(env).unwrap_or_panic(env, OFTError::NotInitialized);
316
+ 10_i128.pow(decimals_diff)
301
317
  }
302
318
 
303
319
  /// Retrieves the shared decimals used for cross-chain normalization.
304
320
  fn __shared_decimals(env: &Env) -> u32 {
305
- let token = Self::__token(env);
306
- let local_decimals = TokenClient::new(env, &token).decimals();
321
+ let local_decimals = TokenClient::new(env, &Self::__token(env)).decimals();
322
+ let decimals_diff = OFTStorage::decimals_diff(env).unwrap_or_panic(env, OFTError::NotInitialized);
323
+ local_decimals - decimals_diff
324
+ }
325
+
326
+ /// Returns the message inspector address if set.
327
+ fn __msg_inspector(env: &Env) -> Option<Address> {
328
+ OFTStorage::msg_inspector(env)
329
+ }
330
+
331
+ /// Sets or removes the message inspector address.
332
+ ///
333
+ /// The message inspector is an optional contract that validates outgoing messages
334
+ /// and options before they are sent cross-chain. If set, the inspector's `inspect`
335
+ /// method will be called during `send` and `quote_send` operations.
336
+ ///
337
+ /// # Arguments
338
+ /// * `inspector` - The address of the inspector contract, or None to remove
339
+ fn __set_msg_inspector(env: &Env, inspector: &Option<Address>) {
340
+ OFTStorage::set_or_remove_msg_inspector(env, inspector);
341
+ MsgInspectorSet { inspector: inspector.clone() }.publish(env);
342
+ }
343
+
344
+ // ----- Receive Handler -----
345
+
346
+ /// Handles incoming cross-chain OFT transfer from LayerZero endpoint.
347
+ ///
348
+ /// Credits tokens to the recipient and optionally queues a compose message.
349
+ /// Override this method to implement custom receive logic (e.g., pausable, rate limiting).
350
+ ///
351
+ /// # Arguments
352
+ /// * `origin` - The origin information (source chain, sender, nonce)
353
+ /// * `guid` - The unique message identifier
354
+ /// * `message` - The encoded OFT message payload
355
+ /// * `extra_data` - Additional data (unused in default implementation)
356
+ /// * `executor` - The address of the executor handling the message (unused in default implementation)
357
+ /// * `value` - The native token value sent with the message (unused in default implementation)
358
+ fn __receive(
359
+ env: &Env,
360
+ origin: &endpoint_v2::Origin,
361
+ guid: &soroban_sdk::BytesN<32>,
362
+ message: &Bytes,
363
+ _extra_data: &Bytes,
364
+ _executor: &Address,
365
+ _value: i128,
366
+ ) {
367
+ let oft_msg = OFTMessage::decode(message);
368
+ let send_to = oft_utils::resolve_address(env, &oft_msg.send_to);
369
+
307
370
  let conversion_rate = Self::__decimal_conversion_rate(env);
308
- local_decimals - conversion_rate.ilog10()
371
+ let amount_received_ld =
372
+ Self::__credit(env, &send_to, oft_utils::to_ld(oft_msg.amount_sd, conversion_rate), origin.src_eid);
373
+
374
+ if oft_msg.is_composed() {
375
+ let compose_msg = OFTComposeMsg {
376
+ nonce: origin.nonce,
377
+ src_eid: origin.src_eid,
378
+ amount_ld: amount_received_ld,
379
+ compose_from: oft_msg.compose_from.unwrap(),
380
+ compose_msg: oft_msg.compose_msg.unwrap(),
381
+ }
382
+ .encode(env);
383
+
384
+ let endpoint_client = MessagingComposerClient::new(env, &Self::endpoint(env));
385
+ endpoint_client.send_compose(&env.current_contract_address(), &send_to, guid, &0, &compose_msg);
386
+ }
387
+
388
+ events::OFTReceived { guid: guid.clone(), src_eid: origin.src_eid, to: send_to, amount_received_ld }
389
+ .publish(env);
309
390
  }
310
391
  }
311
392
 
@@ -352,6 +433,24 @@ pub trait OFTCore: OFTInternal {
352
433
  false
353
434
  }
354
435
 
436
+ /// Returns the message inspector address if set.
437
+ fn msg_inspector(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
438
+ Self::__msg_inspector(env)
439
+ }
440
+
441
+ /// Sets or removes the message inspector address.
442
+ ///
443
+ /// The message inspector is an optional contract that validates outgoing messages
444
+ /// and options before they are sent cross-chain. If set, the inspector's `inspect`
445
+ /// method will be called during `send` and `quote_send` operations.
446
+ ///
447
+ /// # Arguments
448
+ /// * `inspector` - The address of the inspector contract, or None to remove
449
+ #[only_auth]
450
+ fn set_msg_inspector(env: &soroban_sdk::Env, inspector: &Option<soroban_sdk::Address>) {
451
+ Self::__set_msg_inspector(env, inspector);
452
+ }
453
+
355
454
  /// Quotes an OFT transfer without executing.
356
455
  ///
357
456
  /// # Returns
@@ -397,8 +496,8 @@ pub trait OFTCore: OFTInternal {
397
496
  /// Implements `LzReceiveInternal` for an OFT contract using the default OFT receive logic.
398
497
  ///
399
498
  /// 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.
499
+ /// to `OFTInternal::__receive`, which handles decoding the OFT message, crediting
500
+ /// tokens to the recipient, and optionally queuing compose messages.
402
501
  ///
403
502
  /// # Usage
404
503
  ///
@@ -428,52 +527,8 @@ macro_rules! impl_oft_lz_receive {
428
527
  executor: &soroban_sdk::Address,
429
528
  value: i128,
430
529
  ) {
431
- oft_core::lz_receive::<Self>(env, executor, origin, guid, message, extra_data, value)
530
+ <Self as oft_core::OFTInternal>::__receive(env, origin, guid, message, extra_data, executor, value)
432
531
  }
433
532
  }
434
533
  };
435
534
  }
436
-
437
- /// Handles incoming cross-chain OFT transfer from LayerZero endpoint.
438
- ///
439
- /// Credits tokens to the recipient and optionally queues a compose message.
440
- ///
441
- /// # Arguments
442
- /// * `executor` - The address of the executor handling the message (unused in default implementation)
443
- /// * `origin` - The origin information (source chain, sender, nonce)
444
- /// * `guid` - The unique message identifier
445
- /// * `message` - The encoded OFT message payload
446
- /// * `extra_data` - Additional data (unused in default implementation)
447
- /// * `value` - The native token value sent with the message (unused in default implementation)
448
- pub fn lz_receive<T: OFTInternal>(
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,
455
- _value: i128,
456
- ) {
457
- let oft_msg = OFTMessage::decode(message);
458
- let send_to = oft_utils::resolve_address(env, &oft_msg.send_to);
459
-
460
- let conversion_rate = T::__decimal_conversion_rate(env);
461
- let amount_received_ld =
462
- T::__credit(env, &send_to, oft_utils::to_ld(oft_msg.amount_sd, conversion_rate), origin.src_eid);
463
-
464
- if oft_msg.is_composed() {
465
- let compose_msg = OFTComposeMsg {
466
- nonce: origin.nonce,
467
- src_eid: origin.src_eid,
468
- amount_ld: amount_received_ld,
469
- compose_from: oft_msg.compose_from.unwrap(),
470
- compose_msg: oft_msg.compose_msg.unwrap(),
471
- }
472
- .encode(env);
473
-
474
- let endpoint_client = MessagingComposerClient::new(env, &T::endpoint(env));
475
- endpoint_client.send_compose(&env.current_contract_address(), &send_to, guid, &0, &compose_msg);
476
- }
477
-
478
- events::OFTReceived { guid: guid.clone(), src_eid: origin.src_eid, to: send_to, amount_received_ld }.publish(env);
479
- }
@@ -3,11 +3,15 @@ use soroban_sdk::Address;
3
3
 
4
4
  #[storage]
5
5
  pub enum OFTStorage {
6
- /// The decimal conversion rate used for cross-chain normalization
7
- #[instance(i128)]
8
- DecimalConversionRate,
6
+ /// The difference between local and shared decimals (local_decimals - shared_decimals)
7
+ #[instance(u32)]
8
+ DecimalsDiff,
9
9
 
10
10
  /// The address of the underlying token contract
11
11
  #[instance(Address)]
12
12
  Token,
13
+
14
+ /// The optional message inspector contract address
15
+ #[instance(Address)]
16
+ MsgInspector,
13
17
  }
@@ -5,6 +5,7 @@ pub mod test_oft_msg_codec;
5
5
 
6
6
  pub mod test_decimals;
7
7
  pub mod test_lz_receive;
8
+ pub mod test_msg_inspector;
8
9
  pub mod test_oft_version;
9
10
  pub mod test_quote_oft;
10
11
  pub mod test_quote_send;
@@ -17,7 +17,7 @@ fn test_decimal_conversion_rate() {
17
17
  }
18
18
 
19
19
  #[test]
20
- #[should_panic(expected = "Error(Contract, #3000)")] // InvalidLocalDecimals
20
+ #[should_panic(expected = "Error(Contract, #3001)")] // InvalidLocalDecimals
21
21
  fn test_invalid_local_decimals() {
22
22
  let env = Env::default();
23
23
  OFTTestSetupBuilder::new(&env).with_token_decimals(5).with_shared_decimals(6).build();
@@ -34,7 +34,7 @@ fn test_to_ld_overflow() {
34
34
  }
35
35
 
36
36
  #[test]
37
- #[should_panic(expected = "Error(Contract, #3002)")] // Overflow
37
+ #[should_panic(expected = "Error(Contract, #3003)")] // Overflow
38
38
  fn test_to_sd_overflow() {
39
39
  let env = Env::default();
40
40
 
@@ -87,3 +87,38 @@ fn test_to_sd_truncates_dust() {
87
87
  // 20 LD → 2 SD (exact)
88
88
  assert_eq!(to_sd(&env, 20, conversion_rate), 2);
89
89
  }
90
+
91
+ // ==================== Shared Decimals Tests ====================
92
+
93
+ #[test]
94
+ fn test_shared_decimals_default() {
95
+ let env = Env::default();
96
+ let setup = OFTTestSetup::new(&env);
97
+
98
+ // Default setup: 7 local decimals, 6 shared decimals
99
+ let shared = setup.oft.shared_decimals();
100
+ assert_eq!(shared, setup.shared_decimals);
101
+ assert_eq!(shared, 6);
102
+ }
103
+
104
+ #[test]
105
+ fn test_shared_decimals_equal_to_local() {
106
+ let env = Env::default();
107
+ // When shared_decimals == local_decimals, conversion_rate = 1
108
+ let setup = OFTTestSetupBuilder::new(&env).with_token_decimals(8).with_shared_decimals(8).build();
109
+
110
+ let shared = setup.oft.shared_decimals();
111
+ assert_eq!(shared, 8);
112
+ assert_eq!(setup.oft.decimal_conversion_rate(), 1);
113
+ }
114
+
115
+ #[test]
116
+ fn test_shared_decimals_large_difference() {
117
+ let env = Env::default();
118
+ // 18 local decimals, 6 shared decimals → conversion_rate = 10^12
119
+ let setup = OFTTestSetupBuilder::new(&env).with_token_decimals(18).with_shared_decimals(6).build();
120
+
121
+ let shared = setup.oft.shared_decimals();
122
+ assert_eq!(shared, 6);
123
+ assert_eq!(setup.oft.decimal_conversion_rate(), 10_i128.pow(12));
124
+ }
@@ -9,7 +9,7 @@ use crate::{
9
9
  };
10
10
  use endpoint_v2::LayerZeroReceiverClient;
11
11
  use soroban_sdk::{testutils::Address as _, Address, Bytes, BytesN, Env};
12
- use utils::testing_utils::assert_event;
12
+ use utils::testing_utils::assert_contains_event;
13
13
 
14
14
  // Helper function to reduce code duplication for lz_receive tests
15
15
  fn run_lz_receive_test(setup: &OFTTestSetup, recipient: &Address, amount_sd: u64) {
@@ -37,7 +37,7 @@ fn run_lz_receive_test(setup: &OFTTestSetup, recipient: &Address, amount_sd: u64
37
37
  let conversion_rate = setup.oft.decimal_conversion_rate();
38
38
  let expected_amount_ld = (amount_sd as i128) * conversion_rate;
39
39
  setup.lz_receive(&executor, &origin, &guid, &message, &extra_data, 0);
40
- assert_event(
40
+ assert_contains_event(
41
41
  env,
42
42
  &setup.oft.address,
43
43
  OFTReceived { guid: guid.clone(), src_eid, to: recipient.clone(), amount_received_ld: expected_amount_ld },