Repository navigation
Conversation
|
@Ejirosam 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! 🚀 |
Ejirosam
force-pushed
the
fix/19-locked-in-real-yield
branch
from
October 12, 2026 06:49
b102908 to
f8ef699
Compare
Ejirosam
force-pushed
the
fix/19-locked-in-real-yield
branch
2 times, most recently
from
October 15, 2026 00:13
0bbc2cc to
c376305
Compare
This branch has not been deployed
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.
Overview
lock()promised a yield computed from the APY table at lock time andunlock()paidamount + projected_yieldout of the contract's pooled token balance. That balance is every user's principal, so an unfunded pool paid the first withdrawer's "yield" out of the second user's money.This change replaces the promise with shares. A lock now holds
sharesof the pooled vault (crates/loktin-yield), andunlock()pays whatever those shares redeem for at that moment. If the pool earned nothing, a share is worth exactly what was paid for it, so two users who each lock 100 with no pool set both withdraw 100 and the contract is never short.projected_yieldstays onLockas a display estimate only, and is never paid as a promise.The same file's two smaller defects are fixed: the duration range check now runs before the tier lookup (so
InvalidDurationfires for 0 and 61 instead ofDurationTierMissing), and one month is365 / 12days so a 12-month lock earns the advertised APY over a full 365-day year rather than 360 days.Related Issue
#40 — "fix(locked_in): pay real yield instead of yield promised at lock time".
Changes
[ADD]
crates/loktin-yield/Cargo.tomlrlib) holding the share math and the pool interface.[ADD]
crates/loktin-yield/src/lib.rsshares_for_deposit,assets_for_shares,position_yield(ERC-4626-style, rounds down) and thePoolclient trait matchingmock_pool'ssupply/withdraw/get_position.[ADD]
crates/loktin-yield/src/test.rs[MODIFY]
Cargo.toml/Cargo.lock["contracts/*", "crates/*"]; lockfile gains theloktin-yieldpackage and thelocked_independency edge.[MODIFY]
contracts/locked_in/Cargo.tomlloktin-yieldvia path.[MODIFY]
contracts/locked_in/src/types.rsLockgainsshares;projected_yieldis documented as a display-only estimate.DataKeygainsTotalShares.[MODIFY]
contracts/locked_in/src/lib.rslock()prices the deposit against the vault's pre-deposit value, rejects a zero-share mint (Error::ZeroShares), recordsshares, and incrementsTotalShares. Duration range check moved ahead of the tier lookup. One month is now2_628_000s (365 / 12days).unlock()paysassets_for_shares(shares, total_shares, vault value), pulling the shortfall from the pool first and failing withError::InsufficientLiquidityrather than underpaying. LocalPooltrait removed in favour ofloktin_yield::PoolClient.total_shares()/total_assets()reads and extends the instance TTL alongside persistent TTLs.[MODIFY]
contracts/locked_in/src/error.rsZeroShares = 18andInsufficientLiquidity = 19.[MODIFY]
contracts/locked_in/src/events.rsLockedevent carriessharesalongside the display estimate.[MODIFY]
contracts/locked_in/src/test.rsassert_solventclaim check, mirroring test(contracts): add multi-user test harness #36) plus the new regression tests below.Verification Results
test_two_users_no_pool_both_withdraw_in_full. Adds a local harness (funded_user,assert_solvent); #36 is still unmerged, so the helpers live in this test module and move to the shared crate when it lands.test_user_cannot_withdraw_more_than_shares_redeem_forasserts a non-zeroprojected_yieldbut pays only the shares' real value.InvalidDurationfires for duration 0 and for 61test_invalid_duration_fires_for_zero_and_sixty_oneassertsErr(Ok(Error::InvalidDuration))for both; the range check now precedes the tier lookup.shares <= 0→Error::ZeroSharesbefore the token transfer;test_zero_share_mint_is_rejectedalso asserts the balance is untouched and a whole-share deposit still works.loktin-commonextend_ttl_instance, called from the constructor and every state-changing entry point), butloktin-common(#38) is not onstaging, so a local mirror with the same name is used.loktin-commontest_lock_ids_are_one_indexed(they already started at 1 vianext_id); the sharedloktin-common::next_idis not onstagingyet.2_628_000s =365 / 12days, so 12 months is exactlySECONDS_PER_YEAR(365 days) and a "10% APY" tier pays 10%; documented at the constant and asserted intest_lock_records_shares_and_display_estimate.cargo test --workspacepassesCloses #40