@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
@@ -74,21 +74,21 @@ impl LzExecutor {
74
74
  ///
75
75
  /// # Design
76
76
  ///
77
- /// For `lz_receive`: OApps call `verify_and_clear_payload` (see `oapp_receiver.rs`)
78
- /// which requires executor auth and optionally transfers native token from executor to OApp.
77
+ /// ## Helper path (via registered ExecutorHelper):
79
78
  ///
80
- /// For `lz_compose`: Composers follow the same pattern - require executor auth and
81
- /// optionally transfer native token from executor to composer.
79
+ /// The executor helper calls `executor.require_auth()` then delegates to the OApp.
80
+ /// Auth contexts have 2-3 entries depending on whether value == 0:
81
+ /// - Context 1: helper function (e.g., `execute`, `compose`) on the registered helper address
82
+ /// - Context 2: `lz_receive`/`lz_compose` on the OApp/Composer (always present)
83
+ /// - Context 3: `transfer` on native token (only if value != 0)
82
84
  ///
83
- /// Both create 1-2 auth contexts depending on whether value == 0:
84
- /// - Context 1: `lz_receive`/`lz_compose` on the OApp/Composer (always present)
85
- /// - Context 2: `transfer` on native token (only if value != 0)
85
+ /// ## Alert path:
86
86
  ///
87
- /// For `lz_receive_alert`/`lz_compose_alert`: These are called directly on the endpoint
87
+ /// `lz_receive_alert`/`lz_compose_alert` are called directly on the endpoint
88
88
  /// to record failed executions. Only 1 context on the endpoint is expected.
89
89
  fn validate_auth_contexts(env: &Env, contexts: &Vec<Context>) -> Result<(), ExecutorError> {
90
- // Early bounds check: max 2 contexts expected
91
- if contexts.len() > 2 {
90
+ // Early bounds check: max 3 contexts expected (helper fn + lz_receive/lz_compose + optional transfer)
91
+ if contexts.len() > 3 {
92
92
  return Err(ExecutorError::UnauthorizedContext);
93
93
  }
94
94
 
@@ -99,7 +99,7 @@ impl LzExecutor {
99
99
 
100
100
  let first_fn_name = &first_ctx.fn_name;
101
101
 
102
- // Alert path: lz_receive_alert or lz_compose_alert (lazy Symbol creation)
102
+ // Alert path: lz_receive_alert or lz_compose_alert
103
103
  if *first_fn_name == Symbol::new(env, "lz_receive_alert")
104
104
  || *first_fn_name == Symbol::new(env, "lz_compose_alert")
105
105
  {
@@ -110,36 +110,52 @@ impl LzExecutor {
110
110
  return Ok(());
111
111
  }
112
112
 
113
- // Execute path: lz_receive or lz_compose
114
- if *first_fn_name != Symbol::new(env, "lz_receive") && *first_fn_name != Symbol::new(env, "lz_compose") {
113
+ // Helper path: first context must be on the registered executor helper
114
+ let helper_config =
115
+ ExecutorStorage::executor_helper(env).ok_or(ExecutorError::UnauthorizedContext)?;
116
+
117
+ // Validate first context: must be the helper address with an allowed function name
118
+ if first_ctx.contract != helper_config.address
119
+ || !helper_config.allowed_functions.contains(first_fn_name)
120
+ {
121
+ return Err(ExecutorError::UnauthorizedContext);
122
+ }
123
+
124
+ // Second context: must be lz_receive or lz_compose on the OApp/Composer
125
+ let second_ctx = contexts.get(1).ok_or(ExecutorError::UnauthorizedContext)?;
126
+ let Context::Contract(second_ctx) = second_ctx else {
127
+ return Err(ExecutorError::UnauthorizedContext);
128
+ };
129
+
130
+ let second_fn_name = &second_ctx.fn_name;
131
+ if *second_fn_name != Symbol::new(env, "lz_receive")
132
+ && *second_fn_name != Symbol::new(env, "lz_compose")
133
+ {
115
134
  return Err(ExecutorError::UnauthorizedContext);
116
135
  }
117
136
 
118
- let value = Self::extract_i128(env, first_ctx.args.last())?;
137
+ // Extract value from the lz_receive/lz_compose args (last arg is value)
138
+ let value = Self::extract_i128(env, second_ctx.args.last())?;
119
139
  if value == 0 {
120
- // If value is 0, there should be exactly 1 context
121
- if contexts.len() != 1 {
140
+ // No transfer expected - should be exactly 2 contexts
141
+ if contexts.len() != 2 {
122
142
  return Err(ExecutorError::UnauthorizedContext);
123
143
  }
124
144
  return Ok(());
125
145
  }
126
146
 
127
- // value != 0: validate transfer context (only if there are 2 contexts)
128
- if contexts.len() != 2 {
129
- return Err(ExecutorError::UnauthorizedContext);
130
- }
131
-
132
- // Second context must be the transfer context
133
- let Context::Contract(second_ctx) = contexts.get(1).unwrap() else {
147
+ // value != 0: validate transfer context
148
+ let third_ctx = contexts.get(2).ok_or(ExecutorError::UnauthorizedContext)?;
149
+ let Context::Contract(third_ctx) = third_ctx else {
134
150
  return Err(ExecutorError::UnauthorizedContext);
135
151
  };
136
152
 
137
153
  let native_token = LayerZeroEndpointV2Client::new(env, &Self::endpoint(env)).native_token();
138
- let transfer_amount = Self::extract_i128(env, second_ctx.args.get(2))?;
154
+ let transfer_amount = Self::extract_i128(env, third_ctx.args.get(2))?;
139
155
 
140
156
  // Validate transfer context: must be transfer with matching value and native token contract
141
- if second_ctx.fn_name != Symbol::new(env, "transfer")
142
- || second_ctx.contract != native_token
157
+ if third_ctx.fn_name != Symbol::new(env, "transfer")
158
+ || third_ctx.contract != native_token
143
159
  || transfer_amount != value
144
160
  {
145
161
  return Err(ExecutorError::UnauthorizedContext);
@@ -9,11 +9,10 @@ use common_macros::{contract_impl, lz_contract};
9
9
  use endpoint_v2::{FeeRecipient, LayerZeroEndpointV2Client, Origin};
10
10
  use fee_lib_interfaces::{ExecutorFeeLibClient, FeeParams};
11
11
  use message_lib_common::interfaces::ILayerZeroExecutor;
12
- use soroban_sdk::{token::TokenClient, vec, Address, Bytes, BytesN, Env, Vec};
12
+ use soroban_sdk::{token::TokenClient, vec, Address, Bytes, BytesN, Env, Symbol, Vec};
13
13
  use utils::option_ext::OptionExt;
14
14
  use worker::{
15
- assert_acl, assert_not_paused, assert_supported_message_lib, errors::WorkerError, init_worker, require_admin_auth,
16
- Worker,
15
+ assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, Worker, WorkerError,
17
16
  };
18
17
 
19
18
  /// LayerZero Executor contract for cross-chain message execution.
@@ -69,6 +68,32 @@ impl LzExecutor {
69
68
  require_admin_auth::<Self>(env, admin);
70
69
  TokenClient::new(env, token).transfer(&env.current_contract_address(), to, &amount);
71
70
  }
71
+
72
+ /// Registers an executor helper contract with its allowed function names.
73
+ ///
74
+ /// The executor helper is an intermediary contract that calls `executor.require_auth()`
75
+ /// before delegating to OApp functions. The registered address and function names are
76
+ /// used by `validate_auth_contexts` to verify authorization contexts.
77
+ ///
78
+ /// # Arguments
79
+ /// * `admin` - Admin address (must provide authorization)
80
+ /// * `helper` - The executor helper contract address
81
+ /// * `allowed_functions` - Function names the helper is allowed to invoke (e.g., "execute", "compose")
82
+ pub fn set_executor_helper(env: &Env, admin: &Address, helper: &Address, allowed_functions: &Vec<Symbol>) {
83
+ require_admin_auth::<Self>(env, admin);
84
+ ExecutorStorage::set_executor_helper(
85
+ env,
86
+ &crate::storage::ExecutorHelperConfig {
87
+ address: helper.clone(),
88
+ allowed_functions: allowed_functions.clone(),
89
+ },
90
+ );
91
+ }
92
+
93
+ /// Returns the registered executor helper configuration.
94
+ pub fn executor_helper(env: &Env) -> Option<crate::storage::ExecutorHelperConfig> {
95
+ ExecutorStorage::executor_helper(env)
96
+ }
72
97
  }
73
98
 
74
99
  // ============================================================================
@@ -1,9 +1,11 @@
1
1
  #![no_std]
2
2
 
3
- pub mod errors;
4
3
  pub mod events;
5
- pub mod interfaces;
6
4
 
5
+ mod errors;
6
+ mod interfaces;
7
+
8
+ pub use errors::*;
7
9
  pub use interfaces::*;
8
10
 
9
11
  cfg_if::cfg_if! {
@@ -1,8 +1,21 @@
1
1
  use common_macros::storage;
2
- use soroban_sdk::Address;
2
+ use soroban_sdk::{contracttype, Address, Vec, Symbol};
3
3
 
4
4
  use crate::DstConfig;
5
5
 
6
+ /// Configuration for a registered executor helper contract.
7
+ ///
8
+ /// Stores the helper contract address and the set of function names
9
+ /// that the executor is allowed to authorize calls through.
10
+ #[contracttype]
11
+ #[derive(Clone, Debug, Eq, PartialEq)]
12
+ pub struct ExecutorHelperConfig {
13
+ /// The executor helper contract address.
14
+ pub address: Address,
15
+ /// Allowed function names on the helper (e.g., "execute", "compose", "native_drop_and_execute").
16
+ pub allowed_functions: Vec<Symbol>,
17
+ }
18
+
6
19
  /// Storage keys for the Executor contract.
7
20
  ///
8
21
  /// Manages persistent storage for destination configurations and instance storage
@@ -21,4 +34,11 @@ pub enum ExecutorStorage {
21
34
  /// Used for receive-flow operations to interact with the endpoint.
22
35
  #[instance(Address)]
23
36
  Endpoint,
37
+
38
+ /// Executor helper configuration (address + allowed function names).
39
+ ///
40
+ /// Used by `validate_auth_contexts` to verify that auth contexts
41
+ /// originate from a registered helper contract with permitted functions.
42
+ #[instance(ExecutorHelperConfig)]
43
+ ExecutorHelper,
24
44
  }
@@ -31,7 +31,7 @@ fn check_auth(
31
31
  }
32
32
 
33
33
  #[test]
34
- fn test_check_auth_allows_lz_receive_value_zero_single_context() {
34
+ fn test_check_auth_allows_lz_receive_value_zero() {
35
35
  let env = Env::default();
36
36
  let admin_kp = Ed25519KeyPair::generate();
37
37
  let admin_addr = admin_kp.address(&env);
@@ -40,18 +40,21 @@ fn test_check_auth_allows_lz_receive_value_zero_single_context() {
40
40
  let payload = BytesN::from_array(&setup.env, &[1u8; 32]);
41
41
  let sig = mk_sig(&setup, &admin_kp, &payload);
42
42
 
43
- // Executor auth expects `lz_receive`/`lz_compose` contexts and requires the last arg to be `value: i128`.
44
- // Use value = 0 so only a single context is required.
45
- let args: Vec<Val> = vec![&setup.env, 0i128.into_val(&setup.env)];
43
+ // Helper context + lz_receive with value = 0 2 contexts
46
44
  let oapp = Address::generate(&setup.env);
47
- let auth_contexts: Vec<Context> = vec![&setup.env, contract_ctx(&setup.env, oapp, "lz_receive", args)];
45
+ let args: Vec<Val> = vec![&setup.env, 0i128.into_val(&setup.env)];
46
+ let auth_contexts: Vec<Context> = vec![
47
+ &setup.env,
48
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
49
+ contract_ctx(&setup.env, oapp, "lz_receive", args),
50
+ ];
48
51
 
49
52
  let res = check_auth(&setup, &payload, sig, &auth_contexts);
50
53
  assert!(res.is_ok(), "Expected success, got {:?}", res);
51
54
  }
52
55
 
53
56
  #[test]
54
- fn test_check_auth_allows_lz_compose_value_zero_single_context() {
57
+ fn test_check_auth_allows_lz_compose_value_zero() {
55
58
  let env = Env::default();
56
59
  let admin_kp = Ed25519KeyPair::generate();
57
60
  let admin_addr = admin_kp.address(&env);
@@ -60,9 +63,14 @@ fn test_check_auth_allows_lz_compose_value_zero_single_context() {
60
63
  let payload = BytesN::from_array(&setup.env, &[1u8; 32]);
61
64
  let sig = mk_sig(&setup, &admin_kp, &payload);
62
65
 
63
- let args: Vec<Val> = vec![&setup.env, 0i128.into_val(&setup.env)];
66
+ // Helper context + lz_compose with value = 0 → 2 contexts
64
67
  let composer = Address::generate(&setup.env);
65
- let auth_contexts: Vec<Context> = vec![&setup.env, contract_ctx(&setup.env, composer, "lz_compose", args)];
68
+ let args: Vec<Val> = vec![&setup.env, 0i128.into_val(&setup.env)];
69
+ let auth_contexts: Vec<Context> = vec![
70
+ &setup.env,
71
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "compose", Vec::new(&setup.env)),
72
+ contract_ctx(&setup.env, composer, "lz_compose", args),
73
+ ];
66
74
 
67
75
  let res = check_auth(&setup, &payload, sig, &auth_contexts);
68
76
  assert!(res.is_ok(), "Expected success, got {:?}", res);
@@ -78,6 +86,7 @@ fn test_check_auth_allows_value_transfer_when_value_nonzero() {
78
86
  let payload = BytesN::from_array(&setup.env, &[1u8; 32]);
79
87
  let sig = mk_sig(&setup, &admin_kp, &payload);
80
88
 
89
+ // Helper context + lz_receive with value != 0 + transfer → 3 contexts
81
90
  let value: i128 = 123;
82
91
  let oapp = Address::generate(&setup.env);
83
92
  let exec_args: Vec<Val> = vec![&setup.env, value.into_val(&setup.env)];
@@ -89,6 +98,7 @@ fn test_check_auth_allows_value_transfer_when_value_nonzero() {
89
98
 
90
99
  let auth_contexts: Vec<Context> = vec![
91
100
  &setup.env,
101
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
92
102
  contract_ctx(&setup.env, oapp, "lz_receive", exec_args),
93
103
  contract_ctx(&setup.env, setup.native_token.clone(), "transfer", transfer_args),
94
104
  ];
@@ -134,7 +144,11 @@ fn test_check_auth_rejects_signature_mismatch() {
134
144
 
135
145
  let oapp = Address::generate(&setup.env);
136
146
  let args: Vec<Val> = vec![&setup.env, 0i128.into_val(&setup.env)];
137
- let contexts: Vec<Context> = vec![&setup.env, contract_ctx(&setup.env, oapp, "lz_receive", args)];
147
+ let contexts: Vec<Context> = vec![
148
+ &setup.env,
149
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
150
+ contract_ctx(&setup.env, oapp, "lz_receive", args),
151
+ ];
138
152
 
139
153
  let res = check_auth(&setup, &payload, sig, &contexts);
140
154
  assert!(matches!(res, Err(Err(_))), "Expected host error, got {:?}", res);
@@ -150,7 +164,11 @@ fn test_check_auth_rejects_non_admin() {
150
164
  let payload = BytesN::from_array(&setup.env, &[1u8; 32]);
151
165
  let oapp = Address::generate(&setup.env);
152
166
  let args: Vec<Val> = vec![&setup.env, 0i128.into_val(&setup.env)];
153
- let contexts: Vec<Context> = vec![&setup.env, contract_ctx(&setup.env, oapp, "lz_receive", args)];
167
+ let contexts: Vec<Context> = vec![
168
+ &setup.env,
169
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
170
+ contract_ctx(&setup.env, oapp, "lz_receive", args),
171
+ ];
154
172
 
155
173
  // Unauthorized: signer not in admins list
156
174
  let non_admin_kp = Ed25519KeyPair::generate();
@@ -177,11 +195,12 @@ fn test_check_auth_rejects_empty_or_too_many_contexts() {
177
195
  let empty: Vec<Context> = Vec::new(&setup.env);
178
196
  assert_eq!(check_auth(&setup, &payload, sig.clone(), &empty), Err(Ok(ExecutorError::UnauthorizedContext)));
179
197
 
180
- // Too many contexts (>2)
198
+ // Too many contexts (>3)
181
199
  let oapp = Address::generate(&setup.env);
182
200
  let args0: Vec<Val> = vec![&setup.env, 0i128.into_val(&setup.env)];
183
201
  let too_many: Vec<Context> = vec![
184
202
  &setup.env,
203
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
185
204
  contract_ctx(&setup.env, oapp.clone(), "lz_receive", args0.clone()),
186
205
  contract_ctx(&setup.env, oapp.clone(), "lz_receive", args0.clone()),
187
206
  contract_ctx(&setup.env, oapp, "lz_receive", args0),
@@ -210,7 +229,7 @@ fn test_check_auth_rejects_non_contract_first_context() {
210
229
  }
211
230
 
212
231
  #[test]
213
- fn test_check_auth_rejects_invalid_execute_fn_name() {
232
+ fn test_check_auth_rejects_invalid_helper_fn_name() {
214
233
  let env = Env::default();
215
234
  let admin_kp = Ed25519KeyPair::generate();
216
235
  let admin_addr = admin_kp.address(&env);
@@ -219,10 +238,23 @@ fn test_check_auth_rejects_invalid_execute_fn_name() {
219
238
  let payload = BytesN::from_array(&setup.env, &[1u8; 32]);
220
239
  let sig = mk_sig(&setup, &admin_kp, &payload);
221
240
 
241
+ // Helper context with unregistered function name
222
242
  let oapp = Address::generate(&setup.env);
223
- let bad_fn: Vec<Context> =
224
- vec![&setup.env, contract_ctx(&setup.env, oapp, "not_allowed", vec![&setup.env, 0i128.into_val(&setup.env)])];
225
- assert_eq!(check_auth(&setup, &payload, sig, &bad_fn), Err(Ok(ExecutorError::UnauthorizedContext)));
243
+ let bad_fn: Vec<Context> = vec![
244
+ &setup.env,
245
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "not_allowed", Vec::new(&setup.env)),
246
+ contract_ctx(&setup.env, oapp, "lz_receive", vec![&setup.env, 0i128.into_val(&setup.env)]),
247
+ ];
248
+ assert_eq!(check_auth(&setup, &payload, sig.clone(), &bad_fn), Err(Ok(ExecutorError::UnauthorizedContext)));
249
+
250
+ // Wrong helper address
251
+ let wrong_helper = Address::generate(&setup.env);
252
+ let bad_addr: Vec<Context> = vec![
253
+ &setup.env,
254
+ contract_ctx(&setup.env, wrong_helper, "execute", Vec::new(&setup.env)),
255
+ contract_ctx(&setup.env, Address::generate(&setup.env), "lz_receive", vec![&setup.env, 0i128.into_val(&setup.env)]),
256
+ ];
257
+ assert_eq!(check_auth(&setup, &payload, sig, &bad_addr), Err(Ok(ExecutorError::UnauthorizedContext)));
226
258
  }
227
259
 
228
260
  #[test]
@@ -265,13 +297,17 @@ fn test_check_auth_rejects_execute_missing_or_wrong_value_type() {
265
297
 
266
298
  let oapp = Address::generate(&setup.env);
267
299
 
268
- // Execute path requires last arg to be i128 value
269
- let missing_value: Vec<Context> =
270
- vec![&setup.env, contract_ctx(&setup.env, oapp.clone(), "lz_receive", Vec::new(&setup.env))];
300
+ // lz_receive requires last arg to be i128 value
301
+ let missing_value: Vec<Context> = vec![
302
+ &setup.env,
303
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
304
+ contract_ctx(&setup.env, oapp.clone(), "lz_receive", Vec::new(&setup.env)),
305
+ ];
271
306
  assert_eq!(check_auth(&setup, &payload, sig.clone(), &missing_value), Err(Ok(ExecutorError::UnauthorizedContext)));
272
307
 
273
308
  let wrong_value_type: Vec<Context> = vec![
274
309
  &setup.env,
310
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
275
311
  contract_ctx(
276
312
  &setup.env,
277
313
  oapp,
@@ -295,6 +331,7 @@ fn test_check_auth_rejects_value_zero_with_extra_context() {
295
331
  let oapp = Address::generate(&setup.env);
296
332
  let value_zero_with_transfer: Vec<Context> = vec![
297
333
  &setup.env,
334
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
298
335
  contract_ctx(&setup.env, oapp, "lz_receive", vec![&setup.env, 0i128.into_val(&setup.env)]),
299
336
  contract_ctx(
300
337
  &setup.env,
@@ -325,8 +362,11 @@ fn test_check_auth_rejects_value_nonzero_missing_transfer_context() {
325
362
  let sig = mk_sig(&setup, &admin_kp, &payload);
326
363
 
327
364
  let oapp = Address::generate(&setup.env);
328
- let value_nonzero_missing_transfer: Vec<Context> =
329
- vec![&setup.env, contract_ctx(&setup.env, oapp, "lz_receive", vec![&setup.env, 1i128.into_val(&setup.env)])];
365
+ let value_nonzero_missing_transfer: Vec<Context> = vec![
366
+ &setup.env,
367
+ contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env)),
368
+ contract_ctx(&setup.env, oapp, "lz_receive", vec![&setup.env, 1i128.into_val(&setup.env)]),
369
+ ];
330
370
  assert_eq!(
331
371
  check_auth(&setup, &payload, sig, &value_nonzero_missing_transfer),
332
372
  Err(Ok(ExecutorError::UnauthorizedContext))
@@ -343,6 +383,7 @@ fn test_check_auth_rejects_invalid_transfer_context() {
343
383
  let payload = BytesN::from_array(&setup.env, &[1u8; 32]);
344
384
  let sig = mk_sig(&setup, &admin_kp, &payload);
345
385
 
386
+ let helper_ctx: Context = contract_ctx(&setup.env, setup.executor_helper.clone(), "execute", Vec::new(&setup.env));
346
387
  let oapp = Address::generate(&setup.env);
347
388
  let base_exec: Context = contract_ctx(&setup.env, oapp, "lz_receive", vec![&setup.env, 5i128.into_val(&setup.env)]);
348
389
  let transfer_args: Vec<Val> = vec![
@@ -355,6 +396,7 @@ fn test_check_auth_rejects_invalid_transfer_context() {
355
396
  // Wrong transfer fn name
356
397
  let wrong_transfer_fn: Vec<Context> = vec![
357
398
  &setup.env,
399
+ helper_ctx.clone(),
358
400
  base_exec.clone(),
359
401
  contract_ctx(&setup.env, setup.native_token.clone(), "not_transfer", transfer_args.clone()),
360
402
  ];
@@ -366,6 +408,7 @@ fn test_check_auth_rejects_invalid_transfer_context() {
366
408
  // Wrong transfer contract
367
409
  let wrong_transfer_contract: Vec<Context> = vec![
368
410
  &setup.env,
411
+ helper_ctx.clone(),
369
412
  base_exec.clone(),
370
413
  contract_ctx(&setup.env, Address::generate(&setup.env), "transfer", transfer_args.clone()),
371
414
  ];
@@ -377,6 +420,7 @@ fn test_check_auth_rejects_invalid_transfer_context() {
377
420
  // Wrong transfer amount (must match value)
378
421
  let wrong_transfer_amount: Vec<Context> = vec![
379
422
  &setup.env,
423
+ helper_ctx,
380
424
  base_exec,
381
425
  contract_ctx(
382
426
  &setup.env,
@@ -4,7 +4,7 @@ use crate::events::{DstConfigSet, NativeDropApplied};
4
4
  use endpoint_v2::{FeeRecipient, Origin};
5
5
  use soroban_sdk::{testutils::Address as _, vec, Address, Bytes, BytesN, IntoVal};
6
6
  use utils::testing_utils::assert_contains_event;
7
- use worker::errors::WorkerError;
7
+ use worker::WorkerError;
8
8
 
9
9
  // =============================================================================
10
10
  // Construction
@@ -8,6 +8,7 @@ use soroban_sdk::{
8
8
  vec, Address, BytesN, Env, IntoVal, Symbol, Val, Vec,
9
9
  };
10
10
 
11
+ use crate::storage::ExecutorHelperConfig;
11
12
  use crate::{DstConfig, LzExecutor, LzExecutorClient, NativeDropParams, SetDstConfigParam};
12
13
  use fee_lib_interfaces::FeeParams;
13
14
 
@@ -108,6 +109,7 @@ pub struct TestSetup<'a> {
108
109
  pub owner: Address,
109
110
  pub admins: Vec<Address>,
110
111
  pub endpoint: Address,
112
+ pub executor_helper: Address,
111
113
  pub send_lib: Address,
112
114
  pub price_feed: Address,
113
115
  pub worker_fee_lib: Address,
@@ -163,6 +165,21 @@ impl<'a> TestSetup<'a> {
163
165
  );
164
166
  let client = LzExecutorClient::new(&env, &contract_id);
165
167
 
168
+ // Register executor helper config directly in storage
169
+ let executor_helper = Address::generate(&env);
170
+ let allowed_functions: Vec<Symbol> = vec![
171
+ &env,
172
+ Symbol::new(&env, "execute"),
173
+ Symbol::new(&env, "compose"),
174
+ Symbol::new(&env, "native_drop_and_execute"),
175
+ ];
176
+ env.as_contract(&contract_id, || {
177
+ crate::storage::ExecutorStorage::set_executor_helper(
178
+ &env,
179
+ &ExecutorHelperConfig { address: executor_helper.clone(), allowed_functions },
180
+ );
181
+ });
182
+
166
183
  Self {
167
184
  env,
168
185
  contract_id,
@@ -170,6 +187,7 @@ impl<'a> TestSetup<'a> {
170
187
  owner,
171
188
  admins,
172
189
  endpoint,
190
+ executor_helper,
173
191
  send_lib,
174
192
  price_feed,
175
193
  worker_fee_lib,
@@ -1,8 +1,11 @@
1
1
  #![no_std]
2
2
 
3
- pub mod errors;
4
3
  pub mod executor_option;
5
4
 
5
+ mod errors;
6
+
7
+ pub use errors::*;
8
+
6
9
  cfg_if::cfg_if! {
7
10
  // Include implementation when NOT in library mode, OR when testutils is enabled (for tests)
8
11
  if #[cfg(any(not(feature = "library"), feature = "testutils"))] {
@@ -42,17 +42,11 @@ pub struct ExecutorHelper;
42
42
  impl ExecutorHelper {
43
43
  /// Executes `lz_receive` on the target OApp
44
44
  pub fn execute(env: &Env, executor: &Address, params: &ExecutionParams, value_payer: &Address) {
45
+ executor.require_auth();
45
46
  if params.value != 0 {
46
- transfer_value(env, value_payer, executor, params.value);
47
+ value_payer.require_auth();
47
48
  }
48
- LayerZeroReceiverClient::new(env, &params.receiver).lz_receive(
49
- executor,
50
- &params.origin,
51
- &params.guid,
52
- &params.message,
53
- &params.extra_data,
54
- &params.value,
55
- );
49
+ Self::execute_internal(env, executor, params, value_payer);
56
50
  }
57
51
 
58
52
  /// Records a failed `lz_receive` execution for off-chain processing.
@@ -73,7 +67,9 @@ impl ExecutorHelper {
73
67
 
74
68
  /// Executes `lz_compose` on the target composer
75
69
  pub fn compose(env: &Env, executor: &Address, params: &ComposeParams, value_payer: &Address) {
70
+ executor.require_auth();
76
71
  if params.value != 0 {
72
+ value_payer.require_auth();
77
73
  transfer_value(env, value_payer, executor, params.value);
78
74
  }
79
75
  LayerZeroComposerClient::new(env, &params.to).lz_compose(
@@ -128,8 +124,26 @@ impl ExecutorHelper {
128
124
  native_drop_params: &Vec<NativeDropParams>,
129
125
  execute_params: &ExecutionParams,
130
126
  ) {
127
+ executor.require_auth();
128
+ admin.require_auth();
131
129
  Self::native_drop(env, executor, admin, origin, dst_eid, oapp, native_drop_params);
132
- Self::execute(env, executor, execute_params, admin);
130
+ Self::execute_internal(env, executor, execute_params, admin);
131
+ }
132
+
133
+ /// Internal execute logic without auth checks.
134
+ /// Callers (`execute` / `native_drop_and_execute`) are responsible for authorization.
135
+ fn execute_internal(env: &Env, executor: &Address, params: &ExecutionParams, value_payer: &Address) {
136
+ if params.value != 0 {
137
+ transfer_value(env, value_payer, executor, params.value);
138
+ }
139
+ LayerZeroReceiverClient::new(env, &params.receiver).lz_receive(
140
+ executor,
141
+ &params.origin,
142
+ &params.guid,
143
+ &params.message,
144
+ &params.extra_data,
145
+ &params.value,
146
+ );
133
147
  }
134
148
  }
135
149