Skip to content

feat: add storage rent sweep and estimated_storage_footprint (#40) - #42

Open
Clement-coder wants to merge 1 commit into
Kqirox:mainfrom
Clement-coder:feat/issue-40-storage-rent-sweep
Open

feat: add storage rent sweep and estimated_storage_footprint (#40)#42
Clement-coder wants to merge 1 commit into
Kqirox:mainfrom
Clement-coder:feat/issue-40-storage-rent-sweep

Conversation

@Clement-coder

Copy link
Copy Markdown
Contributor

Summary

Closes #40 — adds a contract-internal sweep/reclaim path and storage footprint view across all six contracts.

Problem

Soroban charges ongoing rent on every persistent storage entry. Without an off-ramp, abandoned entries (finished learner progress, reviewed quest submissions, executed proposals) accumulate indefinitely and rent-charges grow monotonically.

Approach

Soroban has no storage-scan API, so the implementation uses inline instance-storage counters maintained at each mutation point plus an append-only index vector (FinishedProgressIndex) for the highest-volume sweep target (learner progress).

Changes by contract

course-registry (primary sweep target)

  • complete_module: appends (learner, course_id) to FinishedProgressIndex on final module completion
  • sweep_storage(admin, sweep_learning_keys) -> u32: pops up to SWEEP_BATCH_SIZE (50) entries, removes DataKey::Progress slots, decrements ProgressCount
  • estimated_storage_footprint() -> u32: CourseCount + ProgressCount

quest-engine

  • sweep_submissions(admin, flag, Vec<(Address,u32)>) -> u32: removes Approved/Rejected submissions, skips Pending
  • estimated_storage_footprint() -> u32: QuestCount + SubmissionCount

governance

  • create_proposal(...) -> u32: new public function (was missing); emits ProposalCreated
  • sweep_storage(admin, flag, Vec<u32>) -> u32: removes executed/cancelled proposals
  • estimated_storage_footprint() -> u32: ProposalCount + VoteCount

badge-nft

  • estimated_storage_footprint() -> u32: BadgeHolderCount (one entry per unique learner)

stake-vault

  • stake / unstake maintain StakerCount (self-cleaning on withdrawal)
  • estimated_storage_footprint() -> u32: StakerCount

reward-pool

  • add_approved_spender: idempotent counter bump
  • estimated_storage_footprint() -> u32: SpenderCount

Testing

196 tests, 0 failures, 0 warnings

26 new tests covering:

  • Footprint counter accuracy (initial zero, increment, decrement where applicable)
  • Sweep removes targeted entries and updates counters
  • Sweep skips entries not eligible for removal (Pending submissions, active proposals)
  • sweep_learning_keys=false is a strict no-op
  • Batch size cap enforced (two sweep calls needed for >50 entries)
  • Unauthorized callers panic

Cost-benefit tradeoffs

Approach Pro Con
Counter-based index (this PR) O(1) footprint reads; no scan Counters can drift if entries expire naturally via TTL
Full storage scan Always accurate Not possible in Soroban
Off-chain indexer No on-chain cost Requires trusted off-chain infra

The counter drift risk is acceptable: TTL expiry is the desired outcome (storage freed), so a slightly stale counter only underestimates the footprint after natural TTL expiry — fine for an estimate.

Cross-cutting changes across all six contracts to address Issue Kqirox#40
(persistent storage rent sweep / reclaim path).

## What changed

### course-registry (primary sweep target)
- complete_module: appends (learner, course_id) to FinishedProgressIndex
  on final module completion so sweep can find finished entries
- sweep_storage(admin, sweep_learning_keys) -> u32: admin-only; pops up
  to SWEEP_BATCH_SIZE (50) entries from FinishedProgressIndex, removes
  each DataKey::Progress slot, decrements ProgressCount
- estimated_storage_footprint() -> u32: CourseCount + ProgressCount
- DataKey: FinishedProgressIndex comment updated; ProgressCount now
  decremented by sweep (not just incremented on enroll)
- 8 new tests covering sweep batch-size limit, multi-learner/course,
  no-op flag, unauthorized panic, empty index, footprint tracking

### quest-engine
- DataKey: added QuestCount, SubmissionCount counters
- create_build_quest / create_explore_quest: increment QuestCount
- submit_proof: increments SubmissionCount
- sweep_submissions(admin, flag, Vec<(Address,u32)>) -> u32: removes
  Approved/Rejected submissions, skips Pending, respects 50-entry cap
- estimated_storage_footprint() -> u32: QuestCount + SubmissionCount
- 5 new tests

### governance
- DataKey: added ProposalCount, VoteCount counters
- create_proposal(proposer, metadata_hash, voting_period_seconds) -> u32:
  new public function (was missing); increments ProposalCount + ID counter;
  emits ProposalCreated event
- cast_vote: increments VoteCount
- sweep_storage(admin, flag, Vec<u32>) -> u32: removes executed/cancelled
  proposals (executed==true), decrements ProposalCount
- estimated_storage_footprint() -> u32: ProposalCount + VoteCount
- 5 new tests

### badge-nft
- DataKey: added BadgeHolderCount counter
- mint_badge: detects first-time badge vec creation; increments
  BadgeHolderCount once per learner (not per badge)
- estimated_storage_footprint() -> u32: BadgeHolderCount
- BadgeNFTInterface trait: exposes estimated_storage_footprint
- 3 new tests

### stake-vault
- DataKey: added StakerCount counter
- stake: increments StakerCount only on first stake per user
- unstake: decrements StakerCount after removing the entry (self-cleaning)
- estimated_storage_footprint() -> u32: StakerCount
- 3 new tests

### reward-pool
- DataKey: added SpenderCount counter
- add_approved_spender: idempotent counter bump (only new spenders counted)
- estimated_storage_footprint() -> u32: SpenderCount
- RewardPoolInterface trait: exposes estimated_storage_footprint
- 2 new tests

## Test results
196 tests, 0 failures, 0 warnings

Closes Kqirox#40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[40] Add persistent storage rent sweep / reclaim path

1 participant