LT (Leveraged Token)
The source code of the LT.vy
contract can be found on GitHub. The contract is written with Vyper version 0.4.3.
Overview
The LT contract is the market’s leveraged-liquidity share token (ERC-20, 18 decimals; symbol yb-asset
). Users deposit the asset token (e.g., BTC) while the contract borrows a matching amount of stablecoin liquidity from the market (via its allocation) to mint Curve LP and hand the LP into the LEVAMM. In return, users receive LT shares that represent a pro-rata claim on the AMM position’s value. Withdrawals burn shares and unwind through the AMM and Curve pool back to asset (and any required stablecoin settlement). The contract exposes standard deposit/withdraw flows with slippage guards and a fair, oracle-anchored price-per-share.
LT also manages the yb markets: it owns the LEVAMM instance, forwards interest-rate and fee updates to the AMM, manages the stablecoin allocation to the AMM (allocate_stablecoins
), and periodically sweeps borrower interest collected in the AMM into the Curve pool as LP tokens (distribute_borrower_fees
).
An optional staker address can be set once to integrate with gauges/rewards, and admin fees are tracked and withdrawable to the protocol’s fee receiver. Safety is enforced with access controls (admin/factory admins), non-reentrancy on state-changing entry points, kill/unkill passthrough to the AMM, and oracle-based checks to keep the position within safe bounds.
Deployed by the Factory: LT instances are cloned from the configured implementation using Vyper’s create_from_blueprint(asset_token, stablecoin, cryptopool, admin_or_factory)
, then connected to the market’s LEVAMM and pricing oracle during market creation.
Function Documentation
Public Variables
The LT contract exposes several public variables for reading contract state and configuration.
Constants
CRYPTOPOOL_N_COINS
(uint256
) - Number of coins in the crypto pool (2)FEE_CLAIM_DISCOUNT
(uint256
) - Fee claim discount (10^16 = 1%)MIN_SHARE_REMAINDER
(uint256
) - Minimum shares to leave if > 0 (10^6)SQRT_MIN_UNSTAKED_FRACTION
(int256
) - Minimum unstaked fraction (10^14 = 1e-4)MIN_STAKED_FOR_FEES
(int256
) - Minimum staked amount for fees (10^16)
Immutable Variables
CRYPTOPOOL
(CurveCryptoPool
) - Curve crypto pool contract (immutable)STABLECOIN
(IERC20
) - Stablecoin contract (immutable)ASSET_TOKEN
(IERC20
) - Asset token contract (immutable)
State Variables
admin
(address
) - Admin addressamm
(LevAMM
) - AMM contract addressagg
(PriceOracle
) - Price aggregator contractstaker
(address
) - Staker contract addressliquidity
(LiquidityValues
) - Liquidity state structallowance
(HashMap[address, HashMap[address, uint256]]
) - ERC20 allowancesbalanceOf
(HashMap[address, uint256]
) - ERC20 balancestotalSupply
(uint256
) - Total token supplydecimals
(uint8
) - Token decimals (18)stablecoin_allocation
(uint256
) - Stablecoin allocation limitstablecoin_allocated
(uint256
) - Currently allocated stablecoins
Data Structs
AMMState
collateral
(uint256
) - LP token amount held as collateraldebt
(uint256
) - Interest-accrued stablecoin debtx0
(uint256
) - Virtual balance for maintaining 2x leverage
Pair
collateral
(uint256
) - LP token amountdebt
(uint256
) - Stablecoin debt amount
OraclizedValue
p_o
(uint256
) - Oracle price used for calculationvalue
(uint256
) - Calculated value in stablecoin terms
LiquidityValues
admin
(int256
) - Admin fees (can be negative)total
(uint256
) - Total position valueideal_staked
(uint256
) - Ideal staked amountstaked
(uint256
) - Current staked amount
LiquidityValuesOut
admin
(int256
) - Admin fees (can be negative)total
(uint256
) - Total position valueideal_staked
(uint256
) - Ideal staked amountstaked
(uint256
) - Current staked amountstaked_tokens
(uint256
) - Staked token balance after reductionssupply_tokens
(uint256
) - Total supply after reductionstoken_reduction
(int256
) - Token reduction amount
Events
SetStaker (Emitted when a new staker contract is set)
staker
(address
) - New staker contract address
WithdrawAdminFees (Emitted when admin fees are withdrawn)
admin
(address
) - Admin addressamount
(uint256
) - Amount of admin fees withdrawn
AllocateStablecoins (Emitted when stablecoins are allocated to the AMM)
allocator
(address
) - Address that allocated stablecoinsstablecoin_allocation
(uint256
) - Total allocation limitstablecoin_allocated
(uint256
) - Currently allocated amount
DistributeBorrowerFees (Emitted when borrower fees are distributed to the Curve pool)
amount
(uint256
) - Amount of fees distributeddiscount
(uint256
) - Discount applied to the distribution
Deposit (Emitted when assets are deposited and shares are minted)
sender
(address
) - Address that initiated the depositowner
(address
) - Address that receives the sharesassets
(uint256
) - Amount of assets depositedshares
(uint256
) - Amount of shares minted
Withdraw (Emitted when shares are burned and assets are withdrawn)
sender
(address
) - Address that initiated the withdrawalreceiver
(address
) - Address that receives the assetsowner
(address
) - Address that owns the sharesassets
(uint256
) - Amount of assets withdrawnshares
(uint256
) - Amount of shares burned
SetAdmin (Emitted when the admin address is changed)
admin
(address
) - New admin address
_calculate_values()
Description:
Internal function that calculates the current liquidity values, including admin fees, token reductions, and position metrics. This is the core function for determining LT token values and managing fee distributions.
Returns:
LiquidityValuesOut
- Struct containing detailed position metrics including:
Field | Type | Description |
---|---|---|
admin | int256 | Admin fees (can be negative) |
total | uint256 | Total position value |
ideal_staked | uint256 | Ideal staked amount |
staked | uint256 | Current staked amount |
staked_tokens | uint256 | Staked token balance after reductions |
supply_tokens | uint256 | Total supply after reductions |
token_reduction | int256 | Token reduction amount |
Input | Type | Description |
---|---|---|
p_o | uint256 | Oracle price for calculations |
📄 View Source Code
@view
def _calculate_values(p_o: uint256) -> 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)
cur_value: int256 = convert((staticcall self.amm.value_oracle()).value * 10**18 // p_o, int256)
prev_value: int256 = convert(prev.total, int256)
value_change: int256 = cur_value - (prev_value + prev.admin)
v_st: int256 = convert(prev.staked, int256)
v_st_ideal: int256 = convert(prev.ideal_staked, int256)
# ideal_staked is set when some tokens are transferred to staker address
# Admin fees are earned only when all losses are paid off
dv_use_36: int256 = 0
v_st_loss: int256 = max(v_st_ideal - v_st, 0)
if staked >= MIN_STAKED_FOR_FEES:
if value_change > 0:
# Admin fee is only charged once the loss if fully paid off
v_loss: int256 = min(value_change, v_st_loss * supply // staked)
dv_use_36 = v_loss * 10**18 + (value_change - v_loss) * (10**18 - f_a)
else:
# Admin doesn't pay for value loss
dv_use_36 = value_change * 10**18
else:
# If staked part is small - positive admin fees are charged on profits and negative on losses
dv_use_36 = value_change * (10**18 - f_a)
prev.admin += (value_change - dv_use_36 // 10**18)
# dv_s is guaranteed to be <= dv_use
# if staked < supply (not exactly 100.0% staked) - dv_s is strictly < dv_use
dv_s_36: int256 = self.mul_div_signed(dv_use_36, staked, supply)
if dv_use_36 > 0:
dv_s_36 = min(dv_s_36, v_st_loss * 10**18)
# new_staked_value is guaranteed to be <= new_total_value
new_total_value_36: int256 = max(prev_value * 10**18 + dv_use_36, 0)
new_staked_value_36: int256 = max(v_st * 10**18 + dv_s_36, 0)
# Solution of:
# staked - token_reduction new_staked_value
# ------------------------- = -------------------
# supply - token_reduction new_total_value
#
# the result:
# new_total_value * staked - new_staked_value * supply
# token_reduction = ------------------------------------------------------
# new_total_value - new_staked_value
token_reduction: int256 = new_total_value_36 - new_staked_value_36 # Denominator
token_reduction = self.mul_div_signed(new_total_value_36, staked, token_reduction) - self.mul_div_signed(new_staked_value_36, supply, token_reduction)
max_token_reduction: int256 = abs(value_change * supply // (prev_value + value_change + 1) * (10**18 - f_a) // SQRT_MIN_UNSTAKED_FRACTION)
# let's leave at least 1 LP token for staked and for total
if staked > 0:
token_reduction = min(token_reduction, staked - 1)
if supply > 0:
token_reduction = min(token_reduction, supply - 1)
# But most likely it's this condition to apply
if token_reduction >= 0:
token_reduction = min(token_reduction, max_token_reduction)
else:
token_reduction = max(token_reduction, -max_token_reduction)
# And don't allow negatives if denominator was too small
if new_total_value_36 - new_staked_value_36 < 10**4 * 10**18:
token_reduction = max(token_reduction, 0)
return LiquidityValuesOut(
admin=prev.admin,
total=convert(new_total_value_36 // 10**18, uint256),
ideal_staked=prev.ideal_staked,
staked=convert(new_staked_value_36 // 10**18, uint256),
staked_tokens=convert(staked - token_reduction, uint256),
supply_tokens=convert(supply - token_reduction, uint256),
token_reduction=token_reduction
)
deposit()
Description:
Method to deposit assets (e.g., BTC) to receive shares (e.g., yield-bearing BTC). This function implements the ERC4626 deposit standard and handles the complex liquidity calculations.
Returns:
uint256
- Amount of shares minted to the receiver.
Emits:
Deposit
- Deposit event with sender, owner, assets, and shares amounts.
Input | Type | Description |
---|---|---|
assets | uint256 | Amount of assets to deposit |
debt | uint256 | Amount of debt for AMM to take (approximately BTC * btc_price) |
min_shares | uint256 | Minimal amount of shares to receive (slippage protection) |
receiver | address | Receiver of the shares (defaults to msg.sender) |
📄 View Source Code
@external
@nonreentrant
def deposit(assets: uint256, debt: uint256, min_shares: uint256, receiver: address = msg.sender) -> uint256:
staker: address = self.staker
assert receiver != staker, "Deposit to staker"
amm: LevAMM = self.amm
assert extcall STABLECOIN.transferFrom(amm.address, self, debt, default_return_value=True)
assert extcall ASSET_TOKEN.transferFrom(msg.sender, self, assets, default_return_value=True)
lp_tokens: uint256 = extcall CRYPTOPOOL.add_liquidity([debt, assets], 0, amm.address, False)
p_o: uint256 = self._price_oracle_w()
supply: uint256 = self.totalSupply
shares: uint256 = 0
liquidity_values: LiquidityValuesOut = empty(LiquidityValuesOut)
if supply > 0:
liquidity_values = self._calculate_values(p_o)
v: OraclizedValue = extcall amm._deposit(lp_tokens, debt)
value_after: uint256 = v.value * 10**18 // p_o
assert staticcall amm.max_debt() // 2 >= v.value, "Debt too high"
if supply > 0 and liquidity_values.total > 0:
supply = liquidity_values.supply_tokens
self.liquidity.admin = liquidity_values.admin
value_before: uint256 = liquidity_values.total
value_after = convert(convert(value_after, int256) - liquidity_values.admin, uint256)
self.liquidity.total = value_after
self.liquidity.staked = liquidity_values.staked
self.totalSupply = liquidity_values.supply_tokens
if staker != empty(address):
self.balanceOf[staker] = liquidity_values.staked_tokens
self._log_token_reduction(staker, liquidity_values.token_reduction)
shares = supply * value_after // value_before - supply
else:
shares = value_after
self.liquidity.ideal_staked = 0
self.liquidity.staked = 0
self.liquidity.total = shares + supply
self.liquidity.admin = 0
if self.balanceOf[staker] > 0:
log IERC20.Transfer(sender=staker, receiver=empty(address), value=self.balanceOf[staker])
self.balanceOf[staker] = 0
assert shares + supply >= MIN_SHARE_REMAINDER, "Remainder too small"
assert shares >= min_shares, "Slippage"
self._mint(receiver, shares)
self._checkpoint_gauge()
log Deposit(sender=msg.sender, owner=receiver, assets=assets, shares=shares)
self._distribute_borrower_fees(FEE_CLAIM_DISCOUNT)
return shares
withdraw()
Description:
Method to withdraw assets (e.g., BTC) by spending shares (e.g., yield-bearing BTC). This function implements the ERC4626 withdraw standard.
Returns:
uint256
- Amount of assets received by the receiver.
Emits:
Withdraw
- Withdrawal event with sender, receiver, owner, assets, and shares amounts.
Input | Type | Description |
---|---|---|
shares | uint256 | Shares to withdraw |
min_assets | uint256 | Minimal amount of assets to receive (slippage protection) |
receiver | address | Receiver of the assets (defaults to msg.sender) |
📄 View Source Code
@external
@nonreentrant
def withdraw(shares: uint256, min_assets: uint256, receiver: address = msg.sender) -> uint256:
assert shares > 0, "Withdrawing nothing"
staker: address = self.staker
assert staker not in [msg.sender, receiver], "Withdraw to/from staker"
assert not (staticcall self.amm.is_killed()), "We're dead. Use emergency_withdraw"
amm: LevAMM = self.amm
liquidity_values: LiquidityValuesOut = self._calculate_values(self._price_oracle_w())
supply: uint256 = liquidity_values.supply_tokens
self.liquidity.admin = liquidity_values.admin
self.liquidity.staked = liquidity_values.staked
self.totalSupply = supply
assert supply >= MIN_SHARE_REMAINDER + shares or supply == shares, "Remainder too small"
if staker != empty(address):
self.balanceOf[staker] = liquidity_values.staked_tokens
self._log_token_reduction(staker, liquidity_values.token_reduction)
admin_balance: uint256 = convert(max(liquidity_values.admin, 0), uint256)
withdrawn: Pair = extcall amm._withdraw(10**18 * liquidity_values.total // (liquidity_values.total + admin_balance) * shares // supply)
assert extcall CRYPTOPOOL.transferFrom(amm.address, self, withdrawn.collateral, default_return_value=True)
crypto_received: uint256 = extcall CRYPTOPOOL.remove_liquidity_fixed_out(withdrawn.collateral, 0, withdrawn.debt, 0)
self._burn(msg.sender, shares)
self.liquidity.total = liquidity_values.total * (supply - shares) // supply
if liquidity_values.admin < 0:
self.liquidity.admin = liquidity_values.admin * convert(supply - shares, int256) // convert(supply, int256)
assert crypto_received >= min_assets, "Slippage"
assert extcall STABLECOIN.transfer(amm.address, withdrawn.debt, default_return_value=True)
assert extcall ASSET_TOKEN.transfer(receiver, crypto_received, default_return_value=True)
self._checkpoint_gauge()
log Withdraw(sender=msg.sender, receiver=receiver, owner=msg.sender, assets=crypto_received, shares=shares)
self._distribute_borrower_fees(FEE_CLAIM_DISCOUNT)
return crypto_received
preview_deposit()
Description:
Returns the amount of shares which can be obtained upon depositing assets, including slippage. This is a view function that simulates the deposit without executing it.
Returns:
uint256
- Amount of shares that would be minted for the given assets and debt.
Input | Type | Description |
---|---|---|
assets | uint256 | Amount of crypto to deposit |
debt | uint256 | Amount of stables to borrow for MMing (approx same value as crypto) |
📄 View Source Code
@external
@view
@nonreentrant
def preview_deposit(assets: uint256, debt: uint256) -> 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 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 amm_max_debt < v.value:
raise "Debt too high"
return v.value * 10**18 // p_o
preview_withdraw()
Description:
Returns the amount of assets which can be obtained upon withdrawing from tokens. This is a view function that simulates the withdrawal without executing it.
Returns:
uint256
- Amount of assets that would be received for the given shares.
Input | Type | Description |
---|---|---|
tokens | uint256 | Amount of shares to withdraw |
📄 View Source Code
@external
@view
@nonreentrant
def preview_withdraw(tokens: uint256) -> uint256:
v: LiquidityValuesOut = self._calculate_values(self._price_oracle())
state: AMMState = staticcall self.amm.get_state()
admin_balance: uint256 = convert(max(v.admin, 0), uint256)
frac: uint256 = 10**18 * v.total // (v.total + admin_balance) * tokens // v.supply_tokens
withdrawn_lp: uint256 = state.collateral * frac // 10**18
withdrawn_debt: uint256 = state.debt * frac // 10**18
return staticcall CRYPTOPOOL.calc_withdraw_fixed_out(withdrawn_lp, 0, withdrawn_debt)
set_amm()
Description:
Sets the AMM contract address. This function can only be called once and only by the admin. It also sets the price aggregator from the AMM's price oracle. AMM can only be changed by the admin
of this contract or the admin
of the Factory
.
Returns:
No return value (void function).
Input | Type | Description |
---|---|---|
amm | LevAMM | AMM contract address |
📄 View Source Code
@external
@nonreentrant
def set_amm(amm: LevAMM):
self._check_admin()
assert self.amm == empty(LevAMM), "Already set"
assert staticcall amm.STABLECOIN() == STABLECOIN.address
assert staticcall amm.COLLATERAL() == CRYPTOPOOL.address
assert staticcall amm.LT_CONTRACT() == self
self.amm = amm
self.agg = PriceOracle(staticcall (staticcall amm.PRICE_ORACLE_CONTRACT()).AGG())
set_admin()
Description:
Sets a new admin address. This function can only be called by the current admin. It performs sanity checks if the new admin is a contract. New admin can only be changed by the current admin
of this contract or the admin
of the Factory
.
Returns:
No return value (void function).
Emits:
SetAdmin
- Admin change event with new admin address.
Input | Type | Description |
---|---|---|
new_admin | address | New admin address |
📄 View Source Code
@external
@nonreentrant
def set_admin(new_admin: address):
self._check_admin()
if new_admin.is_contract:
check_address: address = staticcall Factory(new_admin).admin()
check_address = staticcall Factory(new_admin).emergency_admin()
check_address = staticcall Factory(new_admin).fee_receiver()
check_uint: uint256 = staticcall Factory(new_admin).min_admin_fee()
self.admin = new_admin
log SetAdmin(admin=new_admin)
set_rate()
Description:
Sets the interest rate in the AMM. This function can only be called by the admin and forwards the call to the AMM contract. Interest rate can only be changed by the admin
of this contract or the admin
of the Factory
.
Returns:
No return value (void function).
Input | Type | Description |
---|---|---|
rate | uint256 | New interest rate |
📄 View Source Code
@external
@nonreentrant
def set_rate(rate: uint256):
self._check_admin()
extcall self.amm.set_rate(rate)
set_amm_fee()
Description:
Sets the trading fee in the AMM. This function can only be called by the admin and forwards the call to the AMM contract. AMM fee can only be changed by the admin
of this contract or the admin
of the Factory
.
Returns:
No return value (void function).
Input | Type | Description |
---|---|---|
fee | uint256 | New trading fee |
📄 View Source Code
@external
@nonreentrant
def set_amm_fee(fee: uint256):
self._check_admin()
extcall self.amm.set_fee(fee)
allocate_stablecoins()
Description:
Allocates stablecoins to the AMM for this pool. This method must be used once the contract has received allocation of stablecoins. Allocation can only be handled by the admin
of this contract or the admin
of the Factory
.
Returns:
No return value (void function).
Emits:
AllocateStablecoins
- Allocation event with allocator address, allocation amount, and allocated amount.
Input | Type | Description |
---|---|---|
limit | uint256 | Limit to allocate for this pool (max uint256 = do not change) |
📄 View Source Code
@external
@nonreentrant
def allocate_stablecoins(limit: uint256 = max_value(uint256)):
allocator: address = self.admin
allocation: uint256 = limit
allocated: uint256 = self.stablecoin_allocated
if limit == max_value(uint256):
allocation = self.stablecoin_allocation
else:
self._check_admin()
self.stablecoin_allocation = limit
extcall self.amm.check_nonreentrant()
if allocation > allocated:
assert extcall STABLECOIN.transferFrom(allocator, self.amm.address, allocation - allocated, default_return_value=True)
self.stablecoin_allocated = allocation
elif allocation < allocated:
lp_price: uint256 = extcall (staticcall self.amm.PRICE_ORACLE_CONTRACT()).price_w()
assert allocation >= lp_price * (staticcall self.amm.collateral_amount()) // 10**18, "Not enough stables"
to_transfer: uint256 = min(allocated - allocation, staticcall STABLECOIN.balanceOf(self.amm.address))
allocated -= to_transfer
assert extcall STABLECOIN.transferFrom(self.amm.address, allocator, to_transfer, default_return_value=True)
self.stablecoin_allocated = allocated
log AllocateStablecoins(allocator=allocator, stablecoin_allocation=allocation, stablecoin_allocated=allocated)
pricePerShare()
Description:
Non-manipulatable "fair price per share" oracle of the yb-asset token.
Returns:
uint256
- Price per share in 1e18 units.
📄 View Source Code
@external
@view
def pricePerShare() -> uint256:
v: LiquidityValuesOut = self._calculate_values(self._price_oracle())
if v.supply_tokens == 0:
return 10**18
else:
return v.total * 10**18 // v.supply_tokens
symbol()
Description:
Returns the token symbol in the format 'yb-asset'. This implements the ERC20 symbol standard.
Returns:
String[32]
- Token symbol (e.g., "yb-BTC").
📄 View Source Code
@external
@view
def symbol() -> String[32]:
return concat('yb-', staticcall IERC20Slice(ASSET_TOKEN.address).symbol())
name()
Description:
Returns the token name in the format 'Yield Basis liquidity for asset'. This implements the ERC20 name standard.
Returns:
String[58]
- Token name (e.g., "Yield Basis liquidity for BTC").
📄 View Source Code
@external
@view
def name() -> String[58]:
return concat('Yield Basis liquidity for ', staticcall IERC20Slice(ASSET_TOKEN.address).symbol())