Skip to main content

LT (Leveraged Token)

The source code of the LT.vy contract can be found on GitHub. The contract is written with Vyper version 0.4.3.

Overview

The LT contract is the market’s leveraged-liquidity share token (ERC-20, 18 decimals; symbol yb-asset). Users deposit the asset token (e.g., BTC) while the contract borrows a matching amount of stablecoin liquidity from the market (via its allocation) to mint Curve LP and hand the LP into the LEVAMM. In return, users receive LT shares that represent a pro-rata claim on the AMM position’s value. Withdrawals burn shares and unwind through the AMM and Curve pool back to asset (and any required stablecoin settlement). The contract exposes standard deposit/withdraw flows with slippage guards and a fair, oracle-anchored price-per-share.

LT also manages the yb markets: it owns the LEVAMM instance, forwards interest-rate and fee updates to the AMM, manages the stablecoin allocation to the AMM (allocate_stablecoins), and periodically sweeps borrower interest collected in the AMM into the Curve pool as LP tokens (distribute_borrower_fees).

An optional staker address can be set once to integrate with gauges/rewards, and admin fees are tracked and withdrawable to the protocol’s fee receiver. Safety is enforced with access controls (admin/factory admins), non-reentrancy on state-changing entry points, kill/unkill passthrough to the AMM, and oracle-based checks to keep the position within safe bounds.

Deployed by the Factory: LT instances are cloned from the configured implementation using Vyper’s create_from_blueprint(asset_token, stablecoin, cryptopool, admin_or_factory), then connected to the market’s LEVAMM and pricing oracle during market creation.


Function Documentation

Public Variables

The LT contract exposes several public variables for reading contract state and configuration.

Constants

  • CRYPTOPOOL_N_COINS (uint256) - Number of coins in the crypto pool (2)
  • FEE_CLAIM_DISCOUNT (uint256) - Fee claim discount (10^16 = 1%)
  • MIN_SHARE_REMAINDER (uint256) - Minimum shares to leave if > 0 (10^6)
  • SQRT_MIN_UNSTAKED_FRACTION (int256) - Minimum unstaked fraction (10^14 = 1e-4)
  • MIN_STAKED_FOR_FEES (int256) - Minimum staked amount for fees (10^16)

Immutable Variables

  • CRYPTOPOOL (CurveCryptoPool) - Curve crypto pool contract (immutable)
  • STABLECOIN (IERC20) - Stablecoin contract (immutable)
  • ASSET_TOKEN (IERC20) - Asset token contract (immutable)

State Variables

  • admin (address) - Admin address
  • amm (LevAMM) - AMM contract address
  • agg (PriceOracle) - Price aggregator contract
  • staker (address) - Staker contract address
  • liquidity (LiquidityValues) - Liquidity state struct
  • allowance (HashMap[address, HashMap[address, uint256]]) - ERC20 allowances
  • balanceOf (HashMap[address, uint256]) - ERC20 balances
  • totalSupply (uint256) - Total token supply
  • decimals (uint8) - Token decimals (18)
  • stablecoin_allocation (uint256) - Stablecoin allocation limit
  • stablecoin_allocated (uint256) - Currently allocated stablecoins

Data Structs

AMMState

  • collateral (uint256) - LP token amount held as collateral
  • debt (uint256) - Interest-accrued stablecoin debt
  • x0 (uint256) - Virtual balance for maintaining 2x leverage

Pair

  • collateral (uint256) - LP token amount
  • debt (uint256) - Stablecoin debt amount

OraclizedValue

  • p_o (uint256) - Oracle price used for calculation
  • value (uint256) - Calculated value in stablecoin terms

LiquidityValues

  • admin (int256) - Admin fees (can be negative)
  • total (uint256) - Total position value
  • ideal_staked (uint256) - Ideal staked amount
  • staked (uint256) - Current staked amount

LiquidityValuesOut

  • admin (int256) - Admin fees (can be negative)
  • total (uint256) - Total position value
  • ideal_staked (uint256) - Ideal staked amount
  • staked (uint256) - Current staked amount
  • staked_tokens (uint256) - Staked token balance after reductions
  • supply_tokens (uint256) - Total supply after reductions
  • token_reduction (int256) - Token reduction amount

Events

SetStaker (Emitted when a new staker contract is set)

  • staker (address) - New staker contract address

WithdrawAdminFees (Emitted when admin fees are withdrawn)

  • receiver (address) - Address that received the freshly minted yb-LP shares (the factory fee_receiver)
  • amount (uint256) - Amount of yb-LP minted to the receiver

AllocateStablecoins (Emitted when stablecoins are allocated to the AMM)

  • allocator (indexed address) - Address that allocated stablecoins
  • stablecoin_allocation (uint256) - Total allocation limit
  • stablecoin_allocated (uint256) - Currently allocated amount

DistributeBorrowerFees (Emitted when borrower fees are distributed to the Curve pool)

  • sender (indexed address) - Caller that triggered the distribution (e.g. AMM on swap, LT on deposit/withdraw, or any keeper)
  • amount (uint256) - Amount of crvUSD donated into the Cryptoswap pool
  • min_amount (uint256) - Minimum LP-token amount accepted (computed from the donation discount and CRYPTOPOOL.lp_price())
  • discount (uint256) - Discount applied to the distribution

Deposit (Emitted when assets are deposited and shares are minted)

  • sender (address) - Address that initiated the deposit
  • owner (address) - Address that receives the shares
  • assets (uint256) - Amount of assets deposited
  • shares (uint256) - Amount of shares minted

Withdraw (Emitted when shares are burned and assets are withdrawn)

  • sender (address) - Address that initiated the withdrawal
  • receiver (address) - Address that receives the assets
  • owner (address) - Address that owns the shares
  • assets (uint256) - Amount of assets withdrawn
  • shares (uint256) - Amount of shares burned

SetAdmin (Emitted when the admin address is changed)

  • admin (address) - New admin address

_calculate_values()

Description:
Internal function that calculates the current liquidity values, including admin fees, token reductions, and position metrics. This is the core function for determining LT token values and managing fee distributions.

Returns:
LiquidityValuesOut - Struct containing detailed position metrics including:

FieldTypeDescription
adminint256Admin fees (can be negative)
totaluint256Total position value
ideal_stakeduint256Ideal staked amount
stakeduint256Current staked amount
staked_tokensuint256Staked token balance after reductions
supply_tokensuint256Total supply after reductions
token_reductionint256Token reduction amount
InputTypeDescription
p_ouint256Oracle price for calculations
_amm_valueuint256Optional pre-read AMM value (default 0). When non-zero, the function uses the supplied value instead of calling amm.value_oracle(). emergency_withdraw uses this when the AMM is killed or the oracle has reverted, so the rebase can still complete from a soft-read value.
📄 View Source Code
@view
def _calculate_values(p_o: uint256, _amm_value: uint256 = 0) -> LiquidityValuesOut:
prev: LiquidityValues = self.liquidity
staker: address = self.staker
staked: int256 = 0
if staker != empty(address):
staked = convert(self.balanceOf[self.staker], int256)
supply: int256 = convert(self.totalSupply, int256)
# staked is guaranteed to be <= supply

f_a: int256 = convert(
10**18 - (10**18 - self._min_admin_fee()) * isqrt(convert(10**36 - staked * 10**36 // supply, uint256)) // 10**18,
int256)

amm_value: uint256 = _amm_value
if amm_value == 0:
amm_value = (staticcall self.amm.value_oracle()).value

cur_value: int256 = convert(amm_value * 10**18 // p_o, int256)
prev_value: int256 = convert(prev.total, int256)
value_change: int256 = cur_value - (prev_value + prev.admin)

v_st: int256 = convert(prev.staked, int256)
v_st_ideal: int256 = convert(prev.ideal_staked, int256)
# ideal_staked is set when some tokens are transferred to staker address

# Admin fees are earned only when all losses are paid off
dv_use_36: int256 = 0
v_st_loss: int256 = max(v_st_ideal - v_st, 0)
if staked >= MIN_STAKED_FOR_FEES:
if value_change > 0:
# Admin fee is only charged once the loss if fully paid off
v_loss: int256 = min(value_change, v_st_loss * supply // staked)
dv_use_36 = v_loss * 10**18 + (value_change - v_loss) * (10**18 - f_a)
else:
# Admin doesn't pay for value loss
dv_use_36 = value_change * 10**18
else:
# If staked part is small - positive admin fees are charged on profits and negative on losses
dv_use_36 = value_change * (10**18 - f_a)

prev.admin += (value_change - dv_use_36 // 10**18)

# dv_s is guaranteed to be <= dv_use
# if staked < supply (not exactly 100.0% staked) - dv_s is strictly < dv_use
dv_s_36: int256 = self.mul_div_signed(dv_use_36, staked, supply)
if dv_use_36 > 0:
dv_s_36 = min(dv_s_36, v_st_loss * 10**18)

# new_staked_value is guaranteed to be <= new_total_value
new_total_value_36: int256 = max(prev_value * 10**18 + dv_use_36, 0)
new_staked_value_36: int256 = max(v_st * 10**18 + dv_s_36, 0)

# Solution of:
# staked - token_reduction new_staked_value
# ------------------------- = -------------------
# supply - token_reduction new_total_value
#
# the result:
# new_total_value * staked - new_staked_value * supply
# token_reduction = ------------------------------------------------------
# new_total_value - new_staked_value

token_reduction: int256 = new_total_value_36 - new_staked_value_36 # Denominator
token_reduction = self.mul_div_signed(new_total_value_36, staked, token_reduction) - self.mul_div_signed(new_staked_value_36, supply, token_reduction)

max_token_reduction: int256 = abs(value_change * supply // (prev_value + value_change + 1) * (10**18 - f_a) // SQRT_MIN_UNSTAKED_FRACTION)

# let's leave at least 1 LP token for staked and for total
if staked > 0:
token_reduction = min(token_reduction, staked - 1)
if supply > 0:
token_reduction = min(token_reduction, supply - 1)
# But most likely it's this condition to apply
if token_reduction >= 0:
token_reduction = min(token_reduction, max_token_reduction)
else:
token_reduction = max(token_reduction, -max_token_reduction)
# And don't allow negatives if denominator was too small
if new_total_value_36 - new_staked_value_36 < 10**4 * 10**18:
token_reduction = max(token_reduction, 0)

return LiquidityValuesOut(
admin=prev.admin,
total=convert(new_total_value_36 // 10**18, uint256),
ideal_staked=prev.ideal_staked,
staked=convert(new_staked_value_36 // 10**18, uint256),
staked_tokens=convert(staked - token_reduction, uint256),
supply_tokens=convert(supply - token_reduction, uint256),
token_reduction=token_reduction
)

deposit()

Description:
Method to deposit assets (e.g., BTC) to receive shares (e.g., yield-bearing BTC). This function implements the ERC4626 deposit standard and handles the complex liquidity calculations.

Returns:
uint256 - Amount of shares minted to the receiver.

Emits:
Deposit - Deposit event with sender, owner, assets, and shares amounts.

InputTypeDescription
assetsuint256Amount of assets to deposit
debtuint256Amount of debt for AMM to take (approximately BTC * btc_price)
min_sharesuint256Minimal amount of shares to receive (slippage protection)
receiveraddressReceiver of the shares (defaults to msg.sender)
📄 View Source Code
@external
@nonreentrant
def deposit(assets: uint256, debt: uint256, min_shares: uint256, receiver: address = msg.sender) -> uint256:
staker: address = self.staker
assert receiver != staker, "Deposit to staker"

amm: LevAMM = self.amm
assert extcall STABLECOIN.transferFrom(amm.address, self, debt, default_return_value=True)
assert extcall ASSET_TOKEN.transferFrom(msg.sender, self, assets, default_return_value=True)
lp_tokens: uint256 = extcall CRYPTOPOOL.add_liquidity([debt, assets], 0, amm.address, False)
p_o: uint256 = self._price_oracle_w()

supply: uint256 = self.totalSupply
shares: uint256 = 0

liquidity_values: LiquidityValuesOut = empty(LiquidityValuesOut)
if supply > 0:
liquidity_values = self._calculate_values(p_o)

v: OraclizedValue = extcall amm._deposit(lp_tokens, debt)
value_after: uint256 = v.value * 10**18 // p_o

assert staticcall amm.max_debt() // 2 >= v.value, "Debt too high"

if supply > 0 and liquidity_values.total > 0:
supply = liquidity_values.supply_tokens
self.liquidity.admin = liquidity_values.admin
value_before: uint256 = liquidity_values.total
value_after = convert(convert(value_after, int256) - liquidity_values.admin, uint256)
self.liquidity.total = value_after
self.liquidity.staked = liquidity_values.staked
self.totalSupply = liquidity_values.supply_tokens
if staker != empty(address):
self.balanceOf[staker] = liquidity_values.staked_tokens
self._log_token_reduction(staker, liquidity_values.token_reduction)
shares = supply * value_after // value_before - supply
else:
shares = value_after
self.liquidity.ideal_staked = 0
self.liquidity.staked = 0
self.liquidity.total = shares + supply
self.liquidity.admin = 0
if self.balanceOf[staker] > 0:
log IERC20.Transfer(sender=staker, receiver=empty(address), value=self.balanceOf[staker])
self.balanceOf[staker] = 0

assert shares + supply >= MIN_SHARE_REMAINDER, "Remainder too small"
assert shares >= min_shares, "Slippage"

self._mint(receiver, shares)
self._checkpoint_gauge()
log Deposit(sender=msg.sender, owner=receiver, assets=assets, shares=shares)
self._distribute_borrower_fees(FEE_CLAIM_DISCOUNT)
return shares

withdraw()

Description:
Method to withdraw assets (e.g., BTC) by spending shares (e.g., yield-bearing BTC). This function implements the ERC4626 withdraw standard.

Returns:
uint256 - Amount of assets received by the receiver.

Emits:
Withdraw - Withdrawal event with sender, receiver, owner, assets, and shares amounts.

InputTypeDescription
sharesuint256Shares to withdraw
min_assetsuint256Minimal amount of assets to receive (slippage protection)
receiveraddressReceiver of the assets (defaults to msg.sender)
📄 View Source Code
@external
@nonreentrant
def withdraw(shares: uint256, min_assets: uint256, receiver: address = msg.sender) -> uint256:
assert shares > 0, "Withdrawing nothing"

staker: address = self.staker
assert staker not in [msg.sender, receiver], "Withdraw to/from staker"

assert not (staticcall self.amm.is_killed()), "We're dead. Use emergency_withdraw"

amm: LevAMM = self.amm
liquidity_values: LiquidityValuesOut = self._calculate_values(self._price_oracle_w())
supply: uint256 = liquidity_values.supply_tokens
self.liquidity.admin = liquidity_values.admin
self.liquidity.staked = liquidity_values.staked
self.totalSupply = supply

assert supply >= MIN_SHARE_REMAINDER + shares or supply == shares, "Remainder too small"

if staker != empty(address):
self.balanceOf[staker] = liquidity_values.staked_tokens
self._log_token_reduction(staker, liquidity_values.token_reduction)

admin_balance: uint256 = convert(max(liquidity_values.admin, 0), uint256)

withdrawn: Pair = extcall amm._withdraw(10**18 * liquidity_values.total // (liquidity_values.total + admin_balance) * shares // supply)
assert extcall CRYPTOPOOL.transferFrom(amm.address, self, withdrawn.collateral, default_return_value=True)
crypto_received: uint256 = extcall CRYPTOPOOL.remove_liquidity_fixed_out(withdrawn.collateral, 0, withdrawn.debt, 0)

self._burn(msg.sender, shares)
self.liquidity.total = liquidity_values.total * (supply - shares) // supply
if liquidity_values.admin < 0:
self.liquidity.admin = liquidity_values.admin * convert(supply - shares, int256) // convert(supply, int256)
assert crypto_received >= min_assets, "Slippage"
assert extcall STABLECOIN.transfer(amm.address, withdrawn.debt, default_return_value=True)
assert extcall ASSET_TOKEN.transfer(receiver, crypto_received, default_return_value=True)

self._checkpoint_gauge()
log Withdraw(sender=msg.sender, receiver=receiver, owner=msg.sender, assets=crypto_received, shares=shares)
self._distribute_borrower_fees(FEE_CLAIM_DISCOUNT)
return crypto_received

preview_deposit()

Description:
Returns the amount of shares which can be obtained upon depositing assets, including slippage. This is a view function that simulates the deposit without executing it.

Returns:
uint256 - Amount of shares that would be minted for the given assets and debt.

InputTypeDescription
assetsuint256Amount of crypto to deposit
debtuint256Amount of stables to borrow for MMing (approx same value as crypto)
raise_overflowboolIf True (default), reverts with "Debt too high" when projected debt would exceed AMM.max_debt() / 2. If False, returns 0 instead — used by LTMigrator and HybridVault for soft quotes.
📄 View Source Code
@external
@view
@nonreentrant
def preview_deposit(assets: uint256, debt: uint256, raise_overflow: bool = True) -> uint256:
lp_tokens: uint256 = staticcall CRYPTOPOOL.calc_token_amount([debt, assets], True)
supply: uint256 = self.totalSupply
p_o: uint256 = self._price_oracle()
amm: LevAMM = self.amm
amm_max_debt: uint256 = staticcall amm.max_debt() // 2

if supply > 0:
liquidity: LiquidityValuesOut = self._calculate_values(p_o)
if liquidity.total > 0:
v: OraclizedValue = staticcall amm.value_change(lp_tokens, debt, True)
if raise_overflow:
if amm_max_debt < v.value:
raise "Debt too high"
value_after: uint256 = convert(convert(v.value * 10**18 // p_o, int256) - liquidity.admin, uint256)
return liquidity.supply_tokens * value_after // liquidity.total - liquidity.supply_tokens

v: OraclizedValue = staticcall amm.value_oracle_for(lp_tokens, debt)
if raise_overflow:
if amm_max_debt < v.value:
raise "Debt too high"
return v.value * 10**18 // p_o

preview_withdraw()

Description:
Returns the amount of assets which can be obtained upon withdrawing from tokens. This is a view function that simulates the withdrawal without executing it.

Returns:
uint256 - Amount of assets that would be received for the given shares.

InputTypeDescription
tokensuint256Amount of shares to withdraw
📄 View Source Code
@external
@view
@nonreentrant
def preview_withdraw(tokens: uint256) -> uint256:
v: LiquidityValuesOut = self._calculate_values(self._price_oracle())
state: AMMState = staticcall self.amm.get_state()
admin_balance: uint256 = convert(max(v.admin, 0), uint256)
frac: uint256 = 10**18 * v.total // (v.total + admin_balance) * tokens // v.supply_tokens
withdrawn_lp: uint256 = state.collateral * frac // 10**18
withdrawn_debt: uint256 = state.debt * frac // 10**18
return staticcall CRYPTOPOOL.calc_withdraw_fixed_out(withdrawn_lp, 0, withdrawn_debt)

set_amm()

Description:
Sets the AMM contract address. This function can only be called once and only by the admin. It also sets the price aggregator from the AMM's price oracle. AMM can only be changed by the admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

InputTypeDescription
ammLevAMMAMM contract address
📄 View Source Code
@external
@nonreentrant
def set_amm(amm: LevAMM):
self._check_admin()
assert self.amm == empty(LevAMM), "Already set"
assert staticcall amm.STABLECOIN() == STABLECOIN.address
assert staticcall amm.COLLATERAL() == CRYPTOPOOL.address
assert staticcall amm.LT_CONTRACT() == self
self.amm = amm
self.agg = PriceOracle(staticcall (staticcall amm.PRICE_ORACLE_CONTRACT()).AGG())

set_admin()

Description:
Sets a new admin address. This function can only be called by the current admin. It performs sanity checks if the new admin is a contract. New admin can only be changed by the current admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

Emits:
SetAdmin - Admin change event with new admin address.

InputTypeDescription
new_adminaddressNew admin address
📄 View Source Code
@external
@nonreentrant
def set_admin(new_admin: address):
self._check_admin()

if new_admin.is_contract:
check_address: address = staticcall Factory(new_admin).admin()
check_address = staticcall Factory(new_admin).emergency_admin()
check_address = staticcall Factory(new_admin).fee_receiver()
check_uint: uint256 = staticcall Factory(new_admin).min_admin_fee()

self.admin = new_admin
log SetAdmin(admin=new_admin)

set_rate()

Description:
Sets the interest rate in the AMM. This function can only be called by the admin and forwards the call to the AMM contract. Interest rate can only be changed by the admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

InputTypeDescription
rateuint256New interest rate
📄 View Source Code
@external
@nonreentrant
def set_rate(rate: uint256):
self._check_admin()
extcall self.amm.set_rate(rate)

set_amm_fee()

Description:
Sets the trading fee in the AMM. This function can only be called by the admin and forwards the call to the AMM contract. AMM fee can only be changed by the admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

InputTypeDescription
feeuint256New trading fee
📄 View Source Code
@external
@nonreentrant
def set_amm_fee(fee: uint256):
self._check_admin()
extcall self.amm.set_fee(fee)

allocate_stablecoins()

Description:
Allocates stablecoins to the AMM for this pool. This method must be used once the contract has received allocation of stablecoins. Allocation can only be handled by the admin of this contract or the admin of the Factory.

Returns:
No return value (void function).

Emits:
AllocateStablecoins - Allocation event with allocator address, allocation amount, and allocated amount.

InputTypeDescription
limituint256Limit to allocate for this pool (max uint256 = do not change)
📄 View Source Code
@external
@nonreentrant
def allocate_stablecoins(limit: uint256 = max_value(uint256)):
allocator: address = self.admin
allocation: uint256 = limit
allocated: uint256 = self.stablecoin_allocated
if limit == max_value(uint256):
allocation = self.stablecoin_allocation
else:
self._check_admin()
self.stablecoin_allocation = limit

extcall self.amm.check_nonreentrant()

if allocation > allocated:
# Bring in only what the allocator can actually transfer
to_transfer: uint256 = min(allocation - allocated, staticcall STABLECOIN.balanceOf(allocator))
assert extcall STABLECOIN.transferFrom(allocator, self.amm.address, to_transfer, default_return_value=True)
allocated += to_transfer
self.stablecoin_allocated = allocated

elif allocation < allocated:
lp_price: uint256 = extcall (staticcall self.amm.PRICE_ORACLE_CONTRACT()).price_w()
# Safe lower limit for deflating the market: 3/4 of LP-priced collateral value.
# Equilibrium leaves a 50% surplus (coefficient 1); the edge case is 1/2; 3/4
# is the conservative cap that still lets governance deallocate.
safe_lower_limit: uint256 = lp_price * (staticcall self.amm.collateral_amount()) * 3 // 4 // 10**18
to_transfer: uint256 = min(
min(allocated - allocation, allocated - safe_lower_limit),
staticcall STABLECOIN.balanceOf(self.amm.address))
allocated -= to_transfer
assert extcall STABLECOIN.transferFrom(self.amm.address, allocator, to_transfer, default_return_value=True)
self.stablecoin_allocated = allocated

log AllocateStablecoins(allocator=allocator, stablecoin_allocation=allocation, stablecoin_allocated=allocated)

pricePerShare()

Description:
Non-manipulatable "fair price per share" oracle of the yb-asset token.

Returns:
uint256 - Price per share in 1e18 units.

📄 View Source Code
@external
@view
def pricePerShare() -> uint256:
# Short-circuit BEFORE the heavy _calculate_values call when supply is empty.
if self.totalSupply == 0:
return 10**18
else:
v: LiquidityValuesOut = self._calculate_values(self._price_oracle())
return v.total * 10**18 // v.supply_tokens

symbol()

Description:
Returns the token symbol in the format 'yb-asset'. This implements the ERC20 symbol standard.

Returns:
String[32] - Token symbol (e.g., "yb-BTC").

📄 View Source Code
@external
@view
def symbol() -> String[32]:
return concat('yb-', staticcall IERC20Slice(ASSET_TOKEN.address).symbol())

name()

Description:
Returns the token name in the format 'Yield Basis liquidity for asset'. This implements the ERC20 name standard.

Returns:
String[58] - Token name (e.g., "Yield Basis liquidity for BTC").

📄 View Source Code
@external
@view
def name() -> String[58]:
return concat('Yield Basis liquidity for ', staticcall IERC20Slice(ASSET_TOKEN.address).symbol())

emergency_withdraw() and preview_emergency_withdraw()

Description: The only exit path while the AMM is killed (We're dead. Use emergency_withdraw). Bypasses the oracle-driven _calculate_values rebase: the caller settles debt from their own wallet and the AMM hands back its raw (collateral, debt) pair. Convexity of the bonding curve ensures the worst an attacker can do is lose value, so no min_* slippage parameter is exposed.

Three auth branches, distinguished by whether admin is a contract (factory) and whether the AMM is currently killed:

Callerreceiver constraintWhen allowed
owner of the positionanyAlways — full self-service exit.
emergency_admin (factory only)receiver == ownerWhile AMM is killed OR value_oracle() reverts. Can sweep stuck positions but must send proceeds to the position owner.
admin (factory only)receiver == ownerSame rules as emergency_admin.
Any other contract callerReverts "owner" unless the caller IS the owner.
Any caller while AMM is liveReverts "Not killed". Use the normal withdraw path.

The return tuple is (asset_out, stables_to_return). If stables_to_return < 0, the contract pulls that much crvUSD from msg.sender via transferFrom to settle the debt leg — approve crvUSD to the LT before calling.

preview_emergency_withdraw(shares) -> (uint256, int256) is the matching view. It does not enforce any auth check — quote the position from any address.

Returns:

(asset_received, stables_to_return): int256 on the second leg so the contract can signal that the caller must bring stables (negative value) versus that they will receive stables back (positive).

InputTypeDescription
sharesuint256yb-LP shares to redeem
receiveraddressDestination for the asset leg (and stables leg if positive). Default msg.sender.
owneraddressPosition owner whose shares are burned. Default msg.sender.

withdraw_admin_fees()

Description: Permissionless trigger that materialises pending admin fees as yb-LP shares and mints them to the factory's fee_receiver. Anyone can call it as long as LT.admin resolves to a factory contract (admin.is_contract must be true and Factory(admin).fee_receiver() must be set). Keepers and MEV searchers run this on a schedule to keep the FeeDistributor topped up.

Mechanism:

  1. Runs _calculate_values(self._price_oracle_w()) to refresh buckets — admin must be non-negative (Loss made admin fee negative).
  2. Computes to_mint = supply_tokens × (total + admin) / total − supply_tokens.
  3. Mints to_mint yb-LP to fee_receiver, sets liquidity.admin = 0, and updates liquidity.total accordingly.
  4. Emits WithdrawAdminFees(receiver, amount).

Reverts:

StringCause
Need factoryadmin is an EOA (no fee_receiver).
KilledAMM is killed — wait for unkill or use emergency_withdraw.
No fee_receiverFactory exists but fee_receiver is the zero address.
Staker=fee_receiverSanity check: cannot mint to the staker contract.
Loss made admin fee negativeAdmin bucket is currently in deficit. Wait for fees to recover.

No min_amount or min_assets parameter — the mint is deterministic from the current bucket state.


updated_balances()

Description: View helper used by LiquidityGauge._total_assets() to compute its strict ERC-4626 totalAssets(). Runs _calculate_values against the current oracle without writing.

Returns:

(supply_tokens, staked_tokens) — post-rebase supply and the staker's post-rebase share count. When totalSupply == 0, returns (0, 0).

Used by portfolio trackers and LP-as-collateral integrations that read gauge balances and need to translate them back to LT-share equivalents.

@external
@view
def updated_balances() -> (uint256, uint256):
if self.totalSupply > 0:
lv: LiquidityValuesOut = self._calculate_values(self._price_oracle())
return (lv.supply_tokens, lv.staked_tokens)
else:
return (0, 0)

distribute_borrower_fees()

Description: Permissionless. Sweeps accrued borrower interest out of LEVAMM and donates the resulting crvUSD into the underlying Cryptoswap pool via add_liquidity([amount, 0], min_amount, empty(address), donation=True). The donation flows into the pool's donation-protected buffer and unlocks over the pool's donation window rather than minting fresh LP tokens to a user.

Triggered automatically at the end of every deposit and withdraw with the default FEE_CLAIM_DISCOUNT (1%). External callers can run it more often than that — useful when the pool's reserve is thin and the operator wants to top it up immediately rather than wait for the next user op.

The discount parameter caps how much price-scale movement is acceptable on the Cryptoswap side: min_amount = (1e18 - discount) × crvusd_amount / CRYPTOPOOL.lp_price(). Larger discounts allow the donation to land even when the pool is mid-rebalance. Only admin can pass a discount greater than the default 1%.

Reverts: the underlying Cryptoswap add_liquidity reverts (typically Slippage) when the donation cannot meet min_amount. Try again with a larger discount, or wait for the pool to settle.

Emits DistributeBorrowerFees(sender, amount, min_amount, discount).


Admin-only setters (governance)

These are called only by the LT's admin — either an EOA owner or (via _check_admin) a factory contract whose admin() matches the caller. Integrators usually do not call these directly; documented here for governance audit and indexing.

FunctionEffectEmits
set_amm(amm)One-shot. Sets the AMM contract address; rejects subsequent calls with "Already set".
set_admin(new_admin)Transfers admin to a new EOA or factory. Sanity-checks the candidate's admin/emergency_admin/fee_receiver/gauge_controller/min_admin_fee getters if it is a contract.SetAdmin
set_rate(rate)Forwards to AMM.set_rate. Bounded by MAX_RATE (100% APR).SetRate (from AMM)
set_amm_fee(fee)Forwards to AMM.set_fee. Bounded by MAX_FEE (10%).SetFee (from AMM)
set_staker(staker)One-shot. Wires the LiquidityGauge address. Any pre-existing balance at the staker address is swept to fee_receiver.SetStaker
set_killed(is_killed)Kills or unkills the AMM. Callable by LT admin, factory admin, or emergency_admin.SetKilled (from AMM)

Other public views

FunctionReturns
is_killed()True if the AMM is currently killed (proxies AMM.is_killed). Use this before deciding whether to call withdraw versus emergency_withdraw.
min_admin_fee()Current minimum admin-fee floor (Factory.min_admin_fee() when admin is a factory, else 0).
checkpoint_staker_rebase()Only meaningful when called by the staker contract (LiquidityGauge does this before every gauge op). Forces a _calculate_values write so subsequent ops see fresh buckets. Silent no-op for other callers.