@layerzerolabs/protocol-stellar-v2 0.2.34 → 0.2.35

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 (68) hide show
  1. package/.turbo/turbo-build.log +250 -251
  2. package/.turbo/turbo-lint.log +226 -231
  3. package/.turbo/turbo-test.log +1994 -1731
  4. package/Cargo.lock +10 -10
  5. package/Cargo.toml +1 -1
  6. package/contracts/common-macros/src/storage.rs +7 -5
  7. package/contracts/common-macros/src/tests/storage/snapshots/common_macros__tests__storage__generate_storage__snapshot_generated_storage_code.snap +3 -3
  8. package/contracts/endpoint-v2/src/endpoint_v2.rs +5 -4
  9. package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +7 -8
  10. package/contracts/endpoint-v2/src/messaging_channel.rs +78 -45
  11. package/contracts/endpoint-v2/src/storage.rs +8 -3
  12. package/contracts/endpoint-v2/src/tests/endpoint_setup.rs +2 -2
  13. package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +12 -15
  14. package/contracts/endpoint-v2/src/tests/endpoint_v2/verifiable.rs +46 -9
  15. package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +7 -23
  16. package/contracts/endpoint-v2/src/tests/messaging_channel/clear_payload.rs +23 -20
  17. package/contracts/endpoint-v2/src/tests/messaging_channel/inbound.rs +94 -1
  18. package/contracts/endpoint-v2/src/tests/messaging_channel/inbound_nonce.rs +17 -15
  19. package/contracts/endpoint-v2/src/tests/messaging_channel/mod.rs +1 -1
  20. package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +48 -13
  21. package/contracts/endpoint-v2/src/tests/messaging_channel/pending_inbound_nonces.rs +111 -0
  22. package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +15 -25
  23. package/contracts/layerzero-views/src/layerzero_view.rs +2 -2
  24. package/contracts/layerzero-views/src/tests/layerzero_view_tests.rs +3 -4
  25. package/contracts/layerzero-views/src/tests/setup.rs +0 -21
  26. package/contracts/message-libs/blocked-message-lib/src/lib.rs +4 -4
  27. package/contracts/message-libs/uln-302/src/send_uln.rs +5 -5
  28. package/contracts/oapps/counter/src/counter.rs +6 -0
  29. package/contracts/oapps/oapp/src/oapp_sender.rs +3 -2
  30. package/contracts/oapps/oft/src/extensions/oft_fee.rs +5 -0
  31. package/contracts/oapps/oft/src/oft.rs +5 -4
  32. package/docs/layerzero-v2-on-stellar.md +46 -2
  33. package/package.json +3 -3
  34. package/sdk/.turbo/turbo-test.log +312 -316
  35. package/sdk/dist/generated/bml.d.ts +3 -3
  36. package/sdk/dist/generated/bml.js +3 -3
  37. package/sdk/dist/generated/counter.d.ts +32 -3
  38. package/sdk/dist/generated/counter.js +6 -3
  39. package/sdk/dist/generated/dvn.d.ts +3 -3
  40. package/sdk/dist/generated/dvn.js +3 -3
  41. package/sdk/dist/generated/dvn_fee_lib.d.ts +2 -2
  42. package/sdk/dist/generated/dvn_fee_lib.js +2 -2
  43. package/sdk/dist/generated/endpoint.d.ts +12 -13
  44. package/sdk/dist/generated/endpoint.js +7 -7
  45. package/sdk/dist/generated/executor.d.ts +3 -3
  46. package/sdk/dist/generated/executor.js +3 -3
  47. package/sdk/dist/generated/executor_fee_lib.d.ts +2 -2
  48. package/sdk/dist/generated/executor_fee_lib.js +2 -2
  49. package/sdk/dist/generated/executor_helper.d.ts +2 -2
  50. package/sdk/dist/generated/executor_helper.js +2 -2
  51. package/sdk/dist/generated/layerzero_view.d.ts +3 -3
  52. package/sdk/dist/generated/layerzero_view.js +3 -3
  53. package/sdk/dist/generated/oft.d.ts +32 -3
  54. package/sdk/dist/generated/oft.js +6 -3
  55. package/sdk/dist/generated/price_feed.d.ts +3 -3
  56. package/sdk/dist/generated/price_feed.js +3 -3
  57. package/sdk/dist/generated/sac_manager.d.ts +24 -3
  58. package/sdk/dist/generated/sac_manager.js +4 -3
  59. package/sdk/dist/generated/sml.d.ts +2 -2
  60. package/sdk/dist/generated/sml.js +2 -2
  61. package/sdk/dist/generated/treasury.d.ts +2 -2
  62. package/sdk/dist/generated/treasury.js +2 -2
  63. package/sdk/dist/generated/uln302.d.ts +3 -3
  64. package/sdk/dist/generated/uln302.js +3 -3
  65. package/sdk/dist/generated/upgrader.d.ts +2 -2
  66. package/sdk/dist/generated/upgrader.js +2 -2
  67. package/sdk/package.json +1 -1
  68. package/contracts/endpoint-v2/src/tests/messaging_channel/lazy_inbound_nonce.rs +0 -39
@@ -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
  }
@@ -1,6 +1,6 @@
1
1
  use soroban_sdk::{testutils::Address as _, Address, BytesN};
2
2
 
3
- use crate::{endpoint_v2::EndpointV2, tests::endpoint_setup::setup};
3
+ use crate::{endpoint_v2::EndpointV2, storage, tests::endpoint_setup::setup};
4
4
 
5
5
  // Internal inbound() stores payload hash per nonce and rejects empty payload hash
6
6
  #[test]
@@ -92,3 +92,96 @@ fn test_inbound_rejects_empty_payload_hash() {
92
92
  EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, nonce, &empty_hash)
93
93
  });
94
94
  }
95
+
96
+ #[test]
97
+ fn test_inbound_out_of_order_populates_pending_and_drains_when_gap_closed() {
98
+ let context = setup();
99
+ let env = &context.env;
100
+ let endpoint_client = &context.endpoint_client;
101
+
102
+ let receiver = Address::generate(env);
103
+ let src_eid = 2;
104
+ let sender = BytesN::from_array(env, &[1u8; 32]);
105
+
106
+ let hash_2 = BytesN::from_array(env, &[0x22u8; 32]);
107
+ env.as_contract(&endpoint_client.address, || {
108
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 2, &hash_2)
109
+ });
110
+
111
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
112
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 2u64]);
113
+
114
+ let hash_1 = BytesN::from_array(env, &[0x11u8; 32]);
115
+ env.as_contract(&endpoint_client.address, || {
116
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 1, &hash_1)
117
+ });
118
+
119
+ // Gap is closed, so pending drains and inbound nonce advances to 2.
120
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
121
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
122
+ assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &1u64), Some(hash_1));
123
+ assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2u64), Some(hash_2));
124
+ }
125
+
126
+ #[test]
127
+ #[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
128
+ fn test_inbound_rejects_nonce_beyond_pending_window() {
129
+ let context = setup();
130
+ let env = &context.env;
131
+ let endpoint_client = &context.endpoint_client;
132
+
133
+ let receiver = Address::generate(env);
134
+ let src_eid = 2;
135
+ let sender = BytesN::from_array(env, &[1u8; 32]);
136
+ let hash = BytesN::from_array(env, &[0xabu8; 32]);
137
+
138
+ // inbound_nonce is 0, so nonce 257 is out of range (max is 256).
139
+ env.as_contract(&endpoint_client.address, || {
140
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 257, &hash)
141
+ });
142
+ }
143
+
144
+ #[test]
145
+ #[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
146
+ fn test_inbound_rejects_reverify_when_nonce_leq_inbound_and_payload_missing() {
147
+ let context = setup();
148
+ let env = &context.env;
149
+ let endpoint_client = &context.endpoint_client;
150
+
151
+ let receiver = Address::generate(env);
152
+ let src_eid = 2;
153
+ let sender = BytesN::from_array(env, &[1u8; 32]);
154
+
155
+ // Force inbound_nonce to 1 without storing any payload hashes.
156
+ context.set_inbound_nonce(&receiver, src_eid, &sender, 1);
157
+
158
+ let hash = BytesN::from_array(env, &[0xabu8; 32]);
159
+ env.as_contract(&endpoint_client.address, || {
160
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 1, &hash)
161
+ });
162
+ }
163
+
164
+ #[test]
165
+ fn test_inbound_allows_reverify_when_nonce_leq_inbound_and_payload_exists() {
166
+ let context = setup();
167
+ let env = &context.env;
168
+ let endpoint_client = &context.endpoint_client;
169
+
170
+ let receiver = Address::generate(env);
171
+ let src_eid = 2;
172
+ let sender = BytesN::from_array(env, &[1u8; 32]);
173
+
174
+ let old_hash = BytesN::from_array(env, &[0xabu8; 32]);
175
+ env.as_contract(&endpoint_client.address, || {
176
+ storage::EndpointStorage::set_inbound_payload_hash(env, &receiver, src_eid, &sender, 1u64, &old_hash);
177
+ storage::EndpointStorage::set_inbound_nonce(env, &receiver, src_eid, &sender, &1u64);
178
+ });
179
+ assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &1u64), Some(old_hash));
180
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
181
+
182
+ let new_hash = BytesN::from_array(env, &[0xcdu8; 32]);
183
+ env.as_contract(&endpoint_client.address, || {
184
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 1u64, &new_hash)
185
+ });
186
+ assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &1u64), Some(new_hash));
187
+ }
@@ -1,4 +1,4 @@
1
- use soroban_sdk::{testutils::Address as _, Address, BytesN};
1
+ use soroban_sdk::{testutils::Address as _, vec, Address, BytesN};
2
2
 
3
3
  use crate::tests::endpoint_setup::{setup, TestSetup};
4
4
 
@@ -25,13 +25,13 @@ fn test_inbound_nonce_initially_zero() {
25
25
  let src_eid = 2;
26
26
  let sender = BytesN::from_array(env, &[1u8; 32]);
27
27
 
28
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 0);
29
28
  assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
29
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
30
30
  }
31
31
 
32
- // Lazy nonce is the baseline when there are no consecutive payload hashes
32
+ // Stored inbound nonce is the baseline when there are no pending nonces
33
33
  #[test]
34
- fn test_inbound_nonce_equals_lazy_nonce_when_no_payload_hashes() {
34
+ fn test_inbound_nonce_equals_stored_value_when_no_pending_nonces() {
35
35
  let context = setup();
36
36
  let env = &context.env;
37
37
  let endpoint_client = &context.endpoint_client;
@@ -40,9 +40,9 @@ fn test_inbound_nonce_equals_lazy_nonce_when_no_payload_hashes() {
40
40
  let src_eid = 2;
41
41
  let sender = BytesN::from_array(env, &[1u8; 32]);
42
42
 
43
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, 5);
44
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 5);
43
+ context.set_inbound_nonce(&receiver, src_eid, &sender, 5);
45
44
  assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 5);
45
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
46
46
  }
47
47
 
48
48
  // The inbound_nonce advances through the longest gapless consecutive sequence
@@ -56,13 +56,14 @@ fn test_inbound_nonce_advances_through_consecutive_verified_payload_hashes() {
56
56
  let src_eid = 2;
57
57
  let sender = BytesN::from_array(env, &[1u8; 32]);
58
58
 
59
- // Start from lazy_nonce = 2 and add payloads at 3,4,5.
60
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, 2);
59
+ // Start from inbound_nonce = 2 and add payloads at 3,4,5.
60
+ context.set_inbound_nonce(&receiver, src_eid, &sender, 2);
61
61
  inbound_as_verified_with_fixed_hash(&context, &receiver, src_eid, &sender, 3);
62
62
  inbound_as_verified_with_fixed_hash(&context, &receiver, src_eid, &sender, 4);
63
63
  inbound_as_verified_with_fixed_hash(&context, &receiver, src_eid, &sender, 5);
64
64
 
65
65
  assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 5);
66
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
66
67
  }
67
68
 
68
69
  #[test]
@@ -75,12 +76,13 @@ fn test_inbound_nonce_stops_at_first_gap() {
75
76
  let src_eid = 2;
76
77
  let sender = BytesN::from_array(env, &[1u8; 32]);
77
78
 
78
- // Start from lazy_nonce = 2 and add payloads at 3 and 5 (gap at 4).
79
- context.set_lazy_inbound_nonce(&receiver, src_eid, &sender, 2);
79
+ // Start from inbound_nonce = 2 and add payloads at 3 and 5 (gap at 4).
80
+ context.set_inbound_nonce(&receiver, src_eid, &sender, 2);
80
81
  inbound_as_verified_with_fixed_hash(&context, &receiver, src_eid, &sender, 3);
81
82
  inbound_as_verified_with_fixed_hash(&context, &receiver, src_eid, &sender, 5);
82
83
 
83
84
  assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 3);
85
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), vec![env, 5u64]);
84
86
  }
85
87
 
86
88
  // Path isolation (receiver/src_eid/sender are isolated)
@@ -97,22 +99,22 @@ fn test_inbound_nonce_isolated_by_path() {
97
99
  let sender_a = BytesN::from_array(env, &[1u8; 32]);
98
100
  let sender_b = BytesN::from_array(env, &[2u8; 32]);
99
101
 
100
- // Path A: lazy 10 + payload at 11 => inbound_nonce 11.
101
- context.set_lazy_inbound_nonce(&receiver_a, src_eid_a, &sender_a, 10);
102
+ // Path A: inbound 10 + payload at 11 => inbound_nonce 11.
103
+ context.set_inbound_nonce(&receiver_a, src_eid_a, &sender_a, 10);
102
104
  inbound_as_verified_with_fixed_hash(&context, &receiver_a, src_eid_a, &sender_a, 11);
103
105
  assert_eq!(endpoint_client.inbound_nonce(&receiver_a, &src_eid_a, &sender_a), 11);
104
106
 
105
107
  // Path B: different receiver => independent.
106
- context.set_lazy_inbound_nonce(&receiver_b, src_eid_a, &sender_a, 20);
108
+ context.set_inbound_nonce(&receiver_b, src_eid_a, &sender_a, 20);
107
109
  assert_eq!(endpoint_client.inbound_nonce(&receiver_b, &src_eid_a, &sender_a), 20);
108
110
 
109
111
  // Path C: different src_eid => independent (two consecutive payloads).
110
- context.set_lazy_inbound_nonce(&receiver_a, src_eid_b, &sender_a, 30);
112
+ context.set_inbound_nonce(&receiver_a, src_eid_b, &sender_a, 30);
111
113
  inbound_as_verified_with_fixed_hash(&context, &receiver_a, src_eid_b, &sender_a, 31);
112
114
  inbound_as_verified_with_fixed_hash(&context, &receiver_a, src_eid_b, &sender_a, 32);
113
115
  assert_eq!(endpoint_client.inbound_nonce(&receiver_a, &src_eid_b, &sender_a), 32);
114
116
 
115
117
  // Path D: different sender => independent.
116
- context.set_lazy_inbound_nonce(&receiver_a, src_eid_a, &sender_b, 40);
118
+ context.set_inbound_nonce(&receiver_a, src_eid_a, &sender_b, 40);
117
119
  assert_eq!(endpoint_client.inbound_nonce(&receiver_a, &src_eid_a, &sender_b), 40);
118
120
  }
@@ -3,7 +3,7 @@ mod clear_payload;
3
3
  mod inbound;
4
4
  mod inbound_nonce;
5
5
  mod inbound_payload_hash;
6
- mod lazy_inbound_nonce;
6
+ mod pending_inbound_nonces;
7
7
  mod next_guid;
8
8
  mod nilify;
9
9
  mod outbound;
@@ -128,9 +128,9 @@ fn test_nilify_success_with_empty_payload() {
128
128
  let sender = BytesN::from_array(env, &[1u8; 32]);
129
129
  let nonce = 2;
130
130
 
131
- // Set lazy nonce to 1 so nonce 2 is greater than lazy nonce.
131
+ // Set inbound nonce to 1 so nonce 2 is the next expected nonce.
132
132
  env.as_contract(&endpoint_client.address, || {
133
- storage::EndpointStorage::set_lazy_inbound_nonce(env, &receiver, src_eid, &sender, &1)
133
+ storage::EndpointStorage::set_inbound_nonce(env, &receiver, src_eid, &sender, &1)
134
134
  });
135
135
 
136
136
  // Nilify with None.
@@ -147,6 +147,7 @@ fn test_nilify_success_with_empty_payload() {
147
147
  let nilified_hash = endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce);
148
148
  let expected_nil_hash = nil_hash(&context);
149
149
  assert_eq!(nilified_hash, Some(expected_nil_hash));
150
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
150
151
  }
151
152
 
152
153
  // Nilify with None counts as "verified" for inbound_nonce (but does not change lazy nonce)
@@ -161,17 +162,53 @@ fn test_nilify_with_none_advances_inbound_nonce_without_changing_lazy_nonce() {
161
162
  let sender = BytesN::from_array(env, &[1u8; 32]);
162
163
 
163
164
  // Initial state.
164
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 0);
165
165
  assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
166
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
166
167
 
167
168
  // Nilify nonce 1 with None (non-verified nonce).
168
169
  nilify_with_auth(&context, &receiver, &receiver, src_eid, &sender, 1, &None);
169
170
 
170
171
  // Nilify writes a payload hash, so inbound_nonce can now advance to 1.
171
172
  assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
173
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
174
+ }
175
+
176
+ #[test]
177
+ fn test_nilify_closes_gap_and_drains_pending_nonces() {
178
+ let context = setup();
179
+ let env = &context.env;
180
+ let endpoint_client = &context.endpoint_client;
172
181
 
173
- // But nilify does not update lazy_inbound_nonce.
174
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 0);
182
+ let receiver = Address::generate(env);
183
+ let src_eid = 2;
184
+ let sender = BytesN::from_array(env, &[1u8; 32]);
185
+
186
+ // Force inbound nonce to 1 and create a gap by verifying nonce 3 first.
187
+ context.set_inbound_nonce(&receiver, src_eid, &sender, 1);
188
+ let payload_hash_3 = BytesN::from_array(env, &[0x33u8; 32]);
189
+ env.as_contract(&endpoint_client.address, || {
190
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 3u64, &payload_hash_3)
191
+ });
192
+
193
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
194
+ assert_eq!(
195
+ endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender),
196
+ soroban_sdk::vec![env, 3u64]
197
+ );
198
+
199
+ // Nilify nonce 2 with None closes the gap and should drain pending to advance inbound nonce to 3.
200
+ nilify_with_auth(&context, &receiver, &receiver, src_eid, &sender, 2u64, &None);
201
+
202
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 3);
203
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
204
+ assert_eq!(
205
+ endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2u64),
206
+ Some(nil_hash(&context))
207
+ );
208
+ assert_eq!(
209
+ endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &3u64),
210
+ Some(payload_hash_3)
211
+ );
175
212
  }
176
213
 
177
214
  // Nilify is allowed when nonce <= lazy nonce if a payload hash exists
@@ -187,14 +224,12 @@ fn test_nilify_allows_when_nonce_is_checkpointed_if_payload_exists() {
187
224
  let nonce = 5u64;
188
225
  let payload_hash = BytesN::from_array(env, &[0xabu8; 32]);
189
226
 
190
- // Checkpoint past the target nonce.
227
+ // Advance inbound nonce past the target nonce and store a payload hash at nonce 5.
191
228
  env.as_contract(&endpoint_client.address, || {
192
- storage::EndpointStorage::set_lazy_inbound_nonce(env, &receiver, src_eid, &sender, &10)
229
+ storage::EndpointStorage::set_inbound_nonce(env, &receiver, src_eid, &sender, &10);
230
+ storage::EndpointStorage::set_inbound_payload_hash(env, &receiver, src_eid, &sender, nonce, &payload_hash);
193
231
  });
194
- assert_eq!(endpoint_client.lazy_inbound_nonce(&receiver, &src_eid, &sender), 10);
195
-
196
- // Store a verified payload hash at nonce 5.
197
- context.inbound_as_verified(&receiver, src_eid, &sender, nonce, &payload_hash);
232
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 10);
198
233
  assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(payload_hash.clone()));
199
234
 
200
235
  // Even though nonce <= lazy_nonce, this should succeed because a payload hash exists.
@@ -430,9 +465,9 @@ fn test_nilify_invalid_nonce_when_already_checkpointed_without_payload() {
430
465
  let sender = BytesN::from_array(env, &[1u8; 32]);
431
466
  let nonce = 1u64;
432
467
 
433
- // Set lazy nonce to 1 and ensure there is NO payload for nonce 1.
468
+ // Set inbound nonce to 1 and ensure there is NO payload for nonce 1.
434
469
  env.as_contract(&endpoint_client.address, || {
435
- storage::EndpointStorage::set_lazy_inbound_nonce(env, &receiver, src_eid, &sender, &1)
470
+ storage::EndpointStorage::set_inbound_nonce(env, &receiver, src_eid, &sender, &1)
436
471
  });
437
472
 
438
473
  let result = try_nilify_with_auth(&context, &receiver, &receiver, src_eid, &sender, nonce, &None);
@@ -0,0 +1,111 @@
1
+ use soroban_sdk::{testutils::Address as _, vec, Address, BytesN};
2
+
3
+ use crate::{endpoint_v2::EndpointV2, tests::endpoint_setup::setup};
4
+
5
+ #[test]
6
+ fn test_pending_inbound_nonces_initially_empty() {
7
+ let context = setup();
8
+ let env = &context.env;
9
+ let endpoint_client = &context.endpoint_client;
10
+
11
+ let receiver = Address::generate(env);
12
+ let src_eid = 2u32;
13
+ let sender = BytesN::from_array(env, &[1u8; 32]);
14
+
15
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
16
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
17
+ }
18
+
19
+ #[test]
20
+ fn test_pending_inbound_nonces_sorted_and_no_duplicates() {
21
+ let context = setup();
22
+ let env = &context.env;
23
+ let endpoint_client = &context.endpoint_client;
24
+
25
+ let receiver = Address::generate(env);
26
+ let src_eid = 2u32;
27
+ let sender = BytesN::from_array(env, &[1u8; 32]);
28
+
29
+ let hash_5 = BytesN::from_array(env, &[0x05u8; 32]);
30
+ env.as_contract(&endpoint_client.address, || {
31
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 5u64, &hash_5)
32
+ });
33
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), vec![env, 5u64]);
34
+
35
+ let hash_2 = BytesN::from_array(env, &[0x02u8; 32]);
36
+ env.as_contract(&endpoint_client.address, || {
37
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 2u64, &hash_2)
38
+ });
39
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), vec![env, 2u64, 5u64]);
40
+
41
+ let hash_4 = BytesN::from_array(env, &[0x04u8; 32]);
42
+ env.as_contract(&endpoint_client.address, || {
43
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 4u64, &hash_4)
44
+ });
45
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), vec![env, 2u64, 4u64, 5u64]);
46
+
47
+ // Re-verify the same nonce should not duplicate it in the pending list.
48
+ let hash_4b = BytesN::from_array(env, &[0x44u8; 32]);
49
+ env.as_contract(&endpoint_client.address, || {
50
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 4u64, &hash_4b)
51
+ });
52
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), vec![env, 2u64, 4u64, 5u64]);
53
+ }
54
+
55
+ #[test]
56
+ fn test_pending_inbound_nonces_drains_when_consecutive_sequence_completed() {
57
+ let context = setup();
58
+ let env = &context.env;
59
+ let endpoint_client = &context.endpoint_client;
60
+
61
+ let receiver = Address::generate(env);
62
+ let src_eid = 2u32;
63
+ let sender = BytesN::from_array(env, &[1u8; 32]);
64
+
65
+ let hash_2 = BytesN::from_array(env, &[0x02u8; 32]);
66
+ let hash_3 = BytesN::from_array(env, &[0x03u8; 32]);
67
+ env.as_contract(&endpoint_client.address, || {
68
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 2u64, &hash_2);
69
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 3u64, &hash_3);
70
+ });
71
+
72
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
73
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), vec![env, 2u64, 3u64]);
74
+
75
+ // Inserting nonce 1 closes the gap, so pending drains and inbound nonce advances to 3.
76
+ let hash_1 = BytesN::from_array(env, &[0x01u8; 32]);
77
+ env.as_contract(&endpoint_client.address, || {
78
+ EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 1u64, &hash_1)
79
+ });
80
+
81
+ assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 3);
82
+ assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
83
+ }
84
+
85
+ #[test]
86
+ fn test_pending_inbound_nonces_isolated_by_path() {
87
+ let context = setup();
88
+ let env = &context.env;
89
+ let endpoint_client = &context.endpoint_client;
90
+
91
+ let receiver_a = Address::generate(env);
92
+ let receiver_b = Address::generate(env);
93
+ let src_eid_a = 2u32;
94
+ let src_eid_b = 3u32;
95
+ let sender_a = BytesN::from_array(env, &[1u8; 32]);
96
+ let sender_b = BytesN::from_array(env, &[2u8; 32]);
97
+
98
+ let hash = BytesN::from_array(env, &[0xabu8; 32]);
99
+ env.as_contract(&endpoint_client.address, || {
100
+ EndpointV2::inbound_for_test(env, &receiver_a, src_eid_a, &sender_a, 2u64, &hash);
101
+ EndpointV2::inbound_for_test(env, &receiver_b, src_eid_a, &sender_a, 2u64, &hash);
102
+ EndpointV2::inbound_for_test(env, &receiver_a, src_eid_b, &sender_a, 2u64, &hash);
103
+ EndpointV2::inbound_for_test(env, &receiver_a, src_eid_a, &sender_b, 2u64, &hash);
104
+ });
105
+
106
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver_a, &src_eid_a, &sender_a), vec![env, 2u64]);
107
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver_b, &src_eid_a, &sender_a), vec![env, 2u64]);
108
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver_a, &src_eid_b, &sender_a), vec![env, 2u64]);
109
+ assert_eq!(endpoint_client.pending_inbound_nonces(&receiver_a, &src_eid_a, &sender_b), vec![env, 2u64]);
110
+ }
111
+