@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
@@ -4,7 +4,7 @@ use super::*;
4
4
  // ISendLib Contract Implementation
5
5
  // ==============================================================================
6
6
 
7
- #[contractimpl(contracttrait)]
7
+ #[contract_impl(contracttrait)]
8
8
  impl ISendLib for Uln302 {
9
9
  /// Quotes the total fee for sending a cross-chain message.
10
10
  ///
@@ -81,7 +81,7 @@ impl ISendLib for Uln302 {
81
81
  // ISendUln302 Contract Implementation
82
82
  // ==============================================================================
83
83
 
84
- #[contractimpl(contracttrait)]
84
+ #[contract_impl(contracttrait)]
85
85
  impl ISendUln302 for Uln302 {
86
86
  /// Sets default executor configurations for multiple destination endpoints.
87
87
  #[only_owner]
@@ -22,7 +22,7 @@ use message_lib_common::{
22
22
  };
23
23
  use soroban_sdk::{
24
24
  address_payload::AddressPayload,
25
- assert_with_error, bytes, contract, contractimpl, panic_with_error, vec,
25
+ assert_with_error, bytes, contract, panic_with_error, vec,
26
26
  xdr::{FromXdr, ToXdr},
27
27
  Address, Bytes, BytesN, Env, Map, Vec,
28
28
  };
@@ -54,7 +54,7 @@ impl Uln302 {
54
54
  }
55
55
  }
56
56
 
57
- #[contractimpl(contracttrait)]
57
+ #[contract_impl(contracttrait)]
58
58
  impl IMessageLib for Uln302 {
59
59
  /// Sets OApp-specific configuration parameters for the message library.
60
60
  ///
@@ -31,8 +31,8 @@ pub fn generate_impl(name: &Ident, item_struct: ItemStruct, manual_impl_core: bo
31
31
  });
32
32
 
33
33
  quote! {
34
- #[common_macros::ownable]
35
34
  #[soroban_sdk::contract]
35
+ #[common_macros::ttl_configurable]
36
36
  #item_struct
37
37
 
38
38
  #manual_impl
@@ -29,8 +29,8 @@ pub struct TestOFT;
29
29
 
30
30
  #[contract_impl]
31
31
  impl TestOFT {
32
- pub fn __constructor(env: &Env, token: &Address, owner: &Address, endpoint: &Address, delegate: &Option<Address>) {
33
- oft_initialize::<Self>(env, owner, token, endpoint, delegate)
32
+ pub fn __constructor(env: &Env, token: &Address, owner: &Address, endpoint: &Address, delegate: &Option<Address>, shared_decimals: u32) {
33
+ oft_initialize::<Self>(env, owner, token, endpoint, delegate, shared_decimals)
34
34
  }
35
35
  }
36
36
 
@@ -153,7 +153,8 @@ fn setup_chain<'a>(env: &Env) -> ChainSetup<'a> {
153
153
  let fee_recipient = Address::generate(env);
154
154
  let sml_address = env.register(SimpleMessageLib, (&owner, &endpoint_address, &fee_recipient));
155
155
  let delegate: Option<Address> = Some(owner.clone());
156
- let oft_address = env.register(TestOFT, (&oft_token, &owner, &endpoint_address, &delegate));
156
+ let shared_decimals: u32 = 6; // Default shared decimals
157
+ let oft_address = env.register(TestOFT, (&oft_token, &owner, &endpoint_address, &delegate, &shared_decimals));
157
158
  let composer_address = env.register(DummyComposer, (&endpoint_address,));
158
159
 
159
160
  let endpoint = EndpointV2Client::new(env, &endpoint_address);
@@ -0,0 +1,146 @@
1
+ //! Default OFT implementations.
2
+ //!
3
+ //! This module provides default standalone functions for common OFT operations that can be
4
+ //! reused by trait implementations.
5
+
6
+ use crate::{
7
+ codec::{oft_compose_msg_codec::OFTComposeMsg, oft_msg_codec::OFTMessage},
8
+ constants,
9
+ errors::OFTError,
10
+ events,
11
+ events::OFTSent,
12
+ storage::OFTStorage,
13
+ types::{OFTFeeDetail, OFTLimit, OFTReceipt, SendParam},
14
+ utils::{address_to_bytes32, remove_dust, resolve_address, to_ld, to_sd},
15
+ };
16
+ use endpoint_v2::{MessagingComposerClient, MessagingFee, MessagingReceipt, Origin};
17
+ use oapp::oapp_options_type3::OAppOptionsType3;
18
+ use soroban_sdk::{assert_with_error, vec, Address, Bytes, BytesN, Env, Vec};
19
+
20
+ /// Calculates debit amounts without executing (view function).
21
+ pub fn default_debit_view(env: &Env, amount_ld: i128, min_amount_ld: i128, _dst_eid: u32) -> OFTReceipt {
22
+ let conversion_rate = OFTStorage::decimal_conversion_rate(env).unwrap();
23
+ let amount_sent_ld = remove_dust(amount_ld, conversion_rate);
24
+ let amount_received_ld = amount_sent_ld;
25
+
26
+ assert_with_error!(env, amount_received_ld >= min_amount_ld, OFTError::SlippageExceeded);
27
+
28
+ OFTReceipt { amount_sent_ld, amount_received_ld }
29
+ }
30
+
31
+ /// Builds OFT message and combines options for cross-chain transfer.
32
+ pub fn default_build_msg_and_options<T>(
33
+ env: &Env,
34
+ sender: &Address,
35
+ send_param: &SendParam,
36
+ amount_ld: i128,
37
+ ) -> (Bytes, Bytes)
38
+ where
39
+ T: OAppOptionsType3,
40
+ {
41
+ let has_compose = !send_param.compose_msg.is_empty();
42
+ let conversion_rate = OFTStorage::decimal_conversion_rate(env).unwrap();
43
+ let msg = OFTMessage {
44
+ send_to: send_param.to.clone(),
45
+ amount_sd: to_sd(env, amount_ld, conversion_rate),
46
+ compose_from: if has_compose { Some(address_to_bytes32(sender)) } else { None },
47
+ compose_msg: if has_compose { Some(send_param.compose_msg.clone()) } else { None },
48
+ };
49
+ let (msg, _) = msg.encode(env);
50
+ let msg_type = if has_compose { constants::SEND_AND_CALL } else { constants::SEND };
51
+
52
+ (msg, T::combine_options(env, send_param.dst_eid, msg_type, &send_param.extra_options))
53
+ }
54
+
55
+ /// Handles OFT receive logic.
56
+ pub fn default_lz_receive<T>(
57
+ env: &Env,
58
+ _executor: &Address,
59
+ origin: &Origin,
60
+ guid: &BytesN<32>,
61
+ message: &Bytes,
62
+ _extra_data: &Bytes,
63
+ _value: i128,
64
+ ) where
65
+ T: crate::oft::OFTInner,
66
+ {
67
+ let oft_msg = OFTMessage::decode(message);
68
+ let send_to = resolve_address(env, &oft_msg.send_to);
69
+
70
+ let conversion_rate = OFTStorage::decimal_conversion_rate(env).unwrap();
71
+ let amount_received_ld = T::__credit(env, &send_to, to_ld(oft_msg.amount_sd, conversion_rate), origin.src_eid);
72
+
73
+ if oft_msg.is_composed() {
74
+ let compose_msg = OFTComposeMsg {
75
+ nonce: origin.nonce,
76
+ src_eid: origin.src_eid,
77
+ amount_ld: amount_received_ld,
78
+ compose_from: oft_msg.compose_from.unwrap(),
79
+ compose_msg: oft_msg.compose_msg.unwrap(),
80
+ }
81
+ .encode(env);
82
+
83
+ let endpoint = MessagingComposerClient::new(env, &T::endpoint(env));
84
+ endpoint.send_compose(&env.current_contract_address(), &send_to, guid, &0, &compose_msg);
85
+ }
86
+
87
+ events::OFTReceived { guid: guid.clone(), src_eid: origin.src_eid, to: send_to, amount_received_ld }.publish(env);
88
+ }
89
+
90
+ /// Quotes an OFT transfer without executing.
91
+ /// Returns (OFTLimit, fee details, receipt with estimated amounts).
92
+ pub fn default_quote_oft<T>(env: &Env, send_param: &SendParam) -> (OFTLimit, Vec<OFTFeeDetail>, OFTReceipt)
93
+ where
94
+ T: crate::oft::OFTInner,
95
+ {
96
+ let oft_receipt = T::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
97
+ let oft_fee_details = if oft_receipt.amount_sent_ld > oft_receipt.amount_received_ld {
98
+ vec![env, OFTFeeDetail { fee_amount_ld: oft_receipt.amount_sent_ld - oft_receipt.amount_received_ld, description: Bytes::from_array(env, b"OFT Fee") }]
99
+ } else {
100
+ vec![env]
101
+ };
102
+ (OFTLimit { min_amount_ld: 0, max_amount_ld: i128::MAX }, oft_fee_details, oft_receipt)
103
+ }
104
+
105
+ /// Quotes a send operation including LayerZero messaging fees.
106
+ pub fn default_quote_send<T>(env: &Env, sender: &Address, send_param: &SendParam, pay_in_zro: bool) -> MessagingFee
107
+ where
108
+ T: crate::oft::OFT,
109
+ {
110
+ let OFTReceipt { amount_received_ld, .. } =
111
+ T::__debit_view(env, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
112
+
113
+ let (msg, options) = default_build_msg_and_options::<T>(env, sender, send_param, amount_received_ld);
114
+ T::lz_quote(env, send_param.dst_eid, &msg, &options, pay_in_zro)
115
+ }
116
+
117
+ /// Sends tokens cross-chain to another endpoint.
118
+ /// Sender must be authenticated. Returns (MessagingReceipt, OFTReceipt).
119
+ pub fn default_send<T>(
120
+ env: &Env,
121
+ sender: &Address,
122
+ send_param: &SendParam,
123
+ fee: &MessagingFee,
124
+ refund_address: &Address,
125
+ ) -> (MessagingReceipt, OFTReceipt)
126
+ where
127
+ T: crate::oft::OFT,
128
+ {
129
+ sender.require_auth();
130
+
131
+ let oft_receipt = T::__debit(env, sender, send_param.amount_ld, send_param.min_amount_ld, send_param.dst_eid);
132
+
133
+ let (msg, options) = default_build_msg_and_options::<T>(env, sender, send_param, oft_receipt.amount_received_ld);
134
+ let msg_receipt = T::lz_send(env, sender, send_param.dst_eid, &msg, &options, fee, refund_address);
135
+
136
+ OFTSent {
137
+ guid: msg_receipt.guid.clone(),
138
+ dst_eid: send_param.dst_eid,
139
+ from: sender.clone(),
140
+ amount_sent_ld: oft_receipt.amount_sent_ld,
141
+ amount_received_ld: oft_receipt.amount_received_ld,
142
+ }
143
+ .publish(env);
144
+
145
+ (msg_receipt, oft_receipt)
146
+ }
@@ -0,0 +1,3 @@
1
+ pub mod oft_fee;
2
+ pub mod pausable;
3
+ pub mod rate_limiter;
@@ -0,0 +1,164 @@
1
+ use common_macros::{contract_error, event, only_owner, storage};
2
+ use soroban_sdk::{assert_with_error, contracttrait, token::TokenClient, Address, Env};
3
+ use utils::{option_ext::OptionExt, ownable::Ownable};
4
+
5
+ /// Base fee in basis points (10,000 BPS = 100%)
6
+ /// Used as denominator in fee calculations
7
+ const BASE_FEE_BPS: u64 = 10_000;
8
+
9
+ #[storage]
10
+ pub enum OFTFeeStorage {
11
+ #[instance(u64)]
12
+ #[default(0)]
13
+ DefaultFeeBps,
14
+
15
+ #[persistent(u64)]
16
+ FeeBps { eid: u32 },
17
+
18
+ #[instance(Address)]
19
+ FeeDepositAddress,
20
+ }
21
+
22
+ #[contract_error]
23
+ pub enum OFTFeeError {
24
+ InvalidFeeBps = 2200,
25
+ InvalidFeeDepositAddress,
26
+ NotFound,
27
+ SameValue,
28
+ }
29
+
30
+ #[event]
31
+ pub struct DefaultFeeBpsSet {
32
+ pub fee_bps: u64,
33
+ }
34
+
35
+ #[event]
36
+ pub struct FeeBpsSet {
37
+ pub dst_eid: u32,
38
+ pub fee_bps: u64,
39
+ }
40
+
41
+ #[event]
42
+ pub struct FeeBpsUnset {
43
+ pub dst_eid: u32,
44
+ }
45
+
46
+ #[event]
47
+ pub struct FeeDepositAddressSet {
48
+ pub fee_deposit_address: Address,
49
+ }
50
+
51
+ #[contracttrait]
52
+ pub trait OFTFee: OFTFeeInternal + Ownable + Sized {
53
+ #[only_owner]
54
+ fn set_default_fee_bps(env: &Env, default_fee_bps: u64) {
55
+ assert_with_error!(env, default_fee_bps <= BASE_FEE_BPS, OFTFeeError::InvalidFeeBps);
56
+ let current_default = OFTFeeStorage::default_fee_bps(env);
57
+ assert_with_error!(env, current_default != default_fee_bps, OFTFeeError::SameValue);
58
+ OFTFeeStorage::set_default_fee_bps(env, &default_fee_bps);
59
+ DefaultFeeBpsSet { fee_bps: default_fee_bps }.publish(env);
60
+ }
61
+
62
+ #[only_owner]
63
+ fn set_fee_bps(env: &Env, dst_eid: u32, fee_bps: u64) {
64
+ assert_with_error!(env, fee_bps <= BASE_FEE_BPS, OFTFeeError::InvalidFeeBps);
65
+ if OFTFeeStorage::has_fee_bps(env, dst_eid) {
66
+ let current_fee_bps = OFTFeeStorage::fee_bps(env, dst_eid).unwrap();
67
+ assert_with_error!(env, current_fee_bps != fee_bps, OFTFeeError::SameValue);
68
+ }
69
+ OFTFeeStorage::set_fee_bps(env, dst_eid, &fee_bps);
70
+ FeeBpsSet { dst_eid, fee_bps }.publish(env);
71
+ }
72
+
73
+ #[only_owner]
74
+ fn unset_fee_bps(env: &Env, dst_eid: u32) {
75
+ assert_with_error!(env, OFTFeeStorage::has_fee_bps(env, dst_eid), OFTFeeError::NotFound);
76
+ OFTFeeStorage::remove_fee_bps(env, dst_eid);
77
+ FeeBpsUnset { dst_eid }.publish(env);
78
+ }
79
+
80
+ #[only_owner]
81
+ fn set_fee_deposit_address(env: &Env, fee_deposit_address: Address) {
82
+ if let Some(current_address) = OFTFeeStorage::fee_deposit_address(env) {
83
+ assert_with_error!(env, current_address != fee_deposit_address, OFTFeeError::SameValue);
84
+ }
85
+ OFTFeeStorage::set_fee_deposit_address(env, &fee_deposit_address);
86
+ FeeDepositAddressSet { fee_deposit_address }.publish(env);
87
+ }
88
+
89
+ fn has_fee_bps(env: &Env, dst_eid: u32) -> bool {
90
+ Self::effective_fee_bps(env, dst_eid) > 0
91
+ }
92
+
93
+ fn effective_fee_bps(env: &Env, dst_eid: u32) -> u64 {
94
+ if OFTFeeStorage::has_fee_bps(env, dst_eid) {
95
+ OFTFeeStorage::fee_bps(env, dst_eid).unwrap()
96
+ } else {
97
+ OFTFeeStorage::default_fee_bps(env)
98
+ }
99
+ }
100
+
101
+ fn default_fee_bps(env: &Env) -> u64 {
102
+ OFTFeeStorage::default_fee_bps(env)
103
+ }
104
+
105
+ fn fee_bps(env: &Env, dst_eid: u32) -> Option<u64> {
106
+ OFTFeeStorage::fee_bps(env, dst_eid)
107
+ }
108
+
109
+ fn fee_deposit_address(env: &Env) -> Address {
110
+ OFTFeeStorage::fee_deposit_address(env).unwrap_or_panic(env, OFTFeeError::InvalidFeeDepositAddress)
111
+ }
112
+ }
113
+
114
+ /// Internal trait for OFT fee operations used by OFT hooks.
115
+ /// Contains only truly internal methods that are called from OFTInner implementations.
116
+ pub trait OFTFeeInternal {
117
+ /// Applies the configured fee to the given amount and returns the amount after fee deduction.
118
+ /// Used internally by `__debit_view` to calculate amount after fee.
119
+ ///
120
+ /// # Arguments
121
+ /// * `dst_eid` - Destination endpoint ID to determine which fee rate to apply
122
+ /// * `amount_ld` - The original amount in local decimals
123
+ ///
124
+ /// # Returns
125
+ /// The amount after fee deduction (original amount - calculated fee)
126
+ fn __apply_fee(env: &Env, dst_eid: u32, amount_ld: i128) -> i128 {
127
+ // Calculate effective fee (destination-specific or default)
128
+ let fee_bps = if OFTFeeStorage::has_fee_bps(env, dst_eid) {
129
+ OFTFeeStorage::fee_bps(env, dst_eid).unwrap()
130
+ } else {
131
+ OFTFeeStorage::default_fee_bps(env)
132
+ };
133
+ if fee_bps == 0 {
134
+ return amount_ld;
135
+ }
136
+ // Check that fee deposit address is set (required for fee collection)
137
+ assert_with_error!(
138
+ env,
139
+ OFTFeeStorage::fee_deposit_address(env).is_some(),
140
+ OFTFeeError::InvalidFeeDepositAddress
141
+ );
142
+ let preliminary_fee = (amount_ld * fee_bps as i128) / BASE_FEE_BPS as i128;
143
+ amount_ld - preliminary_fee
144
+ }
145
+
146
+ /// Transfers the fee amount from sender to the fee deposit address.
147
+ /// Used internally by `__debit` to collect the fee after calculating the debit amounts.
148
+ ///
149
+ /// # Arguments
150
+ /// * `token` - The token address to transfer
151
+ /// * `sender` - The sender address to transfer fee from
152
+ /// * `fee_amount` - The fee amount to transfer
153
+ fn __transfer_fee(env: &Env, token: &Address, sender: &Address, fee_amount: i128) {
154
+ if fee_amount > 0 {
155
+ assert_with_error!(
156
+ env,
157
+ OFTFeeStorage::fee_deposit_address(env).is_some(),
158
+ OFTFeeError::InvalidFeeDepositAddress
159
+ );
160
+ let fee_deposit = OFTFeeStorage::fee_deposit_address(env).unwrap();
161
+ TokenClient::new(env, token).transfer(sender, &fee_deposit, &fee_amount);
162
+ }
163
+ }
164
+ }
@@ -0,0 +1,50 @@
1
+ use common_macros::{contract_error, event, only_owner, storage};
2
+ use soroban_sdk::{assert_with_error, contracttrait, Env};
3
+ use utils::ownable::Ownable;
4
+
5
+ #[storage]
6
+ pub enum OFTPausableStorage {
7
+ #[instance(bool)]
8
+ #[default(false)]
9
+ Paused,
10
+ }
11
+
12
+ #[contract_error]
13
+ pub enum OFTPausableError {
14
+ Paused = 2300,
15
+ PauseStatusUnchanged,
16
+ }
17
+
18
+ #[event]
19
+ pub struct PausedSet {
20
+ pub paused: bool,
21
+ }
22
+
23
+ #[contracttrait]
24
+ pub trait OFTPausable: OFTPausableInternal + Ownable + Sized {
25
+ #[only_owner]
26
+ fn set_paused(env: &Env, paused: bool) {
27
+ let current_paused = OFTPausableStorage::paused(env);
28
+ assert_with_error!(env, current_paused != paused, OFTPausableError::PauseStatusUnchanged);
29
+ OFTPausableStorage::set_paused(env, &paused);
30
+ PausedSet { paused }.publish(env);
31
+ }
32
+
33
+ fn is_paused(env: &Env) -> bool {
34
+ OFTPausableStorage::paused(env)
35
+ }
36
+ }
37
+
38
+ /// Internal trait for pausable operations used by OFT hooks.
39
+ /// Contains only truly internal methods that are called from OFTInner implementations.
40
+ pub trait OFTPausableInternal {
41
+ /// Asserts that the OFT is not paused, panics otherwise.
42
+ /// Used internally by `send`, `quote_send`, and `__lz_receive` to enforce pause state.
43
+ ///
44
+ /// # Errors
45
+ /// * `Paused` - If the OFT is currently paused.
46
+ fn __assert_not_paused(env: &Env) {
47
+ assert_with_error!(env, !OFTPausableStorage::paused(env), OFTPausableError::Paused);
48
+ }
49
+ }
50
+
@@ -0,0 +1,198 @@
1
+ use common_macros::{contract_error, event, only_owner, storage};
2
+ use soroban_sdk::{assert_with_error, contracttrait, contracttype, panic_with_error, Env};
3
+ use utils::ownable::Ownable;
4
+
5
+ #[contracttype]
6
+ #[derive(Clone, Debug, Eq, PartialEq)]
7
+ #[repr(u8)]
8
+ pub enum Direction {
9
+ Inbound,
10
+ Outbound,
11
+ }
12
+
13
+ #[contracttype]
14
+ #[derive(Clone, Debug, Eq, PartialEq)]
15
+ pub struct RateLimit {
16
+ limit: i128,
17
+ window_seconds: u64,
18
+ in_flight_on_last_update: i128,
19
+ last_update: u64,
20
+ }
21
+
22
+ #[storage]
23
+ pub enum RateLimitStorage {
24
+ #[persistent(RateLimit)]
25
+ RateLimit { direction: Direction, eid: u32 },
26
+ }
27
+
28
+ #[contract_error]
29
+ pub enum RateLimitError {
30
+ ExceededRateLimit = 2400,
31
+ InvalidTimestamp,
32
+ InvalidWindowSeconds,
33
+ InvalidLimit,
34
+ SameValue,
35
+ }
36
+
37
+ #[event]
38
+ pub struct RateLimitSet {
39
+ pub direction: Direction,
40
+ pub eid: u32,
41
+ pub limit: i128,
42
+ pub window_seconds: u64,
43
+ }
44
+
45
+ #[event]
46
+ pub struct RateLimitUpdated {
47
+ pub direction: Direction,
48
+ pub eid: u32,
49
+ pub limit: i128,
50
+ pub window_seconds: u64,
51
+ }
52
+
53
+ #[event]
54
+ pub struct RateLimitUnset {
55
+ pub direction: Direction,
56
+ pub eid: u32,
57
+ }
58
+
59
+ /// Helper function to calculate the current in-flight amount with decay.
60
+ /// Used by both public trait methods and internal implementations.
61
+ fn calculate_in_flight(env: &Env, direction: &Direction, eid: u32) -> i128 {
62
+ if !RateLimitStorage::has_rate_limit(env, direction, eid) {
63
+ return 0;
64
+ }
65
+ let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
66
+ let timestamp = env.ledger().timestamp();
67
+ assert_with_error!(env, timestamp >= rate_limit.last_update, RateLimitError::InvalidTimestamp);
68
+ let elapsed = timestamp - rate_limit.last_update;
69
+ let decay = (elapsed as i128) * rate_limit.limit / (rate_limit.window_seconds as i128);
70
+ if decay < rate_limit.in_flight_on_last_update {
71
+ rate_limit.in_flight_on_last_update - decay
72
+ } else {
73
+ 0
74
+ }
75
+ }
76
+
77
+ #[contracttrait]
78
+ pub trait RateLimiter: RateLimiterInternal + Ownable + Sized {
79
+ #[only_owner]
80
+ fn set_rate_limit(env: &Env, direction: &Direction, eid: u32, limit: i128, window_seconds: u64) {
81
+ assert_with_error!(env, limit > 0, RateLimitError::InvalidLimit);
82
+ assert_with_error!(env, window_seconds > 0, RateLimitError::InvalidWindowSeconds);
83
+ if RateLimitStorage::has_rate_limit(env, direction, eid) {
84
+ let rate_limit_data = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
85
+ assert_with_error!(
86
+ env,
87
+ limit != rate_limit_data.limit || window_seconds != rate_limit_data.window_seconds,
88
+ RateLimitError::SameValue
89
+ );
90
+ Self::__checkpoint_rate_limit_in_flight(env, direction, eid);
91
+ let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
92
+ rate_limit.limit = limit;
93
+ rate_limit.window_seconds = window_seconds;
94
+ RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
95
+ RateLimitUpdated { direction: direction.clone(), eid, limit, window_seconds }.publish(env);
96
+ } else {
97
+ RateLimitStorage::set_rate_limit(
98
+ env,
99
+ direction,
100
+ eid,
101
+ &RateLimit {
102
+ limit,
103
+ window_seconds,
104
+ in_flight_on_last_update: 0,
105
+ last_update: env.ledger().timestamp(),
106
+ },
107
+ );
108
+ RateLimitSet { direction: direction.clone(), eid, limit, window_seconds }.publish(env);
109
+ }
110
+ }
111
+
112
+ #[only_owner]
113
+ fn unset_rate_limit(env: &Env, direction: &Direction, eid: u32) {
114
+ assert_with_error!(env, RateLimitStorage::has_rate_limit(env, direction, eid), RateLimitError::SameValue);
115
+ RateLimitStorage::remove_rate_limit(env, direction, eid);
116
+ RateLimitUnset { direction: direction.clone(), eid }.publish(env);
117
+ }
118
+
119
+ fn rate_limit_config(env: &Env, direction: &Direction, eid: u32) -> (i128, u64) {
120
+ if !RateLimitStorage::has_rate_limit(env, direction, eid) {
121
+ return (0, 0);
122
+ }
123
+ let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
124
+ (rate_limit.limit, rate_limit.window_seconds)
125
+ }
126
+
127
+ fn rate_limit_in_flight(env: &Env, direction: &Direction, eid: u32) -> i128 {
128
+ calculate_in_flight(env, direction, eid)
129
+ }
130
+
131
+ fn rate_limit_capacity(env: &Env, direction: &Direction, eid: u32) -> i128 {
132
+ if !RateLimitStorage::has_rate_limit(env, direction, eid) {
133
+ return i128::MAX;
134
+ }
135
+ let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
136
+ let in_flight = calculate_in_flight(env, direction, eid);
137
+ if rate_limit.limit > in_flight {
138
+ rate_limit.limit - in_flight
139
+ } else {
140
+ 0
141
+ }
142
+ }
143
+ }
144
+
145
+ /// Internal trait for rate limiter operations used by OFT hooks.
146
+ /// Contains only truly internal methods that are called from OFTInner implementations.
147
+ pub trait RateLimiterInternal {
148
+ /// Tries to consume the specified amount from the rate limit capacity.
149
+ /// Used internally by `send` and `__lz_receive` to enforce rate limits.
150
+ ///
151
+ /// # Errors
152
+ /// * `ExceededRateLimit` - If the amount exceeds the available capacity.
153
+ fn __try_consume_rate_limit_capacity(env: &Env, direction: &Direction, eid: u32, amount: i128) {
154
+ if !RateLimitStorage::has_rate_limit(env, direction, eid) {
155
+ return;
156
+ }
157
+ Self::__checkpoint_rate_limit_in_flight(env, direction, eid);
158
+ let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
159
+ // Check against remaining capacity (limit - current in_flight), not total limit
160
+ let capacity = if rate_limit.limit > rate_limit.in_flight_on_last_update {
161
+ rate_limit.limit - rate_limit.in_flight_on_last_update
162
+ } else {
163
+ 0
164
+ };
165
+ if amount > capacity {
166
+ panic_with_error!(env, RateLimitError::ExceededRateLimit);
167
+ }
168
+ rate_limit.in_flight_on_last_update += amount;
169
+ RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
170
+ }
171
+
172
+ /// Releases the specified amount back to the rate limit capacity.
173
+ /// Used internally by `__lz_receive` to release outbound capacity on inbound messages.
174
+ fn __release_rate_limit_capacity(env: &Env, direction: &Direction, eid: u32, amount: i128) {
175
+ if !RateLimitStorage::has_rate_limit(env, direction, eid) {
176
+ return;
177
+ }
178
+ Self::__checkpoint_rate_limit_in_flight(env, direction, eid);
179
+ let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
180
+ if amount >= rate_limit.in_flight_on_last_update {
181
+ rate_limit.in_flight_on_last_update = 0;
182
+ } else {
183
+ rate_limit.in_flight_on_last_update -= amount;
184
+ }
185
+ RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
186
+ }
187
+
188
+ /// Checkpoints the current in-flight amount by applying decay and updating timestamp.
189
+ /// Used internally before modifying rate limit state.
190
+ fn __checkpoint_rate_limit_in_flight(env: &Env, direction: &Direction, eid: u32) {
191
+ let in_flight = calculate_in_flight(env, direction, eid);
192
+ let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
193
+ rate_limit.in_flight_on_last_update = in_flight;
194
+ rate_limit.last_update = env.ledger().timestamp();
195
+ RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
196
+ }
197
+ }
198
+
@@ -2,8 +2,10 @@
2
2
 
3
3
  pub mod codec;
4
4
  pub mod constants;
5
+ pub mod default_oft_impl;
5
6
  pub mod errors;
6
7
  pub mod events;
8
+ pub mod extensions;
7
9
  pub mod interfaces;
8
10
  pub mod oft;
9
11
  pub mod oft_types;
@@ -15,8 +17,5 @@ pub mod utils;
15
17
  #[path = "../integration-tests/mod.rs"]
16
18
  pub mod integration_tests;
17
19
 
18
- #[cfg(test)]
19
- pub mod macro_tests;
20
-
21
20
  #[cfg(test)]
22
21
  pub mod tests;