@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
@@ -0,0 +1,410 @@
1
+ use super::setup::TestSetup;
2
+ use crate::errors::ExecutorError;
3
+ use crate::events::{DstConfigSet, NativeDropApplied};
4
+ use endpoint_v2::{FeeRecipient, Origin};
5
+ use soroban_sdk::{testutils::Address as _, vec, Address, Bytes, BytesN, IntoVal};
6
+ use utils::testing_utils::assert_contains_event;
7
+ use worker::errors::WorkerError;
8
+
9
+ // =============================================================================
10
+ // Construction
11
+ // =============================================================================
12
+
13
+ #[test]
14
+ fn test_constructor_sets_endpoint_and_worker_config() {
15
+ let setup = TestSetup::new();
16
+
17
+ assert_eq!(setup.client.owner(), Some(setup.owner.clone()));
18
+ assert_eq!(setup.client.admins(), setup.admins);
19
+ assert_eq!(setup.client.is_admin(&setup.admins.get(0).unwrap()), true);
20
+ assert_eq!(setup.client.is_supported_message_lib(&setup.send_lib), true);
21
+ assert_eq!(setup.client.message_libs(), vec![&setup.env, setup.send_lib.clone()]);
22
+
23
+ assert_eq!(setup.client.endpoint(), setup.endpoint);
24
+ assert_eq!(setup.client.deposit_address(), Some(setup.deposit_address.clone()));
25
+ assert_eq!(setup.client.price_feed(), Some(setup.price_feed.clone()));
26
+ assert_eq!(setup.client.worker_fee_lib(), Some(setup.worker_fee_lib.clone()));
27
+ assert_eq!(setup.client.default_multiplier_bps(), setup.default_multiplier_bps);
28
+ assert_eq!(setup.client.paused(), false);
29
+ }
30
+
31
+ // =============================================================================
32
+ // Admin functions (withdraw + admin management)
33
+ // =============================================================================
34
+
35
+ #[test]
36
+ fn test_withdraw_token_transfers_from_contract() {
37
+ let setup = TestSetup::new();
38
+ let admin = setup.admins.get(0).unwrap();
39
+ let to = Address::generate(&setup.env);
40
+
41
+ // Mint native token to the executor contract so it can withdraw it.
42
+ setup.mint_native(&setup.contract_id, 100);
43
+ let before_contract = setup.balance_native(&setup.contract_id);
44
+ let before_to = setup.balance_native(&to);
45
+
46
+ setup.mock_auth(&admin, "withdraw_token", (&admin, &setup.native_token, &to, 40i128));
47
+ setup.client.withdraw_token(&admin, &setup.native_token, &to, &40);
48
+
49
+ assert_eq!(setup.balance_native(&setup.contract_id), before_contract - 40);
50
+ assert_eq!(setup.balance_native(&to), before_to + 40);
51
+ }
52
+
53
+ #[test]
54
+ fn test_withdraw_token_requires_admin() {
55
+ let setup = TestSetup::new();
56
+
57
+ let non_admin = Address::generate(&setup.env);
58
+ let to = Address::generate(&setup.env);
59
+ setup.mock_auth(&non_admin, "withdraw_token", (&non_admin, &setup.native_token, &to, 1i128));
60
+ assert_eq!(
61
+ setup.client.try_withdraw_token(&non_admin, &setup.native_token, &to, &1).unwrap_err().unwrap(),
62
+ WorkerError::Unauthorized.into()
63
+ );
64
+ }
65
+
66
+ #[test]
67
+ fn test_set_admin_add_and_remove() {
68
+ let setup = TestSetup::new();
69
+ let new_admin = Address::generate(&setup.env);
70
+
71
+ // Add new admin
72
+ setup.mock_owner_auth("set_admin", (&new_admin, true));
73
+ setup.client.set_admin(&new_admin, &true);
74
+ assert_eq!(setup.client.is_admin(&new_admin), true);
75
+
76
+ // Remove that admin
77
+ setup.mock_owner_auth("set_admin", (&new_admin, false));
78
+ setup.client.set_admin(&new_admin, &false);
79
+ assert_eq!(setup.client.is_admin(&new_admin), false);
80
+ }
81
+
82
+ #[test]
83
+ #[should_panic(expected = "Error(Auth, InvalidAction)")]
84
+ fn test_set_admin_requires_owner_auth() {
85
+ let setup = TestSetup::new();
86
+ let new_admin = Address::generate(&setup.env);
87
+
88
+ // No mock_auths -> owner.require_auth() must fail.
89
+ setup.client.set_admin(&new_admin, &true);
90
+ }
91
+
92
+ // =============================================================================
93
+ // IExecutor methods
94
+ // =============================================================================
95
+
96
+ #[test]
97
+ fn test_set_dst_config_and_dst_config_view() {
98
+ let setup = TestSetup::new();
99
+ let admin = setup.admins.get(0).unwrap();
100
+
101
+ let cfg_a = setup.new_dst_config(0);
102
+ let cfg_b = setup.new_dst_config(12_000);
103
+ let params = vec![
104
+ &setup.env,
105
+ crate::SetDstConfigParam { dst_eid: 1, dst_config: cfg_a.clone() },
106
+ crate::SetDstConfigParam { dst_eid: 2, dst_config: cfg_b.clone() },
107
+ ];
108
+
109
+ setup.mock_auth(&admin, "set_dst_config", (&admin, &params));
110
+ setup.client.set_dst_config(&admin, &params);
111
+
112
+ assert_contains_event(&setup.env, &setup.contract_id, DstConfigSet { params: params.clone() });
113
+ assert_eq!(setup.client.dst_config(&1), Some(cfg_a));
114
+ assert_eq!(setup.client.dst_config(&2), Some(cfg_b));
115
+ assert_eq!(setup.client.dst_config(&999), None);
116
+ }
117
+
118
+ #[test]
119
+ fn test_set_dst_config_requires_admin() {
120
+ let setup = TestSetup::new();
121
+ let non_admin = Address::generate(&setup.env);
122
+ let cfg = setup.new_dst_config(0);
123
+ let params = vec![&setup.env, crate::SetDstConfigParam { dst_eid: 1, dst_config: cfg }];
124
+
125
+ setup.mock_auth(&non_admin, "set_dst_config", (&non_admin, &params));
126
+ assert_eq!(
127
+ setup.client.try_set_dst_config(&non_admin, &params).unwrap_err().unwrap(),
128
+ WorkerError::Unauthorized.into()
129
+ );
130
+ }
131
+
132
+ #[test]
133
+ fn test_native_drop_emits_success_vector_and_requires_admin() {
134
+ let setup = TestSetup::new();
135
+ let admin = setup.admins.get(0).unwrap();
136
+
137
+ // Fund admin so at least one transfer can succeed.
138
+ setup.mint_native(&admin, 100);
139
+ let admin_before = setup.balance_native(&admin);
140
+
141
+ let receiver_a = Address::generate(&setup.env);
142
+ let receiver_b = Address::generate(&setup.env);
143
+ let receivers = vec![&setup.env, receiver_a.clone(), receiver_b.clone()];
144
+ let amounts = vec![&setup.env, 10i128, 20i128];
145
+ let params = setup.native_drop_params(&receivers, &amounts);
146
+ let receiver_a_before = setup.balance_native(&receiver_a);
147
+ let receiver_b_before = setup.balance_native(&receiver_b);
148
+ let origin = Origin { src_eid: 1, sender: BytesN::from_array(&setup.env, &[7u8; 32]), nonce: 1 };
149
+ let oapp = Address::generate(&setup.env);
150
+
151
+ // Authorize admin calling native_drop, and authorize ONLY the first token transfer sub-invoke.
152
+ setup.env.mock_auths(&[soroban_sdk::testutils::MockAuth {
153
+ address: &admin,
154
+ invoke: &soroban_sdk::testutils::MockAuthInvoke {
155
+ contract: &setup.contract_id,
156
+ fn_name: "native_drop",
157
+ args: (&admin, &origin, 2u32, &oapp, &params).into_val(&setup.env),
158
+ sub_invokes: &[
159
+ soroban_sdk::testutils::MockAuthInvoke {
160
+ contract: &setup.native_token,
161
+ fn_name: "transfer",
162
+ args: (&admin, &receiver_a, &10i128).into_val(&setup.env),
163
+ sub_invokes: &[],
164
+ },
165
+ // receiver_b transfer intentionally NOT authorized -> should fail
166
+ ],
167
+ },
168
+ }]);
169
+
170
+ setup.client.native_drop(&admin, &origin, &2, &oapp, &params);
171
+
172
+ // Validate event: first succeeds, second fails.
173
+ assert_contains_event(
174
+ &setup.env,
175
+ &setup.contract_id,
176
+ NativeDropApplied {
177
+ origin: origin.clone(),
178
+ dst_eid: 2,
179
+ oapp: oapp.clone(),
180
+ native_drop_params: params.clone(),
181
+ success: vec![&setup.env, true, false],
182
+ },
183
+ );
184
+
185
+ // Balance assertions:
186
+ // - only the authorized transfer (receiver_a) should have moved funds
187
+ assert_eq!(setup.balance_native(&receiver_a), receiver_a_before + 10);
188
+ assert_eq!(setup.balance_native(&receiver_b), receiver_b_before);
189
+ assert_eq!(setup.balance_native(&admin), admin_before - 10);
190
+ }
191
+
192
+ #[test]
193
+ fn test_native_drop_requires_admin() {
194
+ let setup = TestSetup::new();
195
+ let origin = Origin { src_eid: 1, sender: BytesN::from_array(&setup.env, &[7u8; 32]), nonce: 1 };
196
+ let oapp = Address::generate(&setup.env);
197
+ let receiver_a = Address::generate(&setup.env);
198
+ let receiver_b = Address::generate(&setup.env);
199
+ let receivers = vec![&setup.env, receiver_a.clone(), receiver_b.clone()];
200
+ let amounts = vec![&setup.env, 10i128, 20i128];
201
+ let params = setup.native_drop_params(&receivers, &amounts);
202
+
203
+ // Requires admin membership (auth provided but non-admin)
204
+ let non_admin = Address::generate(&setup.env);
205
+ setup.mock_auth(&non_admin, "native_drop", (&non_admin, &origin, 2u32, &oapp, &params));
206
+ assert_eq!(
207
+ setup.client.try_native_drop(&non_admin, &origin, &2, &oapp, &params).unwrap_err().unwrap(),
208
+ WorkerError::Unauthorized.into()
209
+ );
210
+ }
211
+
212
+ // =============================================================================
213
+ // Send-flow methods
214
+ // =============================================================================
215
+
216
+ #[test]
217
+ fn test_assign_job_success_returns_fee_recipient() {
218
+ let setup = TestSetup::new();
219
+ let admin = setup.admins.get(0).unwrap();
220
+
221
+ // Need dst config for fee calc.
222
+ let cfg = setup.new_dst_config(12_000);
223
+ setup.set_dst_config_one(&admin, 1, &cfg);
224
+
225
+ let sender = Address::generate(&setup.env);
226
+ let options = Bytes::from_slice(&setup.env, &[9, 9]);
227
+
228
+ // Success: send_lib must auth.
229
+ setup.env.mock_auths(&[soroban_sdk::testutils::MockAuth {
230
+ address: &setup.send_lib,
231
+ invoke: &soroban_sdk::testutils::MockAuthInvoke {
232
+ contract: &setup.contract_id,
233
+ fn_name: "assign_job",
234
+ args: (&setup.send_lib, &sender, 1u32, 7u32, &options).into_val(&setup.env),
235
+ sub_invokes: &[],
236
+ },
237
+ }]);
238
+ let fr: FeeRecipient = setup.client.assign_job(&setup.send_lib, &sender, &1, &7, &options);
239
+ assert_eq!(fr.to, setup.deposit_address);
240
+ let expected_fee = 7i128
241
+ + cfg.lz_receive_base_gas as i128
242
+ + cfg.lz_compose_base_gas as i128
243
+ + cfg.multiplier_bps as i128
244
+ + (cfg.floor_margin_usd % 10_000) as i128
245
+ + (cfg.native_cap % 10_000) as i128;
246
+ assert_eq!(fr.amount, expected_fee);
247
+ }
248
+
249
+ #[test]
250
+ fn test_assign_job_rejects_unsupported_message_lib() {
251
+ let setup = TestSetup::new();
252
+ let admin = setup.admins.get(0).unwrap();
253
+
254
+ // Need dst config for fee calc.
255
+ let cfg = setup.new_dst_config(12_000);
256
+ setup.set_dst_config_one(&admin, 1, &cfg);
257
+
258
+ let sender = Address::generate(&setup.env);
259
+ let options = Bytes::from_slice(&setup.env, &[9, 9]);
260
+
261
+ // Error: unsupported message lib (still must auth)
262
+ let unsupported = Address::generate(&setup.env);
263
+ setup.env.mock_auths(&[soroban_sdk::testutils::MockAuth {
264
+ address: &unsupported,
265
+ invoke: &soroban_sdk::testutils::MockAuthInvoke {
266
+ contract: &setup.contract_id,
267
+ fn_name: "assign_job",
268
+ args: (&unsupported, &sender, 1u32, 7u32, &options).into_val(&setup.env),
269
+ sub_invokes: &[],
270
+ },
271
+ }]);
272
+ assert_eq!(
273
+ setup.client.try_assign_job(&unsupported, &sender, &1, &7, &options).unwrap_err().unwrap(),
274
+ WorkerError::UnsupportedMessageLib.into()
275
+ );
276
+ }
277
+
278
+ #[test]
279
+ #[should_panic(expected = "Error(Auth, InvalidAction)")]
280
+ fn test_assign_job_requires_send_lib_auth() {
281
+ let setup = TestSetup::new();
282
+ let sender = Address::generate(&setup.env);
283
+ let options = Bytes::from_slice(&setup.env, &[9, 9]);
284
+
285
+ // No mock_auths for send_lib.require_auth()
286
+ let _ = setup.client.assign_job(&setup.send_lib, &sender, &1, &7, &options);
287
+ }
288
+
289
+ #[test]
290
+ fn test_get_fee_success_includes_default_multiplier_when_dst_multiplier_is_zero() {
291
+ let setup = TestSetup::new();
292
+ let admin = setup.admins.get(0).unwrap();
293
+
294
+ // Configure dst eid 1 so get_fee succeeds.
295
+ let cfg = setup.new_dst_config(0);
296
+ setup.set_dst_config_one(&admin, 1, &cfg);
297
+
298
+ let sender = Address::generate(&setup.env);
299
+ let options = Bytes::from_slice(&setup.env, &[1, 2, 3]);
300
+
301
+ // Success (multiplier_bps is 0 so fee uses default_multiplier_bps too).
302
+ let fee = setup.client.get_fee(&setup.send_lib, &sender, &1, &7, &options);
303
+ let expected_fee = 7i128
304
+ + cfg.lz_receive_base_gas as i128
305
+ + cfg.lz_compose_base_gas as i128
306
+ + cfg.multiplier_bps as i128
307
+ + (cfg.floor_margin_usd % 10_000) as i128
308
+ + (cfg.native_cap % 10_000) as i128
309
+ + setup.default_multiplier_bps as i128;
310
+ assert_eq!(fee, expected_fee);
311
+ }
312
+
313
+ #[test]
314
+ fn test_get_fee_success_when_multiplier_is_nonzero() {
315
+ let setup = TestSetup::new();
316
+ let admin = setup.admins.get(0).unwrap();
317
+
318
+ let cfg = setup.new_dst_config(12_000);
319
+ setup.set_dst_config_one(&admin, 1, &cfg);
320
+
321
+ let sender = Address::generate(&setup.env);
322
+ let options = Bytes::from_slice(&setup.env, &[1, 2, 3]);
323
+
324
+ let fee = setup.client.get_fee(&setup.send_lib, &sender, &1, &7, &options);
325
+ let expected_fee = 7i128
326
+ + cfg.lz_receive_base_gas as i128
327
+ + cfg.lz_compose_base_gas as i128
328
+ + cfg.multiplier_bps as i128
329
+ + (cfg.floor_margin_usd % 10_000) as i128
330
+ + (cfg.native_cap % 10_000) as i128;
331
+ assert_eq!(fee, expected_fee);
332
+ }
333
+
334
+ #[test]
335
+ fn test_get_fee_rejects_when_paused() {
336
+ let setup = TestSetup::new();
337
+ let admin = setup.admins.get(0).unwrap();
338
+ let cfg = setup.new_dst_config(0);
339
+ setup.set_dst_config_one(&admin, 1, &cfg);
340
+
341
+ let sender = Address::generate(&setup.env);
342
+ let options = Bytes::from_slice(&setup.env, &[1, 2, 3]);
343
+
344
+ setup.mock_owner_auth("set_paused", (true,));
345
+ setup.client.set_paused(&true);
346
+ assert_eq!(
347
+ setup.client.try_get_fee(&setup.send_lib, &sender, &1, &7, &options).unwrap_err().unwrap(),
348
+ WorkerError::WorkerIsPaused.into()
349
+ );
350
+ }
351
+
352
+ #[test]
353
+ fn test_get_fee_rejects_when_sender_not_allowed() {
354
+ let setup = TestSetup::new();
355
+ let admin = setup.admins.get(0).unwrap();
356
+ let cfg = setup.new_dst_config(0);
357
+ setup.set_dst_config_one(&admin, 1, &cfg);
358
+
359
+ let sender = Address::generate(&setup.env);
360
+ let options = Bytes::from_slice(&setup.env, &[1, 2, 3]);
361
+
362
+ let allowlisted = Address::generate(&setup.env);
363
+ setup.mock_owner_auth("set_allowlist", (&allowlisted, true));
364
+ setup.client.set_allowlist(&allowlisted, &true);
365
+ assert_eq!(
366
+ setup.client.try_get_fee(&setup.send_lib, &sender, &1, &7, &options).unwrap_err().unwrap(),
367
+ WorkerError::NotAllowed.into()
368
+ );
369
+ }
370
+
371
+ #[test]
372
+ fn test_get_fee_rejects_when_sender_is_denylisted_even_if_allowlisted() {
373
+ let setup = TestSetup::new();
374
+ let admin = setup.admins.get(0).unwrap();
375
+ let cfg = setup.new_dst_config(0);
376
+ setup.set_dst_config_one(&admin, 1, &cfg);
377
+
378
+ let sender = Address::generate(&setup.env);
379
+ let options = Bytes::from_slice(&setup.env, &[1, 2, 3]);
380
+
381
+ // Put sender on allowlist, then also on denylist. Denylist should take precedence.
382
+ setup.mock_owner_auth("set_allowlist", (&sender, true));
383
+ setup.client.set_allowlist(&sender, &true);
384
+
385
+ setup.mock_owner_auth("set_denylist", (&sender, true));
386
+ setup.client.set_denylist(&sender, &true);
387
+
388
+ assert_eq!(
389
+ setup.client.try_get_fee(&setup.send_lib, &sender, &1, &7, &options).unwrap_err().unwrap(),
390
+ WorkerError::NotAllowed.into()
391
+ );
392
+ }
393
+
394
+ #[test]
395
+ fn test_get_fee_rejects_unsupported_eid() {
396
+ let setup = TestSetup::new();
397
+ let admin = setup.admins.get(0).unwrap();
398
+ let cfg = setup.new_dst_config(0);
399
+ setup.set_dst_config_one(&admin, 1, &cfg);
400
+
401
+ let sender = Address::generate(&setup.env);
402
+ let options = Bytes::from_slice(&setup.env, &[1, 2, 3]);
403
+
404
+ setup.mock_owner_auth("set_allowlist", (&sender, true));
405
+ setup.client.set_allowlist(&sender, &true);
406
+ assert_eq!(
407
+ setup.client.try_get_fee(&setup.send_lib, &sender, &999, &7, &options).unwrap_err().unwrap(),
408
+ ExecutorError::EidNotSupported.into()
409
+ );
410
+ }
@@ -0,0 +1,3 @@
1
+ mod auth;
2
+ mod executor;
3
+ mod setup;
@@ -0,0 +1,250 @@
1
+ use ed25519_dalek::{Signer, SigningKey};
2
+ use rand::thread_rng;
3
+ use soroban_sdk::address_payload::AddressPayload;
4
+ use soroban_sdk::testutils::Address as _;
5
+ use soroban_sdk::{
6
+ testutils::{MockAuth, MockAuthInvoke},
7
+ token::{StellarAssetClient, TokenClient},
8
+ vec, Address, BytesN, Env, IntoVal, Symbol, Val, Vec,
9
+ };
10
+
11
+ use crate::{DstConfig, LzExecutor, LzExecutorClient, NativeDropParams, SetDstConfigParam};
12
+ use fee_lib_interfaces::FeeParams;
13
+
14
+ // =============================================================================
15
+ // Ed25519 Key Pair (for auth tests that need to sign payloads)
16
+ // =============================================================================
17
+
18
+ pub struct Ed25519KeyPair {
19
+ signing_key: SigningKey,
20
+ }
21
+
22
+ impl Ed25519KeyPair {
23
+ pub fn generate() -> Self {
24
+ Self { signing_key: SigningKey::generate(&mut thread_rng()) }
25
+ }
26
+
27
+ pub fn public_key_bytes(&self) -> [u8; 32] {
28
+ self.signing_key.verifying_key().to_bytes()
29
+ }
30
+
31
+ pub fn public_key(&self, env: &Env) -> BytesN<32> {
32
+ BytesN::from_array(env, &self.public_key_bytes())
33
+ }
34
+
35
+ pub fn address(&self, env: &Env) -> Address {
36
+ Address::from_payload(env, AddressPayload::AccountIdPublicKeyEd25519(self.public_key(env)))
37
+ }
38
+
39
+ pub fn sign_payload(&self, env: &Env, payload: &BytesN<32>) -> BytesN<64> {
40
+ let sig = self.signing_key.sign(&payload.to_array());
41
+ BytesN::from_array(env, &sig.to_bytes())
42
+ }
43
+ }
44
+
45
+ // =============================================================================
46
+ // Mock Endpoint (only what executor needs: `native_token()`)
47
+ // =============================================================================
48
+
49
+ use soroban_sdk::{contract, contractimpl};
50
+
51
+ #[contract]
52
+ pub struct MockEndpoint;
53
+
54
+ #[contractimpl]
55
+ impl MockEndpoint {
56
+ pub fn __constructor(env: &Env, native_token: &Address) {
57
+ env.storage().instance().set(&Symbol::new(env, "native_token"), native_token);
58
+ }
59
+
60
+ pub fn native_token(env: &Env) -> Address {
61
+ env.storage().instance().get(&Symbol::new(env, "native_token")).unwrap()
62
+ }
63
+ }
64
+
65
+ // =============================================================================
66
+ // Mock Fee Lib
67
+ // =============================================================================
68
+
69
+ #[contract]
70
+ pub struct MockFeeLib;
71
+
72
+ #[contractimpl]
73
+ impl MockFeeLib {
74
+ pub fn get_fee(_env: &Env, _executor: &Address, params: &FeeParams) -> i128 {
75
+ // Deterministic computation so tests can validate the Executor wires FeeParams correctly.
76
+ // Keep it simple and overflow-safe for test ranges we use.
77
+ let mut fee = params.calldata_size as i128;
78
+ fee += params.lz_receive_base_gas as i128;
79
+ fee += params.lz_compose_base_gas as i128;
80
+ fee += params.multiplier_bps as i128;
81
+ fee += (params.floor_margin_usd % 10_000) as i128;
82
+ fee += (params.native_cap % 10_000) as i128;
83
+ // Use default_multiplier_bps only when dst multiplier is 0 to mimic the intended semantics.
84
+ if params.multiplier_bps == 0 {
85
+ fee += params.default_multiplier_bps as i128;
86
+ }
87
+ // Touch these fields so accidental zeroing/regression becomes observable in tests.
88
+ let _ = &params.sender;
89
+ let _ = params.dst_eid;
90
+ let _ = &params.options;
91
+ let _ = &params.price_feed;
92
+ fee
93
+ }
94
+
95
+ pub fn version(_env: &Env) -> (u64, u32) {
96
+ (1, 0)
97
+ }
98
+ }
99
+
100
+ // =============================================================================
101
+ // Test Setup
102
+ // =============================================================================
103
+
104
+ pub struct TestSetup<'a> {
105
+ pub env: Env,
106
+ pub contract_id: Address,
107
+ pub client: LzExecutorClient<'a>,
108
+ pub owner: Address,
109
+ pub admins: Vec<Address>,
110
+ pub endpoint: Address,
111
+ pub send_lib: Address,
112
+ pub price_feed: Address,
113
+ pub worker_fee_lib: Address,
114
+ pub deposit_address: Address,
115
+ pub default_multiplier_bps: u32,
116
+ pub native_token: Address,
117
+ pub native_token_admin: Address,
118
+ pub native_token_admin_client: StellarAssetClient<'a>,
119
+ }
120
+
121
+ impl<'a> TestSetup<'a> {
122
+ pub fn new() -> Self {
123
+ let env = Env::default();
124
+ let admin = Address::generate(&env);
125
+ Self::new_with_env_and_admin(env, &admin)
126
+ }
127
+
128
+ // For auth tests that need a specific admin address (derived from Ed25519KeyPair)
129
+ pub fn new_with_env_and_admin(env: Env, admin: &Address) -> Self {
130
+ let owner = Address::generate(&env);
131
+ let admins: Vec<Address> = vec![&env, admin.clone()];
132
+
133
+ let send_lib = Address::generate(&env);
134
+ let message_libs: Vec<Address> = vec![&env, send_lib.clone()];
135
+
136
+ let price_feed = Address::generate(&env);
137
+ let default_multiplier_bps = 10_000u32;
138
+ let deposit_address = Address::generate(&env);
139
+
140
+ // Mock endpoint + native token
141
+ let native_token_admin = Address::generate(&env);
142
+ let native_token_sac = env.register_stellar_asset_contract_v2(native_token_admin.clone());
143
+ let native_token = native_token_sac.address();
144
+ let native_token_admin_client = StellarAssetClient::new(&env, &native_token);
145
+
146
+ let endpoint = env.register(MockEndpoint, (&native_token,));
147
+
148
+ // Mock fee lib
149
+ let worker_fee_lib = env.register(MockFeeLib, ());
150
+
151
+ let contract_id = env.register(
152
+ LzExecutor,
153
+ (
154
+ &endpoint,
155
+ &owner,
156
+ &admins,
157
+ &message_libs,
158
+ &price_feed,
159
+ &default_multiplier_bps,
160
+ &worker_fee_lib,
161
+ &deposit_address,
162
+ ),
163
+ );
164
+ let client = LzExecutorClient::new(&env, &contract_id);
165
+
166
+ Self {
167
+ env,
168
+ contract_id,
169
+ client,
170
+ owner,
171
+ admins,
172
+ endpoint,
173
+ send_lib,
174
+ price_feed,
175
+ worker_fee_lib,
176
+ deposit_address,
177
+ default_multiplier_bps,
178
+ native_token,
179
+ native_token_admin,
180
+ native_token_admin_client,
181
+ }
182
+ }
183
+
184
+ pub fn mock_auth<T: IntoVal<Env, Vec<Val>>>(&self, address: &Address, fn_name: &str, args: T) {
185
+ self.env.mock_auths(&[MockAuth {
186
+ address,
187
+ invoke: &MockAuthInvoke {
188
+ contract: &self.contract_id,
189
+ fn_name,
190
+ args: args.into_val(&self.env),
191
+ sub_invokes: &[],
192
+ },
193
+ }]);
194
+ }
195
+
196
+ pub fn mock_owner_auth<T: IntoVal<Env, Vec<Val>>>(&self, fn_name: &str, args: T) {
197
+ self.mock_auth(&self.owner, fn_name, args)
198
+ }
199
+
200
+ pub fn mint_native(&self, to: &Address, amount: i128) {
201
+ self.env.mock_auths(&[MockAuth {
202
+ address: &self.native_token_admin,
203
+ invoke: &MockAuthInvoke {
204
+ contract: &self.native_token,
205
+ fn_name: "mint",
206
+ args: (to, amount).into_val(&self.env),
207
+ sub_invokes: &[],
208
+ },
209
+ }]);
210
+ self.native_token_admin_client.mint(to, &amount);
211
+ }
212
+
213
+ pub fn set_dst_config_one(&self, admin: &Address, dst_eid: u32, dst_config: &DstConfig) {
214
+ let params: Vec<SetDstConfigParam> =
215
+ vec![&self.env, SetDstConfigParam { dst_eid, dst_config: dst_config.clone() }];
216
+ self.mock_auth(admin, "set_dst_config", (admin, &params));
217
+ self.client.set_dst_config(admin, &params);
218
+ }
219
+
220
+ pub fn token_client(&self) -> TokenClient<'_> {
221
+ TokenClient::new(&self.env, &self.native_token)
222
+ }
223
+
224
+ pub fn balance_native(&self, addr: &Address) -> i128 {
225
+ self.token_client().balance(addr)
226
+ }
227
+
228
+ pub fn new_dst_config(&self, multiplier_bps: u32) -> DstConfig {
229
+ DstConfig {
230
+ lz_receive_base_gas: 100,
231
+ multiplier_bps,
232
+ floor_margin_usd: 1234,
233
+ native_cap: 5678,
234
+ lz_compose_base_gas: 50,
235
+ }
236
+ }
237
+
238
+ /// Builds native drop params from receiver + amount vectors.
239
+ ///
240
+ /// This is more flexible than hard-coding "two" and matches how options are typically modeled.
241
+ pub fn native_drop_params(&self, receivers: &Vec<Address>, amounts: &Vec<i128>) -> Vec<NativeDropParams> {
242
+ assert_eq!(receivers.len(), amounts.len(), "receivers/amounts length mismatch");
243
+
244
+ let mut out: Vec<NativeDropParams> = Vec::new(&self.env);
245
+ for i in 0..receivers.len() {
246
+ out.push_back(NativeDropParams { receiver: receivers.get(i).unwrap(), amount: amounts.get(i).unwrap() });
247
+ }
248
+ out
249
+ }
250
+ }
@@ -9,7 +9,12 @@ publish = false
9
9
  crate-type = ["cdylib", "rlib"]
10
10
  doctest = false
11
11
 
12
+ [features]
13
+ library = []
14
+ testutils = []
15
+
12
16
  [dependencies]
17
+ cfg-if = { workspace = true }
13
18
  soroban-sdk = { workspace = true }
14
19
  # workspace dependencies
15
20
  utils = { workspace = true }