Contract 4fc2cb87faa5c119bf5c37c6fc45f80849140cba1fa47ff4ab9b338411d1e078

← Back to Index πŸ“₯ Download WASM

Meta

cliver 27.0.0#5a7c5fe76530bf4248477ac812fc757146b98cc4
rssdkver 25.3.1#e50d95af029c83196dd122f0154bac3f1302394b
rsver 1.96.1

Instances

  • CAC5KUVAJENSRWLXH56JYKATIPLQPBYPABRGQERYQWZD4LNAHJZCRFQA
  • CCOZ2JGQAPLUOG5RVU3TLPGSS7WA356BWCOEWFBJU44DKHDRZTQPABBS

Interface

fn burn(env: soroban_sdk::Env, from: soroban_sdk::Address, amount: i128)
fn name(env: soroban_sdk::Env) -> soroban_sdk::String
fn admin(env: soroban_sdk::Env) -> soroban_sdk::Address
fn pause(env: soroban_sdk::Env)

Unwrap. Burn shares SR from from and send the released underlying to receiver. Returns the underlying actually paid out.

Mirrors SY's redeem(...). The amount returned is what the strategy actually paid, not shares Γ— rate β€” a Blend liquidity shortfall or a rounding difference shows up honestly in the return value rather than being papered over (tofix.md #28).

fn redeem(
    env: soroban_sdk::Env,
    from: soroban_sdk::Address,
    receiver: soroban_sdk::Address,
    shares: i128,
    min_underlying_out: i128,
) -> i128
fn symbol(env: soroban_sdk::Env) -> soroban_sdk::String
fn approve(
    env: soroban_sdk::Env,
    from: soroban_sdk::Address,
    spender: soroban_sdk::Address,
    amount: i128,
    expiration_ledger: u32,
)
fn balance(env: soroban_sdk::Env, id: soroban_sdk::Address) -> i128

Wrap. Pull amount underlying from from, supply it to the strategy, and mint the resulting shares to receiver. Returns SR minted.

Mirrors SY's deposit(receiver, tokenIn, amountTokenToDeposit, minSharesOut). We take one token_in implicitly (the strategy's underlying) β€” a multi-asset front door is an aggregator concern that belongs in a router, not here.

fn deposit(
    env: soroban_sdk::Env,
    from: soroban_sdk::Address,
    receiver: soroban_sdk::Address,
    amount: i128,
    min_shares_out: i128,
) -> i128
fn unpause(env: soroban_sdk::Env)

Human-readable semver of the source build (informational; for verifiable identity use [Self::code_hash]).

fn version(env: soroban_sdk::Env) -> soroban_sdk::String
fn decimals(env: soroban_sdk::Env) -> u32

The yield source behind this SR (SY's yieldToken).

fn strategy(env: soroban_sdk::Env) -> soroban_sdk::Address

The current upgrade delay, seconds. Bounded on chain to [1h, 30d].

fn timelock(env: soroban_sdk::Env) -> u64
fn transfer(
    env: soroban_sdk::Env,
    from: soroban_sdk::Address,
    to: soroban_sdk::Address,
    amount: i128,
)
fn allowance(
    env: soroban_sdk::Env,
    from: soroban_sdk::Address,
    spender: soroban_sdk::Address,
) -> i128

The underlying value of user's SR right now.

fn assets_of(env: soroban_sdk::Env, user: soroban_sdk::Address) -> i128
fn burn_from(
    env: soroban_sdk::Env,
    spender: soroban_sdk::Address,
    from: soroban_sdk::Address,
    amount: i128,
)

The live deployed WASM hash (32-byte SHA-256) β€” reflects the running code across upgrades, so anyone can verify what is actually deployed rather than trusting a version string.

fn code_hash(env: soroban_sdk::Env) -> soroban_sdk::BytesN<32>
fn is_paused(env: soroban_sdk::Env) -> bool

Advance the stored high-water mark to the live rate. Permissionless β€” it only ever raises a floor, never lowers it, and never moves funds. Keeps the clamp tight without needing a deposit to happen.

fn sync_rate(env: soroban_sdk::Env) -> i128

One-shot, admin-gated init. Discovers the underlying from the strategy rather than being told it, so an SR can never be wired to an asset its strategy does not actually hold.

fn initialize(env: soroban_sdk::Env, strategy: soroban_sdk::Address)

The asset SR is denominated in (SY's assetInfo, reduced to the one field we need).

fn underlying(env: soroban_sdk::Env) -> soroban_sdk::Address

Permissionless TTL keep-alive for an SR holder's balance entry (tofix.md #30).

SR balances are bumped on every write, but a holder who deposits and then simply holds is never written to. SR has no maturity of its own, so unlike a receipt or an LP share there is no natural end date bounding the exposure β€” a dormant holder is the normal case, not the edge case. Anyone may call this: it only ever prolongs an entry, never mutates accounting.

No-ops for an address with no balance entry.

fn bump_holder(env: soroban_sdk::Env, user: soroban_sdk::Address)

The current TVL cap in underlying. 0 = uncapped.

fn deposit_cap(env: soroban_sdk::Env) -> i128

Accept a pending admin proposal (step 2 of 2). Callable only by the proposed address.

fn accept_admin(env: soroban_sdk::Env)
fn set_timelock(env: soroban_sdk::Env, secs: u64)

Underlying currently deployed through this wrapper, marked to market β€” supply valued at the stored rate. This is the dashboard number; it is NOT what the cap measures.

It may lag by one sync, which under-states it. See [Self::total_principal] for the cap's measure and why the two are deliberately different.

fn total_assets(env: soroban_sdk::Env) -> i128
fn total_supply(env: soroban_sdk::Env) -> i128

Bind the admin atomically at deploy (no deploy→init front-run window).

fn __constructor(env: soroban_sdk::Env, admin: soroban_sdk::Address)

Apply a scheduled upgrade. Reverts until eta has passed.

fn apply_upgrade(env: soroban_sdk::Env)

Underlying per 1e12 SR (SCALAR_12) β€” the yield oracle for everything above.

A genuinely pure read of stored state. It does NOT call the strategy.

That is not a micro-optimization, it is a correctness requirement discovered on testnet (2026-08-24). strategy::current_rate() writes its RateBound only when the rate has moved. A transaction that reaches it through a read path therefore has a footprint that depends on timing: simulation sees no write and records the strategy read-only, then a ledger passes, the rate moves, and execution needs to write β€” so the host rejects the whole transaction with storage: exceeded_limit β€” trying to access contract instance outside of the footprint. Intermittent, unreproducible locally, and fatal to buy_yt_exact_out.

So the split is explicit: this view reads the stored high-water mark and nothing else, while every mutating path refreshes it through [Self::sync_rate], which ALWAYS writes and is therefore deterministic in the footprint.

The value is mo

fn exchange_rate(env: soroban_sdk::Env) -> i128
fn pending_admin(env: soroban_sdk::Env) -> Option

Propose a new admin (step 1 of 2). The proposed address must then call [Self::accept_admin]; until it does, nothing changes.

fn propose_admin(env: soroban_sdk::Env, new_admin: soroban_sdk::Address)
fn transfer_from(
    env: soroban_sdk::Env,
    spender: soroban_sdk::Address,
    from: soroban_sdk::Address,
    to: soroban_sdk::Address,
    amount: i128,
)
fn cancel_upgrade(env: soroban_sdk::Env)

The largest redemption that can succeed right now, in SR shares (tofix.md #20).

Exits do not only fail when the protocol is insolvent. They fail β€” far more often β€” because borrowers have taken the venue's supply and there is nothing on hand to pay with. Before this, that produced a bare revert: no way to find out in advance, and no way to discover the smaller amount that would have worked.

i128::MAX when liquidity comfortably covers everything, so the common case needs no special handling in callers.

This is an estimate, and deliberately a conservative one. The venue's balance is an upper bound on what it can pay β€” Blend additionally refuses withdrawals that would push utilization past its ceiling β€” so [LIQUIDITY_HAIRCUT_BPS] is taken off before converting to shares. Being wrong low costs a user a second transaction; being wrong high costs them a revert.

fn max_redeemable(env: soroban_sdk::Env) -> i128

Underlying that shares SR would release at the current rate. Panic-free (0 = no quote).

fn preview_redeem(env: soroban_sdk::Env, shares: i128) -> i128

Redeem up to shares, taking whatever the venue can actually pay.

The plain [Self::redeem] is all-or-nothing: ask for more than the venue holds and the whole transaction reverts, leaving the user with nothing and no information. During a liquidity crunch β€” the likely failure mode, not the exotic one β€” that is the difference between getting most of your money out and getting none of it.

So this clamps to what is available and burns only the shares it actually redeems. The rest of the position stays untouched and can be withdrawn as liquidity returns.

Returns (shares_burned, underlying_paid).

Why clamping here is safe to authorize

shares is the user's ceiling, not a computed figure β€” the same shape as every other amount a wallet signs in this codebase. Burning fewer than authorized can only ever leave the user with more than they asked to give up.

min_underlying_out still applies to what is actually paid, so a user who would rather fail than take a partial fill sets it to the f

fn redeem_partial(
    env: soroban_sdk::Env,
    from: soroban_sdk::Address,
    receiver: soroban_sdk::Address,
    shares: i128,
    min_underlying_out: i128,
) -> (i128, i128)
fn pending_upgrade(env: soroban_sdk::Env) -> Option

SR that amount underlying would mint at the current rate. Panic-free (0 = no quote).

fn preview_deposit(env: soroban_sdk::Env, amount: i128) -> i128

The honest twin of [Self::exchange_rate] β€” underlying per 1e12 SR, valued on what actually exists rather than on the stored high-water mark.

total assets that actually exist
realizable_rate = ----------------------------------  x 1e12
total SR shares

Why this exists

exchange_rate is a high-water mark: it ratchets up and never falls. That is deliberate and load-bearing β€” it is what stops the whole stack repricing downward on a transient dip. But it means that after a real Blend principal loss, every value view built on it (assets_of, preview_redeem, total_assets) reports more underlying than a redemption will actually pay.

sr/src/test.rs::resetting_the_rate_floor_unfreezes_exits_and_the_loss_lands_pro_rata measures exactly that: after a 20% haircut each holder is correctly paid 800 USDC on a 1,000 USDC deposit β€” pro rata, first exit no better than the second β€” while exchange_rate still quotes the pre-loss number.

This is the number to quote instead. A holde

fn realizable_rate(env: soroban_sdk::Env) -> i128

Set the TVL cap, in underlying units. 0 lifts it.

This gates deposits only. redeem never consults it, so lowering the cap β€” or setting one below current TVL β€” can never trap a user. That is the whole reason it is safe to hand an admin: the worst they can do with it is stop new money coming in, which they can already do with pause.

fn set_deposit_cap(env: soroban_sdk::Env, cap: i128)

Cost basis β€” underlying deposited and not yet withdrawn. This is what the deposit cap is measured against (anyfix.md F4).

It rises by the exact amount deposited and falls proportionally as shares are destroyed, so it is unaffected by yield: a protocol where nothing happens but time does not slowly close its own deposits. It is a plain stored integer, which is what makes [Self::deposit_headroom] exact rather than an estimate.

fn total_principal(env: soroban_sdk::Env) -> i128

Underlying that could still be deposited before the cap bites. i128::MAX when uncapped.

Actionable: this is computed from the same stored integer [Self::deposit] checks, with no rate in either, so depositing exactly this number always succeeds (anyfix.md F3). It used to be derived from a mark-to-market valuation while deposit synced the rate first, so a max button built on it failed every time.

fn deposit_headroom(env: soroban_sdk::Env) -> i128

What shares are actually worth right now β€” the pro-rata claim, not the high-water quote. See [Self::realizable_rate]. Panic-free (0 = no quote).

fn realizable_value(env: soroban_sdk::Env, shares: i128) -> i128

Schedule an upgrade to wasm_hash, applyable once the timelock elapses. Returns the eta. The pending hash is publicly readable via [Self::pending_upgrade] for the whole window.

fn schedule_upgrade(env: soroban_sdk::Env, wasm_hash: soroban_sdk::BytesN<32>) -> u64

Withdraw a pending proposal. Current admin only.

fn cancel_admin_transfer(env: soroban_sdk::Env)

Imports

WebAssembly Text (WAT) β–Ά