Skip to content

feat(escrow): add versioned storage migration framework - #1

Closed
Gainer-dev wants to merge 230 commits into
Gainer-dev:mainfrom
MentorsMind:main
Closed

feat(escrow): add versioned storage migration framework#1
Gainer-dev wants to merge 230 commits into
Gainer-dev:mainfrom
MentorsMind:main

Conversation

@Gainer-dev

@Gainer-dev Gainer-dev commented Aug 29, 2026

Copy link
Copy Markdown
Owner

Summary

This PR introduces a versioned storage and migration framework for EscrowRecord to ensure existing escrow records remain readable and safe across future schema changes.

Without versioning, adding fields to the large EscrowRecord struct can break positional Soroban XDR deserialization of records already stored on-chain. This implementation provides a safe migration path for schema evolution.

Changes

  • Introduced EscrowRecordV1 representing the current escrow storage schema.
  • Added the versioned VersionedEscrow wrapper with:
    • version: u32
    • data: Bytes
  • Implemented version-aware escrow deserialization.
  • Added lazy migration on escrow reads:
    • Detects the stored schema version.
    • Deserializes using the appropriate schema.
    • Upgrades older records to the current schema.
    • Persists the migrated record back to storage.
  • Added migrate_escrow(env, escrow_id) for explicit/admin-triggered eager migration.
  • Added get_escrow_schema_version(env, escrow_id) -> u32 as a view function.
  • Added safe defaults for fields introduced in newer schema versions.
  • Added migration runbook comments documenting the upgrade process and expected behavior.
  • Added tests covering schema versioning, lazy migration, eager migration, and data preservation.
  • Added fuzz coverage to verify V1 serialization/deserialization invariants when migrating to V2.

Testing

  • Verified V1 escrow records remain readable after deploying the V2 schema.
  • Verified newly introduced V2 fields are populated with safe defaults during migration.
  • Verified migrate_escrow upgrades V1 records to V2 without data loss.
  • Verified schema version reporting.
  • Added fuzz tests to validate migration invariants and serialized record compatibility.

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

sulaimonifeoluwa4-blip and others added 30 commits July 23, 2026 15:35
…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
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.
- contracts/onboarding_escrow: OnboardingStatus tracking, step completion,
  extended first-escrow delay, 30-day refund deadline
- contracts/rent_fund: deposit_rent, check_rent_health, auto_topup,
  RentLow alert at 3-month runway
- 12 new unit tests (6 per contract)

Closes #782
Closes #783
feat: issues #781, #782, #783, #784 — evidence root, onboarding escrow, rent fund, validation
…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.
WISDOM-WOKE22 and others added 29 commits August 26, 2026 23:48
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).
…cial-engineering-key-mgmt-consensus

fix: resolve security issues #866 #867 #868 #869
…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
feat: resolve replay attacks, algorithm gaming, reputation bridging, and exit lock-in (#905, #912, #913, #932)
@Gainer-dev Gainer-dev closed this Aug 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment