🏦VaultManager - Borrowing AgTokens
Angle's Borrowing Module User-Facing Contract
Last updated
Angle's Borrowing Module User-Facing Contract
Last updated
Contract Name: VaultManager
The VaultManager
contract is the main contract of Angle Borrowing Module. It is one of the only user-facing contracts. This contract allows people to deposit collateral and open up loans of a given AgToken. It handles all the loan logic (fees and interest rate) as well as the liquidation logic.
There is one VaultManager
contract per collateral type for a given stablecoin. You may have for instance two different VaultManager
contract for ETH as a collateral, with for instance two different set of parameters.
This contract is also a standard NFT contract on the ERC721MetadataUpgradeable
standard. Vaults (name of the tokens in this contract) are non fungible, but they can be transferred from one address to another, you can also approve another address to interact with you vault or simply check the number of vaults you own.
Implements IERC721MetadataUpgradeable
, IVaultManager
, ReentrancyGuardUpgradeable
and Initializable
. This contract is upgradeable. This contract implements a version of ERC721Permit
which allows to grant allowance to an address for an ensemble of vautlts through a permit signature.
treasury
: Reference to the Treasury
contract associated to this VaultManager
collateral
: Reference to the collateral handled by this VaultManager
stablecoin
: Stablecoin handled by this contract. Another VaultManager
contract could have the same rights as this VaultManager
on the stablecoin contract
oracle
: Oracle contract to get access to the price of the collateral with respect to the stablecoin
veBoostProxy
: Reference to the contract which computes adjusted veANGLE balances for liquidators boosts
dust
: Minimum amount of debt a vault can have
_dustCollateral
: Minimum amount of collateral (in stablecoin value) that can be left in a vault during a liquidation where the health factor function is decreasing
debtCeiling
: Maximum amount of stablecoins that can be issued with this contract
xLiquidationBoost
, yLiquidationBoost
: Threshold veANGLE balance values and value of the boost for liquidators at these threshold values: the length of these arrays should be 2
collateralFactor
: Encodes the maximum ratio stablecoin/collateral a vault can have before being liquidated. It's what determines the minimum collateral ratio of a position
targetHealthFactor
: Maximum Health factor at which a vault can end up after a liquidation (unless it's fully liquidated)
borrowFee
: Upfront fee taken when borrowing stablecoins
repayFee
: Fee taken when repaying a stablecoin debt
interestRate
: Per second interest taken to borrowers taking agToken loans
liquidationSurcharge
: Fee taken by the protocol during a liquidation. Technically, this value is not the fee per se, it's 1 - fee. For instance for a 2% fee, liquidationSurcharge
should be 98%.
maxLiquidationDiscount
: Maximum discount given to liquidators
whitelistingActivated
: Whether whitelisting is required to own a vault or not
paused
: Whether the contract is paused or not
lastInterestAccumulatorUpdated
: Timestamp at which the interestAccumulator
was updated
interestAccumulator
: Keeps track of the interest that should accrue to the protocol. The stored value is not necessarily the true value: this one is recomputed every time an action takes place within the protocol.
totalNormalizedDebt
: Total normalized amount of stablecoins borrowed
surplus
: Surplus accumulated by the contract: surplus is always in stablecoins, and is then reset when the value is communicated to the treasury contract
badDebt
: Bad debt made from liquidated vaults which ended up having no collateral and a positive amount of stablecoins
vaultData
: Maps a vaultID
to its data (namely collateral amount and normalized debt)
isWhitelisted
: Maps an address to whether it's whitelisted and can open or own a vault
This contract has all the mappings and data associated to ERC721 Contracts. Most of them are internal variables which are not accessible externally (like _vaultApprovals
, _owners
, _balances
or _operatorApprovals
). The only publicly available is the vaultIDCount
which is a counter to generate a unique vaultID
for each vault: vaultID
acts as tokenID
in basic ERC721 contracts.
There is also a _nonces
mapping to used to check permit signatures to the contract.
Like in all contracts of this Borrowing Module, restricted functions are checked upon calling the Treasury
contract which then calls its associated CoreBorrow
contract.
All standard EIP721 methods are implemented, such as balanceOf()
, ownerOf()
, approve()
, transferFrom
, etc.
The implementation of these methods is derived from OpenZeppelin's library.
A getControlledVaults
function was also added. It allows to check all the vaults controlled by an address. This is an expensive function that should never be called on-chain as it iterates over all vaultIDs. It is here to reduce dependency on an external graph to link a vaultID ID to its owner.
Beyond these base methods, the contract includes a permit method to grant (or revoke) approval to an address for all the associated vaults of the contract.
Parameters:
owner
: Address which signed the message
spender
: Address to grant or revoke allowance to
approved
: Whether to grant or revoke allowance
deadline
: Timestamp till which the signature is valid
v, r, s
: Signature data
Examples of how to sign can be found in the open-source borrow-contracts
repo.
Everything in the VaultManager
contract was designed to be composable in a single transaction: this can be done with the angle()
functions.
There are two different versions of the function, performing in the end the same actions.
And:
These functions allow composability between calls to the different entry points of this module. Any user calling this function can perform any of the allowed actions in the order of their choice.
This function is optimized to reduce gas cost due to payment from or to the user and that expensive calls or computations (like oracleValue
) are done only once
Parameters:
actions
: Set of actions to perform. Each action corresponds to a different integer.
datas
: Array of data to be decoded for each action. Each item in this array is the abi-encoded version of different parameters for each version: parameters can include like the vaultID
or the stablecoinAmount
to borrow for instance. Each action expects particular data to be given (more detail below). The datas
array should have the same length as the actions
array.
from
: Address from which stablecoins will be taken if one action includes burning stablecoins. This address should either be the msg.sender
or be approved by the latter
to
: Address to which stablecoins and/or collateral will be sent in case of
The following parameters are optional and can be avoided if you are using the second version of the function. For about how to use these parameters correctly, you can look at this guide we have built on auto-leverage and capital-efficiency within Angle Borrowing Module.
who
: Address of the contract to handle in case of repayment of stablecoins from received collateral
repayData
: Data to pass to the repayment contract in case of
Return Value:
paymentData
: Struct containing the final transfers executed. This struct contains four different fields (named from the protocol's perspective):
stablecoinAmountToGive
: Stablecoin amount the contract has given to the to
address
stablecoinAmountToReceive
: Stablecoin amount taken from the from
address to be burnt by the contract
collateralAmountToGive
: Collateral amount the contract has given to the to
address
collateralAmountToReceive
: Collateral amount taken from the msg.sender
address Any contract interfacing with the vaultManager
Below, we detail the list of different actions, their corresponding integers and the data that should be passed for actions to be correctly processed. Note that for all actions requiring a vaultID
, specifiying a vaultID = 0
will automatically process the last vault created.
This action enables vault creation within the protocol. It just creates the structure without any collateral or stablecoins with it. It should be combined with other actions to borrow (like an addCollateral
and a borrow
action to get stablecoins).
Type of the parameter:
address
: address for which the vault should be created
This action enables vault closing within the protocol. An address closing a vault is expected to reimburse its full debt, and gets in exchange the value of the collateral in it. The protocol is coded so that vault closing can be done capital-efficiently. For more details about this, you can look at this guide.
Only the address that owns the vault or addresses that are approved for it can close a vault.
Type of the parameter:
uint256
: ID of the vault to close
Adds collateral in an existing vault. This function can be done in conjunction with a borrow action to collateralize a vault and get stablecoins in just a single transaction.
Any address can add collateral on any vault.
Type and order of the parameters:
uint256
: ID of the vault to add collateral to
uint256
: Amount of collateral to add
Removes collateral from an existing vault. Only addresses that are approved for the concerned vault can perform this action. A solvency check is performed after the operation meaning that removing collateral cannot place an address in a liquidation situation.
Type and order of the parameters:
uint256
: ID of the vault to remove collateral from
uint256
: Amount of collateral to remove
Mints stablecoins from a collateralized vault. A solvency check is performed after the operation meaning that you may be limited by the amount of borrowed stablecoins. This function takes into account borrowing fees. Only addresses that are approved for the concerned vault can perform this action.
If a vault has no stablecoins borrowed, then the first borrow action should make sure to borrow more than the dust
of the vaultManager
.
Type and order of the parameters:
uint256
: ID of the vault for which stablecoins should be borrowed
uint256
: Amount of stablecoins to borrow. If there are borrowing fees, the amount of stablecoins actually going to the to
address
Repays a portion of the debt of a vault. Any address can repay debt on behalf of any vault. During debt repayment, you can specify a stablecoin amount that is voluntarily too high with respect to the debt of the vault: it will repay all the debt remaining. Action will revert though if the amount of debt repaid makes the debt of the vault smaller than the dust.
Type and order of the parameters:
uint256
: ID of the vault for which debt should be repaid
uint256
: Amount of stablecoins to repay. Setting an amount higher than the debt of the vault will simply reimburse all the outstanding debt in the vault. In this case, stablecoin transfer from the from
address will just be equal to the value of the debt, meaning if you use type(uint256).max
, you will just be charged the value of the debt
Gets debt in a vault from another vault potentially in another VaultManager
contract. Only approved addresses by the source vault owner can perform this action, however any vault from any VaultManager
contract of the same stablecoin can see its debt reduced by this means. A solvency check is performed after the debt increase in the source vaultID
.
Type and order of the parameters:
uint256
: ID of the vault in this contract for which debt should be increased
address
: Address of the VaultManager
contract where the vault from which debt should be taken is: it can well be the same VaultManager
contract. If a wrong VaultManager
address is specified (like a contract with specifically the right interface), then this function may just increase the debt in a vault without reducing the debt in another one.
uint256
: ID of the vault from which debt should be taken. This operation will technically just reduce the debt of this vault. If there are different minting fees in both contracts, then this action will make sure that everything is as if for the borrowers they had been the same
uint256
: Amount of stablecoins to increase the debt of
Action to approve the VaultManager
for the collateral (if the specific collateral used supports this).
Type and order of the parameters:
address
: Owner address which has signed the permit and wants to approve the `VaultManager``
uint256
: Amount of collateral to approve
uint256
: Deadline parameter for the permit
uint256
: v
parameter for the permit. This uint256
is casted to uint8
in the contract
bytes32
: r
parameter
bytes32
: s
parameter
There are some wrappers for actions above and other external interaction methods.
It creates a vault that is going to be owned by toVault
and returns the ID of this vault.
This is the function that is called by other VaultManager
contracts if these contracts were requested a getDebtIn
action with debt from a vault in the contract. Hence only VaultManager
of the same treasury contracts can call it. This function ultimately reduces the debt of vaultID
by stablecoinAmount
.
The sender of this action specifies the senderBorrowFee
to make sure that if the senderBorrowFee
is greater than the borrow fee in the contract, then borrow fees are still paid on the debt transferred.
Liquidation plays an essential role in Angle Borrowing Module and liquidations of the vaults of a VaultManager
contract are handled directly in the same contract. There are two external access functions for liquidators to liquidate.
And:
The first function is a more advanced function you can use for capital-efficient liquidations, the second one is a wrapper built on top of the first.
For a complete guide on how to use these functions and how to perform liquidations in Angle Borrowing Module, you can look at this docs.
These functions can be used to liquidate an ensemble of vaults specified by their vault IDs. They revert if one of the ID concerns a vault that does not exist or that should be liquidated. It is recommended to call the checkLiquidation
function for each vault to liquidate before calling for a liquidation.
Parameters:
vaultIDs
: List of the vaults to liquidate
amounts
: Array of amount of stablecoins to bring for the liquidation of each vault. If an amount is too big, it is going to be rounded down by the contract to the max possible amount to repay for an unhealthy vault
from
: Address from which stablecoins for the liquidations will be takne
to
: Address to which collateral will be sent
who
: This is an optional argument (could be the zero address), and can be used in case a specific contract should handle the repayment
data
: Optional again, it can help the who
contract to know which swap to proceed
Return Values:
liqOpp
: This is a struct with several info on the liquidations that were made by this function. Note that these returned values are denominated from a protocol perspective.
stablecoinAmountToReceive
: This is the amount of stablecoins the liquidator has given for the liquidation
collateralAmountToGive
: Current amount of collateral obtained from the protocol from the contract
badDebtFromLiquidation
: Bad debt accrued across the liquidation process in the VaultManager
oracleValue
: Oracle value at which the liquidation took place
newInterestAccumulator
: Value of the interestAccumulator
at the time of the call (this is a value used to track vaults outstanding debt)
As mentionned above, this function checks whether a given vault is liquidable and if yes gives information regarding its liquidation. It is recommended to call it before asking for the liquidation of a given vault.
The address of the liquidator
is optional here: it is here to compute the boost on the discount a liquidator could get.
The returned value is a struct called liqOpp
. It contains data about how much stablecoins can be repaid from the liquidation, what discount the liquidator would get, what collateral amount the liquidator could at most get. For more details on the output of this function, you can check the guide here.
There are two other functions to check the debt of a vault and the total debt accross all vaults at the VaultManager
level. These functions are: getVaultDebt(uint256 vaultID)
and getTotalDebt()
.
Accrues interest accumulated across all vaults to the surplus and sends the surplus to the treasury: this is the function called by the Treasury
contract to pull surplus and bad debt from the contract. The role of the Treasury
contract is then to pool all the surplus and bad debt it got from all VaultManager
to handle governance distribution.
Surplus is technically minted upon calling this function. In the VaultManager
contract, calling this contract resets the surplus
and badDebt
value in storage to zero.
Besides the function above which can only be called by the Treasury
contract, the contract has several restricted functions used by governance (or the guardian) to set parameters:
setUint64
: to change parameters stored as uint64
setDebtCeiling
: to change the debt ceiling of the `VaultManager``
setLiquidationBoostParameters
: sets the parameters for the liquidation booster which encodes the slope of the discount for liquidators
toggleWhitelist
: to toggle permission for vault ownership by any account or to allow/disallow an address in case whitelisting is activated. If this is activated, vaults that existed prior to that will not be transferrable to addresses which are not whitelisted, and new vaults will only be openable by whitelisted addresses
setOracle
: to set the oracle contract
setTreasury
: to change the reference to the Treasury
contract (this function should never be used in practice)
togglePause
: to pause the contract
setBaseURI
: to change the ERC721 Metadata URI