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
Runtime capability detection for the composability interface. Returns `true` for the registered capabilities `score`, `history`, `batch`, `gate`, and `aggr`, letting integrators feature-detect instead of hardcoding contract version numbers.
90
90
91
+
### `propose_upgrade(new_wasm_hash: BytesN<32>)`
92
+
Admin only. Starts a time-locked contract upgrade by committing to `new_wasm_hash`. Stores an `UpgradeProposal` with `executable_after = now + get_upgrade_delay()` and emits `upgrade_proposed`. Does not change the code. Rejected with `UpgradeAlreadyPending` if a proposal is already in flight. See [Upgrade Governance](#upgrade-governance).
93
+
94
+
### `execute_upgrade()`
95
+
Admin only. After the time-lock elapses, re-verifies `now >= executable_after` and installs the new WASM via `env.deployer().update_current_contract_wasm(...)`, clears the proposal, and emits `upgrade_executed`. Returns `UpgradeNotReady` before the delay or `NoPendingUpgrade` if none exists.
96
+
97
+
### `veto_upgrade()`
98
+
Admin only. Cancels the pending proposal during the time-lock window (emergency escape hatch for a malicious proposal or compromised key) and emits `upgrade_vetoed`.
99
+
100
+
### `get_pending_upgrade() -> UpgradeProposal`
101
+
Permissionless. Returns the in-flight proposal so anyone can audit it during the window. Returns `NoPendingUpgrade` if none.
Admin sets the time-lock delay applied to future proposals, bounded to `[MIN_UPGRADE_DELAY_SECS, MAX_UPGRADE_DELAY_SECS]` (48 hours – 14 days); out-of-range values are rejected with `InvalidUpgradeDelay`. Defaults to 48 hours.
105
+
91
106
### `RiskScore` Structure
92
107
93
108
```rust
@@ -150,6 +165,41 @@ A wallet scoring 60-70 on three pairs individually might not breach the per-pair
150
165
151
166
`get_aggregate_score` iterates the wallet's full pair list, so its cost is O(N) in the number of distinct pairs the wallet has scores for. The contract is designed around a practical maximum of `MAX_WALLET_PAIRS` (20) pairs per wallet; this is documented as a constant but not enforced on-chain.
152
167
168
+
## Upgrade Governance
169
+
170
+
Soroban contracts can be upgraded by the admin via `update_current_contract_wasm`, which replaces the **entire** contract logic in a single transaction. Without governance, one admin key — or a compromised one — could silently install a backdoor or disable a security check with no warning. LedgerLens gates every upgrade behind an on-chain **time-lock** so the community always gets a mandatory window to inspect and react.
171
+
172
+
**The flow:**
173
+
174
+
1. The admin **proposes** an upgrade, committing to a new WASM hash.
175
+
2. A mandatory delay passes (**minimum 48 hours**, configurable up to 14 days). During this window anyone can call `get_pending_upgrade` to inspect the committed hash and alert the community.
176
+
3. Only after the delay can the admin **execute** the upgrade. Alternatively, the admin can **veto** it at any time during the window (e.g. if the key was compromised).
177
+
178
+
```
179
+
admin contract community
180
+
│ │ │
181
+
│ propose_upgrade(hash) │ │
182
+
├─────────────────────────────►│ store UpgradeProposal │
183
+
│ │ emit upgrade_proposed ────────►│ inspect via
184
+
│ │ executable_after = now + delay │ get_pending_upgrade
The time-lock is computed from `env.ledger().timestamp()` (deterministic, not caller-settable) and re-verified at execution time — never cached. The configurable delay is bounded to `[MIN_UPGRADE_DELAY_SECS, MAX_UPGRADE_DELAY_SECS]`; **raising** it is always safe, while **lowering** it shortens the veto window and should require community consensus. See [`SECURITY.md`](SECURITY.md#upgrade-governance--threat-model) for the full threat model and monitoring guidance.
202
+
153
203
## Composability
154
204
155
205
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.
@@ -198,6 +248,7 @@ A complete, compiling reference contract lives in [`examples/amm_gate.rs`](examp
198
248
2.**Read-Only Composability**: `get_score` is permissionless and side-effect free, safe for any contract to call
199
249
3.**Bounded Values**: Scores and confidence are constrained to the 0-100 range
200
250
4.**Overflow Protection**: Safe math operations with overflow checks
251
+
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)
in **one transaction, with no warning**. To remove that single point of
66
+
instant failure, upgrades are gated behind an on-chain time-lock.
67
+
68
+
### The flow
69
+
70
+
1.**Propose** — the admin calls `propose_upgrade(new_wasm_hash)`. This stores
71
+
an `UpgradeProposal` (committed hash, `proposed_at`, `executable_after`,
72
+
`proposed_by`) and emits `upgrade_proposed`. It does **not** change the code.
73
+
2.**Monitoring window** — for at least `MIN_UPGRADE_DELAY_SECS` (48 hours;
74
+
configurable up to 14 days) nothing can execute. Anyone — users, monitoring
75
+
bots, integrating protocols — can call `get_pending_upgrade` to read the
76
+
committed hash and `executable_after`, diff the proposed WASM, and alert the
77
+
community.
78
+
3.**Execute or veto** — only after `executable_after` can the admin call
79
+
`execute_upgrade`, which re-checks the clock at execution time (never a
80
+
cached decision) before installing the WASM. At any point during the window
81
+
the admin can `veto_upgrade` to cancel — the escape hatch if a proposal is
82
+
malicious or the key was compromised. The veto emits `upgrade_vetoed` naming
83
+
the caller, completing the audit trail.
84
+
85
+
### Threat model
86
+
87
+
| Concern | Mitigation |
88
+
|---------|------------|
89
+
| Admin pushes a backdoor instantly | No instant path exists — every upgrade waits out the full delay before `execute_upgrade` will run |
90
+
| Compromised **service** key triggers an upgrade | Service keys have no upgrade powers; only the current admin can propose/execute/veto |
91
+
| Caller manipulates the time-lock | Deadlines derive from `env.ledger().timestamp()`, which is deterministic and not caller-settable |
92
+
| Stale/early execution |`execute_upgrade` re-verifies `now >= executable_after` on every call |
93
+
| Admin shortens the window to rush an upgrade |`set_upgrade_delay` is bounded to `[MIN, MAX]`; it can never go below 48 h, and a lowered delay only applies to *future* proposals — an in-flight proposal keeps its original `executable_after`|
94
+
| No record of who acted |`UpgradeProposal.proposed_by` plus the `upgrade_*` events give a full on-chain audit trail |
95
+
96
+
**Safe vs. sensitive delay changes:***raising*`MIN`-bounded delay is always
97
+
safe (it only lengthens scrutiny). *Lowering* the configured delay shortens the
98
+
community veto window and should only be done with broad community consensus.
99
+
100
+
### What monitors should watch
101
+
102
+
Subscribe to the `upgrade_proposed` event (or poll `get_pending_upgrade`). On a
103
+
new proposal, verify the committed `new_wasm_hash` against a reviewed,
104
+
reproducible build before `executable_after`. An unexpected proposal — or one
105
+
whose hash does not match a published, audited build — is the signal to raise
106
+
an alarm and, if warranted, push for a `veto_upgrade`.
0 commit comments