@layerzerolabs/protocol-stellar-v2 0.2.34 → 0.2.36

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 (135) hide show
  1. package/.turbo/turbo-build.log +281 -276
  2. package/.turbo/turbo-lint.log +209 -211
  3. package/.turbo/turbo-test.log +1705 -1701
  4. package/Cargo.lock +10 -10
  5. package/Cargo.toml +1 -1
  6. package/contracts/common-macros/src/auth.rs +5 -5
  7. package/contracts/common-macros/src/lib.rs +69 -0
  8. package/contracts/common-macros/src/rbac.rs +90 -0
  9. package/contracts/common-macros/src/storage.rs +7 -5
  10. package/contracts/common-macros/src/tests/lz_contract.rs +5 -7
  11. package/contracts/common-macros/src/tests/mod.rs +1 -0
  12. package/contracts/common-macros/src/tests/rbac.rs +420 -0
  13. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_multisig_code.snap +4 -4
  14. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__auth__snapshot_generated_ownable_code.snap +5 -12
  15. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__rbac__snapshot_preserve_function_signature.snap +17 -0
  16. package/contracts/common-macros/src/tests/storage/parse_name.rs +0 -1
  17. package/contracts/common-macros/src/tests/storage/snapshots/common_macros__tests__storage__generate_storage__snapshot_generated_storage_code.snap +3 -3
  18. package/contracts/endpoint-v2/src/endpoint_v2.rs +5 -4
  19. package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +7 -8
  20. package/contracts/endpoint-v2/src/messaging_channel.rs +78 -45
  21. package/contracts/endpoint-v2/src/storage.rs +8 -3
  22. package/contracts/endpoint-v2/src/tests/endpoint_setup.rs +2 -2
  23. package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +12 -15
  24. package/contracts/endpoint-v2/src/tests/endpoint_v2/verifiable.rs +46 -9
  25. package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +7 -23
  26. package/contracts/endpoint-v2/src/tests/messaging_channel/clear_payload.rs +23 -20
  27. package/contracts/endpoint-v2/src/tests/messaging_channel/inbound.rs +94 -1
  28. package/contracts/endpoint-v2/src/tests/messaging_channel/inbound_nonce.rs +17 -15
  29. package/contracts/endpoint-v2/src/tests/messaging_channel/mod.rs +1 -1
  30. package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +48 -13
  31. package/contracts/endpoint-v2/src/tests/messaging_channel/pending_inbound_nonces.rs +111 -0
  32. package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +15 -25
  33. package/contracts/layerzero-views/src/layerzero_view.rs +2 -2
  34. package/contracts/layerzero-views/src/tests/layerzero_view_tests.rs +3 -4
  35. package/contracts/layerzero-views/src/tests/setup.rs +0 -21
  36. package/contracts/macro-integration-tests/tests/runtime/lz_contract/wrapper_default.rs +1 -1
  37. package/contracts/macro-integration-tests/tests/runtime/lz_contract/wrapper_multisig.rs +1 -1
  38. package/contracts/macro-integration-tests/tests/runtime/lz_contract/wrapper_multisig_upgradeable.rs +1 -1
  39. package/contracts/macro-integration-tests/tests/runtime/multisig/self_auth.rs +1 -1
  40. package/contracts/macro-integration-tests/tests/runtime/ownable/initialization.rs +8 -5
  41. package/contracts/macro-integration-tests/tests/runtime/ownable/ownership_transfer.rs +2 -2
  42. package/contracts/macro-integration-tests/tests/runtime/rbac/guard_behavior.rs +91 -0
  43. package/contracts/macro-integration-tests/tests/runtime/rbac/mod.rs +30 -0
  44. package/contracts/macro-integration-tests/tests/runtime/ttl_configurable/configuration.rs +2 -2
  45. package/contracts/macro-integration-tests/tests/runtime/upgradeable/migrate_guard_and_state.rs +4 -4
  46. package/contracts/macro-integration-tests/tests/ui/lz_contract/pass/basic.rs +1 -1
  47. package/contracts/macro-integration-tests/tests/ui/ownable/pass/basic.rs +1 -1
  48. package/contracts/macro-integration-tests/tests/ui/rbac/fail/missing_env.rs +18 -0
  49. package/contracts/macro-integration-tests/tests/ui/rbac/fail/missing_env.stderr +16 -0
  50. package/contracts/macro-integration-tests/tests/ui/rbac/fail/param_not_address.rs +18 -0
  51. package/contracts/macro-integration-tests/tests/ui/rbac/fail/param_not_address.stderr +24 -0
  52. package/contracts/macro-integration-tests/tests/ui/rbac/fail/param_not_found.rs +18 -0
  53. package/contracts/macro-integration-tests/tests/ui/rbac/fail/param_not_found.stderr +24 -0
  54. package/contracts/macro-integration-tests/tests/ui/rbac/pass/basic.rs +71 -0
  55. package/contracts/macro-integration-tests/tests/ui_rbac.rs +12 -0
  56. package/contracts/message-libs/blocked-message-lib/src/lib.rs +4 -4
  57. package/contracts/message-libs/uln-302/src/send_uln.rs +5 -5
  58. package/contracts/oapps/counter/src/counter.rs +6 -0
  59. package/contracts/oapps/oapp/src/oapp_sender.rs +3 -2
  60. package/contracts/oapps/oft/src/extensions/oft_fee.rs +5 -0
  61. package/contracts/oapps/oft/src/interfaces/mintable.rs +2 -2
  62. package/contracts/oapps/oft/src/oft.rs +5 -4
  63. package/contracts/oapps/oft/src/tests/extensions/oft_fee.rs +2 -2
  64. package/contracts/oapps/oft/src/tests/extensions/pausable.rs +2 -2
  65. package/contracts/oapps/oft/src/tests/extensions/rate_limiter.rs +2 -2
  66. package/contracts/oapps/sac-manager/Cargo.toml +0 -1
  67. package/contracts/oapps/sac-manager/src/interfaces/mod.rs +3 -0
  68. package/contracts/oapps/sac-manager/src/interfaces/sac_admin_wrapper.rs +49 -0
  69. package/contracts/oapps/sac-manager/src/lib.rs +3 -3
  70. package/contracts/oapps/sac-manager/src/sac_manager.rs +45 -73
  71. package/contracts/oapps/sac-manager/src/storage.rs +2 -9
  72. package/contracts/oapps/sac-manager/src/tests/sac_manager/clawback.rs +8 -10
  73. package/contracts/oapps/sac-manager/src/tests/sac_manager/mint.rs +13 -18
  74. package/contracts/oapps/sac-manager/src/tests/sac_manager/mod.rs +0 -1
  75. package/contracts/oapps/sac-manager/src/tests/sac_manager/set_admin.rs +22 -12
  76. package/contracts/oapps/sac-manager/src/tests/sac_manager/set_authorized.rs +19 -9
  77. package/contracts/oapps/sac-manager/src/tests/sac_manager/test_helper.rs +27 -10
  78. package/contracts/oapps/sac-manager/src/tests/sac_manager/view_functions.rs +0 -15
  79. package/contracts/oapps/sac-manager/src/tests/test_helper.rs +19 -28
  80. package/contracts/upgrader/src/lib.rs +5 -2
  81. package/contracts/utils/src/auth.rs +6 -2
  82. package/contracts/utils/src/errors.rs +18 -0
  83. package/contracts/utils/src/lib.rs +1 -0
  84. package/contracts/utils/src/multisig.rs +5 -1
  85. package/contracts/utils/src/ownable.rs +1 -1
  86. package/contracts/utils/src/rbac.rs +428 -0
  87. package/contracts/utils/src/tests/auth.rs +2 -2
  88. package/contracts/utils/src/tests/mod.rs +1 -0
  89. package/contracts/utils/src/tests/multisig.rs +2 -2
  90. package/contracts/utils/src/tests/ownable.rs +4 -5
  91. package/contracts/utils/src/tests/rbac.rs +559 -0
  92. package/contracts/utils/src/tests/ttl_configurable.rs +5 -6
  93. package/contracts/utils/src/tests/upgradeable.rs +4 -5
  94. package/contracts/workers/worker/src/worker.rs +1 -1
  95. package/docs/layerzero-v2-on-stellar.md +46 -2
  96. package/package.json +3 -3
  97. package/sdk/.turbo/turbo-test.log +370 -372
  98. package/sdk/dist/generated/bml.d.ts +53 -3
  99. package/sdk/dist/generated/bml.js +27 -3
  100. package/sdk/dist/generated/counter.d.ts +84 -5
  101. package/sdk/dist/generated/counter.js +31 -4
  102. package/sdk/dist/generated/dvn.d.ts +55 -5
  103. package/sdk/dist/generated/dvn.js +28 -4
  104. package/sdk/dist/generated/dvn_fee_lib.d.ts +55 -5
  105. package/sdk/dist/generated/dvn_fee_lib.js +28 -4
  106. package/sdk/dist/generated/endpoint.d.ts +64 -15
  107. package/sdk/dist/generated/endpoint.js +32 -8
  108. package/sdk/dist/generated/executor.d.ts +55 -5
  109. package/sdk/dist/generated/executor.js +28 -4
  110. package/sdk/dist/generated/executor_fee_lib.d.ts +55 -5
  111. package/sdk/dist/generated/executor_fee_lib.js +28 -4
  112. package/sdk/dist/generated/executor_helper.d.ts +53 -3
  113. package/sdk/dist/generated/executor_helper.js +27 -3
  114. package/sdk/dist/generated/layerzero_view.d.ts +55 -5
  115. package/sdk/dist/generated/layerzero_view.js +28 -4
  116. package/sdk/dist/generated/oft.d.ts +84 -5
  117. package/sdk/dist/generated/oft.js +31 -4
  118. package/sdk/dist/generated/price_feed.d.ts +55 -5
  119. package/sdk/dist/generated/price_feed.js +28 -4
  120. package/sdk/dist/generated/sac_manager.d.ts +213 -666
  121. package/sdk/dist/generated/sac_manager.js +57 -238
  122. package/sdk/dist/generated/sml.d.ts +55 -5
  123. package/sdk/dist/generated/sml.js +28 -4
  124. package/sdk/dist/generated/treasury.d.ts +55 -5
  125. package/sdk/dist/generated/treasury.js +28 -4
  126. package/sdk/dist/generated/uln302.d.ts +55 -5
  127. package/sdk/dist/generated/uln302.js +28 -4
  128. package/sdk/dist/generated/upgrader.d.ts +53 -3
  129. package/sdk/dist/generated/upgrader.js +27 -3
  130. package/sdk/package.json +1 -1
  131. package/sdk/test/oft-sml.test.ts +10 -9
  132. package/sdk/test/{sac-manager-redistribution.test.ts → sac-manager.test.ts} +49 -25
  133. package/contracts/endpoint-v2/src/tests/messaging_channel/lazy_inbound_nonce.rs +0 -39
  134. package/contracts/oapps/sac-manager/src/errors.rs +0 -14
  135. package/contracts/oapps/sac-manager/src/tests/sac_manager/set_minter.rs +0 -69
@@ -7,7 +7,7 @@ use crate::{
7
7
  util::{compute_guid, keccak256},
8
8
  };
9
9
  use common_macros::contract_impl;
10
- use soroban_sdk::{assert_with_error, Address, Bytes, BytesN, Env};
10
+ use soroban_sdk::{assert_with_error, Address, Bytes, BytesN, Env, Vec};
11
11
 
12
12
  /// Represents an empty payload hash (equivalent to bytes32(uint256(0)) in Solidity)
13
13
  const EMPTY_PAYLOAD_HASH_BYTES: [u8; 32] = [0u8; 32];
@@ -15,6 +15,9 @@ const EMPTY_PAYLOAD_HASH_BYTES: [u8; 32] = [0u8; 32];
15
15
  /// Represents a nilified payload hash (equivalent to bytes32(type(uint256).max) in Solidity)
16
16
  const NIL_PAYLOAD_HASH_BYTES: [u8; 32] = [0xffu8; 32];
17
17
 
18
+ /// Max number of out-of-order nonces in the pending list.
19
+ pub(super) const PENDING_INBOUND_NONCE_MAX_LEN: u64 = 256;
20
+
18
21
  #[contract_impl]
19
22
  impl IMessagingChannel for EndpointV2 {
20
23
  /// Skips the next expected inbound nonce without verifying.
@@ -25,7 +28,7 @@ impl IMessagingChannel for EndpointV2 {
25
28
 
26
29
  let next_nonce = Self::inbound_nonce(env, receiver, src_eid, sender) + 1;
27
30
  assert_with_error!(env, nonce == next_nonce, EndpointError::InvalidNonce);
28
- EndpointStorage::set_lazy_inbound_nonce(env, receiver, src_eid, sender, &nonce);
31
+ Self::insert_and_drain_pending_nonces(env, receiver, src_eid, sender, nonce);
29
32
 
30
33
  InboundNonceSkipped { src_eid, sender: sender.clone(), receiver: receiver.clone(), nonce }.publish(env);
31
34
  }
@@ -45,10 +48,14 @@ impl IMessagingChannel for EndpointV2 {
45
48
  Self::require_oapp_auth(env, caller, receiver);
46
49
 
47
50
  let cur_payload_hash = Self::inbound_payload_hash(env, receiver, src_eid, sender, nonce);
48
- let lazy_nonce = Self::lazy_inbound_nonce(env, receiver, src_eid, sender);
51
+ let inbound_nonce = Self::inbound_nonce(env, receiver, src_eid, sender);
49
52
 
50
53
  assert_with_error!(env, payload_hash == &cur_payload_hash, EndpointError::PayloadHashNotFound);
51
- assert_with_error!(env, nonce > lazy_nonce || cur_payload_hash.is_some(), EndpointError::InvalidNonce);
54
+ assert_with_error!(env, nonce > inbound_nonce || cur_payload_hash.is_some(), EndpointError::InvalidNonce);
55
+
56
+ if nonce > inbound_nonce {
57
+ Self::insert_and_drain_pending_nonces(env, receiver, src_eid, sender, nonce);
58
+ }
52
59
  EndpointStorage::set_inbound_payload_hash(env, receiver, src_eid, sender, nonce, &Self::nil_payload_hash(env));
53
60
 
54
61
  PacketNilified {
@@ -77,9 +84,9 @@ impl IMessagingChannel for EndpointV2 {
77
84
  let cur_payload_hash = Self::inbound_payload_hash(env, receiver, src_eid, sender, nonce);
78
85
  assert_with_error!(env, cur_payload_hash.as_ref() == Some(payload_hash), EndpointError::PayloadHashNotFound);
79
86
 
80
- // Check if nonce is at or below the lazy nonce
81
- let lazy_nonce = Self::lazy_inbound_nonce(env, receiver, src_eid, sender);
82
- assert_with_error!(env, nonce <= lazy_nonce, EndpointError::InvalidNonce);
87
+ // Check if nonce is at or below the inbound nonce
88
+ let inbound_nonce = Self::inbound_nonce(env, receiver, src_eid, sender);
89
+ assert_with_error!(env, nonce <= inbound_nonce, EndpointError::InvalidNonce);
83
90
 
84
91
  // Remove the payload hash from storage
85
92
  EndpointStorage::remove_inbound_payload_hash(env, receiver, src_eid, sender, nonce);
@@ -112,25 +119,15 @@ impl IMessagingChannel for EndpointV2 {
112
119
  /// Returns the max index of the longest gapless sequence of verified message nonces.
113
120
  ///
114
121
  /// The uninitialized value is 0. The first nonce is always 1.
115
- /// It starts from the `lazy_inbound_nonce` (last checkpoint) and iteratively checks
116
- /// if the next nonce has been verified.
117
122
  ///
118
123
  /// Note: OApp explicitly skipped nonces count as "verified" for these purposes.
119
- ///
120
- /// Examples: `[1,2,3,4,6,7] => 4`, `[1,2,6,8,10] => 2`, `[1,3,4,5,6] => 1`
121
124
  fn inbound_nonce(env: &Env, receiver: &Address, src_eid: u32, sender: &BytesN<32>) -> u64 {
122
- let mut nonce_cursor = Self::lazy_inbound_nonce(env, receiver, src_eid, sender);
123
-
124
- // Find the effective inbound current nonce
125
- while EndpointStorage::has_inbound_payload_hash(env, receiver, src_eid, sender, nonce_cursor + 1) {
126
- nonce_cursor += 1;
127
- }
128
- nonce_cursor
125
+ EndpointStorage::inbound_nonce(env, receiver, src_eid, sender)
129
126
  }
130
127
 
131
- /// Returns the lazy inbound nonce (last checkpoint) for a specific path.
132
- fn lazy_inbound_nonce(env: &Env, receiver: &Address, src_eid: u32, sender: &BytesN<32>) -> u64 {
133
- EndpointStorage::lazy_inbound_nonce(env, receiver, src_eid, sender)
128
+ /// Returns the pending inbound nonces for a specific path.
129
+ fn pending_inbound_nonces(env: &Env, receiver: &Address, src_eid: u32, sender: &BytesN<32>) -> Vec<u64> {
130
+ EndpointStorage::pending_inbound_nonces(env, receiver, src_eid, sender)
134
131
  }
135
132
 
136
133
  /// Returns the payload hash for a specific inbound nonce.
@@ -162,9 +159,8 @@ impl EndpointV2 {
162
159
 
163
160
  /// Records an inbound message payload hash for a specific nonce on a specific path.
164
161
  ///
165
- /// Inbound won't update the nonce eagerly to allow unordered verification.
166
- /// Instead, it will update the nonce lazily when the message is received.
167
- /// Messages can only be cleared in order to preserve censorship-resistance.
162
+ /// When nonce > inbound_nonce, inserts into the pending list and drains consecutive
163
+ /// nonces to update the effective inbound nonce.
168
164
  ///
169
165
  /// # Arguments
170
166
  /// * `receiver` - The receiver OApp address
@@ -181,22 +177,27 @@ impl EndpointV2 {
181
177
  payload_hash: &BytesN<32>,
182
178
  ) {
183
179
  assert_with_error!(env, payload_hash != &Self::empty_payload_hash(env), EndpointError::InvalidPayloadHash);
180
+
181
+ let inbound_nonce = Self::inbound_nonce(env, receiver, src_eid, sender);
182
+
183
+ // Only allow to verify new nonces or re-verify unexecuted nonces.
184
+ assert_with_error!(
185
+ env,
186
+ nonce > inbound_nonce || EndpointStorage::has_inbound_payload_hash(env, receiver, src_eid, sender, nonce),
187
+ EndpointError::InvalidNonce
188
+ );
189
+
190
+ if nonce > inbound_nonce {
191
+ Self::insert_and_drain_pending_nonces(env, receiver, src_eid, sender, nonce);
192
+ }
193
+
184
194
  EndpointStorage::set_inbound_payload_hash(env, receiver, src_eid, sender, nonce, payload_hash);
185
195
  }
186
196
 
187
- /// Clears a stored message payload and increments the lazy inbound nonce.
188
- ///
189
- /// Calling this function will clear the stored message and increment the
190
- /// `lazy_inbound_nonce` to the provided nonce.
197
+ /// Clears a stored message payload.
191
198
  ///
192
- /// Note: This function does not change `inbound_nonce`, it only changes
193
- /// the `lazy_inbound_nonce` up to the provided nonce.
194
- ///
195
- /// # EVM Alignment
196
- /// This implementation aligns with the EVM endpoint behavior. Executors should call
197
- /// `clear` from lower nonce to higher nonce sequentially. This ensures the range check
198
- /// `(current_nonce + 1..=nonce)` remains small, avoiding long iteration when verifying
199
- /// that all intermediate nonces have been verified.
199
+ /// Requires nonce <= inbound_nonce (no iteration, O(1) check). The inbound_nonce
200
+ /// is updated during verify when consecutive nonces are drained from the pending list.
200
201
  ///
201
202
  /// # Arguments
202
203
  /// * `receiver` - The receiver OApp address
@@ -212,15 +213,8 @@ impl EndpointV2 {
212
213
  nonce: u64,
213
214
  payload: &Bytes,
214
215
  ) {
215
- let current_nonce = Self::lazy_inbound_nonce(env, receiver, src_eid, sender);
216
-
217
- // Try to update the lazy inbound nonce until the target nonce
218
- if nonce > current_nonce {
219
- let has_payload = (current_nonce + 1..=nonce)
220
- .all(|n| EndpointStorage::has_inbound_payload_hash(env, receiver, src_eid, sender, n));
221
- assert_with_error!(env, has_payload, EndpointError::InvalidNonce);
222
- EndpointStorage::set_lazy_inbound_nonce(env, receiver, src_eid, sender, &nonce);
223
- }
216
+ let inbound_nonce = Self::inbound_nonce(env, receiver, src_eid, sender);
217
+ assert_with_error!(env, nonce <= inbound_nonce, EndpointError::InvalidNonce);
224
218
 
225
219
  // Check the hash of the payload to verify the executor has given the proper payload that has been verified
226
220
  let actual_hash = keccak256(env, payload);
@@ -231,6 +225,45 @@ impl EndpointV2 {
231
225
  EndpointStorage::remove_inbound_payload_hash(env, receiver, src_eid, sender, nonce);
232
226
  }
233
227
 
228
+ /// Inserts a nonce into a sorted pending list, then drains consecutive nonces from the front
229
+ /// to advance `inbound_nonce`.
230
+ ///
231
+ /// Bounded by `PENDING_INBOUND_NONCE_MAX_LEN` to prevent DDoS via unbounded list growth.
232
+ fn insert_and_drain_pending_nonces(
233
+ env: &Env,
234
+ receiver: &Address,
235
+ src_eid: u32,
236
+ sender: &BytesN<32>,
237
+ new_nonce: u64,
238
+ ) {
239
+ let inbound_nonce = Self::inbound_nonce(env, receiver, src_eid, sender);
240
+ assert_with_error!(
241
+ env,
242
+ new_nonce > inbound_nonce && new_nonce <= inbound_nonce + PENDING_INBOUND_NONCE_MAX_LEN,
243
+ EndpointError::InvalidNonce
244
+ );
245
+
246
+ let mut pending_nonces = Self::pending_inbound_nonces(env, receiver, src_eid, sender);
247
+
248
+ // Allow to re-verify at the same nonce and insert the new nonce if it doesn't already exist.
249
+ // When the binary_search returns an error, the nonce is not in the list and should be inserted.
250
+ if let Err(i) = pending_nonces.binary_search(new_nonce) {
251
+ pending_nonces.insert(i, new_nonce);
252
+
253
+ // Drain consecutive nonces from the front to advance the inbound nonce
254
+ let mut new_inbound_nonce = inbound_nonce;
255
+ while !pending_nonces.is_empty() && pending_nonces.first_unchecked() == new_inbound_nonce + 1 {
256
+ new_inbound_nonce = pending_nonces.pop_front_unchecked();
257
+ }
258
+
259
+ // Update the pending nonces and inbound nonce if needed
260
+ EndpointStorage::set_pending_inbound_nonces(env, receiver, src_eid, sender, &pending_nonces);
261
+ if new_inbound_nonce > inbound_nonce {
262
+ EndpointStorage::set_inbound_nonce(env, receiver, src_eid, sender, &new_inbound_nonce);
263
+ }
264
+ }
265
+ }
266
+
234
267
  /// Represents an empty payload hash
235
268
  fn empty_payload_hash(env: &Env) -> BytesN<32> {
236
269
  BytesN::from_array(env, &EMPTY_PAYLOAD_HASH_BYTES)
@@ -1,6 +1,6 @@
1
1
  use crate::Timeout;
2
2
  use common_macros::storage;
3
- use soroban_sdk::{Address, BytesN};
3
+ use soroban_sdk::{Address, BytesN, Vec};
4
4
 
5
5
  #[storage]
6
6
  pub enum EndpointStorage {
@@ -24,10 +24,15 @@ pub enum EndpointStorage {
24
24
  /// Messaging Channel
25
25
  /// ============================================================================================
26
26
 
27
- /// The lazy inbound nonce for a receiver
27
+ /// Sorted list of out-of-order verified nonces
28
+ #[persistent(Vec<u64>)]
29
+ #[default(Vec::new(env))]
30
+ PendingInboundNonces { receiver: Address, src_eid: u32, sender: BytesN<32> },
31
+
32
+ /// The current inbound nonce for a receiver
28
33
  #[persistent(u64)]
29
34
  #[default(0)]
30
- LazyInboundNonce { receiver: Address, src_eid: u32, sender: BytesN<32> },
35
+ InboundNonce { receiver: Address, src_eid: u32, sender: BytesN<32> },
31
36
 
32
37
  /// The inbound payload hash for a receiver
33
38
  #[persistent(BytesN<32>)]
@@ -270,11 +270,11 @@ impl<'a> TestSetup<'a> {
270
270
  self.endpoint_client.send_compose(from, to, guid, &index, message);
271
271
  }
272
272
 
273
- pub fn set_lazy_inbound_nonce(&self, receiver: &Address, src_eid: u32, sender: &BytesN<32>, lazy_nonce: u64) {
273
+ pub fn set_inbound_nonce(&self, receiver: &Address, src_eid: u32, sender: &BytesN<32>, inbound_nonce: u64) {
274
274
  let env = &self.env;
275
275
  let endpoint_client = &self.endpoint_client;
276
276
  env.as_contract(&endpoint_client.address, || {
277
- storage::EndpointStorage::set_lazy_inbound_nonce(env, receiver, src_eid, sender, &lazy_nonce)
277
+ storage::EndpointStorage::set_inbound_nonce(env, receiver, src_eid, sender, &inbound_nonce)
278
278
  });
279
279
  }
280
280
 
@@ -119,9 +119,9 @@ fn test_clear_emits_packet_delivered_event() {
119
119
  assert_eq_event(env, &endpoint_client.address, PacketDelivered { origin: origin.clone(), receiver: receiver.clone() });
120
120
  }
121
121
 
122
- // Lazy inbound nonce updates
122
+ // Inbound nonce is advanced during verify, not during clear
123
123
  #[test]
124
- fn test_clear_updates_lazy_inbound_nonce() {
124
+ fn test_clear_does_not_change_inbound_nonce() {
125
125
  let context = setup();
126
126
  let env = &context.env;
127
127
  let endpoint_client = &context.endpoint_client;
@@ -136,15 +136,13 @@ fn test_clear_updates_lazy_inbound_nonce() {
136
136
  let (_receive_lib, origin, _payload_hash) =
137
137
  arrange_verified_packet_with_auth(&context, src_eid, &sender, &receiver, nonce, &guid, &message);
138
138
 
139
- // Verify initial lazy inbound nonce
140
- let initial_lazy_nonce = endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender);
141
- assert_eq!(initial_lazy_nonce, 0, "Initial lazy inbound nonce should be 0");
139
+ // Verify advanced inbound nonce (happens during verify).
140
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), nonce);
142
141
 
143
142
  clear_packet_with_auth(&context, &receiver, &origin, &receiver, &guid, &message);
144
143
 
145
- // Verify lazy inbound nonce was updated via public interface
146
- let lazy_nonce = endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender);
147
- assert_eq!(lazy_nonce, nonce);
144
+ // Clear does not advance inbound nonce.
145
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), nonce);
148
146
  }
149
147
 
150
148
  // Sequential nonce behavior
@@ -179,9 +177,8 @@ fn test_clear_success_sequential_nonces_update_lazy_nonce_to_latest() {
179
177
  verify_packet_with_auth(&context, &receive_lib, &origin2, &receiver, &payload_hash2);
180
178
  clear_packet_with_auth(&context, &receiver, &origin2, &receiver, &guid2, &message2);
181
179
 
182
- // Verify lazy inbound nonce was updated to 2
183
- let lazy_nonce = endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender);
184
- assert_eq!(lazy_nonce, 2);
180
+ // Verify advanced inbound nonce.
181
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
185
182
  }
186
183
 
187
184
  // Authorization
@@ -410,11 +407,11 @@ fn test_clear_does_not_advance_lazy_nonce_when_clearing_older_nonce() {
410
407
  let origin2 = Origin { src_eid, sender: sender.clone(), nonce: 2 };
411
408
  verify_packet_with_auth(&context, &receive_lib, &origin2, &receiver, &payload_hash2);
412
409
 
413
- // Clear nonce 2 first (this advances lazy nonce to 2 because nonce 1..=2 are present).
410
+ // Clear nonce 2 first. This does not advance inbound nonce (it was advanced during verify).
414
411
  clear_packet_with_auth(&context, &receiver, &origin2, &receiver, &guid2, &message2);
415
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 2);
412
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
416
413
 
417
- // Clearing an older nonce should not change lazy nonce.
414
+ // Clearing an older nonce should not change inbound nonce.
418
415
  clear_packet_with_auth(&context, &receiver, &origin1, &receiver, &guid1, &message1);
419
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 2);
416
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
420
417
  }
@@ -1,6 +1,6 @@
1
1
  use soroban_sdk::{testutils::Address as _, BytesN};
2
2
 
3
- use crate::{tests::endpoint_setup::setup, tests::endpoint_setup::TestSetup, Origin};
3
+ use crate::{storage, tests::endpoint_setup::setup, tests::endpoint_setup::TestSetup, Origin};
4
4
 
5
5
  fn skip_with_auth(context: &TestSetup, receiver: &soroban_sdk::Address, src_eid: u32, sender: &BytesN<32>, nonce: u64) {
6
6
  // `skip` requires authorization from `caller` (the receiver or its delegate).
@@ -20,7 +20,7 @@ fn verify_with_auth(
20
20
  context.endpoint_client.verify(receive_lib, origin, receiver, payload_hash);
21
21
  }
22
22
 
23
- // New path (lazy nonce == 0) => verifiable when origin.nonce > lazy nonce
23
+ // New path (inbound nonce == 0) => verifiable when origin.nonce is within (0, 256]
24
24
  #[test]
25
25
  fn test_verifiable_new_path_nonce_1_true() {
26
26
  let context = setup();
@@ -30,12 +30,12 @@ fn test_verifiable_new_path_nonce_1_true() {
30
30
  let sender = BytesN::from_array(&context.env, &[1u8; 32]);
31
31
  let origin = Origin { src_eid, sender, nonce: 1 };
32
32
 
33
- // For a new path (lazy nonce is 0), nonce 1 > 0 should be verifiable.
33
+ // For a new path (inbound nonce is 0), nonce 1 should be verifiable.
34
34
  let result = endpoint_client.verifiable(&origin, &receiver);
35
35
  assert!(result);
36
36
  }
37
37
 
38
- // Established path (lazy nonce > 0) => verifiable when origin.nonce > lazy nonce
38
+ // Established path => verifiable when origin.nonce is in (inbound_nonce, inbound_nonce + 256]
39
39
  #[test]
40
40
  fn test_verifiable_after_skip_nonce_gt_lazy_true() {
41
41
  let context = setup();
@@ -48,7 +48,7 @@ fn test_verifiable_after_skip_nonce_gt_lazy_true() {
48
48
  // Establish the path by skipping nonce 1.
49
49
  skip_with_auth(&context, &receiver, src_eid, &sender, 1);
50
50
 
51
- // Now lazy_nonce = 1, nonce 2 > 1 should be verifiable.
51
+ // Now inbound_nonce = 1, nonce 2 should be verifiable.
52
52
  let origin = Origin { src_eid, sender, nonce: 2 };
53
53
  let result = endpoint_client.verifiable(&origin, &receiver);
54
54
  assert!(result);
@@ -79,8 +79,8 @@ fn test_verifiable_true_when_nonce_leq_lazy_but_payload_hash_exists() {
79
79
  // Advance lazy nonce to 3 while keeping payload hash for 2 (skip sets lazy nonce only).
80
80
  skip_with_auth(&context, &receiver, src_eid, &sender, 3);
81
81
 
82
- // Sanity: lazy nonce was advanced, and the payload hash for nonce 2 still exists.
83
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 3);
82
+ // Sanity: inbound nonce was advanced, and the payload hash for nonce 2 still exists.
83
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 3);
84
84
  assert!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2u64).is_some());
85
85
 
86
86
  // Now nonce 2 <= lazy 3, but payload hash exists -> verifiable should be true.
@@ -98,13 +98,50 @@ fn test_verifiable_nonce_eq_lazy_false_without_payload_hash() {
98
98
  let sender = BytesN::from_array(env, &[1u8; 32]);
99
99
  let receiver = soroban_sdk::Address::generate(env);
100
100
 
101
- // Set lazy nonce to 2 without storing any payload hashes.
101
+ // Advance inbound nonce to 2 without storing any payload hashes at nonce 2.
102
102
  skip_with_auth(&context, &receiver, src_eid, &sender, 1);
103
103
  skip_with_auth(&context, &receiver, src_eid, &sender, 2);
104
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 2);
104
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
105
105
 
106
106
  // nonce == lazy and payload hash missing -> verifiable should be false.
107
107
  let origin2 = Origin { src_eid, sender: sender.clone(), nonce: 2u64 };
108
108
  assert!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2u64).is_none());
109
109
  assert!(!endpoint_client.verifiable(&origin2, &receiver));
110
110
  }
111
+
112
+ #[test]
113
+ fn test_verifiable_upper_bound_is_enforced() {
114
+ let context = setup();
115
+ let env = &context.env;
116
+ let endpoint_client = &context.endpoint_client;
117
+
118
+ let src_eid = 2u32;
119
+ let sender = BytesN::from_array(env, &[1u8; 32]);
120
+ let receiver = soroban_sdk::Address::generate(env);
121
+
122
+ // For a new path inbound_nonce=0: 256 is allowed, 257 is not.
123
+ let origin_256 = Origin { src_eid, sender: sender.clone(), nonce: 256u64 };
124
+ let origin_257 = Origin { src_eid, sender, nonce: 257u64 };
125
+ assert!(endpoint_client.verifiable(&origin_256, &receiver));
126
+ assert!(!endpoint_client.verifiable(&origin_257, &receiver));
127
+ }
128
+
129
+ #[test]
130
+ fn test_verifiable_upper_bound_is_enforced_when_inbound_nonce_nonzero() {
131
+ let context = setup();
132
+ let env = &context.env;
133
+ let endpoint_client = &context.endpoint_client;
134
+
135
+ let src_eid = 2u32;
136
+ let sender = BytesN::from_array(env, &[1u8; 32]);
137
+ let receiver = soroban_sdk::Address::generate(env);
138
+
139
+ env.as_contract(&endpoint_client.address, || {
140
+ storage::EndpointStorage::set_inbound_nonce(env, &receiver, src_eid, &sender, &100u64)
141
+ });
142
+
143
+ let ok = Origin { src_eid, sender: sender.clone(), nonce: 356u64 }; // 100 + 256
144
+ let too_far = Origin { src_eid, sender, nonce: 357u64 };
145
+ assert!(endpoint_client.verifiable(&ok, &receiver));
146
+ assert!(!endpoint_client.verifiable(&too_far, &receiver));
147
+ }
@@ -84,9 +84,6 @@ fn test_burn_success_with_stored_payload() {
84
84
  let nonce = 1u64;
85
85
  let payload_hash = BytesN::from_array(env, &[0xabu8; 32]);
86
86
 
87
- // Boundary condition: burn requires nonce <= lazy_nonce.
88
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, nonce);
89
-
90
87
  // Store a payload hash first.
91
88
  context.inbound_as_verified(&receiver, src_eid, &sender, nonce, &payload_hash);
92
89
  assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(payload_hash.clone()));
@@ -128,9 +125,6 @@ fn test_burn_with_delegate() {
128
125
  // Set delegate for receiver.
129
126
  env.as_contract(&endpoint_client.address, || storage::EndpointStorage::set_delegate(env, &receiver, &delegate));
130
127
 
131
- // Burn requires nonce <= lazy_nonce.
132
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, 1);
133
-
134
128
  // Store a payload hash first.
135
129
  context.inbound_as_verified(&receiver, src_eid, &sender, nonce, &payload_hash);
136
130
 
@@ -169,9 +163,6 @@ fn test_burn_multiple_payloads() {
169
163
  let nonce1 = 1;
170
164
  let nonce2 = 2;
171
165
 
172
- // Burn requires nonce <= lazy_nonce.
173
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, 2);
174
-
175
166
  // Store multiple payload hashes.
176
167
  context.inbound_as_verified(&receiver, src_eid, &sender, nonce1, &payload_hash1);
177
168
  context.inbound_as_verified(&receiver, src_eid, &sender, nonce2, &payload_hash2);
@@ -203,12 +194,6 @@ fn test_burn_different_paths() {
203
194
  let nonce = 1;
204
195
  let payload_hash = BytesN::from_array(env, &[0xefu8; 32]);
205
196
 
206
- // Burn requires nonce <= lazy_nonce.
207
- context.set_lazy_inbound_nonce(&receiver1, src_eid1, &sender1, 1);
208
- context.set_lazy_inbound_nonce(&receiver2, src_eid1, &sender1, 1);
209
- context.set_lazy_inbound_nonce(&receiver1, src_eid2, &sender1, 1);
210
- context.set_lazy_inbound_nonce(&receiver1, src_eid1, &sender2, 1);
211
-
212
197
  // Store payload hashes for different paths.
213
198
  context.inbound_as_verified(&receiver1, src_eid1, &sender1, nonce, &payload_hash);
214
199
  context.inbound_as_verified(&receiver2, src_eid1, &sender1, nonce, &payload_hash);
@@ -247,9 +232,6 @@ fn test_burn_payload_hash_not_found_when_mismatch() {
247
232
  let payload_hash = BytesN::from_array(env, &[0xabu8; 32]);
248
233
  let wrong_payload_hash = BytesN::from_array(env, &[0x11u8; 32]);
249
234
 
250
- // Burn requires nonce <= lazy_nonce.
251
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, nonce);
252
-
253
235
  // Store a payload hash first.
254
236
  context.inbound_as_verified(&receiver, src_eid, &sender, nonce, &payload_hash);
255
237
  assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(payload_hash.clone()));
@@ -270,8 +252,8 @@ fn test_burn_payload_hash_not_found_when_storage_none() {
270
252
  let nonce = 1u64;
271
253
  let payload_hash = BytesN::from_array(env, &[0xabu8; 32]);
272
254
 
273
- // Even if nonce <= lazy_nonce, burn must fail without a stored payload hash.
274
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, nonce);
255
+ // Burn must fail without a stored payload hash.
256
+ context.set_inbound_nonce(&receiver, src_eid, &sender, nonce);
275
257
  let result = try_burn_with_auth(&context, &receiver, &receiver, src_eid, &sender, nonce, &payload_hash);
276
258
  assert_eq!(result.err().unwrap().ok().unwrap(), EndpointError::PayloadHashNotFound.into());
277
259
  }
@@ -288,9 +270,11 @@ fn test_burn_invalid_nonce_when_greater_than_lazy_nonce() {
288
270
  let nonce = 2u64;
289
271
  let payload_hash = BytesN::from_array(env, &[0xabu8; 32]);
290
272
 
291
- // lazy nonce is 1, trying to burn nonce 2 should fail.
292
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, 1);
293
- context.inbound_as_verified(&receiver, src_eid, &sender, nonce, &payload_hash);
273
+ // inbound nonce is 1, trying to burn nonce 2 should fail (even if a payload hash exists).
274
+ context.set_inbound_nonce(&receiver, src_eid, &sender, 1);
275
+ env.as_contract(&context.endpoint_client.address, || {
276
+ storage::EndpointStorage::set_inbound_payload_hash(env, &receiver, src_eid, &sender, nonce, &payload_hash);
277
+ });
294
278
 
295
279
  let result = try_burn_with_auth(&context, &receiver, &receiver, src_eid, &sender, nonce, &payload_hash);
296
280
  assert_eq!(result.err().unwrap().ok().unwrap(), EndpointError::InvalidNonce.into());
@@ -36,7 +36,7 @@ fn clear_payload(
36
36
  });
37
37
  }
38
38
 
39
- // Internal clear_payload() removes verified payload and advances lazy nonce
39
+ // Internal clear_payload() removes verified payload and does not change inbound nonce
40
40
  #[test]
41
41
  fn test_clear_payload_success() {
42
42
  let context = setup();
@@ -55,11 +55,11 @@ fn test_clear_payload_success() {
55
55
  clear_payload(&context, &receiver, src_eid, &sender, nonce, &payload);
56
56
 
57
57
  assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), None);
58
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), nonce);
58
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), nonce);
59
59
  }
60
60
 
61
61
  #[test]
62
- fn test_clear_payload_with_lazy_nonce_update_skipping_intermediate() {
62
+ fn test_clear_payload_keeps_other_payload_hashes_intact() {
63
63
  let context = setup();
64
64
  let env = &context.env;
65
65
  let endpoint_client = &context.endpoint_client;
@@ -76,21 +76,21 @@ fn test_clear_payload_with_lazy_nonce_update_skipping_intermediate() {
76
76
  let hash2 = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 2, &payload2);
77
77
  let hash3 = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 3, &payload3);
78
78
 
79
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 0);
79
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 3);
80
80
 
81
- // Clearing nonce 3 advances lazy nonce from 0 -> 3, but only if 1..=3 all exist.
81
+ // Clearing nonce 3 removes only nonce 3's payload hash.
82
82
  clear_payload(&context, &receiver, src_eid, &sender, 3, &payload3);
83
83
 
84
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 3);
85
84
  assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &3), None);
86
85
  assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &1), Some(hash1));
87
86
  assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2), Some(hash2));
87
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 3);
88
88
  let _ = hash3; // hash3 is only used to ensure it was computed and stored for nonce 3.
89
89
  }
90
90
 
91
- // Clearing a nonce <= lazy nonce does not update lazy nonce
91
+ // Clearing a nonce <= inbound nonce does not update inbound nonce
92
92
  #[test]
93
- fn test_clear_payload_does_not_update_lazy_nonce_when_nonce_is_not_greater() {
93
+ fn test_clear_payload_does_not_update_inbound_nonce_when_nonce_is_not_greater() {
94
94
  let context = setup();
95
95
  let env = &context.env;
96
96
  let endpoint_client = &context.endpoint_client;
@@ -99,22 +99,25 @@ fn test_clear_payload_does_not_update_lazy_nonce_when_nonce_is_not_greater() {
99
99
  let src_eid = 2;
100
100
  let sender = BytesN::from_array(env, &[1u8; 32]);
101
101
 
102
- // Pretend we already checkpointed to 5.
102
+ // Pretend we already advanced inbound nonce to 5.
103
103
  env.as_contract(&endpoint_client.address, || {
104
- storage::EndpointStorage::set_lazy_inbound_nonce(env, &receiver, src_eid, &sender, &5u64)
104
+ storage::EndpointStorage::set_inbound_nonce(env, &receiver, src_eid, &sender, &5u64)
105
105
  });
106
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 5);
106
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 5);
107
107
 
108
108
  // Store a payload hash at nonce 3, then clear it.
109
109
  let nonce = 3u64;
110
110
  let payload = Bytes::from_array(env, &[0xaa, 0xbb, 0xcc]);
111
- let payload_hash = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, nonce, &payload);
112
- assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(payload_hash));
111
+ let payload_hash = BytesN::from_array(env, &env.crypto().keccak256(&payload).to_array());
112
+ env.as_contract(&endpoint_client.address, || {
113
+ storage::EndpointStorage::set_inbound_payload_hash(env, &receiver, src_eid, &sender, nonce, &payload_hash)
114
+ });
115
+ assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(payload_hash.clone()));
113
116
 
114
117
  clear_payload(&context, &receiver, src_eid, &sender, nonce, &payload);
115
118
 
116
- // Clearing an older nonce should not mutate lazy_inbound_nonce.
117
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 5);
119
+ // Clearing an older nonce should not mutate inbound_nonce.
120
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 5);
118
121
  assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), None);
119
122
  }
120
123
 
@@ -132,9 +135,9 @@ fn test_clear_payload_payload_hash_not_found_when_nonce_is_checkpointed_but_miss
132
135
  // nonce <= lazy_nonce, so clear_payload will NOT run the "has_payload for all intermediate nonces" check.
133
136
  // It should fail at the payload hash check instead.
134
137
  env.as_contract(&endpoint_client.address, || {
135
- storage::EndpointStorage::set_lazy_inbound_nonce(env, &receiver, src_eid, &sender, &5u64)
138
+ storage::EndpointStorage::set_inbound_nonce(env, &receiver, src_eid, &sender, &5u64)
136
139
  });
137
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 5);
140
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 5);
138
141
 
139
142
  // No payload hash is stored for nonce 3.
140
143
  let nonce = 3u64;
@@ -188,16 +191,16 @@ fn test_clear_payload_missing_intermediate_nonce() {
188
191
  let src_eid = 2;
189
192
  let sender = BytesN::from_array(env, &[1u8; 32]);
190
193
 
191
- // Store only nonce 1 and 3, skip nonce 2
194
+ // Store only nonce 1 and 3, skip nonce 2.
192
195
  let payload1 = Bytes::from_array(env, &[0x01]);
193
196
  let payload3 = Bytes::from_array(env, &[0x03]);
194
197
 
195
198
  let _ = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 1, &payload1);
196
199
  let _ = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 3, &payload3);
197
200
 
198
- // Clearing nonce 1 advances lazy nonce from 0 -> 1.
201
+ // Clearing nonce 1 succeeds.
199
202
  clear_payload(&context, &receiver, src_eid, &sender, 1, &payload1);
200
203
 
201
- // Try to clear nonce 3 - should panic because nonce 2 is missing
204
+ // Try to clear nonce 3 - should panic because inbound_nonce is still 1 (nonce 3 is out-of-order).
202
205
  clear_payload(&context, &receiver, src_eid, &sender, 3, &payload3);
203
206
  }