feat(contracts): implement Yield Vault — SC-016, SC-017, SC-018, SC-019#407
Merged
ONEONUORA merged 1 commit intoJun 25, 2026
Merged
Conversation
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().
|
@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! 🚀 |
ONEONUORA
approved these changes
Jun 25, 2026
ONEONUORA
left a comment
Contributor
There was a problem hiding this comment.
Great job @Creed1759
Thank you for your contribution
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.
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):
──────────────────────────────────────────────────────────────────── 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):
──────────────────────────────────────────────────────────────────── SC-018 — Withdraw function
──────────────────────────────────────────────────────────────────── withdraw(user, shares):
View helpers exposed for off-chain indexing / dashboard use:
shares_of(user), total_shares(), total_assets(), yield_index().