@layerzerolabs/protocol-stellar-v2 0.2.8
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 +727 -0
- package/.turbo/turbo-lint.log +158 -0
- package/.turbo/turbo-test.log +796 -0
- package/Cargo.lock +2237 -0
- package/Cargo.toml +63 -0
- package/clippy.toml +7 -0
- package/contracts/common-macros/Cargo.toml +20 -0
- package/contracts/common-macros/src/error.rs +53 -0
- package/contracts/common-macros/src/event.rs +16 -0
- package/contracts/common-macros/src/lib.rs +255 -0
- package/contracts/common-macros/src/ownable.rs +63 -0
- package/contracts/common-macros/src/snapshots/common_macros__tests__tests__snapshot_generated_storage_code.snap +310 -0
- package/contracts/common-macros/src/storage.rs +439 -0
- package/contracts/common-macros/src/tests.rs +287 -0
- package/contracts/common-macros/src/ttl_configurable.rs +60 -0
- package/contracts/endpoint-v2/ARCHITECTURE.md +233 -0
- package/contracts/endpoint-v2/Cargo.toml +30 -0
- package/contracts/endpoint-v2/src/constants.rs +52 -0
- package/contracts/endpoint-v2/src/endpoint_v2.rs +305 -0
- package/contracts/endpoint-v2/src/errors.rs +29 -0
- package/contracts/endpoint-v2/src/events.rs +207 -0
- package/contracts/endpoint-v2/src/interfaces/layerzero_composer.rs +26 -0
- package/contracts/endpoint-v2/src/interfaces/layerzero_endpoint_v2.rs +170 -0
- package/contracts/endpoint-v2/src/interfaces/layerzero_receiver.rs +43 -0
- package/contracts/endpoint-v2/src/interfaces/message_lib.rs +62 -0
- package/contracts/endpoint-v2/src/interfaces/message_lib_manager.rs +220 -0
- package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +121 -0
- package/contracts/endpoint-v2/src/interfaces/messaging_composer.rs +63 -0
- package/contracts/endpoint-v2/src/interfaces/mod.rs +17 -0
- package/contracts/endpoint-v2/src/interfaces/send_lib.rs +70 -0
- package/contracts/endpoint-v2/src/lib.rs +22 -0
- package/contracts/endpoint-v2/src/message_lib_manager.rs +315 -0
- package/contracts/endpoint-v2/src/messaging_channel.rs +218 -0
- package/contracts/endpoint-v2/src/messaging_composer.rs +76 -0
- package/contracts/endpoint-v2/src/storage.rs +78 -0
- package/contracts/endpoint-v2/src/tests/endpoint_setup.rs +131 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/clear.rs +237 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/delegate.rs +42 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/initializable.rs +76 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/lz_receive_alert.rs +211 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/mod.rs +18 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/native_token.rs +10 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/owner.rs +10 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/pay_messaging_fees.rs +424 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/quote.rs +144 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/recover_token.rs +72 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/require_oapp_auth.rs +29 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/send.rs +513 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/set_delegate.rs +43 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/set_zro.rs +27 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/transfer_ownership.rs +30 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/ttl_config.rs +202 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verifiable.rs +59 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/verify.rs +172 -0
- package/contracts/endpoint-v2/src/tests/endpoint_v2/zro.rs +23 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/mod.rs +10 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/register_library.rs +131 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/require_registered.rs +35 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/require_supported_eid.rs +28 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_config.rs +79 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +246 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +285 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_send_library.rs +180 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_receive_library.rs +405 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_receive_library_timeout.rs +80 -0
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_send_library.rs +131 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/burn.rs +358 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/clear.rs +316 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/inbound_nonce.rs +288 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/inbound_payload_hash.rs +316 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/internal.rs +388 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/lazy_inbound_nonce.rs +307 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/mod.rs +10 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/next_guid.rs +239 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/nilify.rs +324 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/outbound_nonce.rs +242 -0
- package/contracts/endpoint-v2/src/tests/messaging_channel/skip.rs +232 -0
- package/contracts/endpoint-v2/src/tests/messaging_composer/clear_compose.rs +212 -0
- package/contracts/endpoint-v2/src/tests/messaging_composer/compose_queue.rs +213 -0
- package/contracts/endpoint-v2/src/tests/messaging_composer/lz_compose_alert.rs +269 -0
- package/contracts/endpoint-v2/src/tests/messaging_composer/mod.rs +4 -0
- package/contracts/endpoint-v2/src/tests/messaging_composer/send_compose.rs +173 -0
- package/contracts/endpoint-v2/src/tests/mock.rs +132 -0
- package/contracts/endpoint-v2/src/tests/mod.rs +12 -0
- package/contracts/endpoint-v2/src/tests/util/build_payload.rs +126 -0
- package/contracts/endpoint-v2/src/tests/util/compute_guid.rs +82 -0
- package/contracts/endpoint-v2/src/tests/util/keccak256.rs +115 -0
- package/contracts/endpoint-v2/src/tests/util/mod.rs +3 -0
- package/contracts/endpoint-v2/src/util.rs +52 -0
- package/contracts/message-libs/Cargo.toml +12 -0
- package/contracts/message-libs/block-message-lib/Cargo.toml +19 -0
- package/contracts/message-libs/block-message-lib/src/lib.rs +70 -0
- package/contracts/message-libs/lib.rs +2 -0
- package/contracts/message-libs/message-lib-common/Cargo.toml +24 -0
- package/contracts/message-libs/message-lib-common/src/errors.rs +20 -0
- package/contracts/message-libs/message-lib-common/src/interfaces/dvn.rs +55 -0
- package/contracts/message-libs/message-lib-common/src/interfaces/executor.rs +46 -0
- package/contracts/message-libs/message-lib-common/src/interfaces/mod.rs +7 -0
- package/contracts/message-libs/message-lib-common/src/interfaces/treasury.rs +17 -0
- package/contracts/message-libs/message-lib-common/src/lib.rs +14 -0
- package/contracts/message-libs/message-lib-common/src/packet_codec_v1.rs +99 -0
- package/contracts/message-libs/message-lib-common/src/testing_utils.rs +27 -0
- package/contracts/message-libs/message-lib-common/src/tests/mod.rs +2 -0
- package/contracts/message-libs/message-lib-common/src/tests/packet_codec_v1.rs +162 -0
- package/contracts/message-libs/message-lib-common/src/tests/worker_options.rs +319 -0
- package/contracts/message-libs/message-lib-common/src/worker_options.rs +190 -0
- package/contracts/message-libs/simple-message-lib/Cargo.toml +26 -0
- package/contracts/message-libs/simple-message-lib/src/errors.rs +11 -0
- package/contracts/message-libs/simple-message-lib/src/lib.rs +14 -0
- package/contracts/message-libs/simple-message-lib/src/simple_message_lib.rs +136 -0
- package/contracts/message-libs/simple-message-lib/src/storage.rs +27 -0
- package/contracts/message-libs/simple-message-lib/src/test.rs +280 -0
- package/contracts/message-libs/treasury/Cargo.toml +27 -0
- package/contracts/message-libs/treasury/src/errors.rs +10 -0
- package/contracts/message-libs/treasury/src/events.rs +28 -0
- package/contracts/message-libs/treasury/src/interfaces/mod.rs +3 -0
- package/contracts/message-libs/treasury/src/interfaces/zro_fee_lib.rs +20 -0
- package/contracts/message-libs/treasury/src/lib.rs +20 -0
- package/contracts/message-libs/treasury/src/storage.rs +18 -0
- package/contracts/message-libs/treasury/src/tests/mod.rs +2 -0
- package/contracts/message-libs/treasury/src/tests/setup.rs +112 -0
- package/contracts/message-libs/treasury/src/tests/treasury_tests.rs +562 -0
- package/contracts/message-libs/treasury/src/treasury.rs +140 -0
- package/contracts/message-libs/uln-302/Cargo.toml +28 -0
- package/contracts/message-libs/uln-302/src/config_validation.rs +173 -0
- package/contracts/message-libs/uln-302/src/errors.rs +29 -0
- package/contracts/message-libs/uln-302/src/events.rs +72 -0
- package/contracts/message-libs/uln-302/src/interfaces/mod.rs +5 -0
- package/contracts/message-libs/uln-302/src/interfaces/receive.rs +82 -0
- package/contracts/message-libs/uln-302/src/interfaces/send.rs +159 -0
- package/contracts/message-libs/uln-302/src/lib.rs +20 -0
- package/contracts/message-libs/uln-302/src/receive.rs +199 -0
- package/contracts/message-libs/uln-302/src/send.rs +349 -0
- package/contracts/message-libs/uln-302/src/storage.rs +47 -0
- package/contracts/message-libs/uln-302/src/tests/config/mod.rs +2 -0
- package/contracts/message-libs/uln-302/src/tests/config/oapp_uln_config.rs +291 -0
- package/contracts/message-libs/uln-302/src/tests/config/uln_config.rs +163 -0
- package/contracts/message-libs/uln-302/src/tests/mod.rs +7 -0
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/commit_verification.rs +183 -0
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/confirmations.rs +128 -0
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/effective_receive_uln_config.rs +104 -0
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/mod.rs +66 -0
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +79 -0
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/verifiable.rs +463 -0
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/verify.rs +173 -0
- package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_executor_config.rs +132 -0
- package/contracts/message-libs/uln-302/src/tests/send_uln302/effective_send_uln_config.rs +117 -0
- package/contracts/message-libs/uln-302/src/tests/send_uln302/mod.rs +6 -0
- package/contracts/message-libs/uln-302/src/tests/send_uln302/quote.rs +586 -0
- package/contracts/message-libs/uln-302/src/tests/send_uln302/send.rs +834 -0
- package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_executor_configs.rs +95 -0
- package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +80 -0
- package/contracts/message-libs/uln-302/src/tests/setup.rs +268 -0
- package/contracts/message-libs/uln-302/src/tests/testing_utils.rs +47 -0
- package/contracts/message-libs/uln-302/src/tests/uln302/get_app_receive_uln_config.rs +51 -0
- package/contracts/message-libs/uln-302/src/tests/uln302/get_app_send_uln_config.rs +51 -0
- package/contracts/message-libs/uln-302/src/tests/uln302/get_oapp_executor_config.rs +48 -0
- package/contracts/message-libs/uln-302/src/tests/uln302/mod.rs +4 -0
- package/contracts/message-libs/uln-302/src/tests/uln302/set_config.rs +998 -0
- package/contracts/message-libs/uln-302/src/uln302.rs +117 -0
- package/contracts/oapp-macros/Cargo.toml +21 -0
- package/contracts/oapp-macros/src/lib.rs +408 -0
- package/contracts/oapp-macros/src/oapp_core.rs +49 -0
- package/contracts/oapp-macros/src/oapp_full.rs +15 -0
- package/contracts/oapp-macros/src/oapp_options_type3.rs +46 -0
- package/contracts/oapp-macros/src/oapp_receiver.rs +67 -0
- package/contracts/oapp-macros/src/oapp_sender.rs +23 -0
- package/contracts/oapp-macros/src/util.rs +103 -0
- package/contracts/oapp-macros/tests/test_macros.rs +522 -0
- package/contracts/oapps/Cargo.toml +12 -0
- package/contracts/oapps/counter/Cargo.toml +24 -0
- package/contracts/oapps/counter/integration_tests/mod.rs +3 -0
- package/contracts/oapps/counter/integration_tests/setup.rs +201 -0
- package/contracts/oapps/counter/integration_tests/test_with_sml.rs +166 -0
- package/contracts/oapps/counter/integration_tests/utils.rs +144 -0
- package/contracts/oapps/counter/src/codec.rs +63 -0
- package/contracts/oapps/counter/src/counter.rs +235 -0
- package/contracts/oapps/counter/src/errors.rs +9 -0
- package/contracts/oapps/counter/src/lib.rs +16 -0
- package/contracts/oapps/counter/src/options.rs +30 -0
- package/contracts/oapps/counter/src/storage.rs +33 -0
- package/contracts/oapps/counter/src/tests/mod.rs +37 -0
- package/contracts/oapps/counter/src/tests/test_codec.rs +64 -0
- package/contracts/oapps/counter/src/tests/test_counter.rs +390 -0
- package/contracts/oapps/counter/src/u256_ext.rs +21 -0
- package/contracts/oapps/lib.rs +2 -0
- package/contracts/oapps/oapp/Cargo.toml +21 -0
- package/contracts/oapps/oapp/src/errors.rs +9 -0
- package/contracts/oapps/oapp/src/lib.rs +10 -0
- package/contracts/oapps/oapp/src/oapp_core.rs +92 -0
- package/contracts/oapps/oapp/src/oapp_options_type3.rs +89 -0
- package/contracts/oapps/oapp/src/oapp_receiver.rs +72 -0
- package/contracts/oapps/oapp/src/oapp_sender.rs +66 -0
- package/contracts/oapps/oapp/src/tests/mod.rs +4 -0
- package/contracts/oapps/oapp/src/tests/test_oapp_core.rs +162 -0
- package/contracts/oapps/oapp/src/tests/test_oapp_options_type3.rs +180 -0
- package/contracts/oapps/oapp/src/tests/test_oapp_receiver.rs +157 -0
- package/contracts/oapps/oapp/src/tests/test_oapp_sender.rs +283 -0
- package/contracts/utils/Cargo.toml +21 -0
- package/contracts/utils/src/buffer_reader.rs +143 -0
- package/contracts/utils/src/buffer_writer.rs +117 -0
- package/contracts/utils/src/bytes_ext.rs +19 -0
- package/contracts/utils/src/errors.rs +30 -0
- package/contracts/utils/src/lib.rs +15 -0
- package/contracts/utils/src/option_ext.rs +38 -0
- package/contracts/utils/src/ownable.rs +88 -0
- package/contracts/utils/src/testing_utils.rs +100 -0
- package/contracts/utils/src/tests/buffer_reader.rs +1006 -0
- package/contracts/utils/src/tests/buffer_writer.rs +330 -0
- package/contracts/utils/src/tests/bytes_ext.rs +77 -0
- package/contracts/utils/src/tests/mod.rs +4 -0
- package/contracts/utils/src/tests/ownable.rs +149 -0
- package/contracts/utils/src/ttl.rs +164 -0
- package/contracts/workers/Cargo.toml +13 -0
- package/contracts/workers/executor/Cargo.toml +26 -0
- package/contracts/workers/executor/src/events.rs +22 -0
- package/contracts/workers/executor/src/executor.rs +347 -0
- package/contracts/workers/executor/src/interfaces/executor.rs +40 -0
- package/contracts/workers/executor/src/interfaces/mod.rs +5 -0
- package/contracts/workers/executor/src/interfaces/types.rs +51 -0
- package/contracts/workers/executor/src/lib.rs +10 -0
- package/contracts/workers/executor/src/storage.rs +23 -0
- package/contracts/workers/lib.rs +2 -0
- package/contracts/workers/worker-common/Cargo.toml +18 -0
- package/contracts/workers/worker-common/src/constants.rs +17 -0
- package/contracts/workers/worker-common/src/errors.rs +6 -0
- package/contracts/workers/worker-common/src/events.rs +34 -0
- package/contracts/workers/worker-common/src/interfaces/executor_fee_lib.rs +35 -0
- package/contracts/workers/worker-common/src/interfaces/mod.rs +7 -0
- package/contracts/workers/worker-common/src/interfaces/price_feed.rs +40 -0
- package/contracts/workers/worker-common/src/interfaces/worker.rs +60 -0
- package/contracts/workers/worker-common/src/lib.rs +19 -0
- package/contracts/workers/worker-common/src/storage.rs +32 -0
- package/contracts/workers/worker-common/src/worker_common.rs +166 -0
- package/package.json +25 -0
- package/rust-toolchain.toml +4 -0
- package/rustfmt.toml +17 -0
- package/sdk/.turbo/turbo-build.log +4 -0
- package/sdk/dist/generated/bml.d.ts +452 -0
- package/sdk/dist/generated/bml.js +72 -0
- package/sdk/dist/generated/counter.d.ts +824 -0
- package/sdk/dist/generated/counter.js +125 -0
- package/sdk/dist/generated/endpoint.d.ts +1676 -0
- package/sdk/dist/generated/endpoint.js +216 -0
- package/sdk/dist/generated/sml.d.ts +810 -0
- package/sdk/dist/generated/sml.js +132 -0
- package/sdk/dist/generated/uln302.d.ts +1227 -0
- package/sdk/dist/generated/uln302.js +185 -0
- package/sdk/dist/index.d.ts +5 -0
- package/sdk/dist/index.js +5 -0
- package/sdk/node_modules/.bin/tsc +21 -0
- package/sdk/node_modules/.bin/tsserver +21 -0
- package/sdk/node_modules/.bin/vitest +21 -0
- package/sdk/node_modules/.bin/zx +21 -0
- package/sdk/package.json +40 -0
- package/sdk/src/index.ts +5 -0
- package/sdk/test/index.test.ts +271 -0
- package/sdk/test/suites/constants.ts +13 -0
- package/sdk/test/suites/deploy.ts +277 -0
- package/sdk/test/suites/localnet.ts +42 -0
- package/sdk/test/suites/scan.ts +189 -0
- package/sdk/tsconfig.json +106 -0
- package/tools/ts-bindings-gen/Cargo.toml +14 -0
- package/tools/ts-bindings-gen/src/main.rs +147 -0
- package/turbo.json +12 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
use crate::interfaces::DstConfig;
|
|
2
|
+
use common_macros::storage;
|
|
3
|
+
use soroban_sdk::Address;
|
|
4
|
+
use utils::ttl::DefaultTtlConfigProvider;
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Executor Storage
|
|
8
|
+
// ============================================================================
|
|
9
|
+
|
|
10
|
+
#[storage(DefaultTtlConfigProvider)]
|
|
11
|
+
pub enum ExecutorData {
|
|
12
|
+
/// Endpoint V2 address
|
|
13
|
+
#[instance(Address)]
|
|
14
|
+
Endpoint,
|
|
15
|
+
|
|
16
|
+
/// Local endpoint ID (EID)
|
|
17
|
+
#[instance(u32)]
|
|
18
|
+
LocalEid,
|
|
19
|
+
|
|
20
|
+
/// Destination configuration per EID
|
|
21
|
+
#[persistent(DstConfig)]
|
|
22
|
+
DstConfig { eid: u32 },
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "worker-common"
|
|
3
|
+
version.workspace = true
|
|
4
|
+
edition.workspace = true
|
|
5
|
+
license.workspace = true
|
|
6
|
+
publish = false
|
|
7
|
+
|
|
8
|
+
[lib]
|
|
9
|
+
crate-type = ["rlib"]
|
|
10
|
+
doctest = false
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
soroban-sdk = { workspace = true }
|
|
14
|
+
utils = { workspace = true }
|
|
15
|
+
common-macros = { workspace = true }
|
|
16
|
+
stellar-access = { workspace = true }
|
|
17
|
+
stellar-macros = { workspace = true }
|
|
18
|
+
stellar-contract-utils = { workspace = true }
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
use soroban_sdk::{symbol_short, Symbol};
|
|
2
|
+
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Role Constants (matching EVM Worker.sol)
|
|
5
|
+
// ============================================================================
|
|
6
|
+
|
|
7
|
+
/// MESSAGE_LIB_ROLE - Role for message libraries that can interact with the worker
|
|
8
|
+
pub const MESSAGE_LIB_ROLE: Symbol = symbol_short!("MSG_LIB");
|
|
9
|
+
|
|
10
|
+
/// ALLOWLIST - Role for addresses allowed to use the worker
|
|
11
|
+
pub const ALLOWLIST: Symbol = symbol_short!("ALLOW");
|
|
12
|
+
|
|
13
|
+
/// DENYLIST - Role for addresses denied from using the worker
|
|
14
|
+
pub const DENYLIST: Symbol = symbol_short!("DENY");
|
|
15
|
+
|
|
16
|
+
/// ADMIN_ROLE - Role for admins who can configure worker settings
|
|
17
|
+
pub const ADMIN_ROLE: Symbol = symbol_short!("ADMIN");
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
use soroban_sdk::{contractevent, Address, Bytes};
|
|
2
|
+
|
|
3
|
+
// === Worker Events ===
|
|
4
|
+
// Note: Role-related events (RoleGranted, RoleRevoked, etc.) are emitted by OpenZeppelin AccessControl
|
|
5
|
+
#[contractevent(topics = ["SetDefaultMultiplierBps"])]
|
|
6
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
7
|
+
pub struct SetDefaultMultiplierBps {
|
|
8
|
+
pub multiplier_bps: u32,
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
#[contractevent(topics = ["SetDepositAddress"])]
|
|
12
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
13
|
+
pub struct SetDepositAddress {
|
|
14
|
+
pub deposit_address: Address,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[contractevent(topics = ["SetPriceFeed"])]
|
|
18
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
19
|
+
pub struct SetPriceFeed {
|
|
20
|
+
pub price_feed: Address,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#[contractevent(topics = ["SetSupportedOptionTypes"])]
|
|
24
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
25
|
+
pub struct SetSupportedOptionTypes {
|
|
26
|
+
pub dst_eid: u32,
|
|
27
|
+
pub option_types: Bytes,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#[contractevent(topics = ["SetWorkerFeeLib"])]
|
|
31
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
32
|
+
pub struct SetWorkerFeeLib {
|
|
33
|
+
pub fee_lib: Address,
|
|
34
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
use soroban_sdk::{contractclient, contracttype, Address, Bytes, Env};
|
|
2
|
+
|
|
3
|
+
/// FeeParams - Parameters for standard fee calculation same with the Sui
|
|
4
|
+
#[contracttype]
|
|
5
|
+
#[derive(Clone, Debug)]
|
|
6
|
+
pub struct FeeParams {
|
|
7
|
+
// message params
|
|
8
|
+
pub sender: Address,
|
|
9
|
+
pub dst_eid: u32,
|
|
10
|
+
pub calldata_size: u32,
|
|
11
|
+
pub options: Bytes,
|
|
12
|
+
// common configed params
|
|
13
|
+
pub price_feed: Address,
|
|
14
|
+
pub default_multiplier_bps: u32,
|
|
15
|
+
// by dst_eid configed params
|
|
16
|
+
pub lz_receive_base_gas: u64,
|
|
17
|
+
pub lz_compose_base_gas: u64,
|
|
18
|
+
pub floor_margin_usd: u128,
|
|
19
|
+
pub native_cap: u128,
|
|
20
|
+
pub multiplier_bps: u32,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// IExecutorFeeLib - Interface for calculating executor fees
|
|
24
|
+
/// This interface is called by the Executor contract to calculate fees
|
|
25
|
+
/// It uses the PriceFeed interface to get price information
|
|
26
|
+
#[contractclient(name = "ExecutorFeeLibClient")]
|
|
27
|
+
pub trait IExecutorFeeLib {
|
|
28
|
+
/// Calculate fee (view function, standard params)
|
|
29
|
+
/// Called by get_fee in the Executor contract
|
|
30
|
+
fn get_fee(env: &Env, fee_lib: &Address, params: &FeeParams) -> i128;
|
|
31
|
+
|
|
32
|
+
/// Returns the current contract version.
|
|
33
|
+
/// Because not allow return u8, use u32 instead.
|
|
34
|
+
fn version(env: &Env) -> (u64, u32);
|
|
35
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
use soroban_sdk::{contractclient, contracttype, Address, Env};
|
|
2
|
+
|
|
3
|
+
/// Struct to encapsulate the return values of estimate_fee_by_eid
|
|
4
|
+
#[contracttype]
|
|
5
|
+
#[derive(Clone, Debug)]
|
|
6
|
+
pub struct EstimateFeeResult {
|
|
7
|
+
pub fee: i128,
|
|
8
|
+
pub price_ratio: u128,
|
|
9
|
+
pub price_ratio_denominator: u128,
|
|
10
|
+
pub native_price_usd: u128,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/// ILayerZeroPriceFeed - Interface for price feed contract
|
|
14
|
+
/// This interface provides price information for fee calculation
|
|
15
|
+
/// Called by ExecutorFeeLib to get pricing data
|
|
16
|
+
#[contractclient(name = "LayerZeroPriceFeedClient")]
|
|
17
|
+
pub trait ILayerZeroPriceFeed {
|
|
18
|
+
/// Estimate fee with detailed breakdown by endpoint ID
|
|
19
|
+
/// This is the main function called by ExecutorFeeLib
|
|
20
|
+
/// @param dst_eid - Destination endpoint ID
|
|
21
|
+
/// @param calldata_size - Size of the calldata in bytes
|
|
22
|
+
/// @param gas - Gas amount needed for execution
|
|
23
|
+
/// @return (fee, price_ratio, price_ratio_denominator, native_price_usd)
|
|
24
|
+
fn estimate_fee_by_eid(
|
|
25
|
+
env: &Env,
|
|
26
|
+
fee_lib: &Address,
|
|
27
|
+
dst_eid: u32,
|
|
28
|
+
calldata_size: u64,
|
|
29
|
+
gas: u128,
|
|
30
|
+
) -> EstimateFeeResult;
|
|
31
|
+
|
|
32
|
+
/// Get the native token price in USD
|
|
33
|
+
/// @return native_token_price_usd - The price of the native token in USD
|
|
34
|
+
fn native_token_price_usd(env: &Env) -> u128;
|
|
35
|
+
|
|
36
|
+
/// Set the native token price in USD
|
|
37
|
+
/// @param price_updater - The address of the price updater
|
|
38
|
+
/// @param native_token_price_usd - The new price of the native token in USD
|
|
39
|
+
fn set_native_token_price_usd(env: &Env, price_updater: &Address, native_token_price_usd: u128);
|
|
40
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
use soroban_sdk::{contractclient, Address, Bytes, Env};
|
|
2
|
+
|
|
3
|
+
/// IWorker - Base worker interface for managing fees, price feeds, and admin operations
|
|
4
|
+
/// Uses OpenZeppelin AccessControl for role-based access management
|
|
5
|
+
#[contractclient(name = "WorkerClient")]
|
|
6
|
+
pub trait IWorker {
|
|
7
|
+
// ========================================================================
|
|
8
|
+
// Mutable Functions
|
|
9
|
+
// ========================================================================
|
|
10
|
+
|
|
11
|
+
/// Set the paused state of the worker (requires DEFAULT_ADMIN_ROLE)
|
|
12
|
+
fn set_paused(env: &Env, paused: bool);
|
|
13
|
+
|
|
14
|
+
/// Set the default multiplier in basis points (ADMIN_ROLE only)
|
|
15
|
+
fn set_default_multiplier_bps(env: &Env, admin: &Address, multiplier_bps: u32);
|
|
16
|
+
|
|
17
|
+
/// Set the deposit address where fees are collected (admin only)
|
|
18
|
+
fn set_deposit_address(env: &Env, admin: &Address, deposit_address: &Address);
|
|
19
|
+
|
|
20
|
+
/// Set the price feed address (admin only)
|
|
21
|
+
fn set_price_feed(env: &Env, admin: &Address, price_feed: &Address);
|
|
22
|
+
|
|
23
|
+
/// Set supported option types for a destination endpoint (ADMIN_ROLE only)
|
|
24
|
+
fn set_supported_option_types(env: &Env, admin: &Address, eid: u32, option_types: Bytes);
|
|
25
|
+
|
|
26
|
+
/// Set the worker fee library address (ADMIN_ROLE only)
|
|
27
|
+
fn set_worker_fee_lib(env: &Env, admin: &Address, worker_fee_lib: &Address);
|
|
28
|
+
|
|
29
|
+
// ========================================================================
|
|
30
|
+
// View Functions
|
|
31
|
+
// ========================================================================
|
|
32
|
+
|
|
33
|
+
/// Check if the worker is paused
|
|
34
|
+
fn paused(env: &Env) -> bool;
|
|
35
|
+
|
|
36
|
+
/// Get the default multiplier in basis points
|
|
37
|
+
fn default_multiplier_bps(env: &Env) -> u32;
|
|
38
|
+
|
|
39
|
+
/// Get the deposit address
|
|
40
|
+
fn deposit_address(env: &Env) -> Address;
|
|
41
|
+
|
|
42
|
+
/// Get the current price feed address
|
|
43
|
+
fn price_feed(env: &Env) -> Address;
|
|
44
|
+
|
|
45
|
+
/// Get supported option types for a destination endpoint
|
|
46
|
+
fn get_supported_option_types(env: &Env, eid: u32) -> Bytes;
|
|
47
|
+
|
|
48
|
+
/// Get the worker fee library address
|
|
49
|
+
fn worker_fee_lib(env: &Env) -> Address;
|
|
50
|
+
|
|
51
|
+
/// Check if an address has ACL permission
|
|
52
|
+
/// ACL logic (matching EVM Worker.sol):
|
|
53
|
+
/// 1) if address has DENYLIST role -> deny
|
|
54
|
+
/// 2) else if allowlist is empty (size == 0) OR address has ALLOWLIST role -> allow
|
|
55
|
+
/// 3) else deny
|
|
56
|
+
fn has_acl(env: &Env, sender: &Address) -> bool;
|
|
57
|
+
|
|
58
|
+
/// Get the size of the allowlist (number of accounts with ALLOWLIST role)
|
|
59
|
+
fn allowlist_size(env: &Env) -> u32;
|
|
60
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#![no_std]
|
|
2
|
+
|
|
3
|
+
mod interfaces;
|
|
4
|
+
mod worker_common;
|
|
5
|
+
|
|
6
|
+
pub mod constants;
|
|
7
|
+
pub mod errors;
|
|
8
|
+
pub mod events;
|
|
9
|
+
pub mod storage;
|
|
10
|
+
|
|
11
|
+
pub use constants::*;
|
|
12
|
+
pub use errors::*;
|
|
13
|
+
pub use events::*;
|
|
14
|
+
pub use interfaces::*;
|
|
15
|
+
pub use storage::*;
|
|
16
|
+
pub use worker_common::*;
|
|
17
|
+
|
|
18
|
+
// #[cfg(test)]
|
|
19
|
+
// mod tests;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
use common_macros::storage;
|
|
2
|
+
use soroban_sdk::{Address, Bytes};
|
|
3
|
+
use utils::ttl::DefaultTtlConfigProvider;
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Worker Storage
|
|
7
|
+
// ============================================================================
|
|
8
|
+
// Note: Access control (roles) are managed by OpenZeppelin AccessControl module
|
|
9
|
+
// We only keep worker-specific data here
|
|
10
|
+
|
|
11
|
+
#[storage(DefaultTtlConfigProvider)]
|
|
12
|
+
pub enum WorkerData {
|
|
13
|
+
/// Address where fees are deposited
|
|
14
|
+
#[instance(Address)]
|
|
15
|
+
DepositAddress,
|
|
16
|
+
|
|
17
|
+
/// Price feed address for fee calculation
|
|
18
|
+
#[instance(Address)]
|
|
19
|
+
PriceFeed,
|
|
20
|
+
|
|
21
|
+
/// Worker fee library address
|
|
22
|
+
#[instance(Address)]
|
|
23
|
+
WorkerFeeLib,
|
|
24
|
+
|
|
25
|
+
/// Default multiplier in basis points for fee calculation
|
|
26
|
+
#[instance(u32)]
|
|
27
|
+
DefaultMultiplierBps,
|
|
28
|
+
|
|
29
|
+
/// Supported option types for a destination EID
|
|
30
|
+
#[persistent(Bytes)]
|
|
31
|
+
SupportedOptionTypes { eid: u32 },
|
|
32
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
use soroban_sdk::{Address, Bytes, Env, Vec};
|
|
2
|
+
use stellar_access::access_control;
|
|
3
|
+
use stellar_contract_utils::pausable::{self as pausable};
|
|
4
|
+
use stellar_macros::{only_admin, only_role};
|
|
5
|
+
|
|
6
|
+
use crate::{
|
|
7
|
+
constants::{ADMIN_ROLE, ALLOWLIST, DENYLIST, MESSAGE_LIB_ROLE},
|
|
8
|
+
events::{SetDefaultMultiplierBps, SetDepositAddress, SetPriceFeed, SetSupportedOptionTypes, SetWorkerFeeLib},
|
|
9
|
+
storage::WorkerData,
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
// ============================================================================
|
|
13
|
+
// WorkerCommon - Encapsulates worker operations
|
|
14
|
+
// ============================================================================
|
|
15
|
+
|
|
16
|
+
/// WorkerCommon provides shared functionality for LayerZero workers (DVN and Executor)
|
|
17
|
+
/// Uses OpenZeppelin AccessControl for role-based permissions
|
|
18
|
+
pub struct WorkerCommon;
|
|
19
|
+
|
|
20
|
+
impl WorkerCommon {
|
|
21
|
+
// ========================================================================
|
|
22
|
+
// Initialization (Constructor Helper)
|
|
23
|
+
// ========================================================================
|
|
24
|
+
|
|
25
|
+
/// Initialize worker with OpenZeppelin AccessControl
|
|
26
|
+
/// This should be called in the contract's constructor (__constructor)
|
|
27
|
+
pub fn initialize(
|
|
28
|
+
env: &Env,
|
|
29
|
+
role_admin: &Address,
|
|
30
|
+
message_libs: &Vec<Address>,
|
|
31
|
+
price_feed: &Address,
|
|
32
|
+
default_multiplier_bps: u32,
|
|
33
|
+
admins: &Vec<Address>,
|
|
34
|
+
) {
|
|
35
|
+
// Set the DEFAULT_ADMIN_ROLE (the top-level admin)
|
|
36
|
+
access_control::set_admin(env, role_admin);
|
|
37
|
+
|
|
38
|
+
// Grant MESSAGE_LIB_ROLE to all message libraries
|
|
39
|
+
for i in 0..message_libs.len() {
|
|
40
|
+
let message_lib = message_libs.get(i).unwrap();
|
|
41
|
+
access_control::grant_role_no_auth(env, role_admin, &message_lib, &MESSAGE_LIB_ROLE);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Grant ADMIN_ROLE to all admins
|
|
45
|
+
for i in 0..admins.len() {
|
|
46
|
+
let admin_addr = admins.get(i).unwrap();
|
|
47
|
+
access_control::grant_role_no_auth(env, role_admin, &admin_addr, &ADMIN_ROLE);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Initialize worker-specific data
|
|
51
|
+
WorkerData::set_price_feed(env, price_feed);
|
|
52
|
+
WorkerData::set_default_multiplier_bps(env, &default_multiplier_bps);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ========================================================================
|
|
56
|
+
// Mutable Functions
|
|
57
|
+
// ========================================================================
|
|
58
|
+
|
|
59
|
+
/// Set the paused state of the worker (requires DEFAULT_ADMIN_ROLE)
|
|
60
|
+
#[only_admin]
|
|
61
|
+
pub fn set_paused(env: &Env, paused: bool) {
|
|
62
|
+
if paused {
|
|
63
|
+
pausable::pause(env);
|
|
64
|
+
} else {
|
|
65
|
+
pausable::unpause(env);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Set the default multiplier in basis points (ADMIN_ROLE only)
|
|
70
|
+
#[only_role(admin, "ADMIN")]
|
|
71
|
+
pub fn set_default_multiplier_bps(env: &Env, admin: &Address, multiplier_bps: u32) {
|
|
72
|
+
WorkerData::set_default_multiplier_bps(env, &multiplier_bps);
|
|
73
|
+
SetDefaultMultiplierBps { multiplier_bps }.publish(env);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/// Set the deposit address where fees are collected (admin only)
|
|
77
|
+
#[only_role(admin, "ADMIN")]
|
|
78
|
+
pub fn set_deposit_address(env: &Env, admin: &Address, deposit_address: &Address) {
|
|
79
|
+
WorkerData::set_deposit_address(env, deposit_address);
|
|
80
|
+
|
|
81
|
+
SetDepositAddress { deposit_address: deposit_address.clone() }.publish(env);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/// Set the price feed address (admin only)
|
|
85
|
+
#[only_role(admin, "ADMIN")]
|
|
86
|
+
pub fn set_price_feed(env: &Env, admin: &Address, price_feed: &Address) {
|
|
87
|
+
WorkerData::set_price_feed(env, price_feed);
|
|
88
|
+
|
|
89
|
+
SetPriceFeed { price_feed: price_feed.clone() }.publish(env);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/// Set supported option types for a destination endpoint (ADMIN_ROLE only)
|
|
93
|
+
#[only_role(admin, "ADMIN")]
|
|
94
|
+
pub fn set_supported_option_types(env: &Env, admin: &Address, eid: u32, option_types: Bytes) {
|
|
95
|
+
WorkerData::set_supported_option_types(env, eid, &option_types);
|
|
96
|
+
|
|
97
|
+
SetSupportedOptionTypes { dst_eid: eid, option_types }.publish(env);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// Set the worker fee library address (ADMIN_ROLE only)
|
|
101
|
+
#[only_role(admin, "ADMIN")]
|
|
102
|
+
pub fn set_worker_fee_lib(env: &Env, admin: &Address, worker_fee_lib: &Address) {
|
|
103
|
+
WorkerData::set_worker_fee_lib(env, worker_fee_lib);
|
|
104
|
+
|
|
105
|
+
SetWorkerFeeLib { fee_lib: worker_fee_lib.clone() }.publish(env);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ========================================================================
|
|
109
|
+
// View Functions
|
|
110
|
+
// ========================================================================
|
|
111
|
+
|
|
112
|
+
/// Check if the worker is paused
|
|
113
|
+
pub fn paused(env: &Env) -> bool {
|
|
114
|
+
pausable::paused(env)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// Get the default multiplier in basis points
|
|
118
|
+
pub fn default_multiplier_bps(env: &Env) -> u32 {
|
|
119
|
+
WorkerData::default_multiplier_bps(env).unwrap_or(0)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Get the deposit address
|
|
123
|
+
pub fn deposit_address(env: &Env) -> Address {
|
|
124
|
+
WorkerData::deposit_address(env).expect("deposit address not set")
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/// Get the current price feed address
|
|
128
|
+
pub fn price_feed(env: &Env) -> Address {
|
|
129
|
+
WorkerData::price_feed(env).expect("price feed not set")
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/// Get supported option types for a destination endpoint
|
|
133
|
+
pub fn get_supported_option_types(env: &Env, eid: u32) -> Bytes {
|
|
134
|
+
WorkerData::supported_option_types(env, eid).unwrap_or(Bytes::new(env))
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/// Get the worker fee library address
|
|
138
|
+
pub fn worker_fee_lib(env: &Env) -> Address {
|
|
139
|
+
WorkerData::worker_fee_lib(env).expect("worker fee lib not set")
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// Check if an address has ACL permission
|
|
143
|
+
/// ACL logic (matching EVM Worker.sol):
|
|
144
|
+
/// 1) if address has DENYLIST role -> deny
|
|
145
|
+
/// 2) else if allowlist is empty (size == 0) OR address has ALLOWLIST role -> allow
|
|
146
|
+
/// 3) else deny
|
|
147
|
+
pub fn has_acl(env: &Env, sender: &Address) -> bool {
|
|
148
|
+
// Check denylist first
|
|
149
|
+
if access_control::has_role(env, sender, &DENYLIST).is_some() {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Check allowlist size and membership
|
|
154
|
+
let allowlist_size = Self::allowlist_size(env);
|
|
155
|
+
if allowlist_size == 0 || access_control::has_role(env, sender, &ALLOWLIST).is_some() {
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
false
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// Get the size of the allowlist (number of accounts with ALLOWLIST role)
|
|
163
|
+
pub fn allowlist_size(env: &Env) -> u32 {
|
|
164
|
+
access_control::get_role_member_count(env, &ALLOWLIST)
|
|
165
|
+
}
|
|
166
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@layerzerolabs/protocol-stellar-v2",
|
|
3
|
+
"version": "0.2.8",
|
|
4
|
+
"private": false,
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"@types/node": "^22.18.6",
|
|
7
|
+
"tsx": "^4.19.3",
|
|
8
|
+
"typescript": "^5.8.2",
|
|
9
|
+
"@layerzerolabs/common-node-utils": "0.2.8",
|
|
10
|
+
"@layerzerolabs/vm-tooling-stellar": "0.2.8"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "restricted",
|
|
14
|
+
"registry": "https://registry.npmjs.org/"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "pnpm build:contracts && pnpm generate:sdk",
|
|
18
|
+
"build:contracts": "pnpm exec lz-tool stellar contract build",
|
|
19
|
+
"format": "pnpm exec lz-tool --script \"cargo fmt\" stellar",
|
|
20
|
+
"generate:sdk": "pnpm exec lz-tool --script \"cargo run -p ts-bindings-gen\" stellar",
|
|
21
|
+
"lint": "pnpm exec lz-tool --script \"cargo clippy -- -D warnings\" stellar",
|
|
22
|
+
"lint:fix": "pnpm exec lz-tool --script \"cargo clippy --fix --allow-dirty --allow-staged && cargo fmt\" stellar",
|
|
23
|
+
"test": "pnpm exec lz-tool --script \"cargo nextest run\" stellar"
|
|
24
|
+
}
|
|
25
|
+
}
|
package/rustfmt.toml
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
format_macro_bodies = true
|
|
3
|
+
max_width = 120
|
|
4
|
+
format_macro_matchers = true
|
|
5
|
+
format_strings = true
|
|
6
|
+
imports_granularity = "Crate"
|
|
7
|
+
match_arm_blocks = false
|
|
8
|
+
reorder_impl_items = true
|
|
9
|
+
group_imports = "StdExternalCrate"
|
|
10
|
+
use_field_init_shorthand = true
|
|
11
|
+
use_small_heuristics = "Max"
|
|
12
|
+
wrap_comments = true
|
|
13
|
+
format_code_in_doc_comments = true
|
|
14
|
+
|
|
15
|
+
# most of these are unstable, so we enable them
|
|
16
|
+
unstable_features = true
|
|
17
|
+
|