@layerzerolabs/protocol-stellar-v2 0.2.10 → 0.2.11

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 (83) hide show
  1. package/.turbo/turbo-build.log +245 -199
  2. package/.turbo/turbo-lint.log +79 -107
  3. package/.turbo/turbo-test.log +1017 -841
  4. package/Cargo.lock +13 -5
  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/event.rs +4 -4
  8. package/contracts/common-macros/src/lib.rs +2 -2
  9. package/contracts/common-macros/src/ownable.rs +9 -5
  10. package/contracts/common-macros/src/tests/contract_impl.rs +178 -86
  11. package/contracts/common-macros/src/tests/error.rs +168 -0
  12. package/contracts/common-macros/src/tests/mod.rs +2 -4
  13. package/contracts/common-macros/src/tests/ownable.rs +37 -60
  14. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__contract_impl__snapshot_generated_contract_impl_code.snap +16 -6
  15. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
  16. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_generated_ownable_code.snap +3 -1
  17. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_only_owner_preserves_function_signature.snap +12 -2
  18. package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +5 -1
  19. package/contracts/common-macros/src/tests/utils.rs +267 -0
  20. package/contracts/common-macros/src/ttl_configurable.rs +15 -12
  21. package/contracts/common-macros/src/utils.rs +35 -6
  22. package/contracts/message-libs/uln-302/src/receive_uln.rs +1 -1
  23. package/contracts/message-libs/uln-302/src/send_uln.rs +2 -2
  24. package/contracts/message-libs/uln-302/src/uln302.rs +2 -2
  25. package/contracts/oapp-macros/src/oapp_core.rs +1 -1
  26. package/contracts/oapps/oft/integration-tests/setup.rs +4 -3
  27. package/contracts/oapps/oft/src/default_oft_impl.rs +146 -0
  28. package/contracts/oapps/oft/src/extensions/mod.rs +3 -0
  29. package/contracts/oapps/oft/src/extensions/oft_fee.rs +164 -0
  30. package/contracts/oapps/oft/src/extensions/pausable.rs +50 -0
  31. package/contracts/oapps/oft/src/extensions/rate_limiter.rs +198 -0
  32. package/contracts/oapps/oft/src/lib.rs +2 -3
  33. package/contracts/oapps/oft/src/oft.rs +16 -85
  34. package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
  35. package/contracts/oapps/oft/src/tests/extensions/mod.rs +11 -0
  36. package/contracts/oapps/oft/src/tests/extensions/setup.rs +888 -0
  37. package/contracts/oapps/oft/src/tests/extensions/test_oft_fee.rs +749 -0
  38. package/contracts/oapps/oft/src/tests/extensions/test_pausable.rs +432 -0
  39. package/contracts/oapps/oft/src/tests/extensions/test_rate_limiter.rs +1078 -0
  40. package/contracts/oapps/oft/src/tests/mod.rs +2 -0
  41. package/contracts/oapps/oft/src/tests/test_utils.rs +24 -6
  42. package/contracts/oapps/{oft-mint-burn → oft-std}/Cargo.toml +1 -8
  43. package/contracts/oapps/oft-std/src/lib.rs +5 -0
  44. package/contracts/oapps/oft-std/src/oft.rs +59 -0
  45. package/contracts/utils/src/ownable.rs +2 -2
  46. package/contracts/utils/src/tests/ownable.rs +0 -63
  47. package/contracts/utils/src/ttl.rs +19 -1
  48. package/contracts/workers/dvn/src/auth.rs +91 -27
  49. package/contracts/workers/dvn/src/dvn.rs +22 -20
  50. package/contracts/workers/dvn/src/interfaces/dvn.rs +48 -3
  51. package/contracts/workers/dvn/src/interfaces/multisig.rs +41 -0
  52. package/contracts/workers/dvn/src/lib.rs +6 -8
  53. package/contracts/workers/dvn/src/multisig.rs +6 -3
  54. package/contracts/workers/dvn/src/tests/auth.rs +1 -1
  55. package/contracts/workers/dvn/src/tests/dvn.rs +3 -4
  56. package/contracts/workers/dvn-fee-lib/Cargo.toml +2 -1
  57. package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +4 -3
  58. package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +8 -6
  59. package/contracts/workers/executor/src/interfaces/executor.rs +5 -2
  60. package/contracts/workers/executor/src/lz_executor.rs +6 -6
  61. package/contracts/workers/price-feed/Cargo.toml +21 -0
  62. package/contracts/workers/price-feed/src/errors.rs +9 -0
  63. package/contracts/workers/price-feed/src/events.rs +30 -0
  64. package/contracts/workers/price-feed/src/lib.rs +11 -0
  65. package/contracts/workers/price-feed/src/price_feed.rs +265 -0
  66. package/contracts/workers/price-feed/src/storage.rs +42 -0
  67. package/contracts/workers/price-feed/src/types.rs +59 -0
  68. package/contracts/workers/worker/src/interfaces/dvn_fee_lib.rs +2 -1
  69. package/package.json +3 -3
  70. package/sdk/dist/generated/bml.js +3 -1
  71. package/sdk/dist/generated/counter.d.ts +102 -0
  72. package/sdk/dist/generated/counter.js +13 -1
  73. package/sdk/dist/generated/endpoint.js +3 -1
  74. package/sdk/dist/generated/sml.js +3 -1
  75. package/sdk/dist/generated/uln302.js +3 -1
  76. package/sdk/package.json +1 -1
  77. package/contracts/oapps/oft/src/macro_tests/mod.rs +0 -2
  78. package/contracts/oapps/oft/src/macro_tests/test_all_default.rs +0 -41
  79. package/contracts/oapps/oft/src/macro_tests/test_override.rs +0 -83
  80. package/contracts/oapps/oft-mint-burn/src/lib.rs +0 -3
  81. package/contracts/oapps/oft-mint-burn/src/oft.rs +0 -28
  82. package/contracts/oapps/oft-mint-burn/src/tests/mod.rs +0 -1
  83. package/contracts/workers/dvn/src/types.rs +0 -26
@@ -1,24 +1,22 @@
1
1
  #![no_std]
2
- #[cfg(test)]
3
- extern crate std;
4
2
 
5
- mod errors;
3
+ pub mod errors;
6
4
  pub mod events;
7
- mod interfaces;
8
- mod types;
5
+ pub mod interfaces;
9
6
 
10
- pub use errors::*;
11
7
  pub use interfaces::*;
12
- pub use types::*;
13
8
 
14
9
  cfg_if::cfg_if! {
15
10
  if #[cfg(any(not(feature = "library"), feature = "testutils"))] {
16
11
  mod storage;
17
12
  mod dvn;
18
13
 
19
- pub use dvn::Dvn;
14
+ pub use dvn::*;
20
15
  }
21
16
  }
22
17
 
18
+ #[cfg(test)]
19
+ extern crate std;
20
+
23
21
  #[cfg(test)]
24
22
  mod tests;
@@ -1,10 +1,13 @@
1
+ use super::*;
2
+
1
3
  use crate::{
4
+ errors::MultisigError,
2
5
  events::{SignerSet, ThresholdSet},
3
6
  storage::MultisigStorage,
4
- IMultisig,
5
7
  };
8
+ use soroban_sdk::assert_with_error;
6
9
 
7
- #[contractimpl]
10
+ #[contract_impl]
8
11
  impl IMultisig for Dvn {
9
12
  fn set_signer(env: &Env, signer: &BytesN<20>, active: bool) {
10
13
  env.current_contract_address().require_auth();
@@ -109,7 +112,7 @@ fn set_threshold(env: &Env, threshold: u32) {
109
112
  ThresholdSet { threshold }.publish(env);
110
113
  }
111
114
 
112
- fn init_multisig(env: &Env, signers: &Vec<BytesN<20>>, threshold: u32) {
115
+ pub fn init_multisig(env: &Env, signers: &Vec<BytesN<20>>, threshold: u32) {
113
116
  signers.iter().for_each(|signer| add_signer(env, &signer));
114
117
 
115
118
  set_threshold(env, threshold);
@@ -1,5 +1,5 @@
1
1
  use crate::tests::setup::{TestSetup, VID};
2
- use crate::{errors::DvnError, errors::MultisigError, TransactionAuthData};
2
+ use crate::{dvn::auth::TransactionAuthData, errors::DvnError, errors::MultisigError};
3
3
  use ed25519_dalek::{Signer, SigningKey};
4
4
  use rand::thread_rng;
5
5
  use soroban_sdk::{auth::Context, vec, Bytes, BytesN, Env, IntoVal, Vec};
@@ -52,11 +52,10 @@ fn test_vid_returns_configured_value() {
52
52
  }
53
53
 
54
54
  #[test]
55
- #[should_panic(expected = "Error(Contract, #5)")] // DvnError::EidNotSupported
56
- fn test_dst_config_not_set_panics() {
55
+ fn test_dst_config_not_set_returns_none() {
57
56
  let setup = TestSetup::new(1);
58
57
  let client = DVNClient::new(&setup.env, &setup.contract_id);
59
- client.dst_config(&999);
58
+ assert_eq!(client.dst_config(&999), None);
60
59
  }
61
60
 
62
61
  #[test]
@@ -339,7 +338,7 @@ struct MockFeeLib;
339
338
 
340
339
  #[contractimpl]
341
340
  impl IDvnFeeLib for MockFeeLib {
342
- fn get_fee(_env: &Env, params: &DvnFeeParams) -> i128 {
341
+ fn get_fee(_env: &Env, _dvn: &Address, params: &DvnFeeParams) -> i128 {
343
342
  if params.multiplier_bps == 0 {
344
343
  params.default_multiplier_bps as i128
345
344
  } else {
@@ -16,9 +16,10 @@ doctest = false
16
16
  [dependencies]
17
17
  cfg-if = "1.0"
18
18
  soroban-sdk = { workspace = true }
19
+ common-macros = { workspace = true }
19
20
  message-lib-common = { workspace = true }
21
+ utils = { workspace = true }
20
22
  worker = { workspace = true }
21
- common-macros = { workspace = true }
22
23
 
23
24
  [dev-dependencies]
24
25
  soroban-sdk = { workspace = true, features = ["testutils"] }
@@ -1,5 +1,6 @@
1
1
  use crate::DvnFeeLibError;
2
- use soroban_sdk::{assert_with_error, contract, contractimpl, Env};
2
+ use common_macros::contract_impl;
3
+ use soroban_sdk::{assert_with_error, contract, Address, Env};
3
4
  use worker::{DvnFeeParams, IDvnFeeLib, LayerZeroPriceFeedClient};
4
5
 
5
6
  // ============================================================================
@@ -29,9 +30,9 @@ pub(crate) const NATIVE_DECIMALS_RATE: u128 = 10_000_000;
29
30
  #[contract]
30
31
  pub struct DvnFeeLib;
31
32
 
32
- #[contractimpl]
33
+ #[contract_impl]
33
34
  impl IDvnFeeLib for DvnFeeLib {
34
- fn get_fee(env: &Env, params: &DvnFeeParams) -> i128 {
35
+ fn get_fee(env: &Env, _dvn: &Address, params: &DvnFeeParams) -> i128 {
35
36
  assert_with_error!(env, params.gas > 0, DvnFeeLibError::InvalidGas);
36
37
  assert_with_error!(env, params.options.is_empty(), DvnFeeLibError::InvalidDVNOptions);
37
38
 
@@ -77,6 +77,7 @@ fn apply_premium_multiplier_wins_over_small_floor() {
77
77
  fn get_fee_panics_when_gas_zero() {
78
78
  let env = Env::default();
79
79
  env.mock_all_auths();
80
+ let fee_lib_addr = env.register(DvnFeeLib, ());
80
81
  let params = DvnFeeParams {
81
82
  sender: Address::generate(&env),
82
83
  dst_eid: 1,
@@ -90,7 +91,7 @@ fn get_fee_panics_when_gas_zero() {
90
91
  floor_margin_usd: 0,
91
92
  };
92
93
 
93
- DvnFeeLib::get_fee(&env, &params);
94
+ env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &fee_lib_addr, &params));
94
95
  }
95
96
 
96
97
  #[test]
@@ -98,6 +99,7 @@ fn get_fee_panics_when_gas_zero() {
98
99
  fn get_fee_panics_when_options_not_empty() {
99
100
  let env = Env::default();
100
101
  env.mock_all_auths();
102
+ let fee_lib_addr = env.register(DvnFeeLib, ());
101
103
  let mut options = Bytes::new(&env);
102
104
  options.push_back(1u8);
103
105
 
@@ -114,7 +116,7 @@ fn get_fee_panics_when_options_not_empty() {
114
116
  floor_margin_usd: 0,
115
117
  };
116
118
 
117
- DvnFeeLib::get_fee(&env, &params);
119
+ env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &fee_lib_addr, &params));
118
120
  }
119
121
 
120
122
  #[contract]
@@ -183,7 +185,7 @@ fn get_fee_success_path() {
183
185
  floor_margin_usd: 0,
184
186
  };
185
187
 
186
- let fee = env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &params));
188
+ let fee = env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &fee_lib_addr, &params));
187
189
 
188
190
  assert_eq!(fee, 100);
189
191
  }
@@ -209,7 +211,7 @@ fn get_fee_uses_default_multiplier_when_zero() {
209
211
  floor_margin_usd: 0,
210
212
  };
211
213
 
212
- let fee = env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &params));
214
+ let fee = env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &fee_lib_addr, &params));
213
215
 
214
216
  assert_eq!(fee, 120);
215
217
  }
@@ -235,7 +237,7 @@ fn get_fee_prefers_dst_multiplier_over_default() {
235
237
  floor_margin_usd: 0,
236
238
  };
237
239
 
238
- let fee = env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &params));
240
+ let fee = env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &fee_lib_addr, &params));
239
241
 
240
242
  assert_eq!(fee, 150);
241
243
  }
@@ -259,7 +261,7 @@ fn get_fee_panics_when_price_feed_returns_negative_fee() {
259
261
  floor_margin_usd: 0,
260
262
  };
261
263
 
262
- env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &params));
264
+ env.as_contract(&fee_lib_addr, || DvnFeeLib::get_fee(&env, &fee_lib_addr, &params));
263
265
  }
264
266
 
265
267
  #[test]
@@ -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
  ///
@@ -19,8 +19,8 @@ use soroban_sdk::{
19
19
  use utils::option_ext::OptionExt;
20
20
  use utils::ownable::Ownable;
21
21
  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,
22
+ assert_acl, assert_not_paused, assert_supported_message_lib, init_worker, require_admin_auth, set_admin_by_owner,
23
+ ExecutorFeeLibClient, FeeParams, Worker,
24
24
  };
25
25
 
26
26
  /// Signature data for Custom Account authorization.
@@ -140,8 +140,8 @@ impl IExecutor for LzExecutor {
140
140
  }
141
141
 
142
142
  /// 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)
143
+ fn dst_config(env: &Env, dst_eid: u32) -> Option<DstConfig> {
144
+ ExecutorStorage::dst_config(env, dst_eid)
145
145
  }
146
146
 
147
147
  /// Native token drops.
@@ -219,7 +219,7 @@ impl ILayerZeroExecutor for LzExecutor {
219
219
  assert_not_paused::<Self>(env);
220
220
  assert_acl::<Self>(env, sender);
221
221
 
222
- let dst_config = Self::dst_config(env, dst_eid);
222
+ let dst_config = Self::dst_config(env, dst_eid).unwrap_or_panic(env, ExecutorError::EidNotSupported);
223
223
  let fee_params = FeeParams {
224
224
  sender: sender.clone(),
225
225
  dst_eid,
@@ -304,5 +304,5 @@ impl LzExecutor {
304
304
  }
305
305
  }
306
306
 
307
- #[contractimpl(contracttrait)]
307
+ #[contract_impl(contracttrait)]
308
308
  impl Worker for LzExecutor {}
@@ -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
+ }