HybridVaultFactory
Source: HybridVaultFactory.vy on GitHub
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, callstablecoin_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).ADMINcan set a personal limit for each HybridVault withHybridVault.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 contractADMIN(address) - Admin address, read once in the constructor:factory.admin().ADMIN()(Factory → HybridFactoryOwner → ADMIN).ADMINdoes not follow a later change of the Factory admin. In contrast,lt_allocate_stablecoinsreadsFACTORY.admin()at each call.
State variables
vault_impl(address) - Current vault implementation for new deploymentsuser_to_vault(HashMap[address, HybridVault]) - Maps user address to vaultvault_to_user(HashMap[HybridVault, address]) - Maps vault to user addressstablecoin_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 vaultscrvusd_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 vaultcrvusd_vault_required(HashMap[HybridVault, uint256]) - Required crvUSD tracked for each HybridVault
Events
The factory emits these events. HybridVault is an address in the ABI.
| Event | Fields |
|---|---|
VaultCreated | user: indexed(address), vault: indexed(HybridVault) |
SetVaultImpl | impl: address |
SetStablecoinFraction | stablecoin_fraction: uint256 |
SetPoolLimit | pool_id: uint256, limit: uint256 |
SetAllowedCrvusdVault | vault: address, allowed: bool |
SetCrvusdVaultLimit | crvusd_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:
| Input | Type | Description |
|---|---|---|
crvusd_vault | address | The 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:
| String | Cause |
|---|---|
Already created | The caller already has a HybridVault. |
Vault not allowed | crvusd_vault is not on the allowlist. |
Vault impl not set | vault_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:
| Input | Type | Description |
|---|---|---|
impl | address | New 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:
| Input | Type | Description |
|---|---|---|
frac | uint256 | The 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:
| Input | Type | Description |
|---|---|---|
pool_id | uint256 | The index of the market in the Factory |
pool_limit | uint256 | Maximum 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 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:
| Input | Type | Description |
|---|---|---|
vault | address | The crvUSD vault address |
allowed | bool | Whether the crvUSD vault is allowed |
limit | uint256 | crvUSD 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:
| Input | Type | Description |
|---|---|---|
crvusd_vault | address | The crvUSD vault address |
limit | uint256 | The 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.
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:
| Input | Type | Description |
|---|---|---|
crvusd_vault | address | The crvUSD vault address the HybridVault uses |
new_required | uint256 | The new total required crvUSD for this HybridVault |
check_limit | bool | If 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:
| String | Cause |
|---|---|
Only vaults can call | The caller is not a HybridVault that this factory created. |
Vault wind-down | On an increase with check_limit=True: the crvUSD vault is not on the allowlist. |
Beyond vault limit | On 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:
| Input | Type | Description |
|---|---|---|
lt | address | The LT to allocate stablecoins for |
limit | uint256 | The 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.
Related
- 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.