Skip to main content

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 the debtAmount. 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 is 100 * 1 = 100.

After one year, Alice repays her 100 debt. Now, the accumulatedRate stands at 1.1, requiring Alice to repay 100 * 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 needs 90.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)

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 above globalDebtCeiling is likely moot.
  • Low cType.debtCeiling curbs borrowing for specific collateral.
  • cType.debtCeiling above globalDebtCeiling 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.