@layerzerolabs/protocol-stellar-v2 0.2.39 → 0.2.41
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.
- package/.turbo/turbo-build.log +226 -313
- package/.turbo/turbo-lint.log +98 -227
- package/.turbo/turbo-test.log +1803 -1954
- package/contracts/common-macros/src/lib.rs +38 -15
- package/contracts/common-macros/src/lz_contract.rs +12 -21
- package/contracts/common-macros/src/tests/lz_contract.rs +17 -8
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__lz_contract__snapshot_generated_lz_contract_code.snap +20 -0
- package/contracts/common-macros/src/upgradeable.rs +37 -30
- package/contracts/endpoint-v2/src/endpoint_v2.rs +4 -3
- package/contracts/endpoint-v2/src/errors.rs +2 -2
- package/contracts/endpoint-v2/src/messaging_channel.rs +11 -0
- package/contracts/endpoint-v2/src/messaging_composer.rs +1 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +12 -25
- package/contracts/endpoint-v2/src/tests/endpoint_v2/initializable.rs +4 -4
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verifiable.rs +50 -10
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verify.rs +6 -35
- package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +2 -2
- package/contracts/endpoint-v2/src/tests/messaging_channel/clear_payload.rs +50 -1
- package/contracts/endpoint-v2/src/tests/messaging_channel/inbound.rs +78 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/insert_and_drain_pending_nonces.rs +272 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/mod.rs +1 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +10 -5
- package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +30 -0
- package/contracts/macro-integration-tests/tests/runtime/oapp/mod.rs +22 -1
- package/contracts/macro-integration-tests/tests/runtime/oapp/oapp_core.rs +13 -11
- package/contracts/macro-integration-tests/tests/runtime/oapp/options_type3.rs +13 -10
- package/contracts/macro-integration-tests/tests/runtime/oapp/receiver.rs +15 -11
- package/contracts/macro-integration-tests/tests/runtime/oapp/sender.rs +3 -2
- package/contracts/macro-integration-tests/tests/runtime/ownable/two_step_transfer.rs +14 -12
- package/contracts/macro-integration-tests/tests/runtime/upgradeable/migrate_guard_and_state.rs +3 -9
- package/contracts/macro-integration-tests/tests/ui/lz_contract/fail/upgradeable_invalid_inner_option.stderr +24 -1
- package/contracts/macro-integration-tests/tests/ui/lz_contract/fail/upgradeable_missing_internal.stderr +3 -3
- package/contracts/macro-integration-tests/tests/ui/lz_contract/pass/upgradeable_rbac.rs +44 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_all.rs +3 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_single_trait.rs +3 -0
- package/contracts/macro-integration-tests/tests/ui/ownable/pass/basic.rs +1 -1
- package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/attr_args.stderr +1 -1
- package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/missing_auth_trait.stderr +2 -2
- package/contracts/macro-integration-tests/tests/ui/upgradeable/fail/missing_upgradeable_internal.stderr +2 -2
- package/contracts/macro-integration-tests/tests/ui/upgradeable/pass/rbac.rs +44 -0
- package/contracts/oapps/counter/integration_tests/utils.rs +5 -3
- package/contracts/oapps/counter/src/tests/mod.rs +16 -1
- package/contracts/oapps/counter/src/tests/test_counter.rs +5 -2
- package/contracts/oapps/oapp/src/oapp_core.rs +21 -7
- package/contracts/oapps/oapp/src/oapp_options_type3.rs +7 -5
- package/contracts/oapps/oapp/src/tests/mod.rs +21 -0
- package/contracts/oapps/oapp/src/tests/oapp_core.rs +12 -10
- package/contracts/oapps/oapp/src/tests/oapp_options_type3.rs +11 -7
- package/contracts/oapps/oapp/src/tests/oapp_receiver.rs +4 -2
- package/contracts/oapps/oapp/src/tests/oapp_sender.rs +3 -2
- package/contracts/oapps/oapp/src/tests/test_macros.rs +15 -0
- package/contracts/oapps/oapp-macros/src/generators.rs +6 -0
- package/contracts/oapps/oapp-macros/src/tests/snapshots/oapp_macros__tests__oapp__snapshot_generate_oapp.snap +15 -0
- package/contracts/oapps/oft/integration-tests/setup.rs +22 -4
- package/contracts/oapps/oft/integration-tests/utils.rs +94 -13
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +23 -10
- package/contracts/oapps/oft/src/extensions/pausable.rs +31 -10
- package/contracts/oapps/oft/src/extensions/rate_limiter.rs +9 -4
- package/contracts/oapps/oft/src/oft.rs +1 -2
- package/contracts/oapps/oft/src/tests/extensions/oft_fee.rs +39 -27
- package/contracts/oapps/oft/src/tests/extensions/pausable.rs +38 -24
- package/contracts/oapps/oft/src/tests/extensions/rate_limiter.rs +87 -69
- package/contracts/oapps/oft-core/integration-tests/setup.rs +27 -3
- package/contracts/oapps/oft-core/src/oft_core.rs +10 -5
- package/contracts/oapps/oft-core/src/tests/test_msg_inspector.rs +20 -20
- package/contracts/oapps/oft-core/src/tests/test_utils.rs +31 -3
- package/contracts/upgrader/src/lib.rs +67 -30
- package/contracts/upgrader/src/tests/test_data/test_upgradeable_contract3.wasm +0 -0
- package/contracts/upgrader/src/tests/test_data/test_upgradeable_contract4.wasm +0 -0
- package/contracts/upgrader/src/tests/test_upgrader.rs +50 -4
- package/contracts/utils/src/ownable.rs +16 -5
- package/contracts/utils/src/tests/ownable.rs +39 -39
- package/contracts/utils/src/upgradeable.rs +60 -17
- package/docs/oapp-guide.md +4 -4
- package/package.json +3 -4
- package/sdk/.turbo/turbo-test.log +381 -366
- package/sdk/dist/generated/bml.d.ts +4 -4
- package/sdk/dist/generated/bml.js +6 -6
- package/sdk/dist/generated/counter.d.ts +158 -12
- package/sdk/dist/generated/counter.js +32 -12
- package/sdk/dist/generated/dvn.d.ts +4 -6
- package/sdk/dist/generated/dvn.js +8 -8
- package/sdk/dist/generated/dvn_fee_lib.d.ts +8 -10
- package/sdk/dist/generated/dvn_fee_lib.js +8 -8
- package/sdk/dist/generated/endpoint.d.ts +9 -9
- package/sdk/dist/generated/endpoint.js +9 -9
- package/sdk/dist/generated/executor.d.ts +9 -11
- package/sdk/dist/generated/executor.js +11 -11
- package/sdk/dist/generated/executor_fee_lib.d.ts +9 -11
- package/sdk/dist/generated/executor_fee_lib.js +11 -11
- package/sdk/dist/generated/executor_helper.d.ts +4 -4
- package/sdk/dist/generated/executor_helper.js +6 -6
- package/sdk/dist/generated/layerzero_view.d.ts +9 -11
- package/sdk/dist/generated/layerzero_view.js +11 -11
- package/sdk/dist/generated/oft.d.ts +194 -27
- package/sdk/dist/generated/oft.js +44 -22
- package/sdk/dist/generated/price_feed.d.ts +8 -10
- package/sdk/dist/generated/price_feed.js +8 -8
- package/sdk/dist/generated/sac_manager.d.ts +8 -8
- package/sdk/dist/generated/sac_manager.js +6 -6
- package/sdk/dist/generated/sml.d.ts +9 -9
- package/sdk/dist/generated/sml.js +9 -9
- package/sdk/dist/generated/treasury.d.ts +9 -9
- package/sdk/dist/generated/treasury.js +9 -9
- package/sdk/dist/generated/uln302.d.ts +9 -9
- package/sdk/dist/generated/uln302.js +9 -9
- package/sdk/dist/generated/upgrader.d.ts +25 -16
- package/sdk/dist/generated/upgrader.js +5 -5
- package/sdk/package.json +1 -1
- package/sdk/test/counter-sml.test.ts +20 -0
- package/sdk/test/counter-uln.test.ts +20 -0
- package/sdk/test/oft-sml.test.ts +22 -0
- package/sdk/test/upgrader.test.ts +1 -0
- package/turbo.json +1 -8
|
@@ -63,35 +63,6 @@ fn test_verify_success() {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
// Storage & Nonce Behavior
|
|
66
|
-
#[test]
|
|
67
|
-
fn test_verify_stores_payload_hash() {
|
|
68
|
-
let context = setup();
|
|
69
|
-
let env = &context.env;
|
|
70
|
-
let endpoint_client = &context.endpoint_client;
|
|
71
|
-
|
|
72
|
-
let src_eid = 2u32;
|
|
73
|
-
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
74
|
-
let receiver = env.register(MockReceiver, ());
|
|
75
|
-
let nonce = 1u64;
|
|
76
|
-
|
|
77
|
-
// Setup receive library
|
|
78
|
-
let receive_lib = context.setup_default_receive_lib(src_eid, 0);
|
|
79
|
-
|
|
80
|
-
// Create payload hash
|
|
81
|
-
let payload_hash = default_payload_hash(env);
|
|
82
|
-
|
|
83
|
-
let origin = Origin { src_eid, sender: sender.clone(), nonce };
|
|
84
|
-
|
|
85
|
-
// Mock auth for receive_lib
|
|
86
|
-
context.mock_auth(&receive_lib, "verify", (&receive_lib, &origin, &receiver, &payload_hash));
|
|
87
|
-
|
|
88
|
-
endpoint_client.verify(&receive_lib, &origin, &receiver, &payload_hash);
|
|
89
|
-
|
|
90
|
-
// Verify inbound payload hash was stored
|
|
91
|
-
let stored_hash = endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce);
|
|
92
|
-
assert_eq!(stored_hash, Some(payload_hash));
|
|
93
|
-
}
|
|
94
|
-
|
|
95
66
|
#[test]
|
|
96
67
|
fn test_verify_overwrites_payload_hash_for_same_nonce() {
|
|
97
68
|
let context = setup();
|
|
@@ -262,7 +233,7 @@ fn test_verify_path_not_initializable() {
|
|
|
262
233
|
context.mock_auth(&receive_lib, "verify", (&receive_lib, &origin, &receiver, &payload_hash));
|
|
263
234
|
|
|
264
235
|
// Library validation passes (receive_lib matches default)
|
|
265
|
-
// initializable checks:
|
|
236
|
+
// initializable checks: inbound_nonce == 0, and receiver.allow_initialize_path(origin) == false
|
|
266
237
|
// So initializable returns false, should panic with PathNotInitializable error
|
|
267
238
|
let result = endpoint_client.try_verify(&receive_lib, &origin, &receiver, &payload_hash);
|
|
268
239
|
assert_eq!(result.err().unwrap().ok().unwrap(), EndpointError::PathNotInitializable.into());
|
|
@@ -281,7 +252,7 @@ fn test_verify_path_not_verifiable() {
|
|
|
281
252
|
// Setup receive library and set as default (so library validation passes)
|
|
282
253
|
let receive_lib = context.setup_default_receive_lib(src_eid, 0);
|
|
283
254
|
|
|
284
|
-
// Skip nonce 1 to
|
|
255
|
+
// Skip nonce 1 to advance inbound_nonce to 1 (so initializable passes: inbound_nonce > 0)
|
|
285
256
|
context.mock_auth(&receiver, "skip", (&receiver, &receiver, &src_eid, &sender, &1u64));
|
|
286
257
|
endpoint_client.skip(&receiver, &receiver, &src_eid, &sender, &1);
|
|
287
258
|
|
|
@@ -291,15 +262,15 @@ fn test_verify_path_not_verifiable() {
|
|
|
291
262
|
let payload = build_payload(env, &guid, &message);
|
|
292
263
|
let payload_hash = keccak256(env, &payload);
|
|
293
264
|
|
|
294
|
-
// Try to verify nonce 1, but
|
|
295
|
-
// verifiable checks: nonce >
|
|
265
|
+
// Try to verify nonce 1, but inbound_nonce is already 1, so nonce 1 is not verifiable
|
|
266
|
+
// verifiable checks: nonce > inbound_nonce (1 > 1 is false) OR has payload hash (false)
|
|
296
267
|
let origin = Origin { src_eid, sender, nonce: 1 };
|
|
297
268
|
|
|
298
269
|
// Mock auth for receive_lib
|
|
299
270
|
context.mock_auth(&receive_lib, "verify", (&receive_lib, &origin, &receiver, &payload_hash));
|
|
300
271
|
|
|
301
|
-
// Library validation passes, initializable passes (
|
|
302
|
-
// verifiable fails (nonce <=
|
|
272
|
+
// Library validation passes, initializable passes (inbound_nonce > 0)
|
|
273
|
+
// verifiable fails (nonce <= inbound_nonce and no payload hash), should panic with PathNotVerifiable error
|
|
303
274
|
let result = endpoint_client.try_verify(&receive_lib, &origin, &receiver, &payload_hash);
|
|
304
275
|
assert_eq!(result.err().unwrap().ok().unwrap(), EndpointError::PathNotVerifiable.into());
|
|
305
276
|
}
|
|
@@ -258,9 +258,9 @@ fn test_burn_payload_hash_not_found_when_storage_none() {
|
|
|
258
258
|
assert_eq!(result.err().unwrap().ok().unwrap(), EndpointError::PayloadHashNotFound.into());
|
|
259
259
|
}
|
|
260
260
|
|
|
261
|
-
// Failure when nonce is greater than
|
|
261
|
+
// Failure when nonce is greater than `inbound_nonce`
|
|
262
262
|
#[test]
|
|
263
|
-
fn
|
|
263
|
+
fn test_burn_invalid_nonce_when_greater_than_inbound_nonce() {
|
|
264
264
|
let context = setup();
|
|
265
265
|
let env = &context.env;
|
|
266
266
|
|
|
@@ -52,6 +52,8 @@ fn test_clear_payload_success() {
|
|
|
52
52
|
let payload_hash = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, nonce, &payload);
|
|
53
53
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(payload_hash.clone()));
|
|
54
54
|
|
|
55
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), nonce);
|
|
56
|
+
|
|
55
57
|
clear_payload(&context, &receiver, src_eid, &sender, nonce, &payload);
|
|
56
58
|
|
|
57
59
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), None);
|
|
@@ -88,6 +90,42 @@ fn test_clear_payload_keeps_other_payload_hashes_intact() {
|
|
|
88
90
|
let _ = hash3; // hash3 is only used to ensure it was computed and stored for nonce 3.
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
#[test]
|
|
94
|
+
fn test_clear_payload_does_not_change_pending_inbound_nonces() {
|
|
95
|
+
let context = setup();
|
|
96
|
+
let env = &context.env;
|
|
97
|
+
let endpoint_client = &context.endpoint_client;
|
|
98
|
+
|
|
99
|
+
let receiver = Address::generate(env);
|
|
100
|
+
let src_eid = 2;
|
|
101
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
102
|
+
|
|
103
|
+
// Verify 1 and 2 consecutively, then verify 4 out-of-order.
|
|
104
|
+
// This produces: inbound_nonce = 2, pending_inbound_nonces = [4].
|
|
105
|
+
let payload1 = Bytes::from_array(env, &[0x01]);
|
|
106
|
+
let payload2 = Bytes::from_array(env, &[0x02]);
|
|
107
|
+
let payload4 = Bytes::from_array(env, &[0x04]);
|
|
108
|
+
|
|
109
|
+
let hash1 = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 1, &payload1);
|
|
110
|
+
let hash2 = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 2, &payload2);
|
|
111
|
+
let hash4 = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 4, &payload4);
|
|
112
|
+
|
|
113
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
114
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 4u64]);
|
|
115
|
+
|
|
116
|
+
// Clear nonce 2. This must not affect pending nonces (which are > inbound_nonce).
|
|
117
|
+
clear_payload(&context, &receiver, src_eid, &sender, 2, &payload2);
|
|
118
|
+
|
|
119
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
120
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 4u64]);
|
|
121
|
+
|
|
122
|
+
// Only nonce 2 is cleared; others remain.
|
|
123
|
+
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2), None);
|
|
124
|
+
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &1), Some(hash1));
|
|
125
|
+
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &4), Some(hash4));
|
|
126
|
+
let _ = hash2; // hash2 is only used to ensure it was computed and stored for nonce 2.
|
|
127
|
+
}
|
|
128
|
+
|
|
91
129
|
// Clearing a nonce <= inbound nonce does not update inbound nonce
|
|
92
130
|
#[test]
|
|
93
131
|
fn test_clear_payload_does_not_update_inbound_nonce_when_nonce_is_not_greater() {
|
|
@@ -132,7 +170,7 @@ fn test_clear_payload_payload_hash_not_found_when_nonce_is_checkpointed_but_miss
|
|
|
132
170
|
let src_eid = 2;
|
|
133
171
|
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
134
172
|
|
|
135
|
-
// nonce <=
|
|
173
|
+
// nonce <= inbound_nonce, so clear_payload will NOT run the "has_payload for all intermediate nonces" check.
|
|
136
174
|
// It should fail at the payload hash check instead.
|
|
137
175
|
env.as_contract(&endpoint_client.address, || {
|
|
138
176
|
storage::EndpointStorage::set_inbound_nonce(env, &receiver, src_eid, &sender, &5u64)
|
|
@@ -186,6 +224,7 @@ fn test_clear_payload_not_stored() {
|
|
|
186
224
|
fn test_clear_payload_missing_intermediate_nonce() {
|
|
187
225
|
let context = setup();
|
|
188
226
|
let env = &context.env;
|
|
227
|
+
let endpoint_client = &context.endpoint_client;
|
|
189
228
|
|
|
190
229
|
let receiver = Address::generate(env);
|
|
191
230
|
let src_eid = 2;
|
|
@@ -198,9 +237,19 @@ fn test_clear_payload_missing_intermediate_nonce() {
|
|
|
198
237
|
let _ = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 1, &payload1);
|
|
199
238
|
let _ = inbound_as_verified_from_payload(&context, &receiver, src_eid, &sender, 3, &payload3);
|
|
200
239
|
|
|
240
|
+
// inbound_nonce should be 1 (nonce 2 is missing), and nonce 3 should be pending.
|
|
241
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
242
|
+
assert_eq!(
|
|
243
|
+
endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender),
|
|
244
|
+
soroban_sdk::vec![env, 3u64]
|
|
245
|
+
);
|
|
246
|
+
|
|
201
247
|
// Clearing nonce 1 succeeds.
|
|
202
248
|
clear_payload(&context, &receiver, src_eid, &sender, 1, &payload1);
|
|
203
249
|
|
|
250
|
+
// clear_payload does not advance inbound_nonce; it remains 1.
|
|
251
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
252
|
+
|
|
204
253
|
// Try to clear nonce 3 - should panic because inbound_nonce is still 1 (nonce 3 is out-of-order).
|
|
205
254
|
clear_payload(&context, &receiver, src_eid, &sender, 3, &payload3);
|
|
206
255
|
}
|
|
@@ -123,6 +123,36 @@ fn test_inbound_out_of_order_populates_pending_and_drains_when_gap_closed() {
|
|
|
123
123
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2u64), Some(hash_2));
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
#[test]
|
|
127
|
+
fn test_inbound_when_nonce_already_pending_does_not_advance_inbound_nonce() {
|
|
128
|
+
let context = setup();
|
|
129
|
+
let env = &context.env;
|
|
130
|
+
let endpoint_client = &context.endpoint_client;
|
|
131
|
+
|
|
132
|
+
let receiver = Address::generate(env);
|
|
133
|
+
let src_eid = 2;
|
|
134
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
135
|
+
|
|
136
|
+
// First inbound at nonce 2 adds it to pending (gap at nonce 1).
|
|
137
|
+
let hash_2_a = BytesN::from_array(env, &[0x22u8; 32]);
|
|
138
|
+
env.as_contract(&endpoint_client.address, || {
|
|
139
|
+
EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 2u64, &hash_2_a)
|
|
140
|
+
});
|
|
141
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
142
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 2u64]);
|
|
143
|
+
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2u64), Some(hash_2_a.clone()));
|
|
144
|
+
|
|
145
|
+
// Re-inbound at the same nonce should overwrite the payload hash, but should NOT duplicate pending
|
|
146
|
+
// entries nor advance inbound_nonce (gap at nonce 1 still exists).
|
|
147
|
+
let hash_2_b = BytesN::from_array(env, &[0x23u8; 32]);
|
|
148
|
+
env.as_contract(&endpoint_client.address, || {
|
|
149
|
+
EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 2u64, &hash_2_b)
|
|
150
|
+
});
|
|
151
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
152
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 2u64]);
|
|
153
|
+
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2u64), Some(hash_2_b));
|
|
154
|
+
}
|
|
155
|
+
|
|
126
156
|
#[test]
|
|
127
157
|
#[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
|
|
128
158
|
fn test_inbound_rejects_nonce_beyond_pending_window() {
|
|
@@ -141,6 +171,52 @@ fn test_inbound_rejects_nonce_beyond_pending_window() {
|
|
|
141
171
|
});
|
|
142
172
|
}
|
|
143
173
|
|
|
174
|
+
#[test]
|
|
175
|
+
fn test_inbound_accepts_upper_bound_when_inbound_nonce_nonzero() {
|
|
176
|
+
let context = setup();
|
|
177
|
+
let env = &context.env;
|
|
178
|
+
let endpoint_client = &context.endpoint_client;
|
|
179
|
+
|
|
180
|
+
let receiver = Address::generate(env);
|
|
181
|
+
let src_eid = 2;
|
|
182
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
183
|
+
|
|
184
|
+
// Set inbound_nonce to 100 so the upper bound is 356 (= 100 + 256).
|
|
185
|
+
context.set_inbound_nonce(&receiver, src_eid, &sender, 100);
|
|
186
|
+
|
|
187
|
+
let hash = BytesN::from_array(env, &[0xabu8; 32]);
|
|
188
|
+
env.as_contract(&endpoint_client.address, || {
|
|
189
|
+
EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 356u64, &hash)
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Nonce 356 is not consecutive to 100, so it stays pending and inbound_nonce does not advance.
|
|
193
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 100);
|
|
194
|
+
assert_eq!(
|
|
195
|
+
endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender),
|
|
196
|
+
soroban_sdk::vec![env, 356u64]
|
|
197
|
+
);
|
|
198
|
+
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &356u64), Some(hash));
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
#[test]
|
|
202
|
+
#[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
|
|
203
|
+
fn test_inbound_rejects_beyond_upper_bound_when_inbound_nonce_nonzero() {
|
|
204
|
+
let context = setup();
|
|
205
|
+
let env = &context.env;
|
|
206
|
+
let endpoint_client = &context.endpoint_client;
|
|
207
|
+
|
|
208
|
+
let receiver = Address::generate(env);
|
|
209
|
+
let src_eid = 2;
|
|
210
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
211
|
+
|
|
212
|
+
context.set_inbound_nonce(&receiver, src_eid, &sender, 100);
|
|
213
|
+
|
|
214
|
+
let hash = BytesN::from_array(env, &[0xabu8; 32]);
|
|
215
|
+
env.as_contract(&endpoint_client.address, || {
|
|
216
|
+
EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 357u64, &hash)
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
144
220
|
#[test]
|
|
145
221
|
#[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
|
|
146
222
|
fn test_inbound_rejects_reverify_when_nonce_leq_inbound_and_payload_missing() {
|
|
@@ -184,4 +260,6 @@ fn test_inbound_allows_reverify_when_nonce_leq_inbound_and_payload_exists() {
|
|
|
184
260
|
EndpointV2::inbound_for_test(env, &receiver, src_eid, &sender, 1u64, &new_hash)
|
|
185
261
|
});
|
|
186
262
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &1u64), Some(new_hash));
|
|
263
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
264
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
187
265
|
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
use soroban_sdk::{testutils::Address as _, Address, BytesN};
|
|
2
|
+
|
|
3
|
+
use crate::{endpoint_v2::EndpointV2, tests::endpoint_setup::setup};
|
|
4
|
+
|
|
5
|
+
fn insert_and_drain(
|
|
6
|
+
context: &crate::tests::endpoint_setup::TestSetup,
|
|
7
|
+
receiver: &Address,
|
|
8
|
+
src_eid: u32,
|
|
9
|
+
sender: &BytesN<32>,
|
|
10
|
+
nonce: u64,
|
|
11
|
+
) {
|
|
12
|
+
let env = &context.env;
|
|
13
|
+
let endpoint_client = &context.endpoint_client;
|
|
14
|
+
env.as_contract(&endpoint_client.address, || {
|
|
15
|
+
EndpointV2::insert_and_drain_pending_nonces_for_test(env, receiver, src_eid, sender, nonce)
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
#[test]
|
|
20
|
+
fn test_insert_and_drain_inserts_sorted_and_dedupes() {
|
|
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
|
+
// Start with inbound_nonce = 0.
|
|
30
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
31
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
32
|
+
|
|
33
|
+
// Insert out of order: 3 then 2. Pending should remain sorted.
|
|
34
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 3);
|
|
35
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 2);
|
|
36
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
37
|
+
assert_eq!(
|
|
38
|
+
endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender),
|
|
39
|
+
soroban_sdk::vec![env, 2u64, 3u64]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
// Re-inserting an already pending nonce should be a no-op (no duplicates, no drain).
|
|
43
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 2);
|
|
44
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
45
|
+
assert_eq!(
|
|
46
|
+
endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender),
|
|
47
|
+
soroban_sdk::vec![env, 2u64, 3u64]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 1);
|
|
51
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 3);
|
|
52
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[test]
|
|
56
|
+
fn test_insert_and_drain_drains_consecutive_sequence() {
|
|
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
|
+
// Insert 2 and 3, then insert 1. Inserting 1 should drain 1,2,3 and advance inbound_nonce to 3.
|
|
66
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 2);
|
|
67
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 3);
|
|
68
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
69
|
+
assert_eq!(
|
|
70
|
+
endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender),
|
|
71
|
+
soroban_sdk::vec![env, 2u64, 3u64]
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 1);
|
|
75
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 3);
|
|
76
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#[test]
|
|
80
|
+
fn test_insert_and_drain_leaves_nonconsecutive_tail_pending() {
|
|
81
|
+
let context = setup();
|
|
82
|
+
let env = &context.env;
|
|
83
|
+
let endpoint_client = &context.endpoint_client;
|
|
84
|
+
|
|
85
|
+
let receiver = Address::generate(env);
|
|
86
|
+
let src_eid = 2u32;
|
|
87
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
88
|
+
|
|
89
|
+
// Insert 2 and 4, then insert 1. Drain should advance to 2 but leave 4 pending (gap at 3).
|
|
90
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 2);
|
|
91
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 4);
|
|
92
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 1);
|
|
93
|
+
|
|
94
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
95
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 4u64]);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#[test]
|
|
99
|
+
fn test_insert_and_drain_accepts_upper_bound_when_inbound_nonce_zero() {
|
|
100
|
+
let context = setup();
|
|
101
|
+
let env = &context.env;
|
|
102
|
+
let endpoint_client = &context.endpoint_client;
|
|
103
|
+
|
|
104
|
+
let receiver = Address::generate(env);
|
|
105
|
+
let src_eid = 2u32;
|
|
106
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
107
|
+
|
|
108
|
+
// inbound_nonce == 0: 256 is allowed.
|
|
109
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 256);
|
|
110
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
111
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 256u64]);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
#[test]
|
|
115
|
+
#[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
|
|
116
|
+
fn test_insert_and_drain_rejects_beyond_upper_bound_when_inbound_nonce_zero() {
|
|
117
|
+
let context = setup();
|
|
118
|
+
let env = &context.env;
|
|
119
|
+
|
|
120
|
+
let receiver = Address::generate(env);
|
|
121
|
+
let src_eid = 2u32;
|
|
122
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
123
|
+
|
|
124
|
+
// inbound_nonce == 0: 257 is out of range (max is 256).
|
|
125
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 257);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
#[test]
|
|
129
|
+
#[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
|
|
130
|
+
fn test_insert_and_drain_rejects_nonce_leq_inbound_nonce() {
|
|
131
|
+
let context = setup();
|
|
132
|
+
let env = &context.env;
|
|
133
|
+
let endpoint_client = &context.endpoint_client;
|
|
134
|
+
|
|
135
|
+
let receiver = Address::generate(env);
|
|
136
|
+
let src_eid = 2u32;
|
|
137
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
138
|
+
|
|
139
|
+
// Advance inbound_nonce to 5 via storage utility.
|
|
140
|
+
context.set_inbound_nonce(&receiver, src_eid, &sender, 5);
|
|
141
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 5);
|
|
142
|
+
|
|
143
|
+
// Calling with new_nonce == inbound_nonce should panic InvalidNonce.
|
|
144
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 5);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
#[test]
|
|
148
|
+
fn test_insert_and_drain_accepts_upper_bound_when_inbound_nonce_nonzero() {
|
|
149
|
+
let context = setup();
|
|
150
|
+
let env = &context.env;
|
|
151
|
+
let endpoint_client = &context.endpoint_client;
|
|
152
|
+
|
|
153
|
+
let receiver = Address::generate(env);
|
|
154
|
+
let src_eid = 2u32;
|
|
155
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
156
|
+
|
|
157
|
+
// inbound_nonce == 100: upper bound is 356 (= 100 + 256).
|
|
158
|
+
context.set_inbound_nonce(&receiver, src_eid, &sender, 100);
|
|
159
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 356);
|
|
160
|
+
|
|
161
|
+
// Not consecutive to 100, so it remains pending and inbound_nonce does not advance.
|
|
162
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 100);
|
|
163
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 356u64]);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
#[test]
|
|
167
|
+
#[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
|
|
168
|
+
fn test_insert_and_drain_rejects_beyond_upper_bound_when_inbound_nonce_nonzero() {
|
|
169
|
+
let context = setup();
|
|
170
|
+
let env = &context.env;
|
|
171
|
+
|
|
172
|
+
let receiver = Address::generate(env);
|
|
173
|
+
let src_eid = 2u32;
|
|
174
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
175
|
+
|
|
176
|
+
context.set_inbound_nonce(&receiver, src_eid, &sender, 100);
|
|
177
|
+
|
|
178
|
+
// inbound_nonce == 100: 357 is out of range (max is 356).
|
|
179
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 357);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
#[test]
|
|
183
|
+
fn test_insert_and_drain_drains_across_existing_pending_tail_when_inbound_nonce_nonzero() {
|
|
184
|
+
let context = setup();
|
|
185
|
+
let env = &context.env;
|
|
186
|
+
let endpoint_client = &context.endpoint_client;
|
|
187
|
+
|
|
188
|
+
let receiver = Address::generate(env);
|
|
189
|
+
let src_eid = 2u32;
|
|
190
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
191
|
+
|
|
192
|
+
// Set inbound_nonce to 2.
|
|
193
|
+
context.set_inbound_nonce(&receiver, src_eid, &sender, 2);
|
|
194
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
195
|
+
|
|
196
|
+
// Insert 4 first -> pending [4], inbound_nonce remains 2 (missing 3).
|
|
197
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 4);
|
|
198
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
199
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 4u64]);
|
|
200
|
+
|
|
201
|
+
// Insert 3 -> should drain 3 then 4 and advance inbound_nonce to 4.
|
|
202
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 3);
|
|
203
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 4);
|
|
204
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
#[test]
|
|
208
|
+
fn test_insert_and_drain_drains_single_next_nonce_when_no_pending() {
|
|
209
|
+
let context = setup();
|
|
210
|
+
let env = &context.env;
|
|
211
|
+
let endpoint_client = &context.endpoint_client;
|
|
212
|
+
|
|
213
|
+
let receiver = Address::generate(env);
|
|
214
|
+
let src_eid = 2u32;
|
|
215
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
216
|
+
|
|
217
|
+
context.set_inbound_nonce(&receiver, src_eid, &sender, 5);
|
|
218
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 5);
|
|
219
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
220
|
+
|
|
221
|
+
// Insert exactly inbound_nonce + 1. This should drain immediately and leave pending empty.
|
|
222
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 6);
|
|
223
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 6);
|
|
224
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#[test]
|
|
228
|
+
fn test_insert_and_drain_window_holds_255_when_inbound_plus_one_missing_then_drains() {
|
|
229
|
+
let context = setup();
|
|
230
|
+
let env = &context.env;
|
|
231
|
+
let endpoint_client = &context.endpoint_client;
|
|
232
|
+
|
|
233
|
+
let receiver = Address::generate(env);
|
|
234
|
+
let src_eid = 2u32;
|
|
235
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
236
|
+
|
|
237
|
+
// Fill the pending window with 2..=256 while nonce 1 is still missing.
|
|
238
|
+
// This creates 255 pending nonces and inbound_nonce remains 0.
|
|
239
|
+
for nonce in 2u64..=256u64 {
|
|
240
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, nonce);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
244
|
+
let pending = endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender);
|
|
245
|
+
assert_eq!(pending.len(), 255);
|
|
246
|
+
assert_eq!(pending.get(0).unwrap(), 2u64);
|
|
247
|
+
assert_eq!(pending.get(254).unwrap(), 256u64);
|
|
248
|
+
|
|
249
|
+
// Inserting nonce 1 closes the gap and drains the entire window.
|
|
250
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 1);
|
|
251
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 256);
|
|
252
|
+
assert!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender).is_empty());
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
#[test]
|
|
256
|
+
#[should_panic(expected = "Error(Contract, #11)")] // EndpointError::InvalidNonce
|
|
257
|
+
fn test_insert_and_drain_rejects_257_when_missing_inbound_plus_one() {
|
|
258
|
+
let context = setup();
|
|
259
|
+
let env = &context.env;
|
|
260
|
+
|
|
261
|
+
let receiver = Address::generate(env);
|
|
262
|
+
let src_eid = 2u32;
|
|
263
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
264
|
+
|
|
265
|
+
// Keep inbound_nonce at 0 and fill 2..=256 into pending.
|
|
266
|
+
for nonce in 2u64..=256u64 {
|
|
267
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, nonce);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 257 is outside the allowed range (0, 256] while inbound_nonce is still 0.
|
|
271
|
+
insert_and_drain(&context, &receiver, src_eid, &sender, 257);
|
|
272
|
+
}
|
|
@@ -92,6 +92,7 @@ fn test_nilify_success_with_stored_payload() {
|
|
|
92
92
|
|
|
93
93
|
// Verify payload hash is stored.
|
|
94
94
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(payload_hash.clone()));
|
|
95
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
95
96
|
|
|
96
97
|
// Nilify the payload.
|
|
97
98
|
let payload_hash_opt = Some(payload_hash.clone());
|
|
@@ -114,9 +115,10 @@ fn test_nilify_success_with_stored_payload() {
|
|
|
114
115
|
let nilified_hash = endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce);
|
|
115
116
|
let expected_nil_hash = nil_hash(&context);
|
|
116
117
|
assert_eq!(nilified_hash, Some(expected_nil_hash));
|
|
118
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
117
119
|
}
|
|
118
120
|
|
|
119
|
-
// Successful nilify with None (
|
|
121
|
+
// Successful nilify with None (no existing payload hash) when nonce > inbound_nonce
|
|
120
122
|
#[test]
|
|
121
123
|
fn test_nilify_success_with_empty_payload() {
|
|
122
124
|
let context = setup();
|
|
@@ -150,9 +152,9 @@ fn test_nilify_success_with_empty_payload() {
|
|
|
150
152
|
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
151
153
|
}
|
|
152
154
|
|
|
153
|
-
// Nilify with None counts
|
|
155
|
+
// Nilify with None counts toward `inbound_nonce` advancement (via pending-nonce draining).
|
|
154
156
|
#[test]
|
|
155
|
-
fn
|
|
157
|
+
fn test_nilify_with_none_advances_inbound_nonce_without_changing_payload_hashes() {
|
|
156
158
|
let context = setup();
|
|
157
159
|
let env = &context.env;
|
|
158
160
|
let endpoint_client = &context.endpoint_client;
|
|
@@ -211,7 +213,7 @@ fn test_nilify_closes_gap_and_drains_pending_nonces() {
|
|
|
211
213
|
);
|
|
212
214
|
}
|
|
213
215
|
|
|
214
|
-
// Nilify is allowed when nonce <=
|
|
216
|
+
// Nilify is allowed when nonce <= inbound_nonce if an `inbound_payload_hash` exists
|
|
215
217
|
#[test]
|
|
216
218
|
fn test_nilify_allows_when_nonce_is_checkpointed_if_payload_exists() {
|
|
217
219
|
let context = setup();
|
|
@@ -232,7 +234,7 @@ fn test_nilify_allows_when_nonce_is_checkpointed_if_payload_exists() {
|
|
|
232
234
|
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 10);
|
|
233
235
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(payload_hash.clone()));
|
|
234
236
|
|
|
235
|
-
// Even though nonce <=
|
|
237
|
+
// Even though nonce <= inbound_nonce, this should succeed because a payload hash exists.
|
|
236
238
|
let payload_hash_opt = Some(payload_hash.clone());
|
|
237
239
|
nilify_with_auth(&context, &receiver, &receiver, src_eid, &sender, nonce, &payload_hash_opt);
|
|
238
240
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(nil_hash(&context)));
|
|
@@ -253,16 +255,19 @@ fn test_nilify_allows_repeated_call_when_already_nilified() {
|
|
|
253
255
|
|
|
254
256
|
// First nilify: verified payload hash -> nil hash.
|
|
255
257
|
context.inbound_as_verified(&receiver, src_eid, &sender, nonce, &payload_hash);
|
|
258
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
256
259
|
let payload_hash_opt = Some(payload_hash);
|
|
257
260
|
nilify_with_auth(&context, &receiver, &receiver, src_eid, &sender, nonce, &payload_hash_opt);
|
|
258
261
|
|
|
259
262
|
let nil = nil_hash(&context);
|
|
260
263
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(nil.clone()));
|
|
264
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
261
265
|
|
|
262
266
|
// Second nilify: passing the current stored nil hash should succeed.
|
|
263
267
|
let nil_opt = Some(nil.clone());
|
|
264
268
|
nilify_with_auth(&context, &receiver, &receiver, src_eid, &sender, nonce, &nil_opt);
|
|
265
269
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce), Some(nil));
|
|
270
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 1);
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
// Delegate authorization (delegate(receiver) is allowed)
|
|
@@ -248,6 +248,36 @@ fn test_skip_closes_gap_and_advances_inbound_nonce() {
|
|
|
248
248
|
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2), Some(payload_hash_2));
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
+
// Skipping a missing nonce can close *part* of the gap while leaving later gaps pending.
|
|
252
|
+
#[test]
|
|
253
|
+
fn test_skip_closes_gap_but_pending_inbound_nonces_not_empty() {
|
|
254
|
+
let context = setup();
|
|
255
|
+
let env = &context.env;
|
|
256
|
+
let endpoint_client = &context.endpoint_client;
|
|
257
|
+
|
|
258
|
+
let receiver = Address::generate(env);
|
|
259
|
+
let src_eid = 2;
|
|
260
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
261
|
+
|
|
262
|
+
// Verify nonce 2 and 4 out-of-order. inbound_nonce stays 0 because nonce 1 is missing, and
|
|
263
|
+
// pending list contains [2, 4] (gap at 1 and 3).
|
|
264
|
+
let payload_hash_2 = BytesN::from_array(env, &[0x11u8; 32]);
|
|
265
|
+
let payload_hash_4 = BytesN::from_array(env, &[0x22u8; 32]);
|
|
266
|
+
context.inbound_as_verified(&receiver, src_eid, &sender, 2, &payload_hash_2);
|
|
267
|
+
context.inbound_as_verified(&receiver, src_eid, &sender, 4, &payload_hash_4);
|
|
268
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 0);
|
|
269
|
+
|
|
270
|
+
// Skip nonce 1 closes the first gap and drains consecutive pending nonces up to 2,
|
|
271
|
+
// but nonce 4 remains pending because nonce 3 is still missing.
|
|
272
|
+
skip_with_auth(&context, &receiver, &receiver, src_eid, &sender, 1);
|
|
273
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
274
|
+
assert_eq!(endpoint_client.pending_inbound_nonces(&receiver, &src_eid, &sender), soroban_sdk::vec![env, 4u64]);
|
|
275
|
+
|
|
276
|
+
// Payload hashes remain intact.
|
|
277
|
+
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &2), Some(payload_hash_2));
|
|
278
|
+
assert_eq!(endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &4), Some(payload_hash_4));
|
|
279
|
+
}
|
|
280
|
+
|
|
251
281
|
// Repeated skip of the same nonce is rejected
|
|
252
282
|
#[test]
|
|
253
283
|
fn test_skip_rejects_repeated_same_nonce() {
|