Skip to main content

HybridVaultFactory

Overview​

HybridVaultFactory deploys HybridVaults on request, at most one for each address. It also holds the global settings that every HybridVault reads at run time. Use this reference to create a vault, to read the global settings, and to review the governance setters.

The factory has these tasks:

  • It deploys and initializes minimal proxies of the vault implementation. Each address can create one HybridVault.
  • It holds the stablecoin fraction: the fraction of position value that crvUSD must back. The constructor sets 55%. Governance changes it with set_stablecoin_fraction. To read it, call stablecoin_fraction(). For the current fraction and its history, read Hybrid Vaults.
  • It holds the pool limit of each market, which Pool limits describes.
  • It holds the allowlist of crvUSD vaults: the ERC-4626 vaults for crvUSD, such as scrvUSD, that HybridVaults can use. Each allowed crvUSD vault can have a crvUSD vault limit.
  • It tracks the required crvUSD of each HybridVault, and the total for each crvUSD vault (crvusd_vault_total_required). It enforces the crvUSD vault limit on that total.
  • It forwards the lt_allocate_stablecoins() calls of HybridVaults to the admin of the YieldBasis Factory.

Pool limits​

A pool limit is the maximum LEVAMM value of a market after a HybridVault deposit, in USD as AMM.value_oracle() reports it. The LEVAMM (contract AMM) is the market's leveraged AMM. A HybridVault deposit reverts with "Beyond pool limit" if AMM value + value of the new shares is above the limit.

A pool limit has these properties:

  • The limit is not a per-deposit size.
  • It does not apply to direct LT.deposit() calls.
  • The effective limit for a HybridVault is personal_limit[pool_id] + factory.pool_limits(pool_id). ADMIN can set a personal limit for each HybridVault with HybridVault.set_personal_limit().

For example, pool_limits(pool_id) = 50_000_000 * 10**18 lets HybridVault deposits into that market continue until the LEVAMM value of the market reaches 50,000,000 USD.


Public variables​

The factory has these public variables.

Immutable variables

  • FACTORY (Factory) - YieldBasis Factory contract
  • ADMIN (address) - Admin address, read once in the constructor: factory.admin().ADMIN() (Factory → HybridFactoryOwner → ADMIN). ADMIN does not follow a later change of the Factory admin. In contrast, lt_allocate_stablecoins reads FACTORY.admin() at each call.

State variables

  • vault_impl (address) - Current vault implementation for new deployments
  • user_to_vault (HashMap[address, HybridVault]) - Maps user address to vault
  • vault_to_user (HashMap[HybridVault, address]) - Maps vault to user address
  • stablecoin_fraction (uint256) - Required crvUSD backing as a fraction of 1e18 (constructor value: 55e16)
  • pool_limits (HashMap[uint256, uint256]) - Maximum LEVAMM value of the market after a HybridVault deposit, per market (pool_id)
  • allowed_crvusd_vaults (HashMap[address, bool]) - Allowlist for crvUSD vaults
  • crvusd_vault_limits (HashMap[address, uint256]) - crvUSD vault limit: the maximum total required crvUSD of all HybridVaults that use a crvUSD vault (0 = unlimited)
  • crvusd_vault_total_required (HashMap[address, uint256]) - Current total required crvUSD of all HybridVaults that use a given crvUSD vault
  • crvusd_vault_required (HashMap[HybridVault, uint256]) - Required crvUSD tracked for each HybridVault

Events​

The factory emits these events. HybridVault is an address in the ABI.

EventFields
VaultCreateduser: indexed(address), vault: indexed(HybridVault)
SetVaultImplimpl: address
SetStablecoinFractionstablecoin_fraction: uint256
SetPoolLimitpool_id: uint256, limit: uint256
SetAllowedCrvusdVaultvault: address, allowed: bool
SetCrvusdVaultLimitcrvusd_vault: address, limit: uint256

Functions​

Each function section lists its own revert strings. For the revert strings of all YieldBasis contracts, read Error codes: HybridVaultFactory.vy.

create_vault()​

Creates a new HybridVault for the caller. The function deploys a minimal proxy of vault_impl and initializes it with the crvUSD vault that the caller chooses. Each address can create only one HybridVault.

The function returns the new HybridVault (HybridVault, an address in the ABI). It emits VaultCreated.

The function takes this input:

InputTypeDescription
crvusd_vaultaddressThe crvUSD vault that the new HybridVault uses, such as scrvUSD
Contract source: create_vault()

The contract source gives this function body:

@external
def create_vault(crvusd_vault: address) -> HybridVault:
assert self.user_to_vault[msg.sender] == empty(HybridVault), "Already created"
assert self.allowed_crvusd_vaults[crvusd_vault], "Vault not allowed"
assert self.vault_impl != empty(address), "Vault impl not set"

vault: HybridVault = HybridVault(create_minimal_proxy_to(self.vault_impl))
extcall vault.initialize(msg.sender, crvusd_vault)

self.user_to_vault[msg.sender] = vault
self.vault_to_user[vault] = msg.sender

log VaultCreated(user=msg.sender, vault=vault)
return vault

create_vault() reverts with these strings:

StringCause
Already createdThe caller already has a HybridVault.
Vault not allowedcrvusd_vault is not on the allowlist.
Vault impl not setvault_impl is empty.

set_vault_impl()​

Sets the vault implementation for new HybridVaults. Existing HybridVaults do not change. Only ADMIN can call it.

The function returns nothing. It emits SetVaultImpl.

The function takes this input:

InputTypeDescription
impladdressNew vault implementation address
Contract source: set_vault_impl()

The contract source gives this function body:

@external
def set_vault_impl(impl: address):
assert msg.sender == ADMIN, "Access"
self.vault_impl = impl
log SetVaultImpl(impl=impl)

set_vault_impl() reverts with Access if the caller is not ADMIN.


set_stablecoin_fraction()​

Sets the fraction of position value that crvUSD in the crvUSD vault must back. The value has 18 decimals. For example, frac = 5 * 10**17 sets 50%, so each 100 crvUSD of position value needs 50 crvUSD of backing. Only ADMIN can call it.

The function returns nothing. It emits SetStablecoinFraction.

The function takes this input:

InputTypeDescription
fracuint256The stablecoin fraction as a fraction of 1e18
Contract source: set_stablecoin_fraction()

The contract source gives this function body:

@external
def set_stablecoin_fraction(frac: uint256):
assert msg.sender == ADMIN, "Access"
self.stablecoin_fraction = frac
log SetStablecoinFraction(stablecoin_fraction=frac)

set_stablecoin_fraction() reverts with Access if the caller is not ADMIN.


set_pool_limit()​

Sets the pool limit for a market. The limit caps the LEVAMM value of the whole market after a HybridVault deposit (pool_value + additional_crvusd). The limit does not cap the size of one deposit. It does not apply to direct LT.deposit() calls. Only ADMIN can call it.

The function returns nothing. It emits SetPoolLimit.

The function takes these inputs:

InputTypeDescription
pool_iduint256The index of the market in the Factory
pool_limituint256Maximum LEVAMM value of the market after a HybridVault deposit (18 decimals, USD as AMM.value_oracle() reports it)
Contract source: set_pool_limit()

The contract source gives this function body:

@external
def set_pool_limit(pool_id: uint256, pool_limit: uint256):
assert msg.sender == ADMIN, "Access"
self.pool_limits[pool_id] = pool_limit
log SetPoolLimit(pool_id=pool_id, limit=pool_limit)

set_pool_limit() reverts with Access if the caller is not ADMIN.


set_allowed_crvusd_vault()​

Allows or disallows a crvUSD vault, with an optional crvUSD vault limit. A limit of 0 means no limit. Only ADMIN can call it.

Pass limit on every call

Pass the limit value on every call. The parameter defaults to 0 (no limit). A call without it resets an earlier limit to 0, also when you only change allowed.

The function returns nothing. It emits SetAllowedCrvusdVault and SetCrvusdVaultLimit.

The function takes these inputs:

InputTypeDescription
vaultaddressThe crvUSD vault address
allowedboolWhether the crvUSD vault is allowed
limituint256crvUSD vault limit: the maximum total required crvUSD of all HybridVaults that use this crvUSD vault (default: 0)
Contract source: set_allowed_crvusd_vault()

The contract source gives this function body:

@external
def set_allowed_crvusd_vault(vault: address, allowed: bool, limit: uint256 = 0):
assert msg.sender == ADMIN, "Access"
self.allowed_crvusd_vaults[vault] = allowed
self.crvusd_vault_limits[vault] = limit
log SetAllowedCrvusdVault(vault=vault, allowed=allowed)
log SetCrvusdVaultLimit(crvusd_vault=vault, limit=limit)

set_allowed_crvusd_vault() reverts with Access if the caller is not ADMIN.


set_crvusd_vault_limit()​

Sets the crvUSD vault limit: the maximum total required crvUSD of all HybridVaults that use the crvUSD vault. 0 means no limit. Only ADMIN can call it.

The function returns nothing. It emits SetCrvusdVaultLimit.

The function takes these inputs:

InputTypeDescription
crvusd_vaultaddressThe crvUSD vault address
limituint256The crvUSD vault limit: the maximum total required crvUSD allowed
Contract source: set_crvusd_vault_limit()

The contract source gives this function body:

@external
def set_crvusd_vault_limit(crvusd_vault: address, limit: uint256):
assert msg.sender == ADMIN, "Access"
self.crvusd_vault_limits[crvusd_vault] = limit
log SetCrvusdVaultLimit(crvusd_vault=crvusd_vault, limit=limit)

set_crvusd_vault_limit() reverts with Access if the caller is not ADMIN.


update_vault_required()​

Updates the tracked required crvUSD of a HybridVault. On an increase with check_limit=True, the function enforces the crvUSD vault limit. On a decrease, it reduces the total for the crvUSD vault. Only registered HybridVaults can call it. The function returns nothing.

Allowlist removal blocks new requirement

When check_limit=True, the function also requires allowed_crvusd_vaults[crvusd_vault]. After a crvUSD vault leaves the allowlist, deposits cannot increase the requirement (revert "Vault wind-down"). Withdrawals call with check_limit=False, so they still update the requirement.

The function takes these inputs:

InputTypeDescription
crvusd_vaultaddressThe crvUSD vault address the HybridVault uses
new_requireduint256The new total required crvUSD for this HybridVault
check_limitboolIf True, revert when an increase exceeds the crvUSD vault limit (default: True)
Contract source: update_vault_required()

The contract source gives this function body:

@external
def update_vault_required(crvusd_vault: address, new_required: uint256, check_limit: bool = True):
hybrid_vault: HybridVault = HybridVault(msg.sender)
assert self.vault_to_user[hybrid_vault] != empty(address), "Only vaults can call"

old_required: uint256 = self.crvusd_vault_required[hybrid_vault]

if new_required > old_required:
increase: uint256 = new_required - old_required
total: uint256 = self.crvusd_vault_total_required[crvusd_vault] + increase
if check_limit:
assert self.allowed_crvusd_vaults[crvusd_vault], "Vault wind-down"
vault_limit: uint256 = self.crvusd_vault_limits[crvusd_vault]
if vault_limit > 0:
assert total <= vault_limit, "Beyond vault limit"
self.crvusd_vault_total_required[crvusd_vault] = total
elif new_required < old_required:
decrease: uint256 = old_required - new_required
self.crvusd_vault_total_required[crvusd_vault] -= decrease

self.crvusd_vault_required[hybrid_vault] = new_required

update_vault_required() reverts with these strings:

StringCause
Only vaults can callThe caller is not a HybridVault that this factory created.
Vault wind-downOn an increase with check_limit=True: the crvUSD vault is not on the allowlist.
Beyond vault limitOn an increase with check_limit=True: the new total is above a crvUSD vault limit that is not 0.

lt_allocate_stablecoins()​

Allocates stablecoins to an LT, the market's vault contract, through the Factory admin. Only registered HybridVaults can call it. The function reads FACTORY.admin(), which returns the HybridFactoryOwner. It then calls lt_allocate_stablecoins() on that contract. The function returns nothing.

At HybridFactoryOwner, the caller is this factory, so this factory must be a limit setter (HybridFactoryOwner.limit_setters). HybridFactoryOwner applies its 95% floor: it raises the limit to at least 95% of the LP-priced collateral value of the market. For the full allocation rules, read HybridFactoryOwner: Stablecoin allocation.

The function takes these inputs:

InputTypeDescription
ltaddressThe LT to allocate stablecoins for
limituint256The allocation limit
Contract source: lt_allocate_stablecoins()

The contract source gives this function body:

@external
def lt_allocate_stablecoins(lt: address, limit: uint256):
assert self.vault_to_user[HybridVault(msg.sender)] != empty(address), "Only vaults can call"
extcall (staticcall FACTORY.admin()).lt_allocate_stablecoins(lt, limit)

lt_allocate_stablecoins() reverts with Only vaults can call if the caller is not a HybridVault that this factory created. HybridFactoryOwner.lt_allocate_stablecoins reverts pass through.

  • HybridVault: the vault that this factory deploys, and the calls that each owner makes.
  • Factory: the YieldBasis Factory, whose admin receives the forwarded allocation calls.
  • HybridFactoryOwner: the Factory admin, which needs this factory as a limit setter.
  • Hybrid Vaults: how the backing requirement works for a user, and the current stablecoin fraction.