@layerzerolabs/protocol-stellar-v2 0.2.40 → 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
|
@@ -457,23 +457,27 @@ pub fn contract_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
|
457
457
|
// Upgradeable Macro
|
|
458
458
|
// ============================================================================
|
|
459
459
|
|
|
460
|
-
/// Generates upgradeable implementation using
|
|
460
|
+
/// Generates upgradeable implementation using `Upgradeable` or `UpgradeableRbac` traits.
|
|
461
461
|
///
|
|
462
|
-
///
|
|
462
|
+
/// `Upgradeable` uses Auth directly; `UpgradeableRbac` layers RoleBased
|
|
463
|
+
/// access control on top of Auth.
|
|
463
464
|
///
|
|
464
465
|
/// # Requirements
|
|
465
|
-
/// -
|
|
466
|
+
/// - `Upgradeable` (default): contract must implement `Auth` (via `#[ownable]` or `#[multisig]`)
|
|
467
|
+
/// - `UpgradeableRbac` (with `rbac`): contract must implement both `Auth` and `RoleBasedAccessControl` (e.g. from OApp)
|
|
466
468
|
/// - By default, requires manual `UpgradeableInternal` implementation
|
|
467
469
|
/// - With `no_migration` flag, auto-generates a no-op `UpgradeableInternal` impl
|
|
468
470
|
///
|
|
469
471
|
/// # Options
|
|
470
|
-
/// - `#[upgradeable]` -
|
|
471
|
-
/// - `#[upgradeable(no_migration)]` -
|
|
472
|
+
/// - `#[upgradeable]` - Implements Upgradeable, requires manual `UpgradeableInternal` (safety by default)
|
|
473
|
+
/// - `#[upgradeable(no_migration)]` - Implements Upgradeable, auto-generates no-op `UpgradeableInternal`
|
|
474
|
+
/// - `#[upgradeable(rbac)]` - Implements UpgradeableRbac, requires manual `UpgradeableInternal`
|
|
475
|
+
/// - `#[upgradeable(rbac, no_migration)]` - Implements UpgradeableRbac, auto-generates no-op `UpgradeableInternal`
|
|
472
476
|
///
|
|
473
477
|
/// # Example
|
|
474
478
|
/// ```ignore
|
|
475
|
-
/// //
|
|
476
|
-
/// #[ownable]
|
|
479
|
+
/// // Implements Upgradeable (default)
|
|
480
|
+
/// #[ownable]
|
|
477
481
|
/// #[upgradeable]
|
|
478
482
|
/// pub struct MyContract;
|
|
479
483
|
///
|
|
@@ -485,16 +489,32 @@ pub fn contract_trait(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
|
485
489
|
/// }
|
|
486
490
|
/// }
|
|
487
491
|
///
|
|
488
|
-
/// //
|
|
489
|
-
/// #[ownable]
|
|
492
|
+
/// // Implements Upgradeable (no migration)
|
|
493
|
+
/// #[ownable]
|
|
490
494
|
/// #[upgradeable(no_migration)]
|
|
491
|
-
/// pub struct
|
|
492
|
-
///
|
|
495
|
+
/// pub struct SimpleContract;
|
|
496
|
+
///
|
|
497
|
+
/// // Implements UpgradeableRbac (layered)
|
|
498
|
+
/// #[ownable]
|
|
499
|
+
/// #[upgradeable(rbac)]
|
|
500
|
+
/// pub struct RbacContract;
|
|
501
|
+
///
|
|
502
|
+
/// impl utils::upgradeable::UpgradeableInternal for RbacContract {
|
|
503
|
+
/// type MigrationData = MyMigrationParams;
|
|
504
|
+
///
|
|
505
|
+
/// fn __migrate(env: &Env, migration_data: &Self::MigrationData) {
|
|
506
|
+
/// // Custom migration logic here
|
|
507
|
+
/// }
|
|
508
|
+
/// }
|
|
509
|
+
///
|
|
510
|
+
/// // Implements UpgradeableRbac (no migration)
|
|
511
|
+
/// #[ownable]
|
|
512
|
+
/// #[upgradeable(rbac, no_migration)]
|
|
513
|
+
/// pub struct SimpleRbacContract;
|
|
493
514
|
/// ```
|
|
494
515
|
///
|
|
495
516
|
/// Generated code includes:
|
|
496
|
-
/// - `upgrade
|
|
497
|
-
/// - `migrate(env, migration_data)` - Runs migration after upgrade (auth required, XDR-decodes `Bytes` to `MigrationData`)
|
|
517
|
+
/// - `upgrade` / `migrate` - Auth-based or Auth + RoleBased depending on options
|
|
498
518
|
/// - `contractmeta!` with `binver` set to the Cargo package version (if not 0.0.0)
|
|
499
519
|
#[proc_macro_attribute]
|
|
500
520
|
pub fn upgradeable(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
@@ -518,8 +538,7 @@ pub fn upgradeable(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
|
518
538
|
/// - `#[ownable]` - Single-owner access control
|
|
519
539
|
///
|
|
520
540
|
/// # Options
|
|
521
|
-
/// - `upgradeable` - Adds `#[upgradeable]
|
|
522
|
-
/// - `upgradeable(no_migration)` - Adds `#[upgradeable(no_migration)]`, auto-generates no-op impl
|
|
541
|
+
/// - `upgradeable(...)` - Adds `#[upgradeable(...)]`; content is passed verbatim to the upgradeable macro
|
|
523
542
|
/// - `multisig` - Uses `#[multisig]` instead of `#[ownable]`
|
|
524
543
|
///
|
|
525
544
|
/// # Examples
|
|
@@ -536,6 +555,10 @@ pub fn upgradeable(attr: TokenStream, item: TokenStream) -> TokenStream {
|
|
|
536
555
|
/// #[lz_contract(upgradeable(no_migration))]
|
|
537
556
|
/// pub struct DVNFeeLib;
|
|
538
557
|
///
|
|
558
|
+
/// // Contract with RBAC-based upgrade support
|
|
559
|
+
/// #[lz_contract(upgradeable(rbac))]
|
|
560
|
+
/// pub struct RbacOft;
|
|
561
|
+
///
|
|
539
562
|
/// // Contract with multisig auth and upgrade support (no migration)
|
|
540
563
|
/// #[lz_contract(multisig, upgradeable(no_migration))]
|
|
541
564
|
/// pub struct DVN;
|
|
@@ -16,21 +16,20 @@ use syn::{
|
|
|
16
16
|
pub struct LzContractConfig {
|
|
17
17
|
/// If true, adds `#[upgradeable]` for contract upgrade support.
|
|
18
18
|
pub upgradeable: bool,
|
|
19
|
-
///
|
|
20
|
-
///
|
|
21
|
-
pub
|
|
19
|
+
/// Raw tokens inside `upgradeable(...)`, passed verbatim to the upgradeable macro.
|
|
20
|
+
/// Empty when `upgradeable` has no parentheses.
|
|
21
|
+
pub upgradeable_attr: TokenStream,
|
|
22
22
|
/// If true, uses `#[multisig]` instead of `#[ownable]` for auth.
|
|
23
23
|
pub multisig: bool,
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
impl Parse for LzContractConfig {
|
|
27
27
|
fn parse(input: ParseStream) -> syn::Result<Self> {
|
|
28
|
+
let mut config = Self::default();
|
|
28
29
|
if input.is_empty() {
|
|
29
|
-
return Ok(
|
|
30
|
+
return Ok(config);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
let mut config = Self::default();
|
|
33
|
-
|
|
34
33
|
// Parse comma-separated items, handling nested parentheses for upgradeable(no_migration)
|
|
35
34
|
while !input.is_empty() {
|
|
36
35
|
let ident: Ident = input.parse()?;
|
|
@@ -38,19 +37,11 @@ impl Parse for LzContractConfig {
|
|
|
38
37
|
match ident.to_string().as_str() {
|
|
39
38
|
"upgradeable" => {
|
|
40
39
|
config.upgradeable = true;
|
|
41
|
-
//
|
|
40
|
+
// Pass through optional (...) content verbatim to the upgradeable macro
|
|
42
41
|
if input.peek(syn::token::Paren) {
|
|
43
42
|
let content;
|
|
44
43
|
parenthesized!(content in input);
|
|
45
|
-
|
|
46
|
-
if inner_ident == "no_migration" {
|
|
47
|
-
config.no_migration = true;
|
|
48
|
-
} else {
|
|
49
|
-
return Err(Error::new(inner_ident.span(), "expected `no_migration`"));
|
|
50
|
-
}
|
|
51
|
-
if !content.is_empty() {
|
|
52
|
-
return Err(Error::new(content.span(), "unexpected tokens in `upgradeable(...)`"));
|
|
53
|
-
}
|
|
44
|
+
config.upgradeable_attr = content.parse()?;
|
|
54
45
|
}
|
|
55
46
|
}
|
|
56
47
|
"multisig" => config.multisig = true,
|
|
@@ -79,8 +70,7 @@ impl Parse for LzContractConfig {
|
|
|
79
70
|
/// - `#[common_macros::ownable]` - Single-owner access control
|
|
80
71
|
///
|
|
81
72
|
/// # Options
|
|
82
|
-
/// - `upgradeable` - Adds `#[upgradeable]
|
|
83
|
-
/// - `upgradeable(no_migration)` - Adds `#[upgradeable(no_migration)]`, auto-generates no-op impl
|
|
73
|
+
/// - `upgradeable(...)` - Adds `#[upgradeable(...)]`; content is passed verbatim to the upgradeable macro
|
|
84
74
|
/// - `multisig` - Uses `#[multisig]` instead of `#[ownable]`
|
|
85
75
|
pub fn generate_lz_contract(attr: TokenStream, input: TokenStream) -> TokenStream {
|
|
86
76
|
let config: LzContractConfig =
|
|
@@ -94,10 +84,11 @@ pub fn generate_lz_contract(attr: TokenStream, input: TokenStream) -> TokenStrea
|
|
|
94
84
|
};
|
|
95
85
|
|
|
96
86
|
let upgrade = if config.upgradeable {
|
|
97
|
-
if config.
|
|
98
|
-
quote! { #[common_macros::upgradeable(no_migration)] }
|
|
99
|
-
} else {
|
|
87
|
+
if config.upgradeable_attr.is_empty() {
|
|
100
88
|
quote! { #[common_macros::upgradeable] }
|
|
89
|
+
} else {
|
|
90
|
+
let upgradeable_attr = &config.upgradeable_attr;
|
|
91
|
+
quote! { #[common_macros::upgradeable(#upgradeable_attr)] }
|
|
101
92
|
}
|
|
102
93
|
} else {
|
|
103
94
|
quote! {}
|
|
@@ -30,9 +30,24 @@ fn snapshot_generated_lz_contract_code() {
|
|
|
30
30
|
&syn::parse2::<syn::File>(upgradeable_no_migration_result).expect("failed to parse generated code"),
|
|
31
31
|
);
|
|
32
32
|
|
|
33
|
+
let upgradeable_rbac_result =
|
|
34
|
+
crate::lz_contract::generate_lz_contract(quote! { upgradeable(rbac) }, quote! { pub struct MyContract; });
|
|
35
|
+
let upgradeable_rbac_formatted = prettyplease::unparse(
|
|
36
|
+
&syn::parse2::<syn::File>(upgradeable_rbac_result).expect("failed to parse generated code"),
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// Pass-through: order and content preserved verbatim
|
|
40
|
+
let upgradeable_rbac_no_migration_result = crate::lz_contract::generate_lz_contract(
|
|
41
|
+
quote! { upgradeable(rbac, no_migration) },
|
|
42
|
+
quote! { pub struct MyContract; },
|
|
43
|
+
);
|
|
44
|
+
let upgradeable_rbac_no_migration_formatted = prettyplease::unparse(
|
|
45
|
+
&syn::parse2::<syn::File>(upgradeable_rbac_no_migration_result).expect("failed to parse generated code"),
|
|
46
|
+
);
|
|
47
|
+
|
|
33
48
|
let combined = format!(
|
|
34
|
-
"// === Default (ownable) ===\n\n{}\n\n// === MultiSig + Upgradeable ===\n\n{}\n\n// === Upgradeable (no_migration) ===\n\n{}",
|
|
35
|
-
default_formatted, multisig_upgradeable_formatted, upgradeable_no_migration_formatted
|
|
49
|
+
"// === Default (ownable) ===\n\n{}\n\n// === MultiSig + Upgradeable ===\n\n{}\n\n// === Upgradeable (no_migration) ===\n\n{}\n\n// === Upgradeable (rbac) ===\n\n{}\n\n// === Upgradeable (rbac, no_migration) pass-through ===\n\n{}",
|
|
50
|
+
default_formatted, multisig_upgradeable_formatted, upgradeable_no_migration_formatted, upgradeable_rbac_formatted, upgradeable_rbac_no_migration_formatted
|
|
36
51
|
);
|
|
37
52
|
|
|
38
53
|
insta::assert_snapshot!(combined);
|
|
@@ -49,12 +64,6 @@ fn test_lz_contract_invalid_config_table_driven() {
|
|
|
49
64
|
let cases: Vec<(&str, TokenStream, &str)> = vec![
|
|
50
65
|
("unknown option", quote! { not_a_real_option }, "expected one of `upgradeable`, `multisig`"),
|
|
51
66
|
("invalid attr syntax", quote! { 123 }, "failed to parse lz_contract config"),
|
|
52
|
-
("upgradeable(bad_inner)", quote! { upgradeable(not_migration) }, "expected `no_migration`"),
|
|
53
|
-
(
|
|
54
|
-
"upgradeable(extra_tokens)",
|
|
55
|
-
quote! { upgradeable(no_migration, extra) },
|
|
56
|
-
"unexpected tokens in `upgradeable(...)`",
|
|
57
|
-
),
|
|
58
67
|
];
|
|
59
68
|
|
|
60
69
|
for (case, attr, expected_substring) in cases {
|
|
@@ -29,3 +29,23 @@ pub struct MyContract;
|
|
|
29
29
|
#[common_macros::ownable]
|
|
30
30
|
#[common_macros::upgradeable(no_migration)]
|
|
31
31
|
pub struct MyContract;
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
// === Upgradeable (rbac) ===
|
|
35
|
+
|
|
36
|
+
#[soroban_sdk::contract]
|
|
37
|
+
#[common_macros::ttl_configurable]
|
|
38
|
+
#[common_macros::ttl_extendable]
|
|
39
|
+
#[common_macros::ownable]
|
|
40
|
+
#[common_macros::upgradeable(rbac)]
|
|
41
|
+
pub struct MyContract;
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
// === Upgradeable (rbac, no_migration) pass-through ===
|
|
45
|
+
|
|
46
|
+
#[soroban_sdk::contract]
|
|
47
|
+
#[common_macros::ttl_configurable]
|
|
48
|
+
#[common_macros::ttl_extendable]
|
|
49
|
+
#[common_macros::ownable]
|
|
50
|
+
#[common_macros::upgradeable(rbac, no_migration)]
|
|
51
|
+
pub struct MyContract;
|
|
@@ -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
|
+
}
|