Contract 5378f8b59421aea057285208a071494264f4eda6e435ed5f37672dba87cba967

← Back to Index 📥 Download WASM

Meta

cliver 28.0.0#300aaf69ab100536678bdb641428b06f06b318ea
rssdkver 22.0.11#34f7f53ae31e0fd02aab436a9872e79fa671ca02
rsver 1.98.1

Instances

  • CBRAD5MD7CCXNXRLRGTRKG4NNZKR3N643VUEBNJGWB2L6KLZDLFWMXHQ

Interface

Deposit amount USDC into the vault. USDC is forwarded to the yield adapter, which deploys it to the underlying protocol.

Returns the number of mUSDC shares minted to the caller.

fn deposit(
    env: soroban_sdk::Env,
    caller: soroban_sdk::Address,
    amount: i128,
    min_shares_out: i128,
) -> Result

Withdraw by burning shares mUSDC. Returns the USDC amount sent back to the caller.

min_usdc_out is a caller-supplied slippage floor. If the computed USDC output falls below this value the transaction reverts with MinAmountOutNotMet, giving the caller a predictable, typed failure rather than an opaque WithdrawalTooSmall. Pass 0 to opt out of the floor (behaviour is then identical to the pre-guard contract).

This guards against ratio-shifting: a concurrent withdrawal by another depositor changes the shared ADPT_SH/TOTAL_SH ratio before this transaction lands, silently shrinking the payout. With min_usdc_out the caller can bound how much shrinkage they are willing to accept.

fn withdraw(
    env: soroban_sdk::Env,
    caller: soroban_sdk::Address,
    shares: i128,
    min_usdc_out: i128,
) -> Result

Returns the current admin address.

fn get_admin(env: soroban_sdk::Env) -> Result

Returns whether deposits are currently paused.

fn is_paused(env: soroban_sdk::Env) -> bool

Retained so the ABI of vaults already deployed from earlier WASM is unchanged, and so an old vault can still be initialized by hand.

On any vault deployed from this WASM it is unreachable: __constructor has already set ADMIN, so every call returns AlreadyInitialized. That is the intended behavior, not a leftover. An attacker calling this against a freshly deployed vault is rejected instead of served.

fn initialize(
    env: soroban_sdk::Env,
    admin: soroban_sdk::Address,
    usdc: soroban_sdk::Address,
    musdc: soroban_sdk::Address,
    adapter: soroban_sdk::Address,
) -> Result<(), ContractError>

Admin-only emergency switch. While paused, new deposits are rejected. Withdrawals are deliberately left open so a pause can never trap funds.

fn set_paused(env: soroban_sdk::Env, paused: bool) -> Result<(), ContractError>

Returns the current adapter address.

fn get_adapter(env: soroban_sdk::Env) -> Result

Called by the mUSDC token contract immediately after it moves a transfer's balances, splitting Principal/Entry pro-rata between sender and receiver (#578) — the fix for the honest-0 degradation documented on get_principal/get_entry_time, from back when mUSDC was a plain Stellar Asset Contract with no hook for the vault to observe a transfer at all.

Requires the mUSDC token's own require_auth(), which succeeds only when mUSDC is the direct caller of this exact invocation — Soroban treats a contract's own direct sub-calls as inherently authorized by that contract, with no signature needed (see Env::authorize_as_current_contract's doc comment: "All the direct calls that the current contract performs are always considered to have been authorized"). This can therefore never be triggered by anything other than a genuine transfer on the one real, configured mUSDC contract — the same direction-reversed pattern adapter-common::require_vault_auth already uses for every adapter to verify a call

fn on_transfer(
    env: soroban_sdk::Env,
    from: soroban_sdk::Address,
    to: soroban_sdk::Address,
    amount: i128,
    sender_balance_before: i128,
    receiver_balance_before: i128,
) -> Result<(), ContractError>

Replace the yield adapter. The vault must have no shares outstanding (TOTAL_SH == 0) and no position left at the old adapter (ADPT_SH == 0) before calling this.

Checking TOTAL_SH alone is not sufficient: the two counters can desync (see migrate_adapter's NoAdapterPosition doc), so a vault that looks empty by TOTAL_SH can still have real value sitting at the old adapter. Deliberately does not reset ADPT_SH to zero on swap the way it used to -- doing so would destroy the only evidence that a stranded position existed, and it is unnecessary: ADPT_SH is only ever nonzero here in a genuinely-empty vault as a result of a bug, and clearing it would delete the observability that bug needs to be diagnosed, not fix it. In the normal case (both counters already zero) leaving ADPT_SH alone is a no-op.

fn set_adapter(
    env: soroban_sdk::Env,
    new_adapter: soroban_sdk::Address,
) -> Result<(), ContractError>

Completes a pending admin handover. Requires the nominee's own require_auth(), not the current admin's, so the transfer can only complete once the new address has demonstrably proven it controls a working signing key. Fails with NoPendingAdmin if no transfer_admin nomination is outstanding.

fn accept_admin(env: soroban_sdk::Env) -> Result<(), ContractError>

Returns the address's mUSDC share balance, read from the share token itself. mUSDC received by transfer counts immediately and withdraws normally, exactly like minted shares; there is no separate vault-side balance that could disagree with the token.

Returns 0 before initialize, rather than erroring: this is a view used by dashboards, and "no position" is the truthful answer for a vault that holds nothing yet.

fn get_position(env: soroban_sdk::Env, address: soroban_sdk::Address) -> i128

Returns the address's cost basis: the net USDC it deposited and has not yet withdrawn. Yield earned is current share value minus this value.

Cost basis is history, not a holding, so unlike the share balance it cannot be derived from the token on its own — but as of #578, mUSDC is a custom SEP-41 token the vault controls the code of (not a plain Stellar Asset Contract), so its transfer/transfer_from call back into on_transfer, which splits a sender's basis and hands the pro-rata share to the receiver at the moment of transfer. See on_transfer's doc comment for the split math.

An address that transferred its entire position away reports 0 here because it holds nothing, rather than a stale basis for shares it no longer has — on_transfer clears both records itself the moment a full transfer-out reaches zero.

Like get_entry_time, the self-heal below (clearing a zero-position holder's leftover record) should be unreachable in the current system; it's kept as a defensive fallback, and as a bridg

fn get_principal(env: soroban_sdk::Env, address: soroban_sdk::Address) -> i128

Sets the admin, USDC token address, mUSDC share token address, and the initial yield adapter address, inside the deploying transaction's own CreateContract operation. Unlike a separate initialize() call, there is no intervening ledger where an attacker could land a self-authorized call first: the deployer's transaction is the only one that can ever set this contract's state (#551, same bug class as #505, fixed for the adapters/mUSDC in #550).

Unlike the adapters/mUSDC's constructor arguments, admin is a human-held key, not a programmatically-derived contract address, so require_auth() is called on it here too: without it, DEPLOYER alone could set any address as admin with no proof it is controlled by anyone, permanently bricking the vault on a typo (transfer_admin/ accept_admin both require the current admin's own signature to move away from it). Soroban only honors require_auth() inside a constructor for the address that is the deploying transaction's own source account, so this requires `

fn __constructor(
    env: soroban_sdk::Env,
    admin: soroban_sdk::Address,
    usdc: soroban_sdk::Address,
    musdc: soroban_sdk::Address,
    adapter: soroban_sdk::Address,
)

Returns the ledger timestamp of the address's deposit, or 0 if it holds no position. Reset whenever the position is fully withdrawn.

Entry time belongs to a depositor, not to the shares: it is recorded when an address first deposits, and now (#578) is also split pro-rata on a transfer by on_transfer — see that function's doc comment for the split math. An address holding no mUSDC reports 0 even if it deposited earlier and later transferred everything away, so a record left behind by a full transfer-out is never reported as a live position.

on_transfer clears a sender's record itself the moment a full transfer-out reaches zero, so a leftover record found here should be unreachable in the current system; this self-heal is kept as a defensive fallback for any balance that reached zero another way this contract doesn't yet account for (and for state left behind by mUSDC's pre-#578 life as a plain Stellar Asset Contract with no transfer hook at all, before a live-holder migration if one hasn't run yet). Cl

fn get_entry_time(env: soroban_sdk::Env, address: soroban_sdk::Address) -> u64

Admin-only: nominate new_admin as the next admin. Requires the current admin's require_auth(). Does not itself change who the admin is — that only happens once the nominee calls accept_admin with their own signature, so a typo'd or unreachable address can never brick admin: the old admin stays in control until a working key on the other end proves it can sign. Overwrites any prior, not-yet-accepted nomination.

fn transfer_admin(
    env: soroban_sdk::Env,
    new_admin: soroban_sdk::Address,
) -> Result<(), ContractError>

Phase 1 of a two-phase migration, and the trigger for MIN_LEDGER_GAP's ~1-day timelock. Snapshots the target adapter's total_assets() and the current ledger sequence so that a later migrate_adapter call can verify the valuation has been stable for at least MIN_LEDGER_GAP ledgers. This prevents an observer from griefing or masking a migration by front-running a transiently-shifted valuation (issue #567), and separately gives observers or automated monitoring a real window to notice and react to a migration triggered by a compromised admin key before it can execute (issue #557) — the same unattended signer used for routine migration-keeper operations can call this function, so the delay itself is the only thing standing between a leaked key and the vault's entire position moving to an address the attacker controls.

Can be called repeatedly for the same or different adapters; each call overwrites the previous snapshot. The admin must then wait for the ledger gap to elapse before calling `migrate_adapt

fn begin_migration(
    env: soroban_sdk::Env,
    new_adapter: soroban_sdk::Address,
) -> Result<(), ContractError>

Phase 2 of a two-phase migration. Must be preceded by begin_migration(new_adapter) and at least MIN_LEDGER_GAP ledgers must have elapsed. Moves the vault's entire position from the current adapter to new_adapter in one atomic transaction, without requiring depositors to withdraw first. Unlike set_adapter, this is safe to call with shares outstanding.

Withdraws everything from the old adapter into the vault, deposits it into new_adapter, and performs two independent checks:

  1. Slippage: new_adapter.total_assets() must be at least (10_000 - max_slippage_bps) / 10_000 of the old adapter's pre-migration value.

  2. Stability: new_adapter.total_assets() must be at least (10_000 - max_slippage_bps) / 10_000 of the snapshot value recorded by begin_migration, proving the valuation has been stable across the ledger-gap cooldown.

If either check fails the whole call reverts and nothing moves (Soroban transactions are atomic). On success the snapshot is cleared. TOTAL_SH, every holder's m

fn migrate_adapter(
    env: soroban_sdk::Env,
    new_adapter: soroban_sdk::Address,
    max_slippage_bps: u32,
) -> Result<(), ContractError>

Total USDC value managed by the vault as reported by the adapter. Includes yield accrued by the underlying protocol.

fn get_total_assets(env: soroban_sdk::Env) -> Result

Returns total mUSDC shares outstanding.

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

Returns the pending admin nominee, if any.

fn get_pending_admin(env: soroban_sdk::Env) -> Option

Permissionless entry point that extends the TTL of instance storage and the position records for address. Anyone can call it, so off-chain keepers or the user themselves can keep a position alive without needing a signature on the vault.

fn extend_position_ttl(env: soroban_sdk::Env, address: soroban_sdk::Address)

Returns the current migration snapshot, if one has been recorded by begin_migration. Off-chain callers can use this to verify the cooldown is progressing.

fn get_migration_snapshot(
    env: soroban_sdk::Env,
) -> Result

Imports

WebAssembly Text (WAT) ▶