@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,6 +1,7 @@
1
+ use crate as oft;
1
2
  use common_macros::{contract_error, contract_trait, only_auth, storage};
2
- use soroban_sdk::{assert_with_error, contractevent, contracttype, panic_with_error, Env};
3
- use utils::ownable::Ownable;
3
+ use soroban_sdk::{assert_with_error, contractevent, contracttype, Env};
4
+ use utils::auth::Auth;
4
5
 
5
6
  // =========================================================================
6
7
  // Types
@@ -14,12 +15,29 @@ pub enum Direction {
14
15
  Outbound,
15
16
  }
16
17
 
18
+ /// Configuration for rate limiting, used as input parameter.
17
19
  #[contracttype]
18
20
  #[derive(Clone, Debug, Eq, PartialEq)]
19
- pub struct RateLimit {
20
- limit: i128,
21
- window_seconds: u64,
21
+ pub struct RateLimitConfig {
22
+ pub limit: i128,
23
+ pub window_seconds: u64,
24
+ }
25
+
26
+ /// Internal storage struct for rate limit state.
27
+ ///
28
+ /// The rate limiter uses a "leaky bucket" algorithm where:
29
+ /// - `config.limit` defines the maximum tokens that can be "in flight" at any time
30
+ /// - `config.window_seconds` defines how long it takes for the bucket to fully drain
31
+ /// - Tokens decay linearly over time: `decay = elapsed_time * limit / window_seconds`
32
+ /// - Current in-flight = `in_flight_on_last_update - decay` (clamped to 0)
33
+ #[contracttype]
34
+ #[derive(Clone, Debug, Eq, PartialEq)]
35
+ struct RateLimitState {
36
+ /// The rate limit configuration (limit and window_seconds)
37
+ config: RateLimitConfig,
38
+ /// The in-flight amount at the time of `last_update` (before decay is applied)
22
39
  in_flight_on_last_update: i128,
40
+ /// Timestamp of the last update (used to calculate decay)
23
41
  last_update: u64,
24
42
  }
25
43
 
@@ -28,8 +46,8 @@ pub struct RateLimit {
28
46
  // =========================================================================
29
47
 
30
48
  #[storage]
31
- pub enum RateLimitStorage {
32
- #[persistent(RateLimit)]
49
+ enum RateLimitStorage {
50
+ #[persistent(RateLimitState)]
33
51
  RateLimit { direction: Direction, eid: u32 },
34
52
  }
35
53
 
@@ -40,9 +58,9 @@ pub enum RateLimitStorage {
40
58
  #[contract_error]
41
59
  pub enum RateLimitError {
42
60
  ExceededRateLimit = 3120,
61
+ InvalidAmount,
43
62
  InvalidTimestamp,
44
- InvalidWindowSeconds,
45
- InvalidLimit,
63
+ InvalidConfig,
46
64
  SameValue,
47
65
  }
48
66
 
@@ -55,24 +73,8 @@ pub enum RateLimitError {
55
73
  pub struct RateLimitSet {
56
74
  pub direction: Direction,
57
75
  pub eid: u32,
58
- pub limit: i128,
59
- pub window_seconds: u64,
60
- }
61
-
62
- #[contractevent]
63
- #[derive(Clone, Debug, Eq, PartialEq)]
64
- pub struct RateLimitUpdated {
65
- pub direction: Direction,
66
- pub eid: u32,
67
- pub limit: i128,
68
- pub window_seconds: u64,
69
- }
70
-
71
- #[contractevent]
72
- #[derive(Clone, Debug, Eq, PartialEq)]
73
- pub struct RateLimitUnset {
74
- pub direction: Direction,
75
- pub eid: u32,
76
+ /// The rate limit configuration, or None if the rate limit is removed
77
+ pub config: Option<RateLimitConfig>,
76
78
  }
77
79
 
78
80
  // =========================================================================
@@ -80,114 +82,167 @@ pub struct RateLimitUnset {
80
82
  // =========================================================================
81
83
 
82
84
  #[contract_trait]
83
- pub trait RateLimiter: RateLimiterInternal + Ownable + Sized {
84
- #[only_auth]
85
- fn set_rate_limit(env: &Env, direction: &Direction, eid: u32, limit: i128, window_seconds: u64) {
86
- assert_with_error!(env, limit > 0, RateLimitError::InvalidLimit);
87
- assert_with_error!(env, window_seconds > 0, RateLimitError::InvalidWindowSeconds);
88
- if RateLimitStorage::has_rate_limit(env, direction, eid) {
89
- let rate_limit_data = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
90
- assert_with_error!(
91
- env,
92
- limit != rate_limit_data.limit || window_seconds != rate_limit_data.window_seconds,
93
- RateLimitError::SameValue
94
- );
95
- checkpoint_rate_limit_in_flight(env, direction, eid);
96
- let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
97
- rate_limit.limit = limit;
98
- rate_limit.window_seconds = window_seconds;
99
- RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
100
- RateLimitUpdated { direction: direction.clone(), eid, limit, window_seconds }.publish(env);
101
- } else {
102
- RateLimitStorage::set_rate_limit(
103
- env,
104
- direction,
105
- eid,
106
- &RateLimit {
107
- limit,
108
- window_seconds,
109
- in_flight_on_last_update: 0,
110
- last_update: env.ledger().timestamp(),
111
- },
112
- );
113
- RateLimitSet { direction: direction.clone(), eid, limit, window_seconds }.publish(env);
114
- }
115
- }
85
+ pub trait RateLimiter: RateLimiterInternal + Auth {
86
+ // =========================================================================
87
+ // Management Functions
88
+ // =========================================================================
116
89
 
90
+ /// Sets or removes a rate limit for a specific direction and endpoint.
91
+ ///
92
+ /// # Arguments
93
+ /// * `direction` - The direction (Inbound or Outbound)
94
+ /// * `eid` - The endpoint ID
95
+ /// * `config` - The rate limit configuration, or None to remove the rate limit
117
96
  #[only_auth]
118
- fn unset_rate_limit(env: &Env, direction: &Direction, eid: u32) {
119
- assert_with_error!(env, RateLimitStorage::has_rate_limit(env, direction, eid), RateLimitError::SameValue);
120
- RateLimitStorage::remove_rate_limit(env, direction, eid);
121
- RateLimitUnset { direction: direction.clone(), eid }.publish(env);
97
+ fn set_rate_limit(
98
+ env: &soroban_sdk::Env,
99
+ direction: &oft::rate_limiter::Direction,
100
+ eid: u32,
101
+ config: Option<oft::rate_limiter::RateLimitConfig>,
102
+ ) {
103
+ Self::__set_rate_limit(env, direction, eid, config);
122
104
  }
123
105
 
124
- fn rate_limit_config(env: &Env, direction: &Direction, eid: u32) -> (i128, u64) {
125
- if !RateLimitStorage::has_rate_limit(env, direction, eid) {
126
- return (0, 0);
127
- }
128
- let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
129
- (rate_limit.limit, rate_limit.window_seconds)
106
+ // =========================================================================
107
+ // View Functions
108
+ // =========================================================================
109
+
110
+ /// Returns the rate limit configuration for a direction and endpoint.
111
+ /// Returns None if no rate limit is configured.
112
+ fn rate_limit_config(
113
+ env: &soroban_sdk::Env,
114
+ direction: &oft::rate_limiter::Direction,
115
+ eid: u32,
116
+ ) -> Option<oft::rate_limiter::RateLimitConfig> {
117
+ Self::__rate_limit_config(env, direction, eid)
130
118
  }
131
119
 
132
- fn rate_limit_in_flight(env: &Env, direction: &Direction, eid: u32) -> i128 {
133
- calculate_in_flight(env, direction, eid)
120
+ /// Returns the current in-flight amount for a direction and endpoint.
121
+ fn rate_limit_in_flight(env: &soroban_sdk::Env, direction: &oft::rate_limiter::Direction, eid: u32) -> i128 {
122
+ Self::__rate_limit_in_flight(env, direction, eid)
134
123
  }
135
124
 
136
- fn rate_limit_capacity(env: &Env, direction: &Direction, eid: u32) -> i128 {
137
- if !RateLimitStorage::has_rate_limit(env, direction, eid) {
138
- return i128::MAX;
139
- }
140
- let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
141
- let in_flight = calculate_in_flight(env, direction, eid);
142
- if rate_limit.limit > in_flight {
143
- rate_limit.limit - in_flight
144
- } else {
145
- 0
146
- }
125
+ /// Returns the available capacity for a direction and endpoint.
126
+ /// Returns i128::MAX if no rate limit is configured.
127
+ fn rate_limit_capacity(env: &soroban_sdk::Env, direction: &oft::rate_limiter::Direction, eid: u32) -> i128 {
128
+ Self::__rate_limit_capacity(env, direction, eid)
147
129
  }
148
130
  }
149
131
 
150
132
  /// Internal trait for rate limiter operations used by OFT hooks.
151
133
  /// Contains only truly internal methods that are called from OFTInternal implementations.
152
134
  pub trait RateLimiterInternal {
135
+ // =========================================================================
136
+ // OFT Hooks
137
+ // =========================================================================
138
+
153
139
  /// Consumes the specified amount from the rate limit capacity.
154
140
  /// Used internally by `__debit` and `__credit` to enforce rate limits.
155
141
  ///
156
142
  /// # Errors
157
143
  /// * `ExceededRateLimit` - If the amount exceeds the available capacity.
158
144
  fn __consume_rate_limit_capacity(env: &Env, direction: &Direction, eid: u32, amount: i128) {
159
- if !RateLimitStorage::has_rate_limit(env, direction, eid) {
145
+ assert_with_error!(env, amount >= 0, RateLimitError::InvalidAmount);
146
+
147
+ let Some(mut state) = RateLimitStorage::rate_limit(env, direction, eid) else {
160
148
  return;
161
- }
162
- checkpoint_rate_limit_in_flight(env, direction, eid);
163
- let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
164
- // Check against remaining capacity (limit - current in_flight), not total limit
165
- let capacity = if rate_limit.limit > rate_limit.in_flight_on_last_update {
166
- rate_limit.limit - rate_limit.in_flight_on_last_update
167
- } else {
168
- 0
169
149
  };
170
- if amount > capacity {
171
- panic_with_error!(env, RateLimitError::ExceededRateLimit);
172
- }
173
- rate_limit.in_flight_on_last_update += amount;
174
- RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
150
+
151
+ // Apply decay and update timestamp
152
+ let in_flight = calculate_decayed_in_flight(env, &state);
153
+ state.in_flight_on_last_update = in_flight;
154
+ state.last_update = env.ledger().timestamp();
155
+
156
+ // Check capacity and consume
157
+ let capacity = (state.config.limit - in_flight).max(0);
158
+ assert_with_error!(env, amount <= capacity, RateLimitError::ExceededRateLimit);
159
+ state.in_flight_on_last_update += amount;
160
+
161
+ RateLimitStorage::set_rate_limit(env, direction, eid, &state);
175
162
  }
176
163
 
177
164
  /// Releases the specified amount back to the rate limit capacity.
178
165
  /// Used internally by `__credit` to release outbound capacity on inbound messages.
179
166
  fn __release_rate_limit_capacity(env: &Env, direction: &Direction, eid: u32, amount: i128) {
180
- if !RateLimitStorage::has_rate_limit(env, direction, eid) {
167
+ assert_with_error!(env, amount >= 0, RateLimitError::InvalidAmount);
168
+
169
+ let Some(mut state) = RateLimitStorage::rate_limit(env, direction, eid) else {
181
170
  return;
171
+ };
172
+
173
+ // Apply decay and update timestamp
174
+ let in_flight = calculate_decayed_in_flight(env, &state);
175
+ state.in_flight_on_last_update = (in_flight - amount).max(0);
176
+ state.last_update = env.ledger().timestamp();
177
+
178
+ RateLimitStorage::set_rate_limit(env, direction, eid, &state);
179
+ }
180
+
181
+ // =========================================================================
182
+ // Management Functions
183
+ // =========================================================================
184
+
185
+ /// Sets or removes a rate limit for a specific direction and endpoint.
186
+ ///
187
+ /// # Arguments
188
+ /// * `direction` - The direction (Inbound or Outbound)
189
+ /// * `eid` - The endpoint ID
190
+ /// * `config` - The rate limit configuration, or None to remove the rate limit
191
+ fn __set_rate_limit(env: &Env, direction: &Direction, eid: u32, config: Option<RateLimitConfig>) {
192
+ let current_state = RateLimitStorage::rate_limit(env, direction, eid);
193
+ let current_config = current_state.as_ref().map(|s| s.config.clone());
194
+ assert_with_error!(env, current_config != config, RateLimitError::SameValue);
195
+
196
+ match config {
197
+ Some(cfg) => {
198
+ assert_with_error!(env, cfg.limit >= 0 && cfg.window_seconds > 0, RateLimitError::InvalidConfig);
199
+
200
+ let state = if let Some(mut existing) = current_state {
201
+ // Update existing: checkpoint in-flight before changing config
202
+ existing.in_flight_on_last_update = calculate_decayed_in_flight(env, &existing);
203
+ existing.last_update = env.ledger().timestamp();
204
+ existing.config = cfg.clone();
205
+ existing
206
+ } else {
207
+ // Create new
208
+ RateLimitState {
209
+ config: cfg.clone(),
210
+ in_flight_on_last_update: 0,
211
+ last_update: env.ledger().timestamp(),
212
+ }
213
+ };
214
+
215
+ RateLimitStorage::set_rate_limit(env, direction, eid, &state);
216
+ RateLimitSet { direction: direction.clone(), eid, config: Some(cfg) }.publish(env);
217
+ }
218
+ None => {
219
+ RateLimitStorage::remove_rate_limit(env, direction, eid);
220
+ RateLimitSet { direction: direction.clone(), eid, config: None }.publish(env);
221
+ }
182
222
  }
183
- checkpoint_rate_limit_in_flight(env, direction, eid);
184
- let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
185
- if amount >= rate_limit.in_flight_on_last_update {
186
- rate_limit.in_flight_on_last_update = 0;
187
- } else {
188
- rate_limit.in_flight_on_last_update -= amount;
189
- }
190
- RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
223
+ }
224
+
225
+ // =========================================================================
226
+ // View Functions
227
+ // =========================================================================
228
+
229
+ /// Returns the rate limit configuration for a direction and endpoint.
230
+ /// Returns None if no rate limit is configured.
231
+ fn __rate_limit_config(env: &Env, direction: &Direction, eid: u32) -> Option<RateLimitConfig> {
232
+ RateLimitStorage::rate_limit(env, direction, eid).map(|s| s.config)
233
+ }
234
+
235
+ /// Returns the current in-flight amount for a direction and endpoint.
236
+ fn __rate_limit_in_flight(env: &Env, direction: &Direction, eid: u32) -> i128 {
237
+ RateLimitStorage::rate_limit(env, direction, eid).map(|s| calculate_decayed_in_flight(env, &s)).unwrap_or(0)
238
+ }
239
+
240
+ /// Returns the available capacity for a direction and endpoint.
241
+ /// Returns i128::MAX if no rate limit is configured.
242
+ fn __rate_limit_capacity(env: &Env, direction: &Direction, eid: u32) -> i128 {
243
+ RateLimitStorage::rate_limit(env, direction, eid)
244
+ .map(|s| (s.config.limit - calculate_decayed_in_flight(env, &s)).max(0))
245
+ .unwrap_or(i128::MAX)
191
246
  }
192
247
  }
193
248
 
@@ -195,30 +250,15 @@ pub trait RateLimiterInternal {
195
250
  // Helper Functions
196
251
  // =========================================================================
197
252
 
198
- /// Helper function to calculate the current in-flight amount with decay.
199
- /// Used by both public trait methods and internal implementations.
200
- fn calculate_in_flight(env: &Env, direction: &Direction, eid: u32) -> i128 {
201
- if !RateLimitStorage::has_rate_limit(env, direction, eid) {
202
- return 0;
203
- }
204
- let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
253
+ /// Calculates the current in-flight amount with decay applied.
254
+ /// This is a pure function that doesn't access storage.
255
+ fn calculate_decayed_in_flight(env: &Env, state: &RateLimitState) -> i128 {
205
256
  let timestamp = env.ledger().timestamp();
206
- assert_with_error!(env, timestamp >= rate_limit.last_update, RateLimitError::InvalidTimestamp);
207
- let elapsed = timestamp - rate_limit.last_update;
208
- let decay = (elapsed as i128) * rate_limit.limit / (rate_limit.window_seconds as i128);
209
- if decay < rate_limit.in_flight_on_last_update {
210
- rate_limit.in_flight_on_last_update - decay
211
- } else {
212
- 0
213
- }
214
- }
257
+ assert_with_error!(env, timestamp >= state.last_update, RateLimitError::InvalidTimestamp);
258
+
259
+ let elapsed = timestamp - state.last_update;
260
+ let decay = (elapsed as i128) * state.config.limit / (state.config.window_seconds as i128);
215
261
 
216
- /// Checkpoints the current in-flight amount by applying decay and updating timestamp.
217
- /// Used internally before modifying rate limit state.
218
- fn checkpoint_rate_limit_in_flight(env: &Env, direction: &Direction, eid: u32) {
219
- let in_flight = calculate_in_flight(env, direction, eid);
220
- let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
221
- rate_limit.in_flight_on_last_update = in_flight;
222
- rate_limit.last_update = env.ledger().timestamp();
223
- RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
262
+ // Ensure the decayed in-flight amount is not negative
263
+ (state.in_flight_on_last_update - decay).max(0)
224
264
  }
@@ -1,4 +1,5 @@
1
1
  use crate::{
2
+ self as oft,
2
3
  extensions::{
3
4
  oft_fee::{OFTFee, OFTFeeInternal},
4
5
  pausable::{OFTPausable, OFTPausableInternal},
@@ -7,7 +8,6 @@ use crate::{
7
8
  oft_types::{lock_unlock, mint_burn, OftType},
8
9
  };
9
10
  use common_macros::{contract_impl, storage};
10
- use endpoint_v2::Origin;
11
11
  use oapp_macros::oapp;
12
12
  use oft_core::{
13
13
  errors::OFTError,
@@ -15,7 +15,7 @@ use oft_core::{
15
15
  types::{OFTFeeDetail, OFTLimit, OFTReceipt, SendParam},
16
16
  utils as oft_utils, OFTCore, OFTInternal,
17
17
  };
18
- use soroban_sdk::{assert_with_error, vec, Address, Bytes, BytesN, Env, Vec};
18
+ use soroban_sdk::{assert_with_error, vec, Address, Bytes, Env, Vec};
19
19
 
20
20
  // =========================================================================
21
21
  // Storage
@@ -83,19 +83,26 @@ impl OFTCore for OFT {
83
83
  /// OFT behavior for standard OFT with extension hooks
84
84
  impl OFTInternal for OFT {
85
85
  /// Overrides default to add pausable check and fee calculation.
86
+ ///
87
+ /// Dust handling (consistent with EVM):
88
+ /// - fee = 0: dust stays with sender
89
+ /// - fee > 0: dust goes to fee recipient (included in charged fee)
86
90
  fn __debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, dst_eid: u32) -> OFTReceipt {
87
91
  Self::__assert_not_paused(env);
88
92
 
89
93
  let conversion_rate = Self::__decimal_conversion_rate(env);
90
-
91
- // Apply the fee before dust removal to ensure the fee is calculated on the full amount and dust is not transferred
92
94
  let fee = Self::__fee_view(env, dst_eid, amount_ld);
93
- let amount_after_fee = amount_ld - fee;
94
- let amount_received_ld = oft_utils::remove_dust(amount_after_fee, conversion_rate);
95
95
 
96
- // amount_sent_ld = what recipient receives + fee charged
97
- // Dust is excluded (stays with sender)
98
- let amount_sent_ld = amount_received_ld + fee;
96
+ let (amount_sent_ld, amount_received_ld) = if fee == 0 {
97
+ // No fee: dust stays with sender (default OFT behavior)
98
+ let amount_sent_ld = oft_utils::remove_dust(amount_ld, conversion_rate);
99
+ (amount_sent_ld, amount_sent_ld)
100
+ } else {
101
+ // With fee: match EVM OFTFee behavior
102
+ // - dust is included in charged fee (goes to fee recipient)
103
+ let amount_received_ld = oft_utils::remove_dust(amount_ld - fee, conversion_rate);
104
+ (amount_ld, amount_received_ld)
105
+ };
99
106
 
100
107
  assert_with_error!(env, amount_received_ld >= min_amount_ld, OFTError::SlippageExceeded);
101
108
 
@@ -130,9 +137,9 @@ impl OFTInternal for OFT {
130
137
  OftType::MintBurn => mint_burn::credit::<Self>(env, to, amount_ld, src_eid),
131
138
  };
132
139
 
133
- // Rate limit checks (using amount_credited - the actual credited amount)
134
- Self::__consume_rate_limit_capacity(env, &Direction::Inbound, src_eid, amount_credited);
135
- Self::__release_rate_limit_capacity(env, &Direction::Outbound, src_eid, amount_credited);
140
+ // Rate limit checks
141
+ Self::__consume_rate_limit_capacity(env, &Direction::Inbound, src_eid, amount_ld);
142
+ Self::__release_rate_limit_capacity(env, &Direction::Outbound, src_eid, amount_ld);
136
143
 
137
144
  amount_credited
138
145
  }
@@ -4,7 +4,7 @@
4
4
  //! tokens from the contract on credit (receive).
5
5
  //! Used for OFT adapters that wrap existing tokens.
6
6
 
7
- use oft_core::{oft_core::OFTCore, types::OFTReceipt};
7
+ use oft_core::{types::OFTReceipt, OFTCore};
8
8
  use soroban_sdk::{token::TokenClient, Address, Env};
9
9
 
10
10
  /// Debit tokens using LockUnlock OFT type (locks tokens in contract).
@@ -3,7 +3,7 @@
3
3
  //! This OFT type burns tokens on debit (send) and mints tokens on credit (receive).
4
4
  //! Used when the OFT contract has mint/burn authority over the token.
5
5
 
6
- use oft_core::{oft_core::OFTCore, types::OFTReceipt};
6
+ use oft_core::{types::OFTReceipt, OFTCore};
7
7
  use soroban_sdk::{token::StellarAssetClient, Address, Env};
8
8
 
9
9
  /// Debit tokens using MintBurn OFT type (burns tokens from sender).
@@ -21,7 +21,4 @@ simple-message-lib = { workspace = true }
21
21
  message-lib-common = { workspace = true, features = ["testutils"] }
22
22
  endpoint-v2 = { workspace = true, features = ["testutils"] }
23
23
  utils = { workspace = true, features = ["testutils"] }
24
- insta = { workspace = true }
25
- oapp-macros = { workspace = true }
26
- stellar-macros = { workspace = true }
27
- stellar-tokens = { workspace = true }
24
+ oapp-macros = { workspace = true }
@@ -7,7 +7,7 @@ extern crate std;
7
7
  use crate::{
8
8
  self as oft_core,
9
9
  integration_tests::utils::{address_to_peer_bytes32, peer_bytes32_to_address},
10
- oft_core::{lz_receive, OFTClient, OFTCore, OFTInternal},
10
+ oft_core::{OFTClient, OFTCore, OFTInternal},
11
11
  storage::OFTStorage,
12
12
  types::OFTReceipt,
13
13
  };
@@ -39,7 +39,7 @@ impl LzReceiveInternal for TestOFT {
39
39
  executor: &Address,
40
40
  value: i128,
41
41
  ) {
42
- lz_receive::<Self>(env, executor, origin, guid, message, extra_data, value)
42
+ <Self as OFTInternal>::__receive(env, origin, guid, message, extra_data, executor, value)
43
43
  }
44
44
  }
45
45
 
@@ -155,10 +155,28 @@ pub fn lz_compose(
155
155
 
156
156
  // returns (encoded_payload, options, send_library)
157
157
  pub fn scan_packet_sent_event(env: &Env, endpoint: &Address) -> Option<(Bytes, Bytes, Address)> {
158
+ use soroban_sdk::TryFromVal;
159
+
158
160
  let mut packet = None;
159
- for (emitter, topics, data) in env.events().all() {
160
- let packet_sent_symbol = Symbol::new(env, "packet_sent").to_val();
161
- if emitter == *endpoint && topics.contains(packet_sent_symbol) {
161
+ let events = env.events().all().filter_by_contract(endpoint);
162
+ for event in events.events().iter() {
163
+ let v0 = match &event.body {
164
+ soroban_sdk::xdr::ContractEventBody::V0(v0) => v0,
165
+ };
166
+
167
+ // Check if this is a packet_sent event by looking at topics
168
+ let mut is_packet_sent = false;
169
+ for topic in v0.topics.iter() {
170
+ if let Ok(sym) = Symbol::try_from_val(env, topic) {
171
+ if sym == Symbol::new(env, "packet_sent") {
172
+ is_packet_sent = true;
173
+ break;
174
+ }
175
+ }
176
+ }
177
+
178
+ if is_packet_sent {
179
+ let data: Val = Val::try_from_val(env, &v0.data).unwrap();
162
180
  let map: Map<Symbol, Val> = data.into_val(env);
163
181
 
164
182
  let encoded_payload: Bytes = map.get(Symbol::new(env, "encoded_packet")).unwrap().into_val(env);
@@ -1,12 +1,13 @@
1
1
  use common_macros::contract_error;
2
2
 
3
3
  // OFT library error codes: 3000-3099
4
- // See ERROR_SPEC.md for allocation rules
4
+ // See docs/error-spec.md for allocation rules
5
5
 
6
6
  /// OFTError: 3000-3099
7
7
  #[contract_error]
8
8
  pub enum OFTError {
9
- InvalidLocalDecimals = 3000,
9
+ InvalidAmount = 3000,
10
+ InvalidLocalDecimals,
10
11
  NotInitialized,
11
12
  Overflow,
12
13
  SlippageExceeded,
@@ -18,3 +18,9 @@ pub struct OFTReceived {
18
18
  pub to: Address,
19
19
  pub amount_received_ld: i128,
20
20
  }
21
+
22
+ #[contractevent]
23
+ #[derive(Clone, Debug, Eq, PartialEq)]
24
+ pub struct MsgInspectorSet {
25
+ pub inspector: Option<Address>,
26
+ }
@@ -3,7 +3,7 @@
3
3
  pub mod codec;
4
4
  pub mod errors;
5
5
  pub mod events;
6
- pub mod oft_core;
6
+ mod oft_core;
7
7
  pub mod storage;
8
8
  pub mod types;
9
9
  pub mod utils;