You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Called by the authorised LedgerLens off-chain service to register a computed risk score on-chain. Requires authorization from the configured LedgerLens service account. `score` and `confidence` must be in the range 0-100.
Called by the authorised LedgerLens off-chain service to register a computed risk score on-chain. Requires authorization from the configured LedgerLens service account (or, under the M-of-N multisig model, from `threshold` of the listed `signers`). `score` and `confidence` must be in the range 0-100. `attestation` is required once `set_service_pubkey` has been configured — see [Score Attestation](#score-attestation).
Read-only function callable by any account or contract. Returns the total number of score submissions ever recorded for `wallet` / `asset_pair`. Unlike `get_score_history` (which caps at `HISTORY_MAX_DEPTH`), this counter is never truncated, giving off-chain services a cheap O(1) signal to distinguish newly monitored wallets from those with a long history.
69
+
70
+
### `set_history_max_depth(depth: u32)`
71
+
Admin-only. Sets the maximum number of entries retained in the per-wallet / per-asset-pair score history ring buffer. `depth` must be in the range `[1, 50]`; values outside this range are rejected with `InvalidHistoryDepth`. Defaults to `10` until configured.
72
+
73
+
**Lazy-truncation behaviour:** reducing the depth does not remove existing entries immediately. Entries beyond the new cap remain in the ring until the next `submit_score` (or `submit_scores_batch`) call for that pair triggers the eviction loop, at which point the ring is trimmed in a single pass. Off-chain consumers reading `get_score_history` between the depth change and the next submission may temporarily observe more entries than the new cap.
74
+
75
+
### `get_history_max_depth() -> u32`
76
+
Read-only. Returns the current ring-buffer depth. Defaults to `10` until the admin sets one explicitly.
77
+
67
78
### `set_service(new_service: Address)`
68
79
Rotates the authorised off-chain scoring service address. Admin only.
69
80
@@ -82,6 +93,14 @@ Sets the weight used for `asset_pair` in the aggregate risk computation. Default
82
93
### `get_pair_weight(asset_pair: Symbol) -> u32`
83
94
Read-only lookup of the configured weight for `asset_pair`.
Called by the authorised LedgerLens off-chain service to register multiple risk scores in a single invocation. The service account authorises once for the whole batch.
99
+
100
+
Returns a `BatchResult` containing per-entry outcomes so the caller knows exactly which entries succeeded and why any failed. Entries with out-of-range `score` (>100) or `confidence` (>100), zero `timestamp`, or that arrive before the submission cooldown has elapsed, are recorded as rejected with an appropriate `rejection_code`.
101
+
102
+
**ABI change in contract version 2:** The return type changed from `u32` (count of accepted entries) to the structured `BatchResult`. Callers built against the old ABI must regenerate their client bindings.
The cross-contract integration primitive. Returns `true` when the wallet's score is **strictly below**`gate_threshold` (safe to proceed), and `false` when the score is `>= gate_threshold`**or no score exists**. It is **infallible** (returns `bool`, never an error), **never panics**, and is **side-effect free** — designed to be called directly from inside another protocol's guard clause. See [Composability](#composability) and [`docs/interface-spec.md`](docs/interface-spec.md).
Read-only lookup of the ledger timestamp of the last accepted submission for `(wallet, asset_pair)`, or `0` if none has ever been accepted (or it was cleared by `override_rate_limit`).
Admin only. Permanently erases the score history ring buffer for `wallet` / `asset_pair`. No-op if no history exists. Emits `clr_hist` for the on-chain audit trail. **Keep off-chain backups before calling — this cannot be undone on-chain.**
Admin only. Permanently erases the latest score entry for `wallet` / `asset_pair`. After this call, `get_score` returns `ScoreNotFound`. No-op if no score exists. Emits `clr_scr` for the on-chain audit trail. **Keep off-chain backups before calling — this cannot be undone on-chain.**
Admin sets (or rotates) the off-chain detection pipeline's secp256k1 public key — 33 bytes compressed or 65 bytes uncompressed, rejected otherwise with `InvalidPubkeyLength` — used to verify `ScoreAttestation`s. Once set it cannot be unset, only rotated. `get_service_pubkey` returns `ServicePubkeyNotSet` before one has been configured. See [Score Attestation](#score-attestation).
### `BatchResult` and `BatchEntryResult` Structures
172
+
173
+
`submit_scores_batch` returns a `BatchResult` that the off-chain API service can inspect to learn which entries succeeded and which were rejected:
174
+
175
+
```rust
176
+
pubstructBatchEntryResult {
177
+
pubindex:u32, // zero-based position in the submitted batch
178
+
pubaccepted:bool, // true if written to storage
179
+
pubrejection_code:u32, // 0 if accepted; Error discriminant if rejected
180
+
}
181
+
182
+
pubstructBatchResult {
183
+
pubaccepted_count:u32, // number of entries written to storage
184
+
pubrejected_count:u32, // number of entries rejected
185
+
pubresults:Vec<BatchEntryResult>, // per-entry outcomes, same order as input
186
+
}
187
+
```
188
+
189
+
Possible `rejection_code` values (from the `Error` enum):
190
+
191
+
| Code | Meaning |
192
+
|-----:|---------|
193
+
| 4 |`InvalidScore` — score > 100 |
194
+
| 5 |`InvalidConfidence` — confidence > 100 |
195
+
| 23 |`RateLimitExceeded` — submission cooldown not yet elapsed |
196
+
| 25 |`InvalidTimestamp` — timestamp == 0 |
197
+
144
198
The weighted average is:
145
199
146
200
```
@@ -253,6 +307,19 @@ The cooldown defaults to **1 hour** and is admin-configurable via `set_cooldown`
253
307
254
308
Like the upgrade time-lock, the cooldown deadline is computed from `env.ledger().timestamp()` — deterministic and not caller-settable — so it cannot be bypassed by manipulating submission metadata such as the `timestamp` field on `RiskScore` itself.
255
309
310
+
## Score Attestation
311
+
312
+
The service account's `require_auth` proves a transaction was sent by the authorised key, but says nothing about whether the score payload inside that transaction matches what the off-chain detection pipeline actually computed — relevant when the service key is held by infrastructure (a relayer, a batching service, a multisig signer) that's trusted to submit transactions but shouldn't be able to silently alter scores in transit.
313
+
314
+
`submit_score`'s optional `attestation: Option<ScoreAttestation>` closes that gap with a secp256k1 signature over the exact payload:
315
+
316
+
1. The admin registers the off-chain pipeline's public key via `set_service_pubkey`. Until this is called, `attestation` is ignored entirely and every existing integration keeps working unchanged.
317
+
2. Once a pubkey is configured, every `submit_score` call must carry a valid `ScoreAttestation` — a missing or invalid one is rejected with `InvalidAttestation`. There is no way to turn this back off short of a contract upgrade.
318
+
3. On each call, the contract independently recomputes the SHA-256 commitment over the wallet, asset pair, score fields, this contract's address, and the network id (binding the signature to one deployment on one network), and rejects the call if it disagrees with the attestation's `commitment` field — that field is never trusted as input, only checked.
319
+
4. The signature is then verified via `secp256k1_recover` against the registered pubkey, supporting both compressed and uncompressed key formats.
320
+
321
+
The full byte layout and verification algorithm are specified in [`docs/attestation-spec.md`](docs/attestation-spec.md).
322
+
256
323
## Composability
257
324
258
325
LedgerLens is only useful if other protocols can actually *act* on its scores. A risk score that lives in isolation is a dashboard widget; a risk score that an AMM, a lending market, or a DEX aggregator can read mid-transaction is a shared fraud-prevention layer for the entire Stellar DeFi ecosystem.
@@ -303,6 +370,7 @@ A complete, compiling reference contract lives in [`examples/amm_gate.rs`](examp
303
370
4.**Overflow Protection**: Safe math operations with overflow checks
304
371
5.**Time-Locked Upgrades**: Contract WASM upgrades require a mandatory delay (≥48 h) with a public proposal anyone can inspect and an admin veto — see [Upgrade Governance](#upgrade-governance)
305
372
6.**Submission Rate Limiting**: A configurable per-`(wallet, asset_pair)` cooldown (default 1 h) bounds how often the service account can overwrite a score — see [Rate Limiting](#rate-limiting)
373
+
7.**Score Attestation**: An opt-in secp256k1 signature over the score payload lets the off-chain pipeline vouch for its contents independent of `require_auth` — see [Score Attestation](#score-attestation)
|`submit_score(wallet, asset_pair, score, benford_flag, ml_flag, timestamp, confidence)`| LedgerLens service account |`service.require_auth()`|**`api`** — writes scores produced by `core`|
465
533
|`get_score(wallet, asset_pair)`| anyone | none (read-only) |**`api`**, **`dashboard`** (via api), and any third-party Soroban contract that wants to gate on LedgerLens risk |
Copy file name to clipboardExpand all lines: SECURITY.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -56,6 +56,7 @@ We follow [Responsible Disclosure](https://en.wikipedia.org/wiki/Coordinated_vul
56
56
| Large batch denial of service | Batch size capped at `MAX_BATCH_SIZE` (20) per invocation |
57
57
| Compromised service floods a pair with submissions | Per-`(wallet, asset_pair)` cooldown (`RateLimitExceeded`); admin-bounded `[MIN_COOLDOWN_SECS, MAX_COOLDOWN_SECS]`, with `override_rate_limit` as an audited emergency escape hatch |
58
58
| Silent malicious contract upgrade | Time-locked upgrade governance (see below): mandatory delay + on-chain proposal anyone can inspect, plus admin veto |
59
+
| Data-residency / GDPR erasure request |`clear_score_history` and `clear_score` (admin-only) permanently remove scoring data from persistent storage; `clr_hist` / `clr_scr` events provide an on-chain audit trail of every erasure |
0 commit comments