@layerzerolabs/protocol-stellar-v2 0.2.29 → 0.2.30

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 (205) hide show
  1. package/.turbo/turbo-build.log +371 -321
  2. package/.turbo/turbo-lint.log +211 -202
  3. package/.turbo/turbo-test.log +1766 -1673
  4. package/Cargo.lock +11 -1
  5. package/contracts/common-macros/src/lib.rs +0 -2
  6. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__upgradeable__snapshot_generated_upgradeable_code.snap +1 -0
  7. package/contracts/endpoint-v2/src/messaging_channel.rs +32 -3
  8. package/contracts/endpoint-v2/src/tests/endpoint_setup.rs +1 -1
  9. package/contracts/endpoint-v2/src/tests/messaging_channel/clear_payload.rs +1 -1
  10. package/contracts/endpoint-v2/src/tests/messaging_channel/inbound.rs +6 -6
  11. package/contracts/endpoint-v2/src/tests/messaging_channel/inbound_payload_hash.rs +1 -1
  12. package/contracts/endpoint-v2/src/tests/messaging_channel/outbound.rs +16 -10
  13. package/contracts/macro-integration-tests/tests/runtime/oapp/options_type3.rs +10 -10
  14. package/contracts/macro-integration-tests/tests/runtime/oapp/receiver.rs +3 -3
  15. package/contracts/macro-integration-tests/tests/runtime/oapp/sender.rs +4 -3
  16. package/contracts/macro-integration-tests/tests/runtime/upgradeable/migrate_guard_and_state.rs +1 -57
  17. package/contracts/macro-integration-tests/tests/ui/lz_contract/fail/upgradeable_missing_internal.stderr +0 -30
  18. package/contracts/macro-integration-tests/tests/ui/oapp/fail/custom_wrong_value.stderr +5 -3
  19. package/contracts/macro-integration-tests/tests/ui/oapp/fail/non_struct_input.stderr +6 -4
  20. package/contracts/macro-integration-tests/tests/ui/oapp/fail/unknown_custom_option.stderr +5 -3
  21. package/contracts/macro-integration-tests/tests/ui/oapp/fail/wrong_key.stderr +5 -3
  22. package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/missing_auth_trait.stderr +0 -30
  23. package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/missing_upgradeable_internal.stderr +0 -30
  24. package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/basic.rs +0 -2
  25. package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/multisig_contract.rs +0 -2
  26. package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/no_migration.rs +0 -2
  27. package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/no_user_contractimpl.rs +1 -3
  28. package/contracts/message-libs/message-lib-common/src/packet_codec_v1.rs +3 -6
  29. package/contracts/message-libs/message-lib-common/src/tests/worker_options/extract_type_3_options.rs +10 -0
  30. package/contracts/message-libs/message-lib-common/src/worker_options.rs +6 -2
  31. package/contracts/message-libs/treasury/src/interfaces/zro_fee_lib.rs +3 -3
  32. package/contracts/message-libs/treasury/src/lib.rs +2 -1
  33. package/contracts/message-libs/treasury/src/tests/setup.rs +1 -1
  34. package/contracts/message-libs/treasury/src/treasury.rs +5 -2
  35. package/contracts/message-libs/uln-302/src/errors.rs +2 -0
  36. package/contracts/message-libs/uln-302/src/events.rs +3 -3
  37. package/contracts/message-libs/uln-302/src/interfaces/receive_uln.rs +8 -0
  38. package/contracts/message-libs/uln-302/src/lib.rs +2 -1
  39. package/contracts/message-libs/uln-302/src/receive_uln.rs +16 -13
  40. package/contracts/message-libs/uln-302/src/send_uln.rs +51 -24
  41. package/contracts/message-libs/uln-302/src/storage.rs +2 -2
  42. package/contracts/message-libs/uln-302/src/tests/receive_uln302/effective_receive_uln_config.rs +45 -1
  43. package/contracts/message-libs/uln-302/src/tests/receive_uln302/verifiable.rs +63 -0
  44. package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_executor_config.rs +47 -2
  45. package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_send_uln_config.rs +50 -1
  46. package/contracts/message-libs/uln-302/src/uln302.rs +0 -8
  47. package/contracts/oapps/counter/Cargo.toml +4 -4
  48. package/contracts/oapps/counter/integration_tests/setup_uln.rs +22 -2
  49. package/contracts/oapps/counter/src/counter.rs +8 -8
  50. package/contracts/oapps/oapp/src/interfaces/oapp_msg_inspector.rs +33 -10
  51. package/contracts/oapps/oapp/src/lib.rs +6 -2
  52. package/contracts/oapps/oapp/src/oapp_core.rs +49 -24
  53. package/contracts/oapps/oapp/src/oapp_options_type3.rs +21 -14
  54. package/contracts/oapps/oapp/src/oapp_receiver.rs +17 -16
  55. package/contracts/oapps/oapp/src/oapp_sender.rs +66 -15
  56. package/contracts/oapps/oapp/src/tests/oapp_core.rs +5 -5
  57. package/contracts/oapps/oapp/src/tests/oapp_options_type3.rs +18 -18
  58. package/contracts/oapps/oapp/src/tests/oapp_receiver.rs +4 -4
  59. package/contracts/oapps/oapp/src/tests/oapp_sender.rs +3 -3
  60. package/contracts/oapps/oapp-macros/Cargo.toml +0 -1
  61. package/contracts/oapps/oapp-macros/src/generators.rs +87 -46
  62. package/contracts/oapps/oapp-macros/src/lib.rs +3 -61
  63. package/contracts/oapps/oapp-macros/src/tests/oapp.rs +9 -23
  64. package/contracts/oapps/oapp-macros/src/tests/parse_custom_impls.rs +15 -11
  65. package/contracts/oapps/oft/Cargo.toml +1 -1
  66. package/contracts/oapps/oft/integration-tests/extensions/test_oft_fee.rs +3 -3
  67. package/contracts/oapps/oft/integration-tests/extensions/test_pausable.rs +4 -4
  68. package/contracts/oapps/oft/integration-tests/extensions/test_rate_limiter.rs +144 -8
  69. package/contracts/oapps/oft/integration-tests/setup.rs +4 -2
  70. package/contracts/oapps/oft/integration-tests/utils.rs +25 -11
  71. package/contracts/oapps/oft/src/extensions/oft_fee.rs +65 -63
  72. package/contracts/oapps/oft/src/extensions/pausable.rs +2 -3
  73. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +22 -5
  74. package/contracts/oapps/oft/src/interfaces/mint_burnable.rs +18 -0
  75. package/contracts/oapps/oft/src/interfaces/mod.rs +3 -0
  76. package/contracts/oapps/oft/src/lib.rs +4 -2
  77. package/contracts/oapps/oft/src/oft.rs +35 -36
  78. package/contracts/oapps/oft/src/oft_types/lock_unlock.rs +13 -9
  79. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +14 -9
  80. package/contracts/oapps/oft/src/oft_types/mod.rs +14 -12
  81. package/contracts/oapps/oft/src/tests/extensions/oft_fee.rs +28 -20
  82. package/contracts/oapps/oft/src/tests/extensions/rate_limiter.rs +136 -2
  83. package/contracts/oapps/oft/src/tests/oft_types/lock_unlock.rs +12 -8
  84. package/contracts/oapps/oft-core/integration-tests/setup.rs +8 -9
  85. package/contracts/oapps/oft-core/integration-tests/test_with_sml.rs +7 -6
  86. package/contracts/oapps/oft-core/integration-tests/utils.rs +5 -4
  87. package/contracts/oapps/oft-core/src/codec/oft_compose_msg_codec.rs +2 -2
  88. package/contracts/oapps/oft-core/src/codec/oft_msg_codec.rs +33 -37
  89. package/contracts/oapps/oft-core/src/errors.rs +2 -1
  90. package/contracts/oapps/oft-core/src/events.rs +6 -0
  91. package/contracts/oapps/oft-core/src/lib.rs +8 -4
  92. package/contracts/oapps/oft-core/src/oft_core.rs +205 -148
  93. package/contracts/oapps/oft-core/src/storage.rs +4 -2
  94. package/contracts/oapps/oft-core/src/tests/test_decimals.rs +2 -2
  95. package/contracts/oapps/oft-core/src/tests/test_lz_receive.rs +6 -6
  96. package/contracts/oapps/oft-core/src/tests/test_msg_inspector.rs +7 -6
  97. package/contracts/oapps/oft-core/src/tests/test_oft_msg_codec.rs +11 -82
  98. package/contracts/oapps/oft-core/src/tests/test_quote_oft.rs +13 -13
  99. package/contracts/oapps/oft-core/src/tests/test_quote_send.rs +1 -1
  100. package/contracts/oapps/oft-core/src/tests/test_resolve_address.rs +2 -2
  101. package/contracts/oapps/oft-core/src/tests/test_send.rs +22 -22
  102. package/contracts/oapps/oft-core/src/tests/test_utils.rs +20 -22
  103. package/contracts/oapps/oft-core/src/utils.rs +12 -8
  104. package/contracts/sac-manager/Cargo.toml +25 -0
  105. package/contracts/sac-manager/src/errors.rs +18 -0
  106. package/contracts/sac-manager/src/extensions/mod.rs +6 -0
  107. package/contracts/sac-manager/src/extensions/redistribution.rs +109 -0
  108. package/contracts/sac-manager/src/extensions/supply_control/mod.rs +488 -0
  109. package/contracts/sac-manager/src/extensions/supply_control/rate_limit.rs +126 -0
  110. package/contracts/sac-manager/src/interfaces/mod.rs +3 -0
  111. package/contracts/sac-manager/src/interfaces/sac_manager.rs +52 -0
  112. package/contracts/sac-manager/src/lib.rs +23 -0
  113. package/contracts/sac-manager/src/sac_manager.rs +193 -0
  114. package/contracts/sac-manager/src/storage.rs +20 -0
  115. package/contracts/sac-manager/src/tests/mod.rs +14 -0
  116. package/contracts/sac-manager/src/tests/redistribution/mod.rs +1 -0
  117. package/contracts/sac-manager/src/tests/redistribution/redistribute_funds.rs +82 -0
  118. package/contracts/sac-manager/src/tests/sac_manager/admin_mint.rs +206 -0
  119. package/contracts/sac-manager/src/tests/sac_manager/burn.rs +215 -0
  120. package/contracts/sac-manager/src/tests/sac_manager/clawback.rs +209 -0
  121. package/contracts/sac-manager/src/tests/sac_manager/mint.rs +252 -0
  122. package/contracts/sac-manager/src/tests/sac_manager/mod.rs +9 -0
  123. package/contracts/sac-manager/src/tests/sac_manager/set_admin.rs +36 -0
  124. package/contracts/sac-manager/src/tests/sac_manager/set_authorized.rs +43 -0
  125. package/contracts/sac-manager/src/tests/sac_manager/set_oft_address.rs +47 -0
  126. package/contracts/sac-manager/src/tests/sac_manager/test_helper.rs +75 -0
  127. package/contracts/sac-manager/src/tests/sac_manager/view_functions.rs +60 -0
  128. package/contracts/sac-manager/src/tests/supply_control/enumerable_set.rs +256 -0
  129. package/contracts/sac-manager/src/tests/supply_control/mod.rs +8 -0
  130. package/contracts/sac-manager/src/tests/supply_control/refill.rs +90 -0
  131. package/contracts/sac-manager/src/tests/supply_control/set_mint_whitelist.rs +245 -0
  132. package/contracts/sac-manager/src/tests/supply_control/set_supply_controller.rs +267 -0
  133. package/contracts/sac-manager/src/tests/supply_control/set_supply_controller_manager.rs +122 -0
  134. package/contracts/sac-manager/src/tests/supply_control/test_helper.rs +38 -0
  135. package/contracts/sac-manager/src/tests/supply_control/update_allow_any_mint_burn.rs +114 -0
  136. package/contracts/sac-manager/src/tests/supply_control/update_limit_config.rs +257 -0
  137. package/contracts/sac-manager/src/tests/test_helper.rs +190 -0
  138. package/contracts/upgrader/src/lib.rs +2 -1
  139. package/contracts/utils/src/errors.rs +0 -1
  140. package/contracts/utils/src/tests/upgradeable.rs +0 -66
  141. package/contracts/utils/src/upgradeable.rs +0 -18
  142. package/contracts/workers/dvn/src/dvn.rs +2 -2
  143. package/contracts/workers/dvn/src/interfaces/dvn.rs +2 -2
  144. package/contracts/workers/dvn/src/lib.rs +2 -1
  145. package/contracts/workers/dvn-fee-lib/src/lib.rs +3 -1
  146. package/contracts/workers/executor/src/auth.rs +42 -26
  147. package/contracts/workers/executor/src/executor.rs +28 -3
  148. package/contracts/workers/executor/src/lib.rs +4 -2
  149. package/contracts/workers/executor/src/storage.rs +21 -1
  150. package/contracts/workers/executor/src/tests/auth.rs +64 -20
  151. package/contracts/workers/executor/src/tests/executor.rs +1 -1
  152. package/contracts/workers/executor/src/tests/setup.rs +18 -0
  153. package/contracts/workers/executor-fee-lib/src/lib.rs +4 -1
  154. package/contracts/workers/executor-helper/src/executor_helper.rs +24 -10
  155. package/contracts/workers/executor-helper/src/tests/setup.rs +147 -34
  156. package/contracts/workers/price-feed/src/lib.rs +3 -1
  157. package/contracts/workers/worker/src/lib.rs +2 -1
  158. package/contracts/workers/worker/src/worker.rs +31 -17
  159. package/docs/oapp-guide.md +17 -8
  160. package/docs/oft-guide.md +3 -3
  161. package/package.json +3 -3
  162. package/sdk/.turbo/turbo-test.log +512 -351
  163. package/sdk/dist/generated/bml.d.ts +3 -9
  164. package/sdk/dist/generated/bml.js +6 -7
  165. package/sdk/dist/generated/counter.d.ts +22 -28
  166. package/sdk/dist/generated/counter.js +11 -12
  167. package/sdk/dist/generated/dvn.d.ts +36 -54
  168. package/sdk/dist/generated/dvn.js +10 -15
  169. package/sdk/dist/generated/dvn_fee_lib.d.ts +3 -21
  170. package/sdk/dist/generated/dvn_fee_lib.js +6 -11
  171. package/sdk/dist/generated/endpoint.d.ts +3 -9
  172. package/sdk/dist/generated/endpoint.js +6 -7
  173. package/sdk/dist/generated/executor.d.ts +80 -54
  174. package/sdk/dist/generated/executor.js +16 -16
  175. package/sdk/dist/generated/executor_fee_lib.d.ts +3 -21
  176. package/sdk/dist/generated/executor_fee_lib.js +6 -11
  177. package/sdk/dist/generated/executor_helper.d.ts +36 -42
  178. package/sdk/dist/generated/executor_helper.js +9 -10
  179. package/sdk/dist/generated/layerzero_view.d.ts +20 -32
  180. package/sdk/dist/generated/layerzero_view.js +25 -26
  181. package/sdk/dist/generated/oft.d.ts +147 -79
  182. package/sdk/dist/generated/oft.js +47 -54
  183. package/sdk/dist/generated/price_feed.d.ts +20 -38
  184. package/sdk/dist/generated/price_feed.js +15 -20
  185. package/sdk/dist/generated/sac_manager.d.ts +1309 -0
  186. package/sdk/dist/generated/sac_manager.js +484 -0
  187. package/sdk/dist/generated/sml.d.ts +3 -9
  188. package/sdk/dist/generated/sml.js +6 -7
  189. package/sdk/dist/generated/treasury.d.ts +3 -9
  190. package/sdk/dist/generated/treasury.js +8 -9
  191. package/sdk/dist/generated/uln302.d.ts +20 -20
  192. package/sdk/dist/generated/uln302.js +25 -22
  193. package/sdk/dist/generated/upgrader.d.ts +3 -9
  194. package/sdk/dist/generated/upgrader.js +6 -7
  195. package/sdk/dist/index.d.ts +1 -0
  196. package/sdk/dist/index.js +1 -0
  197. package/sdk/package.json +1 -1
  198. package/sdk/src/index.ts +1 -0
  199. package/sdk/test/oft-sml.test.ts +7 -5
  200. package/sdk/test/sac-manager-redistribution.test.ts +578 -0
  201. package/sdk/test/suites/globalSetup.ts +11 -6
  202. package/sdk/test/test_data/test_upgradeable_dvn.wasm +0 -0
  203. package/sdk/test/upgrader.test.ts +75 -202
  204. package/sdk/test/utils.ts +40 -0
  205. package/tools/ts-bindings-gen/src/main.rs +1 -0
@@ -16,8 +16,8 @@
16
16
  //!
17
17
  //! #[contractimpl]
18
18
  //! impl MyOFT {
19
- //! pub fn __constructor(env: &Env, token: &Address, owner: &Address, endpoint: &Address, delegate: &Option<Address>) {
20
- //! Self::__initialize_oft(env, owner, token, endpoint, delegate, 6)
19
+ //! pub fn __constructor(env: &Env, token: &Address, owner: &Address, endpoint: &Address, delegate: &Address) {
20
+ //! Self::__initialize_oft(env, token, 6, owner, endpoint, delegate)
21
21
  //! }
22
22
  //! }
23
23
  //!
@@ -28,7 +28,7 @@
28
28
  //! // Internal methods - NOT exposed as contract entrypoints
29
29
  //! // IMPORTANT: Do NOT use #[contractimpl] here to keep methods internal
30
30
  //! impl OFTInternal for MyOFT {
31
- //! fn __debit(env: &Env, sender: &Address, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> OFTReceipt {
31
+ //! fn __debit(env: &Env, sender: &Address, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> (i128, i128) {
32
32
  //! // Your debit logic (e.g., burn or lock tokens)
33
33
  //! oft_core::oft_types::mint_burn::debit::<Self>(env, sender, amount_ld, min_amount_ld, dst_eid)
34
34
  //! }
@@ -45,21 +45,24 @@
45
45
 
46
46
  use crate::{
47
47
  self as oft_core,
48
- codec::{oft_compose_msg_codec::OFTComposeMsg, oft_msg_codec::OFTMessage},
48
+ codec::{
49
+ oft_compose_msg_codec::OFTComposeMsg,
50
+ oft_msg_codec::{ComposeData, OFTMessage},
51
+ },
49
52
  errors::OFTError,
50
53
  events::{self, MsgInspectorSet, OFTSent},
51
54
  storage::OFTStorage,
52
- types::{self, OFTFeeDetail, OFTLimit, OFTReceipt, SendParam},
55
+ types::{OFTFeeDetail, OFTLimit, OFTReceipt, SendParam, SEND, SEND_AND_CALL},
53
56
  utils as oft_utils,
54
57
  };
55
58
  use common_macros::{contract_trait, only_auth};
56
59
  use endpoint_v2::{MessagingComposerClient, MessagingFee, MessagingReceipt};
57
60
  use oapp::{
58
- interfaces::OAppMsgInspectorClient,
59
- oapp_core::{initialize_oapp, OAppCore},
61
+ oapp_core::initialize_oapp,
60
62
  oapp_options_type3::OAppOptionsType3,
61
63
  oapp_receiver::OAppReceiver,
62
- oapp_sender::OAppSenderInternal,
64
+ oapp_sender::{FeePayer, OAppSenderInternal},
65
+ OAppMsgInspectorClient,
63
66
  };
64
67
  use soroban_sdk::{assert_with_error, token::TokenClient, vec, Address, Bytes, Env, Vec};
65
68
  use utils::{option_ext::OptionExt, ownable::OwnableInitializer};
@@ -84,7 +87,7 @@ use utils::{option_ext::OptionExt, ownable::OwnableInitializer};
84
87
  ///
85
88
  /// This trait extends all OApp supertraits and contains both the token operations
86
89
  /// and the internal sending logic. `OFTCore` serves only as an entrypoint wrapper.
87
- pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOptionsType3 + OwnableInitializer {
90
+ pub trait OFTInternal: OAppReceiver + OAppSenderInternal + OAppOptionsType3 + OwnableInitializer {
88
91
  // =========================================================================
89
92
  // Initialization
90
93
  // =========================================================================
@@ -96,21 +99,21 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
96
99
  /// enabling consistent token amounts regardless of each chain's native token decimals.
97
100
  ///
98
101
  /// # Arguments
99
- /// * `owner` - The address that will own this OFT contract
100
102
  /// * `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
103
  /// * `shared_decimals` - The shared decimal precision for cross-chain compatibility (must be <= local decimals)
104
+ /// * `owner` - The address that will own this OFT contract
105
+ /// * `endpoint` - The LayerZero endpoint address for cross-chain messaging
106
+ /// * `delegate` - The delegate address for endpoint configuration permissions
104
107
  ///
105
108
  /// # Panics
106
109
  /// * `OFTError::InvalidLocalDecimals` - If the token's local decimals are less than `shared_decimals`
107
110
  fn __initialize_oft(
108
111
  env: &Env,
109
- owner: &Address,
110
112
  token: &Address,
111
- endpoint: &Address,
112
- delegate: &Option<Address>,
113
113
  shared_decimals: u32,
114
+ owner: &Address,
115
+ endpoint: &Address,
116
+ delegate: &Address,
114
117
  ) {
115
118
  // Initialize OApp (includes owner initialization)
116
119
  initialize_oapp::<Self>(env, owner, endpoint, delegate);
@@ -136,8 +139,9 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
136
139
  /// * `dst_eid` - The destination chain ID
137
140
  ///
138
141
  /// # Returns
139
- /// `OFTReceipt` containing the amount sent and amount received
140
- fn __debit(env: &Env, from: &Address, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> OFTReceipt;
142
+ /// * `amount_sent_ld` - The amount sent in local decimals
143
+ /// * `amount_received_ld` - The amount received in local decimals on the remote
144
+ fn __debit(env: &Env, from: &Address, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> (i128, i128);
141
145
 
142
146
  /// Credits tokens to recipient after receiving cross-chain transfer.
143
147
  ///
@@ -156,59 +160,33 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
156
160
 
157
161
  // ----- Quote Methods -----
158
162
 
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
- assert_with_error!(env, send_param.amount_ld >= 0, OFTError::InvalidAmount);
163
+ /// Quotes an OFT transfer without executing. Returns (limits, fee details, receipt).
164
+ fn __quote_oft(env: &Env, _from: &Address, send_param: &SendParam) -> (OFTLimit, Vec<OFTFeeDetail>, OFTReceipt) {
165
+ assert_nonnegative_amount(env, send_param);
171
166
 
172
- let limit = OFTLimit { min_amount_ld: 0, max_amount_ld: i128::MAX };
167
+ let limit = OFTLimit { min_amount_ld: 0, max_amount_ld: u64::MAX as i128 };
173
168
  let fee_details = vec![env];
174
- let oft_receipt = Self::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
175
- (limit, fee_details, oft_receipt)
169
+ let (amount_sent_ld, amount_received_ld) =
170
+ Self::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
171
+ (limit, fee_details, OFTReceipt { amount_sent_ld, amount_received_ld })
176
172
  }
177
173
 
178
- /// Quotes the LayerZero messaging fee for a send operation.
179
- ///
180
- /// # Arguments
181
- /// * `from` - The address initiating the transfer
182
- /// * `send_param` - The send parameters to quote
183
- /// * `pay_in_zro` - Whether to pay the fee in ZRO token
184
- ///
185
- /// # Returns
186
- /// The messaging fee required for the transfer
174
+ /// Quotes the LayerZero messaging fee for a send. Builds the message internally
175
+ /// to get an accurate fee estimate from the endpoint.
187
176
  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);
177
+ assert_nonnegative_amount(env, send_param);
189
178
 
190
- let OFTReceipt { amount_received_ld, .. } =
179
+ let (_amount_sent_ld, amount_received_ld) =
191
180
  Self::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
192
181
 
193
- let (msg, options) = Self::__build_msg_and_options(env, from, send_param, amount_received_ld);
194
- Self::__quote(env, send_param.dst_eid, &msg, &options, pay_in_zro)
182
+ let (message, options) = Self::__build_msg_and_options(env, from, send_param, amount_received_ld);
183
+ Self::__quote(env, send_param.dst_eid, &message, &options, pay_in_zro)
195
184
  }
196
185
 
197
186
  // ----- Send Method -----
198
187
 
199
- /// Executes a cross-chain token transfer via LayerZero.
200
- ///
201
- /// Debits tokens from the `from` address, builds the message, and sends via the endpoint.
202
- /// The `from` address must be authenticated.
203
- ///
204
- /// # Arguments
205
- /// * `from` - The address sending tokens (must authorize)
206
- /// * `send_param` - The send parameters (destination, recipient, amount, options)
207
- /// * `fee` - The messaging fee to pay
208
- /// * `refund_address` - The address to refund excess fees to
209
- ///
210
- /// # Returns
211
- /// A tuple of (messaging receipt, OFT receipt with amounts)
188
+ /// Executes a cross-chain token transfer: debits `from`, builds the OFT message,
189
+ /// and dispatches it via the LayerZero endpoint.
212
190
  fn __send(
213
191
  env: &Env,
214
192
  from: &Address,
@@ -218,89 +196,85 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
218
196
  ) -> (MessagingReceipt, OFTReceipt) {
219
197
  from.require_auth();
220
198
 
221
- assert_with_error!(env, send_param.amount_ld >= 0, OFTError::InvalidAmount);
199
+ assert_nonnegative_amount(env, send_param);
222
200
 
223
- let oft_receipt = Self::__debit(env, from, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
201
+ let (amount_sent_ld, amount_received_ld) =
202
+ Self::__debit(env, from, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
224
203
 
225
- let (msg, options) = Self::__build_msg_and_options(env, from, send_param, oft_receipt.amount_received_ld);
226
- let msg_receipt = Self::__lz_send(env, send_param.dst_eid, &msg, &options, from, fee, refund_address);
204
+ let (message, options) = Self::__build_msg_and_options(env, from, send_param, amount_received_ld);
205
+ let messaging_receipt = Self::__lz_send(
206
+ env,
207
+ send_param.dst_eid,
208
+ &message,
209
+ &options,
210
+ &FeePayer::Verified(from.clone()),
211
+ fee,
212
+ refund_address,
213
+ );
227
214
 
228
215
  OFTSent {
229
- guid: msg_receipt.guid.clone(),
216
+ guid: messaging_receipt.guid.clone(),
230
217
  dst_eid: send_param.dst_eid,
231
218
  from: from.clone(),
232
- amount_sent_ld: oft_receipt.amount_sent_ld,
233
- amount_received_ld: oft_receipt.amount_received_ld,
219
+ amount_sent_ld,
220
+ amount_received_ld,
234
221
  }
235
222
  .publish(env);
236
223
 
237
- (msg_receipt, oft_receipt)
224
+ (messaging_receipt, OFTReceipt { amount_sent_ld, amount_received_ld })
238
225
  }
239
226
 
240
227
  // ----- View/Helper Methods -----
241
228
 
242
- /// Simulates a debit operation without executing, used for quoting.
243
- ///
244
- /// The default implementation does not charge any fee, so `amount_sent_ld` equals
245
- /// `amount_received_ld` (after dust removal). Override this method to implement
246
- /// custom fee logic.
247
- ///
248
- /// # Arguments
249
- /// * `amount_ld` - The amount of tokens to send in local decimals
250
- /// * `min_amount_ld` - The minimum amount to send in local decimals (slippage protection)
251
- /// * `dst_eid` - The destination chain ID (unused in default implementation)
229
+ /// Simulates a debit for quoting removes dust but charges no fee by default.
230
+ /// Override to add custom fee logic. Panics with `SlippageExceeded` if the
231
+ /// resulting amount is below `min_amount_ld`.
252
232
  ///
253
233
  /// # Returns
254
- /// `OFTReceipt` containing the amount sent and amount received after dust removal
255
- ///
256
- /// # Panics
257
- /// * `OFTError::SlippageExceeded` - If `amount_received_ld` is less than `min_amount_ld`
258
- fn __debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, _dst_eid: u32) -> OFTReceipt {
234
+ /// * `amount_sent_ld` - The amount sent in local decimals
235
+ /// * `amount_received_ld` - The amount received in local decimals on the remote
236
+ fn __debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, _dst_eid: u32) -> (i128, i128) {
259
237
  let conversion_rate = Self::__decimal_conversion_rate(env);
260
238
  let amount_sent_ld = oft_utils::remove_dust(amount_ld, conversion_rate);
261
239
  let amount_received_ld = amount_sent_ld;
262
240
 
263
241
  assert_with_error!(env, amount_received_ld >= min_amount_ld, OFTError::SlippageExceeded);
264
242
 
265
- OFTReceipt { amount_sent_ld, amount_received_ld }
243
+ (amount_sent_ld, amount_received_ld)
266
244
  }
267
245
 
268
- /// Builds OFT message and combines options for cross-chain transfer.
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
- ///
273
- /// # Arguments
274
- /// * `from` - The address initiating the transfer
275
- /// * `send_param` - The send parameters including destination, recipient, and options
276
- /// * `amount_receive_ld` - The amount to be received in local decimals (after dust removal)
277
- ///
278
- /// # Returns
279
- /// A tuple of (encoded message, combined options)
246
+ /// Encodes the OFT message payload and merges enforced + extra options.
247
+ /// Runs the message inspector (if set) before returning `(message, options)`.
280
248
  fn __build_msg_and_options(
281
249
  env: &Env,
282
250
  from: &Address,
283
251
  send_param: &SendParam,
284
- amount_receive_ld: i128,
252
+ amount_received_ld: i128,
285
253
  ) -> (Bytes, Bytes) {
286
254
  let has_compose = !send_param.compose_msg.is_empty();
255
+ let compose_data = has_compose
256
+ .then(|| ComposeData { from: oft_utils::address_payload(env, from), msg: send_param.compose_msg.clone() });
257
+
258
+ // Build the OFT message
287
259
  let conversion_rate = Self::__decimal_conversion_rate(env);
288
- let (msg, _) = OFTMessage {
260
+ let message = OFTMessage {
289
261
  send_to: send_param.to.clone(),
290
- amount_sd: oft_utils::to_sd(env, amount_receive_ld, conversion_rate),
291
- compose_from: if has_compose { Some(oft_utils::address_payload(from)) } else { None },
292
- compose_msg: if has_compose { Some(send_param.compose_msg.clone()) } else { None },
262
+ amount_sd: oft_utils::to_sd(env, amount_received_ld, conversion_rate),
263
+ compose: compose_data,
293
264
  }
294
265
  .encode(env);
295
- let msg_type = if has_compose { types::SEND_AND_CALL } else { types::SEND };
266
+
267
+ // Combine the options with the message type
268
+ let msg_type = if has_compose { SEND_AND_CALL } else { SEND };
296
269
  let options = Self::combine_options(env, send_param.dst_eid, msg_type, &send_param.extra_options);
297
270
 
298
271
  // Optionally inspect message and options if inspector is set
272
+ // If it fails inspection, needs to revert in the implementation. ie. does not rely on return boolean
299
273
  if let Some(inspector) = Self::__msg_inspector(env) {
300
- OAppMsgInspectorClient::new(env, &inspector).inspect(&env.current_contract_address(), &msg, &options);
274
+ OAppMsgInspectorClient::new(env, &inspector).inspect(&env.current_contract_address(), &message, &options);
301
275
  }
302
276
 
303
- (msg, options)
277
+ (message, options)
304
278
  }
305
279
 
306
280
  // ----- Storage Accessors -----
@@ -310,17 +284,20 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
310
284
  OFTStorage::token(env).unwrap_or_panic(env, OFTError::NotInitialized)
311
285
  }
312
286
 
287
+ /// Retrieves the difference between local and shared decimals (`local_decimals - shared_decimals`).
288
+ fn __decimals_diff(env: &Env) -> u32 {
289
+ OFTStorage::decimals_diff(env).unwrap_or_panic(env, OFTError::NotInitialized)
290
+ }
291
+
313
292
  /// Retrieves the decimal conversion rate used for cross-chain normalization.
314
293
  fn __decimal_conversion_rate(env: &Env) -> i128 {
315
- let decimals_diff = OFTStorage::decimals_diff(env).unwrap_or_panic(env, OFTError::NotInitialized);
316
- 10_i128.pow(decimals_diff)
294
+ 10_i128.pow(Self::__decimals_diff(env))
317
295
  }
318
296
 
319
297
  /// Retrieves the shared decimals used for cross-chain normalization.
320
298
  fn __shared_decimals(env: &Env) -> u32 {
321
299
  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
300
+ local_decimals - Self::__decimals_diff(env)
324
301
  }
325
302
 
326
303
  /// Returns the message inspector address if set.
@@ -329,13 +306,6 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
329
306
  }
330
307
 
331
308
  /// 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
309
  fn __set_msg_inspector(env: &Env, inspector: &Option<Address>) {
340
310
  OFTStorage::set_or_remove_msg_inspector(env, inspector);
341
311
  MsgInspectorSet { inspector: inspector.clone() }.publish(env);
@@ -367,17 +337,19 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
367
337
  let oft_msg = OFTMessage::decode(message);
368
338
  let send_to = oft_utils::resolve_address(env, &oft_msg.send_to);
369
339
 
340
+ // Convert the amount to local decimals and credit the recipient
370
341
  let conversion_rate = Self::__decimal_conversion_rate(env);
371
342
  let amount_received_ld =
372
343
  Self::__credit(env, &send_to, oft_utils::to_ld(oft_msg.amount_sd, conversion_rate), origin.src_eid);
373
344
 
374
- if oft_msg.is_composed() {
345
+ // If there is a compose message, send it
346
+ if let Some(compose) = oft_msg.compose {
375
347
  let compose_msg = OFTComposeMsg {
376
348
  nonce: origin.nonce,
377
349
  src_eid: origin.src_eid,
378
350
  amount_ld: amount_received_ld,
379
- compose_from: oft_msg.compose_from.unwrap(),
380
- compose_msg: oft_msg.compose_msg.unwrap(),
351
+ compose_from: compose.from,
352
+ compose_msg: compose.msg,
381
353
  }
382
354
  .encode(env);
383
355
 
@@ -394,97 +366,167 @@ pub trait OFTInternal: OAppCore + OAppReceiver + OAppSenderInternal + OAppOption
394
366
  // OFTCore Trait (exposed as contract entrypoints)
395
367
  // ===========================================================================
396
368
 
397
- /// The public OFT trait defining the cross-chain token transfer interface.
369
+ /// Public OFT (Omnichain Fungible Token) interface for cross-chain token transfers.
398
370
  ///
399
- /// This trait is marked with `#[contracttrait]` so all its methods are exposed as
400
- /// contract entrypoints. Users implement this with `#[contractimpl(contracttrait)]`.
371
+ /// `OFTCore` defines the externally callable contract entrypoints for interacting with an
372
+ /// OFT deployment. Every method in this trait becomes a Soroban contract function via
373
+ /// `#[contract_trait]`. All business logic lives in [`OFTInternal`] — this trait is a
374
+ /// thin entrypoint layer that delegates to the internal implementations.
401
375
  ///
402
- /// This trait only exposes entrypoints. All internal logic is in `OFTInternal`.
376
+ /// # Typical Client Workflow
377
+ ///
378
+ /// 1. Call [`quote_oft`](OFTCore::quote_oft) to preview transfer limits, fees, and the
379
+ /// estimated receipt (amounts sent vs. received after dust removal and fees).
380
+ /// 2. Call [`quote_send`](OFTCore::quote_send) to obtain the LayerZero messaging fee.
381
+ /// 3. Call [`send`](OFTCore::send) to execute the cross-chain transfer, supplying the
382
+ /// quoted fee and a refund address for any excess.
403
383
  #[contract_trait(client_name = "OFTClient")]
404
384
  pub trait OFTCore: OFTInternal {
405
- /// Retrieves the token address associated with this OFT.
385
+ /// Returns the address of the underlying SEP-41 token managed by this OFT.
406
386
  fn token(env: &soroban_sdk::Env) -> soroban_sdk::Address {
407
387
  Self::__token(env)
408
388
  }
409
389
 
410
- /// Returns OFT version as (major, minor).
390
+ /// Returns the OFT messaging protocol version as `(major, minor)`.
391
+ ///
392
+ /// The version is used by off-chain tooling and peer contracts to verify wire-format
393
+ /// compatibility.
411
394
  fn oft_version(_env: &soroban_sdk::Env) -> (u64, u64) {
412
395
  (1, 1)
413
396
  }
414
397
 
415
- /// Retrieves the shared decimals used for cross-chain normalization.
398
+ /// Returns the **shared decimals** — the common decimal precision used in cross-chain
399
+ /// messages.
400
+ ///
401
+ /// Token amounts are normalized to this precision before encoding into LayerZero
402
+ /// messages, ensuring consistent values regardless of each chain's native token
403
+ /// decimals. For example, a token with 18 local decimals and 6 shared decimals has a
404
+ /// conversion rate of 10^12.
416
405
  fn shared_decimals(env: &soroban_sdk::Env) -> u32 {
417
406
  Self::__shared_decimals(env)
418
407
  }
419
408
 
420
- /// Retrieves the decimal conversion rate used for cross-chain normalization.
409
+ /// Returns the **decimal conversion rate** (`10 ^ (local_decimals - shared_decimals)`).
410
+ ///
411
+ /// This multiplier converts between local-decimal amounts (used on-chain) and
412
+ /// shared-decimal amounts (used in cross-chain messages). Any sub-conversion-rate
413
+ /// remainder ("dust") is stripped before sending to avoid rounding discrepancies
414
+ /// across chains.
421
415
  fn decimal_conversion_rate(env: &soroban_sdk::Env) -> i128 {
422
416
  Self::__decimal_conversion_rate(env)
423
417
  }
424
418
 
425
- /// Whether a separate token approval is required before sending.
419
+ /// Indicates whether the caller must approve a token allowance before calling [`send`](OFTCore::send).
426
420
  ///
427
- /// Helps wallet implementers determine integration requirements.
421
+ /// - **`false`** (default) the OFT contract itself controls the token (mint-burn model),
422
+ /// so no prior approval is needed.
423
+ /// - **`true`** — the OFT locks externally owned tokens (adapter model), requiring the
424
+ /// sender to call `token.approve(oft_address, amount)` beforehand.
428
425
  ///
429
- /// # Returns
430
- /// - `true` if a separate token approval step is required
431
- /// - `false` if no separate approval is needed
426
+ /// Wallet and frontend integrators should check this to determine whether an approval
427
+ /// transaction must precede the send.
432
428
  fn approval_required(_env: &soroban_sdk::Env) -> bool {
433
429
  false
434
430
  }
435
431
 
436
- /// Returns the message inspector address if set.
432
+ /// Returns the current message inspector contract address, or `None` if unset.
433
+ ///
434
+ /// When set, the inspector's `inspect` method is invoked during both
435
+ /// [`quote_send`](OFTCore::quote_send) and [`send`](OFTCore::send) to validate the
436
+ /// outgoing message payload and options before they reach the LayerZero endpoint.
437
437
  fn msg_inspector(env: &soroban_sdk::Env) -> Option<soroban_sdk::Address> {
438
438
  Self::__msg_inspector(env)
439
439
  }
440
440
 
441
- /// Sets or removes the message inspector address.
441
+ /// Sets or removes the message inspector contract.
442
+ ///
443
+ /// The message inspector is an **optional** validation hook. When configured, every
444
+ /// outbound message (from both `send` and `quote_send`) is passed to the inspector
445
+ /// contract's `inspect(contract, message, options)` method. The inspector should
446
+ /// **panic** to reject invalid messages, acting as an on-chain policy gate.
447
+ ///
448
+ /// Pass `None` to remove the inspector and disable outbound validation.
442
449
  ///
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.
450
+ /// # Authorization
451
+ /// Requires owner authentication (`#[only_auth]`).
446
452
  ///
447
453
  /// # Arguments
448
- /// * `inspector` - The address of the inspector contract, or None to remove
454
+ /// * `inspector` - Address of the inspector contract, or `None` to remove it
449
455
  #[only_auth]
450
456
  fn set_msg_inspector(env: &soroban_sdk::Env, inspector: &Option<soroban_sdk::Address>) {
451
457
  Self::__set_msg_inspector(env, inspector);
452
458
  }
453
459
 
454
- /// Quotes an OFT transfer without executing.
460
+ /// Previews an OFT transfer **without executing** it.
455
461
  ///
456
- /// # Returns
457
- /// (OFTLimit, fee details, receipt with estimated amounts)
462
+ /// Use this to display transfer details to the user before they commit. Returns:
463
+ ///
464
+ /// - **`OFTLimit`** — the minimum and maximum transferable amounts in local decimals.
465
+ /// - **`Vec<OFTFeeDetail>`** — itemized fees (empty by default; populated when custom
466
+ /// fee logic is implemented via [`OFTInternal::__quote_oft`]).
467
+ /// - **`OFTReceipt`** — estimated `amount_sent_ld` and `amount_received_ld` after
468
+ /// dust removal and any fees.
469
+ ///
470
+ /// # Arguments
471
+ /// * `from` - The address that would initiate the transfer
472
+ /// * `send_param` - The proposed transfer parameters (destination, amount, options, etc.)
458
473
  fn quote_oft(
459
474
  env: &soroban_sdk::Env,
460
- send_param: &oft_core::types::SendParam,
461
- ) -> (oft_core::types::OFTLimit, soroban_sdk::Vec<oft_core::types::OFTFeeDetail>, oft_core::types::OFTReceipt) {
462
- Self::__quote_oft(env, send_param)
475
+ from: &soroban_sdk::Address,
476
+ send_param: &oft_core::SendParam,
477
+ ) -> (oft_core::OFTLimit, soroban_sdk::Vec<oft_core::OFTFeeDetail>, oft_core::OFTReceipt) {
478
+ Self::__quote_oft(env, from, send_param)
463
479
  }
464
480
 
465
- /// Quotes a send operation including LayerZero messaging fees.
481
+ /// Quotes the **LayerZero messaging fee** required for a cross-chain send.
482
+ ///
483
+ /// Builds the outgoing message and options from `send_param`, then queries the
484
+ /// LayerZero endpoint for the corresponding fee. If a message inspector is set, it
485
+ /// will also validate the message at this stage.
486
+ ///
487
+ /// # Arguments
488
+ /// * `from` - The address that would initiate the transfer
489
+ /// * `send_param` - The proposed transfer parameters
490
+ /// * `pay_in_zro` - `true` to pay the messaging fee in the ZRO token; `false` to pay
491
+ /// in the chain's native token
492
+ ///
493
+ /// # Returns
494
+ /// A [`MessagingFee`](endpoint_v2::MessagingFee) containing the `native_fee` and
495
+ /// `zro_fee` required by the endpoint. Pass this value (or a superset) to
496
+ /// [`send`](OFTCore::send).
466
497
  fn quote_send(
467
498
  env: &soroban_sdk::Env,
468
499
  from: &soroban_sdk::Address,
469
- send_param: &oft_core::types::SendParam,
500
+ send_param: &oft_core::SendParam,
470
501
  pay_in_zro: bool,
471
502
  ) -> endpoint_v2::MessagingFee {
472
503
  Self::__quote_send(env, from, send_param, pay_in_zro)
473
504
  }
474
505
 
475
- /// Sends tokens cross-chain to another endpoint.
506
+ /// Executes a cross-chain token transfer via the LayerZero endpoint.
507
+ ///
508
+ /// Builds the OFT message and options, then sends the message through the LayerZero endpoint.
476
509
  ///
477
- /// From must be authenticated.
510
+ /// # Arguments
511
+ /// * `from` - The token sender (must authorize the call)
512
+ /// * `send_param` - Transfer parameters including destination chain (`dst_eid`),
513
+ /// recipient (`to`), amount, slippage floor (`min_amount_ld`), extra options, and
514
+ /// an optional compose message
515
+ /// * `fee` - The messaging fee to pay (obtain from [`quote_send`](OFTCore::quote_send))
516
+ /// * `refund_address` - Address to receive any excess fee refund
478
517
  ///
479
518
  /// # Returns
480
- /// (MessagingReceipt, OFTReceipt)
519
+ /// * [`MessagingReceipt`](endpoint_v2::MessagingReceipt) — the LayerZero message GUID,
520
+ /// nonce, and fee actually consumed
521
+ /// * [`OFTReceipt`](crate::types::OFTReceipt) — `amount_sent_ld` (debited) and
522
+ /// `amount_received_ld` (credited on destination after dust removal / fees)
481
523
  fn send(
482
524
  env: &soroban_sdk::Env,
483
525
  from: &soroban_sdk::Address,
484
- send_param: &oft_core::types::SendParam,
526
+ send_param: &oft_core::SendParam,
485
527
  fee: &endpoint_v2::MessagingFee,
486
528
  refund_address: &soroban_sdk::Address,
487
- ) -> (endpoint_v2::MessagingReceipt, oft_core::types::OFTReceipt) {
529
+ ) -> (endpoint_v2::MessagingReceipt, oft_core::OFTReceipt) {
488
530
  Self::__send(env, from, send_param, fee, refund_address)
489
531
  }
490
532
  }
@@ -532,3 +574,18 @@ macro_rules! impl_oft_lz_receive {
532
574
  }
533
575
  };
534
576
  }
577
+
578
+ // ===========================================================================
579
+ // Helper Functions
580
+ // ===========================================================================
581
+
582
+ /// Asserts that the send amount and min_amount are nonnegative.
583
+ /// # Arguments
584
+ /// * `env` - The environment
585
+ /// * `send_param` - The send parameters to assert
586
+ ///
587
+ /// # Panics
588
+ /// * `OFTError::InvalidAmount` - If the send amount or min_amount is negative
589
+ pub fn assert_nonnegative_amount(env: &Env, send_param: &SendParam) {
590
+ assert_with_error!(env, send_param.amount_ld >= 0 && send_param.min_amount_ld >= 0, OFTError::InvalidAmount);
591
+ }
@@ -3,11 +3,13 @@ use soroban_sdk::Address;
3
3
 
4
4
  #[storage]
5
5
  pub enum OFTStorage {
6
- /// The difference between local and shared decimals (local_decimals - shared_decimals)
6
+ /// The difference between local and shared decimals (local_decimals - shared_decimals).
7
+ /// Immutable: set once during construction and never modified.
7
8
  #[instance(u32)]
8
9
  DecimalsDiff,
9
10
 
10
- /// The address of the underlying token contract
11
+ /// The address of the underlying token contract.
12
+ /// Immutable: set once during construction and never modified.
11
13
  #[instance(Address)]
12
14
  Token,
13
15
 
@@ -17,7 +17,7 @@ fn test_decimal_conversion_rate() {
17
17
  }
18
18
 
19
19
  #[test]
20
- #[should_panic(expected = "Error(Contract, #3001)")] // InvalidLocalDecimals
20
+ #[should_panic(expected = "Error(Contract, #3002)")] // 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, #3003)")] // Overflow
37
+ #[should_panic(expected = "Error(Contract, #3004)")] // Overflow
38
38
  fn test_to_sd_overflow() {
39
39
  let env = Env::default();
40
40