LT
Source: LT.vy on GitHub
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 instaker). 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:
| Function | Use 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 spendassetsof 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 value10**14._calculate_valuesuses it to cap the per-steptoken_reductionwhen the gauge holds almost all LT shares.token_reductionis 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 addressamm(LevAMM) - LEVAMM contract addressagg(PriceOracle) - Price aggregator contractstaker(address) - Gauge (LiquidityGauge) contract addressliquidity(LiquidityValues) - Liquidity state structallowance(HashMap[address, HashMap[address, uint256]]) - ERC-20 allowancesbalanceOf(HashMap[address, uint256]) - ERC-20 balancestotalSupply(uint256) - Total supply of LT sharesstablecoin_allocation(uint256) - crvUSD allocation limitstablecoin_allocated(uint256) - crvUSD that LT has moved to the LEVAMM
Data structs
LT uses these structs.
AMMState
collateral(uint256) - LP token amount held as collateraldebt(uint256) - crvUSD debt with accrued interestx0(uint256) - Virtual balance for maintaining 2× leverage
Pair
collateral(uint256) - LP token amountdebt(uint256) - crvUSD debt amount
OraclizedValue
p_o(uint256) - Oracle price used for calculationvalue(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 valueideal_staked(uint256) - Ideal staked amount (the watermark)staked(uint256) - Current staked amount
LiquidityValuesOut
admin(int256) - Admin fees (can be negative)total(uint256) - Total position valueideal_staked(uint256) - Ideal staked amount (the watermark)staked(uint256) - Current staked amountstaked_tokens(uint256) - Staked token balance after reductionssupply_tokens(uint256) - Total supply after reductionstoken_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 Factoryfee_receiver)amount(uint256) - Amount of LT shares minted to the receiver
AllocateStablecoins (allocate_stablecoins() emits it)
allocator(indexed address) - Address that allocated crvUSDstablecoin_allocation(uint256) - Total allocation limitstablecoin_allocated(uint256) - Allocated amount after the call
DistributeBorrowerFees (LT emits it when it donates crvUSD to the Curve pool)
sender(indexed address) -msg.senderof the call. It is the LEVAMM after a swap, the depositor or withdrawer afterdeposit()orwithdraw(), or the external caller.amount(uint256) - Amount of crvUSD donated into the Curve poolmin_amount(uint256) - Minimum LP-token amount accepted (computed from the donation discount andCRYPTOPOOL.lp_price())discount(uint256) - Discount applied to the distribution
Deposit (deposit() emits it)
sender(indexed address) - Address that initiated the depositowner(indexed address) - Address that receives the LT sharesassets(uint256) - Amount of assets depositedshares(uint256) - Amount of LT shares minted
Withdraw (withdraw() emits it. emergency_withdraw() does not.)
sender(indexed address) - Address that initiated the withdrawalreceiver(indexed address) - Address that receives the assetsowner(indexed address) - Address that owns the LT sharesassets(uint256) - Amount of assets withdrawnshares(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.
If you calculate debt yourself, make it proportional to the LEVAMM book. If you do not, the deposit reverts with "LT deviation".
- Read
AMM.get_debt()andAMM.collateral_amount(). - Calculate
lp_tokens = CRYPTOPOOL.calc_token_amount([debt, assets], True). - Set
debt = lp_tokens · AMM.get_debt() / AMM.collateral_amount(). - Do steps 2 and 3 again until the change in
debtis much less thanratio_tolerance. - 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:
| Input | Type | Description |
|---|---|---|
assets | uint256 | Amount of the asset token to deposit, in raw units at the asset's decimals |
debt | uint256 | Amount 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_shares | uint256 | Minimum LT shares to receive (slippage protection) |
receiver | address | Receiver 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:
| String | Raised by | Condition |
|---|---|---|
Deposit to staker | LT.deposit | receiver is the gauge (staker) address. |
Debt too high | LT.deposit | After the deposit, the LEVAMM value is more than AMM.max_debt() / 2. |
Unsafe min, Unsafe max | The LEVAMM (AMM._deposit()) | The LEVAMM debt-to-collateral ratio leaves its safe bounds. |
Remainder too small | LT.deposit | The total supply after the deposit is less than 10^6 share units. |
Slippage | LT.deposit | shares < min_shares. |
LT deviation | The pool policy, inside add_liquidity | Only on a pool that runs the oracle-report policy YBOraclizedPolicy. For the condition, read LT deviation check. |
slippage, donation above cap! | The Curve pool | The borrower-fee donation at the end of the call fails in the Curve pool. For the causes, read distribute_borrower_fees(). |
GAS | LT._checkpoint_gauge | The 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:
| Input | Type | Description |
|---|---|---|
assets | uint256 | Amount of the asset token to deposit |
debt | uint256 | Amount of crvUSD that the LEVAMM lends for the deposit |
raise_overflow | bool | If 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. |
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:
| String | Raised by | Condition |
|---|---|---|
Debt too high | LT.preview_deposit | raise_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:
| Input | Type | Description |
|---|---|---|
shares | uint256 | LT shares to withdraw |
min_assets | uint256 | Minimum amount of the asset token to receive (slippage protection) |
receiver | address | Receiver 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:
| String | Raised by | Condition |
|---|---|---|
Withdrawing nothing | LT.withdraw | shares == 0. |
Withdraw to/from staker | LT.withdraw | The caller or receiver is the gauge. |
We're dead. Use emergency_withdraw | LT.withdraw | The LEVAMM is killed. |
Remainder too small | LT.withdraw | The LT supply after the withdrawal is below MIN_SHARE_REMAINDER (10^6) and not 0. |
Slippage | LT.withdraw | crypto_received < min_assets. |
slippage, donation above cap! | The Curve pool | The borrower-fee donation at the end of the call fails. For the causes, read distribute_borrower_fees(). |
GAS | LT._checkpoint_gauge | The 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:
| Input | Type | Description |
|---|---|---|
tokens | uint256 | Amount 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:
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.
| Call | Live | value_oracle() reverts | Killed |
|---|---|---|---|
deposit | Works | Reverts | Reverts in AMM._deposit (no message) |
withdraw | Works | Reverts | Reverts with We're dead. Use emergency_withdraw |
emergency_withdraw | Only the owner, else Not killed | Works 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_fees | Works | Reverts | Reverts with Killed |
pricePerShare, preview_deposit, preview_withdraw, updated_balances | Work | Revert | Work only while value_oracle() succeeds. These views do not check the kill. |
preview_emergency_withdraw | Works | Reverts | Works without value_oracle() |
Gauge deposit, mint, withdraw, and redeem, and LT transfers to or from the gauge | Work | Revert | Work, without the staker rebase. In redeem, Factory.emergency_admin() can act for any holder with receiver = owner. |
HybridVault.unstake | Works | Reverts | Works |
checkpoint_staker_rebase (gauge caller) | Updates the buckets | Reverts | Does nothing |
distribute_borrower_fees | Works | Does 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:
| Caller | LEVAMM state | receiver | Result |
|---|---|---|---|
owner of the position | live, value_oracle() succeeds | any | Allowed. |
| Not the owner | live, value_oracle() succeeds | — | Reverts with "Not killed". |
Factory.emergency_admin() (LT admin is a contract) | killed, or value_oracle() reverts | must 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() reverts | must be owner, else reverts with "receiver" | Allowed for any owner. |
| Any other caller | killed, or value_oracle() reverts | any | Allowed 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:
| Input | Type | Description |
|---|---|---|
shares | uint256 | LT shares to redeem |
receiver | address | Destination for the asset leg (and the crvUSD leg if positive). Default msg.sender. |
owner | address | Position owner whose LT shares are burned. Default msg.sender. |
emergency_withdraw() can revert with these strings:
| String | Raised by | Condition |
|---|---|---|
Not killed | LT.emergency_withdraw | The LEVAMM is live, value_oracle() succeeds, and the caller is not owner. |
receiver | LT.emergency_withdraw | Factory.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. |
owner | LT.emergency_withdraw | Any other caller calls with an owner other than msg.sender, while the LEVAMM is killed or value_oracle() reverts. |
Withdraw to/from staker | LT.emergency_withdraw | owner or receiver is the gauge. |
Remainder too small | LT.emergency_withdraw | The LT supply after the withdrawal is below MIN_SHARE_REMAINDER (10^6) and not 0. |
GAS | LT._checkpoint_gauge | The 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:
| Input | Type | Description |
|---|---|---|
shares | uint256 | LT 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:
| Input | Type | Description |
|---|---|---|
discount | uint256 | Discount 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:
| String | Raised by | Condition |
|---|---|---|
slippage | The Curve pool | The donation cannot meet min_amount. |
donation above cap! | The Curve pool | The 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:
- Checks that
adminis a contract and that the LEVAMM is not killed. - Checks that
fee_receiveris not empty and is not the gauge (staker). - Calls
amm.check_nonreentrant(). - Runs
_calculate_values(self._price_oracle_w())to refresh the buckets. The admin bucket must not be negative (Loss made admin fee negative). - Calculates
to_mint = supply_tokens × (total + admin) / total − supply_tokens. - Mints
to_mintLT shares tofee_receiver, setsliquidity.admin = 0, and setsliquidity.totaltototal + admin. - 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:
| String | Raised by | Condition |
|---|---|---|
Need factory | LT.withdraw_admin_fees | admin is an EOA (no fee_receiver). |
Killed | LT.withdraw_admin_fees | The LEVAMM is killed. Nobody can withdraw admin fees until the LEVAMM is live again. |
No fee_receiver | LT.withdraw_admin_fees | Factory exists but fee_receiver is the zero address. |
Staker=fee_receiver | LT.withdraw_admin_fees | The fee_receiver is the gauge (staker). |
Loss made admin fee negative | LT.withdraw_admin_fees | The 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 storedstablecoin_allocation. If the stored value is lower, it moves crvUSD back to the allocator. - Only the LT
adminor the Factory admin can set a newlimit.
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:
| Input | Type | Description |
|---|---|---|
limit | uint256 | Limit 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:
| String | Raised by | Condition |
|---|---|---|
Access | LT._check_admin | limit 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:
| Input | Type | Description |
|---|---|---|
amm | LevAMM | LEVAMM 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:
| String | Raised by | Condition |
|---|---|---|
Access | LT._check_admin | The caller is neither the LT admin nor, when the admin is a contract, the admin() of that contract. |
Already set | LT.set_amm | The 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:
| Input | Type | Description |
|---|---|---|
new_admin | address | New 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:
| String | Raised by | Condition |
|---|---|---|
Access | LT._check_admin | The 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:
| Input | Type | Description |
|---|---|---|
rate | uint256 | New 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:
| String | Raised by | Condition |
|---|---|---|
Access | LT._check_admin | The caller is neither the LT admin nor, when the admin is a contract, the admin() of that contract. |
Rate too high | The 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:
| Input | Type | Description |
|---|---|---|
fee | uint256 | New 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:
| String | Raised by | Condition |
|---|---|---|
Access | LT._check_admin | The 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:
The function runs these steps:
- It computes the admin share
f_afrommin_admin_feeand the staked share of the supply: the LT balance of the gauge divided bytotalSupply. - It computes
value_change: the LEVAMM value in asset units minus the storedtotalandadmin. The LEVAMM value isvalue_oracle().value / p_o, or the value that the caller passes as_amm_value. - 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_changeis 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_ato the admin bucket.
- Small-stake case: the gauge holds less than
- It gives the staked bucket its pro rata part of
dv_use. On a gain, it caps that part atmax(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. - 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:
| Field | Type | Description |
|---|---|---|
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) |
The function takes these inputs:
| Input | Type | Description |
|---|---|---|
p_o | uint256 | Oracle price for calculations |
_amm_value | uint256 | Optional 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:
| Function | Effect | Emits |
|---|---|---|
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:
| Function | Returns |
|---|---|
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:
| Function | Effect |
|---|---|
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. |
Related
- 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.