feat(escrow): add versioned storage migration framework - #1
Closed
Gainer-dev wants to merge 230 commits into
Closed
Conversation
…tent storage (#654) LenderDepositLedger, BlockBorrowTotal, and BlockBorrowLedger were stored in instance storage, which shares a single TTL across the entire contract. When instance TTL expires, all entries are silently wiped — breaking the flash-loan guard and allowing same-block deposit/withdraw attacks. Changes: - Move LenderDepositLedger, BlockBorrowTotal, and BlockBorrowLedger from instance storage to persistent storage with per-key TTL bumping - Add LEDGER_GUARD_TTL (7 days), LEDGER_GUARD_TTL_THRESHOLD (500k ledgers), and LEDGER_GUARD_TTL_BUMP (1.2M ledgers / 70 days) constants - Call extend_ttl on every deposit, withdraw, and borrow for guard keys - Bump instance TTL on every borrow to protect liquidity snapshots - Add detailed TTL strategy documentation in DataKey comments - Add test_same_block_guard_survives_ttl_expiry test that advances the ledger 1.5M sequences past typical TTL boundaries and verifies both the same-block deposit/withdraw guard and per-block borrow cap still work - Add lending_pool to workspace members in root Cargo.toml Closes #654
Changes made
…spute → evidence → resolution → insurance claim flow
…ards #614 - Oracle TWAP manipulation resistance: - Add MAX_STALENESS_SECS (3600s) heartbeat filter; stale readings excluded - Add PricePoint.feeder field to track per-feeder submissions - Add count_distinct_feeders; get_price panics if active feeders < MIN_FEEDERS - Add outlier rejection: drop readings where |price - median| > median - Expose get_oracle_health() -> OracleHealth { active_feeders, last_update, is_stale } - treasury::buyback_and_burn now gates on oracle health (OracleUnhealthy / OracleStale) - Add OracleContractClient interface and OracleHealth mirror type in treasury - Tests: basic median, insufficient feeders, stale exclusion, outlier rejection, health struct, N-1 manipulation property, treasury oracle gate integration #619 - Monotonic version check in upgrade_contract: - Add Error::VersionNotMonotonic(7), TimelockNotElapsed(8), NoPendingUpgrade(9) - Add PendingUpgrade struct and DataKey::UpgradeDelay / PendingUpgrade storage keys - PATH A (recommended): schedule_upgrade + execute_pending_upgrade with full guards - PATH B (deprecated): upgrade_contract and register_upgrade both now enforce VersionNotMonotonic and TimelockNotElapsed - identical security guarantees - initialize() now accepts upgrade_delay param - Module-level RFC comment documents intended upgrade path - Tests: downgrade regression on both paths, timelock enforcement, happy-path two-step upgrade, execute-without-schedule, same-version rejection
- Add CreditScoreContractTrait / CreditScoreClient for the cross-contract get_score call in LendingPool::borrow. - Reject borrows below the configured minimum with Error::LowCreditScore. - Add DataKey::MinCreditScore (defaults to 600) plus admin-gated set_min_credit_score and get_min_credit_score. - Register lending_pool and credit_score as workspace members. - Add boundary tests (599 fail / 600 pass / 601 pass) with mock credit score + token, and register a passing mock in flash_loan_tests setup. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
#709 - RBAC: O(1) dedup via RoleMember key check, add RoleMemberCount + get_role_member_count #710 - Anomaly detector: configurable thresholds via AnomalyConfig, set_thresholds, get_thresholds #711 - Bounty: multi-tier milestones with Milestone struct, verify_milestone, PartiallyVerified status, proportional refund #712 - Session registry: paginated get_sessions_by_mentor_page/learner_page, indexed storage, deprecated old functions at 50 cap
Add protocol-wide solvency
Implement signature-based admin rotation
Resolved conflicts in: - Cargo.toml: kept upstream members list, removed duplicate entries - contracts/bounty/src/lib.rs: integrated milestone logic with upstream CEI + lock pattern - contracts/session_registry/src/lib.rs: merged both new DataKey variants Updated verify_milestone to follow same reentrancy-guard pattern as verify_completion
Implement contract upgrade impact analysis
…cryptographic proof of outcome
PR Title: feat: Implement DeFi incentives, Reputation Governance, High-Value Escrow, and ZK Proofs PR Description:
…dleware Closes #781 Closes #784 - #781: add Merkle root computation to dispute evidence (compute_evidence_root, get_evidence_root, verify_evidence_set), EvidenceRoot storage, evidence_root in resolution records, EvidenceRootUpdated event - #784: add Validator builder pattern in shared/validation.rs (require_positive, require_non_negative, require_future_timestamp, require_range, require_nonzero), ValidationError type, require_auth_and_validate helper, 10 unit tests Note: Issues #782 (mentor onboarding escrow) and #783 (storage rent fund) are large architectural features requiring new contracts — not included in this PR.
…ation Implements four independent feature issues: - DisputeStats aggregate (opens/resolutions/appeals/avg resolution time) - get_dispute_stats, get_mentor_dispute_rate view functions - MentorDisputeRateAlert event when a mentor's dispute rate exceeds 20% - dispute_evidence now notifies health_dashboard on dispute open/resolution (optional, backwards compatible) - TierRequirements (stake + min rating + min sessions per tier) - compute_tier cross-calls reputation/session_registry when configured, falling back to stake-only tiering when they aren't (backwards compatible with existing deployments/tests) - set_tier_requirements/get_tier_requirements for governance-adjustable thresholds - NftMetadata populated from session_registry/reputation on mint_bundle - metadata_hash = sha256(xdr(name, skill, mentor, learner, completed_at, rating)) as an on-chain tamper-evidence seal - get_token_metadata, verify_metadata_integrity - Metadata TTL scaled by sessions_count - shared::GasEstimate type - estimate_deploy_escrow_cost (escrow_factory), estimate_release_escrow_cost (escrow), estimate_governance_vote_cost (governance) — heuristic, view-only, calibrated to within 20% of measured env.budget() cost Also fixes several pre-existing compile-blocking issues in files this work touches (missing imports, a scrambled merge in governance's ExecuteCall tests/template-validation code, a missing session_registry DataKey variant, duplicate imports in escrow_factory/session_nft) so these crates build and test cleanly. Unrelated pre-existing failures elsewhere in the workspace (e.g. other contracts' own broken tests) were left alone as out of scope. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Track 30-minute mentor slots, reject overlaps (with 15-minute buffer), expose availability, and release slots on cancel. Fixes #786.
Epoch-bucket mentor revenue with prune-on-write, 30d queries, and paginated protocol totals. Fixes #749.
feat: harden cross-layer, incentive, and privacy primitives
#866 Cross-Chain State Synchronization Race Conditions - Add contracts/shared/src/cross_chain_sync.rs with full two-phase-commit atomic cross-chain protocol, Merkle state proof synchronization, finality-aware operations (Deterministic/Probabilistic/Optimistic/Instant), reorg protection (REORG_SAFE_DEPTH=64), inconsistency detection, and emergency chain isolation with cooling-off periods - Integrate into bridge_receiver/src/lib.rs: chain isolation checks on all receive paths, new begin/acknowledge/confirm/rollback_bridge_op methods, validate_bridge_state_proof, emergency_isolate_chain/lift methods - Integrate into oracle/src/lib.rs: oracle chain isolation controls, cross-chain price divergence detection triggering record_inconsistency #867 Social Engineering Attack Vectors - Add contracts/shared/src/transaction_guard.rs: TransactionIntent evaluation with multi-factor anomaly scoring, RiskLevel classification (Low/Medium/ High/Critical), cooling-off period enforcement, multi-signature confirmation requirements for critical operations, automatic account blocking at score threshold, suspicious pattern recording - Integrate into multisig_admin: evaluate_action_risk, is_account_blocked - Integrate into governance: evaluate_vote_risk, is_voter_flagged, clear_voter_flag with account protection state tracking #868 Advanced Cryptographic Key Management - Add contracts/shared/src/key_management.rs: hierarchical deterministic key derivation (SHA-256 chaining, up to depth 10), algorithm-agility KeyScheme enum (Ed25519/Secp256k1/PostQuantumLattice/PostQuantumHash), automatic key rotation with 7-day overlap window, threshold k-of-N key shares (max 7), emergency revocation with REVOCATION_COOLDOWN_SECS, social recovery with guardian quorum (min 3 of max 7), forward secrecy enabled on rotation - Integrate into multisig_admin: register_signer_key, propose/execute_signer_ key_rotation, emergency_revoke_signer_key, is_key_rotation_due, get_signer_key #869 Consensus-Layer Attack Resistance - Add contracts/shared/src/validator_accountability.rs: graduated slashing (SLASH_MINOR/MAJOR/CRITICAL_BPS), reputation scoring (0-10000 bps), ViolationType enum (MissedEpoch/Equivocation/Censorship/ConsensusAttack/ StakeConcentration), automatic ejection on critical violations, incentive alignment assessment, consensus attack detection with network anomaly scoring, emergency consensus with alternative validator selection, readmission after EJECTION_COOLDOWN_SECS (30 days) - Integrate into oracle/src/lib.rs: feeders registered as validators, epoch participation tracking on price submission, circuit-breaker trip counting with feeder flagging, slash_feeder and detect_oracle_consensus_attack admin functions - Integrate into governance/src/lib.rs: register_governance_validator, assess_validator_alignment, governance emergency activation/deactivation, is_governance_validator_ejected All new modules include comprehensive unit test suites (35 tests total).
added governance
…and exit lock-in (#905, #912, #913, #932) - #905: Implement session uniqueness validation, cryptographic content integrity checksums, and temporal replay detection across shared, session_registry, and certificates contracts. - #912: Implement fair multi-factor mentor visibility calculations, algorithm recommendation scoring, and manipulation-resistant matching across shared, session_registry, and reputation contracts. - #913: Implement cross-platform credential verification, external reputation importing with isolated scoring, and identity consistency bridging across shared, verification, and reputation contracts. - #932: Implement platform migration facilitation, full user data portability exports, and learner mobility protections across shared and session_registry contracts.
…ity docs BatchOp::Transfer/Invoke were tuple variants, but every caller (treasury's allocate and schedule_staker_distribution paths) matched on them with struct-pattern syntax — a hard compile error. Convert them to struct variants so the existing call sites are actually valid. Also documents the real atomicity model: Soroban reverts all storage writes and transfers for an invocation the moment its top-level Result is Err, so AtomicBatch's job is ordering + fail-fast + a bounded, validated op count, not manual undo. Adds AtomicBatch::validate() (rejects an empty or oversized batch before any operation runs) and MAX_BATCH_SIZE, and wires validate() into both treasury call sites.
Implements the batch primitive named in #830 for treasury: every request in the batch is validated (token approval, amount sanity, per-tx cap, and a per-token balance check against the summed batch total) before any transfer runs. Execution then goes through AtomicBatch, which aborts at the first failure; since batch_allocate propagates that failure as Err, Soroban reverts every transfer already made in the call, giving true all-or-nothing semantics. Each successful request gets an AllocationHistory entry and an operation-log entry (existing audit-trail infrastructure), plus a summary event for the batch as a whole. Also drops MIN_STAKING_DURATION_SECS, REWARD_LOCKUP_SECS, BASIS_POINTS, and SuspiciousPatternFlag from this file's imports — grep confirms none of them were ever referenced (#987).
Adds the pagination module named in #831: Pagination::bounds() resolves an (offset, limit) request into a clamped [start, end) range (limit capped to MAX_PAGE_SIZE=100, end capped to the collection's real size), a BoundedIteration trait for documenting a collection's page cap, and OperationBudget as an explicit operation-count ceiling. OperationBudget exists in place of true runtime gas monitoring: Soroban's guest environment has no API for a contract to introspect its own remaining CPU/memory budget (Env::cost_estimate() is testutils-only), so "suspend at 80% of the block gas limit" isn't implementable from inside a contract. An explicit per-call operation cap is the practical substitute.
…831) Previously scanned every escrow id from 1..=EscrowCount on every call with no upper bound, so the cost of a single view call grew linearly with total escrows ever created — a caller (or the growth of the escrow count itself) could push this past the block gas limit. Adds offset/limit params resolved through the new shared Pagination helper, capping the scan window to MAX_PAGE_SIZE (100) ids per call regardless of what limit is requested.
…evenue_batch (#831) get_stakers previously iterated every staker on every call (its doc comment claimed "Paginated" but the implementation wasn't) — now takes offset/limit resolved through the shared Pagination helper. distribute_revenue_batch capped `end` to `count` but never capped `limit` itself, so a caller passing a large limit still forced a full scan once the staker set grew large enough; `.min(count)` alone degenerates to "process everyone" rather than bounding per-call work. Now resolves offset/limit through Pagination so `limit` itself is capped. internal_distribute_revenue's epoch snapshot must capture every staker atomically in one pass for its anti-dilution guarantee to hold, so it can't simply be paginated across calls without weakening that guarantee. Added a MAX_STAKERS_PER_SNAPSHOT guard that panics past a safe staker count instead of risking the block gas limit mid-snapshot, and points callers at the (dilution-tolerant but genuinely paginated) distribute_revenue_batch as the alternative execution path for that scale.
…overy imports EMERGENCY_THRESHOLD, calculate_backoff_delay, classify_failure, compute_failure_hash, PreConditionCheck, PostConditionCheck, StateTransitionProof, compute_transition_proof_hash, and all_checkpoints_passed were imported from shared but never referenced again in this file (verified by grep, excluding StateMachine and SafeMath which are trait imports used only via associated-function/method syntax and therefore legitimately have no other textual occurrence) (#987).
Maps all 35 shared protection/utility modules to the contracts that actually import from them (grep-verified), documents the events module's qualified-path-only usage pattern that a flat-symbol match misses, flags governance_voting/interface_id/ttl_utils as declared but never imported anywhere in the workspace, and lists the unused-import findings from this pass plus unverified candidates for a future pass — with a methodology note on why a trait import (StateMachine, SafeMath) can look unused by a naive occurrence count and not be.
Adds log_contract_error(env, operation, reason, subject_id, caller), which publishes a ContractErrorContext diagnostic event before a contract panics or returns a typed Error. This is purely additive: existing error codes / panic messages are untouched (error code compatibility is preserved), but a failure is now observable off-chain with the operation name, a short reason code, the record it was acting on, and the caller — context a bare "Not initialized" panic or an Error::Unauthorized variant alone can't carry.
… checks (#988) Adds _require_admin_for_escrow(env, caller, escrow_id, operation), which replaces the repeated "let stored_admin = ...expect(\"Not initialized\"); ... panic!(\"Unauthorized\")" block (identical, un-attributable message across every admin-gated function) with a message naming the operation and escrow id, plus a log_contract_error call so the failure is observable off-chain with full context even when the panic message alone isn't enough. Wired into record_evidence_count, resolve_dispute_secure, emergency_isolate_escrow, and recover_isolated_escrow. release_funds's "Caller not authorized" panic (its highest-traffic authorization check) gets the same treatment inline. This establishes the pattern rather than covering every panic site in the file — escrow alone has 20+ "Not initialized"-shaped panics across ~5000 lines; a full sweep is future work.
…ity-protections-errors DoS pagination, batch atomicity, protection mapping, and error context
…ation feat:implement Gas Estimation Accuracy Verification
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR introduces a versioned storage and migration framework for
EscrowRecordto ensure existing escrow records remain readable and safe across future schema changes.Without versioning, adding fields to the large
EscrowRecordstruct can break positional Soroban XDR deserialization of records already stored on-chain. This implementation provides a safe migration path for schema evolution.Changes
EscrowRecordV1representing the current escrow storage schema.VersionedEscrowwrapper with:version: u32data: Bytesmigrate_escrow(env, escrow_id)for explicit/admin-triggered eager migration.get_escrow_schema_version(env, escrow_id) -> u32as a view function.Testing
migrate_escrowupgrades V1 records to V2 without data loss.Migration Safety
Existing escrow records are never assumed to match the latest schema directly. The stored version is detected before deserialization, allowing older records to be migrated safely without losing existing escrow data.
Related Issue
closes MentorsMind#559
closes MentorsMind#567
closes MentorsMind#621
closes MentorsMind#609