Accounting Engine
1. Summary
The AccountingEngine
receives both system surplus and system debt. It covers deficits via debt auctions and disposes off surplus via auctions (Burning/RecyclingSurplusAuctionHouse
) or transfers (to extraSurplusReceiver
).
2. Contract Variables & Functions
Variables
contractEnabled
- settlement flag (1
or0
).authorizedAccounts[usr: address]
- addresses allowed to callmodifyParameters()
anddisableContract()
.safeEngine
- address of theSAFEEngine
.surplusAuctionHouse
- address of thePreSettlementSurplusAuctionHouse
.debtAuctionHouse
- address of theDebtAuctionHouse
.systemStakingPool
- the system's staking pool acting as lender of first resort.extraSurplusReceiver
- address that receives surplus in case the strategy to get rid of excessive surplus is transfer instead of auction.debtQueue[timestamp: uint256]
- the system debt queue. TheLiquidationEngine
adds a new debt block every time it starts a new collateral auction. Each block can be popped out of the list afterpopDebtDelay
seconds.debtPoppers[timestamp: uint256]
- mapping that records the addresses which pop debt out ofdebtQueue
.lastSurplusTransferTime
- the last timestamp when the engine transferred extra surplus out.lastSurplusAuctionTime
- the last timestamp when the engine auctioned extra surplus.surplusTransferDelay
- the minimum delay between two consecutive extra surplus transfers.surplusAuctionDelay
- the minimum delay between two consecutive extra surplus auctions.extraSurplusIsTransferred
-0
if extra surplus is auctioned,1
if it's transferred.postSettlementSurplusDrain
- contract meant to auction/dispose off any remaining surplus after theAccountingEngine
is disabled (and in case surplus couldn't be settled with bad debt because of a bug).protocolTokenAuthority
** - address of ** authority contract that says which addresses are able to mint an burn protocol tokens.totalQueuedDebt
- the total amount of debt in the queue.totalOnAuctionDebt
- the total amount of debt being auctioned in theDebtAuctionHouse
.popDebtDelay
- length of time for which a debt block must stay in thedebtQueue
.debtAuctionBidSize
- the fixed amount of debt to be covered by a single debt auction.initialDebtAuctionMintedTokens
- the starting amount of protocol tokens offered to cover the auctioned debt.surplusAuctionAmountToSell
- amount of surplus to be sold in a single surplus auction.surplusTransferAmount
- the amount of extra surplus that is transferred withtransferExtraSurplus()
.surplusBuffer
- threshold that must be exceeded before surplus auctions are possible.disableCooldown
- time that must elapse after theAccountingEngine
is disabled and until it can send all its remaining surplus to thepostSettlementSurplusDrain
. Must be bigger thanGlobalSettlement.shutdownCooldown
.disableTimestamp
- timestamp when theAccountingEngine
was disabled.
Modifiers
isAuthorized
**** - checks whether an address is part ofauthorizedAddresses
(and thus can call authed functions).
Functions
modifyParameters(bytes32 parameter
,uint256 data)
- update auint256
parameter.modifyParameters(bytes32 parameter
,address data)
- update anaddress
parameter.addAuthorization(usr: address)
- add an address toauthorizedAddresses
.removeAuthorization(usr: address)
- remove an address fromauthorizedAddresses
.canPrintProtocolTokens() public view returns (bool)
- returnstrue
ifsystemStakingPool
is null or ifsystemStakingPool.canPrintProtocolTokens()
reverts. Returns true or false depending on whatsystemStakingPool.canPrintProtocolTokens()
returns.pushDebtToQueue(debtBlock: uint256)
- adds a bad debt block to the auctions queue.popDebtFromQueue(timestamp: uint256)
- release a debt block from the debt queue.settleDebt(rad: uint256)
- callssettleDebt
on thesafeEngine
in order to cancel out surplus and debt.cancelAuctionedDebtWithSurplus(rad: uint256)
- cancels out surplus coming fromDebtAuctionHouse
auctions and auctioned (bad) debt.auctionSurplus()
- trigger a surplus auction (SurplusAuctionHouse.startAuction
).transferExtraSurplus()
- transfers extra surplus (above thesurplusBuffer
) from the engine toextraSurplusReceiver
.auctionDebt()
- trigger a deficit auction (DebtAuctionHouse.startAuction
).settleDebtAuction(id: uint256)
- authed function meant to be called bydebtAuctionHouse
in order to signal that a specific auction settled.transferPostSettlementSurplus()
- transfer any post settlement, leftover surplus to thepostSettlementSurplusDrain
. Meant to be a backup in caseGlobalSettlement.processSAFE
has a bug (cannot process a specific Safe), governance doesn't have power over the system and there's still surplus left in theAccountingEngine
which then blocksGlobalSettlement.setOutstandingCoinSupply
.disableContract()
- setcontractEnabled
to zero and settle as much remaining debt as possible (if any)
Events
AddAuthorization
- emitted when a new address becomes authorized. Contains:account
- the new authorized account
RemoveAuthorization
- emitted when an address is de-authorized. Contains:- account - the address that was de-authorized
ModifyParameters
- emitted when a parameter is modifiedPushDebtToQueue
- emitted when a new debt block is added todebtQueue
. Contains:timestamp
- timestamp at which the debt block is pushed intodebtQueue
debtQueueBlock
- the size of the debt blocktotalQueuedDebt
- total amount of queued debt in the queue
PopDebtFromQueue
- emitted when a debt block is popped out of thedebtQueue
. Contains:timestamp
- timestamp from which all the debt is popped out of the queuedebtQueueBlock
- amount of debt popped from the queuetotalQueuedDebt
- total remaining amount of queued debt in the queue
SettleDebt
- emitted when an amount of debt that is not queued or in a debt auction is settled with an equal amount of surplus. Contains:rad
- the amount of debt to settlecoinBalance
- the remaining amount of surplus after the debt is settleddebtBalance
- the remaining amount of debt afterrad
debt is settled
CancelAuctionedDebtWithSurplus
- emitted when the contract settles debt that was in a debt auction. Contains:rad
- amount of auctioned debt to settletotalOnAuctionDebt
- remaining amount of debt being auctionedcoinBalance
- theAccountingEngine
's coin balance after the debt is settleddebtBalance
- remaining amount of debt
AuctionDebt
- emitted when a new debt auction starts. Contains:id
- the ID of the new debt auctiontotalOnAuctionDebt
- total debt being auctioned across all debt auctionsdebtBalance
- theAccountingEngine
's debt balance in theSAFEEngine
AuctionSurplus
- emitted when a new surplus auction starts. Contains:id
- the ID of the new surplus auctionlastSurplusAuctionTime
- the current timestamp which is now the last time when a surplus auction was triggeredcoinBalance
- theAccountingEngine
's surplus balance
DisableContract
- emitted when the contract is disabledTransferPostSettlementSurplus
- emitted when any remaining surplus after the contract is disabled is transferred to thepostSettlementSurplusDrain.
Contains:postSettlementSurplusDrain
- the address of the surplus draincoinBalance
- the remaining surplus balance of theAccountingEngine
debtBalance
- the remaining debt balance of theAccountingEngine
TransferExtraSurplus
- emitted when extra surplus is transferred toextraSurplusReceiver
. Contains:extraSurplusReceiver
- the address of theextraSurplusReceiver
lastSurplusAuctionTime
- the current block timestampcoinBalance
- the current coin balance of theAccountingEngine
now that some surplus has been transferred
3. Walkthrough
Auctioning Debt
When a SAFE is liquidated, the seized debt is put in a queue in the AccountingEngine
. This occurs at the block timestamp of the liquidateSAFE
action (debtQueue[timestamp]
). It can be released with the help of popDebtFromQueue
once AccountingEngine.popDebtDelay
has expired. Once released, it can be settled using the surplus gathered from the SAFE's liquidation or, if there wasn't enough surplus gathered, the debt can be auctioned using the DebtAuctionHouse
. NOTE: the AccountingEngine
can start a new debt auction only if canPrintProtocolTokens
returns true
and if canPrintProtocolTokens
also doesn't unexpectedly revert.
The main risk is related to popDebtDelay
< CollateralAuctionHouse.totalAuctionLength
which would result in debt auctions starting before the associated collateral auctions could complete.
Auctioning Surplus
When the AccountingEngine
has a surplus balance above the surplusBuffer
(safeEngine.coinBalance[accountingEngine]
> surplusBuffer
), if the extra surplus on top of the buffer is not reserved to nullify the engine's bad debt (safeEngine.debtBalance[accountingEngine]
) and if extraSurplusIsTransferred
is 0
, the extra surplus can be auctioned off using the Burning/RecyclingSurplusAuctionHouse
. This process results in burning protocol tokens that are being offered in exchange for the auctioned surplus.
Transferring Extra Surplus
When the AccountingEngine
has a surplus balance above the surplusBuffer
(safeEngine.coinBalance[accountingEngine]
> surplusBuffer
), if the extra surplus on top of the buffer is not reserved to nullify the engine's bad debt (safeEngine.debtBalance[accountingEngine]
) and if extraSurplusIsTransferred
is 1
, the extra surplus can be transferred to extraSurplusReceiver
.
Disabling the Accounting Engine
When an authorized address calls AccountingEngine.disableContract
the system will try to settle as much remaining safeEngine.debtBalance[accountingEngine]
as possible.
4. Gotchas (Potential source of user error)
- When the
AccountingEngine
is upgraded, there are multiple references to it that must be updated at the same time (GlobalSettlement
,TaxCollector
,CoinSavingsAccount
). - The
AccountingEngine
is the only user with a non-zerototalQueuedDebt
balance (not asafeEngine
invariant as there can be multipleAccountingEngine
s). - CollateralType storage is split across the
SafeEngine
,TaxCollector
,CoinSavingsAccount
andAccountingEngine
modules. TheLiquidationEngine
also stores the liquidation penalty and maximum auction size. - A portion of the Stability Fee is allocated for the Coin Savings Acount by increasing the amount of
totalQueuedDebt
in theAccountingEngine
at everyCoinSavingsAccount.updateAccumulatedRate( )
call. - Setting an incorrect value for
accountingEngine
can cause the surplus to be lost or stolen.
5. Failure Modes (Bounds on Operating Conditions & External Risk Factors)
Safe Liquidation
- A failure mode could arise when no actors call
cancelAuctionedDebtWithSurplus
,popDebtFromQueue
orsettleDebt
to reconcile/queue the debt.
Auctions
- A failure mode could arise if a user does not call
auctionSurplus
orauctionDebt
to kick off auctions. AccountingEngine.popDebtDelay
, when set too high (popDebtDelay
is too long), theauctionDebt
auctions can no longer occur. This provides a risk of undercollateralization.AccountingEngine.popDebtDelay
, when set too low, can cause too manyauctionDebt
auctions, while preventingauctionSurplus
auctions from occurring.AccountingEngine.surplusAuctionAmountToSell
, when set too high, can result in noauctionSurplus
auctions being possible. Thus, if noauctionSurplus
auction takes place, there will be no FLX bidding as part of that process and, accordingly, no automated FLX burn as a result of a successful auction.AccountingEngine.surplusAuctionAmountToSell
, when set too low, results inauctionSurplus
auctions not being profitable for participants (amountToSell
size is worth less than gas cost). Thus, no FLX will be bid during aauctionSurplus
auction and, as a result, there will be no automated FLX burn.AccountingEngine.debtAuctionBidSize
, when set too high, noauctionDebt
auctions are possible. This results in the system not being able to recover from an undercollateralized state.AccountingEngine.debtAuctionBidSize
, when set too low,auctionDebt
auctions are not profitable for participants (where theamountToSell
size is worth less than gas cost). This results in FLX inflation due to automated FLX minting.AccountingEngine.initialDebtAuctionMintedTokens
, when set too high,auctionDebt
auctions risk not being able to close or mint a large amount of FLX, creating a risk of FLX dilution and the possibility of a governance attack.AccountingEngine.initialDebtAuctionMintedTokens
, when set too low,auctionDebt
auctions have to bestartAuction
ed many times before they will be interesting to keepers.AccountingEngine.surplusBuffer
, when set too high, theauctionSurplus
auctions would never occur. If aauctionSurplus
auction does not occur, there is no sale of surplus, and thus, no burning of bid FLX.AccountingEngine.surplusBuffer
, if set too low, can cause surplus to be auctioned off viaauctionSurplus
auctions before it is used to canceldebtBalance
from liquidations, necessitatingauctionDebt
auctions and making the system run inefficiently.