@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
@@ -1,7 +1,4 @@
1
- use endpoint_v2::{
2
- util::{build_payload, keccak256},
3
- OutboundPacket,
4
- };
1
+ use endpoint_v2::{util, OutboundPacket};
5
2
  use soroban_sdk::{assert_with_error, Bytes, BytesN, Env};
6
3
  use utils::{buffer_reader::BufferReader, buffer_writer::BufferWriter};
7
4
 
@@ -88,10 +85,10 @@ pub fn decode_packet_header(env: &Env, encoded_header: &Bytes) -> PacketHeader {
88
85
 
89
86
  /// Returns the payload (GUID + message) from an outbound packet.
90
87
  pub fn payload(env: &Env, packet: &OutboundPacket) -> Bytes {
91
- build_payload(env, &packet.guid, &packet.message)
88
+ util::build_payload(env, &packet.guid, &packet.message)
92
89
  }
93
90
 
94
91
  /// Returns the keccak256 hash of the packet payload.
95
92
  pub fn payload_hash(env: &Env, packet: &OutboundPacket) -> BytesN<32> {
96
- keccak256(env, &payload(env, packet))
93
+ util::keccak256(env, &payload(env, packet))
97
94
  }
@@ -115,6 +115,16 @@ fn test_extract_type_3_options_interleaved_executor_and_dvn_keeps_executor_order
115
115
  assert_eq!(reader.remaining_len(), 0);
116
116
  }
117
117
 
118
+ #[test]
119
+ #[should_panic(expected = "Error(Contract, #1114)")] // WorkerOptionsError::InvalidOptions
120
+ fn test_extract_type_3_options_rejects_zero_option_size() {
121
+ let env = Env::default();
122
+ // worker_id = 1 (executor) + option_size = 0 (invalid)
123
+ let body = Bytes::from_slice(&env, &hex!("010000"));
124
+ let mut reader = BufferReader::new(&body);
125
+ extract_type_3_options(&env, &mut reader);
126
+ }
127
+
118
128
  #[test]
119
129
  #[should_panic(expected = "Error(Contract, #1115)")] // WorkerOptionsError::InvalidWorkerId
120
130
  fn test_extract_type_3_options_rejects_unknown_worker_id() {
@@ -15,7 +15,7 @@ pub const DVN_WORKER_ID: u8 = 2;
15
15
  pub const EXECUTOR_OPTION_TYPE_LZRECEIVE: u8 = 1;
16
16
  pub const EXECUTOR_OPTION_TYPE_NATIVE_DROP: u8 = 2;
17
17
 
18
- // DVN option byte offset (dvn_idx is the 4th byte of the option bytes)
18
+ // DVN option byte offset for the dvn_idx field after the worker_id and option_size fields
19
19
  pub const DVN_IDX_OFFSET: u32 = 3;
20
20
 
21
21
  /// Splits worker options into separate executor and DVN option collections.
@@ -70,6 +70,7 @@ pub fn extract_type_3_options(env: &Env, options_reader: &mut BufferReader) -> (
70
70
  while options_reader.remaining_len() > 0 {
71
71
  let worker_id = options_reader.read_u8();
72
72
  let option_size = options_reader.read_u16() as u32;
73
+ assert_with_error!(env, option_size > 0, WorkerOptionsError::InvalidOptions);
73
74
 
74
75
  // Rewind to the start of the current option and read the complete option bytes
75
76
  // 3 bytes for worker_id (1) + option_size (2)
@@ -147,8 +148,11 @@ pub fn convert_legacy_options(env: &Env, options: &mut BufferReader, option_type
147
148
  ///
148
149
  /// Searches for existing DVN options with the same index and concatenates them,
149
150
  /// or creates a new entry if this is the first option for this DVN index.
151
+ ///
152
+ /// DVN option_bytes layout: `[worker_id: u8][option_size: u16][dvn_idx: u8][dvn_option_data: bytes]`
153
+ /// The dvn_idx at byte offset 3 (DVN_IDX_OFFSET) identifies which DVN this option belongs to.
150
154
  fn append_dvn_option(env: &Env, dvn_options: &mut Map<u32, Bytes>, option_bytes: Bytes) {
151
- let dvn_idx = option_bytes.get(DVN_IDX_OFFSET).unwrap() as u32;
155
+ let dvn_idx = option_bytes.get(DVN_IDX_OFFSET).unwrap_or_panic(env, WorkerOptionsError::InvalidOptions) as u32;
152
156
  let mut existing = dvn_options.get(dvn_idx).unwrap_or(bytes!(env));
153
157
  existing.append(&option_bytes);
154
158
  dvn_options.set(dvn_idx, existing);
@@ -6,15 +6,15 @@ use soroban_sdk::{contractclient, Address, Env};
6
6
  /// This allows for custom fee calculation logic when users opt to pay fees in ZRO.
7
7
  #[contractclient(name = "ZroFeeLibClient")]
8
8
  pub trait IZroFeeLib {
9
- /// Get the treasury fee in ZRO tokens based on the worker fee.
9
+ /// Get the treasury fee in ZRO tokens based on the total native fee.
10
10
  ///
11
11
  /// # Arguments
12
12
  /// * `sender` - The sender OApp address
13
13
  /// * `dst_eid` - The destination endpoint ID
14
- /// * `worker_fee` - The worker fee
14
+ /// * `total_native_fee` - The total native fee charged to the sender
15
15
  /// * `native_treasury_fee` - The treasury fee in native tokens
16
16
  ///
17
17
  /// # Returns
18
18
  /// The amount of ZRO tokens to be paid
19
- fn get_fee(env: &Env, sender: &Address, dst_eid: u32, worker_fee: i128, native_treasury_fee: i128) -> i128;
19
+ fn get_fee(env: &Env, sender: &Address, dst_eid: u32, total_native_fee: i128, native_treasury_fee: i128) -> i128;
20
20
  }
@@ -1,7 +1,8 @@
1
1
  #![no_std]
2
2
 
3
- mod errors;
4
3
  pub mod events;
4
+
5
+ mod errors;
5
6
  mod interfaces;
6
7
 
7
8
  pub use errors::*;
@@ -107,7 +107,7 @@ impl<'a> TestSetup<'a> {
107
107
 
108
108
  /// Gets the token balance of an address
109
109
  pub fn get_token_balance(&self, token: &Address, address: &Address) -> i128 {
110
- let token_client = token::Client::new(&self.env, token);
110
+ let token_client = token::TokenClient::new(&self.env, token);
111
111
  token_client.balance(address)
112
112
  }
113
113
  }
@@ -6,7 +6,7 @@ use crate::{
6
6
  };
7
7
  use common_macros::{contract_impl, lz_contract, only_auth};
8
8
  use message_lib_common::interfaces::ILayerZeroTreasury;
9
- use soroban_sdk::{assert_with_error, token::Client, Address, Env};
9
+ use soroban_sdk::{assert_with_error, token::TokenClient, Address, Env};
10
10
  use utils::option_ext::OptionExt;
11
11
 
12
12
  /// Denominator for basis point calculations (10000 = 100%).
@@ -66,7 +66,7 @@ impl Treasury {
66
66
  /// * `amount` - The amount to withdraw (must be positive)
67
67
  #[only_auth]
68
68
  pub fn withdraw_token(env: &Env, token: &Address, to: &Address, amount: i128) {
69
- Client::new(env, token).transfer(&env.current_contract_address(), to, &amount);
69
+ TokenClient::new(env, token).transfer(&env.current_contract_address(), to, &amount);
70
70
  TokenWithdrawn { token: token.clone(), to: to.clone(), amount }.publish(env);
71
71
  }
72
72
 
@@ -117,15 +117,18 @@ impl ILayerZeroTreasury for Treasury {
117
117
  fn get_fee(env: &Env, sender: &Address, dst_eid: u32, total_native_fee: i128, pay_in_zro: bool) -> i128 {
118
118
  assert_with_error!(env, total_native_fee >= 0, TreasuryError::InvalidTotalNativeFee);
119
119
 
120
+ // If fee collection is disabled, return 0
120
121
  if !Self::fee_enabled(env) {
121
122
  return 0;
122
123
  }
123
124
 
125
+ // If paying in native, calculate and return the native treasury fee
124
126
  let native_treasury_fee = Self::calculate_native_fee(env, total_native_fee);
125
127
  if !pay_in_zro {
126
128
  return native_treasury_fee;
127
129
  }
128
130
 
131
+ // If paying in ZRO, quote the ZRO fee from the ZRO fee library
129
132
  let zro_fee =
130
133
  Self::expect_zro_fee_lib_client(env).get_fee(sender, &dst_eid, &total_native_fee, &native_treasury_fee);
131
134
  assert_with_error!(env, zro_fee >= 0, TreasuryError::InvalidZroFee);
@@ -20,6 +20,8 @@ pub enum Uln302Error {
20
20
  InvalidConfirmations,
21
21
  /// Packet header destination EID does not match this endpoint's EID
22
22
  InvalidEID,
23
+ /// Fee returned by a worker or treasury is negative
24
+ InvalidFee,
23
25
  /// Message size exceeds executor's configured maximum
24
26
  InvalidMessageSize,
25
27
  /// Optional DVNs count exceeds maximum allowed (127)
@@ -12,7 +12,7 @@ pub struct ExecutorConfigSet {
12
12
  pub sender: Address,
13
13
  #[topic]
14
14
  pub dst_eid: u32,
15
- pub config: OAppExecutorConfig,
15
+ pub config: Option<OAppExecutorConfig>,
16
16
  }
17
17
 
18
18
  #[contractevent]
@@ -22,7 +22,7 @@ pub struct SendUlnConfigSet {
22
22
  pub sender: Address,
23
23
  #[topic]
24
24
  pub dst_eid: u32,
25
- pub config: OAppUlnConfig,
25
+ pub config: Option<OAppUlnConfig>,
26
26
  }
27
27
 
28
28
  #[contractevent]
@@ -32,7 +32,7 @@ pub struct ReceiveUlnConfigSet {
32
32
  pub receiver: Address,
33
33
  #[topic]
34
34
  pub src_eid: u32,
35
- pub config: OAppUlnConfig,
35
+ pub config: Option<OAppUlnConfig>,
36
36
  }
37
37
 
38
38
  #[contractevent]
@@ -6,6 +6,10 @@ use soroban_sdk::{contractclient, Address, Bytes, BytesN, Env, Vec};
6
6
  /// Handles DVN verification and message commitment on the receiving chain.
7
7
  #[contractclient(name = "ReceiveUln302Client")]
8
8
  pub trait IReceiveUln302 {
9
+ // ============================================================================================
10
+ // Verification Functions
11
+ // ============================================================================================
12
+
9
13
  /// Called by a DVN to verify a message with a specific number of confirmations.
10
14
  ///
11
15
  /// # Arguments
@@ -46,6 +50,10 @@ pub trait IReceiveUln302 {
46
50
  /// * `payload_hash` - The hash of the message payload
47
51
  fn commit_verification(env: &Env, packet_header: &Bytes, payload_hash: &BytesN<32>);
48
52
 
53
+ // ============================================================================================
54
+ // Configuration Functions
55
+ // ============================================================================================
56
+
49
57
  /// Sets default receive ULN configurations for multiple source endpoints.
50
58
  ///
51
59
  /// # Arguments
@@ -1,7 +1,8 @@
1
1
  #![no_std]
2
2
 
3
- mod errors;
4
3
  pub mod events;
4
+
5
+ mod errors;
5
6
  mod interfaces;
6
7
  mod types;
7
8
 
@@ -41,7 +41,7 @@ impl IReceiveUln302 for Uln302 {
41
41
  /// (all required DVNs + optional DVN threshold). Once verified, cleans up DVN confirmation
42
42
  /// storage and calls the endpoint to mark the message as verified and executable.
43
43
  fn commit_verification(env: &Env, packet_header: &Bytes, payload_hash: &BytesN<32>) {
44
- let (header, uln_config) = Self::decode_packet_header_with_config(env, packet_header);
44
+ let (header, uln_config, receiver) = Self::decode_packet_header_with_config(env, packet_header);
45
45
  let header_hash = util::keccak256(env, packet_header);
46
46
 
47
47
  // check if the message is verifiable
@@ -60,7 +60,7 @@ impl IReceiveUln302 for Uln302 {
60
60
  LayerZeroEndpointV2Client::new(env, &Self::endpoint(env)).verify(
61
61
  &env.current_contract_address(),
62
62
  &Origin { src_eid: header.src_eid, sender: header.sender, nonce: header.nonce },
63
- &Address::from_payload(env, AddressPayload::ContractIdHash(header.receiver)),
63
+ &receiver,
64
64
  payload_hash,
65
65
  );
66
66
  }
@@ -92,7 +92,7 @@ impl IReceiveUln302 for Uln302 {
92
92
 
93
93
  /// Checks if a message has been sufficiently verified by DVNs and is ready to commit.
94
94
  fn verifiable(env: &Env, packet_header: &Bytes, payload_hash: &BytesN<32>) -> bool {
95
- let (_, uln_config) = Self::decode_packet_header_with_config(env, packet_header);
95
+ let (_, uln_config, _) = Self::decode_packet_header_with_config(env, packet_header);
96
96
  Self::verifiable_internal(env, &uln_config, &util::keccak256(env, packet_header), payload_hash)
97
97
  }
98
98
 
@@ -124,13 +124,15 @@ impl IReceiveUln302 for Uln302 {
124
124
  // ============================================================================================
125
125
 
126
126
  impl Uln302 {
127
- /// Sets OApp-specific receive ULN config.
127
+ /// Sets or removes OApp-specific receive ULN config.
128
128
  ///
129
+ /// If `config` is `None`, the OApp-specific config is removed (falling back to defaults).
129
130
  /// Panics if the final effective config is invalid.
130
- pub(super) fn set_receive_uln_config(env: &Env, receiver: &Address, src_eid: u32, config: &OAppUlnConfig) {
131
- config.validate_oapp_config(env);
132
-
133
- UlnStorage::set_oapp_receive_uln_configs(env, receiver, src_eid, config);
131
+ pub(super) fn set_receive_uln_config(env: &Env, receiver: &Address, src_eid: u32, config: &Option<OAppUlnConfig>) {
132
+ if let Some(c) = config {
133
+ c.validate_oapp_config(env);
134
+ }
135
+ UlnStorage::set_or_remove_oapp_receive_uln_configs(env, receiver, src_eid, config);
134
136
  // validate the config by getting the effective config
135
137
  let _ = Self::effective_receive_uln_config(env, receiver, src_eid);
136
138
 
@@ -141,8 +143,8 @@ impl Uln302 {
141
143
  // Verification Helpers Functions
142
144
  // ============================================================================================
143
145
 
144
- /// Decodes packet header and returns header and effective ULN config.
145
- fn decode_packet_header_with_config(env: &Env, packet_header: &Bytes) -> (PacketHeader, UlnConfig) {
146
+ /// Decodes packet header and returns header, effective ULN config, and receiver address.
147
+ fn decode_packet_header_with_config(env: &Env, packet_header: &Bytes) -> (PacketHeader, UlnConfig, Address) {
146
148
  let header = packet_codec_v1::decode_packet_header(env, packet_header);
147
149
  assert_with_error!(
148
150
  env,
@@ -150,10 +152,11 @@ impl Uln302 {
150
152
  Uln302Error::InvalidEID
151
153
  );
152
154
 
155
+ // convert the receiver address from BytesN<32> to ContractAddress
153
156
  let receiver = Address::from_payload(env, AddressPayload::ContractIdHash(header.receiver.clone()));
154
157
  let uln_config = Self::effective_receive_uln_config(env, &receiver, header.src_eid);
155
158
 
156
- (header, uln_config)
159
+ (header, uln_config, receiver)
157
160
  }
158
161
 
159
162
  /// Checks if all required DVNs verified and optional DVN threshold is met.
@@ -183,8 +186,8 @@ impl Uln302 {
183
186
  dvn: &Address,
184
187
  header_hash: &BytesN<32>,
185
188
  payload_hash: &BytesN<32>,
186
- confirmations: u64,
189
+ required_confirmations: u64,
187
190
  ) -> bool {
188
- Self::confirmations(env, dvn, header_hash, payload_hash).map(|c| c >= confirmations).unwrap_or(false)
191
+ Self::confirmations(env, dvn, header_hash, payload_hash).map(|c| c >= required_confirmations).unwrap_or(false)
189
192
  }
190
193
  }
@@ -45,8 +45,7 @@ impl ISendLib for Uln302 {
45
45
 
46
46
  // Treasury fee
47
47
  let workers_fee = executor_fee + dvns_fee;
48
- let treasury_client = LayerZeroTreasuryClient::new(env, &Self::treasury(env));
49
- let treasury_fee = treasury_client.get_fee(&packet.sender, &packet.dst_eid, &workers_fee, &pay_in_zro);
48
+ let (_, treasury_fee) = Self::quote_treasury(env, &packet.sender, packet.dst_eid, &workers_fee, &pay_in_zro);
50
49
 
51
50
  if pay_in_zro {
52
51
  MessagingFee { native_fee: workers_fee, zro_fee: treasury_fee }
@@ -91,13 +90,8 @@ impl ISendLib for Uln302 {
91
90
 
92
91
  // Treasury fee
93
92
  let total_worker_fee = native_fee_recipients.iter().map(|fee| fee.amount).sum();
94
- let treasury_addr = Self::treasury(env);
95
- let treasury_fee = LayerZeroTreasuryClient::new(env, &treasury_addr).get_fee(
96
- &packet.sender,
97
- &packet.dst_eid,
98
- &total_worker_fee,
99
- &pay_in_zro,
100
- );
93
+ let (treasury_addr, treasury_fee) =
94
+ Self::quote_treasury(env, &packet.sender, packet.dst_eid, &total_worker_fee, &pay_in_zro);
101
95
 
102
96
  // Handle ZRO fee recipients
103
97
  let mut zro_fee_recipients = vec![env];
@@ -207,19 +201,23 @@ impl ISendUln302 for Uln302 {
207
201
  // ==============================================================================
208
202
 
209
203
  impl Uln302 {
210
- /// Sets OApp-specific executor configuration for a destination endpoint.
211
- pub(super) fn set_executor_config(env: &Env, sender: &Address, dst_eid: u32, config: &OAppExecutorConfig) {
212
- UlnStorage::set_oapp_executor_configs(env, sender, dst_eid, config);
204
+ /// Sets or removes OApp-specific executor configuration for a destination endpoint.
205
+ ///
206
+ /// If `config` is `None`, the OApp-specific config is removed (falling back to defaults).
207
+ pub(super) fn set_executor_config(env: &Env, sender: &Address, dst_eid: u32, config: &Option<OAppExecutorConfig>) {
208
+ UlnStorage::set_or_remove_oapp_executor_configs(env, sender, dst_eid, config);
213
209
  ExecutorConfigSet { sender: sender.clone(), dst_eid, config: config.clone() }.publish(env);
214
210
  }
215
211
 
216
- /// Sets OApp-specific send ULN configuration for a destination endpoint.
212
+ /// Sets or removes OApp-specific send ULN configuration for a destination endpoint.
217
213
  ///
214
+ /// If `config` is `None`, the OApp-specific config is removed (falling back to defaults).
218
215
  /// Panics if the final effective config is invalid.
219
- pub(super) fn set_send_uln_config(env: &Env, sender: &Address, dst_eid: u32, config: &OAppUlnConfig) {
220
- config.validate_oapp_config(env);
221
-
222
- UlnStorage::set_oapp_send_uln_configs(env, sender, dst_eid, config);
216
+ pub(super) fn set_send_uln_config(env: &Env, sender: &Address, dst_eid: u32, config: &Option<OAppUlnConfig>) {
217
+ if let Some(c) = config {
218
+ c.validate_oapp_config(env);
219
+ }
220
+ UlnStorage::set_or_remove_oapp_send_uln_configs(env, sender, dst_eid, config);
223
221
  // validate the config by getting the effective config
224
222
  let _ = Self::effective_send_uln_config(env, sender, dst_eid);
225
223
 
@@ -231,13 +229,15 @@ impl Uln302 {
231
229
  // ============================================================================================
232
230
 
233
231
  /// Quotes the executor fee for message execution.
234
- fn quote_executor(env: &Env, sender: &Address, dst_eid: u32, message_length: u32, options: &Bytes) -> i128 {
232
+ fn quote_executor(env: &Env, sender: &Address, dst_eid: u32, message_size: u32, options: &Bytes) -> i128 {
235
233
  // Get the effective executor config and validate message size
236
234
  let executor_config = Self::effective_executor_config(env, sender, dst_eid);
237
- assert_with_error!(env, message_length <= executor_config.max_message_size, Uln302Error::InvalidMessageSize);
235
+ assert_with_error!(env, message_size <= executor_config.max_message_size, Uln302Error::InvalidMessageSize);
238
236
 
239
237
  let executor_client = LayerZeroExecutorClient::new(env, &executor_config.executor);
240
- executor_client.get_fee(&env.current_contract_address(), sender, &dst_eid, &message_length, options)
238
+ let fee = executor_client.get_fee(&env.current_contract_address(), sender, &dst_eid, &message_size, options);
239
+ assert_with_error!(env, fee >= 0, Uln302Error::InvalidFee);
240
+ fee
241
241
  }
242
242
 
243
243
  /// Quotes the total DVN fees for message verification.
@@ -260,11 +260,36 @@ impl Uln302 {
260
260
  .map(|(idx, dvn_addr)| {
261
261
  let dvn_client = LayerZeroDVNClient::new(env, &dvn_addr);
262
262
  let dvn_opts = dvn_options.get(idx as u32).unwrap_or(bytes!(env));
263
- dvn_client.get_fee(&send_lib, sender, &dst_eid, packet_header, payload_hash, &confirmations, &dvn_opts)
263
+ let fee = dvn_client.get_fee(
264
+ &send_lib,
265
+ sender,
266
+ &dst_eid,
267
+ packet_header,
268
+ payload_hash,
269
+ &confirmations,
270
+ &dvn_opts,
271
+ );
272
+ assert_with_error!(env, fee >= 0, Uln302Error::InvalidFee);
273
+ fee
264
274
  })
265
275
  .sum()
266
276
  }
267
277
 
278
+ /// Quotes the treasury fee for the protocol.
279
+ fn quote_treasury(
280
+ env: &Env,
281
+ sender: &Address,
282
+ dst_eid: u32,
283
+ workers_fee: &i128,
284
+ pay_in_zro: &bool,
285
+ ) -> (Address, i128) {
286
+ let treasury_addr = Self::treasury(env);
287
+ let treasury_fee =
288
+ LayerZeroTreasuryClient::new(env, &treasury_addr).get_fee(sender, &dst_eid, workers_fee, pay_in_zro);
289
+ assert_with_error!(env, treasury_fee >= 0, Uln302Error::InvalidFee);
290
+ (treasury_addr, treasury_fee)
291
+ }
292
+
268
293
  // ============================================================================================
269
294
  // Assign Job Functions
270
295
  // ============================================================================================
@@ -275,16 +300,17 @@ impl Uln302 {
275
300
  guid: &BytesN<32>,
276
301
  sender: &Address,
277
302
  dst_eid: u32,
278
- message_length: u32,
303
+ message_size: u32,
279
304
  options: &Bytes,
280
305
  ) -> FeeRecipient {
281
306
  // Get the effective executor config and validate message size
282
307
  let executor_config = Self::effective_executor_config(env, sender, dst_eid);
283
- assert_with_error!(env, message_length <= executor_config.max_message_size, Uln302Error::InvalidMessageSize);
308
+ assert_with_error!(env, message_size <= executor_config.max_message_size, Uln302Error::InvalidMessageSize);
284
309
 
285
310
  let executor_client = LayerZeroExecutorClient::new(env, &executor_config.executor);
286
311
  let recipient =
287
- executor_client.assign_job(&env.current_contract_address(), sender, &dst_eid, &message_length, options);
312
+ executor_client.assign_job(&env.current_contract_address(), sender, &dst_eid, &message_size, options);
313
+ assert_with_error!(env, recipient.amount >= 0, Uln302Error::InvalidFee);
288
314
 
289
315
  ExecutorFeePaid { guid: guid.clone(), executor: executor_client.address.clone(), fee: recipient.clone() }
290
316
  .publish(env);
@@ -319,6 +345,7 @@ impl Uln302 {
319
345
  &confirmations,
320
346
  &dvn_opts,
321
347
  );
348
+ assert_with_error!(env, dvn_fee_recipient.amount >= 0, Uln302Error::InvalidFee);
322
349
  fees.push_back(dvn_fee_recipient);
323
350
  dvns.push_back(dvn_addr);
324
351
  }
@@ -5,11 +5,11 @@ use soroban_sdk::{Address, BytesN};
5
5
  /// Storage for the Uln302 message library.
6
6
  #[storage]
7
7
  pub enum UlnStorage {
8
- /// The endpoint address (set in constructor)
8
+ /// The endpoint address (immutable, set once in constructor)
9
9
  #[instance(Address)]
10
10
  Endpoint,
11
11
 
12
- /// The treasury address for fee collection (set in constructor)
12
+ /// The treasury address for fee collection (immutable, set once in constructor)
13
13
  #[instance(Address)]
14
14
  Treasury,
15
15
 
@@ -60,7 +60,7 @@ fn test_effective_receive_uln_config_with_custom_config() {
60
60
  assert_eq_event(
61
61
  &env,
62
62
  &uln302.address,
63
- ReceiveUlnConfigSet { config: custom_config.clone(), receiver: oapp.clone(), src_eid: eid },
63
+ ReceiveUlnConfigSet { config: Some(custom_config.clone()), receiver: oapp.clone(), src_eid: eid },
64
64
  );
65
65
 
66
66
  let config = uln302.effective_receive_uln_config(&oapp, &eid);
@@ -100,3 +100,47 @@ fn test_effective_receive_uln_config_must_have_at_least_one_dvn() {
100
100
  let result = endpoint.try_set_config(&Address::generate(&env), &oapp, &uln302.address, &params);
101
101
  assert_eq!(result.err().unwrap().ok().unwrap(), Uln302Error::UlnAtLeastOneDVN.into());
102
102
  }
103
+
104
+ #[test]
105
+ fn test_remove_receive_uln_config_by_setting_none() {
106
+ let setup = setup();
107
+
108
+ let default_config = UlnConfig::generate(&setup.env, 15, 1, 2, 1);
109
+ let eid = 101;
110
+
111
+ setup.set_default_configs(eid, default_config.clone());
112
+
113
+ let TestSetup { env, uln302, endpoint, .. } = setup;
114
+
115
+ let oapp = Address::generate(&env);
116
+
117
+ // Step 1: Set a custom receive ULN config
118
+ let custom_config = OAppUlnConfig {
119
+ use_default_confirmations: true,
120
+ use_default_required_dvns: false,
121
+ use_default_optional_dvns: false,
122
+ uln_config: UlnConfig::generate(&env, 0, 3, 1, 1),
123
+ };
124
+
125
+ let config_bytes = custom_config.clone().to_xdr(&env);
126
+ let params = vec![&env, SetConfigParam { eid, config_type: CONFIG_TYPE_RECEIVE_ULN, config: config_bytes }];
127
+ endpoint.set_config(&Address::generate(&env), &oapp, &uln302.address, &params);
128
+
129
+ // Verify custom config is applied
130
+ assert!(uln302.oapp_receive_uln_config(&oapp, &eid).is_some());
131
+ let config = uln302.effective_receive_uln_config(&oapp, &eid);
132
+ assert_eq!(config.required_dvns, custom_config.uln_config.required_dvns);
133
+
134
+ // Step 2: Remove the custom config by setting None
135
+ let none_config: Option<OAppUlnConfig> = None;
136
+ let config_bytes = none_config.to_xdr(&env);
137
+ let params = vec![&env, SetConfigParam { eid, config_type: CONFIG_TYPE_RECEIVE_ULN, config: config_bytes }];
138
+ endpoint.set_config(&Address::generate(&env), &oapp, &uln302.address, &params);
139
+
140
+ // Verify the OApp-specific config is removed
141
+ assert_eq!(uln302.oapp_receive_uln_config(&oapp, &eid), None);
142
+
143
+ // Verify the effective config falls back to defaults
144
+ let config = uln302.effective_receive_uln_config(&oapp, &eid);
145
+ assert_eq!(config, default_config);
146
+ }
@@ -461,3 +461,66 @@ fn test_verifiable_partial_optional_verification() {
461
461
  // Verifiable because threshold is met
462
462
  assert!(receive_client.verifiable(&packet_header, &payload_hash));
463
463
  }
464
+
465
+ #[test]
466
+ fn test_not_verifiable_required_verified_but_optional_threshold_not_met() {
467
+ let test_setup = setup();
468
+ let TestSetup { env, uln302, .. } = &test_setup;
469
+
470
+ let dvn1 = Address::generate(env);
471
+ let dvn2 = Address::generate(env);
472
+ let dvn3 = Address::generate(env);
473
+ let dvn4 = Address::generate(env);
474
+ let receiver = Address::generate(env);
475
+
476
+ // Set up config with 2 required DVNs and 2 optional DVNs with threshold of 2
477
+ let mut config = create_test_uln_config(env);
478
+ config.required_dvns = vec![env, dvn1.clone(), dvn2.clone()];
479
+ config.optional_dvns = vec![env, dvn3.clone(), dvn4.clone()];
480
+ config.optional_dvn_threshold = 2; // Need both optional DVNs
481
+ TestSetup::set_default_receive_uln_config(&test_setup, SRC_EID, config);
482
+
483
+ let packet_header = create_test_packet_header(env, &receiver);
484
+ let payload_hash = create_test_payload_hash(env);
485
+
486
+ let receive_client = ReceiveUln302Client::new(env, &uln302.address);
487
+
488
+ // Verify all required DVNs
489
+ env.mock_auths(&[MockAuth {
490
+ address: &dvn1,
491
+ invoke: &MockAuthInvoke {
492
+ contract: &uln302.address,
493
+ fn_name: "verify",
494
+ args: (&dvn1, &packet_header, &payload_hash, &CONFIRMATIONS).into_val(env),
495
+ sub_invokes: &[],
496
+ },
497
+ }]);
498
+ receive_client.verify(&dvn1, &packet_header, &payload_hash, &CONFIRMATIONS);
499
+
500
+ env.mock_auths(&[MockAuth {
501
+ address: &dvn2,
502
+ invoke: &MockAuthInvoke {
503
+ contract: &uln302.address,
504
+ fn_name: "verify",
505
+ args: (&dvn2, &packet_header, &payload_hash, &CONFIRMATIONS).into_val(env),
506
+ sub_invokes: &[],
507
+ },
508
+ }]);
509
+ receive_client.verify(&dvn2, &packet_header, &payload_hash, &CONFIRMATIONS);
510
+
511
+ // Verify only 1 optional DVN (need 2)
512
+ env.mock_auths(&[MockAuth {
513
+ address: &dvn3,
514
+ invoke: &MockAuthInvoke {
515
+ contract: &uln302.address,
516
+ fn_name: "verify",
517
+ args: (&dvn3, &packet_header, &payload_hash, &CONFIRMATIONS).into_val(env),
518
+ sub_invokes: &[],
519
+ },
520
+ }]);
521
+ receive_client.verify(&dvn3, &packet_header, &payload_hash, &CONFIRMATIONS);
522
+ // DVN4 doesn't verify
523
+
524
+ // Not verifiable because optional threshold (2) is not met (only 1 verified)
525
+ assert!(!receive_client.verifiable(&packet_header, &payload_hash));
526
+ }
@@ -7,7 +7,7 @@ use crate::{
7
7
  uln302::CONFIG_TYPE_EXECUTOR,
8
8
  };
9
9
  use endpoint_v2::SetConfigParam;
10
- use soroban_sdk::{log, testutils::Address as _, vec, xdr::ToXdr, Address, Env};
10
+ use soroban_sdk::{log, testutils::Address as _, vec, xdr::ToXdr, Address, Bytes, Env};
11
11
  use utils::testing_utils::assert_eq_event;
12
12
 
13
13
  #[test]
@@ -60,7 +60,7 @@ fn test_effective_executor_config_with_custom_executor() {
60
60
  assert_eq_event(
61
61
  &setup.env,
62
62
  &setup.uln302.address,
63
- ExecutorConfigSet { config: custom_config.clone(), dst_eid: eid, sender: oapp.clone() },
63
+ ExecutorConfigSet { config: Some(custom_config.clone()), dst_eid: eid, sender: oapp.clone() },
64
64
  );
65
65
 
66
66
  let config = setup.uln302.effective_executor_config(&oapp, &eid);
@@ -130,3 +130,48 @@ fn test_effective_executor_config_fully_custom() {
130
130
  assert_eq!(config.executor, custom_executor);
131
131
  assert_eq!(config.max_message_size, custom_config.max_message_size);
132
132
  }
133
+
134
+ #[test]
135
+ fn test_remove_executor_config_by_setting_none() {
136
+ let setup = setup();
137
+
138
+ let eid = 102;
139
+ let default_executor = setup.register_executable_address();
140
+ let default_config = ExecutorConfig { max_message_size: 5000, executor: default_executor.clone() };
141
+
142
+ let default_uln_config = UlnConfig::generate(&setup.env, 10, 2, 3, 2);
143
+
144
+ setup.set_default_executor_config(eid, default_config.clone());
145
+ setup.set_default_receive_uln_config(eid, default_uln_config.clone());
146
+ setup.set_default_send_uln_config(eid, default_uln_config.clone());
147
+
148
+ let oapp = Address::generate(&setup.env);
149
+
150
+ // Step 1: Set a custom executor config
151
+ let custom_executor = setup.register_executable_address();
152
+ let custom_config = OAppExecutorConfig { max_message_size: 8000, executor: Some(custom_executor.clone()) };
153
+
154
+ let config_bytes = custom_config.clone().to_xdr(&setup.env);
155
+ let params = vec![&setup.env, SetConfigParam { eid, config_type: CONFIG_TYPE_EXECUTOR, config: config_bytes }];
156
+ setup.endpoint.set_config(&Address::generate(&setup.env), &oapp, &setup.uln302.address, &params);
157
+
158
+ // Verify custom config is applied
159
+ let config = setup.uln302.effective_executor_config(&oapp, &eid);
160
+ assert_eq!(config.executor, custom_executor);
161
+ assert_eq!(config.max_message_size, 8000);
162
+ assert!(setup.uln302.oapp_executor_config(&oapp, &eid).is_some());
163
+
164
+ // Step 2: Remove the custom config by setting None
165
+ let none_config: Option<OAppExecutorConfig> = None;
166
+ let config_bytes = none_config.to_xdr(&setup.env);
167
+ assert_eq!(config_bytes, Bytes::from_array(&setup.env, &[0, 0, 0, 1])); // ScVal::Void XDR encoding
168
+ let params = vec![&setup.env, SetConfigParam { eid, config_type: CONFIG_TYPE_EXECUTOR, config: config_bytes }];
169
+ setup.endpoint.set_config(&Address::generate(&setup.env), &oapp, &setup.uln302.address, &params);
170
+
171
+ // Verify the OApp-specific config is removed
172
+ assert_eq!(setup.uln302.oapp_executor_config(&oapp, &eid), None);
173
+
174
+ // Verify the effective config falls back to defaults
175
+ let config = setup.uln302.effective_executor_config(&oapp, &eid);
176
+ assert_eq!(config, default_config);
177
+ }