Skip to content

feat(contracts): implement Yield Vault — SC-016, SC-017, SC-018, SC-019#407

Merged
ONEONUORA merged 1 commit into
Fracverse:masterfrom
Creed1759:feat/sc-016-017-018-019-yield-vault
Jun 25, 2026
Merged

feat(contracts): implement Yield Vault — SC-016, SC-017, SC-018, SC-019#407
ONEONUORA merged 1 commit into
Fracverse:masterfrom
Creed1759:feat/sc-016-017-018-019-yield-vault

Conversation

@Creed1759

Copy link
Copy Markdown
Contributor

Closes #357 [SC-016] — Storage keys & configuration state
Closes #358 [SC-017] — Yield Vault deposit function
Closes #359 [SC-018] — Yield Vault withdraw function
Closes #360 [SC-019] — Yield compounding math based on time/ledgers

──────────────────────────────────────────────────────────────────── NEW FILES
──────────────────────────────────────────────────────────────────── contracts/contracts/yield_vault/Cargo.toml
Standard Soroban crate manifest (cdylib + rlib, soroban-sdk 20.0.0).

contracts/contracts/yield_vault/src/lib.rs
Single-file Soroban contract implementing all four issues.

MODIFIED FILES
contracts/Cargo.toml
Added 'contracts/yield_vault' to the workspace members list.

──────────────────────────────────────────────────────────────────── SC-016 — Storage keys & configuration state
──────────────────────────────────────────────────────────────────── Defined seven Symbol constants as Soroban instance-storage keys:
OWNER_KEY, TOKEN_KEY, APY_KEY, SHARES_KEY, ASSETS_KEY,
IDX_KEY (yield index), IDX_LED_KEY (last checkpoint ledger).

Defined a DataKey enum with variant UserShares(Address) for persistent per-user share balances.

Implemented initialize(owner, token, apy_bps):

  • Panics with 'already initialized' on re-call (single-init guard).
  • Stores owner address, accepted token address, APY in basis points.
  • Seeds the yield index at PRECISION (1e8 = 1.0) and records the current ledger sequence as the index baseline.
  • Zeroes out total_shares and total_assets counters.

──────────────────────────────────────────────────────────────────── SC-019 — Yield compounding math based on time/ledgers ──────────────────────────────────────────────────────────────────── All math uses a fixed-point PRECISION factor of 1e8 to avoid floating-point and keep values within i128 for any realistic APY (up to tens of thousands of bps) and time window.

current_index(env) — private helper:
Reads old_index, apy_bps, and delta ledgers since last checkpoint.
Accrued yield = old_index * apy_bps * delta / (10_000 * LEDGERS_PER_YEAR).
Added to old_index with checked_mul / checked_add to panic on
overflow rather than silently wrap.
Returns old_index unchanged when delta == 0 or apy_bps == 0.

checkpoint_index(env) — private helper:
Calls current_index, persists the new value, and resets the
reference ledger to the current sequence so subsequent calls
accumulate yield only from this point forward.

──────────────────────────────────────────────────────────────────── SC-017 — Deposit function
──────────────────────────────────────────────────────────────────── deposit(depositor, amount):

  1. Calls depositor.require_auth() — no unauthenticated deposits.
  2. Asserts amount > 0.
  3. Calls checkpoint_index to snapshot accrued yield first.
  4. Uses soroban_sdk::token::Client to transfer 'amount' tokens from the depositor's address to the vault's own address (env.current_contract_address()), satisfying the pull-pattern requirement.
  5. Calculates shares_minted = amount * PRECISION / current_index so early depositors get more shares (higher index over time → fewer shares per unit, meaning each share is worth more).
  6. Adds shares to the user's persistent DataKey::UserShares entry.
  7. Increments SHARES_KEY and ASSETS_KEY instance totals.
  8. Emits a 'Deposited' event (depositor, amount, shares).

──────────────────────────────────────────────────────────────────── SC-018 — Withdraw function
──────────────────────────────────────────────────────────────────── withdraw(user, shares):

  1. Calls user.require_auth() as required by the acceptance criteria.
  2. Asserts shares > 0.
  3. Calls checkpoint_index so withdrawers receive yield earned up to the exact ledger of the withdrawal.
  4. Reads the user's share balance and panics with 'insufficient shares' if they do not own enough.
  5. Calculates assets_out = shares * current_index / PRECISION — the inverse of the deposit formula, so principal + accrued yield is returned.
  6. Deducts the redeemed shares from the user's persistent balance (burn-equivalent; shares are simply removed, not moved).
  7. Decrements SHARES_KEY and ASSETS_KEY; clamps to 0 to prevent negative totals from integer rounding drift.
  8. Transfers assets_out tokens from the vault to the user via token::Client.transfer(vault_addr, user, assets_out).
  9. Emits a 'Withdrawn' event (user, shares, assets_out).

View helpers exposed for off-chain indexing / dashboard use:
shares_of(user), total_shares(), total_assets(), yield_index().

Closes Fracverse#357 [SC-016] — Storage keys & configuration state
Closes Fracverse#358 [SC-017] — Yield Vault deposit function
Closes Fracverse#359 [SC-018] — Yield Vault withdraw function
Closes Fracverse#360 [SC-019] — Yield compounding math based on time/ledgers

────────────────────────────────────────────────────────────────────
NEW FILES
────────────────────────────────────────────────────────────────────
contracts/contracts/yield_vault/Cargo.toml
  Standard Soroban crate manifest (cdylib + rlib, soroban-sdk 20.0.0).

contracts/contracts/yield_vault/src/lib.rs
  Single-file Soroban contract implementing all four issues.

MODIFIED FILES
contracts/Cargo.toml
  Added 'contracts/yield_vault' to the workspace members list.

────────────────────────────────────────────────────────────────────
SC-016 — Storage keys & configuration state
────────────────────────────────────────────────────────────────────
Defined seven Symbol constants as Soroban instance-storage keys:
  OWNER_KEY, TOKEN_KEY, APY_KEY, SHARES_KEY, ASSETS_KEY,
  IDX_KEY (yield index), IDX_LED_KEY (last checkpoint ledger).

Defined a DataKey enum with variant UserShares(Address) for
persistent per-user share balances.

Implemented initialize(owner, token, apy_bps):
  - Panics with 'already initialized' on re-call (single-init guard).
  - Stores owner address, accepted token address, APY in basis points.
  - Seeds the yield index at PRECISION (1e8 = 1.0) and records the
    current ledger sequence as the index baseline.
  - Zeroes out total_shares and total_assets counters.

────────────────────────────────────────────────────────────────────
SC-019 — Yield compounding math based on time/ledgers
────────────────────────────────────────────────────────────────────
All math uses a fixed-point PRECISION factor of 1e8 to avoid
floating-point and keep values within i128 for any realistic APY
(up to tens of thousands of bps) and time window.

current_index(env) — private helper:
  Reads old_index, apy_bps, and delta ledgers since last checkpoint.
  Accrued yield = old_index * apy_bps * delta / (10_000 * LEDGERS_PER_YEAR).
  Added to old_index with checked_mul / checked_add to panic on
  overflow rather than silently wrap.
  Returns old_index unchanged when delta == 0 or apy_bps == 0.

checkpoint_index(env) — private helper:
  Calls current_index, persists the new value, and resets the
  reference ledger to the current sequence so subsequent calls
  accumulate yield only from this point forward.

────────────────────────────────────────────────────────────────────
SC-017 — Deposit function
────────────────────────────────────────────────────────────────────
deposit(depositor, amount):
  1. Calls depositor.require_auth() — no unauthenticated deposits.
  2. Asserts amount > 0.
  3. Calls checkpoint_index to snapshot accrued yield first.
  4. Uses soroban_sdk::token::Client to transfer 'amount' tokens
     from the depositor's address to the vault's own address
     (env.current_contract_address()), satisfying the pull-pattern
     requirement.
  5. Calculates shares_minted = amount * PRECISION / current_index
     so early depositors get more shares (higher index over time →
     fewer shares per unit, meaning each share is worth more).
  6. Adds shares to the user's persistent DataKey::UserShares entry.
  7. Increments SHARES_KEY and ASSETS_KEY instance totals.
  8. Emits a 'Deposited' event (depositor, amount, shares).

────────────────────────────────────────────────────────────────────
SC-018 — Withdraw function
────────────────────────────────────────────────────────────────────
withdraw(user, shares):
  1. Calls user.require_auth() as required by the acceptance criteria.
  2. Asserts shares > 0.
  3. Calls checkpoint_index so withdrawers receive yield earned up
     to the exact ledger of the withdrawal.
  4. Reads the user's share balance and panics with 'insufficient
     shares' if they do not own enough.
  5. Calculates assets_out = shares * current_index / PRECISION —
     the inverse of the deposit formula, so principal + accrued
     yield is returned.
  6. Deducts the redeemed shares from the user's persistent balance
     (burn-equivalent; shares are simply removed, not moved).
  7. Decrements SHARES_KEY and ASSETS_KEY; clamps to 0 to prevent
     negative totals from integer rounding drift.
  8. Transfers assets_out tokens from the vault to the user via
     token::Client.transfer(vault_addr, user, assets_out).
  9. Emits a 'Withdrawn' event (user, shares, assets_out).

View helpers exposed for off-chain indexing / dashboard use:
  shares_of(user), total_shares(), total_assets(), yield_index().
@drips-wave

drips-wave Bot commented Jun 25, 2026

Copy link
Copy Markdown

@Creed1759 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@ONEONUORA ONEONUORA left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job @Creed1759

Thank you for your contribution

@ONEONUORA ONEONUORA merged commit f1d89ee into Fracverse:master Jun 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants