SAFE Engine
See SAFEEngine.sol for more details.
1. Introduction
The SAFE Engine serves as the central component of the Azos framework, managing data on user-owned SAFEs and the interest rates for different forms of collateral. It performs the following functions:
- Monitoring the debt generated at the system level, by a specific collateral type, or by individual SAFEs.
- Facilitating the internal movement of coins, collateral, or debt between accounts.
- Seizing collateral from SAFEs, usually during liquidation events.
- Managing account permissions.
- Implementing debt caps on a global scale, as well as for each type of collateral and individual SAFE.
Users have the ability to alter the status of their SAFEs via the SAFE Engine, provided that the collateralization ratio remains above the designated minimum threshold.
Notice: The SAFE Engine relies on join adapter contracts to hold the balance of ERC20 tokens of the system. Transfers within the system are handled entirely by the SAFE Engine, which does not handle any ERC20 tokens directly.
2. Contract Details
Key Methods:
Permissioned
transferCollateral
: Transfers collateral from one account to another.transferInternalCoins
: Transfers coins from one account to another.transferSAFECollateralAndDebt
: Transfers collateral and debt from one SAFE to another.modifySAFECollateralization
: Locks/Releases collateral in a SAFE, and/or generates/repays a SAFE's debt.
Authorized
updateCollateralPrice
: Updates the prices of a collateral type.updateAccumulatedRate
: Updates the accumulated rate of a collateral type.modifyCollateralBalance
: Modifies the collateral balance of an account.confiscateSAFECollateralAndDebt
: Confiscates collateral and debt from a SAFE.disableContract
: Locks the SAFEs, accumulated rates, and collateral prices from being modified.
Required Authorities:
- Oracle Relayer: needs authorization to call
updateCollateralPrice
. - Tax Collector: needs authorization to call
updateAccumulatedRate
. - Liquidation Engine: needs authorization to call
confiscateSAFECollateralAndDebt
. - Coin Join: needs authorization to call
transferInternalCoins
. - Collateral Join: needs authorization to call
modifyCollateralBalance
. - Global Settlement: needs authorization to call
disableContract
.
Contract Parameters:
Global
globalDebtCeiling
: The max amount of debt that can be generated by the system.safeDebtCeiling
: The max amount of debt that can be generated by a SAFE.
Per Collateral Type
debtCeiling
: The max amount of debt that can be generated globally by the collateral type.debtFloor
: The min amount of debt that can be generated by a SAFE of that collateral type.
Contract Data:
Global
globalDebt
: The amount of debt that was generated by the system.globalUnbackedDebt
: The amount of debt that was generated by the system and is not backed by collateral.
Per Collateral Type
debtAmount
: The amount of debt that was generated by the collateral type.lockedAmount
: The amount of collateral that is locked in all SAFEs using the collateral type.accumulatedRate
: A value that represents the accumulated interest rate.safetyPrice
: The price of the collateral type (vs ZAI) at which the SAFE is considered unsafe.liquidationPrice
: The price of the collateral type (vs ZAI) at which the SAFE is considered liquidatable.
A user action should never be able to modify the SAFE collateralization resulting in an unsafe state. When an update the
safetyPrice
leaves the SAFE in an unsafe state, the user may only add collateral and/or repay debt in order to restore the SAFE's collateralization to a safe state.Notice: The
lockedAmount
is a storage variable used to visibilize the total amount of collateral that backs thedebtAmount
. However, this value can be artificially increased, by creating a SAFE without debt and locking collateral in it: on the event of Global Settlement, this user would be allowed to withdraw all of his collateral and it would not be accounted for the redemptions.
3. Key Mechanisms & Concepts
ACCOUNTs vs SAFEs
The SAFE Engine handles 2 different types of entities:
- ACCOUNTs:
- May have coins and collateral (non confiscatable) balance
- May have SAFEs (one for each collateral type)
- May have authorized accounts to modify their balance (or SAFEs)
- May have in some cases (unbacked) debt (confiscatable)
- SAFEs:
- Defined by the account's address (owner) and a collateral type
- May only have locked collateral and generated debt
- May be modified by the owner account, or by authorized accounts
Notice: The protocol may be able to confiscate collateral from SAFEs, but not from ACCOUNTs, all debt generated to ACCOUNTs is considered unbacked. Core contracts of the protocol may have debt, and they should try to settle it by destroying COINs in their balance.
The Collaterals & SAFEs Accountances
A SAFE consists of the following information:
account
: The address of the owner.collateralType
: The collateral type identifier.generatedDebt
: The amount of debt that was generated.lockedCollateral
: The amount of collateral that is locked in.
The SAFE Engine also tracks per collateral type the following information:
accumulatedRate
: A value that represents the accumulated interest rate.safetyPrice
: The price of the collateral type (vs ZAI) at which the SAFE is considered unsafe.liquidationPrice
: The price of the collateral type (vs ZAI) at which the SAFE is considered liquidatable
A SAFE is considered healthy when the following condition is met:
lockedCollateral * safetyPrice >= generatedDebt * accumulatedRate
COIN, DEBT, and ZAI Dynamics
In this system, COINs and DEBT function similarly to matter and antimatter, created and annulled in pairs. The ZAI token acts as the ERC20 counterpart of a COIN when it's outside the system, maintaining a redeemable 1:1 ratio. The system enables users to lock COINs to mint ZAI or burn ZAI to unlock COINs, making them operable within the framework.
DEBT, when unsecured, can be nullified using COINs on a 1:1 basis. However, within SAFEs, the relationship between generatedDebt
and generated COINs diverges from the 1:1 ratio.
Interest Accumulation
The "accumulated rate" represents the constantly accumulating interest or fees on the outstanding ZAI-generated debt. Calculated over time, it's based on the amount of ZAI minted and the applicable stability fee rate. This ensures that the debt owed by users evolves due to the ongoing addition of the stability fee. Whenever a user mints or repays ZAI, the formula for generatedDebt
is:
coinAmount = generatedDebt * accumulatedRate
Here, coinAmount
is the number of coins the user wishes to mint or burn, and accumulatedRate
is the relevant collateral's accumulated interest rate. The coinAmount
could also be a negative figure, indicating debt dissolution.
Example: Assume a 10% annual interest rate on collateral TKN.
Initially, the
accumulatedRate
is 1. When Alice mints 100 ZAI tokens (COINs) from the SAFE Engine, her resulting debt is100 * 1 = 100
.After one year, Alice repays her 100 debt. Now, the
accumulatedRate
stands at 1.1, requiring Alice to repay100 * 1.1 = 110
ZAI tokens.Concurrently, Bob mints 100 ZAI, resulting in a (
100 / 1.1
)90.9
debt.By year two, the
accumulatedRate
becomes 1.21. To repay his debt, Bob needs90.9 * 1.21 = 110
ZAI tokens, identical to Alice's amount.Note: Negative interest rates could technically be implemented using the same mechanics.
Whenever the system refreshes the accumulatedRate
, it leads to a surplus of COINs that get allocated to an unspecified address. This surplus emerges from the updateAccumulatedRate
function, invoked by the Tax Collector. Importantly, these additional COINs are not directly extracted from any SAFE; instead, they manifest as a simultaneous debt increment for all SAFEs holding the taxed collateral type.
Transfer Collateral and Coins events
Since the transfer of collateral and coins is handled by the SAFE Engine, the events TransferCollateral
and TransferInternalCoins
are emitted by the SAFE Engine, and not by the ERC20 contracts. The events emitted by the SAFE Engine try to follow the same structure as the ERC20 events, but with the addition of the collateral type identifier in the TransferCollateral
event.
Generating Debt and minting ZAI:
- Lock collateral in a SAFE:
- Deposit TKN:
TransferCollateral(TKN, source, ACCOUNT, amount)
- Lock TKN:
TransferCollateral(TKN, ACCOUNT, 0, amount)
TransferCollateral(TKN, 0, SAFE, amount)
- Generate DEBT:
TransferInternalCoins(0, ACCOUNT, amount)
- Mint ZAI:
TransferInternalCoins(ACCOUNT, COIN_JOIN, amount)
ERC20.Transfer(0, destination, amount)
- Deposit TKN:
4. Gotchas
Permissioned vs Authorized
Authorized accounts are addresses allowed to call SAFE Engine isAuthorized
methods. While a SAFE can add approval to an account, which gives permission to the account to modify the SAFE's state (on isSAFEAllowed
methods), this account is not authorized to call SAFE Engine authorized methods (isAuthorized
).
SAFE State vs COIN and DEBT
The generatedDebt
and lockedCollateral
of a SAFE is measured in WAD units, while the COINs and DEBT (that gets limited, for example, by the debt ceilings) are measured in RAD units.
Notice: The reason for this is that the resultant
COIN|DEBT
is calculated by:generatedDebt[WAD] * accumulatedRate[RAY] = COIN|DEBT[RAD]
Collateral Balance vs Locked Collateral
As stated before, an ACCOUNT can have collateral balance, and an account's SAFE can have locked collateral. The difference between these two is that the collateral balance is the amount of collateral that the user has available to use (transfer or withdraw), and the locked collateral is the amount of collateral that the user has locked in a SAFE, that the user would need to modify the SAFE collateralization in order to use.
Unbacked Debt and Debt Settlement
Unbacked debt, or debt that is accounted to an ACCOUNT (not a SAFE with locked collateral) is only generated to contracts of the protocol. This debt can only be settled by having an equal an equal amount of COINs in the ACCOUNT's balance, and calling settleDebt
with the amount of DEBT/COINs to destroy.
This debt is only generated when a SAFE gets liquidated, a portion of the SAFE's collateral is transferred to the Collateral Auction House, the SAFE's debt is transferred to the Accounting Engine (unbacked). The Auction House will the transfer COINs to the Accounting Engine in order for the debt to be settled.
Confiscation of Collateral and Debt
An authorized account may call confiscateSAFECollateralAndDebt
to confiscate the locked collateral and/or debt of a SAFE. This method does not perform any checks on the SAFE's state. The flow of value is inverse when it happens during liquidations, than when the system is under global settlement. This means that this method is always authorized to arbitrarily modify the SAFE's state (and the DEBT balance of an account), even after the system was shutdown.
5. Failure Modes
Parameters misconfiguration
- Low
globalDebtCeiling
may limit user borrowing. - High
globalDebtCeiling
risks debt overload. - Low
safeDebtCeiling
hampers individual borrowing. safeDebtCeiling
aboveglobalDebtCeiling
is likely moot.- Low
cType.debtCeiling
curbs borrowing for specific collateral. cType.debtCeiling
aboveglobalDebtCeiling
is typically irrelevant.- Low
cType.debtFloor
raises liquidation risks. - High
cType.debtFloor
deters small borrowers.
Liquidation mechanics
Despite the fact that the SAFE Engine holds the latest collateral prices and SAFE state, the liquidation of a SAFE is handled by the Liquidation Engine. The Liquidation Engine is authorized to call confiscateSAFECollateralAndDebt
, which doesn't perform healthy checks, and allows it to modify the SAFE's state in an arbitrary way. If the Liquidation Engine (or any authorized address) is misconfigured, it may result in the liquidation of SAFEs that are not unsafe, or malicious modifications to any SAFE's state.
Accumulated Rate and Collateral Prices
Both the updateAccumulatedRate
and updateCollateralPrice
are authorized methods that perform no further checks in the validity of the parameters passed. If the Oracle Relayer or the Tax Collector (or any other authorized address) are misconfigured, it may result in the accumulatedRate
or safetyPrice
being set to an arbitrary value.
If the accumulatedRate
for a given collateral type is set to 0
, the collateral type may be bricked beyond repair, as the accumulatedRate
is iteratively calculated by multiplying the previous value to a multiplier.