Skip to content

Commit 55c3397

Browse files
authored
Merge pull request #60 from MooreTheAnalyst/feat/implement-time-decay-37
feat: implement time-weighted exponential decay for aggregate scores
2 parents 2650ed3 + 77910f8 commit 55c3397

8 files changed

Lines changed: 824 additions & 36 deletions

File tree

contracts/ledgerlens-score/src/constants.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ pub const DEFAULT_RISK_THRESHOLD: u32 = 75;
1717

1818
/// Semantic contract version; bump on breaking ABI changes.
1919
///
20+
/// Bumped to 2 when `submit_score` gained its `attestation` parameter (see
21+
/// `docs/attestation-spec.md`).
22+
/// Bumped to 3 when `AggregateRiskScore` gained `decay_lambda_applied` field.
2023
/// Bumped to 3 when all admin-tier functions gained `admin_signers: Vec<Address>`
2124
/// for M-of-N governance and the `AdminSet` / `AdminThreshold` storage keys
2225
/// were introduced.
@@ -86,3 +89,24 @@ pub const DEFAULT_STALENESS_WINDOW_SECS: u64 = 604_800;
8689
/// the rare admin pause/unpause path; the hot `is_pair_paused` read used by
8790
/// every submission never touches the index. See `set_pair_paused`.
8891
pub const MAX_PAUSED_PAIRS: u32 = 50;
92+
93+
// ── Time-weighted exponential decay ───────────────────────────────────────────
94+
95+
/// Fixed-point scale factor used in decay computations (1_000_000 = 6 decimal
96+
/// places of precision). Decay factors are computed as fixed-point integers
97+
/// in the range [0, DECAY_FIXED_POINT_SCALE].
98+
pub const DECAY_FIXED_POINT_SCALE: u64 = 1_000_000;
99+
100+
/// Default decay rate numerator — 0 means no decay until configured.
101+
pub const DEFAULT_DECAY_LAMBDA_NUM: u32 = 0;
102+
103+
/// Default decay rate denominator — 1 avoids division-by-zero in the default.
104+
pub const DEFAULT_DECAY_LAMBDA_DEN: u32 = 1;
105+
106+
/// Maximum allowed decay rate numerator. Caps λ at 1/1 (full decay per
107+
/// unit time), preventing scores from being instantly zeroed by a
108+
/// misconfigured rate.
109+
pub const MAX_DECAY_LAMBDA_NUM: u32 = 1;
110+
111+
/// Maximum allowed decay rate denominator (paired with MAX_DECAY_LAMBDA_NUM).
112+
pub const MAX_DECAY_LAMBDA_DEN: u32 = 1;

contracts/ledgerlens-score/src/errors.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,38 @@ pub enum Error {
9292
/// Returned by `withdraw_fees` when another withdrawal call is already
9393
/// in-flight (concurrency lock held).
9494
WithdrawalInProgress = 32,
95+
96+
// ── Per-pair circuit breaker ───────────────────────────────────────────
97+
/// Returned by `submit_score` / `submit_scores_batch` when the target
98+
/// `asset_pair` has been individually paused via `set_pair_paused`.
99+
PairPaused = 33,
100+
/// Returned by `set_pair_paused` when trying to pause a new pair but the
101+
/// `PausedPairIndex` already holds `MAX_PAUSED_PAIRS` entries.
102+
PausedPairIndexFull = 34,
103+
104+
// ── Admin M-of-N multi-sig ─────────────────────────────────────────────
105+
/// `add_admin_signer` called when the admin set is already at
106+
/// `MAX_ADMIN_SIGNERS`.
107+
AdminSetFull = 35,
108+
/// A signer in `admin_signers` is not a member of the admin set.
109+
AdminSignerNotInSet = 36,
110+
/// Fewer than the configured threshold of admin signers were supplied.
111+
InsufficientAdminSigners = 37,
112+
113+
// ── Wallet-score delegation ────────────────────────────────────────────
114+
/// `set_score_delegate` would create a cycle (wallet → custodian →
115+
/// wallet).
116+
CyclicDelegation = 38,
117+
/// `remove_score_delegate` called for a wallet that has no delegate.
118+
DelegateNotFound = 39,
119+
120+
// ── Cross-contract gate ────────────────────────────────────────────────
121+
/// Returned by an integrating contract (e.g. AMM) when `query_risk_gate`
122+
/// returns `false`. Not returned by the LedgerLens contract itself.
123+
HighRiskWallet = 40,
124+
125+
// ── Time-weighted exponential decay ───────────────────────────────────
126+
/// `set_decay_rate` called with a denominator of 0, or with a
127+
/// numerator/denominator ratio exceeding `MAX_DECAY_LAMBDA`.
128+
InvalidDecayRate = 41,
95129
}

contracts/ledgerlens-score/src/events.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,13 @@ pub fn history_depth_updated(env: &Env, depth: u32) {
158158
env.events().publish((symbol_short!("hd_upd"),), depth);
159159
}
160160

161+
// ── Time-weighted exponential decay ────────────────────────────────────────
162+
163+
/// Emitted when the admin sets the exponential decay rate via `set_decay_rate`.
164+
pub fn decay_rate_updated(env: &Env, numerator: u32, denominator: u32) {
165+
env.events().publish((symbol_short!("decay_upd"),), (numerator, denominator));
166+
}
167+
161168
// ── Fee withdrawal ────────────────────────────────────────────────────────────
162169

163170
/// Emitted when the admin configures or rotates the fee token via
@@ -174,12 +181,26 @@ pub fn fee_withdrawn(
174181
fee_token: &Address,
175182
amount: i128,
176183
) {
177-
env.events()
178-
.publish((symbol_short!("fee_out"),), (admin.clone(), recipient.clone(), fee_token.clone(), amount));
184+
env.events().publish(
185+
(symbol_short!("fee_out"),),
186+
(admin.clone(), recipient.clone(), fee_token.clone(), amount),
187+
);
179188
}
180189

181190
/// Emitted when `withdraw_fees` is rejected because the concurrency lock is
182191
/// already held by an in-flight call.
183192
pub fn withdrawal_locked(env: &Env, admin: &Address) {
184193
env.events().publish((symbol_short!("wdl_lck"),), admin.clone());
185194
}
195+
196+
// ── Wallet-score delegation ───────────────────────────────────────────────────
197+
198+
/// Emitted when `set_score_delegate` registers or updates a delegation.
199+
pub fn delegate_set(env: &Env, sub_wallet: &Address, custodian: &Address) {
200+
env.events().publish((symbol_short!("dlg_set"),), (sub_wallet.clone(), custodian.clone()));
201+
}
202+
203+
/// Emitted when `remove_score_delegate` removes a delegation.
204+
pub fn delegate_removed(env: &Env, sub_wallet: &Address) {
205+
env.events().publish((symbol_short!("dlg_rem"),), sub_wallet.clone());
206+
}

0 commit comments

Comments
 (0)