@layerzerolabs/protocol-stellar-v2 0.2.10 → 0.2.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/.turbo/turbo-build.log +273 -219
  2. package/.turbo/turbo-lint.log +79 -107
  3. package/.turbo/turbo-test.log +1016 -840
  4. package/Cargo.lock +14 -6
  5. package/contracts/common-macros/src/contract_impl.rs +6 -3
  6. package/contracts/common-macros/src/error.rs +9 -17
  7. package/contracts/common-macros/src/lib.rs +4 -37
  8. package/contracts/common-macros/src/ownable.rs +9 -5
  9. package/contracts/common-macros/src/tests/contract_impl.rs +178 -86
  10. package/contracts/common-macros/src/tests/error.rs +168 -0
  11. package/contracts/common-macros/src/tests/mod.rs +2 -4
  12. package/contracts/common-macros/src/tests/ownable.rs +37 -60
  13. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__contract_impl__snapshot_generated_contract_impl_code.snap +16 -6
  14. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
  15. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_generated_ownable_code.snap +3 -1
  16. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_only_owner_preserves_function_signature.snap +12 -2
  17. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +5 -1
  18. package/contracts/common-macros/src/tests/utils.rs +267 -0
  19. package/contracts/common-macros/src/ttl_configurable.rs +15 -12
  20. package/contracts/common-macros/src/utils.rs +35 -6
  21. package/contracts/endpoint-v2/src/endpoint_v2.rs +4 -4
  22. package/contracts/endpoint-v2/src/events.rs +40 -22
  23. package/contracts/endpoint-v2/src/interfaces/message_lib.rs +2 -2
  24. package/contracts/endpoint-v2/src/interfaces/message_lib_manager.rs +2 -2
  25. package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +2 -2
  26. package/contracts/endpoint-v2/src/interfaces/messaging_composer.rs +2 -2
  27. package/contracts/endpoint-v2/src/interfaces/send_lib.rs +2 -2
  28. package/contracts/endpoint-v2/src/message_lib_manager.rs +3 -3
  29. package/contracts/endpoint-v2/src/messaging_channel.rs +1 -1
  30. package/contracts/endpoint-v2/src/messaging_composer.rs +1 -1
  31. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +4 -8
  32. package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +3 -7
  33. package/contracts/message-libs/{block-message-lib → blocked-message-lib}/Cargo.toml +1 -1
  34. package/contracts/message-libs/treasury/src/events.rs +9 -6
  35. package/contracts/message-libs/uln-302/src/events.rs +19 -11
  36. package/contracts/message-libs/uln-302/src/interfaces/receive_uln.rs +2 -2
  37. package/contracts/message-libs/uln-302/src/interfaces/send_uln.rs +2 -2
  38. package/contracts/message-libs/uln-302/src/receive_uln.rs +2 -2
  39. package/contracts/message-libs/uln-302/src/send_uln.rs +3 -3
  40. package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +5 -5
  41. package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +5 -5
  42. package/contracts/message-libs/uln-302/src/tests/setup.rs +3 -3
  43. package/contracts/message-libs/uln-302/src/types.rs +24 -24
  44. package/contracts/message-libs/uln-302/src/uln302.rs +2 -2
  45. package/contracts/oapp-macros/src/oapp_core.rs +1 -1
  46. package/contracts/oapps/counter/integration_tests/utils.rs +1 -1
  47. package/contracts/oapps/oapp/src/oapp_core.rs +4 -3
  48. package/contracts/oapps/oapp/src/oapp_options_type3.rs +4 -3
  49. package/contracts/oapps/oft/integration-tests/setup.rs +4 -3
  50. package/contracts/oapps/oft/integration-tests/utils.rs +1 -1
  51. package/contracts/oapps/oft/src/default_oft_impl.rs +146 -0
  52. package/contracts/oapps/oft/src/events.rs +5 -4
  53. package/contracts/oapps/oft/src/extensions/mod.rs +3 -0
  54. package/contracts/oapps/oft/src/extensions/oft_fee.rs +168 -0
  55. package/contracts/oapps/oft/src/extensions/pausable.rs +50 -0
  56. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +200 -0
  57. package/contracts/oapps/oft/src/lib.rs +2 -3
  58. package/contracts/oapps/oft/src/oft.rs +16 -85
  59. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
  60. package/contracts/oapps/oft/src/tests/extensions/mod.rs +11 -0
  61. package/contracts/oapps/oft/src/tests/extensions/setup.rs +888 -0
  62. package/contracts/oapps/oft/src/tests/extensions/test_oft_fee.rs +749 -0
  63. package/contracts/oapps/oft/src/tests/extensions/test_pausable.rs +432 -0
  64. package/contracts/oapps/oft/src/tests/extensions/test_rate_limiter.rs +1078 -0
  65. package/contracts/oapps/oft/src/tests/mod.rs +2 -0
  66. package/contracts/oapps/oft/src/tests/test_utils.rs +24 -6
  67. package/contracts/oapps/{oft-mint-burn → oft-std}/Cargo.toml +1 -8
  68. package/contracts/oapps/oft-std/src/lib.rs +5 -0
  69. package/contracts/oapps/oft-std/src/oft.rs +59 -0
  70. package/contracts/utils/src/ownable.rs +8 -6
  71. package/contracts/utils/src/tests/ownable.rs +0 -63
  72. package/contracts/utils/src/tests/testing_utils.rs +7 -5
  73. package/contracts/utils/src/ttl.rs +21 -2
  74. package/contracts/workers/dvn/src/auth.rs +108 -30
  75. package/contracts/workers/dvn/src/dvn.rs +103 -33
  76. package/contracts/workers/dvn/src/errors.rs +10 -13
  77. package/contracts/workers/dvn/src/events.rs +7 -5
  78. package/contracts/workers/dvn/src/interfaces/dvn.rs +76 -3
  79. package/contracts/workers/dvn/src/interfaces/multisig.rs +41 -0
  80. package/contracts/workers/dvn/src/lib.rs +6 -8
  81. package/contracts/workers/dvn/src/multisig.rs +98 -72
  82. package/contracts/workers/dvn/src/storage.rs +9 -12
  83. package/contracts/workers/dvn/src/tests/auth.rs +56 -26
  84. package/contracts/workers/dvn/src/tests/dvn.rs +40 -41
  85. package/contracts/workers/dvn/src/tests/multisig/set_signer.rs +8 -8
  86. package/contracts/workers/dvn/src/tests/multisig/set_threshold.rs +9 -9
  87. package/contracts/workers/dvn/src/tests/multisig/verify_signatures.rs +6 -6
  88. package/contracts/workers/dvn/src/tests/setup.rs +5 -5
  89. package/contracts/workers/dvn-fee-lib/Cargo.toml +2 -1
  90. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +4 -3
  91. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +8 -6
  92. package/contracts/workers/executor/src/auth.rs +93 -0
  93. package/contracts/workers/executor/src/events.rs +5 -4
  94. package/contracts/workers/executor/src/{lz_executor.rs → executor.rs} +30 -103
  95. package/contracts/workers/executor/src/interfaces/executor.rs +5 -2
  96. package/contracts/workers/executor/src/interfaces/mod.rs +1 -1
  97. package/contracts/workers/executor/src/lib.rs +6 -5
  98. package/contracts/workers/price-feed/Cargo.toml +21 -0
  99. package/contracts/workers/price-feed/src/errors.rs +9 -0
  100. package/contracts/workers/price-feed/src/events.rs +30 -0
  101. package/contracts/workers/price-feed/src/lib.rs +11 -0
  102. package/contracts/workers/price-feed/src/price_feed.rs +265 -0
  103. package/contracts/workers/price-feed/src/storage.rs +42 -0
  104. package/contracts/workers/price-feed/src/types.rs +59 -0
  105. package/contracts/workers/worker/src/events.rs +23 -13
  106. package/contracts/workers/worker/src/interfaces/dvn_fee_lib.rs +2 -1
  107. package/contracts/workers/worker/src/worker.rs +32 -21
  108. package/package.json +3 -3
  109. package/sdk/dist/generated/bml.js +24 -22
  110. package/sdk/dist/generated/counter.d.ts +102 -0
  111. package/sdk/dist/generated/counter.js +36 -24
  112. package/sdk/dist/generated/endpoint.js +24 -22
  113. package/sdk/dist/generated/sml.js +24 -22
  114. package/sdk/dist/generated/uln302.d.ts +1 -1
  115. package/sdk/dist/generated/uln302.js +34 -32
  116. package/sdk/package.json +1 -1
  117. package/sdk/test/index.test.ts +1 -1
  118. package/sdk/test/oft.test.ts +847 -0
  119. package/sdk/test/suites/scan.ts +20 -4
  120. package/tools/ts-bindings-gen/src/main.rs +2 -1
  121. package/contracts/common-macros/src/event.rs +0 -16
  122. package/contracts/oapps/oft/src/macro_tests/mod.rs +0 -2
  123. package/contracts/oapps/oft/src/macro_tests/test_all_default.rs +0 -41
  124. package/contracts/oapps/oft/src/macro_tests/test_override.rs +0 -83
  125. package/contracts/oapps/oft-mint-burn/src/lib.rs +0 -3
  126. package/contracts/oapps/oft-mint-burn/src/oft.rs +0 -28
  127. package/contracts/oapps/oft-mint-burn/src/tests/mod.rs +0 -1
  128. package/contracts/workers/dvn/src/types.rs +0 -26
  129. /package/contracts/message-libs/{block-message-lib → blocked-message-lib}/src/lib.rs +0 -0
@@ -1,15 +1,16 @@
1
- use common_macros::event;
2
1
  use endpoint_v2::Origin;
3
- use soroban_sdk::{Address, Vec};
2
+ use soroban_sdk::{contractevent, Address, Vec};
4
3
 
5
4
  use crate::interfaces::{NativeDropParams, SetDstConfigParam};
6
5
 
7
- #[event]
6
+ #[contractevent]
7
+ #[derive(Clone, Debug, Eq, PartialEq)]
8
8
  pub struct DstConfigSet {
9
9
  pub params: Vec<SetDstConfigParam>,
10
10
  }
11
11
 
12
- #[event]
12
+ #[contractevent]
13
+ #[derive(Clone, Debug, Eq, PartialEq)]
13
14
  pub struct NativeDropApplied {
14
15
  pub origin: Origin,
15
16
  pub dst_eid: u32,
@@ -8,44 +8,14 @@ use crate::{
8
8
  use common_macros::{contract_impl, only_owner, ttl_configurable};
9
9
  use endpoint_v2::{FeeRecipient, LayerZeroEndpointV2Client, Origin};
10
10
  use message_lib_common::interfaces::ILayerZeroExecutor;
11
- use soroban_sdk::{
12
- address_payload::AddressPayload,
13
- auth::{Context, CustomAccountInterface},
14
- contract, contractimpl, contracttype,
15
- crypto::Hash,
16
- token::TokenClient,
17
- vec, Address, Bytes, BytesN, Env, Symbol, Vec,
18
- };
11
+ use soroban_sdk::{contract, token::TokenClient, vec, Address, Bytes, BytesN, Env, Symbol, Vec};
19
12
  use utils::option_ext::OptionExt;
20
13
  use utils::ownable::Ownable;
21
14
  use worker::{
22
- assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, ExecutorFeeLibClient,
23
- FeeParams, Worker, set_admin_by_owner,
15
+ assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, set_admin_by_owner,
16
+ ExecutorFeeLibClient, FeeParams, Worker,
24
17
  };
25
18
 
26
- /// Signature data for Custom Account authorization.
27
- /// Contains the admin's public key and their Ed25519 signature over the authorization payload.
28
- #[contracttype]
29
- #[derive(Clone, Debug, Eq, PartialEq)]
30
- pub struct ExecutorSignature {
31
- /// Admin's Ed25519 public key (32 bytes) - must correspond to a registered admin
32
- pub public_key: BytesN<32>,
33
- /// Ed25519 signature (64 bytes) over the signature_payload
34
- pub signature: BytesN<64>,
35
- }
36
-
37
- /// Whitelist entry for authorized (contract, function) pairs.
38
- ///
39
- /// Used to configure which contracts and functions can trigger Executor authorization.
40
- #[contracttype]
41
- #[derive(Clone, Debug, Eq, PartialEq)]
42
- pub struct WhitelistEntry {
43
- /// Contract address that is allowed to call the function.
44
- pub contract: Address,
45
- /// Function name that is whitelisted for this contract.
46
- pub fn_name: Symbol,
47
- }
48
-
49
19
  /// LayerZero Executor contract for cross-chain message execution.
50
20
  #[contract]
51
21
  #[ttl_configurable]
@@ -65,6 +35,8 @@ impl LzExecutor {
65
35
  /// * `price_feed` - Price feed contract address for fee calculations
66
36
  /// * `default_multiplier_bps` - Default fee multiplier in basis points (10000 = 1x)
67
37
  /// * `whitelist` - Initial whitelisted (contract, function) pairs for authorization
38
+ /// * `worker_fee_lib` - Worker fee library contract address
39
+ /// * `deposit_address` - Address to receive fee payments
68
40
  pub fn __constructor(
69
41
  env: &Env,
70
42
  endpoint: &Address,
@@ -73,15 +45,25 @@ impl LzExecutor {
73
45
  message_libs: &Vec<Address>,
74
46
  price_feed: &Address,
75
47
  default_multiplier_bps: u32,
76
- whitelist: &Vec<WhitelistEntry>,
48
+ whitelist: &Vec<(Address, Symbol)>,
49
+ worker_fee_lib: &Address,
50
+ deposit_address: &Address,
77
51
  ) {
78
52
  Self::init_owner(env, owner);
79
- init_worker::<Self>(env, admins, message_libs, price_feed, default_multiplier_bps);
53
+ init_worker::<Self>(
54
+ env,
55
+ admins,
56
+ message_libs,
57
+ price_feed,
58
+ default_multiplier_bps,
59
+ worker_fee_lib,
60
+ deposit_address,
61
+ );
80
62
  ExecutorStorage::set_endpoint(env, endpoint);
81
63
 
82
64
  // Set initial whitelisted functions
83
- for entry in whitelist.iter() {
84
- ExecutorStorage::set_whitelisted_fn(env, &entry.contract, &entry.fn_name, &true);
65
+ for (contract, fn_name) in whitelist.iter() {
66
+ ExecutorStorage::set_whitelisted_fn(env, &contract, &fn_name, &true);
85
67
  }
86
68
  }
87
69
 
@@ -140,8 +122,8 @@ impl IExecutor for LzExecutor {
140
122
  }
141
123
 
142
124
  /// Returns the destination configuration for a specific endpoint.
143
- fn dst_config(env: &Env, dst_eid: u32) -> DstConfig {
144
- ExecutorStorage::dst_config(env, dst_eid).unwrap_or_panic(env, ExecutorError::EidNotSupported)
125
+ fn dst_config(env: &Env, dst_eid: u32) -> Option<DstConfig> {
126
+ ExecutorStorage::dst_config(env, dst_eid)
145
127
  }
146
128
 
147
129
  /// Native token drops.
@@ -219,7 +201,7 @@ impl ILayerZeroExecutor for LzExecutor {
219
201
  assert_not_paused::<Self>(env);
220
202
  assert_acl::<Self>(env, sender);
221
203
 
222
- let dst_config = Self::dst_config(env, dst_eid);
204
+ let dst_config = Self::dst_config(env, dst_eid).unwrap_or_panic(env, ExecutorError::EidNotSupported);
223
205
  let fee_params = FeeParams {
224
206
  sender: sender.clone(),
225
207
  dst_eid,
@@ -239,70 +221,15 @@ impl ILayerZeroExecutor for LzExecutor {
239
221
  }
240
222
 
241
223
  // ============================================================================
242
- // CustomAccountInterface implementation, used for receive-flow execution
224
+ // Worker Implementation
243
225
  // ============================================================================
244
226
 
245
- #[contractimpl]
246
- impl CustomAccountInterface for LzExecutor {
247
- type Signature = ExecutorSignature;
248
- type Error = ExecutorError;
249
-
250
- /// Verifies authorization for the executor contract.
251
- ///
252
- /// The public key must correspond to a registered admin and must have signed the signature_payload.
253
- /// Uses Ed25519 signature verification.
254
- /// Only whitelisted function calls are authorized.
255
- fn __check_auth(
256
- env: Env,
257
- signature_payload: Hash<32>,
258
- auth_data: Self::Signature,
259
- auth_contexts: Vec<Context>,
260
- ) -> Result<(), Self::Error> {
261
- Self::verify_admin_signature(&env, &signature_payload, &auth_data)?;
262
- Self::validate_auth_contexts(&env, &auth_contexts)?;
263
- Ok(())
264
- }
265
- }
266
-
267
- impl LzExecutor {
268
- /// Verifies that the signature is from a registered admin.
269
- ///
270
- /// Converts the public key to an address, checks admin registration,
271
- /// and verifies the Ed25519 signature over the payload.
272
- fn verify_admin_signature(
273
- env: &Env,
274
- signature_payload: &Hash<32>,
275
- auth_data: &ExecutorSignature,
276
- ) -> Result<(), ExecutorError> {
277
- let admin = Address::from_payload(env, AddressPayload::AccountIdPublicKeyEd25519(auth_data.public_key.clone()));
278
- if !Self::is_admin(env, &admin) {
279
- return Err(ExecutorError::Unauthorized);
280
- }
281
- env.crypto().ed25519_verify(&auth_data.public_key, &signature_payload.clone().into(), &auth_data.signature);
282
- Ok(())
283
- }
284
-
285
- /// Validates that the first auth context is a whitelisted (contract, function) pair.
286
- /// Sub-invocations are trusted since they're controlled by the whitelisted contract.
287
- fn validate_auth_contexts(env: &Env, contexts: &Vec<Context>) -> Result<(), ExecutorError> {
288
- let first_context = contexts.first().ok_or(ExecutorError::UnauthorizedContext)?;
289
-
290
- match first_context {
291
- Context::Contract(contract_context) => {
292
- // Check if (contract, fn_name) pair is whitelisted
293
- if !Self::is_whitelisted_fn(env, &contract_context.contract, &contract_context.fn_name) {
294
- return Err(ExecutorError::UnauthorizedContext);
295
- }
296
- }
297
- // Contract creation is not allowed
298
- Context::CreateContractHostFn(_) | Context::CreateContractWithCtorHostFn(_) => {
299
- return Err(ExecutorError::UnauthorizedContext);
300
- }
301
- }
227
+ #[contract_impl(contracttrait)]
228
+ impl Worker for LzExecutor {}
302
229
 
303
- Ok(())
304
- }
305
- }
230
+ // ============================================================================
231
+ // Include SubModules
232
+ // ============================================================================
306
233
 
307
- #[contractimpl(contracttrait)]
308
- impl Worker for LzExecutor {}
234
+ #[path = "auth.rs"]
235
+ pub mod auth;
@@ -59,11 +59,14 @@ pub trait IExecutor: Worker + ILayerZeroExecutor {
59
59
  /// * `params` - Vector of (dst_eid, DstConfig) pairs to set
60
60
  fn set_dst_config(env: &Env, admin: &Address, params: &Vec<SetDstConfigParam>);
61
61
 
62
- /// Returns the destination configuration for a specific endpoint.
62
+ /// Gets the destination configuration for a specific endpoint.
63
63
  ///
64
64
  /// # Arguments
65
65
  /// * `dst_eid` - Destination endpoint ID (chain identifier)
66
- fn dst_config(env: &Env, dst_eid: u32) -> DstConfig;
66
+ ///
67
+ /// # Returns
68
+ /// The destination configuration, or `None` if not set
69
+ fn dst_config(env: &Env, dst_eid: u32) -> Option<DstConfig>;
67
70
 
68
71
  /// Native token drops.
69
72
  ///
@@ -1,3 +1,3 @@
1
- pub mod executor;
1
+ mod executor;
2
2
 
3
3
  pub use executor::*;
@@ -1,17 +1,18 @@
1
1
  #![no_std]
2
2
 
3
- mod interfaces;
3
+ pub mod errors;
4
+ pub mod events;
5
+ pub mod interfaces;
6
+
4
7
  pub use interfaces::*;
5
8
 
6
9
  cfg_if::cfg_if! {
7
10
  // Include implementation when NOT in library mode, OR when testutils is enabled (for tests)
8
11
  if #[cfg(any(not(feature = "library"), feature = "testutils"))] {
9
- mod errors;
10
- mod events;
11
12
  mod storage;
12
- mod lz_executor;
13
+ mod executor;
13
14
 
14
- pub use lz_executor::{LzExecutor, LzExecutorClient, WhitelistEntry};
15
+ pub use executor::*;
15
16
  }
16
17
  }
17
18
 
@@ -0,0 +1,21 @@
1
+ [package]
2
+ name = "price-feed"
3
+ version.workspace = true
4
+ edition.workspace = true
5
+ license.workspace = true
6
+ publish = false
7
+
8
+ [lib]
9
+ crate-type = ["cdylib"]
10
+ doctest = false
11
+
12
+ [dependencies]
13
+ soroban-sdk = { workspace = true }
14
+ endpoint-v2 = { workspace = true, features = ["library"] }
15
+ utils = { workspace = true }
16
+ worker = { workspace = true }
17
+ common-macros = { workspace = true }
18
+
19
+ [dev-dependencies]
20
+ soroban-sdk = { workspace = true, features = ["testutils"] }
21
+
@@ -0,0 +1,9 @@
1
+ use common_macros::contract_error;
2
+
3
+ #[contract_error]
4
+ pub enum PriceFeedError {
5
+ OnlyPriceUpdater,
6
+ InvalidDenominator,
7
+ NotAnOpStack,
8
+ NoPrice,
9
+ }
@@ -0,0 +1,30 @@
1
+ use soroban_sdk::{contractevent, Address};
2
+
3
+ use worker::Price;
4
+
5
+ use crate::types::ArbitrumPriceExt;
6
+
7
+ // ============================================================================
8
+ // Price Feed Events
9
+ // ============================================================================
10
+
11
+ #[contractevent]
12
+ #[derive(Clone, Debug, Eq, PartialEq)]
13
+ pub struct PriceUpdaterSet {
14
+ pub updater: Address,
15
+ pub active: bool,
16
+ }
17
+
18
+ #[contractevent]
19
+ #[derive(Clone, Debug, Eq, PartialEq)]
20
+ pub struct PriceUpdated {
21
+ pub dst_eid: u32,
22
+ pub price: Price,
23
+ }
24
+
25
+ #[contractevent]
26
+ #[derive(Clone, Debug, Eq, PartialEq)]
27
+ pub struct ArbitrumPriceExtUpdated {
28
+ pub dst_eid: u32,
29
+ pub arbitrum_price_ext: ArbitrumPriceExt,
30
+ }
@@ -0,0 +1,11 @@
1
+ #![no_std]
2
+
3
+ mod errors;
4
+ mod events;
5
+ mod price_feed;
6
+ mod storage;
7
+ mod types;
8
+
9
+ pub use errors::*;
10
+ pub use price_feed::{LzPriceFeed, LzPriceFeedClient};
11
+ pub use types::*;
@@ -0,0 +1,265 @@
1
+ use common_macros::{only_owner, ttl_configurable};
2
+ use soroban_sdk::{assert_with_error, contract, contractimpl, panic_with_error, Address, Env, Vec};
3
+ use utils::ownable::Ownable;
4
+ use worker::{FeeEstimate, ILayerZeroPriceFeed, Price};
5
+
6
+ use crate::{
7
+ errors::PriceFeedError,
8
+ events::{ArbitrumPriceExtUpdated, PriceUpdated, PriceUpdaterSet},
9
+ storage::PriceFeedStorage,
10
+ types::{ArbitrumPriceExt, ModelType, SetEidToModelTypeParam, UpdatePrice, UpdatePriceExt},
11
+ };
12
+
13
+ #[contract]
14
+ #[ttl_configurable]
15
+ pub struct LzPriceFeed;
16
+
17
+ #[contractimpl]
18
+ impl LzPriceFeed {
19
+ pub fn __constructor(env: &Env, owner: &Address, price_updater: &Address) {
20
+ Self::init_owner(env, owner);
21
+
22
+ PriceFeedStorage::set_price_updater(env, price_updater, &true);
23
+ }
24
+ }
25
+
26
+ #[contractimpl]
27
+ impl ILayerZeroPriceFeed for LzPriceFeed {
28
+ /// Estimate fee with detailed breakdown by endpoint ID
29
+ /// Corresponds to estimateFeeByEid in PriceFeed.sol
30
+ fn estimate_fee_by_eid(env: &Env, fee_lib: &Address, dst_eid: u32, calldata_size: u32, gas: u128) -> FeeEstimate {
31
+ fee_lib.require_auth();
32
+
33
+ let eid = dst_eid % 30_000;
34
+
35
+ let (fee, price_ratio) = if eid == 110 || eid == 10143 || eid == 20143 {
36
+ Self::estimate_fee_with_arbitrum_model(env, eid, calldata_size, gas)
37
+ } else if eid == 111 || eid == 10132 || eid == 20132 {
38
+ Self::estimate_fee_with_optimism_model(env, eid, calldata_size, gas)
39
+ } else {
40
+ // Check configured model type
41
+ let model_type = Self::eid_to_model_type(env, eid);
42
+ match model_type {
43
+ ModelType::OpStack => Self::estimate_fee_with_optimism_model(env, eid, calldata_size, gas),
44
+ ModelType::ArbStack => Self::estimate_fee_with_arbitrum_model(env, eid, calldata_size, gas),
45
+ ModelType::Default => Self::estimate_fee_with_default_model(env, eid, calldata_size, gas),
46
+ }
47
+ };
48
+
49
+ let price_ratio_denominator = Self::get_price_ratio_denominator(env);
50
+ let native_price_usd = Self::native_token_price_usd(env);
51
+
52
+ FeeEstimate { total_gas_fee: fee as i128, price_ratio, price_ratio_denominator, native_price_usd }
53
+ }
54
+
55
+ /// Get the native token price in USD
56
+ fn native_token_price_usd(env: &Env) -> u128 {
57
+ PriceFeedStorage::native_price_usd(env)
58
+ }
59
+
60
+ /// Get the price for a destination EID.
61
+ fn get_price(env: &Env, dst_eid: u32) -> Price {
62
+ PriceFeedStorage::default_model_price(env, dst_eid)
63
+ .unwrap_or_else(|| panic_with_error!(env, PriceFeedError::NoPrice))
64
+ }
65
+
66
+ /// Get the price ratio denominator.
67
+ fn get_price_ratio_denominator(env: &Env) -> u128 {
68
+ PriceFeedStorage::price_ratio_denominator(env)
69
+ }
70
+ }
71
+
72
+ #[contractimpl]
73
+ impl LzPriceFeed {
74
+ // ========================================================================
75
+ // Owner Functions
76
+ // ========================================================================
77
+
78
+ /// Set price updater status (owner only)
79
+ #[only_owner]
80
+ pub fn set_price_updater(env: &Env, updater: &Address, active: bool) {
81
+ PriceFeedStorage::set_price_updater(env, updater, &active);
82
+ PriceUpdaterSet { updater: updater.clone(), active }.publish(env);
83
+ }
84
+
85
+ /// Set the price ratio denominator (owner only)
86
+ #[only_owner]
87
+ pub fn set_price_ratio_denominator(env: &Env, denominator: u128) {
88
+ assert_with_error!(env, denominator > 0, PriceFeedError::InvalidDenominator);
89
+ PriceFeedStorage::set_price_ratio_denominator(env, &denominator);
90
+ }
91
+
92
+ /// Set the Arbitrum compression percentage (owner only)
93
+ #[only_owner]
94
+ pub fn set_arbitrum_compression_percent(env: &Env, compression_percent: u128) {
95
+ PriceFeedStorage::set_arbitrum_compression_percent(env, &compression_percent);
96
+ }
97
+
98
+ /// Set the fee model type for destination EIDs (owner only)
99
+ #[only_owner]
100
+ pub fn set_eid_to_model_type(env: &Env, params: &Vec<SetEidToModelTypeParam>) {
101
+ for param in params.iter() {
102
+ PriceFeedStorage::set_eid_to_model_type(env, param.dst_eid, &param.model_type);
103
+ }
104
+ }
105
+
106
+ // ========================================================================
107
+ // Price Updater Functions
108
+ // ========================================================================
109
+
110
+ /// Set prices for multiple destinations (price updater or owner)
111
+ pub fn set_price(env: &Env, price_updater: &Address, prices: &Vec<UpdatePrice>) {
112
+ Self::require_price_updater(env, price_updater);
113
+
114
+ for update in prices.iter() {
115
+ Self::set_price_internal(env, update.eid, &update.price);
116
+ }
117
+ }
118
+
119
+ /// Set price for Arbitrum with extension (price updater or owner)
120
+ /// Corresponds to setPriceForArbitrum in PriceFeed.sol
121
+ pub fn set_price_for_arbitrum(env: &Env, price_updater: &Address, update: &UpdatePriceExt) {
122
+ Self::require_price_updater(env, price_updater);
123
+
124
+ Self::set_price_internal(env, update.eid, &update.price);
125
+
126
+ // Update Arbitrum-specific price extension
127
+ PriceFeedStorage::set_arbitrum_price_ext(env, &update.extend);
128
+ ArbitrumPriceExtUpdated { dst_eid: update.eid, arbitrum_price_ext: update.extend.clone() }.publish(env);
129
+ }
130
+
131
+ /// Set the native token price in USD (price updater or owner).
132
+ ///
133
+ /// Kept as a standalone contract function (not part of the canonical `worker::ILayerZeroPriceFeed` interface).
134
+ pub fn set_native_token_price_usd(env: &Env, price_updater: &Address, native_token_price_usd: u128) {
135
+ Self::require_price_updater(env, price_updater);
136
+ PriceFeedStorage::set_native_price_usd(env, &native_token_price_usd);
137
+ }
138
+
139
+ // ========================================================================
140
+ // View Functions
141
+ // ========================================================================
142
+
143
+ /// Check if an address is an active price updater
144
+ pub fn price_updater(env: &Env, updater: &Address) -> bool {
145
+ PriceFeedStorage::price_updater(env, updater)
146
+ }
147
+
148
+ /// Get the Arbitrum compression percent
149
+ pub fn arbitrum_compression_percent(env: &Env) -> u128 {
150
+ PriceFeedStorage::arbitrum_compression_percent(env)
151
+ }
152
+
153
+ /// Get the Arbitrum price extension
154
+ pub fn arbitrum_price_ext(env: &Env) -> ArbitrumPriceExt {
155
+ PriceFeedStorage::arbitrum_price_ext(env)
156
+ }
157
+
158
+ /// Get the model type for a destination EID
159
+ pub fn eid_to_model_type(env: &Env, dst_eid: u32) -> ModelType {
160
+ PriceFeedStorage::eid_to_model_type(env, dst_eid)
161
+ }
162
+
163
+ // ========================================================================
164
+ // Internal Helper Functions
165
+ // ========================================================================
166
+
167
+ /// Set price for a destination EID
168
+ fn set_price_internal(env: &Env, dst_eid: u32, price: &Price) {
169
+ PriceFeedStorage::set_default_model_price(env, dst_eid, price);
170
+ PriceUpdated { dst_eid, price: price.clone() }.publish(env);
171
+ }
172
+
173
+ /// Estimate fee with default model
174
+ fn estimate_fee_with_default_model(env: &Env, dst_eid: u32, calldata_size: u32, gas: u128) -> (u128, u128) {
175
+ let price = Self::get_price(env, dst_eid);
176
+
177
+ // assuming the _gas includes (1) the 21,000 overhead and (2) not the calldata gas
178
+ let gas_for_calldata = (calldata_size as u128) * (price.gas_per_byte as u128);
179
+ let remote_fee = (gas_for_calldata + gas) * (price.gas_price_in_unit as u128);
180
+ let fee = (remote_fee * price.price_ratio) / Self::get_price_ratio_denominator(env);
181
+
182
+ (fee, price.price_ratio)
183
+ }
184
+
185
+ /// Estimate fee with Optimism model
186
+ fn estimate_fee_with_optimism_model(env: &Env, dst_eid: u32, calldata_size: u32, gas: u128) -> (u128, u128) {
187
+ let ethereum_id = Self::get_l1_lookup_id_for_optimism_model(env, dst_eid);
188
+
189
+ // L1 fee (Ethereum)
190
+ let ethereum_price = Self::get_price(env, ethereum_id);
191
+ let gas_for_l1_calldata = ((calldata_size as u128) * (ethereum_price.gas_per_byte as u128)) + 3188; // 2100 + 68 * 16
192
+ let l1_fee = gas_for_l1_calldata * (ethereum_price.gas_price_in_unit as u128);
193
+
194
+ // L2 fee (Optimism)
195
+ let optimism_price = Self::get_price(env, dst_eid);
196
+ let gas_for_l2_calldata: u128 = (calldata_size as u128) * (optimism_price.gas_per_byte as u128);
197
+ let l2_fee = (gas_for_l2_calldata + gas) * (optimism_price.gas_price_in_unit as u128);
198
+
199
+ let price_ratio_denom = Self::get_price_ratio_denominator(env);
200
+ let l1_fee_in_src_price = (l1_fee * ethereum_price.price_ratio) / price_ratio_denom;
201
+ let l2_fee_in_src_price = (l2_fee * optimism_price.price_ratio) / price_ratio_denom;
202
+ let gas_fee = l1_fee_in_src_price + l2_fee_in_src_price;
203
+
204
+ (gas_fee, optimism_price.price_ratio)
205
+ }
206
+
207
+ /// Estimate fee with Arbitrum model
208
+ fn estimate_fee_with_arbitrum_model(env: &Env, dst_eid: u32, calldata_size: u32, gas: u128) -> (u128, u128) {
209
+ let arbitrum_price = Self::get_price(env, dst_eid);
210
+
211
+ let arbitrum_price_ext = Self::arbitrum_price_ext(env);
212
+
213
+ // L1 fee (compressed calldata)
214
+ let gas_for_l1_calldata = (((calldata_size as u128) * Self::arbitrum_compression_percent(env)) / 100)
215
+ * (arbitrum_price_ext.gas_per_l1_calldata_byte as u128);
216
+
217
+ // L2 fee
218
+ let gas_for_l2_calldata = (calldata_size as u128) * (arbitrum_price.gas_per_byte as u128);
219
+ let gas_fee = (gas + (arbitrum_price_ext.gas_per_l2_tx as u128) + gas_for_l1_calldata + gas_for_l2_calldata)
220
+ * (arbitrum_price.gas_price_in_unit as u128);
221
+
222
+ let fee = (gas_fee * arbitrum_price.price_ratio) / Self::get_price_ratio_denominator(env);
223
+
224
+ (fee, arbitrum_price.price_ratio)
225
+ }
226
+
227
+ /// Get L1 lookup ID for Optimism model
228
+ fn get_l1_lookup_id_for_optimism_model(env: &Env, l2_eid: u32) -> u32 {
229
+ let eid = l2_eid % 30_000;
230
+
231
+ if eid == 111 {
232
+ return 101;
233
+ } else if eid == 10132 {
234
+ return 10121; // Ethereum Goerli
235
+ } else if eid == 20132 {
236
+ return 20121; // Ethereum Goerli
237
+ }
238
+
239
+ // Check if this EID is configured as OP_STACK model type
240
+ assert_with_error!(env, Self::eid_to_model_type(env, eid) == ModelType::OpStack, PriceFeedError::NotAnOpStack);
241
+
242
+ if eid < 10000 {
243
+ 101
244
+ } else if eid < 20000 {
245
+ 10161 // Ethereum Sepolia
246
+ } else {
247
+ 20121 // Ethereum Goerli
248
+ }
249
+ }
250
+
251
+ /// Check if caller is price updater or owner
252
+ fn require_price_updater(env: &Env, caller: &Address) {
253
+ caller.require_auth();
254
+
255
+ // Owner is always approved
256
+ if let Some(owner) = Self::owner(env) {
257
+ if &owner == caller {
258
+ return;
259
+ }
260
+ }
261
+
262
+ // Check if caller is an active price updater
263
+ assert_with_error!(env, Self::price_updater(env, caller), PriceFeedError::OnlyPriceUpdater);
264
+ }
265
+ }
@@ -0,0 +1,42 @@
1
+ use common_macros::storage;
2
+ use soroban_sdk::Address;
3
+ use worker::Price;
4
+
5
+ use crate::types::{ArbitrumPriceExt, ModelType};
6
+
7
+ #[storage()]
8
+ pub enum PriceFeedStorage {
9
+ /// Price ratio denominator used for price calculations (initialized to 1e20)
10
+ #[instance(u128)]
11
+ #[default(10u128.pow(20))]
12
+ PriceRatioDenominator,
13
+
14
+ /// Price updater status mapping (address => bool active)
15
+ #[persistent(bool)]
16
+ #[default(false)]
17
+ PriceUpdater { updater: Address },
18
+
19
+ /// Default price model for each destination EID
20
+ #[persistent(Price)]
21
+ DefaultModelPrice { dst_eid: u32 },
22
+
23
+ /// Arbitrum-specific price extension
24
+ #[instance(ArbitrumPriceExt)]
25
+ #[default(ArbitrumPriceExt { gas_per_l2_tx: 0, gas_per_l1_calldata_byte: 0 })]
26
+ ArbitrumPriceExt,
27
+
28
+ /// Native token price in USD (uses PRICE_RATIO_DENOMINATOR)
29
+ #[instance(u128)]
30
+ #[default(0)]
31
+ NativePriceUSD,
32
+
33
+ /// Arbitrum compression percentage (initialized to 47)
34
+ #[instance(u128)]
35
+ #[default(47)]
36
+ ArbitrumCompressionPercent,
37
+
38
+ /// Fee model type for each destination EID
39
+ #[persistent(ModelType)]
40
+ #[default(ModelType::Default)]
41
+ EidToModelType { dst_eid: u32 },
42
+ }