@layerzerolabs/protocol-stellar-v2 0.2.65 → 0.2.67
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 +218 -299
- package/.turbo/turbo-lint.log +225 -98
- package/.turbo/turbo-test.log +2010 -1924
- package/Cargo.lock +0 -16
- package/Cargo.toml +0 -1
- package/contracts/oapps/oft/integration-tests/extensions/test_oft_fee.rs +22 -0
- package/contracts/oapps/oft/integration-tests/extensions/test_pausable.rs +9 -2
- package/contracts/oapps/oft/integration-tests/extensions/test_rate_limiter.rs +27 -2
- package/contracts/oapps/oft/integration-tests/setup.rs +22 -18
- package/contracts/oapps/oft/integration-tests/utils.rs +81 -34
- package/contracts/oapps/oft/src/extensions/oft_fee.rs +13 -0
- package/contracts/oapps/oft/src/oft.rs +10 -2
- package/package.json +4 -4
- package/sdk/.turbo/turbo-test.log +299 -307
- package/sdk/dist/generated/oft.d.ts +3 -3
- package/sdk/dist/generated/oft.js +3 -3
- package/sdk/node_modules/.bin/vitest +2 -2
- package/sdk/package.json +1 -1
- package/contracts/oapps/console-oft/Cargo.toml +0 -30
- package/contracts/oapps/console-oft/integration-tests/extensions/mod.rs +0 -5
- package/contracts/oapps/console-oft/integration-tests/extensions/test_combined.rs +0 -90
- package/contracts/oapps/console-oft/integration-tests/extensions/test_oft_fee.rs +0 -186
- package/contracts/oapps/console-oft/integration-tests/extensions/test_ownership.rs +0 -161
- package/contracts/oapps/console-oft/integration-tests/extensions/test_pausable.rs +0 -154
- package/contracts/oapps/console-oft/integration-tests/extensions/test_rate_limiter.rs +0 -479
- package/contracts/oapps/console-oft/integration-tests/mod.rs +0 -3
- package/contracts/oapps/console-oft/integration-tests/setup.rs +0 -303
- package/contracts/oapps/console-oft/integration-tests/utils.rs +0 -685
- package/contracts/oapps/console-oft/src/errors.rs +0 -7
- package/contracts/oapps/console-oft/src/extensions/mod.rs +0 -3
- package/contracts/oapps/console-oft/src/extensions/oft_fee.rs +0 -239
- package/contracts/oapps/console-oft/src/extensions/pausable.rs +0 -185
- package/contracts/oapps/console-oft/src/extensions/rate_limiter.rs +0 -478
- package/contracts/oapps/console-oft/src/interfaces/mintable.rs +0 -14
- package/contracts/oapps/console-oft/src/interfaces/mod.rs +0 -3
- package/contracts/oapps/console-oft/src/lib.rs +0 -26
- package/contracts/oapps/console-oft/src/oft.rs +0 -208
- package/contracts/oapps/console-oft/src/oft_access_control.rs +0 -93
- package/contracts/oapps/console-oft/src/oft_types/lock_unlock.rs +0 -50
- package/contracts/oapps/console-oft/src/oft_types/mint_burn.rs +0 -50
- package/contracts/oapps/console-oft/src/oft_types/mod.rs +0 -24
- package/contracts/oapps/console-oft/src/tests/extensions/mod.rs +0 -3
- package/contracts/oapps/console-oft/src/tests/extensions/oft_fee.rs +0 -255
- package/contracts/oapps/console-oft/src/tests/extensions/pausable.rs +0 -212
- package/contracts/oapps/console-oft/src/tests/extensions/rate_limiter.rs +0 -992
- package/contracts/oapps/console-oft/src/tests/mod.rs +0 -2
- package/contracts/oapps/console-oft/src/tests/oft_types/lock_unlock.rs +0 -185
- package/contracts/oapps/console-oft/src/tests/oft_types/mod.rs +0 -1
|
@@ -1,478 +0,0 @@
|
|
|
1
|
-
use crate as oft;
|
|
2
|
-
use common_macros::{contract_error, contract_trait, only_role, storage};
|
|
3
|
-
use soroban_sdk::{assert_with_error, contractevent, contracttype, Address, Env};
|
|
4
|
-
use utils::rbac::RoleBasedAccessControl;
|
|
5
|
-
|
|
6
|
-
/// Role required for all rate limiter management operations.
|
|
7
|
-
pub const RATE_LIMITER_MANAGER_ROLE: &str = "RATE_LIMITER_MANAGER_ROLE";
|
|
8
|
-
|
|
9
|
-
/// ID of the default rate limit configuration.
|
|
10
|
-
pub const DEFAULT_ID: u128 = 0;
|
|
11
|
-
|
|
12
|
-
/// Sentinel value indicating unlimited capacity (no rate limiting applied).
|
|
13
|
-
pub const UNLIMITED_AMOUNT: i128 = i128::MAX;
|
|
14
|
-
|
|
15
|
-
// =========================================================================
|
|
16
|
-
// Types
|
|
17
|
-
// =========================================================================
|
|
18
|
-
|
|
19
|
-
/// Global configuration for the rate limiter.
|
|
20
|
-
///
|
|
21
|
-
/// - `use_global_state`: when true, all IDs share config and state from DEFAULT_ID (0)
|
|
22
|
-
/// - `is_globally_disabled`: when true, all rate limit checks are bypassed
|
|
23
|
-
#[contracttype]
|
|
24
|
-
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
25
|
-
pub struct RateLimitGlobalConfig {
|
|
26
|
-
pub use_global_state: bool,
|
|
27
|
-
pub is_globally_disabled: bool,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/// Per-Destination ID configuration: flags, limits, and windows.
|
|
31
|
-
///
|
|
32
|
-
/// Stored per Destination ID. If no config is stored for an ID, it inherits from DEFAULT_ID (0).
|
|
33
|
-
/// If DEFAULT_ID has no config either, `RateLimitConfig::default()` is used.
|
|
34
|
-
#[contracttype]
|
|
35
|
-
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
36
|
-
pub struct RateLimitConfig {
|
|
37
|
-
/// Whether outbound (send) rate limiting is enabled
|
|
38
|
-
pub outbound_enabled: bool,
|
|
39
|
-
/// Whether inbound (receive) rate limiting is enabled
|
|
40
|
-
pub inbound_enabled: bool,
|
|
41
|
-
/// Whether inbound flow releases outbound capacity and vice versa
|
|
42
|
-
pub net_accounting_enabled: bool,
|
|
43
|
-
/// Whether per-address exemptions are checked for this Destination ID
|
|
44
|
-
pub address_exemption_enabled: bool,
|
|
45
|
-
/// Maximum outbound (send) capacity in outbound window period
|
|
46
|
-
pub outbound_limit: i128,
|
|
47
|
-
/// Maximum inbound (receive) capacity in inbound window period
|
|
48
|
-
pub inbound_limit: i128,
|
|
49
|
-
/// Time window (in seconds) over which outbound usage fully decays back to zero
|
|
50
|
-
pub outbound_window: u64,
|
|
51
|
-
/// Time window (in seconds) over which inbound usage fully decays back to zero
|
|
52
|
-
pub inbound_window: u64,
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
impl Default for RateLimitConfig {
|
|
56
|
-
fn default() -> Self {
|
|
57
|
-
Self {
|
|
58
|
-
outbound_enabled: true,
|
|
59
|
-
inbound_enabled: true,
|
|
60
|
-
net_accounting_enabled: true,
|
|
61
|
-
address_exemption_enabled: false,
|
|
62
|
-
outbound_limit: 0,
|
|
63
|
-
inbound_limit: 0,
|
|
64
|
-
outbound_window: 0,
|
|
65
|
-
inbound_window: 0,
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/// Mutable usage counters and timestamp for a rate-limited Destination ID.
|
|
71
|
-
#[contracttype]
|
|
72
|
-
#[derive(Clone, Debug, Default, Eq, PartialEq)]
|
|
73
|
-
pub struct RateLimitState {
|
|
74
|
-
pub outbound_usage: i128,
|
|
75
|
-
pub inbound_usage: i128,
|
|
76
|
-
pub last_updated: u64,
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/// Decayed usage and available capacity for a single Destination ID.
|
|
80
|
-
#[contracttype]
|
|
81
|
-
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
82
|
-
pub struct RateLimitUsages {
|
|
83
|
-
pub outbound_usage: i128,
|
|
84
|
-
pub outbound_available_amount: i128,
|
|
85
|
-
pub inbound_usage: i128,
|
|
86
|
-
pub inbound_available_amount: i128,
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// =========================================================================
|
|
90
|
-
// Storage
|
|
91
|
-
// =========================================================================
|
|
92
|
-
|
|
93
|
-
#[storage]
|
|
94
|
-
enum RateLimitStorage {
|
|
95
|
-
#[instance(RateLimitGlobalConfig)]
|
|
96
|
-
#[default(Default::default())]
|
|
97
|
-
GlobalConfig,
|
|
98
|
-
|
|
99
|
-
#[persistent(RateLimitConfig)]
|
|
100
|
-
RateLimitConfigs { id: u128 },
|
|
101
|
-
|
|
102
|
-
#[persistent(RateLimitState)]
|
|
103
|
-
#[default(Default::default())]
|
|
104
|
-
RateLimitStates { id: u128 },
|
|
105
|
-
|
|
106
|
-
#[persistent(bool)]
|
|
107
|
-
#[default(false)]
|
|
108
|
-
AddressExemptions { user: Address },
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// =========================================================================
|
|
112
|
-
// Errors
|
|
113
|
-
// =========================================================================
|
|
114
|
-
|
|
115
|
-
#[contract_error]
|
|
116
|
-
pub enum RateLimitError {
|
|
117
|
-
ExemptionStateIdempotent = 3120,
|
|
118
|
-
InboundLimitNegative,
|
|
119
|
-
InboundUsageNegative,
|
|
120
|
-
InvalidAmount,
|
|
121
|
-
LastUpdatedInFuture,
|
|
122
|
-
OutboundLimitNegative,
|
|
123
|
-
OutboundUsageNegative,
|
|
124
|
-
RateLimitExceeded,
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// =========================================================================
|
|
128
|
-
// Events
|
|
129
|
-
// =========================================================================
|
|
130
|
-
|
|
131
|
-
#[contractevent]
|
|
132
|
-
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
133
|
-
pub struct RateLimitConfigUpdated {
|
|
134
|
-
#[topic]
|
|
135
|
-
pub id: u128,
|
|
136
|
-
pub config: Option<RateLimitConfig>,
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
#[contractevent]
|
|
140
|
-
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
141
|
-
pub struct RateLimitStateUpdated {
|
|
142
|
-
#[topic]
|
|
143
|
-
pub id: u128,
|
|
144
|
-
pub state: RateLimitState,
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
#[contractevent]
|
|
148
|
-
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
149
|
-
pub struct RateLimitGlobalConfigUpdated {
|
|
150
|
-
pub global_config: Option<RateLimitGlobalConfig>,
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
#[contractevent]
|
|
154
|
-
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
155
|
-
pub struct RateLimitExemptionUpdated {
|
|
156
|
-
#[topic]
|
|
157
|
-
pub user: Address,
|
|
158
|
-
pub is_exempt: bool,
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// =========================================================================
|
|
162
|
-
// Public Trait — mirrors EVM RateLimiterRBACUpgradeable
|
|
163
|
-
// =========================================================================
|
|
164
|
-
|
|
165
|
-
/// Rate limiter extension — controls token flow per Destination ID with rolling window decay.
|
|
166
|
-
///
|
|
167
|
-
/// **Default state:** Closed (outbound+inbound enabled, limits=0). All transfers are blocked
|
|
168
|
-
/// until the admin explicitly sets non-zero limits via `set_rate_limit_config`.
|
|
169
|
-
///
|
|
170
|
-
/// **Config resolution:** Each Destination ID can override the DEFAULT_ID (0) config, or
|
|
171
|
-
/// inherit it. When `use_global_state` is true, all Destination IDs share config and state from DEFAULT_ID.
|
|
172
|
-
///
|
|
173
|
-
/// **Decay model:** Usage decays linearly over the configured window:
|
|
174
|
-
/// `current_usage = max(0, in_flight - limit * time_elapsed / window)`
|
|
175
|
-
/// `available = max(0, limit - current_usage)`
|
|
176
|
-
///
|
|
177
|
-
/// **Net accounting:** When enabled, inbound flow releases outbound capacity and vice versa.
|
|
178
|
-
#[contract_trait]
|
|
179
|
-
pub trait RateLimiter: RateLimiterInternal + RoleBasedAccessControl {
|
|
180
|
-
// === Management (RBAC-guarded) ===
|
|
181
|
-
|
|
182
|
-
/// Sets or removes the global config (use_global_state, is_globally_disabled).
|
|
183
|
-
///
|
|
184
|
-
/// - `Some(config)` — stores global config.
|
|
185
|
-
/// - `None` — removes global config (reverts to default: both flags `false`).
|
|
186
|
-
#[only_role(operator, RATE_LIMITER_MANAGER_ROLE)]
|
|
187
|
-
fn set_rate_limit_global_config(
|
|
188
|
-
env: &soroban_sdk::Env,
|
|
189
|
-
global_config: &Option<oft::rate_limiter::RateLimitGlobalConfig>,
|
|
190
|
-
operator: &soroban_sdk::Address,
|
|
191
|
-
) {
|
|
192
|
-
Self::__set_rate_limit_global_config(env, global_config);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/// Sets or removes rate limit configuration for a Destination ID.
|
|
196
|
-
///
|
|
197
|
-
/// - `Some(config)` — validates and stores config for this Destination ID.
|
|
198
|
-
/// - `None` — removes per-Destination ID config (falls back to DEFAULT_ID).
|
|
199
|
-
#[only_role(operator, RATE_LIMITER_MANAGER_ROLE)]
|
|
200
|
-
fn set_rate_limit_config(
|
|
201
|
-
env: &soroban_sdk::Env,
|
|
202
|
-
id: u128,
|
|
203
|
-
config: &Option<oft::rate_limiter::RateLimitConfig>,
|
|
204
|
-
operator: &soroban_sdk::Address,
|
|
205
|
-
) {
|
|
206
|
-
Self::__set_rate_limit_config(env, id, config);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
/// Directly sets rate limit state (usage, last_updated) for admin correction.
|
|
210
|
-
#[only_role(operator, RATE_LIMITER_MANAGER_ROLE)]
|
|
211
|
-
fn set_rate_limit_state(
|
|
212
|
-
env: &soroban_sdk::Env,
|
|
213
|
-
id: u128,
|
|
214
|
-
state: &oft::rate_limiter::RateLimitState,
|
|
215
|
-
operator: &soroban_sdk::Address,
|
|
216
|
-
) {
|
|
217
|
-
Self::__set_rate_limit_state(env, id, state);
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/// Sets per-address exemption from rate limiting.
|
|
221
|
-
///
|
|
222
|
-
/// Exempt addresses bypass rate limit checks entirely (when `address_exemption_enabled`
|
|
223
|
-
/// is true in the config).
|
|
224
|
-
#[only_role(operator, RATE_LIMITER_MANAGER_ROLE)]
|
|
225
|
-
fn set_rate_limit_exemption(
|
|
226
|
-
env: &soroban_sdk::Env,
|
|
227
|
-
user: &soroban_sdk::Address,
|
|
228
|
-
is_exempt: bool,
|
|
229
|
-
operator: &soroban_sdk::Address,
|
|
230
|
-
) {
|
|
231
|
-
Self::__set_rate_limit_exemption(env, user, is_exempt);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/// Snapshots decayed usage to storage for the given Destination ID.
|
|
235
|
-
#[only_role(operator, RATE_LIMITER_MANAGER_ROLE)]
|
|
236
|
-
fn checkpoint_rate_limit(env: &soroban_sdk::Env, id: u128, operator: &soroban_sdk::Address) {
|
|
237
|
-
Self::__checkpoint_rate_limit(env, id);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// === Views ===
|
|
241
|
-
|
|
242
|
-
/// Returns the global rate limit configuration.
|
|
243
|
-
fn get_rate_limit_global_config(env: &soroban_sdk::Env) -> oft::rate_limiter::RateLimitGlobalConfig {
|
|
244
|
-
RateLimitStorage::global_config(env)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/// Returns the raw per-Destination ID state and config (not resolved through inheritance).
|
|
248
|
-
///
|
|
249
|
-
/// Config is `None` when no per-Destination ID config is stored.
|
|
250
|
-
fn rate_limits(
|
|
251
|
-
env: &soroban_sdk::Env,
|
|
252
|
-
id: u128,
|
|
253
|
-
) -> (oft::rate_limiter::RateLimitState, Option<oft::rate_limiter::RateLimitConfig>) {
|
|
254
|
-
(RateLimitStorage::rate_limit_states(env, id), RateLimitStorage::rate_limit_configs(env, id))
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/// Returns whether the given address is exempt from rate limiting.
|
|
258
|
-
fn is_rate_limit_exemption(env: &soroban_sdk::Env, user: &soroban_sdk::Address) -> bool {
|
|
259
|
-
RateLimitStorage::address_exemptions(env, user)
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/// Returns the current decayed usage and available capacity for the given Destination ID.
|
|
263
|
-
///
|
|
264
|
-
/// If globally disabled, returns `UNLIMITED_AMOUNT` for available amounts.
|
|
265
|
-
/// If a direction is not enabled, returns `UNLIMITED_AMOUNT` for that direction's available amount.
|
|
266
|
-
fn get_rate_limit_usages(env: &soroban_sdk::Env, id: u128) -> oft::rate_limiter::RateLimitUsages {
|
|
267
|
-
let (_state_id, state, config) = Self::__get_rate_limit_state_and_config(env, id);
|
|
268
|
-
|
|
269
|
-
let mut usages = Self::__get_rate_limit_usages(env, &config, &state);
|
|
270
|
-
let global = RateLimitStorage::global_config(env);
|
|
271
|
-
if global.is_globally_disabled {
|
|
272
|
-
usages.outbound_available_amount = UNLIMITED_AMOUNT;
|
|
273
|
-
usages.inbound_available_amount = UNLIMITED_AMOUNT;
|
|
274
|
-
} else {
|
|
275
|
-
if !config.outbound_enabled {
|
|
276
|
-
usages.outbound_available_amount = UNLIMITED_AMOUNT;
|
|
277
|
-
}
|
|
278
|
-
if !config.inbound_enabled {
|
|
279
|
-
usages.inbound_available_amount = UNLIMITED_AMOUNT;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
usages
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// =========================================================================
|
|
288
|
-
// Internal Trait — mirrors EVM RateLimiterBaseUpgradeable internals
|
|
289
|
-
// =========================================================================
|
|
290
|
-
|
|
291
|
-
/// Internal trait for rate limiter operations.
|
|
292
|
-
///
|
|
293
|
-
/// Provides the flow hooks (`__outflow` / `__inflow`) called by `OFTInternal::__debit` and
|
|
294
|
-
/// `OFTInternal::__credit`, as well as the core decay calculation and config resolution.
|
|
295
|
-
pub trait RateLimiterInternal {
|
|
296
|
-
// === OFT Hooks ===
|
|
297
|
-
|
|
298
|
-
/// Hook called from `__debit` — consumes outbound capacity and releases inbound (if net accounting).
|
|
299
|
-
fn __outflow(env: &Env, id: u128, from: &Address, amount: i128) {
|
|
300
|
-
Self::__apply_rate_limit(env, id, from, amount, true);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/// Hook called from `__credit` — consumes inbound capacity and releases outbound (if net accounting).
|
|
304
|
-
fn __inflow(env: &Env, id: u128, to: &Address, amount: i128) {
|
|
305
|
-
Self::__apply_rate_limit(env, id, to, amount, false);
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// === Core Logic ===
|
|
309
|
-
|
|
310
|
-
/// Applies rate limit for a single flow direction.
|
|
311
|
-
fn __apply_rate_limit(env: &Env, id: u128, user: &Address, amount: i128, is_outflow: bool) {
|
|
312
|
-
assert_with_error!(env, amount >= 0, RateLimitError::InvalidAmount);
|
|
313
|
-
|
|
314
|
-
if RateLimitStorage::global_config(env).is_globally_disabled {
|
|
315
|
-
return;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
let (state_id, mut state, config) = Self::__get_rate_limit_state_and_config(env, id);
|
|
319
|
-
|
|
320
|
-
let (forward_enabled, backward_enabled) = if is_outflow {
|
|
321
|
-
(config.outbound_enabled, config.inbound_enabled)
|
|
322
|
-
} else {
|
|
323
|
-
(config.inbound_enabled, config.outbound_enabled)
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
if (!forward_enabled && (!backward_enabled || !config.net_accounting_enabled))
|
|
327
|
-
|| (config.address_exemption_enabled && RateLimitStorage::has_address_exemptions(env, user))
|
|
328
|
-
{
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
let usages = Self::__get_rate_limit_usages(env, &config, &state);
|
|
333
|
-
|
|
334
|
-
let (mut forward_usage, forward_available, mut backward_usage) = if is_outflow {
|
|
335
|
-
(usages.outbound_usage, usages.outbound_available_amount, usages.inbound_usage)
|
|
336
|
-
} else {
|
|
337
|
-
(usages.inbound_usage, usages.inbound_available_amount, usages.outbound_usage)
|
|
338
|
-
};
|
|
339
|
-
|
|
340
|
-
if forward_enabled {
|
|
341
|
-
assert_with_error!(env, amount <= forward_available, RateLimitError::RateLimitExceeded);
|
|
342
|
-
forward_usage += amount;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
if backward_enabled && config.net_accounting_enabled {
|
|
346
|
-
backward_usage = (backward_usage - amount).max(0);
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
let (new_outbound_usage, new_inbound_usage) =
|
|
350
|
-
if is_outflow { (forward_usage, backward_usage) } else { (backward_usage, forward_usage) };
|
|
351
|
-
|
|
352
|
-
state.outbound_usage = new_outbound_usage;
|
|
353
|
-
state.inbound_usage = new_inbound_usage;
|
|
354
|
-
state.last_updated = env.ledger().timestamp();
|
|
355
|
-
|
|
356
|
-
RateLimitStorage::set_rate_limit_states(env, state_id, &state);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/// Resolves the state and config for a given Destination ID.
|
|
360
|
-
///
|
|
361
|
-
/// Returns `(state_id, state, config)` where:
|
|
362
|
-
/// - `state_id`: Effective storage key (may differ from `id` when `use_global_state` is true)
|
|
363
|
-
/// - `state`: Current state from storage (returned by value; caller should copy to a `mut` local)
|
|
364
|
-
/// - `config`: Resolved config (per-Destination ID override, or inherited from DEFAULT_ID)
|
|
365
|
-
///
|
|
366
|
-
/// Resolution rules:
|
|
367
|
-
/// - If `use_global_state`: all Destination IDs share DEFAULT_ID state and config
|
|
368
|
-
/// - If per-Destination ID config exists: use it
|
|
369
|
-
/// - Otherwise: inherit config from DEFAULT_ID (or default if DEFAULT_ID config absent)
|
|
370
|
-
fn __get_rate_limit_state_and_config(env: &Env, id: u128) -> (u128, RateLimitState, RateLimitConfig) {
|
|
371
|
-
let effective_id = if RateLimitStorage::global_config(env).use_global_state { DEFAULT_ID } else { id };
|
|
372
|
-
let state = RateLimitStorage::rate_limit_states(env, effective_id);
|
|
373
|
-
let config = Self::__effective_config(env, effective_id);
|
|
374
|
-
(effective_id, state, config)
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/// Resolves the effective config for a given Destination ID.
|
|
378
|
-
///
|
|
379
|
-
/// If a per-Destination ID config exists, uses it.
|
|
380
|
-
/// Otherwise falls back to DEFAULT_ID config, or `RateLimitConfig::default()` if absent.
|
|
381
|
-
fn __effective_config(env: &Env, id: u128) -> RateLimitConfig {
|
|
382
|
-
RateLimitStorage::rate_limit_configs(env, id)
|
|
383
|
-
.unwrap_or_else(|| RateLimitStorage::rate_limit_configs(env, DEFAULT_ID).unwrap_or_default())
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// === Decay Calculation ===
|
|
387
|
-
|
|
388
|
-
/// Calculates decayed outbound and inbound usages from the resolved config + state.
|
|
389
|
-
fn __get_rate_limit_usages(env: &Env, config: &RateLimitConfig, state: &RateLimitState) -> RateLimitUsages {
|
|
390
|
-
let (outbound_usage, outbound_available_amount) = Self::__get_rate_limit_usage(
|
|
391
|
-
env,
|
|
392
|
-
state.last_updated,
|
|
393
|
-
state.outbound_usage,
|
|
394
|
-
config.outbound_limit,
|
|
395
|
-
config.outbound_window,
|
|
396
|
-
);
|
|
397
|
-
let (inbound_usage, inbound_available_amount) = Self::__get_rate_limit_usage(
|
|
398
|
-
env,
|
|
399
|
-
state.last_updated,
|
|
400
|
-
state.inbound_usage,
|
|
401
|
-
config.inbound_limit,
|
|
402
|
-
config.inbound_window,
|
|
403
|
-
);
|
|
404
|
-
RateLimitUsages { outbound_usage, outbound_available_amount, inbound_usage, inbound_available_amount }
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
/// Calculates the decayed usage and available capacity for a single direction.
|
|
408
|
-
///
|
|
409
|
-
/// Decay formula: `decay = limit * time_elapsed / window` (pro-rata, saturating on overflow).
|
|
410
|
-
/// A window of 0 is treated as 1 to avoid division by zero.
|
|
411
|
-
fn __get_rate_limit_usage(
|
|
412
|
-
env: &Env,
|
|
413
|
-
last_updated: u64,
|
|
414
|
-
amount_in_flight: i128,
|
|
415
|
-
limit: i128,
|
|
416
|
-
window: u64,
|
|
417
|
-
) -> (i128, i128) {
|
|
418
|
-
let now = env.ledger().timestamp();
|
|
419
|
-
assert_with_error!(env, now >= last_updated, RateLimitError::LastUpdatedInFuture);
|
|
420
|
-
let time_since_last_update = (now - last_updated) as i128;
|
|
421
|
-
let effective_window = if window == 0 { 1i128 } else { window as i128 };
|
|
422
|
-
|
|
423
|
-
let decay = limit.saturating_mul(time_since_last_update) / effective_window;
|
|
424
|
-
let current_usage = (amount_in_flight - decay).max(0);
|
|
425
|
-
let available_amount = (limit - current_usage).max(0);
|
|
426
|
-
(current_usage, available_amount)
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// === Checkpoint ===
|
|
430
|
-
|
|
431
|
-
fn __checkpoint_rate_limit(env: &Env, id: u128) {
|
|
432
|
-
let (state_id, mut state, config) = Self::__get_rate_limit_state_and_config(env, id);
|
|
433
|
-
let usages = Self::__get_rate_limit_usages(env, &config, &state);
|
|
434
|
-
|
|
435
|
-
state.outbound_usage = usages.outbound_usage;
|
|
436
|
-
state.inbound_usage = usages.inbound_usage;
|
|
437
|
-
state.last_updated = env.ledger().timestamp();
|
|
438
|
-
|
|
439
|
-
RateLimitStorage::set_rate_limit_states(env, state_id, &state);
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// === Internal Setters ===
|
|
443
|
-
|
|
444
|
-
fn __set_rate_limit_global_config(env: &Env, global_config: &Option<RateLimitGlobalConfig>) {
|
|
445
|
-
RateLimitStorage::set_or_remove_global_config(env, global_config);
|
|
446
|
-
RateLimitGlobalConfigUpdated { global_config: global_config.clone() }.publish(env);
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
fn __set_rate_limit_config(env: &Env, id: u128, config: &Option<RateLimitConfig>) {
|
|
450
|
-
if let Some(cfg) = config {
|
|
451
|
-
assert_with_error!(env, cfg.outbound_limit >= 0, RateLimitError::OutboundLimitNegative);
|
|
452
|
-
assert_with_error!(env, cfg.inbound_limit >= 0, RateLimitError::InboundLimitNegative);
|
|
453
|
-
}
|
|
454
|
-
RateLimitStorage::set_or_remove_rate_limit_configs(env, id, config);
|
|
455
|
-
RateLimitConfigUpdated { id, config: config.clone() }.publish(env);
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
fn __set_rate_limit_state(env: &Env, id: u128, state: &RateLimitState) {
|
|
459
|
-
assert_with_error!(env, state.last_updated <= env.ledger().timestamp(), RateLimitError::LastUpdatedInFuture);
|
|
460
|
-
assert_with_error!(env, state.outbound_usage >= 0, RateLimitError::OutboundUsageNegative);
|
|
461
|
-
assert_with_error!(env, state.inbound_usage >= 0, RateLimitError::InboundUsageNegative);
|
|
462
|
-
|
|
463
|
-
RateLimitStorage::set_rate_limit_states(env, id, state);
|
|
464
|
-
RateLimitStateUpdated { id, state: state.clone() }.publish(env);
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
fn __set_rate_limit_exemption(env: &Env, user: &Address, is_exempt: bool) {
|
|
468
|
-
let current = RateLimitStorage::address_exemptions(env, user);
|
|
469
|
-
assert_with_error!(env, current != is_exempt, RateLimitError::ExemptionStateIdempotent);
|
|
470
|
-
|
|
471
|
-
if is_exempt {
|
|
472
|
-
RateLimitStorage::set_address_exemptions(env, user, &true);
|
|
473
|
-
} else {
|
|
474
|
-
RateLimitStorage::remove_address_exemptions(env, user);
|
|
475
|
-
}
|
|
476
|
-
RateLimitExemptionUpdated { user: user.clone(), is_exempt }.publish(env);
|
|
477
|
-
}
|
|
478
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
//! Mintable trait - the interface the OFT uses to mint tokens on credit (receive).
|
|
2
|
-
|
|
3
|
-
use soroban_sdk::{contractclient, Address, Env};
|
|
4
|
-
|
|
5
|
-
/// The mint interface for OFT MintBurn operations.
|
|
6
|
-
///
|
|
7
|
-
/// A contract that implements `mint` (e.g. SAC Manager or a token wrapper) is used
|
|
8
|
-
/// for crediting; the OFT calls the token (SAC) directly for burn on debit.
|
|
9
|
-
#[contractclient(name = "MintableClient")]
|
|
10
|
-
pub trait Mintable {
|
|
11
|
-
/// Mints `amount` tokens to `to`. The `operator` address is the caller (e.g. OFT)
|
|
12
|
-
/// requesting the mint, for use by SAC wrappers that enforce authorization.
|
|
13
|
-
fn mint(env: &Env, to: &Address, amount: i128, operator: &Address);
|
|
14
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#![no_std]
|
|
2
|
-
|
|
3
|
-
mod errors;
|
|
4
|
-
mod extensions;
|
|
5
|
-
mod interfaces;
|
|
6
|
-
mod oft_types;
|
|
7
|
-
|
|
8
|
-
pub use errors::*;
|
|
9
|
-
pub use extensions::*;
|
|
10
|
-
pub use interfaces::*;
|
|
11
|
-
pub use oft_types::*;
|
|
12
|
-
|
|
13
|
-
cfg_if::cfg_if! {
|
|
14
|
-
// Include implementation when NOT in library mode, OR when testutils is enabled (for tests)
|
|
15
|
-
if #[cfg(any(not(feature = "library"), feature = "testutils"))] {
|
|
16
|
-
mod oft;
|
|
17
|
-
pub use oft::*;
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
#[cfg(test)]
|
|
22
|
-
#[path = "../integration-tests/mod.rs"]
|
|
23
|
-
pub mod integration_tests;
|
|
24
|
-
|
|
25
|
-
#[cfg(test)]
|
|
26
|
-
mod tests;
|