Skip to main content

LT

Overview​

LT is the market's vault contract and the share token of the market. This page is the function reference for integrators and auditors who call LT directly. You deposit the asset token, and LT mints yb-LP shares to you. This page calls them LT shares. LT shares are an ERC-20 token with 18 decimals and the symbol yb- plus the asset symbol (for example, yb-WBTC).

This page describes the current LT source. Markets 7 to 10 run this source. Markets 0 to 6 run older LT code. Where the behavior differs, this page says so.

LT works with these contracts of its market:

  • The LEVAMM (contract AMM), the market's leveraged AMM. It holds Curve pool liquidity provider (LP) tokens as collateral and crvUSD as debt. The LEVAMM book is its current debt (AMM.get_debt()) and its LP collateral (AMM.collateral_amount()).
  • The Curve pool (CRYPTOPOOL), where LT adds and removes liquidity.
  • The gauge of the market (LiquidityGauge, stored in staker). You stake LT shares in the gauge to earn YB emissions.
  • The Factory, which deploys LT and is its admin.

Most integrators need only these functions:

FunctionUse it to
deposit(assets, debt, min_shares, receiver)Deposit the asset and receive LT shares. Compute debt first: from the LEVAMM book on a pool with a live (not killed) YBOraclizedPolicy, or from the asset price (assets * pool.price_oracle() // 10**asset_decimals) on other pools.
preview_deposit(assets, debt)Quote the LT shares that a deposit mints. It does not run the LT deviation check.
withdraw(shares, min_assets, receiver)Burn LT shares and receive the asset.
preview_withdraw(tokens)Quote what a withdrawal returns now (Redemption Value).
pricePerShare()Read the oracle-based value of one LT share (Fundamental Value).
is_killed()Choose between withdraw() and emergency_withdraw().
emergency_withdraw(shares, receiver, owner)Exit when withdraw() is not available.

For Fundamental Value and Redemption Value, read Fundamental Value, Redemption Value, and TRD.

LT is also a standard ERC-20 token (balanceOf, approve, transfer). To stake LT shares, deposit them in the gauge of the market. For the gauge functions, read LiquidityGauge.

When you deposit the asset token, LT takes a matching amount of crvUSD from the LEVAMM allocation. LT adds both tokens to the Curve pool and gives the LP tokens to the LEVAMM. You receive LT shares. Each share is a pro rata claim on the value of the LEVAMM position.

A withdrawal burns LT shares. LT takes the LP tokens and debt out of the LEVAMM, removes the liquidity from the Curve pool, and sends the asset to the receiver. Deposits and withdrawals have slippage limits. pricePerShare() gives an oracle-based value per share.

LT also manages its market. It owns the link to the LEVAMM and forwards rate and fee changes to the LEVAMM. It manages the crvUSD allocation of the LEVAMM (allocate_stablecoins). It donates the borrower interest that the AMM collects to the Curve pool (distribute_borrower_fees). The donation mints no LP tokens to a user.

The admin can set an optional gauge address (staker) one time. LT tracks admin fees and mints them as LT shares to the fee receiver of the protocol.

LT has these safety controls:

  • Access controls (admin and factory admins).
  • Non-reentrancy on state-changing entry points.
  • Kill and unkill passthrough to the LEVAMM.
  • Oracle-based checks that keep the position within safe bounds.

The Factory deploys each LT from the lt_impl blueprint with create_from_blueprint(lt_impl, asset_token, STABLECOIN, pool, factory). The Factory is the LT admin. The Factory then connects the LT to the market LEVAMM and price oracle.

The LT price oracle is CRYPTOPOOL.price_scale() · agg.price() / 1e18 (_price_oracle()). price_scale is the Curve pool's internal price, and agg is the crvUSD price aggregator. Markets 0 to 2 were deployed from an earlier LT blueprint whose _price_oracle() reads the pool's price_oracle(). The LT of markets 3 to 10 reads price_scale(). On Twocrypto v3.0.0 pools (markets 7 to 10), a pool policy can steer price_scale. So a change in price_scale changes share values. For how a policy sets the price_scale target, read Pool policy.


Approvals​

You give LT an approval before each call that pulls tokens from you:

  • Before deposit(), approve LT to spend assets of the asset token.
  • Before emergency_withdraw(), approve crvUSD to LT. The call pulls crvUSD from you when the crvUSD that the exit returns is less than your share of the debt.

Public variables​

LT has these public variables for contract state and configuration.

Public constant

  • decimals (uint8) - Token decimals (18)

Constants (internal, no getter)

These constants are not public. A call such as lt.MIN_STAKED_FOR_FEES() reverts. LT has these internal constants:

  • CRYPTOPOOL_N_COINS (uint256) - Number of coins in the Curve 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) - Declared value 10**14. _calculate_values uses it to cap the per-step token_reduction when the gauge holds almost all LT shares. token_reduction is the staker rebase. The staker rebase changes the LT share balance of the gauge and the total supply. After the rebase, the staked fraction of the supply matches the staked fraction of the value.
  • MIN_STAKED_FOR_FEES (int256) - 10^16 share units (0.01 LT shares). If the gauge holds fewer LT shares, admin fees apply to profit and to loss.

Immutable variables

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

State variables

  • admin (address) - Admin address
  • amm (LevAMM) - LEVAMM contract address
  • agg (PriceOracle) - Price aggregator contract
  • staker (address) - Gauge (LiquidityGauge) contract address
  • liquidity (LiquidityValues) - Liquidity state struct
  • allowance (HashMap[address, HashMap[address, uint256]]) - ERC-20 allowances
  • balanceOf (HashMap[address, uint256]) - ERC-20 balances
  • totalSupply (uint256) - Total supply of LT shares
  • stablecoin_allocation (uint256) - crvUSD allocation limit
  • stablecoin_allocated (uint256) - crvUSD that LT has moved to the LEVAMM

Data structs​

LT uses these structs.

AMMState

  • collateral (uint256) - LP token amount held as collateral
  • debt (uint256) - crvUSD debt with accrued interest
  • x0 (uint256) - Virtual balance for maintaining 2× leverage

Pair

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

OraclizedValue

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

LiquidityValues: the liquidity() getter returns this struct. Each value is in asset units with 18 decimals, whatever the decimals of the asset.

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

LiquidityValuesOut

  • admin (int256) - Admin fees (can be negative)
  • total (uint256) - Total position value
  • ideal_staked (uint256) - Ideal staked amount (the watermark)
  • 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 (the staker rebase)

Events​

LT emits these events.

SetStaker (set_staker() emits it)

  • staker (indexed address) - New gauge contract address

WithdrawAdminFees (withdraw_admin_fees() emits it)

  • receiver (address) - Address that received the minted LT shares (the Factory fee_receiver)
  • amount (uint256) - Amount of LT shares minted to the receiver

AllocateStablecoins (allocate_stablecoins() emits it)

  • allocator (indexed address) - Address that allocated crvUSD
  • stablecoin_allocation (uint256) - Total allocation limit
  • stablecoin_allocated (uint256) - Allocated amount after the call

DistributeBorrowerFees (LT emits it when it donates crvUSD to the Curve pool)

  • sender (indexed address) - msg.sender of the call. It is the LEVAMM after a swap, the depositor or withdrawer after deposit() or withdraw(), or the external caller.
  • amount (uint256) - Amount of crvUSD donated into the Curve 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 (deposit() emits it)

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

Withdraw (withdraw() emits it. emergency_withdraw() does not.)

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

SetAdmin (set_admin() emits it)

  • admin (address) - New admin address

Functions​

A function section lists the main revert strings of that function. For every LT revert string in one table, read Error codes: LT.vy.

deposit()​

Deposits the asset token and mints LT shares to receiver. The function uses the ERC-4626 event names. Its signature is not ERC-4626: it takes debt and min_shares. LT has no asset(), totalAssets(), mint(), or redeem().

Before the call, approve LT to spend assets of the asset token.

Debt on a pool that runs the oracle-report policy

If you calculate debt yourself, make it proportional to the LEVAMM book. If you do not, the deposit reverts with "LT deviation".

  1. Read AMM.get_debt() and AMM.collateral_amount().
  2. Calculate lp_tokens = CRYPTOPOOL.calc_token_amount([debt, assets], True).
  3. Set debt = lp_tokens · AMM.get_debt() / AMM.collateral_amount().
  4. Do steps 2 and 3 again until the change in debt is much less than ratio_tolerance.
  5. Send the deposit in the next transaction that you can. The LEVAMM book changes with each operation.

Do not use assets · price_oracle as debt on a pool that runs this policy. This value can differ from the book ratio by more than ratio_tolerance.

For the full procedure and a callable solver, read Compute debt on a pool that runs the oracle-report policy.

An LT deposit or withdrawal pays half the pool fee rate on the imbalanced part. preview_deposit and preview_withdraw include this fee. For the fee rule and the spam penalty, read get_fee in Pool policy.

The function returns the number of LT shares minted to receiver (uint256). It emits Deposit with the sender, owner, assets, and shares amounts.

The function takes these inputs:

InputTypeDescription
assetsuint256Amount of the asset token to deposit, in raw units at the asset's decimals
debtuint256Amount of crvUSD that the LEVAMM lends for this deposit, in raw units at 18 decimals. On a pool that runs a live YBOraclizedPolicy, make it proportional to the LEVAMM book: debt ≈ lp_tokens · AMM.get_debt() / AMM.collateral_amount(). The caution at the start of this section gives the steps. On other pools, use the price rule, assets * pool.price_oracle() // 10**asset_decimals.
min_sharesuint256Minimum LT shares to receive (slippage protection)
receiveraddressReceiver of the LT shares (defaults to msg.sender)
Contract source: deposit()

The contract source gives this function body:

@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

deposit() can revert with these strings:

StringRaised byCondition
Deposit to stakerLT.depositreceiver is the gauge (staker) address.
Debt too highLT.depositAfter the deposit, the LEVAMM value is more than AMM.max_debt() / 2.
Unsafe min, Unsafe maxThe LEVAMM (AMM._deposit())The LEVAMM debt-to-collateral ratio leaves its safe bounds.
Remainder too smallLT.depositThe total supply after the deposit is less than 10^6 share units.
SlippageLT.depositshares < min_shares.
LT deviationThe pool policy, inside add_liquidityOnly on a pool that runs the oracle-report policy YBOraclizedPolicy. For the condition, read LT deviation check.
slippage, donation above cap!The Curve poolThe borrower-fee donation at the end of the call fails in the Curve pool. For the causes, read distribute_borrower_fees().
GASLT._checkpoint_gaugeThe checkpoint call to the GaugeController fails, and less than 200,000 gas remains for the fixed-gas retry.

LT deviation check​

A live policy is one that is not killed. On a pool with a live oracle-report policy (YBOraclizedPolicy), the policy checks the debt ratio of each LT deposit. On such a pool, a deposit reverts with LT deviation if its added debt does not match the LEVAMM debt-to-collateral ratio within ratio_tolerance. ratio_tolerance is a relative tolerance (1e18 = 100%). To read it, call config() on the policy.

Each policy contract binds one pool and the LEVAMM of its market. To check the binding, call POOL() and AMM() on the policy. To see which policy a pool runs, call POLICY() on the pool or read the table in Contract addresses.

preview_deposit() does not run this check. A deposit can pass preview_deposit() and then revert in the pool.

For the check steps, the formula, and the exemptions, read Pool callback and LT deviation check in Pool policy. For a flowchart that chooses the debt rule, read Choose the debt rule in Deposit and withdraw.


preview_deposit()​

Returns the number of LT shares that a deposit of assets and debt mints, including slippage (uint256). The view does not run the LT deviation check of a pool policy.

The function takes these inputs:

InputTypeDescription
assetsuint256Amount of the asset token to deposit
debtuint256Amount of crvUSD that the LEVAMM lends for the deposit
raise_overflowboolIf True (default), reverts with "Debt too high" when the LEVAMM value after the deposit is more than AMM.max_debt() / 2. If False, the function does not do this check and returns the share count. LTMigrator and HybridVault use False.
Cap check when raise_overflow is False

If you call preview_deposit() with raise_overflow = False, do the cap check yourself. Compare AMM.max_debt() // 2 with AMM.value_change(lp_tokens, debt, True).value. If the value is larger, deposit() reverts with "Debt too high".

Contract source: preview_deposit()

The contract source gives this function body:

@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_deposit() can revert with this string:

StringRaised byCondition
Debt too highLT.preview_depositraise_overflow is True, and the LEVAMM value after the deposit is more than AMM.max_debt() / 2.

withdraw()​

Burns LT shares and sends the asset token to receiver. The function uses the ERC-4626 event names. Its signature is not ERC-4626.

The function returns the amount of the asset token that receiver gets (uint256), in raw units at the asset's decimals. It emits Withdraw with the sender, receiver, owner, assets, and shares amounts.

The function takes these inputs:

InputTypeDescription
sharesuint256LT shares to withdraw
min_assetsuint256Minimum amount of the asset token to receive (slippage protection)
receiveraddressReceiver of the asset token (defaults to msg.sender)
Contract source: withdraw()

The contract source gives this function body:

@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

withdraw() can revert with these strings:

StringRaised byCondition
Withdrawing nothingLT.withdrawshares == 0.
Withdraw to/from stakerLT.withdrawThe caller or receiver is the gauge.
We're dead. Use emergency_withdrawLT.withdrawThe LEVAMM is killed.
Remainder too smallLT.withdrawThe LT supply after the withdrawal is below MIN_SHARE_REMAINDER (10^6) and not 0.
SlippageLT.withdrawcrypto_received < min_assets.
slippage, donation above cap!The Curve poolThe borrower-fee donation at the end of the call fails. For the causes, read distribute_borrower_fees().
GASLT._checkpoint_gaugeThe checkpoint call to the GaugeController fails, and less than 200,000 gas remains for the fixed-gas retry.

preview_withdraw()​

Returns the amount of the asset token that a withdrawal of tokens LT shares gives now (Redemption Value). The view simulates the withdrawal and does not execute it.

The function returns the amount in raw asset-token units (uint256). The unit has the decimals of the asset, for example 8 for cbBTC. This is the unit that withdraw() transfers.

The function takes this input:

InputTypeDescription
tokensuint256Amount of LT shares to withdraw, in share units (18 decimals)

For example, on a cbBTC market (8 decimals), a return of 101_796_000 from preview_withdraw(10**18) means that one LT share redeems for 1.01796 cbBTC.

The Temporary Redemption Discount (TRD) is the signed gap between Redemption Value (preview_withdraw) and Fundamental Value (pricePerShare()). A negative TRD means that a withdrawal now returns less. pricePerShare() is 1e18-based whatever the decimals of the asset. So normalize the decimals before you compare the two values. For the scaling formula and a converted example, read preview_withdraw and pricePerShare in Deposit and withdraw. For what TRD means for a position, read Fundamental Value, Redemption Value, and TRD.

Contract source: preview_withdraw()

The contract source gives this function body:

@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)

pricePerShare()​

Returns the oracle-based value of one LT share (Fundamental Value). It does not read pool spot balances. It reads price_scale from the pool, which a pool policy can steer on v3.0.0 pools. Markets 0 to 2 run an earlier LT blueprint that reads price_oracle instead.

The function returns the value of one share in asset-token units, 1e18-based (uint256). LT converts the LEVAMM value with CRYPTOPOOL.price_scale() · agg.price() / 1e18. The result is 1e18-based whatever the decimals of the asset: 10**18 means one asset token per share. When totalSupply is 0, the function returns 10**18.

For example, on a cbBTC market, pricePerShare() returns 1_020_000_000_000_000_000. One yb-cbBTC share has a Fundamental Value of 1.02 cbBTC. preview_withdraw() returns raw asset units. For how to compare the two values, read preview_withdraw and pricePerShare in Deposit and withdraw. For figures of what moves price_scale and how pricePerShare() and preview_withdraw() read it, read Price computation in CryptopoolLPOracle.

Contract source: pricePerShare()

The contract source gives this function body:

@external
@view
def pricePerShare() -> uint256:
if self.totalSupply == 0:
return 10**18
else:
v: LiquidityValuesOut = self._calculate_values(self._price_oracle())
return v.total * 10**18 // v.supply_tokens

emergency_withdraw()​

The exit path when withdraw() is not available. On markets 7 to 10, this is while the AMM is killed (We're dead. Use emergency_withdraw) or value_oracle() reverts. On markets 0 to 6, only a killed AMM opens this path. While the AMM is live, the function calls value_oracle() and reverts if the oracle reverts. The owner can also use it while the AMM is live.

Market states and allowed calls​

For these calls, a market is in one of three states. The following figure shows the states and what moves a market between them:

LT market states: inside the not-killed state, a market is live while value_oracle() succeeds and moves to a value_oracle()-reverts condition when the LP price oracle or the LEVAMM math fails, and back when the failure stops. LT.set_killed(True) moves a market to the killed state, and LT.set_killed(False) moves it back.LT market states: inside the not-killed state, a market is live while value_oracle() succeeds and moves to a value_oracle()-reverts condition when the LP price oracle or the LEVAMM math fails, and back when the failure stops. LT.set_killed(True) moves a market to the killed state, and LT.set_killed(False) moves it back.

A market that is not killed is live while value_oracle() succeeds. No call sets the second state: value_oracle() starts to revert when the LP price oracle or the LEVAMM math fails, and the state ends when the failure stops. Only the kill is stored. Factory.admin() or Factory.emergency_admin() kills or revives the market with LT.set_killed.

The following table gives the result of each call in each state, for the current LT source. A staked holder cannot exit while value_oracle() reverts on a market that is not killed: every gauge call reverts, so the LT shares stay in the gauge until the oracle works again or the market is killed.

CallLivevalue_oracle() revertsKilled
depositWorksRevertsReverts in AMM._deposit (no message)
withdrawWorksRevertsReverts with We're dead. Use emergency_withdraw
emergency_withdrawOnly the owner, else Not killedWorks for unstaked LT shares: the owner, or Factory.emergency_admin() for any owner with receiver = owner. Checkpoints the gauge.The same callers. No gauge checkpoint.
withdraw_admin_feesWorksRevertsReverts with Killed
pricePerShare, preview_deposit, preview_withdraw, updated_balancesWorkRevertWork only while value_oracle() succeeds. These views do not check the kill.
preview_emergency_withdrawWorksRevertsWorks without value_oracle()
Gauge deposit, mint, withdraw, and redeem, and LT transfers to or from the gaugeWorkRevertWork, without the staker rebase. In redeem, Factory.emergency_admin() can act for any holder with receiver = owner.
HybridVault.unstakeWorksRevertsWorks
checkpoint_staker_rebase (gauge caller)Updates the bucketsRevertsDoes nothing
distribute_borrower_feesWorksDoes not read value_oracle(). It fails only if a pool call fails.Reverts in AMM.collect_fees (no message)

emergency_withdraw() takes a share of the LEVAMM (collateral, debt) pair and removes the LP from the Curve pool in balance. The caller settles any crvUSD shortfall from the wallet.

When the LEVAMM is killed or value_oracle() reverts, the function does not run _calculate_values. When the LEVAMM is live and value_oracle() succeeds, it runs _calculate_values first, as withdraw() does. On markets 0 to 6, read "killed" for "killed, or value_oracle() reverts" in this section and in the table. The function has no minimum-output parameter. The NatSpec states that the convexity of the bonding curves lets an attacker only lose value.

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

The rules for the caller depend on the LEVAMM state and on the type of the LT admin. The production LT admin is the Factory (a contract). The admin can also be an externally owned account (EOA). This table gives the rules for each caller:

CallerLEVAMM statereceiverResult
owner of the positionlive, value_oracle() succeedsanyAllowed.
Not the ownerlive, value_oracle() succeeds—Reverts with "Not killed".
Factory.emergency_admin() (LT admin is a contract)killed, or value_oracle() revertsmust be owner, else reverts with "receiver"Allowed for any owner. Proceeds go to the owner.
LT admin (only when the admin is an EOA)killed, or value_oracle() revertsmust be owner, else reverts with "receiver"Allowed for any owner.
Any other callerkilled, or value_oracle() revertsanyAllowed only if owner == msg.sender. Else reverts with "owner".

When the LT admin is the Factory, the Factory admin cannot act for other owners. Only Factory.emergency_admin() can.

The function emits no Withdraw event. It checkpoints the gauge only when the LEVAMM is not killed.

The function takes these inputs:

InputTypeDescription
sharesuint256LT shares to redeem
receiveraddressDestination for the asset leg (and the crvUSD leg if positive). Default msg.sender.
owneraddressPosition owner whose LT shares are burned. Default msg.sender.

emergency_withdraw() can revert with these strings:

StringRaised byCondition
Not killedLT.emergency_withdrawThe LEVAMM is live, value_oracle() succeeds, and the caller is not owner.
receiverLT.emergency_withdrawFactory.emergency_admin() (LT admin is a contract) or the LT admin (an EOA) calls with a receiver other than owner, while the LEVAMM is killed or value_oracle() reverts.
ownerLT.emergency_withdrawAny other caller calls with an owner other than msg.sender, while the LEVAMM is killed or value_oracle() reverts.
Withdraw to/from stakerLT.emergency_withdrawowner or receiver is the gauge.
Remainder too smallLT.emergency_withdrawThe LT supply after the withdrawal is below MIN_SHARE_REMAINDER (10^6) and not 0.
GASLT._checkpoint_gaugeThe LEVAMM is not killed, the checkpoint call to the GaugeController fails, and less than 200,000 gas remains for the fixed-gas retry.

preview_emergency_withdraw()​

preview_emergency_withdraw(shares) -> (uint256, int256) is the matching view of emergency_withdraw(). It does not check the caller. You can quote the position from any address. While the LEVAMM is not killed, the view runs _calculate_values, so it reverts while value_oracle() reverts. After a kill, it skips _calculate_values and quotes without the oracle.

The function returns (asset_received, stables_to_return). The second value is int256. A negative value means that the caller must bring crvUSD. A positive value means that the caller receives crvUSD back.

The function takes this input:

InputTypeDescription
sharesuint256LT shares to redeem

distribute_borrower_fees()​

Anyone can call this function. When the caller is not the LEVAMM, the function first collects borrower interest from the LEVAMM. Then it donates the full crvUSD balance of LT to the Curve pool with add_liquidity([amount, 0], min_amount, empty(address), True). amount is the full crvUSD balance of LT, not only the collected interest. The donation mints no LP tokens to a user.

The pool releases donation shares over its donation_duration. The pool constructor sets 7 days, and governance can change it. To read the value, call donation_duration() on the pool.

deposit() and withdraw() call this function at the end with the default FEE_CLAIM_DISCOUNT (1%). The LEVAMM calls it after each swap.

The discount parameter sets the minimum LP amount of the donation: min_amount = (1e18 - discount) × crvusd_amount / CRYPTOPOOL.lp_price(). A larger discount lets the donation succeed when the pool price moves. Only the admin can pass a discount larger than the default 1%.

The function takes this input:

InputTypeDescription
discountuint256Discount that sets min_amount, 1e18-based. Defaults to FEE_CLAIM_DISCOUNT (1%).

The function emits DistributeBorrowerFees(sender, amount, min_amount, discount), only when amount > 0.

Contract source: LT._distribute_borrower_fees

The contract source gives this internal function body (decorator and comments removed):

# LT.vy
def _distribute_borrower_fees(discount: uint256):
amm: LevAMM = self.amm
if discount > FEE_CLAIM_DISCOUNT:
self._check_admin()
if msg.sender != amm.address:
extcall amm.collect_fees()
amount: uint256 = staticcall STABLECOIN.balanceOf(self)
if amount > 0:
min_amount: uint256 = (10**18 - discount) * amount // staticcall CRYPTOPOOL.lp_price()
extcall CRYPTOPOOL.add_liquidity([amount, 0], min_amount, empty(address), True)
log DistributeBorrowerFees(sender=msg.sender, amount=amount, min_amount=min_amount, discount=discount)

On Twocrypto v3.0.0 pools, the pool add_liquidity call can revert with these strings:

StringRaised byCondition
slippageThe Curve poolThe donation cannot meet min_amount.
donation above cap!The Curve poolThe donation shares go above donation_shares_max_ratio.

If the donation reverts, try again later. The admin can also try again with a larger discount.


withdraw_admin_fees()​

Mints the pending admin fees as LT shares to the Factory fee_receiver. Anyone can call it when LT.admin is a contract and Factory(admin).fee_receiver() is not empty. To find the receiver, call fee_receiver() on the Factory.

The function runs these steps in order:

  1. Checks that admin is a contract and that the LEVAMM is not killed.
  2. Checks that fee_receiver is not empty and is not the gauge (staker).
  3. Calls amm.check_nonreentrant().
  4. Runs _calculate_values(self._price_oracle_w()) to refresh the buckets. The admin bucket must not be negative (Loss made admin fee negative).
  5. Calculates to_mint = supply_tokens × (total + admin) / total − supply_tokens.
  6. Mints to_mint LT shares to fee_receiver, sets liquidity.admin = 0, and sets liquidity.total to total + admin.
  7. Emits WithdrawAdminFees(receiver, amount).

The function has no min_amount or min_assets parameter. The current bucket state sets the mint amount.

withdraw_admin_fees() can revert with these strings:

StringRaised byCondition
Need factoryLT.withdraw_admin_feesadmin is an EOA (no fee_receiver).
KilledLT.withdraw_admin_feesThe LEVAMM is killed. Nobody can withdraw admin fees until the LEVAMM is live again.
No fee_receiverLT.withdraw_admin_feesFactory exists but fee_receiver is the zero address.
Staker=fee_receiverLT.withdraw_admin_feesThe fee_receiver is the gauge (staker).
Loss made admin fee negativeLT.withdraw_admin_feesThe admin bucket is negative. Try again after fees cover the deficit.

allocate_stablecoins()​

Moves crvUSD between the allocator (the LT admin, which is the Factory) and the LEVAMM. Who can call it depends on the argument:

  • With no argument (limit = max_value(uint256)), anyone can call the function. It moves crvUSD from the allocator up to the stored stablecoin_allocation. If the stored value is lower, it moves crvUSD back to the allocator.
  • Only the LT admin or the Factory admin can set a new limit.

A decrease cannot take the allocation below 75% of the LP-priced collateral value (safe_lower_limit). If safe_lower_limit is larger than stablecoin_allocated, a decrease reverts on underflow. The crvUSD balance of the LEVAMM also limits a decrease.

The function returns nothing. It emits AllocateStablecoins with the allocator address, the allocation limit, and the allocated amount.

The function takes this input:

InputTypeDescription
limituint256Limit to allocate for this market (max uint256 = do not change)
Contract source: allocate_stablecoins()

The contract source gives this function body:

@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:
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()
# Normal operation leaves 50% surplus - coefficient 1
# "At the edge" is 1/2
# Safe lower limit for deflating the market - 3/4
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)

allocate_stablecoins() can revert with this string:

StringRaised byCondition
AccessLT._check_adminlimit is not max_value(uint256), and the caller is neither the LT admin nor, when the admin is a contract, the admin() of that contract.

updated_balances()​

View helper that LiquidityGauge._total_assets() uses to calculate its ERC-4626 totalAssets(). The function runs _calculate_values against the current oracle and writes nothing.

The function returns (supply_tokens, staked_tokens). supply_tokens is the total supply after the staker rebase. staked_tokens is the LT share balance of the gauge after the rebase. When totalSupply == 0, the function returns (0, 0).

You can use it to convert gauge balances to LT shares.

Contract source: updated_balances()

The contract source in LT.vy gives this function body:

@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)

symbol()​

Returns the symbol of the LT share token: yb- plus the asset symbol. This implements the ERC-20 symbol function.

The function returns a String[32] token symbol (for example, yb-WBTC).

Contract source: symbol()

The contract source gives this function body:

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

name()​

Returns the token name in the format Yield Basis liquidity for <asset symbol>. This implements the ERC-20 name function.

The function returns a String[58] token name (for example, Yield Basis liquidity for WBTC).

Contract source: name()

The contract source gives this function body:

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

set_amm()​

Sets the LEVAMM contract address. The function also sets the price aggregator from the price oracle of the LEVAMM. The function can run only one time. The LT admin can call it. When the admin is a contract, the admin() of that contract can also call it.

The function returns nothing.

The function takes this input:

InputTypeDescription
ammLevAMMLEVAMM contract address
Contract source: set_amm()

The contract source gives this function body:

@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_amm() can revert with these strings:

StringRaised byCondition
AccessLT._check_adminThe caller is neither the LT admin nor, when the admin is a contract, the admin() of that contract.
Already setLT.set_ammThe LEVAMM address is already set (a second call).

set_admin()​

Sets a new admin address. If the new admin is a contract, the function runs sanity checks on its getters. The LT admin can call it. When the admin is a contract, the admin() of that contract can also call it.

The function returns nothing. It emits SetAdmin with the new admin address.

The function takes this input:

InputTypeDescription
new_adminaddressNew admin address
Contract source: set_admin()

The contract source gives this function body:

@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_address = staticcall Factory(new_admin).gauge_controller()
check_uint: uint256 = staticcall Factory(new_admin).min_admin_fee()

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

set_admin() can revert with this string:

StringRaised byCondition
AccessLT._check_adminThe caller is neither the LT admin nor, when the admin is a contract, the admin() of that contract.

set_rate()​

Sets the interest rate in the LEVAMM. The function forwards the call to the LEVAMM contract. The LT admin can call it. When the admin is a contract, the admin() of that contract can also call it.

The function returns nothing.

The function takes this input:

InputTypeDescription
rateuint256New rate, as a 1e18-based fraction per second. The maximum is 10**18 // 31_536_000 (100% per year).
Contract source: set_rate()

The contract source gives this function body:

@external
@nonreentrant
def set_rate(rate: uint256):
self._check_admin()
extcall self.amm.set_rate(rate)

set_rate() can revert with these strings:

StringRaised byCondition
AccessLT._check_adminThe caller is neither the LT admin nor, when the admin is a contract, the admin() of that contract.
Rate too highThe LEVAMM (AMM.set_rate)rate is larger than the maximum.

set_amm_fee()​

Sets the trading fee in the LEVAMM. The function forwards the call to the LEVAMM contract. The LT admin can call it. When the admin is a contract, the admin() of that contract can also call it.

The function returns nothing.

The function takes this input:

InputTypeDescription
feeuint256New trading fee, 1e18-based (1e18 = 100%). The maximum is 10% (AMM.MAX_FEE).
Contract source: set_amm_fee()

The contract source gives this function body:

@external
@nonreentrant
def set_amm_fee(fee: uint256):
self._check_admin()
extcall self.amm.set_fee(fee)

set_amm_fee() can revert with this string:

StringRaised byCondition
AccessLT._check_adminThe caller is neither the LT admin nor, when the admin is a contract, the admin() of that contract.

_calculate_values()​

Internal function that calculates the current liquidity values: admin fees, token reductions, and position metrics. This function sets the LT token values and the fee split.

The following figure shows how the function allocates one value change between the buckets:

Flowchart of LT._calculate_values: compute the admin share f_a from the staking ratio and the value change since the last stored values. With less than 0.01 LT in the gauge, the admin share applies to gains and losses alike. Otherwise a loss goes wholly to LP value, and a gain first covers the staked gap with no admin fee before f_a applies to the rest. The staked part is pro rata, capped at the watermark gap on a gain. LT then updates total, staked, and admin, and computes the staker rebase.Flowchart of LT._calculate_values: compute the admin share f_a from the staking ratio and the value change since the last stored values. With less than 0.01 LT in the gauge, the admin share applies to gains and losses alike. Otherwise a loss goes wholly to LP value, and a gain first covers the staked gap with no admin fee before f_a applies to the rest. The staked part is pro rata, capped at the watermark gap on a gain. LT then updates total, staked, and admin, and computes the staker rebase.

The function runs these steps:

  1. It computes the admin share f_a from min_admin_fee and the staked share of the supply: the LT balance of the gauge divided by totalSupply.
  2. It computes value_change: the LEVAMM value in asset units minus the stored total and admin. The LEVAMM value is value_oracle().value / p_o, or the value that the caller passes as _amm_value.
  3. It sets dv_use, the change in LP value (total), by the regimes of Gain and loss sharing in the Math primer:
    • Small-stake case: the gauge holds less than MIN_STAKED_FOR_FEES, an absolute balance of 10^16 units (0.01 LT). The admin share applies to gains and to losses.
    • Loss regime: value_change is 0 or less. LP value takes the whole change, and the admin bucket does not change.
    • Recovery regime: the part of a gain that covers the staked gap goes to LP value with no admin fee.
    • Post-recovery regime: the rest of the gain pays f_a to the admin bucket.
  4. It gives the staked bucket its pro rata part of dv_use. On a gain, it caps that part at max(ideal_staked − staked, 0), so with no watermark gap the staked bucket gets no part of the gain. LT does not store the unstaked value, total − staked.
  5. It computes the staker rebase, token_reduction, so that the gauge's share of the LT supply matches the staked share of LP value, within bounds. A positive value burns LT shares of the gauge, and a negative value mints them. For the bounds, read Negative rebase of staked supply.

These calls write the result: deposit (when LT already has shares), withdraw, emergency_withdraw (while the LEVAMM is live and value_oracle() succeeds), checkpoint_staker_rebase (gauge caller, LEVAMM not killed), withdraw_admin_fees, and LT transfers to or from the gauge by another caller (LEVAMM not killed). These views compute it without a write: pricePerShare, updated_balances, and preview_deposit (supply above 0), preview_withdraw, and preview_emergency_withdraw (LEVAMM not killed).

LT changes ideal_staked on transfers to or from the gauge. A deposit into an LT with zero supply resets it to 0.

The function returns a LiquidityValuesOut struct with these fields:

FieldTypeDescription
adminint256Admin fees (can be negative)
totaluint256Total position value
ideal_stakeduint256Ideal staked amount (the watermark)
stakeduint256Current staked amount
staked_tokensuint256Staked token balance after reductions
supply_tokensuint256Total supply after reductions
token_reductionint256Token reduction amount (the staker rebase)

The function takes these inputs:

InputTypeDescription
p_ouint256Oracle price for calculations
_amm_valueuint256Optional LEVAMM value that the caller read before (default 0). When it is not 0, the function uses it and does not call amm.value_oracle(). When the LEVAMM is live, emergency_withdraw passes the value_oracle() value that it read with a call that cannot revert. So it does not call the oracle a second time. Markets 0 to 6 do not have this input.
Contract source: _calculate_values()

The contract source gives this function body:

@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
)

Admin-only setters (governance)​

Only the LT admin calls these functions. The admin is an EOA or a Factory contract. When the admin is a contract, _check_admin also accepts the admin() of that contract. This table, for governance audits and indexers, gives the effect and the event of each setter:

FunctionEffectEmits
set_amm(amm)Sets the LEVAMM contract address. It can run one time. A second call reverts with "Already set".—
set_admin(new_admin)Transfers admin to a new EOA or Factory. If the new admin is a contract, the function calls its admin, emergency_admin, fee_receiver, gauge_controller, and min_admin_fee getters.SetAdmin
set_rate(rate)Forwards to AMM.set_rate. Bounded by MAX_RATE (100% APR).SetRate (from the LEVAMM)
set_amm_fee(fee)Forwards to AMM.set_fee. Bounded by MAX_FEE (10%).SetFee (from the LEVAMM)
set_staker(staker)Sets the LiquidityGauge address. It can run one time. LT moves any balance at the gauge address to Factory(admin).fee_receiver(). This needs a Factory admin when the gauge address has a balance. Reverts: Staker already set, Staker=fee_receiver.SetStaker
set_killed(is_killed)Kills or unkills the LEVAMM. With a Factory admin: the Factory, the Factory admin, or Factory.emergency_admin(). With an EOA admin: only that admin.SetKilled (from the LEVAMM)

Other public views​

LT also has these view functions:

FunctionReturns
is_killed()True if the LEVAMM is killed (proxies AMM.is_killed). Read it to choose between withdraw and emergency_withdraw.
min_admin_fee()Minimum admin-fee floor (Factory.min_admin_fee() when admin is a factory, else 0).

State-changing helpers​

LT also has this state-changing helper:

FunctionEffect
checkpoint_staker_rebase()Writes state. It acts only when the gauge (staker) calls it (LiquidityGauge does this in deposit, mint, withdraw, and redeem) and the LEVAMM is not killed. It runs _calculate_values and stores the result. For other callers, or when the LEVAMM is killed, it does nothing.
  • Deposit and withdraw: both debt rules, the TRD scaling, and the error modes of each call.
  • Pool policy: the LT deviation check and the pool fee on deposits and withdrawals.
  • LEVAMM: the contract that holds the LP collateral and the crvUSD debt of the market.
  • LiquidityGauge: the gauge where you stake LT shares.
  • Error codes: every LT revert string in one table.