@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.
- package/.turbo/turbo-build.log +273 -219
- package/.turbo/turbo-lint.log +79 -107
- package/.turbo/turbo-test.log +1016 -840
- package/Cargo.lock +14 -6
- package/contracts/common-macros/src/contract_impl.rs +6 -3
- package/contracts/common-macros/src/error.rs +9 -17
- package/contracts/common-macros/src/lib.rs +4 -37
- package/contracts/common-macros/src/ownable.rs +9 -5
- package/contracts/common-macros/src/tests/contract_impl.rs +178 -86
- package/contracts/common-macros/src/tests/error.rs +168 -0
- package/contracts/common-macros/src/tests/mod.rs +2 -4
- package/contracts/common-macros/src/tests/ownable.rs +37 -60
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__contract_impl__snapshot_generated_contract_impl_code.snap +16 -6
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__error__snapshot_generated_contract_error_code.snap +20 -0
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_generated_ownable_code.snap +3 -1
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ownable__snapshot_only_owner_preserves_function_signature.snap +12 -2
- package/contracts/common-macros/src/tests/snapshots/common_macros__tests__ttl_configurable__snapshot_generated_ttl_configurable_code.snap +5 -1
- package/contracts/common-macros/src/tests/utils.rs +267 -0
- package/contracts/common-macros/src/ttl_configurable.rs +15 -12
- package/contracts/common-macros/src/utils.rs +35 -6
- package/contracts/endpoint-v2/src/endpoint_v2.rs +4 -4
- package/contracts/endpoint-v2/src/events.rs +40 -22
- package/contracts/endpoint-v2/src/interfaces/message_lib.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/message_lib_manager.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/messaging_channel.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/messaging_composer.rs +2 -2
- package/contracts/endpoint-v2/src/interfaces/send_lib.rs +2 -2
- package/contracts/endpoint-v2/src/message_lib_manager.rs +3 -3
- package/contracts/endpoint-v2/src/messaging_channel.rs +1 -1
- package/contracts/endpoint-v2/src/messaging_composer.rs +1 -1
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_lib_timeout.rs +4 -8
- package/contracts/endpoint-v2/src/tests/message_lib_manager/set_default_receive_library.rs +3 -7
- package/contracts/message-libs/{block-message-lib → blocked-message-lib}/Cargo.toml +1 -1
- package/contracts/message-libs/treasury/src/events.rs +9 -6
- package/contracts/message-libs/uln-302/src/events.rs +19 -11
- package/contracts/message-libs/uln-302/src/interfaces/receive_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/interfaces/send_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/receive_uln.rs +2 -2
- package/contracts/message-libs/uln-302/src/send_uln.rs +3 -3
- package/contracts/message-libs/uln-302/src/tests/receive_uln302/set_default_receive_uln_configs.rs +5 -5
- package/contracts/message-libs/uln-302/src/tests/send_uln302/set_default_send_uln_configs.rs +5 -5
- package/contracts/message-libs/uln-302/src/tests/setup.rs +3 -3
- package/contracts/message-libs/uln-302/src/types.rs +24 -24
- package/contracts/message-libs/uln-302/src/uln302.rs +2 -2
- package/contracts/oapp-macros/src/oapp_core.rs +1 -1
- package/contracts/oapps/counter/integration_tests/utils.rs +1 -1
- package/contracts/oapps/oapp/src/oapp_core.rs +4 -3
- package/contracts/oapps/oapp/src/oapp_options_type3.rs +4 -3
- package/contracts/oapps/oft/integration-tests/setup.rs +4 -3
- package/contracts/oapps/oft/integration-tests/utils.rs +1 -1
- package/contracts/oapps/oft/src/default_oft_impl.rs +146 -0
- package/contracts/oapps/oft/src/events.rs +5 -4
- package/contracts/oapps/oft/src/extensions/mod.rs +3 -0
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +168 -0
- package/contracts/oapps/oft/src/extensions/pausable.rs +50 -0
- package/contracts/oapps/oft/src/extensions/rate_limiter.rs +200 -0
- package/contracts/oapps/oft/src/lib.rs +2 -3
- package/contracts/oapps/oft/src/oft.rs +16 -85
- package/contracts/oapps/oft/src/oft_types/mint_burn.rs +1 -1
- package/contracts/oapps/oft/src/tests/extensions/mod.rs +11 -0
- package/contracts/oapps/oft/src/tests/extensions/setup.rs +888 -0
- package/contracts/oapps/oft/src/tests/extensions/test_oft_fee.rs +749 -0
- package/contracts/oapps/oft/src/tests/extensions/test_pausable.rs +432 -0
- package/contracts/oapps/oft/src/tests/extensions/test_rate_limiter.rs +1078 -0
- package/contracts/oapps/oft/src/tests/mod.rs +2 -0
- package/contracts/oapps/oft/src/tests/test_utils.rs +24 -6
- package/contracts/oapps/{oft-mint-burn → oft-std}/Cargo.toml +1 -8
- package/contracts/oapps/oft-std/src/lib.rs +5 -0
- package/contracts/oapps/oft-std/src/oft.rs +59 -0
- package/contracts/utils/src/ownable.rs +8 -6
- package/contracts/utils/src/tests/ownable.rs +0 -63
- package/contracts/utils/src/tests/testing_utils.rs +7 -5
- package/contracts/utils/src/ttl.rs +21 -2
- package/contracts/workers/dvn/src/auth.rs +108 -30
- package/contracts/workers/dvn/src/dvn.rs +103 -33
- package/contracts/workers/dvn/src/errors.rs +10 -13
- package/contracts/workers/dvn/src/events.rs +7 -5
- package/contracts/workers/dvn/src/interfaces/dvn.rs +76 -3
- package/contracts/workers/dvn/src/interfaces/multisig.rs +41 -0
- package/contracts/workers/dvn/src/lib.rs +6 -8
- package/contracts/workers/dvn/src/multisig.rs +98 -72
- package/contracts/workers/dvn/src/storage.rs +9 -12
- package/contracts/workers/dvn/src/tests/auth.rs +56 -26
- package/contracts/workers/dvn/src/tests/dvn.rs +40 -41
- package/contracts/workers/dvn/src/tests/multisig/set_signer.rs +8 -8
- package/contracts/workers/dvn/src/tests/multisig/set_threshold.rs +9 -9
- package/contracts/workers/dvn/src/tests/multisig/verify_signatures.rs +6 -6
- package/contracts/workers/dvn/src/tests/setup.rs +5 -5
- package/contracts/workers/dvn-fee-lib/Cargo.toml +2 -1
- package/contracts/workers/dvn-fee-lib/src/dvn_fee_lib.rs +4 -3
- package/contracts/workers/dvn-fee-lib/src/tests/dvn_fee_lib.rs +8 -6
- package/contracts/workers/executor/src/auth.rs +93 -0
- package/contracts/workers/executor/src/events.rs +5 -4
- package/contracts/workers/executor/src/{lz_executor.rs → executor.rs} +30 -103
- package/contracts/workers/executor/src/interfaces/executor.rs +5 -2
- package/contracts/workers/executor/src/interfaces/mod.rs +1 -1
- package/contracts/workers/executor/src/lib.rs +6 -5
- package/contracts/workers/price-feed/Cargo.toml +21 -0
- package/contracts/workers/price-feed/src/errors.rs +9 -0
- package/contracts/workers/price-feed/src/events.rs +30 -0
- package/contracts/workers/price-feed/src/lib.rs +11 -0
- package/contracts/workers/price-feed/src/price_feed.rs +265 -0
- package/contracts/workers/price-feed/src/storage.rs +42 -0
- package/contracts/workers/price-feed/src/types.rs +59 -0
- package/contracts/workers/worker/src/events.rs +23 -13
- package/contracts/workers/worker/src/interfaces/dvn_fee_lib.rs +2 -1
- package/contracts/workers/worker/src/worker.rs +32 -21
- package/package.json +3 -3
- package/sdk/dist/generated/bml.js +24 -22
- package/sdk/dist/generated/counter.d.ts +102 -0
- package/sdk/dist/generated/counter.js +36 -24
- package/sdk/dist/generated/endpoint.js +24 -22
- package/sdk/dist/generated/sml.js +24 -22
- package/sdk/dist/generated/uln302.d.ts +1 -1
- package/sdk/dist/generated/uln302.js +34 -32
- package/sdk/package.json +1 -1
- package/sdk/test/index.test.ts +1 -1
- package/sdk/test/oft.test.ts +847 -0
- package/sdk/test/suites/scan.ts +20 -4
- package/tools/ts-bindings-gen/src/main.rs +2 -1
- package/contracts/common-macros/src/event.rs +0 -16
- package/contracts/oapps/oft/src/macro_tests/mod.rs +0 -2
- package/contracts/oapps/oft/src/macro_tests/test_all_default.rs +0 -41
- package/contracts/oapps/oft/src/macro_tests/test_override.rs +0 -83
- package/contracts/oapps/oft-mint-burn/src/lib.rs +0 -3
- package/contracts/oapps/oft-mint-burn/src/oft.rs +0 -28
- package/contracts/oapps/oft-mint-burn/src/tests/mod.rs +0 -1
- package/contracts/workers/dvn/src/types.rs +0 -26
- /package/contracts/message-libs/{block-message-lib → blocked-message-lib}/src/lib.rs +0 -0
|
@@ -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
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
use
|
|
2
|
-
use soroban_sdk::{Address, BytesN};
|
|
1
|
+
use soroban_sdk::{contractevent, Address, BytesN};
|
|
3
2
|
|
|
4
|
-
#[
|
|
3
|
+
#[contractevent]
|
|
4
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
5
5
|
pub struct OFTSent {
|
|
6
6
|
pub guid: BytesN<32>,
|
|
7
7
|
pub dst_eid: u32,
|
|
@@ -10,7 +10,8 @@ pub struct OFTSent {
|
|
|
10
10
|
pub amount_received_ld: i128,
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
#[
|
|
13
|
+
#[contractevent]
|
|
14
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
14
15
|
pub struct OFTReceived {
|
|
15
16
|
pub guid: BytesN<32>,
|
|
16
17
|
pub src_eid: u32,
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
use common_macros::{contract_error, only_owner, storage};
|
|
2
|
+
use soroban_sdk::{assert_with_error, contractevent, 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
|
+
#[contractevent]
|
|
31
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
32
|
+
pub struct DefaultFeeBpsSet {
|
|
33
|
+
pub fee_bps: u64,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
#[contractevent]
|
|
37
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
38
|
+
pub struct FeeBpsSet {
|
|
39
|
+
pub dst_eid: u32,
|
|
40
|
+
pub fee_bps: u64,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#[contractevent]
|
|
44
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
45
|
+
pub struct FeeBpsUnset {
|
|
46
|
+
pub dst_eid: u32,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#[contractevent]
|
|
50
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
51
|
+
pub struct FeeDepositAddressSet {
|
|
52
|
+
pub fee_deposit_address: Address,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[contracttrait]
|
|
56
|
+
pub trait OFTFee: OFTFeeInternal + Ownable + Sized {
|
|
57
|
+
#[only_owner]
|
|
58
|
+
fn set_default_fee_bps(env: &Env, default_fee_bps: u64) {
|
|
59
|
+
assert_with_error!(env, default_fee_bps <= BASE_FEE_BPS, OFTFeeError::InvalidFeeBps);
|
|
60
|
+
let current_default = OFTFeeStorage::default_fee_bps(env);
|
|
61
|
+
assert_with_error!(env, current_default != default_fee_bps, OFTFeeError::SameValue);
|
|
62
|
+
OFTFeeStorage::set_default_fee_bps(env, &default_fee_bps);
|
|
63
|
+
DefaultFeeBpsSet { fee_bps: default_fee_bps }.publish(env);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#[only_owner]
|
|
67
|
+
fn set_fee_bps(env: &Env, dst_eid: u32, fee_bps: u64) {
|
|
68
|
+
assert_with_error!(env, fee_bps <= BASE_FEE_BPS, OFTFeeError::InvalidFeeBps);
|
|
69
|
+
if OFTFeeStorage::has_fee_bps(env, dst_eid) {
|
|
70
|
+
let current_fee_bps = OFTFeeStorage::fee_bps(env, dst_eid).unwrap();
|
|
71
|
+
assert_with_error!(env, current_fee_bps != fee_bps, OFTFeeError::SameValue);
|
|
72
|
+
}
|
|
73
|
+
OFTFeeStorage::set_fee_bps(env, dst_eid, &fee_bps);
|
|
74
|
+
FeeBpsSet { dst_eid, fee_bps }.publish(env);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
#[only_owner]
|
|
78
|
+
fn unset_fee_bps(env: &Env, dst_eid: u32) {
|
|
79
|
+
assert_with_error!(env, OFTFeeStorage::has_fee_bps(env, dst_eid), OFTFeeError::NotFound);
|
|
80
|
+
OFTFeeStorage::remove_fee_bps(env, dst_eid);
|
|
81
|
+
FeeBpsUnset { dst_eid }.publish(env);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#[only_owner]
|
|
85
|
+
fn set_fee_deposit_address(env: &Env, fee_deposit_address: Address) {
|
|
86
|
+
if let Some(current_address) = OFTFeeStorage::fee_deposit_address(env) {
|
|
87
|
+
assert_with_error!(env, current_address != fee_deposit_address, OFTFeeError::SameValue);
|
|
88
|
+
}
|
|
89
|
+
OFTFeeStorage::set_fee_deposit_address(env, &fee_deposit_address);
|
|
90
|
+
FeeDepositAddressSet { fee_deposit_address }.publish(env);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fn has_fee_bps(env: &Env, dst_eid: u32) -> bool {
|
|
94
|
+
Self::effective_fee_bps(env, dst_eid) > 0
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
fn effective_fee_bps(env: &Env, dst_eid: u32) -> u64 {
|
|
98
|
+
if OFTFeeStorage::has_fee_bps(env, dst_eid) {
|
|
99
|
+
OFTFeeStorage::fee_bps(env, dst_eid).unwrap()
|
|
100
|
+
} else {
|
|
101
|
+
OFTFeeStorage::default_fee_bps(env)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fn default_fee_bps(env: &Env) -> u64 {
|
|
106
|
+
OFTFeeStorage::default_fee_bps(env)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
fn fee_bps(env: &Env, dst_eid: u32) -> Option<u64> {
|
|
110
|
+
OFTFeeStorage::fee_bps(env, dst_eid)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
fn fee_deposit_address(env: &Env) -> Address {
|
|
114
|
+
OFTFeeStorage::fee_deposit_address(env).unwrap_or_panic(env, OFTFeeError::InvalidFeeDepositAddress)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// Internal trait for OFT fee operations used by OFT hooks.
|
|
119
|
+
/// Contains only truly internal methods that are called from OFTInner implementations.
|
|
120
|
+
pub trait OFTFeeInternal {
|
|
121
|
+
/// Applies the configured fee to the given amount and returns the amount after fee deduction.
|
|
122
|
+
/// Used internally by `__debit_view` to calculate amount after fee.
|
|
123
|
+
///
|
|
124
|
+
/// # Arguments
|
|
125
|
+
/// * `dst_eid` - Destination endpoint ID to determine which fee rate to apply
|
|
126
|
+
/// * `amount_ld` - The original amount in local decimals
|
|
127
|
+
///
|
|
128
|
+
/// # Returns
|
|
129
|
+
/// The amount after fee deduction (original amount - calculated fee)
|
|
130
|
+
fn __apply_fee(env: &Env, dst_eid: u32, amount_ld: i128) -> i128 {
|
|
131
|
+
// Calculate effective fee (destination-specific or default)
|
|
132
|
+
let fee_bps = if OFTFeeStorage::has_fee_bps(env, dst_eid) {
|
|
133
|
+
OFTFeeStorage::fee_bps(env, dst_eid).unwrap()
|
|
134
|
+
} else {
|
|
135
|
+
OFTFeeStorage::default_fee_bps(env)
|
|
136
|
+
};
|
|
137
|
+
if fee_bps == 0 {
|
|
138
|
+
return amount_ld;
|
|
139
|
+
}
|
|
140
|
+
// Check that fee deposit address is set (required for fee collection)
|
|
141
|
+
assert_with_error!(
|
|
142
|
+
env,
|
|
143
|
+
OFTFeeStorage::fee_deposit_address(env).is_some(),
|
|
144
|
+
OFTFeeError::InvalidFeeDepositAddress
|
|
145
|
+
);
|
|
146
|
+
let preliminary_fee = (amount_ld * fee_bps as i128) / BASE_FEE_BPS as i128;
|
|
147
|
+
amount_ld - preliminary_fee
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/// Transfers the fee amount from sender to the fee deposit address.
|
|
151
|
+
/// Used internally by `__debit` to collect the fee after calculating the debit amounts.
|
|
152
|
+
///
|
|
153
|
+
/// # Arguments
|
|
154
|
+
/// * `token` - The token address to transfer
|
|
155
|
+
/// * `sender` - The sender address to transfer fee from
|
|
156
|
+
/// * `fee_amount` - The fee amount to transfer
|
|
157
|
+
fn __transfer_fee(env: &Env, token: &Address, sender: &Address, fee_amount: i128) {
|
|
158
|
+
if fee_amount > 0 {
|
|
159
|
+
assert_with_error!(
|
|
160
|
+
env,
|
|
161
|
+
OFTFeeStorage::fee_deposit_address(env).is_some(),
|
|
162
|
+
OFTFeeError::InvalidFeeDepositAddress
|
|
163
|
+
);
|
|
164
|
+
let fee_deposit = OFTFeeStorage::fee_deposit_address(env).unwrap();
|
|
165
|
+
TokenClient::new(env, token).transfer(sender, &fee_deposit, &fee_amount);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
use common_macros::{contract_error, only_owner, storage};
|
|
2
|
+
use soroban_sdk::{assert_with_error, contractevent, 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
|
+
#[contractevent]
|
|
19
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
20
|
+
pub struct PausedSet {
|
|
21
|
+
pub paused: bool,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
#[contracttrait]
|
|
25
|
+
pub trait OFTPausable: OFTPausableInternal + Ownable + Sized {
|
|
26
|
+
#[only_owner]
|
|
27
|
+
fn set_paused(env: &Env, paused: bool) {
|
|
28
|
+
let current_paused = OFTPausableStorage::paused(env);
|
|
29
|
+
assert_with_error!(env, current_paused != paused, OFTPausableError::PauseStatusUnchanged);
|
|
30
|
+
OFTPausableStorage::set_paused(env, &paused);
|
|
31
|
+
PausedSet { paused }.publish(env);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
fn is_paused(env: &Env) -> bool {
|
|
35
|
+
OFTPausableStorage::paused(env)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/// Internal trait for pausable operations used by OFT hooks.
|
|
40
|
+
/// Contains only truly internal methods that are called from OFTInner implementations.
|
|
41
|
+
pub trait OFTPausableInternal {
|
|
42
|
+
/// Asserts that the OFT is not paused, panics otherwise.
|
|
43
|
+
/// Used internally by `send`, `quote_send`, and `__lz_receive` to enforce pause state.
|
|
44
|
+
///
|
|
45
|
+
/// # Errors
|
|
46
|
+
/// * `Paused` - If the OFT is currently paused.
|
|
47
|
+
fn __assert_not_paused(env: &Env) {
|
|
48
|
+
assert_with_error!(env, !OFTPausableStorage::paused(env), OFTPausableError::Paused);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
use common_macros::{contract_error, only_owner, storage};
|
|
2
|
+
use soroban_sdk::{assert_with_error, contractevent, 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
|
+
#[contractevent]
|
|
38
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
39
|
+
pub struct RateLimitSet {
|
|
40
|
+
pub direction: Direction,
|
|
41
|
+
pub eid: u32,
|
|
42
|
+
pub limit: i128,
|
|
43
|
+
pub window_seconds: u64,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#[contractevent]
|
|
47
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
48
|
+
pub struct RateLimitUpdated {
|
|
49
|
+
pub direction: Direction,
|
|
50
|
+
pub eid: u32,
|
|
51
|
+
pub limit: i128,
|
|
52
|
+
pub window_seconds: u64,
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
#[contractevent]
|
|
56
|
+
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
57
|
+
pub struct RateLimitUnset {
|
|
58
|
+
pub direction: Direction,
|
|
59
|
+
pub eid: u32,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Helper function to calculate the current in-flight amount with decay.
|
|
63
|
+
/// Used by both public trait methods and internal implementations.
|
|
64
|
+
fn calculate_in_flight(env: &Env, direction: &Direction, eid: u32) -> i128 {
|
|
65
|
+
if !RateLimitStorage::has_rate_limit(env, direction, eid) {
|
|
66
|
+
return 0;
|
|
67
|
+
}
|
|
68
|
+
let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
|
|
69
|
+
let timestamp = env.ledger().timestamp();
|
|
70
|
+
assert_with_error!(env, timestamp >= rate_limit.last_update, RateLimitError::InvalidTimestamp);
|
|
71
|
+
let elapsed = timestamp - rate_limit.last_update;
|
|
72
|
+
let decay = (elapsed as i128) * rate_limit.limit / (rate_limit.window_seconds as i128);
|
|
73
|
+
if decay < rate_limit.in_flight_on_last_update {
|
|
74
|
+
rate_limit.in_flight_on_last_update - decay
|
|
75
|
+
} else {
|
|
76
|
+
0
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
#[contracttrait]
|
|
81
|
+
pub trait RateLimiter: RateLimiterInternal + Ownable + Sized {
|
|
82
|
+
#[only_owner]
|
|
83
|
+
fn set_rate_limit(env: &Env, direction: &Direction, eid: u32, limit: i128, window_seconds: u64) {
|
|
84
|
+
assert_with_error!(env, limit > 0, RateLimitError::InvalidLimit);
|
|
85
|
+
assert_with_error!(env, window_seconds > 0, RateLimitError::InvalidWindowSeconds);
|
|
86
|
+
if RateLimitStorage::has_rate_limit(env, direction, eid) {
|
|
87
|
+
let rate_limit_data = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
|
|
88
|
+
assert_with_error!(
|
|
89
|
+
env,
|
|
90
|
+
limit != rate_limit_data.limit || window_seconds != rate_limit_data.window_seconds,
|
|
91
|
+
RateLimitError::SameValue
|
|
92
|
+
);
|
|
93
|
+
Self::__checkpoint_rate_limit_in_flight(env, direction, eid);
|
|
94
|
+
let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
|
|
95
|
+
rate_limit.limit = limit;
|
|
96
|
+
rate_limit.window_seconds = window_seconds;
|
|
97
|
+
RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
|
|
98
|
+
RateLimitUpdated { direction: direction.clone(), eid, limit, window_seconds }.publish(env);
|
|
99
|
+
} else {
|
|
100
|
+
RateLimitStorage::set_rate_limit(
|
|
101
|
+
env,
|
|
102
|
+
direction,
|
|
103
|
+
eid,
|
|
104
|
+
&RateLimit {
|
|
105
|
+
limit,
|
|
106
|
+
window_seconds,
|
|
107
|
+
in_flight_on_last_update: 0,
|
|
108
|
+
last_update: env.ledger().timestamp(),
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
RateLimitSet { direction: direction.clone(), eid, limit, window_seconds }.publish(env);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
#[only_owner]
|
|
116
|
+
fn unset_rate_limit(env: &Env, direction: &Direction, eid: u32) {
|
|
117
|
+
assert_with_error!(env, RateLimitStorage::has_rate_limit(env, direction, eid), RateLimitError::SameValue);
|
|
118
|
+
RateLimitStorage::remove_rate_limit(env, direction, eid);
|
|
119
|
+
RateLimitUnset { direction: direction.clone(), eid }.publish(env);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
fn rate_limit_config(env: &Env, direction: &Direction, eid: u32) -> (i128, u64) {
|
|
123
|
+
if !RateLimitStorage::has_rate_limit(env, direction, eid) {
|
|
124
|
+
return (0, 0);
|
|
125
|
+
}
|
|
126
|
+
let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
|
|
127
|
+
(rate_limit.limit, rate_limit.window_seconds)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
fn rate_limit_in_flight(env: &Env, direction: &Direction, eid: u32) -> i128 {
|
|
131
|
+
calculate_in_flight(env, direction, eid)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fn rate_limit_capacity(env: &Env, direction: &Direction, eid: u32) -> i128 {
|
|
135
|
+
if !RateLimitStorage::has_rate_limit(env, direction, eid) {
|
|
136
|
+
return i128::MAX;
|
|
137
|
+
}
|
|
138
|
+
let rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
|
|
139
|
+
let in_flight = calculate_in_flight(env, direction, eid);
|
|
140
|
+
if rate_limit.limit > in_flight {
|
|
141
|
+
rate_limit.limit - in_flight
|
|
142
|
+
} else {
|
|
143
|
+
0
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Internal trait for rate limiter operations used by OFT hooks.
|
|
149
|
+
/// Contains only truly internal methods that are called from OFTInner implementations.
|
|
150
|
+
pub trait RateLimiterInternal {
|
|
151
|
+
/// Tries to consume the specified amount from the rate limit capacity.
|
|
152
|
+
/// Used internally by `send` and `__lz_receive` to enforce rate limits.
|
|
153
|
+
///
|
|
154
|
+
/// # Errors
|
|
155
|
+
/// * `ExceededRateLimit` - If the amount exceeds the available capacity.
|
|
156
|
+
fn __try_consume_rate_limit_capacity(env: &Env, direction: &Direction, eid: u32, amount: i128) {
|
|
157
|
+
if !RateLimitStorage::has_rate_limit(env, direction, eid) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
Self::__checkpoint_rate_limit_in_flight(env, direction, eid);
|
|
161
|
+
let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
|
|
162
|
+
// Check against remaining capacity (limit - current in_flight), not total limit
|
|
163
|
+
let capacity = if rate_limit.limit > rate_limit.in_flight_on_last_update {
|
|
164
|
+
rate_limit.limit - rate_limit.in_flight_on_last_update
|
|
165
|
+
} else {
|
|
166
|
+
0
|
|
167
|
+
};
|
|
168
|
+
if amount > capacity {
|
|
169
|
+
panic_with_error!(env, RateLimitError::ExceededRateLimit);
|
|
170
|
+
}
|
|
171
|
+
rate_limit.in_flight_on_last_update += amount;
|
|
172
|
+
RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// Releases the specified amount back to the rate limit capacity.
|
|
176
|
+
/// Used internally by `__lz_receive` to release outbound capacity on inbound messages.
|
|
177
|
+
fn __release_rate_limit_capacity(env: &Env, direction: &Direction, eid: u32, amount: i128) {
|
|
178
|
+
if !RateLimitStorage::has_rate_limit(env, direction, eid) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
Self::__checkpoint_rate_limit_in_flight(env, direction, eid);
|
|
182
|
+
let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
|
|
183
|
+
if amount >= rate_limit.in_flight_on_last_update {
|
|
184
|
+
rate_limit.in_flight_on_last_update = 0;
|
|
185
|
+
} else {
|
|
186
|
+
rate_limit.in_flight_on_last_update -= amount;
|
|
187
|
+
}
|
|
188
|
+
RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// Checkpoints the current in-flight amount by applying decay and updating timestamp.
|
|
192
|
+
/// Used internally before modifying rate limit state.
|
|
193
|
+
fn __checkpoint_rate_limit_in_flight(env: &Env, direction: &Direction, eid: u32) {
|
|
194
|
+
let in_flight = calculate_in_flight(env, direction, eid);
|
|
195
|
+
let mut rate_limit = RateLimitStorage::rate_limit(env, direction, eid).unwrap();
|
|
196
|
+
rate_limit.in_flight_on_last_update = in_flight;
|
|
197
|
+
rate_limit.last_update = env.ledger().timestamp();
|
|
198
|
+
RateLimitStorage::set_rate_limit(env, direction, eid, &rate_limit);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -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;
|