@layerzerolabs/protocol-stellar-v2 0.2.40 → 0.2.43
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 +312 -397
- package/.turbo/turbo-lint.log +185 -245
- package/.turbo/turbo-test.log +1846 -1942
- package/Cargo.lock +22 -127
- package/Cargo.toml +4 -6
- 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 +25 -6
- 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 +5 -3
- package/contracts/macro-integration-tests/tests/runtime/ownable/mod.rs +1 -1
- 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/fail/missing_auth_trait.rs +28 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_auth_trait.stderr +397 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_lz_receive_internal.rs +1 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/fail/missing_lz_receive_internal.stderr +10 -10
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_all.rs +4 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/custom_single_trait.rs +7 -0
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/minimal_contract.rs +5 -4
- package/contracts/macro-integration-tests/tests/ui/oapp/pass/struct_with_fields.rs +2 -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/counter.rs +4 -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 +22 -8
- 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 +14 -11
- package/contracts/oapps/oapp/src/tests/oapp_options_type3.rs +17 -10
- package/contracts/oapps/oapp/src/tests/oapp_receiver.rs +6 -3
- package/contracts/oapps/oapp/src/tests/oapp_sender.rs +5 -3
- package/contracts/oapps/oapp/src/tests/test_macros.rs +25 -0
- package/contracts/oapps/oapp-macros/src/generators.rs +12 -9
- package/contracts/oapps/oapp-macros/src/lib.rs +1 -1
- package/contracts/oapps/oapp-macros/src/tests/snapshots/oapp_macros__tests__oapp__snapshot_generate_oapp.snap +15 -7
- 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 +3 -3
- 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/src/tests/oft_types/lock_unlock.rs +1 -0
- package/contracts/oapps/oft-core/integration-tests/setup.rs +28 -3
- package/contracts/oapps/oft-core/src/oft_core.rs +11 -6
- package/contracts/oapps/oft-core/src/tests/test_msg_inspector.rs +20 -20
- package/contracts/oapps/oft-core/src/tests/test_utils.rs +33 -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 +18 -13
- package/package.json +5 -5
- package/sdk/.turbo/turbo-test.log +359 -348
- package/sdk/dist/generated/bml.d.ts +4 -4
- package/sdk/dist/generated/bml.js +6 -6
- package/sdk/dist/generated/counter.d.ts +269 -123
- package/sdk/dist/generated/counter.js +45 -25
- 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 +323 -156
- package/sdk/dist/generated/oft.js +65 -43
- 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/ts-bindings-gen.toml +67 -0
- package/turbo.json +1 -8
- package/tools/ts-bindings-gen/Cargo.toml +0 -16
- package/tools/ts-bindings-gen/src/main.rs +0 -214
|
@@ -4,7 +4,7 @@ use proc_macro2::TokenStream;
|
|
|
4
4
|
use quote::quote;
|
|
5
5
|
use syn::{
|
|
6
6
|
parse::{Parse, ParseStream},
|
|
7
|
-
Ident, ItemStruct,
|
|
7
|
+
Ident, ItemStruct, Token,
|
|
8
8
|
};
|
|
9
9
|
|
|
10
10
|
/// Configuration options for the `#[upgradeable]` macro.
|
|
@@ -13,51 +13,52 @@ pub struct UpgradeableConfig {
|
|
|
13
13
|
/// If true, generates a default no-op `UpgradeableInternal` implementation.
|
|
14
14
|
/// Use this for initial deployments when no migration logic is needed yet.
|
|
15
15
|
pub no_migration: bool,
|
|
16
|
+
/// If true, uses `UpgradeableRbac` (Auth + RoleBased) instead of `Upgradeable` (Auth only).
|
|
17
|
+
pub rbac: bool,
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
impl Parse for UpgradeableConfig {
|
|
19
21
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
22
|
+
let mut config = Self::default();
|
|
20
23
|
if input.is_empty() {
|
|
21
|
-
return Ok(
|
|
24
|
+
return Ok(config);
|
|
22
25
|
}
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
while !input.is_empty() {
|
|
28
|
+
let ident: Ident = input.parse()?;
|
|
29
|
+
match ident.to_string().as_str() {
|
|
30
|
+
"no_migration" => config.no_migration = true,
|
|
31
|
+
"rbac" => config.rbac = true,
|
|
32
|
+
_ => return Err(syn::Error::new(ident.span(), "expected `no_migration` or `rbac`")),
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Consume optional trailing comma
|
|
36
|
+
if input.peek(Token![,]) {
|
|
37
|
+
input.parse::<Token![,]>()?;
|
|
38
|
+
}
|
|
29
39
|
}
|
|
40
|
+
Ok(config)
|
|
30
41
|
}
|
|
31
42
|
}
|
|
32
43
|
|
|
33
44
|
/// Generates the upgradeable implementation from the `#[upgradeable]` attribute macro.
|
|
34
45
|
///
|
|
35
|
-
///
|
|
36
|
-
///
|
|
37
|
-
/// WASM bytecode with migration support.
|
|
46
|
+
/// Generates an impl of `Upgradeable` or `UpgradeableRbac` for a contract type,
|
|
47
|
+
/// enabling upgrades by replacing WASM bytecode with migration support.
|
|
38
48
|
///
|
|
39
49
|
/// # Behavior
|
|
40
50
|
///
|
|
41
|
-
/// -
|
|
51
|
+
/// - By default implements `Upgradeable` (Auth-based, `#[only_auth]`). With `rbac`,
|
|
52
|
+
/// implements `UpgradeableRbac` (Auth + RoleBased, `UPGRADER_ROLE`).
|
|
42
53
|
/// - Sets the contract crate version as `"binver"` metadata using
|
|
43
|
-
/// `soroban_sdk::contractmeta!`.
|
|
44
|
-
/// `
|
|
45
|
-
///
|
|
46
|
-
/// -
|
|
47
|
-
/// - With `
|
|
48
|
-
///
|
|
49
|
-
/// # Example
|
|
50
|
-
/// ```ignore
|
|
51
|
-
/// // Requires manual UpgradeableInternal implementation (default, safety first)
|
|
52
|
-
/// #[ownable]
|
|
53
|
-
/// #[upgradeable]
|
|
54
|
-
/// pub struct MyContract;
|
|
54
|
+
/// `soroban_sdk::contractmeta!`. Uses `CARGO_PKG_VERSION` (from Cargo.toml
|
|
55
|
+
/// `[package]` version). Skips if missing or `"0.0.0"`.
|
|
56
|
+
/// - By default, requires the contract to implement `UpgradeableInternal`.
|
|
57
|
+
/// - With `no_migration`, generates a no-op `UpgradeableInternal` impl.
|
|
58
|
+
/// - With `rbac`, uses `UpgradeableRbac` (requires `RoleBasedAccessControl`, which
|
|
59
|
+
/// extends `Auth`) instead of `Upgradeable` (requires `Auth`).
|
|
55
60
|
///
|
|
56
|
-
///
|
|
57
|
-
/// #[ownable]
|
|
58
|
-
/// #[upgradeable(no_migration)]
|
|
59
|
-
/// pub struct MyContract;
|
|
60
|
-
/// ```
|
|
61
|
+
/// See the `#[upgradeable]` macro documentation for full examples.
|
|
61
62
|
pub fn generate_upgradeable_impl(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|
62
63
|
let config: UpgradeableConfig =
|
|
63
64
|
syn::parse2(attr).unwrap_or_else(|e| panic!("failed to parse upgradeable config: {}", e));
|
|
@@ -78,17 +79,23 @@ pub fn generate_upgradeable_impl(attr: TokenStream, input: TokenStream) -> Token
|
|
|
78
79
|
quote! {}
|
|
79
80
|
};
|
|
80
81
|
|
|
82
|
+
let trait_path = if config.rbac {
|
|
83
|
+
quote! { utils::upgradeable::UpgradeableRbac }
|
|
84
|
+
} else {
|
|
85
|
+
quote! { utils::upgradeable::Upgradeable }
|
|
86
|
+
};
|
|
87
|
+
|
|
81
88
|
quote! {
|
|
82
89
|
#item_struct
|
|
83
90
|
|
|
84
|
-
use
|
|
91
|
+
use #trait_path as _;
|
|
85
92
|
|
|
86
93
|
#binver
|
|
87
94
|
|
|
88
95
|
#default_internal_impl
|
|
89
96
|
|
|
90
97
|
#[common_macros::contract_impl(contracttrait)]
|
|
91
|
-
impl
|
|
98
|
+
impl #trait_path for #name {}
|
|
92
99
|
}
|
|
93
100
|
}
|
|
94
101
|
|
|
@@ -52,7 +52,7 @@ impl ILayerZeroEndpointV2 for EndpointV2 {
|
|
|
52
52
|
let packet = Self::build_outbound_packet(env, sender, *dst_eid, receiver, message, nonce);
|
|
53
53
|
|
|
54
54
|
let fee = SendLibClient::new(env, &send_lib).quote(&packet, options, pay_in_zro);
|
|
55
|
-
assert_with_error!(env, fee.native_fee >= 0 && fee.zro_fee >= 0, EndpointError::
|
|
55
|
+
assert_with_error!(env, fee.native_fee >= 0 && fee.zro_fee >= 0, EndpointError::InvalidAmount);
|
|
56
56
|
|
|
57
57
|
fee
|
|
58
58
|
}
|
|
@@ -127,6 +127,7 @@ impl ILayerZeroEndpointV2 for EndpointV2 {
|
|
|
127
127
|
reason: &Bytes,
|
|
128
128
|
) {
|
|
129
129
|
executor.require_auth();
|
|
130
|
+
assert_with_error!(env, gas >= 0 && value >= 0, EndpointError::InvalidAmount);
|
|
130
131
|
LzReceiveAlert {
|
|
131
132
|
receiver: receiver.clone(),
|
|
132
133
|
executor: executor.clone(),
|
|
@@ -257,7 +258,7 @@ impl EndpointV2 {
|
|
|
257
258
|
// Fee amounts are modeled as non-negative values. The field type is i128 for
|
|
258
259
|
// compatibility with token APIs, but negative fees are always invalid and rejected
|
|
259
260
|
// here, while zero amounts are treated as a no-op (skipped by the check below).
|
|
260
|
-
assert_with_error!(env, r.amount >= 0, EndpointError::
|
|
261
|
+
assert_with_error!(env, r.amount >= 0, EndpointError::InvalidAmount);
|
|
261
262
|
if r.amount > 0 {
|
|
262
263
|
assert_with_error!(env, native_fee_supplied >= r.amount, EndpointError::InsufficientNativeFee);
|
|
263
264
|
native_fee_supplied -= r.amount;
|
|
@@ -286,7 +287,7 @@ impl EndpointV2 {
|
|
|
286
287
|
// Fee amounts are modeled as non-negative values. The field type is i128 for
|
|
287
288
|
// compatibility with token APIs, but negative fees are always invalid and rejected
|
|
288
289
|
// here, while zero amounts are treated as a no-op (skipped by the check below).
|
|
289
|
-
assert_with_error!(env, r.amount >= 0, EndpointError::
|
|
290
|
+
assert_with_error!(env, r.amount >= 0, EndpointError::InvalidAmount);
|
|
290
291
|
if r.amount > 0 {
|
|
291
292
|
assert_with_error!(env, zro_fee_supplied >= r.amount, EndpointError::InsufficientZroFee);
|
|
292
293
|
zro_fee_supplied -= r.amount;
|
|
@@ -18,8 +18,8 @@ pub enum EndpointError {
|
|
|
18
18
|
InsufficientZroFee,
|
|
19
19
|
/// Timeout expiry is invalid (already expired)
|
|
20
20
|
InvalidExpiry,
|
|
21
|
-
///
|
|
22
|
-
|
|
21
|
+
/// Amount is invalid (negative)
|
|
22
|
+
InvalidAmount,
|
|
23
23
|
/// Compose index exceeds maximum allowed value
|
|
24
24
|
InvalidIndex,
|
|
25
25
|
/// Nonce is invalid for the requested operation
|
|
@@ -322,5 +322,16 @@ mod test {
|
|
|
322
322
|
) {
|
|
323
323
|
Self::clear_payload(env, receiver, src_eid, sender, nonce, payload)
|
|
324
324
|
}
|
|
325
|
+
|
|
326
|
+
/// Test-only wrapper for insert_and_drain_pending_nonces.
|
|
327
|
+
pub fn insert_and_drain_pending_nonces_for_test(
|
|
328
|
+
env: &Env,
|
|
329
|
+
receiver: &Address,
|
|
330
|
+
src_eid: u32,
|
|
331
|
+
sender: &BytesN<32>,
|
|
332
|
+
new_nonce: u64,
|
|
333
|
+
) {
|
|
334
|
+
Self::insert_and_drain_pending_nonces(env, receiver, src_eid, sender, new_nonce)
|
|
335
|
+
}
|
|
325
336
|
}
|
|
326
337
|
}
|
|
@@ -73,6 +73,7 @@ impl IMessagingComposer for EndpointV2 {
|
|
|
73
73
|
reason: &Bytes,
|
|
74
74
|
) {
|
|
75
75
|
executor.require_auth();
|
|
76
|
+
assert_with_error!(env, gas >= 0 && value >= 0, EndpointError::InvalidAmount);
|
|
76
77
|
assert_compose_index(env, index);
|
|
77
78
|
LzComposeAlert {
|
|
78
79
|
executor: executor.clone(),
|
|
@@ -92,33 +92,18 @@ fn test_clear_removes_inbound_payload_hash() {
|
|
|
92
92
|
// Now clear the payload
|
|
93
93
|
clear_packet_with_auth(&context, &receiver, &origin, &receiver, &guid, &message);
|
|
94
94
|
|
|
95
|
+
// Verify PacketDelivered event was emitted.
|
|
96
|
+
assert_eq_event(
|
|
97
|
+
env,
|
|
98
|
+
&endpoint_client.address,
|
|
99
|
+
PacketDelivered { origin: origin.clone(), receiver: receiver.clone() },
|
|
100
|
+
);
|
|
101
|
+
|
|
95
102
|
// Verify payload hash was removed via public interface
|
|
96
103
|
let stored_hash = endpoint_client.inbound_payload_hash(&receiver, &src_eid, &sender, &nonce);
|
|
97
104
|
assert_eq!(stored_hash, None);
|
|
98
105
|
}
|
|
99
106
|
|
|
100
|
-
// Event emission
|
|
101
|
-
#[test]
|
|
102
|
-
fn test_clear_emits_packet_delivered_event() {
|
|
103
|
-
let context = setup();
|
|
104
|
-
let env = &context.env;
|
|
105
|
-
let endpoint_client = &context.endpoint_client;
|
|
106
|
-
|
|
107
|
-
let src_eid = 2u32;
|
|
108
|
-
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
109
|
-
let receiver = env.register(MockReceiver, ());
|
|
110
|
-
let nonce = 1u64;
|
|
111
|
-
|
|
112
|
-
let message = Bytes::from_array(env, &[1, 2, 3, 4]);
|
|
113
|
-
let guid = BytesN::from_array(env, &[5u8; 32]);
|
|
114
|
-
let (_receive_lib, origin, _payload_hash) =
|
|
115
|
-
arrange_verified_packet_with_auth(&context, src_eid, &sender, &receiver, nonce, &guid, &message);
|
|
116
|
-
|
|
117
|
-
clear_packet_with_auth(&context, &receiver, &origin, &receiver, &guid, &message);
|
|
118
|
-
|
|
119
|
-
assert_eq_event(env, &endpoint_client.address, PacketDelivered { origin: origin.clone(), receiver: receiver.clone() });
|
|
120
|
-
}
|
|
121
|
-
|
|
122
107
|
// Inbound nonce is advanced during verify, not during clear
|
|
123
108
|
#[test]
|
|
124
109
|
fn test_clear_does_not_change_inbound_nonce() {
|
|
@@ -147,7 +132,7 @@ fn test_clear_does_not_change_inbound_nonce() {
|
|
|
147
132
|
|
|
148
133
|
// Sequential nonce behavior
|
|
149
134
|
#[test]
|
|
150
|
-
fn
|
|
135
|
+
fn test_clear_success_sequential_nonces_keep_inbound_nonce_at_latest_verified() {
|
|
151
136
|
let context = setup();
|
|
152
137
|
let env = &context.env;
|
|
153
138
|
let endpoint_client = &context.endpoint_client;
|
|
@@ -175,8 +160,10 @@ fn test_clear_success_sequential_nonces_update_lazy_nonce_to_latest() {
|
|
|
175
160
|
let origin2 = Origin { src_eid, sender: sender.clone(), nonce: 2 };
|
|
176
161
|
|
|
177
162
|
verify_packet_with_auth(&context, &receive_lib, &origin2, &receiver, &payload_hash2);
|
|
178
|
-
clear_packet_with_auth(&context, &receiver, &origin2, &receiver, &guid2, &message2);
|
|
179
163
|
|
|
164
|
+
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
165
|
+
|
|
166
|
+
clear_packet_with_auth(&context, &receiver, &origin2, &receiver, &guid2, &message2);
|
|
180
167
|
// Verify advanced inbound nonce.
|
|
181
168
|
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
182
169
|
}
|
|
@@ -382,7 +369,7 @@ fn test_clear_failure_missing_intermediate_nonce() {
|
|
|
382
369
|
}
|
|
383
370
|
|
|
384
371
|
#[test]
|
|
385
|
-
fn
|
|
372
|
+
fn test_clear_does_not_advance_inbound_nonce_when_clearing_older_nonce() {
|
|
386
373
|
let context = setup();
|
|
387
374
|
let env = &context.env;
|
|
388
375
|
let endpoint_client = &context.endpoint_client;
|
|
@@ -20,7 +20,7 @@ fn test_initializable_new_path_receiver_allows() {
|
|
|
20
20
|
let sender = BytesN::from_array(&context.env, &[1u8; 32]);
|
|
21
21
|
let origin = Origin { src_eid, sender, nonce: 1 };
|
|
22
22
|
|
|
23
|
-
// For a new path (
|
|
23
|
+
// For a new path (inbound_nonce is 0), initializable depends on receiver contract.
|
|
24
24
|
let result = endpoint_client.initializable(&origin, &receiver);
|
|
25
25
|
assert!(result);
|
|
26
26
|
}
|
|
@@ -36,12 +36,12 @@ fn test_initializable_new_path_receiver_rejects() {
|
|
|
36
36
|
let sender = BytesN::from_array(&context.env, &[1u8; 32]);
|
|
37
37
|
let origin = Origin { src_eid, sender, nonce: 1 };
|
|
38
38
|
|
|
39
|
-
// For a new path (
|
|
39
|
+
// For a new path (inbound_nonce is 0), initializable depends on receiver contract.
|
|
40
40
|
let result = endpoint_client.initializable(&origin, &receiver);
|
|
41
41
|
assert!(!result);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
// Established paths always return true (
|
|
44
|
+
// Established paths always return true (inbound_nonce > 0)
|
|
45
45
|
#[test]
|
|
46
46
|
fn test_initializable_established_path_always_true() {
|
|
47
47
|
let context = setup();
|
|
@@ -58,7 +58,7 @@ fn test_initializable_established_path_always_true() {
|
|
|
58
58
|
context.mock_auth(&receiver, "skip", (&receiver, &receiver, &src_eid, &sender, &nonce));
|
|
59
59
|
context.endpoint_client.skip(&receiver, &receiver, &src_eid, &sender, &nonce);
|
|
60
60
|
|
|
61
|
-
// Now the path is established (
|
|
61
|
+
// Now the path is established (inbound_nonce > 0), it should return true regardless of receiver.
|
|
62
62
|
let origin2 = Origin { src_eid, sender, nonce: 2 };
|
|
63
63
|
let result = endpoint_client.initializable(&origin2, &receiver);
|
|
64
64
|
assert!(result);
|
|
@@ -37,7 +37,7 @@ fn test_verifiable_new_path_nonce_1_true() {
|
|
|
37
37
|
|
|
38
38
|
// Established path => verifiable when origin.nonce is in (inbound_nonce, inbound_nonce + 256]
|
|
39
39
|
#[test]
|
|
40
|
-
fn
|
|
40
|
+
fn test_verifiable_after_skip_nonce_gt_inbound_nonce_true() {
|
|
41
41
|
let context = setup();
|
|
42
42
|
let env = &context.env;
|
|
43
43
|
let endpoint_client = &context.endpoint_client;
|
|
@@ -54,9 +54,9 @@ fn test_verifiable_after_skip_nonce_gt_lazy_true() {
|
|
|
54
54
|
assert!(result);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// Nonce <=
|
|
57
|
+
// Nonce <= inbound_nonce is still verifiable when an inbound payload hash exists
|
|
58
58
|
#[test]
|
|
59
|
-
fn
|
|
59
|
+
fn test_verifiable_true_when_nonce_leq_inbound_nonce_but_payload_hash_exists() {
|
|
60
60
|
let context = setup();
|
|
61
61
|
let env = &context.env;
|
|
62
62
|
let endpoint_client = &context.endpoint_client;
|
|
@@ -68,28 +68,28 @@ fn test_verifiable_true_when_nonce_leq_lazy_but_payload_hash_exists() {
|
|
|
68
68
|
// Setup receive library (needed to store a payload hash via verify).
|
|
69
69
|
let receive_lib = context.setup_default_receive_lib(src_eid, 0);
|
|
70
70
|
|
|
71
|
-
// Establish the path by skipping nonce 1 (
|
|
71
|
+
// Establish the path by skipping nonce 1 (inbound_nonce becomes 1).
|
|
72
72
|
skip_with_auth(&context, &receiver, src_eid, &sender, 1);
|
|
73
73
|
|
|
74
|
-
// Verify nonce 2 (allowed because 2 >
|
|
74
|
+
// Verify nonce 2 (allowed because 2 > inbound_nonce 1) which stores inbound payload hash for nonce 2.
|
|
75
75
|
let origin2 = Origin { src_eid, sender: sender.clone(), nonce: 2u64 };
|
|
76
76
|
let payload_hash2 = BytesN::from_array(env, &[0x33u8; 32]);
|
|
77
77
|
verify_with_auth(&context, &receive_lib, &origin2, &receiver, &payload_hash2);
|
|
78
78
|
|
|
79
|
-
// Advance
|
|
79
|
+
// Advance inbound_nonce to 3 while keeping payload hash for 2 (skip doesn't clear payload hashes).
|
|
80
80
|
skip_with_auth(&context, &receiver, src_eid, &sender, 3);
|
|
81
81
|
|
|
82
82
|
// Sanity: inbound nonce was advanced, and the payload hash for nonce 2 still exists.
|
|
83
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
|
-
// Now nonce 2 <=
|
|
86
|
+
// Now nonce 2 <= inbound_nonce 3, but payload hash exists -> verifiable should be true.
|
|
87
87
|
assert!(endpoint_client.verifiable(&origin2, &receiver));
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
// Boundary case (nonce ==
|
|
90
|
+
// Boundary case (nonce == inbound_nonce)
|
|
91
91
|
#[test]
|
|
92
|
-
fn
|
|
92
|
+
fn test_verifiable_nonce_eq_inbound_nonce_false_without_payload_hash() {
|
|
93
93
|
let context = setup();
|
|
94
94
|
let env = &context.env;
|
|
95
95
|
let endpoint_client = &context.endpoint_client;
|
|
@@ -103,7 +103,7 @@ fn test_verifiable_nonce_eq_lazy_false_without_payload_hash() {
|
|
|
103
103
|
skip_with_auth(&context, &receiver, src_eid, &sender, 2);
|
|
104
104
|
assert_eq!(endpoint_client.inbound_nonce(&receiver, &src_eid, &sender), 2);
|
|
105
105
|
|
|
106
|
-
// nonce ==
|
|
106
|
+
// nonce == inbound_nonce 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));
|
|
@@ -145,3 +145,43 @@ fn test_verifiable_upper_bound_is_enforced_when_inbound_nonce_nonzero() {
|
|
|
145
145
|
assert!(endpoint_client.verifiable(&ok, &receiver));
|
|
146
146
|
assert!(!endpoint_client.verifiable(&too_far, &receiver));
|
|
147
147
|
}
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
#[test]
|
|
151
|
+
fn test_verifiable_true_when_payload_hash_exists_even_if_nonce_outside_window() {
|
|
152
|
+
let context = setup();
|
|
153
|
+
let env = &context.env;
|
|
154
|
+
let endpoint_client = &context.endpoint_client;
|
|
155
|
+
|
|
156
|
+
let src_eid = 2u32;
|
|
157
|
+
let sender = BytesN::from_array(env, &[1u8; 32]);
|
|
158
|
+
let receiver = soroban_sdk::Address::generate(env);
|
|
159
|
+
|
|
160
|
+
// NOTE: This state should be unreachable in normal execution flows.
|
|
161
|
+
//
|
|
162
|
+
// In production, payload hashes are written by `verify()` -> `inbound()`. For *new* nonces
|
|
163
|
+
// (`nonce > inbound_nonce`), `inbound()` calls `insert_and_drain_pending_nonces()` which enforces
|
|
164
|
+
// the pending window bound (`nonce <= inbound_nonce + 256`).
|
|
165
|
+
//
|
|
166
|
+
// Therefore, with `inbound_nonce == 0`, a payload hash at a "far" nonce (e.g. 999) cannot be
|
|
167
|
+
// created through valid contract calls (it would fail `verifiable()` and/or the pending window
|
|
168
|
+
// bound on insertion).
|
|
169
|
+
//
|
|
170
|
+
// We write storage directly here to simulate corrupted/invalid state and to ensure `verifiable()`
|
|
171
|
+
// preserves its OR semantics: if a payload hash exists for a nonce, `verifiable()` must return
|
|
172
|
+
// true even when the nonce is outside the pending window.
|
|
173
|
+
|
|
174
|
+
// Choose a nonce that is clearly outside the pending window when inbound_nonce == 0.
|
|
175
|
+
let far_nonce = 999u64;
|
|
176
|
+
|
|
177
|
+
// Write payload hash directly to storage to exercise the OR branch:
|
|
178
|
+
// `EndpointStorage::has_inbound_payload_hash(...) == true` should make verifiable() return true,
|
|
179
|
+
// even if the nonce is outside (inbound_nonce, inbound_nonce + 256].
|
|
180
|
+
let payload_hash = BytesN::from_array(env, &[0x42u8; 32]);
|
|
181
|
+
env.as_contract(&endpoint_client.address, || {
|
|
182
|
+
storage::EndpointStorage::set_inbound_payload_hash(env, &receiver, src_eid, &sender, far_nonce, &payload_hash)
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
let origin_far = Origin { src_eid, sender, nonce: far_nonce };
|
|
186
|
+
assert!(endpoint_client.verifiable(&origin_far, &receiver));
|
|
187
|
+
}
|
|
@@ -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
|
}
|