Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
Loading...
The Maker Protocol is the platform through which anyone, anywhere can generate the Dai stablecoin against crypto collateral assets. Learn how it works.
MakerDAO is a decentralized organization dedicated to bringing stability to the cryptocurrency economy. The Maker Protocol employs a two-token system. The first being, Dai, a collateral-backed stablecoin that offers stability. The Maker Foundation and the MakerDAO community believe that a decentralized stablecoin is required to have any business or individual realize the advantages of digital money. Second, there is MKR, a governance token that is used by stakeholders to maintain the system and manage Dai. MKR token holders are the decision-makers of the Maker Protocol, supported by the larger public community and various other external parties.
Maker is unlocking the power of decentralized finance for everyone by creating an inclusive platform for economic empowerment; enabling everyone with equal access to the global financial marketplace.
With the new version of the Maker Protocol, Multi Collateral Dai (MCD), being released and live on the main Ethereum network, we wanted to go over a few of the changes and features that it comes with. The biggest change to the Maker Protocol is that it now accepts any Ethereum-based asset as collateral to generate Dai given that it has been approved by MKR holders and has been given specific, corresponding Risk Parameters through the Maker decentralized governance process.
Additionally, there are a few other newly introduced features that come with the MCD upgrade. These new features include:
Support for multiple Vault collateral types (Launching with ETH and BAT)
More robust peg ensuring mechanisms (MKR acting as backstop)
Stability fees paid every block, rather than on Dai repayment
MKR and governance remains the same
@ MakerDAO.world
@ manual.makerdao.com
@ docs.makerdao.com ← You are here
@ collateral.makerdao.com
@ mips.makerdao.com
To open a Vault, head to
To use the DSR, head to
Getting Started with Maker Protocol
The DAI token contract and all of the adapters DaiJoin adapters.
Module Name: DAI Module
Type/Category: Proxy —> Dai.sol and DaiJoin.sol
Contract Sources:
The origin of DAI was designed to represent any token that the core system considers equal in value to its internal debt unit. Thus, the DAI Module contains the DAI token contract and all of the adapters DaiJoin adapters.
Key Functionalities (as defined in the smart contract)
Mint
- Mint to an address
Burn
- Burn at an address
Push
- Transfer
Pull
- Transfer From
Move
- Transfer From
Approve
- Allow pulls and moves
Permit
- Approve by signature
Other
name
- Dai Stablecoin
symbol
- DAI
version
- 1
decimals
- 18
totalSupply
- Total DAI Supply
balanceOf(usr: address)
- User balance
allowance(src: address, dst: address)
- Approvals
nonces(usr: address)
- Permit nonce
vat
- storage of the Vat’s address
ilk
- id of the Ilk for which a GemJoin
is created for
gem
- the address of the ilk
for transferring
dai
- the address of the dai
token
one
- a 10^27 uint used for math in DaiJoin
The Dai
contract is the user facing ERC20 contract maintaining the accounting for external Dai balances. Most functions are standard for a token with changing supply, but it also notably features the ability to issue approvals for transfers based on signed messages.
Join
consists of three smart contracts, one of which is the DaiJoin contract. Each join contract is created specifically to allow the given token type to be joined to the vat. Because of this, each join contract has slightly different logic to account for the different types of tokens within the system. The DaiJoin contract allows users to withdraw their Dai from the system into a standard ERC20 token.
There are limited sources of user error in the join
contracts system due to the limited functionality of the system. Barring a contract bug, should a user call join by accident they could always get their tokens back through the corresponding exit call on the given join contract. The main issue to be aware of here would be a well-executed phishing attack. As the system evolves and potentially more join contracts are created, or more user interfaces are made, there is the potential for a user to have their funds stolen by a malicious join contract which does not actually send tokens to the vat, but instead to some other contract or wallet.
There could potentially be a vat
upgrade that would require new join
contracts to be created.
If a gem
contract were to go through a token upgrade or have the tokens frozen while a user's collateral was in the system, there could potentially be a scenario in which the users were unable to redeem their collateral after the freeze or upgrade was finished. This seems to be a small risk though because it would seem likely that the token going through this upgrade would want to work alongside the maker community to be sure this was not an issue.
The Maker Protocol's Core Accounting System
Contract Name: Vat.sol
Type/Category: DSS —> Core System Accounting
The Vat
is the core Vault engine of dss
. It stores Vaults and tracks all the associated Dai and Collateral balances. It also defines the rules by which Vaults and balances can be manipulated. The rules defined in the Vat
are immutable, so in some sense, the rules in the Vat
can be viewed as the constitution of dss
.
gem
: collateral tokens.
dai
: stablecoin tokens.
sin
: unbacked stablecoin (system debt, not belonging to any urn
).
ilks
: a mapping of Ilk
types.
Ilk
: a collateral type.
Art
: total normalized stablecoin debt.
rate
: stablecoin debt multiplier (accumulated stability fees).
spot
: collateral price with safety margin, i.e. the maximum stablecoin allowed per unit of collateral.
line
: the debt ceiling for a specific collateral type.
dust
: the debt floor for a specific collateral type.
urns
: a mapping of Urn
types.
Urn
: a specific Vault.
ink
: collateral balance.
art
: normalized outstanding stablecoin debt.
init
: create a new collateral type.
slip
: modify a user's collateral balance.
flux
: transfer collateral between users.
move
: transfer stablecoin between users.
grab
: liquidate a Vault.
heal
: create / destroy equal quantities of stablecoin and system debt (vice
).
fold
: modify the debt multiplier, creating / destroying corresponding debt.
suck
: mint unbacked stablecoin (accounted for with vice
).
Line
: the total debt ceiling for all collateral types.
frob
: modify a Vault.
lock
: transfer collateral into a Vault.
free
: transfer collateral from a Vault.
draw
: increase Vault debt, creating Dai.
wipe
: decrease Vault debt, destroying Dai.
dink
: change in collateral.
dart
: change in debt.
fork
: to split a Vault - binary approval or splitting/merging Vaults.
dink
: amount of collateral to exchange.
dart
: amount of stablecoin debt to exchange.
wish
: check whether an address is allowed to modify another address's gem or dai balance.
hope
: enable wish
for a pair of addresses.
nope
: disable wish
for a pair of addresses.
Note: art
and Art
represent normalized debt, i.e. a value that when multiplied by the correct rate gives the up-to-date, current stablecoin debt.
debt
is the sum of all dai
(the total quantity of dai issued).
vice
is the sum of all sin
(the total quantity of system debt).
Ilk.Art
the sum of all art
in the urn
s for that Ilk
.
debt
is vice
plus the sum of Ilk.Art * Ilk.rate
across all ilks
.
gem
can always be transferred to any address by it's owner.
dai
can only move with the consent of it's owner.
dai
can always be transferred to any address by it's owner.
The core Vault, Dai, and collateral state is kept in the Vat
. The Vat
contract has no external dependencies and maintains the central "Accounting Invariants" of Dai. The core principles that apply to the vat
are as follows:
Dai cannot exist without collateral:
An ilk
is a particular type of collateral.
Collateral gem
is assigned to users with slip
.
Collateral gem
is transferred between users with flux
.
2. The Vault data structure is the Urn
:
has ink
- encumbered collateral
has art
- encumbered, normalized debt
3. Similarly, a collateral is an Ilk
:
has Art
- encumbered, normalized debt
has rate
- debt scaling factor (discussed further below)
has spot
- price with safety margin
has line
- debt ceiling
has dust
- debt floor
Note: Above, when using the term "encumbered", this refers to being "locked in a Vault".
Vaults are managed via frob(i, u, v, w, dink, dart)
, which modifies the Vault of user u
, using gem
from user v
and creating dai
for user w
.
Vaults are confiscated via grab(i, u, v, w, dink, dart)
, which modifies the Vault of user u
, giving gem
to user v
and creating sin
for user w
. grab
is the means by which Vaults are liquidated, transferring debt from the Vault to a users sin
balance.
Sin represents "seized" or "bad" debt and can be canceled out with an equal quantity of Dai using heal(uint rad
where msg.sender
is used as the address for the dai
and sin
balances.
Note: Only the Vow will ever have sin
, so only the Vow can successfully call heal
. This is because whenever grab
and suck
are called, the Vow's address is passed as the recipient of sin
. Note that this is contingent on the current design and implementation of the system.
Note: heal
can only be called with a positive number (uint) and will sub(dai[u])
along with sub
ing the sin
.
The quantity dai
can be transferred between users with move
.
fold(bytes32 ilk, address u, int rate)
An ilk's rate
is the conversion factor between any normalized debt (art
) drawn against it and the present value of that debt with accrued fees. The rate
parameter to fold
is actually the change in the Ilk.rate
value, i.e. a difference of scaling factors (new - old). It is a signed integer, and hence current account values may increase or decrease. The quantity Ilk.Art*rate
is added to the dai
balance of the address u
(representing an increase or decrease in system surplus); the debt balances of all Vaults collateralized with the specified Ilk
are updated implicitly via the addition of rate
to Ilk.rate
.
For more information on Rates and System Stabilization, see the Rates Module and System Stabilizer Module documentation below:
The methods in the Vat
are written to be as generic as possible and as such have interfaces that can be quite verbose. Care should be taken that you have not mixed the order of parameters.
Any module that is auth
ed against the Vat
has full root access, and can therefore steal all collateral in the system. This means that the addition of a new collateral type (and associated adapter) carries considerable risk.
A bug in the Vat
could be catastrophic and could lead to the loss (or locking) of all Dai and Collateral in the system. It could become impossible to modify Vault's or to transfer Dai. Auctions could cease to function. Shutdown could fail.
The Vat
relies upon a set of trusted oracles to provide price data. Should these price feeds fail, it would become possible for unbacked Dai to be minted, or safe Vaults could be unfairly liquidated.
Governance can authorize new modules against the Vat
. This allows them to steal collateral (slip
) or mint unbacked Dai (suck
/ addition of worthless collateral types). Should the cryptoeconomic protections that make doing so prohibitively expensive fail, the system may be vulnerable and left open for bad actors to drain collateral.
The Vat
relies on external Adapter contracts to ensure that the collateral balances in the Vat
represent real external collateral balances. Adapter contracts are authorized to make arbitrary modifications to all collateral balances. A faulty collateral adapter could result in the loss of all collateral in the system.
(referenced in Join - Detailed Documentation)
DAI
is also susceptible to the known , but should not normally be an issue with unlimited approval. We recommend any users using the approval
for a specific amount be aware of this particular issue and use caution when authorizing other contracts to perform transfers on their behalf.
Keeping the Maker Protocol Stable
Module Name: System Stabilizer
Type/Category: DSS —> System Stabilizer Module (Vow.sol, Flap.sol, Flop.sol)
Contract Sources:
The System Stabilizer Module's purpose is to correct the system when the value of the collateral backing Dai drops below the liquidation level (determined by governance) when the stability of the system is at risk. The system stabilizer module creates incentives for Auction Keepers (external actors) to step in and drive the system back to a safe state (system balance) by participating in both debt and surplus auctions and, in turn, earn profits by doing so.
For a better understanding of how the Auction Keepers relate to the System Stabilization Mechanism, read the following resources:
The System Stabilizer Module has 3 core components consisting of the Vow
, Flop
, and Flap
contracts.
The Vow
represents the overall Maker Protocol's balance (both system surplus and system debt). The purpose of the vow
is to cover deficits via debt auctions and discharge surpluses via surplus auctions.
The Flopper
(Debt Auction) is used to get rid of the Vow
’s debt by auctioning off MKR for a fixed amount of internal system Dai. When flop
auctions are kicked off, bidders compete with decreasing bids of MKR. After the auction settlement, the Flopper sends received internal Dai to the Vow
in order to cancel out its debt. Lastly, the Flopper mints the MKR for the winning bidder.
The Flapper
(Surplus Auction) is used to get rid of the Vow
’s surplus by auctioning off a fixed amount of internal Dai for MKR. When flap
auctions are kicked off, bidders compete with increasing amounts of MKR. After auction settlement, the Flapper burns the winning MKR bid and sends internal DAI to the winning bidder.
Vow
, Flop
and Flap
contracts help the MCD system operate?When the Maker Protocol receives system debt and system surplus through collateral auctions and CDP stability fee accumulation, it will deviate from system equilibrium. The job of the Vow is to bring it back to system equilibrium.
The Priority of the Vow
To kick-off debt and surplus auctions (Flop and Flap), which in turn, corrects the system’s imbalances.
The purpose of debt auctions is to cover the system deficit, which is resembled by Sin
(the total debt in the queue). It sells an amount of minted MKR and purchases Dai that will be canceled 1-to-1 with Sin
.
The Priorities of the Flopper:
To raise an amount of Dai equivalent to the amount of bad debt as fast as possible.
To minimize the amount of MKR inflation.
The purpose of the surplus auctions is to release Dai surplus from the Vow
while users bid with MKR, which will be burned, thus reducing the overall MKR supply. It will sell a fixed amount of Dai to purchase and burn MKR.
The Priority of the Flapper:
To mechanically reduce the MKR supply when auctioning off Dai surplus.
This failure mode is due to the fact that there is nothing the system can do to stop a user from paying significantly more than the fair market value for the token in an auction (this goes for all auction types, flip
, flop
, and flap
).
Keepers that are performing badly are primarily at risk during the dent
phase since they could return too much collateral to the original CDP and end up overpaying (i.e. pay too much Dai (bid
) for too few gems (lot
)).
Note: This does not apply to the Flap contract (Flop and Flip only)
When no Auction Keepers Bid:
For both the Flip
and Flap
auctions, the tick
function will restart an auction if there have been 0 bids and the original end
has passed.
In the case of a Flop
auction expiring without receiving any bids, anyone can restart the auction by calling tick
. Along with restarting the auction, it also includes a necessary increase of the initial lot
size by pad
(default to 50%). This extra process is because the lack of bidding for the lot
could be due market circumstances, where the lot
value is too low and is no longer enough for recovering the bid
amount.
beg
If too high, it would discourage bidding and create a less efficient auction mechanism.
If too low, it would not be that significant but would encourage small bids and increase the likelihood of the Bid.end
being hit before the "true" price was found.
ttl
If too high, it would cause the winning bidder to wait "too long" to collect their winnings (depending on the situation, possibly subjecting them to market volatility).
If too low, it would increase the likelihood of bids expiring before other bidders get a chance to respond.
tau
If too high, it would not have that significant of an impact as auctions would still operate normally based on the Bid.tic
.
If too low, it would increase the chance that an auction will end before the "true" price was found.
Potential Issues around Network Congestion
When auctioning off collateral tokens, bids are finalized after an interval of time (ttl
) has passed. Hence, in the case when extreme network congestion occurs, ttl
and auctions are affected because they can take longer than three hours to confirm a transaction. Therefore, due to Ethereum network congestion, this can result in auctions settling for less than the fair market value. Due to this potential issue, bidders need to calculate network congestion risks when making bids.
Example:
When high network congestion occurs, where common APIs can be shut down due to denial of service (DoS), and where high gas prices are present, this can result in bidding becoming extremely expensive regardless of whether bids win or not. Unfortunately, this also results in only enabling those who have the experience and technical knowledge to build their own transactions are able to use and participate in auctions. Therefore, auctions thus settle for less than the fair market value.
Transaction-reordering / Front Running Attacks on Auctions
In the context of running an Auction Keeper to perform bids within an auction, a primary failure mode would occur when a Keeper specifies an unprofitable price for MKR (more info ).
Audit Section Source: Timestamp dependence in auctions enables denial of service
Front running attacks are possible. In order to mitigate this possibility, we reviewed and evaluated other auction options such as and/or commit reveal but do not currently feel this change is worth delaying the whole system for. Due to the modular nature of the system and the fact that the auction modules can be swapped out, it will be possible for governance to upgrade the auction process in the future.
Contract Name: join.sol
Type/Category: DSS —> Token Adapter Module
Etherscan
Join consists of three smart contracts: GemJoin
, ETHJoin
, and DaiJoin:
GemJoin
- allows standard ERC20 tokens to be deposited for use with the system. ETHJoin
- allows native Ether to be used with the system.
DaiJoin
- allows users to withdraw their Dai from the system into a standard ERC20 token.
Each join
contract is created specifically to allow the given token type to be join
'ed to the vat
. Because of this, each join
contract has slightly different logic to account for the different types of tokens within the system.
vat
- storage of the Vat
’s address.
ilk
- id of the Ilk for which a GemJoin
is created for.
gem
- the address of the ilk
for transferring.
dai
- the address of the dai
token.
one
- a 10^27 uint used for math in DaiJoin
.
live
- an access flag for the join
adapter.
dec
- decimals for the Gem.
Every join
contract has 4 public functions: a constructor, join
, exit
, and cage
. The constructor is used on contract initialization and sets the core variables of that join
contract. Join
and exit
are both true to their names. Join
provides a mechanism for users to add the given token type to the vat
. It has slightly different logic in each variation, but generally resolves down to a transfer
and a function call in the vat
. Exit
is very similar, but instead allows the the user to remove their desired token from the vat
. Cage
allows the adapter to be drained (allows tokens to move out but not in).
The GemJoin
contract serves a very specified and singular purpose which is relatively abstracted away from the rest of the core smart contract system. When a user desires to enter the system and interact with the dss
contracts, they must use one of the join
contracts. After they have finished with the dss
contracts, they must call exit
to leave the system and take out their tokens. When the GemJoin
gets cage
d by an auth
ed address, it can exit
collateral from the Vat but it can no longer join
new collateral.
User balances for collateral tokens added to the system via join
are accounted for in the Vat
as Gem
according to collateral type Ilk
until they are converted into locked collateral tokens (ink
) so the user can draw Dai.
The DaiJoin
contract serves a similar purpose. It manages the exchange of Dai that is tracked in the Vat
and ERC-20 Dai that is tracked by Dai.sol
. After a user draws Dai against their collateral, they will have a balance in Vat.dai
. This Dai balance can be exit
' ed from the Vat using the DaiJoin
contract which holds the balance of Vat.dai
and mint's ERC-20 Dai. When a user wants to move their Dai back into the Vat
accounting system (to pay back debt, participate in auctions, pack bag
's in the End
, or utilize the DSR, etc), they must call DaiJoin.join
. By calling DaiJoin.join
this effectively burn
's the ERC-20 Dai and transfers Vat.dai
from the DaiJoin
's balance to the User's account in the Vat
. Under normal operation of the system, the Dai.totalSupply
should equal the Vat.dai(DaiJoin)
balance. When the DaiJoin
contract gets cage
'd by an auth
'ed address, it can move Dai back into the Vat but it can no longer exit
Dai from the Vat.
The main source of user error with the Join
contract is that Users should never transfer
tokens directly to the contracts, they must use the join
functions or they will not be able to retrieve their tokens.
There are limited sources of user error in the join
contract system due to the limited functionality of the system. Barring a contract bug, should a user call join
by accident they could always get their tokens back through the corresponding exit
call on the given join
contract.
The main issue to be aware of here would be a well-executed phishing attack. As the system evolves and potentially more join
contracts are created, or more user interfaces are made, there is the potential for a user to have their funds stolen by a malicious join
contract which does not actually send tokens to the vat
, but instead to some other contract or wallet.
There could potentially be a vat
upgrade that would require new join
contracts to be created.
If a gem
contract were to go through a token upgrade or have the tokens frozen while a user's collateral was in the system, there could potentially be a scenario in which the users were unable to redeem their collateral after the freeze or upgrade was finished. This scenario likely presents little risk though because the token going through this upgrade would more than likely want to work alongside the Maker community to be sure this was not an issue.
The Maker Protocol's liaison between the Oracles and Core Contracts
Contract Name: spot.sol
Type/Category: DSS —> Core Module
The Spot
liaison between the oracles
and the core contracts. It functions as an interface contract and only stores the current ilk
list.
All mathematical operations will revert on overflow or underflow
All methods execute in constant time
ilk
a given collateral type
ilk.pip
the contract which holds the current price of a given ilk
ilk.mat
the liquidation ratio for a given ilk
vat
the core of the mcd system
par
value of DAI in the reference asset (e.g. $1 per DAI)
Only authorized users can update any variables in contract
poke
is the only non-authenticated function in spot
. The function takes in a bytes32
of the ilk
to be "poked". poke
calls two external
functions:
When calculating the spot
, the par
is crucial to this calculation as it defines the relationship between DAI and 1 unit of value in the price. The val
is then divided by the par
(to get a ratio of val
to DAI
) and then the resulting value is divided by the ilk.mat
. This gives us the current spot
price for the given ilk
.
file
is then called after calculating the spot
. This updates the vat
with the current liquidation price of the ilk
which the function was called for.
The methods in the spotter
are relatively basic compared to most other portions of dss
. There is not much room for user error in the single unauthed method poke
. If an incorrect bytes32
is supplied the call will fail.
Any module that is authed against the spot
has full root access, and can, therefore, add and remove which ilks
can be "poked". While not completely breaking the system, this could cause considerable risk.
A bug in spot
would most likely result in the prices for collaterals not being updated anymore. In this case, the system would need to authorize a new spot
which would then be able to update the prices. Overall this is not a catastrophic failure as this would only pause all price fluctuation for some period.
The spot
relies upon a set of trusted oracles to provide price data. Should these price feeds fail, it would become possible for unbacked Dai to be minted, or safe Vaults could be unfairly liquidated.
When poke
is not called frequently enough, the Vat
's spot
price will become stale. This could arise for a few reasons including tragedy of the commons or miner collusion and could lead to negative outcomes such as inappropriate liquidations, or the prevention of liquidations that should be possible.
The Dai Token Contract
Contract Name: dai.sol
Type/Category: DSS —> Dai Module
The Dai
contract is the user-facing ERC20 token contract maintaining the accounting for external Dai balances. Most functions are standard for a token with changing supply, but it also notably features the ability to issue approvals for transfers based on signed messages.
Key Functionalities (as defined in the smart contract)
Mint
- Mint to an address
Burn
- Burn at an address
Push
- Transfer
Pull
- Transfer From
Move
- Transfer From
Approve
- Allow pulls and moves
Permit
- Approve by signature
Other
name
- Dai Stablecoin
symbol
- DAI
version
- 1
decimals
- 18
totalSupply
- Total DAI Supply
balanceOf(usr: address)
- User balance
allowance(src: address, dst: address)
- Approvals
nonces(usr: address)
- Permit nonce
wad
- fixed point decimal with 18 decimals (for basic quantities, e.g. balances).
transferFrom
in the DAI contract works in a slightly different form than the generic transferFrom
function. The DAI contract allows for "unlimited approval". Should the user approve an address for the maximum uint256 value, then that address will have unlimited approval until told otherwise.
push
, pull
& move
are aliases for transferFrom
calls in the form of transferFrom(msg.sender, usr, amount)
, transferFrom(usr, msg.sender, amount)
& transferFrom(src, dst, amount)
.
permit
is a signature-based approval function. This allows for an end-user to sign a message which can then be relayed by another party to submit their approval. This can be useful for applications in which the end-user does not need to hold ETH
.
In order to use this functionality, a user's address must sign a message with the holder
, spender
, nonce
, expiry
and the allowed
amount. This can then be submitted to Permit()
to update the user's approval.
Unlimited allowance is a relatively uncommon practice (though becoming more common). This could be something used to trick a user by a malicious contract into giving access to all their DAI. This is concerning in upgradeable contracts where the contract may appear innocent until upgraded to a malicious contract.
There is a slight deviation in transferFrom
functionality: If the src == msg.sender
the function does not require approval
first and treats it as a normal transfer
from the msg.sender
to the dst
.
The Dai token provides offchain approval, which means that as an owner of an ETH address, you can sign a permission (using the permit() function) which basically grants allowance to another ETH address. The ETH address that you provide permission to can then take care of the execution of the transfer but has an allowance.
N/a
The Maker Protocol's Debt Auction House
Contract Name: flop.sol
Type/Category: DSS —> System Stabilizer Module
Summary: Debt Auctions are used to recapitalize the system by auctioning off MKR for a fixed amount of DAI. In this process, bidders compete by offering to accept decreasing amounts of MKR for the DAI they will end up paying.
flop
: debt auction (covering debt by inflating MKR and selling for stablecoins)
lot
: quantity up for auction / gems for sale (MKR)
guy
: high bidder (address)
gal
: recipient of auction income / receives dai income (this is the Vow contract)
ttl
: bid lifetime (Max bid duration / single bid lifetime)
beg
: minimum bid decrease
pad
: Increase for lot
size during tick
(default to 50%)
tau
: maximum auction duration
end
: when the auction will finish / max auction duration
kick
: start an auction / Put up a new MKR bid
for auction
dent
: make a bid, decreasing the lot size (Submit a fixed DAI bid
with decreasing lot
size)
deal
: claim a winning bid / settles a completed auction
vat
- the Vat's address
gem
- MKR Token (address)
kicks
- Total auction count, used to track auction id
s
live
- Cage flag
wards [usr: address]
, rely
/deny
/auth
- Auth mechanisms
Bid
- State of a specific Auction {bid
, lot
, guy
, tic
, end
}
bid
- Bid amount inDAI / DAI paid
tic
- Bid expiry
tick
- restarts an auction
The Maker Governance voters determine the debt limit. The Debt auction is triggered when the system has DAI debt above that limit.
Maker Governance sets the Vow.dump
which determines the starting lot
for an auction as well as the pad
which determines how much that lot
can increase during tick
.
The contracts that are auth
'ed to call kick()
(should only be Vow
) and file()
to change beg
, ttl
, tau
(should only be governance contracts).
Informational Note: The cage
sets the Flop to not be live anymore and the yank
is used during Global Settlement in order to return a bid to the bidder since the dent
and deal
can no longer be called.
The Flop
is a reverse auction, where keepers bid on how little MKR they are willing to accept for the fixed Dai amount they have to pay at auction settlement. The bidders will basically compete with decreasing lot
amounts of MKR for a fixed bid
amount of Dai. Once kick
ed, the bid
is set to the flop auction bid size (Vow.sump
) and lot
is set to a sufficiently large number (Vow.dump
). The auction will end when the latest bid duration (ttl
) has passed OR when the auction duration (tau
) has been reached. The payback process begins when the first bid is placed. The first bid will pay back the system debt and each subsequent bid will pay back the previous (no longer winning) bidder. When the auction is over, the process ends by cleaning up the bid and minting MKR for the winning bidder.
If the auction expires without receiving any bids, anyone can restart the auction by calling tick(uint auction_id)
. This will do two things:
It resets bids[id].end
to now + tau
It resets bids[id].lot
to bids[id].lot * pad / ONE
During an auction, lot
amounts will decrease by a percentage with each new dent
decreasing the lot
by the beg
for the same bid
of Dai. For example, the beg
could be set to 5%, meaning if the current bidder has a lot
of 10 (MKR) for a bid
of 100 (Dai), then the next bid must pass at most a lot
of 9.5 (MKR) for a bid
of 100 (Dai).
When a bid is beaten out by another bidder, the new winner's internal DAI balance is used to refund the previous winning bidder. Once placed, bids cannot be canceled.
Vow kick
s a new Flop Auction.
Bidder 1 makes a bid that decreases the lot
size by beg
from the initial amount. Bidder 1's DAI balance in the Vat is decreased by bid
and the Vow's DAI balance in the Vat is increased by bid
.
Bidder 2 makes a bid that decreases Bidder 1's lot
by beg
. Bidder 2's DAI balance in the Vat is decreased by bid
and Bidder 1's DAI balance in the Vat is increased by bid
(thereby refunding Bidder 1 for their now-losing bid).
Bidder 1 makes a bid that decreases Bidder 2's lot
by beg
. Bidder 1's DAI = Vat.dai[bidder1]
- bid
; Bidder 2's DAI = Vat.dai[bidder2]
+ bid
.
Bidder 2 (and all the other bidders within the auction) decide it is no longer worth it to continue to accept lower lot
s, so they stop bidding. Once the Bid.tic
expires, Bidder 1 calls deal
and new MKR tokens are minted to their address (MKR token contract.balances(Bidder1)
= MKR.balances(Bidder1)
+ lot
).
Note: During a Flop auction, the beg
is actually the minimum decrease amount. In dent
the new bid has to have a lot
* beg
that is less than or equal to the current lot
size. Since the theory of the Flop auction is that a bidder’s offer is to take fewer and fewer MKR tokens (lot
) for the same amount of dai (bid
) then the beg
is the amount each bid's offer should decrease by.
This failure mode is due to the fact that there is nothing the system can do stop a user from paying significantly more than the fair market value for the token in an auction (this goes for all auction types, flip
, flop
, and flap
).
This means, in the case of Flop, that since the Dai amount is fixed for the entire auction, the risk to the keeper is that they would make a "winning" bid that pays the bid amount in Dai but does not receive any MKR (lot
== 0). Subsequent executions of this bad strategy would be limited by the amount of Dai (not MKR) in their vat balance.
Flopper
has the potential to issue an excessively huge amount of MKR and despite the mitigation efforts (the addition of the dump
and pad
parameters), if dump
is not set correctly by governance, the huge issuance of MKR could still occur.
peek
calls the for the given ilk
and takes back in the val
and has
(a boolean which is false if there was an error in the osm
). The second external call only happens if has == true
.
For the most part, dai.sol
functions as a typical ERC20 token. These tokens have been already been and it is recommended to read through that documentation for the core functions of an ERC20 token.
DAI is also susceptible to the known , but should not normally be an issue with unlimited approval. We recommend any users using the approval
for a specific amount be aware of this particular issue and use caution when authorizing other contracts to perform transfers on their behalf.
The Flop Auction process begins with Maker Governance voters determining the system debt limit (). Debt Auctions are then triggered when the system has Dai debt that has passed that specified debt limit.
In order to determine whether the system has net debt, the surplus, accrued stability fees, and debt must be reconciled. Any user can do this by sending the heal
transaction to the system contract named . Provided there is sufficient debt (i.e. debt after healing > Vow.sump
), any user can send a Vow.flop
transaction to trigger a debt auction.
In the context of running a keeper (more info ) to perform bids within an auction, a primary failure mode would occur when a keeper specifies an unprofitable price for MKR.
See .
The Maker Protocol's Surplus Auction House
Contract Name: flap.sol
Type/Category: DSS —> System Stabilizer Module
Summary: Flapper is a Surplus Auction. These auctions are used to auction off a fixed amount of the surplus Dai in the system for MKR. This surplus Dai will come from the Stability Fees that are accumulated from Vaults. In this auction type, bidders compete with increasing amounts of MKR. Once the auction has ended, the Dai auctioned off is sent to the winning bidder. The system then burns the MKR received from the winning bid.
Flap
- surplus auction (selling stablecoins for MKR) [contract]
wards [usr: address]
- rely
/deny
/auth
Auth Mechanisms [uint]
Bid
- State of a specific Auction[Bid]
bid
- quantity being offered for the lot
(MKR) [uint]
lot
- lot amount (DAI) [uint]
guy
- high bidder [address]
tic
- Bid expiry [uint48]
end
- when the auction will finish [uint48]
bids (id: uint)
- storage of all Bid
s by id
[mapping]
vat
- storage of the Vat's address [address]
ttl
- bid lifetime / max bid duration (default: 3 hours) [uint48]
lot
- lot amount (DAI) [uint]
beg
- minimum bid increase (default: 5%) [uint]
tau
- maximum auction duration (default: 2 days) [uint48]
kick
- start an auction / put up a new DAI lot
for auction [function]
tend
- make a bid, thus increasing the bid size / submit an MKR bid (increasing bid
) [function]
deal
- claim a winning bid / settling a completed auction [function]
gem
- MKR Token [address]
kicks
- total auction count [uint]
live
- cage flag [uint]
file
- used by governance to set beg
, ttl
, and tau
[function]
yank
- is used during Global Settlement to move tend
phase auctions to the End
by retrieving the collateral and repaying DAI to the highest bidder. [function]
tick()
- resets the end
value if there has been 0 bids and the original end
has passed.
The Maker Governance voters determine the surplus limit. The surplus auction is triggered when the system has an amount of Dai above that set limit.
Parameters Set through file
:
beg
ttl
tau
Note: MKR governance also determines the Vow.bump
which sets the Bid.lot
for each Flap auction and the Vow.hump
which determines the surplus buffer.
auth
- check whether an address can call this method [modifier function]
rely
- allow an address to call auth'ed methods [function]
deny
- disallow an address from calling auth'ed methods [function]
Once the auction has begun, a fixed amount (lot
) of DAI is put up for sale. Bidders then complete for a fixed lot
amount of DAI with increasing bid
amounts of MKR. In other words, this means that bidders will keep placing MKR bid amounts in increments greater than the minimum bid increase amount that has been set (this is the beg
in action).
The surplus auction officially ends when the bid duration ends (ttl
) without another bid getting placed OR when auction duration (tau
) has been reached. At auction end, the MKR received for the surplus DAI is then sent to be burnt thereby contracting the overall MKR supply.
This failure mode is due to the fact that there is nothing the system can do to stop a user from paying significantly more than the fair market value for the token in an auction (this goes for all auction types, flip
, flop
, and flap
).
Keepers that are performing badly in a flap
auction run the risk of overpaying MKR for the DAI as there is no upper limit to the bid
size other than their MKR balance.
During tend
, bid
amounts will increase by a beg
percentage with each new tend
. The bidder must know the auction's id
, specify the right amount of lot
for the auction, bid at least beg
% more than the last bid and must have a sufficient MKR balance.
One risk is "front-running" or malicious miners. In this scenario, an honest keeper's bid of [Past-bid + beg
%] would get committed after the dishonest keeper's bid for the same, thereby preventing the honest keeper's bid from being accepted and forcing them to rebid with a higher price ((Past-bid + beg) + beg)). The dishonest keeper would need to pay higher gas fees to try to get a miner to put their transaction in first or collude with a miner to ensure their transaction is first. This could become especially important as the bid reaches the current market rate for MKR<>DAI.
Quick Example:
The beg
could be set to 3%, meaning if the current bidder has placed a bid of 1 MKR, then the next bid must be at least 1.03 MKR. Overall, the purpose of the bid increment system is to incentivize early bidding and make the auction process move quickly.
Bidders send MKR tokens from their addresses to the system/specific auction. If one bid is beat by another, the losing bid is refunded back to that bidder’s address. It’s important to note, however, that once a bid is submitted, there is no way to cancel it. The only possible way to have that bid returned is if it is outbid (or if the system goes into Global Settlement).
Vow kick
's a new Flap Auction.
Bidder 1 sends a bid (MKR) that increases the bid
above the initial 0 value set during the kick
. Bidder 1's MKR balance is decreased and the Flap's balance is increased by the bid size. bid.guy
is reset from the Vow address to Bidder 1's and bid.tic
is reset to now + ttl
.
Next, Bidder 2 makes a bid that increases Bidder 1's bid by at least beg
. Bidder 2's MKR balance is decreased and Bidder 1's balance is increased by Bidder 1's bid
. The difference between Bidder 2's and Bidder 1's bid
is sent from Bidder 2 to the Flap.
Bidder 1 then makes a bid that increases Bidder 2's bid
by at least beg
. Bidder 1's MKR balance is decreased and Bidder 2's MKR balance is increased by Bidder 2's bid
. The amount Bidder 1 increased the bid is then sent from Bidder 1 to the Flap.
Bidder 2, as well as all the other bidders participating within the auction, decide it is no longer worth it to continue to bid higher bid
s, so they stop making bids. Once the Bid.tic
expires, Bidder 1 calls deal
and the surplus DAI tokens are sent to the winning bidder's address (Bidder 1) in the Vat
and the system then burns the MKR received from the winning bidder. gem.burn(address(this), bids[id].bid)
.
Resulting from when MKR is burned
There is the possibility where a situation arises where the MKR token makes the transaction revert (e.g. gets stopped or the Vow's permission to call burn() is revoked). In a case like this, deal can't succeed until someone fixes the issue with the MKR token. In the case of stoppage, this could include the deploying of a new MKR token. This new deployment could be completed by any individual using the MCD System but governance would need to add it to the system. Next, it would need to replace the old surplus and debt auctions with the new ones using the new MKR token. Lastly, it is crucial to enable the possibility to vote with the new version as well.
When there is massive surplus
This would result in many Flap auctions occurring as the surplus over bump
+ hump
is always auctioned off in bump
increments. However, auctions run concurrently, so this would "flood the keeper market" and possibly result in too few bids being placed on any auction. This could happen through keepers not bidding on multiple auctions at once, which would result in network congestion because all keepers are trying to bid on all of the auctions. This could also lead to possible keeper collusion (if the capital pool is large enough, they may be more willing to work together to split it evenly at the system's expense).
The Maker Protocol's Balance Sheet
Contract Name: vow.sol
Type/Category: DSS —> System Stabilizer Module
The Vow
contract represents the Maker Protocol's balance sheet. In particular, the Vow
acts as the recipient of both the system surplus and system debt. Its main functions are to cover deficits via debt (Flop
) auctions and discharge surpluses via surplus (Flap
) auctions.
Pictured:
Inter-contract calls necessary for the module function
Not pictured:
cage
calls from Vow
to Flap
and Flop
Auction and user auction interactions
Governance calls / interactions
sin
: the system debt queue.
Sin
: the total amount of debt in the queue.
Ash
: the total amount of on-auction debt.
wait
: length of the debt queue
sump
: debt auction bid size, i.e. the fixed debt quantity to be covered by any one debt auction
dump
: debt auction lot size, i.e. the starting amount of MKR offered to cover the lot
/sump
bump
: surplus auction lot size, i.e. the fixed surplus quantity to be sold by any one surplus auction
hump
: surplus buffer, must be exceeded before surplus auctions are possible
Other terms included in the above diagram:
move
: transfers stablecoin between users.
kick
: starts an auction.
Fess - Pushes bad debt to the auctions queue (add debt to the queue).
Flog - Release queued debt for auction (realize debt from the queue).
Heal - vow
calls heal
on the vat
contract to cancel out surplus and debt. (Optimize debt buffer (vat.heal
)).
Kiss - Cancels out surplus and on-auction debt. Release on-auction debt and Heal (vat.heal
).
Flap - Trigger a surplus auction (flapper.kick
)
Flop - Trigger a deficit auction (flopper.kick
)
The vow
contract calls kick
on flop
and flap
to start an auction (debt and surplus auctions, respectively).
Flopper
(Debt auctions) - If the deficit is not covered in the forward auction portion of the flip
auction, then debt auctions are used for getting rid of the Vow’s debt by auctioning off MKR for a fixed amount of Dai. Once the auction ends, the Flop
per will then send the received Dai to the Vow
in order to cancel its debt. Lastly, the Flop
per will mint the MKR for the winning bidder.
Flapper
(Surplus auctions) - These are used for getting rid of the Vow
’s surplus by auctioning off a fixed amount of internal Dai for MKR. Once the auction ends, the Flap
per burns the winning MKR bid and sends internal Dai to the winning bidder.
System config
Vow.wait
- Flop delay Vow.sump
- Flop fixed bid size
Vow.dump
- Flop starting lot size Vow.bump
- Flap fixed lot size Vow.hump
- Surplus buffer
The Sin
is stored when it's in the debt queue, but the debt available to auction isn't explicitly stored anywhere. This is because the debt that is eligible for auction is derived by comparing the Sin
(i.e. debt on the holding queue) with the dai balance of the Vow
as recorded in Vat.dai[Vow]
. For instance, if Vat.sin[Vow]
is greater than the sum of Vow.Sin
and the Ash
(debt currently on auction), then the difference may be eligible for a Flop
auction.
Notes:
In the case of when a cat.bite
/ vow.fess
is executed, the debt tab
is added to sin[now]
and Sin
, which blocks that tab
amount to be sent to the flop
auction and all of the DAI is recovered with a flip
auction. In theory, unblocking the tab
amount in the Sin
shouldn't be necessary, but in practice it actually is. If this debt is not unblocked, then when we have a real need to send a flop
auction, we might have a big Sin
that blocks it. To summarize, this means that each registry of sin[era]
that has an amount > 0 should be flog
'ed before kicking a flop
auction (this is because in order to kick the whole thing, you need every register to be 0, otherwise, it would be blocking debt).
Each sin[era]
isn’t required to be a single bite
, it will group all the bite
’s that are in the same Ethereum block together.
The auction-keeper
will flog
every era
with positive Sin
if the woe
+ Sin
>= sump
, where woe
= vat.sin[vow]
- vow.Sin
- vow.Ash
. Where the components within vat.sin(vow)
- vow.Sin
- vow.Ash
are defined as:
vat.sin(vow)
- total bad debt
vow.Sin
- debt blocked
vow.Ash
- debt in auctions
Vow.sin
records individual portions of debt (marked with a timestamp). These are not directly auctioned off, but cleared when flog
is called.
If the Sin
is not covered by holding a flip
auction within the designated wait time (tau
), the Sin
“matures” and gets marked as bad debt to the Vow
. This bad debt can be covered through a debt auction (flop
) when it exceeds a minimum value (the lot
size). In short, the time between the debt being added to the sin[]
queue and becoming "mature" (when it flog
s off the queue and is eligible for Flop
auction) is the amount of time that Flip
auction has to clear that debt. This is due to the fact that when a Flip
auction receives DAI, it decreases the Vow
's DAI balance in the Vat
.
Note: In this case, there is a risk that a circumstance can occur where the Vow.wait
is different than the Flip.tau
. The main risk being related to wait
< tau
, which would result in debt auctions running before the associated seized-collateral auctions could complete.
Overall Sin
can affect the system in the following way:
There can be separate Vow
s each with their own sin
s
In the case of an upgrade, if we remove a Vow
that has sin
, this can create untracked bad debt in the system.
Vow.Sin
- This calculates the total queued debt in the system. Vow.Ash
- This calculates the total on-auction debt.
It is important to note that the Maker Protocol will deviate from its equilibrium. This occurs when it receives system debt and system surplus through the collateral auctions and Vault stability fee accumulation. The Vow
contract contains the logic to trigger both the debt (flop
) and surplus (flap
) auctions, which work to correct the system’s monetary imbalances.
Summary
System Debt: In the case where Vaults are bitten (liquidated), their debt is taken on by the Vow
contract as a Sin
(the system debt unit). The Sin
amount is then placed in the Sin
queue. Note: When the Sin
is not covered by a flip
auction (within the dedicated wait
time, the Sin
is considered to have bad debt to the Vow
. This bad debt is then covered through a debt auction (flop
) when it exceeds a minimum value (the lot
size).
System Surplus: Occurs from stability fee accumulation, resulting in additional internal Dai in the Vow
. This surplus is then discharged through a surplus auction (flap
).
When the Vow
is upgraded, there are multiple references to it that must be updated at the same time (End
, Jug
, Pot
).
The Vow
is the only user with a non-zero Sin
balance (not a vat
invariant as there can be multiple Vow
s).
Ilk storage is split across the Vat
, Jug
, Pot
and Vow
modules. The cat
also stores the liquidation penalty and maximum auction size.
A portion of the Stability Fee is allocated for the Dai Savings Rate (DSR) by increasing the amount of Sin
in the Vow
at every Pot.drip( )
call.
Setting an incorrect value for vow
can cause the surplus to be lost or stolen.
A failure mode could arise when no actors call kiss
, flog
or heal
to reconcile/queue the debt.
A failure mode could arise if a user does not call flap
or flop
to kick off auctions.
Vow.wait
, when set too high (wait
is too long), the flop
auctions can no longer occur. This provides a risk of undercollateralization.
Vow.wait
, when set too low, can cause too many flop
auctions, while preventing flap
auctions from occurring.
Vow.bump
, when set too high, can result in no flap
auctions being possible. Thus, if no flap
auction takes place, there will be no MKR bidding as part of that process and, accordingly, no automated MKR burn as a result of a successful auction.
Vow.bump
, when set too low, results in flap
auctions not being profitable for participants (lot
size is worth less than gas cost). Thus, no MKR will be bid during a flap
auction and, as a result, there will be no automated MKR burn.
Vow.sump
, when set too high, no flop
auctions are possible. This results in the system not being able to recover from an undercollateralized state.
Vow.sump
, when set too low, flop
auctions are not profitable for participants (where the lot
size is worth less than gas cost). This results in MKR inflation due to automated MKR minting.
Vow.dump
, when set too high, flop
auctions risk not being able to close or mint a large amount of MKR, creating a risk of MKR dilution and the possibility of a governance attack.
Vow.dump
, when set too low, flop
auctions have to be kick
ed many times before they will be interesting to keepers.
Vow.hump
, when set too high, the flap
auctions would never occur. If a flap
auction does not occur, there is no sale of surplus, and thus, no burning of bid MKR.
Vow.hump
, if set too low, can cause surplus to be auctioned off via flap
auctions before it is used to cancel sin
from liquidations, necessitating flop
auctions and making the system run inefficiently.
Contract Name: OSM
Type/Category: Oracles - Price Feed Module
stopped
: flag (uint256
) that disables price feed updates if non-zero
src
: address
of DSValue that the OSM will read from
ONE_HOUR
: 3600 seconds (uint16(3600)
)
hop
: time delay between poke
calls (uint16
); defaults to ONE_HOUR
zzz
: time of last update (rounded down to nearest multiple of hop
)
cur
: Feed
struct that holds the current price value
nxt
: Feed
struct that holds the next price value
bud
: mapping from address
to uint256
; whitelists feed readers
These functions can only be called by authorized addresses (i.e. addresses usr
such that wards[usr] == 1
).
rely
/deny
: add or remove authorized users (via modifications to the wards
mapping)
stop()
/start()
: toggle whether price feed can be updated (by changing the value of stopped
)
change(address)
: change data source for prices (by setting src
)
step(uint16)
: change interval between price updates (by setting hop
)
void()
: similar to stop
, except it also sets cur
and nxt
to a Feed
struct with zero values
kiss(address)
/diss(address)
: add/remove authorized feed consumers (via modifications to the buds
mapping)
These can only be called by whitelisted addresses (i.e. addresses usr
such that buds[usr] == 1
):
peek()
: returns the current feed value and a boolean indicating whether it is valid
peep()
: returns the next feed value (i.e. the one that will become the current value upon the next poke()
call), and a boolean indicating whether it is valid
read()
: returns the current feed value; reverts if it was not set by some valid mechanism
poke()
: updates the current feed value and reads the next one
Feed
struct: a struct with two uint128
members, val
and has
. Used to store price feed data.
Other contracts, if whitelisted, may inspect the cur
value via the peek()
and read()
methods (peek()
returns an additional boolean indicating whether the value has actually been set; read()
reverts if the value has not been set). The nxt
value may be inspected via peep()
.
The contract uses a dual-tier authorization scheme: addresses mapped to 1 in wards
may start and stop, set the src
, call void()
, and add new readers; addresses mapped to 1 in buds
may call peek()
, peep()
, and read()
.
peek()
for peep()
(or vice-versa)The names of these methods differ by only a single character and in current linguistic usage, both "peek" and "peep" have essentially the same meaning. This makes it easy for a developer to confuse the two and call the wrong one. The effects of such an error are naturally context-dependent, but could e.g. completely invalidate the purpose of the OSM if the peep()
is called where instead peek()
should be used. A mnemonic to help distinguish them: "since 'k' comes before 'p' in the English alphabet, the value returned by peek()
comes before the value returned by peep()
in chronological order". Or: "peek()
returns the kurrent value".
poke()
is not called promptly, allowing malicious prices to be swiftly uptakenFor several reasons, poke()
is always callable as soon as block.timestamp / hop
increments, regardless of when the last poke()
call occurred (because zzz
is rounded down to the nearest multiple of hop
). This means the contract does not actually guarantee that a time interval of at least hop
seconds has passed since the last poke()
call before the next one; rather this is only (approximately) guaranteed if the last poke()
call occurred shortly after the previous increase of block.timestamp / hop
. Thus, a malicious price value can be acknowledged by the system in a time potentially much less than hop
.
This was a deliberate design decision. The arguments that favoured it, roughly speaking, are:
Providing a predictable time at which MKR holders should check for evidence of oracle attacks (in practice, hop
is 1 hour, so checks must be performed at the top of the hour)
Allowing all OSMs to be reliably poked at the same time in a single transaction
The fact that poke
is public, and thus callable by anyone, helps mitigate concerns, though it does not eliminate them. For example, network congestion could prevent anyone from successfully calling poke()
for a period of time. If an MKR holder observes that poke
has not been promptly called, the actions they can take include:
Call poke()
themselves and decide if the next value is malicious or not
Call stop()
or void()
(the former if only nxt
is malicious; the latter if the malicious value is already in cur
)
Trigger emergency shutdown (if the integrity of the overall system has already been compromised or if it is believed the rogue oracle(s) cannot be fixed in a reasonable length of time)
In the future, the contract's logic may be tweaked to further mitigate this (e.g. by only allowing poke()
calls in a short time window each hop
period).
Various damaging actions can be taken by authorized individuals or contracts, either maliciously or accidentally:
Revoking access of core contracts to the methods that read values, causing mayhem as prices fail to update
Completely revoking all access to the contract
Changing src
to either a malicious contract or to something that lacks a peek()
interface, causing transactions that poke()
the affected OSM to revert
Calling disruptive functions like stop
and void
inappropriately
The only solution to these issues is diligence and care regarding the wards
of the OSM.
The Maker Protocol's trusted reference price
Contract Name: median.sol
Type/Category: Oracles Module
The median provides Maker's trusted reference price. In short, it works by maintaining a whitelist of price feed contracts which are authorized to post price updates. Every time a new list of prices is received, the median of these is computed and used to update the stored value. The median has permissioning logic which is what enables the addition and removal of whitelisted price feed addresses that are controlled via governance. The permissioning logic allows governance to set other parameters that control the Median's behavior—for example, the bar
parameter is the minimum number of prices necessary to accept a new median value.
A High-level overview diagram of the components that involve and interact with the median:
Note: All arrows without labels are governance calls.
read
- Gets a non-zero price or fails.
peek
- Gets the price and validity.
poke
- Updates price from whitelisted providers.
lift
- Adds an address to the writers whitelist.
drop
- Removes an address from the writers whitelist.
setBar
- Sets the bar
.
kiss
- Adds an address to the reader's whitelist.
diss
- Removes an address from the readers whitelist.
Note: read
returns the value
or fails if it's invalid & peek
gives back the value
and if the value
is valid or not.
wards(usr: address)
- Auth mechanisms.
orcl(usr: address)
- val
writers whitelist / signers of the prices (whitelisted via governance / the authorized parties).
bud(usr: address)
- val
readers whitelist.
val
- the price (private) must be read with read()
or peek()
age
- the Block timestamp of last price val
update.
wat
- the price oracles type (ex: ETHUSD) / tells us what the type of asset is.
bar
- the Minimum writers quorum for poke
/ min number of valid messages you need to have to update the price.
As mentioned above, the median
is the smart contract that provides Maker's trusted reference price. Authorization (auth) is a key component included in the mechanism of this contract and its interactions. For example, the price (val
) is intentionally kept not public because the intention is to only read it from the two functions read
and peek
, which are whitelisted. This means that you need to be authorized, which is completed through the bud
. The bud
is modified to get whitelisted authorities to read it on-chain (permissioned), whereas, everything of off-chain is public.
The poke
method is not under any kind of auth
. This means that anybody can call it. This was designed for the purpose of getting Keepers to call this function and interact with Auctions. The only way to modify its state is if you call it and send it valid data. For example, let's say this oracle needs 15 different sources. This means that we would need it to send 15 different signatures. It will then proceed to go through each of them and validate that whoever sent the the data has been auth
'd to do so. In the case of it being an authorized oracle, it will check if it signed the message with a timestamp that is greater than the last one. This is done for the purpose of ensuring that it is not a stale message. The next step is to check for order values, this requires that you send everything in an array that is formatted in ascending order. If not sent in the correct order (ascending), the median is not calculated correctly. This is because if you assume the prices are ordered, it would just grab the middle value which may not be sufficient or work. In order to check for uniqueness, we have implemented the use of a bloom
filter. In short, a bloom filter is a data structure designed to tell us, rapidly and memory-efficiently, whether an element is present in a set. This use of the bloom filter helps with optimization. In order to whitelist signers, the first two characters of their addresses (the first byte
) have to be unique. For example, let's say that you have 15 different price signers, none of the first two characters of their addresses can be the same. This helps to filter that all 15 signers are different.
Next, there are lift
functions. These functions tell us who can sign messages. Multiple messages can be sent or it can just be one but they are put into the authorized oracle). However, there is currently nothing preventing someone from lift
'ing two prices signers that start with the same address. This is something for example, that governance needs to be aware of (see an example of what a governance proposal would look like in this case in the Gotchas section).
Due to the mechanism design of how the oracles work, the quorum has to be an odd number. If it is an even number, it will not work. This was designed as an optimization (val = uint128(val_[val_.length >> 1]);
); this code snippet outlines how it works, which is by taking the array of values (all the prices that each of the prices signers reported, ordered from 200-215) and then grabbing the one in the middle. This is done by taking the length of the array (15) and shifting it to the right by 1 (which is the same as dividing by 2). This ends up being 7.5 and then the EVM floors it to 7. If we were to accept even numbers this would be less efficient. This presents the issue that you should have a defined balance between how many you require and how many signers you actually have. For example, let's say the oracle needs 15 signatures, you need at least 17-18 signers because if you require 15 and you only have 15 and one of them goes down, you have no way of modifying the price, so you should always have a bit more. However, you should not have too many, as it could compromise the operation.
They can shutdown the price feed but cannot bring it back up. Bringing the price feed back up requires governance to step in.
If you void the oracles Ethereum module, the idea is that you cannot interact with any Vault that depends on that ilk.
Example: ETHUSD shutdown (can still add collateral and pay back debt - increases safety) but you cannot do anything that increases risk (decreases safety - remove collateral, generate dai, etc.) because the system would not know if you would be undercollateralized.
They need to keep all relayers functioning.
The community would need to self-police (by looking at each price signer, etc.) if any of them needs to be replaced. They would need to make sure they are constantly being called every hour (for every hour, a transaction gets sent to the OSM, which means that a few transactions have already been sent to the median to update it as well. In addition, there would need to be a transaction sent to the spotter
, as DSS operates in a pool-type method (doesn't update the system/write to it, you tell it to read it from the OSM).
lift
'ing two prices signers that start with the same addressThe only thing that this prevents is that you cannot have more than 256 oracles but we don't expect to ever have that many, so it is a hard limit. However, Governance needs to be sure that whoever they are voting in anyone that they have already voting in before with the same two first characters.
An example of what a governance proposal would look like in this case:
We are adding a new oracle and are proposing (the Foundation) a list of signers (that have been used in the past) and we already have an oracle but want to add someone new (e.g. Dharma or dydx). We would say that they want to be price signers, so these are their addresses and we want to lift those two addresses. They would vote for that, and we would need to keep a list of the already existing addresses and they would need to create an address that doesn't conflict with the existing ones.
By design, there is currently no way right now to turn off the oracle (failure or returns false) if all the oracles come together and sign a price of zero. This would result in the price being invalid and would return false on peek
, telling us to not trust the value.
We are currently researching (Oracles ETH module) that would invalidate the price but there is no way to do this in the median today. This is due to the separation of concerns that DSS does not read directly from median, it reads from the OSM, but this may end up changing.
The Maker Protocol's Governance Contracts
Module Name: Governance Module
Type/Category: Governance —> Chief.sol, Pause.sol, Spell.sol
Contract Sources:
The Governance Module contains the contracts that facilitate MKR voting, proposal execution, and voting security of the Maker Protocol.
The Governance Module has 3 core components consisting of the Chief
, Pause
and Spell
contracts.
Chief
- The Ds-Chief smart contract provides a method to elect a "chief" contract via an approval voting system. This may be combined with another contract, such as DSAuthority
, to elect a ruleset for a smart contract system.
Pause
- The ds-pause
is a delegatecall based proxy with an enforced delay. This allows authorized users to schedule function calls that can only be executed once a predetermined waiting period has elapsed. The configurable delay attribute sets the minimum wait time that will be used during the governance of the system.
Spell
- A DS-Spell
is an un-owned object that performs one action or series of atomic actions (multiple transactions) one time only. This can be thought of as a one-off DSProxy with no owner (no DSAuth mixing, it is not a DSThing).
Chief
In general, when we refer to the "chief", it can be both addresses or people that represent contracts. Thus, ds-chief can work well as a method for selecting code for execution just as well as it can for realizing political processes.
IOU Token: The purpose of the IOU token is to allow for the chaining of governance contracts. In other words, this allows you to have a number of DSChief
, DSPrism
, or other similar contracts use the same governance token by means of accepting the IOU token of the DSChief
contract before it is a governance token.
Pause
Identity & Trust: In order to protect the internal storage of the pause from malicious writes during plan execution, a delegatecall operation is performed in a separate contract with an isolated storage context (DSPauseProxy), where each pause has its own individual proxy. This means that plans are executed with the identity of the proxy
. Thus when integrating the pause into some auth scheme, you will want to trust the pause's proxy and not the pause itself.
Spell
The spell is only marked as "done" if the CALL it makes succeeds, meaning it did not end in an exceptional condition and it did not revert. Conversely, contracts that use return values instead of exceptions to signal errors could be successfully called without having the effect you might desire. "Approving" spells to take action on a system after the spell is deployed generally requires the system to use exception-based error handling to avoid griefing.
Chief
MKR users moving their votes from one spell to another: One of the biggest potential failure modes occurs when people are moving their votes from one spell to another. This opens up a gap/period of time when only a small amount of MKR is needed to lift a random hat.
Pause
There is no way to bypass the delay.
The code executed by the delegatecall cannot directly modify storage on the pause.
The pause will always retain ownership of it's proxy.
Spell
The main failure mode of the spell
arises when there is an instance of the spell remaining uncast when it has an amount of MKR voting for it that later becomes a target.
The Maker Protocol's Collateral Auction House (Liquidation System 2.0)
Type/Category: DSS —> Liquidation 2.0 Module
Etherscan
Summary: In the context of the Maker protocol, a liquidation is the automatic transfer of collateral from an insufficiently collateralized Vault, along with the transfer of that Vault’s debt to the protocol. In the liquidation contract (the Dog
), an auction is started promptly to sell the transferred collateral for DAI in an attempt to cancel out the debt now assigned to the protocol.
Unlike the old Liquidation 1.2 system which utilized English auctions, in which DAI bids are placed, with a participant's capital remaining locked until they are outbid or until the auction terminates, Liquidation 2.0 uses Dutch auctions which settle instantly. They do so according to a price calculated from the initial price and the time elapsed since the auction began. Price versus time curves are discussed more later. The lack of a lock-up period mitigates much of the price volatility risk for auction participants and allows for faster capital recycling.
This feature, enabled by instant settlement, eliminates any capital requirement for bidders (excepting gas)—in the sense that even a participant with zero DAI (and nothing to trade for DAI) could still purchase from an auction by directing the sale of the auction's collateral into other protocols in exchange for DAI. Thus, all DAI liquidity available across DeFi can be used by any participant to purchase collateral, subject only to gas requirements. The exact mechanics are discussed above, but essentially a participant needs to specify a contract who
(which conforms to a particular interface), and calldata
to supply to it, and the auction contract will automatically invoke whatever logic is in the external contract.
Price-versus-time curves are specified through an interface that treats price at the current time as a function of the initial price of an auction and the time at which that price was set. How to determine the most effective price curve for a given collateral is still an active area of research; some initial options (linear, step-wise exponential, and continuous exponential) have been implemented for research purposes and initial deployment. Other candidates besides these include a piecewise linear curve and a piecewise exponential curve. This module is configurable and can be replaced in the course of innovation.
It is important for bidders to take into account that while the price almost always decreases with time, there are infrequent occasions on which the price of an active auction may increase, and this could potentially result in collateral being purchased at a higher price than intended. The most obvious event that would increase the price in a running auction is if that auction is reset (via redo
), but changes to the configurable parameters of a price decrease calculator (or even the switching out of one calculator for another) by governance can also increase the price. It is recommended that bidders (or bidding UIs that abstract this detail away from the user) choose the maximum acceptable price (max
) argument to take
carefully to ensure a desirable outcome if the price should unexpectedly increase.
As mentioned above, auctions can reach a defunct state that requires resetting for two reasons:
too much time has elapsed since the auction started (controlled by the tail
governance parameter)
the ratio of the current price to the initial price has fallen below a certain level (specified by the cusp
governance parameter).
The reset function, when called, first ensures that at least one of these conditions holds. Then it adjusts the starting time of the auction to the current time, and sets the starting price in exactly the same way as is done in the initialization function (i.e. the current OSM price increased percentage-wise by the buf
parameter). This process will repeat until all collateral has been sold or the whole debt has been collected (unless the auction is canceled via yank
, e.g. during Emergency Shutdown); contrast this behavior with the current auctions, which reset until at least one bid is received.
If keepers decide to use the clipperCallee
pattern, then they need not store DAI or collateral on that account. This means a keeper need only hold enough ETH to execute transactions that can orchestrate the Clipper.take
call, sending collateral to a contract that returns DAI to the msg.sender
to pay for the collateral all in one transaction. The contract implementing the clipperCallee
interface can send any remaining collateral or DAI beyond owe
to a cold wallet address inaccessible to the keeper. NOTE: If the keeper chooses to behave as an EOA address, then the DAI and collateral would be exposed just as in LIQ-1.2
unless special care is taken to create a proxy contract.
file
)Abacus/LinearDecrease
-- tau [seconds]Seconds after auction start when the price reaches zero.
Abacus/StairstepExponentialDecrease
-- cut [ray]Per-step
multiplicative factor. cut = 0.99 * RAY
specifies a 1% drop every step
seconds.
Abacus/StairstepExponentialDecrease
-- step [seconds]Length of time between price drops.
Abacus/ExponentialDecrease
-- cut [ray]Per-second multiplicative factor. cut = 0.99 * RAY
specifies a 1% drop every second.
Clipper
-- buf [ray]The multiplicative factor to increase the starting price of an auction. E.g. if the current OSM price of an asset is 1,000 and buf = 1.2 * RAY
(20% above), then the initial price of that auction will be 1,200.
Clipper
-- calc [address]The contract address of the price calculator function. Adheres to Abacus
interface. Some examples of price functions are found in abaci.sol
file.
Clipper
-- chip [wad]Percentage of tab
to suck
from vow
to incentivize keepers when liquidating a vault or resetting an already existing auction. chip = 0.02 * WAD
is 2%.
Clipper
-- cusp [ray]Percentage price drop that can occur before an auction must be reset. Together with tail
, this parameter determines when an auction needs to be reset. E.g. if the initial price of an auction (top
) is set to 1,200 and cusp = 0.6 * RAY
(60% of the starting price), then the auction will need to be reset when reaching just below the price of 720.
Clipper
-- dog [address]The address of the liquidation module contract.
Clipper
-- spotter [address]The Collateral price module contract address.
Clipper
-- tail [seconds]Seconds that can elapse before an auction must be reset. Together with cusp
, this parameter determines when an auction needs to be reset. E.g. if tail
is 1800 seconds, then if an auction is not complete after 30 minutes have elapsed, it will need to be reset.
Clipper
-- tip [rad]Flat fee to suck
from vow
to incentivize keepers when liquidating a vault or resetting an already existing auction. tip = 100 * RAD
is 100 DAI.
Clipper
-- vow [address]The address of the accounting system contract. The recipient of DAI raised in auctions.
Dog
-- Hole [rad]Max DAI needed to cover debt + liquidation penalty of active auctions. Hole = 10,000,000 * RAD
is 10M DAI.
Dog
-- ilk.chop [wad]Liquidation Penalty per collateral (ilk
). E.g. if there is a vault ready to be liquidated that has a debt of 1,000 DAI and chop = 1.13 * WAD
(13% above), then the max amount to be raised by the auction will be 1,130 DAI.
Dog
-- ilk.clip [address]The address of the auction module contract. One clip
per collateral (ilk
).
Dog
-- ilk.hole [rad]Max DAI needed to cover debt + liquidation penalty of active auctions per collateral (ilk
). hole = 10,000,000 * RAD
is 10M DAI.
Dog
-- vow [address]The address of the accounting system contract. The recipient of the bad debt coming from a vault when it's liquidated.
Dog.bark
) takes three caller supplied arguments:ilk
: the collateral ilk
urn
: the Vault to be liquidated
kpr
: the address where DAI incentives will be sent
Dog.bark
performs several actions:confiscates the given Vault urn
if it's undercollateralized and
sends the collateral to the ilk
's Clipper
increments the vow
's bad debt accumulator
pushes the bad debt into the debt queue
adds the bad debt plus the liquidation penalty to the Hole
with the Dirt
accumulator
adds the bad debt plus the liquidation penalty to the ilk.hole
with the ilk.dirt
accumulator
initiates the auction by calling Clipper.kick()
fires the Bark()
event
In the context of the Maker protocol, a "liquidation" is the automatic transfer of collateral from an insufficiently collateralized Vault, along with the transfer of that Vault's debt to the protocol. In both the Liquidation 1.2 version (the Cat
) and the Liquidation 2.0 version (the Dog
), an auction is started promptly to sell the transferred collateral for DAI in an attempt to cancel out the debt now assigned to the protocol. This makes the behavior of the new contract very similar to that of the old one, but there are some important differences, explained below.
In the current system, in each call to the liquidation function (Cat.bite
) transfers a fixed amount of debt (the dunk
) from the affected Vault, along with a proportional amount of the Vault's collateral to the protocol. For example, if 50% of a Vault's debt is taken by the protocol, then half of its collateral is taken as well. If the Vault's debt is less than the dunk
, then all debt and collateral is taken. In 2.0, all debt is taken when the liquidation function (Dog.bark
) is called, and no analogue of the dunk
parameter exists. The reasoning behind this change is that because the new auctions allow partial purchases of collateral, the liquidity available to a participant no longer limits their ability to participate in auctions, so instead the total number of auctions should be minimized. Just to emphasize, there is no longer a minimum DAI liquidity requirement for the sale of collateral on a per-participant basis.
In situations involving large amounts of collateral at auction, the current and new designs modify the behavior described in the previous section. Both liquidations 1.2 and 2.0 implement a limit on the total amount of DAI needed to cover the summed debt and liquidation penalty associated with all active auctions. In LIQ-1.2
this is called the box
, and in LIQ-2.0
we call this the Hole
. Whenever the maximum possible addition to this value is less than the amount of debt+fees that would otherwise be sent to auction, a partial liquidation is performed so as not to exceed this amount. In the current system there is only a global limit; in 2.0, in addition to the global limit, there is also a per-collateral limit. Similar to how there is an ilk.line
for a collateral's debt ceiling and a Line
for the overall system debt ceiling, there is now an ilk.hole
to correspond with the Hole
. This ensures that typical market depth against DAI can be taken into account on a per-collateral basis by those determining risk parameters. The Dirt
accumulator tracks the total DAI needed to cover the debt and penalty fees of all active auctions, and must be less than Hole
for a call to bark
to succeed. For each collateral type, ilk.dirt
tracks the total DAI needed to cover the debt and penalty fees of a all active auctions for that ilk
, and must be less than ilk.hole
for a call to bark
(on a Vault of that ilk
) to succeed.
Clipper.kick
) takes four caller supplied arguments:tab
: the target DAI to raise from the auction (debt
+ stability fees
+ liquidation penalty
) [rad]
lot
: the amount of collateral available for purchase [wad]
usr
: the address to return any excess collateral to
kpr
: the address where DAI incentives will be sent
Clipper.kick
performs several checks and actions:checks that the caller is authorized (only the Dog
or governance may call Clipper.kick
)
checks that liquidations are enabled in the four-stage circuit breaker
increments a counter and assigns a unique numerical id
to the new auction
inserts the id
into a list tracking active auctions
creates a structure to record the parameters of the auction; this includes:
it's position in the active auctions list
the target amount of DAI to raise from bidders (tab
)
the amount of collateral available for purchase (lot
)
the Vault that was liquidated to create this auction
allows return of collateral if not all of it is sold
allows the return of collateral and tab
when Clipper.yank
is called by End.snip
the timestamp of the auctions creation (as a Unix epoch)
the initial price of collateral in the auction (top
)
sends an incentive denominated in DAI to the kpr
fires the Kick()
event
In this design, there is less incentive to quickly liquidate Vaults than in the current system, because there is no inherent advantage obtained by doing so. In contrast, the current auction system grants the account triggering a liquidation the privilege of making the first bid in the resulting auction. It is unclear whether this matters significantly in practice, or whether some stronger incentive should be added (for example, a small DAI reward paid to liquidators).
To ensure there was a remedy for this potential issue, an incentive mechanism was added for liquidators. The form of the incentive is, on a per-collateral type basis, a constant amount of DAI plus an amount of DAI that scales linearly with the amount of debt associated with the liquidation. Either contribution can be set to zero. Such a structure is justified by the following:
The reward is set per-collateral to give maximum flexibility to include not just per-collateral risk parameters like mat
(collateralization ratio) and chop
(liquidation penalty) in its setting, but also to allow for unique market conditions that might only apply to one or a few collateral types.
The component of the reward that increases linearly with the total Vault debt is intended to be used to reward liquidators for reducing risk to the system, as risk itself scales with the size of undercollateralized Vaults—a Vault that is twice as big as another represents twice the risk of bad debt accrual. Or viewed another way, liquidating two vaults of size X represents the same risk reduction as liquidating one Vault of size 2X—thus the reward to a liquidator ought to be similar. Further, the system can afford to pay more for larger liquidations, because the liquidation penalty is also proportional to the amount of debt outstanding for a given Vault.
The constant component of the reward can be used to cover gas costs (which are per-Vault for liquidators) or to allow MKR holders to effectively pay Keepers to clear small Vaults that would otherwise not be attractive for liquidation.
These parameters must be set extremely carefully, lest it be possible to exploit the system by "farming" liquidation rewards (e.g. creating Vaults with the intention of liquidating them and profiting from the too-high rewards). Generally, the liquidation reward should remain less than the minimum liquidation penalty by some margin of safety so that the system is unlikely to accrue a deficit. This doesn't necessarily prevent farming, it just helps ensure the system remains solvent. For example, incentives can be farmed in a capital-efficient way when Dirt
is close to Hole
or when ilk.dirt
is close to ilk.hole
for some collateral type. An attacker would purchase a small amount of collateral from a running auction, freeing up a small amount of room relative to either Hole
or ilk.hole
, then liquidate a small portion of an existing Vault to collect the reward, and repeat the process. This can be done over and over in the same transaction, and the activation of EIP 2929 (scheduled for the Berlin hard fork at the time of writing) will significantly reduce the associated gas costs (due to warm storage reads and writes). Note that since the attacker does not need to create their own Vaults, the size of the liquidation penalty in relation to the incentive value is not a deterrent; only gas costs matter to the attacker. The fact that the Dog prevents partial liquidations that remove less than ilk.dust
debt from a Vault helps to mitigate this scenario--so long as the liquidation penalty exceeds the total reward for this minimal liquidation size, and the liquidation penalty is reliably collected by the resulting auction (which may not hold under conditions of market or network perturbation), the system should not on balance accrue bad debt.
In this section, we'll cover the four stages of the liquidation circuit breaker. In contrast to LIQ-1.2
, Liquidations 2.0 comes with a four-stage circuit breaker built into the Clipper
contract. The stages are:
liquidations enabled(0
): This means the breaker is not tripped and the protocol can liquidate new Vaults as well as service old liquidations.
new liquidations disabled(1
): This means no new liquidations (Clipper.kick
).
new liquidations and resets disabled(2
): This means no new liquidations (Clipper.kick
), and auctions that have reached either a price or time endpoint cannot be reset (Clipper.redo
).
liquidations disabled(3
): This means no new liquidations (Clipper.kick
), no takes (Clipper.take
), and no resets (Clipper.redo
).
Just like in LIQ-1.2
, the circuit breaker will be available through a ClipperMom
contract giving governance the ability to bypass the GSM
delay for circuit breaker actions.
Clipper.take
) takes five caller supplied arguments:id
: the numerical id of the auction to bid on
amt
: the maximum amount of collateral to buy (amt
) — a purchase behaves like a limit order [wad]
max
: the maximum acceptable price in DAI per unit collateral (max
) [ray]
who
: address that will receive the collateral (who
)
data
: an arbitrary bytestring (if provided, the address who
, if it is neither the Dog
nor Vat
address stored by the Clipper, is called as a contract via an interface described below, to which this data is passed) [bytes]
Clipper.take
performs several initial checks:a reentrancy check to ensure the function is not being recursively invoked
that the four-stage circuit breaker is not tripped
that the auction id corresponds to a valid auction
that the auction does not need to be reset, either due to having experienced too large a percentage decrease in price, or having existed for too long of a time duration
that the caller's specified maximum price is at least the current auction price
Then, the amount of collateral to attempt to purchase is computed as the minimum of the collateral left in the auction (lot
) and the caller's specified quantity (amt
)—the resulting value is the slice
. This value is then multiplied by the current price of the auction to compute the DAI owed in exchange (owe
). If owe
exceeds the DAI collection target of the auction (tab
), then owe
is adjusted to be equal to tab
, and slice
is set to tab / price
(i.e. the auction will not sell more collateral than is needed to cover debt+fees from the liquidated Vault).
To make it less likely that auctions are left in a state that is unattractive to bid on, further logic is applied if there will be both left over DAI target and collateral based on the initial determinations for slice
and owe
. If the remaining tab
in such a case would be less than chost
(a value stored by the contract, asynchronously set to ilk.dust * ilk.chop / WAD
by the upchost()
function), then: 1) If the overall DAI target is less than chost
, the function reverts. Callers should be aware of this possibility and account for it when necessary. 2) Otherwise, owe
is set to tab - chost
, and slice
is set to (tab - chost) / price
.
This heuristic for preventing a too-small remaining collateral amount and/or DAI target is imperfect and may fail in some situations; if this occurs, there is no serious harm to the protocol; the auction will simply remain uncompleted. However, governance may wish to fully clear such "lost" auctions via take
or yank
to avoid cluttering the list of active auctions each Clipper
maintains, which could impair the performance of DEX and aggregator integrations (and possibly off-chain bots too, depending on how they are written).
Lastly, various values are updated to record the changed state of the auction: the DAI needed to cover debt and fees for outstanding auctions, and outstanding auctions of the given collateral type, are reduced (via a callback to the liquidator contract) is reduced by owe
, and the tab
(DAI collection target) and lot
(collateral for sale) of the auction are adjusted as well. If all collateral has been drained from an auction, all its data is cleared and it is removed from the active auctions list. If collateral remains, but the DAI collection target has been reached, the same is done and excess collateral is returned to the liquidated Vault.
Figure 1: NOTE: in the above figure, tau
is tail
.
In this example we can see a linear decrease function (calc
), with an ETH-A
OSM price of 200 DAI, a buf
of 20%, a tail (tau)
of 21600 seconds, a tab
of *60,000 DAI with a lot
of 347.32
ETH. There are two bidders, Alice and Bob. Alice calls take
first and is willing to give *50,000 DAI in return for 256.41 ETH collateral, a price of 195 DAI per ETH. The price of ETH continues to fall over time, and Bob picks up the remaining 90.91 ETH for 10,000 DAI, a price of 110 DAI per ETH. If more than tail
seconds have elapsed since the start of the auction, or if the price has fallen to less than cusp
percent of top
, then Clipper.take
would revert if called, and the auction would need to be reset with a Clipper.redo
call.
redo()
Clipper.redo
) takes two caller supplied arguments:id
: the current auction id
kpr
: the address where DAI incentives will be sent
Clipper.redo
performs several checks and actions:a reentrancy check to ensure the function is not being recursively invoked
that the four-stage circuit breaker is not tripped
that the auction id corresponds to a valid auction
that the auction needs to be reset, either due to having experienced too large a percentage decrease in price, or having existed for too long of a time duration
updates several fields of the existing auction
tic
to reset the auction start time
top
with the current OSM price and buf
percent
vat.suck
s DAI to the kpr
as an incentive if eligible
fires the Redo()
event
An auction that has expired or which is currently offered at a value higher than the oracle threshold will likely not complete at favorable values. The system therefore provides a direct incentive to Clipper.redo
the auction, resetting it's expiration and setting the starting price to match the current oracle price + buf. The redo includes the same Dai incentive to the keeper as the Clipper.kick
, which is based on the flat fee plus the governance-defined percentage of collateral. There is one exception to this incentive. If the tab
or lot * price
remaining is under the Clipper.chost
limit, then there will be no incentive paid to redo
the auction. This is to help prevent incentive farming attacks where no keepers bid on dusty lots, and Clipper.redo
is called repeatedly. If auctions in this state build up, governance may choose to pay a keeper to clean them up.
A started auction can be reverted via the auth
function called yank
in Clipper
contract. This function requires that the auction exists and executes the following actions:
calls dog.digs
in order to decrease its Dirt
and ilk.dirt
values by the remaining auction tab
sends the remaining collateral to its msg.sender
removes the auction struct data
fires the Yank()
event
This function might be thought of as a general purpose operation to migrate existing auctions to a new contract; however, the only use-case now is in the End
module, which takes care of system shutdown.
The End
module will be upgraded together with the auction contracts as a new function End.snip
is required.
This function End.snip
is responsible for calling Clipper.yank
for any running auction when shutdown is triggered. It will receive the collateral from Clipper.yank
and will send it back to the vault owner together with the remaining debt to recover. One consideration to note is that the debt that the vault owner will receive includes the liquidation penalty part already charged.
Dog
InterfaceStandard MakerDAO authorization structure.
DSS core address introspection.
Returns values configured for a given ilk (ex. ETH-A
).
Returns 1
if the system is active.
Getters for the global Hole and Dirt configuration. Both return a rad-precision value.
(Authenticated) Parameter modification functions, available to governance. The precision for a numeric argument should match the parameter being set.
Getter for the chop
value of a given ilk
. chop
has wad precision.
The main liquidation function. Initiates an auction. A keeper calls this function to begin the auction of urn
for a particular ilk
. kpr
is the address where the keeper incentive is sent, allowing keepers to have liquidation rewards sent to a different address than the caller.
(Authenticated) Removes collateral from the accumulator. Called by the Clipper
. The rad
argument has rad precision.
(Authenticated) Deactivates the Dog
and sets live
to 0.
Clipper
InterfaceStandard MakerDAO authorization structure.
The ilk that this Clipper is associated with.
DSS core address introspection.
The address of the pricing function used by this Clipper.
Getters for governance-configured auction parameters.
Getter for the stored product of Vat.ilk.dust
and Dog.ilk.chop
. This value is used as a heuristic for whether a partial purchase will leave an auction with too little collateral and whether incentives should be given out when redo
is called.
Auction counter. Increments each time an auction is initiated.
Returns the id of the auction at index pos
in the list of active auctions.
Returns information on a particular auction. Completed auctions are removed from the mapping.
Returns the current circuit breaker status. 0
for all functions allowed, 1
if kick
cannot be called, 2
if kick
and redo
cannot be called, and 3
if kick
, redo
and take
cannot be called.
(Authenticated) Parameter modification functions, available to governance. Numeric arguments should match the precision of the parameter being set.
(Authenticated) Initiates the auction. Called by the Dog
. tab
is rad-precion, lot
is wad-precision.
Called to reset an auction due to expiry or price deviation.
Called to purchase collateral.
Helpers for iterating the list of active auctions. Use list()
to get the unsorted array of auctions. Get the number of active auctions with count()
. The function active(uint256 pos)
(see above) can be used to access individual entries without needing to call list()
.
Returns a bool if the auction is eligible for redo and the current price.
Updates the chost
value stored in the contract to equal the product of Vat.ilk.dust
and Dog.ilk.chop
(the precision of the final value is rad). This allows take
and redo
to obtain chost
with a single SLOAD
(reading dust
from the Vat
requires 5 SLOAD
operations due to how the API is structured, and reading chop
is an additional SLOAD
).
(Authenticated) Allows an auction to be removed during Emergency Shutdown or via a goveranance action.
This section covers some of the known risks with Liquidations 2.0
Periodically, governance may increase the ilk.dust
amount. When this happens, it's usually because gas has become so expensive it impacts the efficiency of liquidations. That is, the cost of calling Dog.bark()
, Clipper.take()
, or Clipper.redo()
may exceed the collateral offered. Incentives may be used as a remedy to this potential issue and possibly a way for the protocol to keep ilk.dust
lower; however, governance must take care when increasing the ilk
s dust
, tip
, or chip
not to incentivize the creation of many Vaults to farm this incentive. An example of an exploit is as follows:
governance decides to increment dust
by 1500 DAI at the same time they scale ilk.tip
to subsidize auctions.
an attacker realizes it would be profitable between gas and the chop
to shard (fork
) their existing Vault into many Vaults or create many new Vaults.
the spell is voted on and passed
using one transaction the attacker puts their Vaults at the edge of unsafe, poke()
s the OSM when next price is going down, then calls Dog.bark()
on all their Vaults to collect the incentive.
Using the gains, they can also slightly overbid for their Vaults auctions.
In order to thwart this attack governance must be careful when setting ilk.tip
and ilk.chip
so as not to create this perverse incentive.
If the price decreases too quickly it can have the following consequences:
the auction ends without any bid, then it needs to be reset and possibly this will keep happening
bidders end up having reverts due the auction ended before tx confirmation
bidders end up paying much less than what they were willing to pay (possibly generating permanent bad debt)
If the price decreases too slowly it can have the following consequences:
Auction price never catches up with the market price, eventually being reset
After the reset the price catches up, but is less than an optimal market price
After the reset the price catches up, but still leaves bad debt
After the reset the auction price still might not catch up, causing more resets and very likely leaving bad debt
In LIQ-1.2
there is limited front-running risk as it requires capital to participate in auctions; however, in liquidations 2.0 if a keeper chooses to participate with no capital, there is substantial front-running risk from generalized front-running bots. The easier it is to replace the from
address of the transaction with one's own, the greater the risk. To mitigate this risk keepers are encouraged to used authorized proxy contracts to interact with liquidations 2.0 and provide some amount of their own capital when bidding. More aggressive gas prices may also work. Unfortunately, we found no great way to prevent generalized front-running that preserves single-block composability.
Because Clipper.kick
and Clipper.redo
consult the OSM for the collateral price, we are vulnerable to an oracle attack that can only be mitigated by the oracle delay, Dog.Hole
, and ilk.hole
. We must rely on the number of guards in place to prevent price manipulation and oracle attacks. The fact that the price is delayed by one hour, however, prseents a risk of its own: since the price is out-of-date relative to the market, it may be either too high or too low to allow for efficient settlement given other parameters like 'buf' and the price decrease function. The consequences of either case are effectively covered by the sections on the risk of the price decreasing either too quickly or too slowly.
While Dog.Hole
and ilk.hole
can be set much higher in liquidations 2.0, there are still risks to setting this too high. A value for Dog.Hole
that's set too high could result in far too much DAI demand, breaking the peg high. This is somewhat mitigated by the PSM and stablecoin collateral types, but should still factor in to how this parameter is set. An ilk.hole
that is set too high, may have the additional result of causing a downward spiral as the liquidations push the asset price lower. In addition, if there is an oracle attack, this parameter can be thought of as our maximum exposure.
If we set either Dog.Hole
or ilk.hole
too low, we run the risk of not being able to liquidate enough collateral at once. This could lead to a buildup of undercollateralized positions in the system, eventually causing the accrual of bad debt.
Parameters, e.g. tail
, cusp
, or the price decrease function or any of its parameters, can be changed at any time by governance and will affect the behavior of running auctions. Integrators should take this possibility into account, reasoning through how sudden changes in parameters would impact their bidding strategies. Governance should endeavor to change parameters infrequently and if possible, only when there are not any auctions that will be affected.
These invariants have not yet been formally proven, and thus should only be considered intended behavior for now.
Dog
InvariantsClipper
InvariantsThe above is an inequality (and not an equality) because it is impossible to prevent a user or contract from choosing to send collateral to a Clipper
, even though it only makes sense for the Dog
to do so. Governance (or a new module) could create a violation of this property by calling kick
without transferring appropriate collateral to the Clipper
. Thus this is actually a pseudo-invariant that only holds if callers of kick
respect the requirement to send collateral to the Clipper
, but it should hold for the live system if deployment and authorization is done correctly. It should hold if only active auctions are summed over as well.
Dog
and Clipper
The above should hold if only active auctions are summed over as well.
The above should hold if only active auctions are summed over as well.
Note that (c28.1) and (c28.2) together imply (c26.1).
Contract Name: spell.sol
Type/Category: Governance Module
A DSSpell
is an un-owned object that performs one action or series of atomic actions (multiple transactions) one time only. This can be thought of as a one-off DSProxy with no owner (no DSAuth mix-in, it is not a DSThing).
The spell.sol
contract contains two main contracts: DSSPELL and DSSpellBook. DSSPELL is the core contract that, with call instructions set in the constructor, can actually perform the one-time action. DSSpellBook is a factory contract designed to make the creation of DSSPELLs easier.
whom
- is the address the spell is targeting, usually SAI_MOM in SCD.
mana
- is the amount of ETH you are sending, which in spells it is usually 0.
data
- bytes memory calldata.
done
- indicates that the spell has been called successfully.
hat
- A spell comes into effect as the hat when someone calls the lift function. This is only possible when the spell in question has more MKR voted towards it than the current hat.
cast
- Once a spell has become the hat, it can be cast and its new variables will go into effect as part of the live Maker system. It is worth noting that a spell can only be cast once.
lift
- The process whereby a new spell replaces the old proposal.
Note: the hat
and lift
have more to do with ds-chief
than ds-spell
but are important to mention here for context.
whom
, mana
, and data
are set in the constructor, so the action a spell is to perform cannot be changed after the contract has been deployed.
Note that the spell is only marked as "done" if the CALL it makes succeeds, meaning it did not end in an exceptional condition and it did not revert. Conversely, contracts that use return values instead of exceptions to signal errors could be successfully called without having the effect you might desire. "Approving" spells to take action on a system after the spell is deployed generally requires the system to use exception-based error handling to avoid griefing.
spell
- A spell may remain uncast if it did not reach the required amount of MKR in order to pass. If this occurs, the spell may remain available as a later target if enough MKR is voted towards it.
cast
- If, when cast
is called, the spell's one-time action fails, done
does not get flipped and the spell remains castable.
The MKR Governance Token Implementation
Contract Name: token.sol
Type/Category: MKR Module
guy
- user address
wad
- a quantity of tokens, usually as a fixed point integer with 10^18 decimal places.
dst
- refers to the destination address.
mint
- credit tokens at an address whilst simultaneously increasing totalSupply (requires auth).
burn
- debit tokens at an address whilst simultaneously decreasing totalSupply (requires auth).
push
- transfer an amount from msg.sender to a given address.
pull
- transfer an amount from a given address to msg.sender (requires trust or approval).
move
- transfer an amount from a given src address to a given dst address (requires trust or approval).
Standard ERC-20
name
- returns the name of the token - e.g. "MyToken".
symbol
- token symbol.
decimals
- returns the number of decimals the token uses - e.g. 8
, means to divide the token amount by 100000000
to get its user representation.
transfer
- transfers _value amount of tokens to address _to, and MUST fire the Transfer event. This SHOULD throw if the message caller’s account balance does not have enough tokens to spend.
transferFrom
- transfers _value amount of tokens from address _from to address _to, and MUST fire the Transfer event.
approve
- allows _spender to withdraw from your account multiple times, up to the _value amount. If this function is called again it overwrites the current allowance with _value.
totalSupply
- returns the total token supply.
balanceOf
- returns the account balance of another account with address _owner.
allowance
- returns the amount which _spender is still allowed to withdraw from _owner.
Important note: there is a transferFrom
auto approval when src == msg.sender
.
Along with MKR having a standard ERC20 token interface, it also has the addition of DSAuth-protected mint and burn functions; binary approval via MAX_UINT; as well as a push, pull and move aliases for transferFrom operations.
As a utility token: As Dai stability fees earned on Vaults accrue within the Maker Protocol, MKR holders can use MKR to vote to enable the Flapper auction house to sell Dai surplus for MKR. Once the auction is complete the Maker protocol burns the MKR.
As a governance token: MKR is used by MKR holders to vote for the risk management and business logic of the Maker Protocol. Tokens are a simple representation of voting power.
As a recapitalization resource: MKR can autonomously be minted by the Flopper auction house and sold for DAI, which is used to recap the Maker Protocol in times of insolvency.
The MKR token is an ERC-20 token created using DSToken. A key difference to note between Dai and most other popular ERC20 tokens is that both these fields use bytes32
instead of the string
type.
MKR.stop
- ES cannot be triggered. MKR in the chief can still vote, but cannot join or exit.
Electing a Chief contract via an approval voting system
Contract Name: chief.sol
Type/Category: Governance Module
The Ds-Chief
smart contract provides a method to elect a "chief" contract via an approval voting system. This may be combined with another contract, such as DSAuthority
, to elect a ruleset for a smart contract system.
**DSChiefApprovals
provides the following public properties:**
slates
: A mapping of bytes32
to address
arrays. Represents sets of candidates. Weighted votes are given to slates.
votes
: A mapping of voter addresses to the slate they have voted for.
approvals
: A mapping of candidate addresses to their uint
weight.
deposits
: A mapping of voter addresses to uint
number of tokens locked.
GOV
: DSToken
used for voting.
IOU
: DSToken
issued in exchange for locking GOV
tokens.
hat
: Contains the address of the current "chief."
MAX_YAYS
: Maximum number of candidates a slate can hold.
Etch(bytes32 indexed slate)
: Fired when a slate is created.
There are two contracts in ds-chief:
DSChiefApprovals
DSChief, which inherits from DSChiefApprovals.
DSChiefApprovals(DSToken GOV_, DSToken IOU_, uint MAX_YAYS_)
The constructor. Sets GOV
, IOU
, and MAX_YAYS
.
lock(uint wad)
Charges the user wad
GOV
tokens, issues an equal amount of IOU
tokens to the user, and adds wad
weight to the candidates on the user's selected slate. Fires a LogLock
event.
free(uint wad)
Charges the user wad
IOU
tokens, issues an equal amount of GOV
tokens to the user, and subtracts wad
weight from the candidates on the user's selected slate. Fires a LogFree
event.
etch(address[] yays) returns (bytes32 slate)
Save a set of ordered addresses as a slate
and return a unique identifier for it.
vote(address[] yays) returns (bytes32 slate)
Save a set of ordered addresses as a slate
, moves the voter's weight from their current slate to the new slate, and returns the slate's identifier.
vote(bytes32 slate)
Removes voter's weight from their current slate and adds it to the specified slate.
lift(address whom)
Checks the given address and promotes it to s/chief/hat
if it has more weight than the current s/chief/hat
.
DSChief
is a combination of DSRoles
from the ds-roles
package and DSChiefApprovals
. It can be used in conjunction with ds-auth
(as an authority object) to govern smart contract systems.
DSChief(DSToken GOV_, DSToken IOU_, uint MAX_YAYS_)
The constructor. Sets GOV
, IOU
, and MAX_YAYS
.
setOwner(address owner_)
Reverts the transaction. Overridden from DSAuth
.
setAuthority(DSAuthority authority_)
Reverts the transaction. Overridden from DSAuth
.
isUserRoot(address who) constant returns (bool)
Returns true
if the given address is the chief.
setRootUser(address who, bool enabled)
Reverts the transaction. Overridden from DSRoles
.
DSRoles
In general, when we refer to the "hat", it can be any address — be it a single-use contract like ds-spell, a multi-use contract or an individual's wallet. Thus, ds-chief can work well as a method for selecting code for execution just as well as it can for realizing political processes.
Example:
The ds-chief
could be used as a token-weighted voting system governing another set of smart contracts that uses the ds-auth
with ds-roles
. In a scenario such as this, "candidates" would consist of contracts changing the state of the smart contract set under governance. Such a contract being elected as ”hat" would be granted all of the permissions to execute whatever changes are necessary. The ds-chief
could also be used within such a contract set in conjunction with a proxy contract, such as ds-proxy
or a name resolution system like ENS for the purpose of voting in new versions of contracts.
The purpose of the IOU token is to allow for the chaining of governance contracts. In other words, this allows you to have a number of DSChief
or other similar contracts use the same governance token by means of accepting the IOU token of the DSChief
contract before it is a governance token.
Example:
Let’s say there are three DSChief
contracts (chiefA, chiefB, and chiefC) and a chiefA.GOV
that is the MKR token. If we set chiefB.GOV
to chiefA.IOU
and chiefC.GOV
to chiefB.IOU
, this allows all three contracts to run using a common group of MKR.
This type of voting is when each voter selects which candidates they approve of, with the top n "most approved" candidates being then elected. Each voter can cast up to n + k votes, where k equals some non-zero positive integer. This way voters to move their approval from one candidate to another without needing to first withdraw support from the candidate being replaced. Without this in place, moving approval to a new candidate could result in a less-approved candidate moving momentarily into the set of the elected candidates. Note: In the case of ds-chief
, n is equal to 1.
In addition, the ds-chief
weighs votes according to the quantity of a voting token that has been chosen to lock up in the DSChief
or the DSChiefApprovals
contract. It's important to note that the voting token used in a ds-chief
deployment must be specified at the time of deployment and cannot be changed afterward.
If you are writing a front-end UI for this smart contract, please note that the address[] parameters that are passed to the etch
and vote
functions must be byte-ordered sets.
Example:
Using [0x0, 0x1, 0x2, ...]
is valid but using [0x1, 0x0, ...]
and [0x0, 0x0, 0x1, ...]
is not. This ordering constraint allows the contract to cheaply ensure voters cannot multiply their weights by listing the same candidate on their slate multiple times.
MKR users moving their votes from one spell to another
One of the biggest potential failure modes occurs when people are moving their votes from one spell to another. This opens up a gap/period of time when only a small amount of MKR is needed to lift a random hat.
Lift is not called on spells that have more MKR than the current hat
The only way a spell can get the hat is if lift
is called on it. So, even if a spell gains much more MKR on it than the hat, if lift
is never called on it, the hat will remain on a spell that no longer has the most MKR. This could lower the bar for the amount of MKR needed to pass something, potentially making the system less safe.
Stray spells without expiration
Due to the continuous nature of voting, a spell will remain live in the system even if it was not approved to be the governing proposal. This means that MKR holders can continue to vote on the candidate and in times of lower voter participation there is potential for them to introduce a failure mode by voting for an unexpected and/or older candidate. This illustrates why increased voter participation is important and that a higher amount of MKR on the current governing proposal adds to the stability of the system.
Unsafe states when migrating to a new chief contract
When migrating to a new chief, authority must be transferred to the new contract and revoked from the old. This poses a small coordination problem as the new contract must already have enough MKR on its hat
to be safe against governance attacks while the voters enacting the change itself must have enough MKR in the old chief to pass the proposal.
The Dai Savings Rate
Contract Name: pot.sol
Type/Category: DSS —> Rates Module
The Pot is the core of theDai Savings Rate
. It allows users to deposit dai
and activate the Dai Savings Rate and earning savings on their dai
. The DSR is set by Maker Governance, and will typically be less than the base stability fee to remain sustainable. The purpose of Pot is to offer another incentive for holding Dai.
mul(uint, uint)
, rmul(uint, uint)
, add(uint, uint)
& sub(uint, uint)
- will revert on overflow or underflow
rpow(uint x, uint n, uint base)
, used for exponentiation in drip
, is a fixed-point arithmetic function that raises x
to the power n
. It is implemented in assembly as a repeated squaring algorithm. x
(and the result) are to be interpreted as fixed-point integers with scaling factor base
. For example, if base == 100
, this specifies two decimal digits of precision and the normal decimal value 2.1 would be represented as 210; rpow(210, 2, 100)
should return 441 (the two-decimal digit fixed-point representation of 2.1^2 = 4.41). In the current implementation, 10^27 is passed for base
, making x
and the rpow
result both of type ray
in standard MCD fixed-point terminology. rpow
’s formal invariants include “no overflow” as well as constraints on gas usage.
wards
are allowed to call protected functions (Administration)
pie
- stores the address' Pot
balance.
Pie
- stores the total balance in the Pot
.
dsr
- the dai savings rate
. It starts as 1
(ONE = 10^27
), but can be updated by governance.
chi
- the rate accumulator. This is the always increasing value which decides how much dai
- given when drip()
is called.
vat
- an address that conforms to a VatLike
interface. It is set during the constructor and cannot be changed.
vow
- an address that conforms to a VowLike
interface. Not set in constructor. Must be set by governance.
rho
- the last time that drip is called.
The values of dsr
and vow
can be changed by an authorized address in the contract (i.e. Maker Governance). The values of chi
, pie
, Pie
, and rho
are updated internally in the contract and cannot be changed manually.
Calculates the most recent chi
and pulls dai
from the vow
(by increasing the Vow
's Sin
).
A user should always make sure that this has been called before calling the exit()
function.
drip has to be called before a user join
s and it is in their interest to call it again before they exit
, but there isn't a set rule for how often drip is called.
uint wad
this parameter is based on the amount of dai (since wad
= dai
/ chi
) that you want to join
to the pot
. The wad * chi
must be present in the vat
and owned by the msg.sender
.
the msg.sender
's pie
amount is updated to include the wad
.
the total Pie
amount is also updated to include the wad
.
exit()
essentially functions as the exact opposite of join()
.
uint wad
this parameter is based on the amount of dai that you want to exit
the pot
. The wad * chi
must be present in the vat
and owned by the pot
and must be less than msg.sender
's pie
balance.
The msg.senders
pie
amount is updated by subtracting the wad
.
The total Pie
amount is also updated by subtracting the wad
.
Various file function signatures for administering Pot
:
Setting new dsr (file(bytes32, uint256)
)
Setting new vow (file(bytes32, address)
)
The primary usage will be for addresses
to store their dai
in the pot
to accumulate interest over time
The dsr
is set (globally) through the governance system. It can be set to any number > 0%. This includes the possibility of it being set to a number that would cause the DSR to accumulate faster than the collective Stability Fees, thereby accruing system debt and eventually causing MKR to be minted.
If drip()
has not been called recently before an address calls exit()
they will not get the full amount they have earned over the time of their deposit.
A bug in the Pot
could lead to locking of dai
if the exit()
function or the underlying vat.suck()
or vat.move()
functions were to have bugs.
The dsr
rate initially can be set through the Chief. Governance will be able to change the DSR based on the rules that the DS-Chief employs (which would include a Pause for actions).
One serious risk is if governance chooses to set the dsr
to an extremely high rate, this could cause the system's fees to be far too high. Furthermore, if governance allows the dsr
to (significantly) exceed the system fees, it would cause debt to accrue and increase the Flop auctions.
A delegatecall based proxy with an enforced delay
Contract Name: pause.sol
Type/Category: Governance Module
The ds-pause
is a delegatecall based proxy with an enforced delay. This allows authorized users to schedule function calls that can only be executed once a predetermined waiting period has elapsed. The configurable delay attribute sets the minimum wait time that will be used during the governance of the system.
Plans A plan describes a single delegatecall operation and a unix timestamp eta
before which it cannot be executed.
A plan consists of:
usr
: address to delegatecall into
tag
: the expected codehash of usr
fax
: calldata to use
eta
: first possible time of execution (as seconds since unix epoch)
It is important to note that each plan has a unique id, defined as a keccack256(abi.encode(usr, tag, fax, eta)).
Plans can be manipulated in the following ways:
plot
: schedule a plan
exec
: execute a scheduled plan
drop
: cancel a scheduled plan
The pause
contract contains the DSPauseProxy
contract in order to allow plan to be executed in an isolated storage context to protect the pause from malicious storage modification during plan execution.
The ds-pause
was designed to be used as a component in the Maker Protocol’s governance system in order to give affected parties time to respond to decisions. If those affected by governance decisions have e.g. exit or veto rights, then the pause can serve as an effective check on governance power.
In order to protect the internal storage of the pause from malicious writes during plan execution, we perform the delegatecall
operation in a separate contract with an isolated storage context (DSPauseProxy), where each pause has its own individual proxy.
This means that plans are executed with the identity of the proxy
. Thus when integrating the pause into some auth scheme, you will want to trust the pause's proxy and not the pause itself.
A break of any of the following would be classified as a critical issue:
High level
There is no way to bypass the delay
The code executed by the delegatecall cannot directly modify storage on the pause
The pause will always retain ownership of it's proxy
Administrative
authority, owner, and delay can only be changed if an authorized user plots a plan to do so
Plot
A plan can only be plotted if its eta is after block.timestamp + delay
A plan can only be plotted by authorized users
Exec
A plan can only be executed if it has previously been plotted
A plan can only be executed once it's eta has passed
A plan can only be executed if its tag matches extcodehash(usr)
A plan can only be executed once
A plan can be executed by anyone
Drop
A plan can only be dropped by authorized users
DSPause.delay
- when the pause delay is set to the maximum, governance can no longer modify the system.
DSPause.delay
- when the pause delay is set to the minimum, it is easier to pass malicious governance actions.
Accumulation of Stability Fees for Collateral Types
Contract Name: Jug
Type/Category: DSS —> Rates Module
Ilk
: contains two uint256
values—duty
, the collateral-specific risk premium, and rho
, the timestamp of the last fee update
VatLike
: mock contract to make Vat interfaces callable from code without an explicit dependency on the Vat contract itself
wards
: mapping(address => uint)
that indicates which addresses may call administrative functions
ilks
: mapping (bytes32 => Ilk)
that stores an Ilk
struct for each collateral type
vow
: the address
of the Vow contract
base
: a uint256
that specifies a fee applying to all collateral types
These methods require wards[msg.sender] == 1
(i.e. only authorized users may call them).
rely
/deny
: add or remove authorized users (via modifications to the wards
mapping)
init(bytes32)
: start stability fee collection for a particular collateral type
file(bytes32, bytes32, uint)
: set duty
for a particular collateral type
file(bytes32, data)
: set the base
value
file(bytes32, address)
: set the vow
value
drip(bytes32)
: collect stability fees for a given collateral type
drip
drip(bytes32 ilk)
performs stability fee collection for a specific collateral type when it is called (note that it is a public function and may be called by anyone). drip
does essentially three things:
calculates the change in the rate parameter for the collateral type specified by ilk
based on the time elapsed since the last update and the current instantaneous rate (base + duty
);
calls Vat.fold
to update the collateral's rate
, total tracked debt, and Vow surplus;
updates ilks[ilk].rho
to be equal to the current timestamp.
The change in the rate is calculated as:
where "now" represents the current time, "rate" is Vat.ilks[ilk].rate
, "base" is Jug.base
, "rho" is Jug.ilks[ilk].rho
, and "duty" is Jug.ilks[ilk].duty
. The function reverts if any sub-calculation results in under- or overflow. Refer to the Vat documentation for more detail on fold
.
rpow
rpow(uint x, uint n, uint b)
, used for exponentiation in drip
, is a fixed-point arithmetic function that raises x
to the power n
. It is implemented in Solidity assembly as a repeated squaring algorithm. x
and the returned value are to be interpreted as fixed-point integers with scaling factor b
. For example, if b == 100
, this specifies two decimal digits of precision and the normal decimal value 2.1 would be represented as 210; rpow(210, 2, 100)
returns 441 (the two-decimal digit fixed-point representation of 2.1^2 = 4.41). In the current implementation, 10^27 is passed for b
, making x
and the rpow
result both of type ray
in standard MCD fixed-point terminology. rpow
's formal invariants include "no overflow" as well as constraints on gas usage.
Jug stores some sensitive parameters, particularly the base rate and collateral-specific risk premiums that determine the overall stability fee rate for each collateral type. Its built-in authorization mechanisms need to allow only authorized MakerDAO governance contracts/actors to set these values. See "Failure Modes" for a description of what can go wrong if parameters are set to unsafe values.
init(bytes32 ilk)
must called when a new collateral is added (setting duty
via file()
is not sufficient)—otherwise rho
will be uninitialized and fees will accumulate based on a start date of January 1st, 1970 (start of Unix epoch).
base + Ilk.duty
imbalance in drip()
A call to drip(bytes32 ilk)
will add the base
rate to the Ilk.duty
rate. The rate is a calculated compounded rate, so rate(base + duty) != rate(base) + rate(duty)
. This means that if base is set, the duty will need to be set factoring the existing compounding factor in base, otherwise the result will be outside of the rate tolerance. Updates to the base
value will require all of the ilks
to be updated as well.
If drip()
is called very infrequently for some collateral types (due, for example, to low overall system usage or extremely stable collateral types that have essentially zero liquidation risk), then the system will fail to collect fees on Vaults opened and closed between drip()
calls. As the system achieves scale, this becomes less of a concern, as both Keepers and MKR holders are have an incentive to regularly call drip (the former to trigger liquidation auctions, the latter to ensure that surplus accumulates to decrease MKR supply); however, a hypothetical asset with very low volatility yet high risk premium might still see infrequent drip calls at scale (there is not at present a real-world example of this—the most realistic possibility is base
being large, elevating rates for all collateral types).
Various parameters of Jug may be set to values that damage the system. While this can occur by accident, the greatest concern is malicious attacks, especially by an entity that somehow becomes authorized to make calls directly to Jug's administrative methods, bypassing governance. Setting duty
(for at least one ilk) or base
too low can lead to Dai oversupply; setting either one too high can trigger excess liquidations and therefore unjust loss of collateral. Setting a value for vow
other than the true Vow's address can cause surplus to be lost or stolen.
The Maker Protocol's Oracles
Module Name: Oracle Module
Type/Category: Oracles —> OSM.sol & Median.sol
Contract Sources:
An oracle module is deployed for each collateral type, feeding it the price data for a corresponding collateral type to the Vat
. The Oracle Module introduces the whitelisting of addresses, which allows them to broadcast price updates off-chain, which are then fed into a median
before being pulled into the OSM
. The Spot
'ter will then proceed to read from the OSM
and will act as the liaison between the oracles
and dss
.
The Oracle Module has 2 core components consisting of the Median
and OSM
contracts.
The Median
provides Maker's trusted reference price. In short, it works by maintaining a whitelist of price feed contracts which are authorized to post price updates. Every time a new list of prices is received, the median of these is computed and used to update the stored value. The median has permissioning logic which is what enables the addition and removal of whitelisted price feed addresses that are controlled via governance. The permissioning logic allows governance to set other parameters that control the Median's behavior—for example, the bar
parameter is the minimum number of prices necessary to accept a new median value.
You can read straight from the median and in return, you would get a more real-time price. However, this depends on the cadence of updates (calls to poke).
The OSM is similar but has a 1-hour price delay. It has the same process for reading (whitelist, auth, read and peek) as a median. The way the OSM works, is you cannot update it directly but you can poke
it to go and read from something that also has the same structure (the peek
method - in this case, its the median but you can set it to read from anything that conforms to the same interface).
Whenever the OSM reads from a source, it queues the value that it reads for the following hour or following hop
property, which is set to 1 hour (but can be anything). When it is poke
'd, it reads the value of the median and it will save the value. Then the previous value becomes that, so it is always off by an hour. After an hour passes, when poke
d, the value that it saved becomes the current value and whatever value is in the median becomes the future value for the next hour.
spot
- if you poke it with an ilk (ex: ETH) it will read form the OSM and if the price is valid, it updates.
Spot
'ter:In relation to the Spot
the oracle module handles how market prices are recorded on the blockchain. The Spot
ter operates as the interface contract, which external actors can use to retrieve the current market price from the Oracle module for the specified collateral type. The Vat
in turn reads the market price from the spot
ter.
Median
- there is currently no way to turn off the oracle (failure or returns false) if all the oracles come together and sign a price of zero. This would result in the price being invalid and would return false on peek
, telling us to not trust the value.
OSM
poke()
is not called promptly, allowing malicious prices to be swiftly uptaken.
Authorization Attacks and Misconfigurations.
Allowing Users to interact with the Maker Protocol more easily
Module Name: Proxy Module
Type/Category: Proxy —> DsrManager.sol, DssCdpManager.sol, VoteProxy.sol & DssProxyActions.sol
Source code:
The Proxy module was created in order to make it more convenient for users/developers to interact with the Maker Protocol. It contains contract interfaces, proxies, and aliases to functions necessary for both DSR and Vault management and Maker governance.
The DssCdpManager
(aka manager
) was created to enable a formalized process for Vaults to be transferred between owners. In short, the manager
works by having a dss
wrapper that allows users to interact with their Vaults in an easy way, treating them as non-fungible tokens (NFTs).
The VoteProxy facilitates online voting with offline MKR storage. By having a VoteProxy, this allows users to have a linked hot wallet that can pull and push MKR from the proxy’s corresponding cold wallet and to DS-Chief, where voting can take place with the online hot wallet.
There are two main reasons to have/use this contract:
To support two different voting mechanisms
To minimize the time that MKR owners need to have their wallet online.
The dss-proxy-actions
was designed to be used by the Ds-Proxy, which is owned individually by users to interact more easily with the Maker Protocol. Note that it is not intended to be used directly (this will be covered later). The dss-proxy-actions contract was developed to serve as a library for user's ds-proxies.
In general, the ds proxy receives two parameters:
Proxy library address
In this case, the dss proxy actions library.
Call data
Functions and parameters you want to execute.
DSR Manager
CDP Manager
For the developers who want to integrate with the manager
, they will need to understand that the Vault actions are still in the urn
environment. Regardless of this, the manager
tries to abstract the urn
usage by a CDPId
. This means that developers will need to get the urn
(urn = manager.urns(cdpId)
) to allow the join
ing of collateral to that Vault.
Vote Proxy
One-time proxy setup cost: As a new proxy contract user, you will need to set it up before you can use it for future voting. The price of the setup will depend on the current Ethereum gas price but will ultimately make voting easier and safer for users.
Proxy Actions
Using dss-proxy-actions directly can result in the loss of control over your Vault: If you open a new Vault via the dss proxy actions (centralized) without a ds proxy you would be creating a Vault that is owned by the dss proxy actions that anyone could call publicly. It would be owned by the dss proxy actions contact and anyone could execute actions on your Vault. Therefore, if you use the dss proxy actions directly it can be quite risky.
CDP Manager
Potential Issues around Chain Reorganization
Vote Proxy
The loss of private keys for both the hot and cold wallet will prevent you from voting.
Proxy Actions
Ds proxy is a general purpose proxy / there is always a risk when using a proxy
In terms of failure modes, this means you can execute a malicious proxy action as well as a direct action that could potentially send your ETH to a random address. To be extra cautious, you should check your wallets call data and/or audit what your wallet does as they could potentially present users with some unwanted random call data and execute unwanted actions.
Read more here (link)
The mechanism begins with the MKR holders (Maker Governance Voters) of the system. MKR holders will specify the amount of surplus allowed in the system through the voting system. Once they come to an agreement on what it should be set to, surplus auctions are triggered when the system has a surplus of DAI above the amount decided during the vote. System surplus is determined in the Vow
when the Vow
has and has accumulated enough DAI to
In order to determine whether the system has a net surplus, both the income and debt in the system must be reconciled. In short, any user can do this by sending the to the system contract named the "Vow". Provided there is a net surplus in the system, the surplus auction will begin when any user sends the flap transaction to the .
In the context of running a keeper (more info ) in order to perform bids within an auction, a primary failure mode could occur when a keeper specifies an unprofitable price for MKR.
When a Vault is liquidated (bite
- ), the seized debt is put in a queue for an auction in a Vow
(labeled as sin[timestamp]
- the system debt unit). This occurs at the block timestamp of the bite
action. It can be released for auction via flog
(flog
releases queued debt for the auction) once the allotted Vow.wait
(the flop delay) time has expired.
The OSM (named via acronym from "Oracle Security Module") ensures that new price values propagated from the Oracles are not taken up by the system until a specified delay has passed. Values are read from a designated contract (or any contract that has the read()
and peek()
interfaces) via the poke()
method; the read()
and peek()
methods will give the current value of the price feed, and other contracts must be whitelisted in order to call these. An OSM contract can only read from a single price feed, so in practice one OSM contract must be deployed per collateral type.
The central mechanism of the OSM is to periodically feed a delayed price into the MCD system for a particular collateral type. For this to work properly, an external actor must regularly call the poke()
method to update the current price and read the next price. The contract tracks the time of the last call to poke()
in the zzz
variable (rounded down to the nearest multiple of hop
; see for more discussion of this), and will not allow poke()
to be called again until block.timestamp
is at least zzz+hop
. Values are read from a designated DSValue contract (its address is stored in src
). The purpose of this delayed updating mechanism is to ensure that there is time to detect and react to an Oracle attack (e.g. setting a collateral's price to zero). Responses to this include calling stop()
or void()
, or triggering Emergency Shutdown.
Approval Voting: This type of voting is when each voter selects which candidates they approve of, with the top n "most approved" candidates being then elected. Each voter can cast up to n + k votes, where k equals some non-zero positive integer. Read more .
Implementations: If you are writing a front-end UI for this smart contract, please note that the address[] parameters that are passed to the etch
and vote
functions must be byte-ordered sets. Read more
Read more
Contract Source(s): , ,
The initial price is set by reading the current price in the corresponding (OSM) and multiplying by a configurable percentage (the buf
parameter). Note that the current OSM price is between one and two hours delayed relative to the actual market price. A keeper doesn't make a call to Clipper.kick
directly, but rather makes a call to Dog.bark
which in turn calls Clipper.kick
.
Next, collateral is transferred (within the protocol's ) to the who
address provided by the caller. If the caller provided a bytestring with greater than zero length, an external call is made to the who
address, assuming it exposes a function, follow Solidity conventions, with the following signature:
The first argument is the recipient of DAI. That is, this is the address the clipperCallee
must return DAI to. The second argument is DAI owed (as a 45 decimal digit fixed-precision integer, or rad
), the third argument is the collateral being purchased (as an 18 decimal digit fixed-precision integer, or wad
), regardless of the precision of the external token contract, and the last argument is identical to what bytestring the caller originally supplied to the purchase function. As mentioned earlier, a locking mechanism prevents reentry into the purchase function during this external call, and the Clipper.redo
call, for security reasons. Example implementations of the ClipperCallee
interface can be found in the repository.
After the external call completes (or immediately following the transfer of collateral, if no external call was executed), DAI is transferred (internally, within the core ) from msg.sender
to the protocol.
This primitive is useful to express objects that do actions which shouldn't depend on "sender", like an upgrade to a contract system that needs to be given root permission. By convention, it is usually what is used to change SCD or MCD system parameters (where it is given auth via voting in ).
lift
- Although spells cannot be cast a second time, they can be lifted to become the hat more than once if enough MKR votes remain on that proposal. The proposals parameters will not go into effect, however any additional spell will need to have more than that amount of MKR voted towards it in order to become the new hat. See for a description of this having once occurred.
The MKR Module contains the MKR token, which is a deployed contract. It is an ERC20 token that provides a standard ERC20 token interface. It also contains logic for burning and authorized minting of MKR.
Further information about the ERC20 Token standard can be found .
The MKR token has 3 methods of use within the Maker Protocol (reference ):
In short, voters will lock up their voting tokens in order to give their votes some weight in the system. The voting is then done by continuous , where users receive IOU tokens when they lock their voting tokens up, which is useful for secondary governance mechanisms. The IOU tokens may not be exchanged for the locked tokens except by individuals who have actually locked funds in the contract itself, and only up to the amount they have locked.
Most of the functions are decorated with the the note
modifier from , meaning that they fire a standardized event when called. Additionally, one custom event is also provided:
See for inherited features.
If a user wants to join
or exit
1 DAI into/from the Pot, they should send a wad
= to 1 / chi
as the amount moved from their balance will be 1 * chi
(for an example of this, see
The primary function of the Jug smart contract is to accumulate stability fees for a particular collateral type whenever its drip()
method is called. This effectively updates the accumulated debt for all Vaults of that collateral type as well as the total accumulated debt as tracked by the Vat (global) and the amount of Dai surplus (represented as the amount of Dai owned by the ).
vat
: a VatLike
that points the the system's contract
The OSM
(named via acronym from "Oracle Security Module") ensures that new price values propagated from the Oracles are not taken up by the system until a specified delay has passed. Values are read from a designated contract (or any contract that implements the read()
and peek()
interface) via the poke()
method; the read()
and peek()
methods will give the current value of the price feed, and other contracts must be whitelisted in order to call these. An OSM contract can only read from a single price feed, so in practice one OSM contract must be deployed per collateral type.
Read more
The DsrManager
provides an easy to use smart contract that allows service providers to deposit/withdraw dai
into the contract , to start earning the Dai Saving Rate on a pool of dai in a single function call without the need of a ds-proxy
contract. This is useful for smart contracts integrating DSR functionality.
For developers who want to integrate with DsrManager
, it is important to realize that user balances in the pot
will be owned by the DsrManager
, which has an internal mapping to determine user balances. Consequently the deposited Dai in DSR might not show up in solutions that are based on ds-proxy
(such as )
Read more .
Read more .
Read more .
Read more .
When open
is executed, a new urn
is created and a cdpId
is assigned to it for a specific owner
. If the user uses join
to add collateral to the urn
immediately after the transaction is mined, there is a chance that a reorganization of the chain occurs. This would result in the user losing the ownership of that cdpId
/urn
pair, therefore losing their collateral. However, this issue can only arise when avoiding the use of the via a as the user will open
the cdp
and join
collateral in the same transaction.
Read more .
Enables any user to execute a flash mint of Dai.
Contract Name: flash.sol
Type/Category: DSS
The Flash
module allows anyone to mint Dai up to a limit set by Maker Governance with the one condition that they pay it all back in the same transaction with a fee. This allows anyone to exploit arbitrage opportunities in the DeFi space without having to commit upfront capital. Flash
provides many benefits to the Dai ecosystem including, but not limited to:
Improved market efficiencies for Dai.
Democratization of arbitrage - anyone can participate.
Exploits requiring a large amount of capital will be found quicker which makes the DeFi space safer overall.
Fees provide an income source for the protocol.
Debt Ceiling: The maximum amount of Dai any single transaction can borrow. Encoded as line
in rad
units.
Minting Fees: How much additional Dai must be returned to the Flash
module at the end of the transaction. This fee is transferred into the vow
at the end of a successful mint
. Encoded as toll
in wad
units.
Since the Flash module conforms to the ERC3156 spec, you can just use the reference borrower implementation from the spec:
It may be that users are interested in moving dai around in the internal vat balances. Instead of wasting gas by minting/burning ERC20 dai you can instead use the vat dai flash mint function to short cut this.
The vat dai version of flash mint is roughly the same as the ERC20 dai version with a few caveats:
Function Signature
vatDaiFlashLoan(IVatDaiFlashBorrower receiver, uint256 amount, bytes calldata data)
vs
flashLoan(IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data)
Notice that no token is required because it is assumed to be vat dai. Also, the amount
is in rad
and not in wad
.
Approval Mechanism
ERC3156 specifies using a token approval to approve the amount to repay to the lender. Unfortunately vat dai does not have a way to specify delegation amounts, so instead of giving the flash mint module full rights to withdraw any amount of vat dai we have instead opted to have the receiver push the balance owed at the end of the transaction.
Example
Here is an example similar to the one above to showcase the differences:
Managing Vaults to be transferred between users
Contract Name: cdpManager.sol
Type/Category: Vault Management
Summary: The DssCdpManager
(aka manager
) was created to enable a formalized process for Vaults to be transferred between owners, much like assets are transferred. It is recommended that all interactions with Vaults be done through the CDP Manager. Once unlocked collateral has been deposited into the Maker Protocol, users can make use of the following features:
Multi Vault ownership and numerical identification (users can own N number of Vaults)
Vault transferability
Note: The MCD system diagram above shows that the Vault user goes through the proxy in order to interact with the CDP Manager but it is also possible to directly use the CDP Manager contract.
cdpAllow(uint cdp, address usr, uint ok)
: Allow/Disallow (ok
) a usr
address to manage the cdp
.
urnAllow(address usr, uint ok)
: Allow/Disallow (ok
) a usr
address to interact with an urn for the purposes of either entering (src
) or quitting (dst).
open(bytes32 ilk, address usr)
: Opens a new Vault for usr
to be used for an ilk
collateral type.
give(uint cdp, address dst)
: Transfers cdp
to dst
.
frob(uint cdp, int dink, int dart)
: Increments/decrements the ink
amount of collateral locked and increments/decrements the art
amount of debt in the cdp
depositing the generated DAI or collateral freed in the cdp
address.
frob(uint cdp, address dst, int dink, int dart)
: Increments/decrements the ink
amount of collateral locked and increments/decrements the art
amount of debt in the cdp
depositing the generated DAI or collateral freed into a specified dst
address.
flux(bytes32 ilk, uint cdp, address dst, uint wad)
: Moves wad
(precision 18) amount of collateral ilk
from cdp
to dst
.
flux(uint cdp, address dst, uint wad)
: Moves wad
amount of cdp
collateral from cdp
to dst
.
move(uint cdp, address dst, uint rad)
: Moves rad
(precision 45) amount of DAI from cdp
to dst
.
quit(uint cdp, address dst)
: Moves the collateral locked and debt generated from cdp
to dst
.
Note: dst
refers to the destination address.
vat
: core contract address that holds the Vaults.
cdpi
: Auto incremental id.
urns
: Mapping CDPId => UrnHandler
list
: Mapping CDPId => Prev & Next CDPIds
(double linked list)
owns
: Mapping CDPId => Owner
ilks
: Mapping CDPId => Ilk
(collateral type)
first
: Mapping Owner => First CDPId
last
: Mapping Owner => Last CDPId
count
: Mapping Owner => Amount of CDPs
allows
: Mapping Owner => CDPId => Allowed Addr => True/False
The manager
receives the vat
address in its creation and acts as an interface contract between it and the users.
The manager
keeps an internal registry of id => owner
and id => urn
allowing for the owner
to execute vat
functions for their urn
via the manager
.
The manager
keeps a double linked list structure that allows the retrieval of all the Vaults that an owner
has via on-chain calls.
In short, this is what the GetCdps
is for. This contract is a helper contract that allows the fetching of all the Vaults in just one call.
A User executes open
and gets a CDPId
in return.
After this, the CDPId
gets associated with an urn
with manager.urns(cdpId)
and then join
's collateral to it.
The user can then execute frob
to choose which dst
address they want to use to send the generated DAI to.
If the user executes frob
without dst
then the generated DAI will remain in the Vault's urn
. In this case, the user can move
it at a later point in time.
Note that this is the same process for collateral that is freed after frob
(for the frob
function that doesn't require the dst
address). The user can flux
it to another address at a later time.
In the case where a user wants to abandon the manager
, they can use quit
as a way to migrate their position of their Vault to another dst
address.
For the developers who want to integrate with the manager
, they will need to understand that the Vault actions are still in the urn
environment. Regardless of this, the manager
tries to abstract the urn
usage by a CDPId
. This means that developers will need to get the urn
(urn = manager.urns(cdpId)
) to allow the join
ing of collateral to that Vault.
As the manager
assigns a specific ilk
per CDPId
and doesn't allow others to use it for theirs, there is a second flux
function which expects an ilk
parameter. This function has the simple purpose of taking out collateral that was wrongly sent to a Vault that can't handle it/is incompatible.
Frob Function(s):
When you frob
in the CDP manager, you generate new DAI in the vat
via the CDP manager which is then deposited in the urn
that the CDP manager manages. This process depends on which frob
function you use (there exist two frob
functions). In short, one allows a destination address and the other doesn’t require it.
If you use the frob
function that has the destiny (dst
) address, you are saying that you can send any Dai generated or collateral that has been freed. The second frob
function is meant for leaving the collateral in the urn
address because the urn
is owned by the CDP manager. In this case, you would need to manually use the flux
or move
functions to get the DAI or collateral out. These functions (flux
and move
) may be more beneficial for a developer working with the proxy function, as it allows for more flexibility. For example, by using these functions you can move a specific amount of collateral and can use the other functions to do it. Overall, it can make working with it a little more flexible on specific developer needs.
Allowing MKR users to vote with a hot or cold wallet using a proxy voting identity
Contract Name: VoteProxy.sol
Type/Category: Proxy Module
The VoteProxy
contract allows for MKR users to vote with a hot or cold wallet using a proxy voting identity instead of interacting directly with the chief. In addition to supporting two different voting mechanisms, the vote proxy also minimizes the time that MKR owners need to have their wallet(s) online.
approvals
: A mapping of candidate addresses to their uint
weight.
slate
- A mapping of bytes32
to address
arrays. Represents sets of candidates. Weighted votes are given to slates.
votes
: A mapping of voter addresses to the slate they have voted for.
GOV
: DSToken
used for voting.
IOU
: DSToken
issued in exchange for locking GOV
tokens.
The VoteProxy
contract enables MKR owners to vote with the full weight of the MKR they own, for both for Governance and Executive votes. As mentioned above, this process also reduces the risk for MKR users when voting with a cold wallet. This is done by allowing the MKR owner to designate a “hot wallet” which is used to transfer MKR to the proxy and can only be used for voting on Governance and Executive votes. The “hot wallet” can then be used to lock MKR in the voting system and draw it back to their cold wallet.
auth
- Checks to confirm that the sender must be a Cold or Hot Wallet.
lock
- Charges the user wad
MKR tokens, issues an equal amount of IOU tokens to the VoteProxy, and adds wad
weight to the candidates on the user's selected slate.
free
- Charges the user wad
IOU tokens, issues an equal amount of MKR tokens to the user, and subtracts wad
weight from the candidates on the user's selected slate.
vote
- Saves a set of ordered addresses as a slate, moves the voter's weight from their current slate to the new slate, and returns the slate's identifier.
vote(bytes32 slate)
- Removes voter's weight from their current slate and adds it to the specified slate.
One-time proxy setup cost
As a new proxy contract user, you will need to set it up before you can use it for future voting. The price of the setup will depend on the current Ethereum gas price but will ultimately make voting easier and safer for users.
Any MKR moved/transferred from a user's vote proxy during a Polling vote, will be subtracted/removed from any existing poll that a user has voted on. For your vote to count, you must ensure the MKR is in your wallet when the poll ends.
Note: For the users who don't want to use the VoteProxy
, they can now vote directly with a single wallet, by depositing directly into Chief and then voting with their wallet.
The loss of private keys for both the hot and cold wallet will prevent you from voting.
The simplest way to integrate DSR in smart contracts
wad
- some quantity of tokens, as a fixed point integer with 18 decimal places.
ray
- a fixed point integer, with 27 decimal places.
rad
- a fixed point integer, with 45 decimal places.
mul(uint, uint)
, rmul(uint, uint)
, add(uint, uint)
& sub(uint, uint)
- will revert on overflow or underflow
Rdiv
- Divide two ray
s and return a new ray
. Always rounds down. A ray
is a decimal number with 27 digits of precision that is being represented as an integer.
Rdivup
- Divide two ray
s and return a new ray
. Always rounds up. A ray
is a decimal number with 27 digits of precision that is being represented as an integer.
pot
- stores the contract address of the main Dai Savings Rate contract pot
.
dai
- stores the contract address of dai.
daiJoin
- stores the contract address of the Dai token adapter.
supply
- the supply of Dai in the DsrManager.
pieOf
- mapping (addresses=>uint256)
mapping of user addresses and normalized Dai balances (amount of dai / chi
) deposited into pot
.
pie
- stores the address' pot
balance.
chi
- the rate accumulator. This is the always increasing value which decides how much dai is given when drip()
is called.
vat
- an address that conforms to a VatLike
interface.
rho
- the last time that drip
is called.
Calculates and returns the Dai balance of the specified address usr in the DsrManager contract. (Existing Dai balance + accrued dsr)
uint wad
this parameter specifies the amount of Dai that you want to join to the pot. The wad
amount of Dai must be present in the account of msg.sender
.
address dst
specifies a destination address for the deposited dai in the pot. Allows a hot wallet address (msg.sender
) to deposit dai into the pot and transfer ownership of that dai to a cold wallet (or any other address for that matter)
The normalized balance pie
is calculated by dividing wad with the rate acumulator chi
.
the dst
's pieOf
amount is updated to include the pie
.
The total supply amount is also updated by adding the pie
.
wad
amount of dai is transferred to the DsrManager contract
The DsrManager contract joins wad
amount of dai into the MCD system through the dai token adapter daiJoin
.
The DsrManager contract join
s pie
amount of dai to the pot
.
exit()
essentially functions as the exact opposite of join()
.
uint wad
this parameter is based on the amount of dai that you want to exit
the pot
.
address dst
specifies a destination address for the retrieved dai from the pot
. Allows a cold wallet address (msg.sender
) to retrieve dai from the pot
and transfer ownership of that dai to a hot wallet (or any other address for that matter)
The normalized balance pie
is calculated by dividing wad with the rate acumulator chi
.
The msg.sender
’s pieOf
amount is updated by subtracting the pie
.
The total supply amount is also updated by subtracting the pie
.
The contract calls exit on the pot
contract.
It calculates the amount of dai to retrieve by multiplying pie
with chi
.
Then exits the dai from the dai token adapter daiJoin
to the destination address dst
.
exitAll()
functions like the exit
function, except it simply looks into the mapping pieOf
, to determine how much dai the msg.sender
has, and exit
s the entire amount of dai, instead of a specified amount.
In order to use the join
function, you need to approve
the contract to transfer Dai from your wallet. You need to call approve
on the Dai token, specifying the DsrManager
contract and the amount that the contract should be able to pull (can be set to -1
, if you want to set an unlimited approval)
A generalized wrapper for the Maker Protocol
Contract Name: DssProxyActions.sol
Type/Category: Proxy Module
Etherscan
The Proxy Actions contract is a generalized wrapper for the Maker Protocol. It's basically a set of proxy functions for MCD (using dss-cdp-manager). The contract’s purpose is to be used via a personal ds-proxy and can be compared to the original Sag-Proxy as it offers the ability to execute a sequence of actions atomically.
manager
- enables a formalized process for CDPs to be transferred between owners.
ilk
- a collateral type.
usr
- user address.
cdp
- Collateralized Debt Position (now, known as Vault
).
dst
- refers to the destination address.
wad
- quantity of tokens, usually as a fixed point integer with 10^18 decimal places.
rad
- a fixed point integer, with 10^45 decimal places.
dink
- change in collateral.
dart
- change in debt.
ethJoin
- allows native Ether to be used with the system.
gemJoin
- allows standard ERC20 tokens to be deposited for use with the system.
daiJoin
- allows users to withdraw their Dai from the system into a standard ERC20 token.
open()
: creates an UrnHandler
(cdp
) for the address usr
(for a specific ilk
) and allows the user to manage it via the internal registry of the manager
.
give()
: transfers the ownership of the cdp
to the usr
address in the manager
registry.
giveToProxy()
: transfers the ownership of cdp
to the proxy of usr
address (via proxyRegistry
) in the manager
registry.
cdpAllow()
: allows/denies usr
address to manage the cdp
.
urnAllow()
: allows/denies usr
address to manage the msg.sender
address as dst
for quit
.
flux()
: moves wad
amount of collateral from cdp
address to dst
address.
move()
: moves rad
amount of DAI from cdp
address to dst
address.
frob()
: executes frob
to cdp
address assigning the collateral freed and/or DAI drawn to the same address.
quit()
: moves cdp
collateral balance and debt to dst
address.
enter()
: moves src
collateral balance and debt to cdp
.
shift()
: moves cdpSrc
collateral balance and debt to cdpDst
.
lockETH()
: deposits msg.value
amount of ETH in ethJoin
adapter and executes frob
to cdp
increasing the locked value.
safeLockETH()
: same than lockETH
but requiring owner == cdp owner
.
lockGem()
: deposits wad
amount of collateral in gemJoin
adapter and executes frob
to cdp
increasing the locked value. Gets funds from msg.sender
if transferFrom == true
.
safeLockGem()
: same than lockGem
but requiring owner == cdp owner
.
freeETH()
: executes frob
to cdp
decreasing locked collateral and withdraws wad
amount of ETH from ethJoin
adapter.
freeGem()
: executes frob
to cdp
decreasing locked collateral and withdraws wad
amount of collateral from gemJoin
adapter.
draw()
: updates collateral fee rate, executes frob
to cdp
increasing debt and exits wad
amount of DAI token (minting it) from daiJoin
adapter.
wipe()
: joins wad
amount of DAI token to daiJoin
adapter (burning it) and executes frob
to cdp
for decreasing debt.
safeWipe()
: same as wipe
but requiring owner == cdp owner
.
wipeAll()
: joins all the necessary amount of DAI token to daiJoin
adapter (burning it) and executes frob
to cdp
setting the debt to zero.
safeWipeAll()
: same as wipeAll
but requiring owner == cdp owner
.
lockETHAndDraw()
: combines lockETH
and draw
.
openLockETHAndDraw()
: combines open
, lockETH
and draw
.
lockGemAndDraw()
: combines lockGem
and draw
.
openLockGemAndDraw()
: combines open
, lockGem
and draw
.
wipeAndFreeETH()
: combines wipe
and freeETH
.
wipeAllAndFreeETH()
: combines wipeAll
and freeETH
.
wipeAndFreeGem()
: combines wipe
and freeGem
.
wipeAllAndFreeGem()
: combines wipeAll
and freeGem
.
exitETH()
: exits wad
amount of ETH from ethJoin
adapter. This is received in the cdp
urn after the liquidation auction is over.
exitGem()
: exits wad
amount of collateral from gemJoin
adapter. This is received in the cdp
urn after the liquidation auction is over.
freeETH()
: once the system is caged, this recovers the remaining ETH from cdp
(pays the remaining debt if exists).
freeGem()
: once the system is caged, this recovers the remaining token from cdp
(pays remaining debt if exists).
pack()
: once the system is caged, this packs wad
amount of DAI to be ready for cashing.
cashETH()
: once the system is caged, this cashes wad
amount of previously packed DAI and returns the equivalent in ETH.
cashGem()
: once the system is caged, this cashes wad
amount of previously packed DAI and returns the equivalent in gem token.
join()
: joins wad
amount of DAI token to daiJoin
adapter (burning it) and moves the balance to pot
for DAI Saving Rates.
exit()
: retrieves wad
amount of DAI from pot
and exits DAI token from daiJoin
adapter (minting it).
exitAll()
: performs the same actions as exit
but for all of the available amount.
The dss-proxy-actions
was designed to be used by the Ds-Proxy, which is owned individually by users to interact more easily with the Maker Protocol. Note that it is not intended to be used directly (this will be covered later). The dss-proxy-actions
contract was developed to serve as a library for user's ds proxies. In general, the ds proxy receives two parameters:
Proxy library address
In this case, the dss proxy actions library
Call data
Functions and parameters you want to execute
The ds-proxy contact's purpose is to execute transactions and sequences of transactions by proxy. The contract allows code execution using a persistent identity. This can be very useful to execute a sequence of atomic actions. Since the owner of the proxy can be changed, this allows for dynamic ownership models, e.g. a multisig.
In the later example, we will see how the execute function works by using the proxy to execute calldata _data on contract _code.
The functions parameters are:
address _target
bytes memory _data
For the address-target you pass in, that will be the library you want to use, in this case the proxy actions library.
For the Memory data you pass in, that will be the call data of what exactly you want to execute.
Ex: Want to open a Vault; then the bytes memory data you will pass in will be an ABI encoder that executes open function with certain parameters.
Note: This is used for both SCD and MCD.
Your ds-proxy is only for you, so we create it for a wallet, so each wallet has its own ds proxy that nobody else should be able to execute with that proxy.
In MCD, the Vaults will not be owned by your wallet but by your ds proxy, which allows you to execute any function via the ds proxy. Such as performing actions within your Vaults and/or group a lot of actions within one transaction.
The execution looks something like this:
Proxy execute (call to the Ethereum blockchain) where the first parameter is the contract you are using for the library (in this case, dss proxy actions). Not that this is something the frontend will do for you. Example: When you want to open a Vault, it will send the transaction to the proxy to execute the execute function, and will then pass in the dss proxy action address and the second parameter that will be passed is the function itself that your ds proxy needs to execute from the dss proxy actions. In this case, we want to execute the open function from dss proxy action - so your proxy will delegate calling the open function from the dss proxy actions library. We need to do it this way because the second parameter is the bytes call data format parameter, so this function we ABI Encode with signature open. So, we pass the signature and then the actual parameters we want to pass to this function. In this case, the manager, the first param of the open function, the Collateral type, and the address you want to create the Vault for (In this case, the address is 0x123)
Note: UI decides which proxy action the user will use.
Using dss-proxy-actions directly can result in the loss of control over your Vault
If you open a new Vault via the dss proxy actions (centralized) without a ds proxy you would be creating a Vault that is owned by the dss proxy actions that anyone could call publicly. It would be owned by the dss proxy actions contract and anyone could execute actions on your Vault. Therefore, there is significant risk if you directly use the dss proxy actions.
When interacting with the dss-proxy-actions you need a certain allowance to get Dai or MKR funds from the user's wallet. You need allowance from your wallet to the ds-proxy (not dss-proxy-actions). Because, when you execute the dss-proxy actions, you are actually performing that action in the environment of your ds-proxy, which is delegating calls or importing the function from the proxy actions and not executing them directly.
Ds proxy is a general purpose proxy
This means that as a user of the ds-proxy, you can execute whatever you want whether that be the Dss-proxy-actions or any other piece of code. Users are therefore responsible for what they are executing and thus, need to have trust in the UI they are using (similar to any other transaction you are executing from your wallet).
In terms of failure modes, this means you can execute a malicious proxy action as well as a direct action that could potentially send your ETH to a random address. To be extra cautious, you should check your wallets call data and/or audit what your wallet does as they could potentially present users with some unwanted random call data and execute unwanted actions.
Overall, this point is to say that there is always a risk when using a ds proxy.
The ESM is the trigger system for the shutdown of the Maker Protocol
Contract Name: esm.sol
Type/Category: Emergency Shutdown Module
The Emergency Shutdown Module (ESM) is a contract with the ability to call End.cage()
to trigger the Shutdown of the Maker Protocol.
Key Functionalities (as defined in the smart contract)
rely
- Grant an address admin powers
deny
- Revoke admin powers from an address
file
- Allow admin to update threshold min
and address of end
cage
- Permanently disable the shutdown module
fire
- Trigger shutdown by calling End.cage
denyProxy
- Following the wards rely/deny pattern, calls deny on a given contract
join
- Deposit MKR to the shutdown module
burn
- Burn any MKR deposited into the shutdown module
Other
gem
- MKR Token contract [address]
wards(admin: address)
- Whether an address has admin powers [address: uint]
sum(usr: address)
- MKR join balance by user [address: uint]
Sum
- Total MKR deposited [uint]
min
- Minimum MKR amount required for fire
and denyProxy
[uint]
end
- The End contract [address]
live
- Whether the contract is live (not caged) [uint]
MKR holders that wish to trigger Shutdown must join
MKR into the ESM. When the ESM's internal Sum
variable is equal to or greater than the minimum threshold (min
), the ESM's fire()
and denyProxy()
methods may be called by anyone. The fire()
method, in turn, calls End.cage()
, which starts the Shutdown process.
The ESM is intended to be used in a few potential scenarios:
To mitigate malicious governance
To prevent the exploitation of a critical bug (for example one that allows collateral to be stolen)
In the case of a malicious governance attack, the joiners will have no expectation of recovering their funds (as that would require a malicious majority to pass the required vote), and their only option is to set up an alternative fork in which the majority's funds are slashed and their funds are restored.
In other cases, the remaining MKR holders may choose to refund the ESM joiners by minting new tokens.
Note: Governance can disarm the ESM by calling cage()
(this is distinct from End.cage()
).
It is important for users to keep in mind that joining MKR into the ESM is irreversible—they lose it forever, regardless of whether they successfully trigger Shutdown. While it is possible that the remaining MKR holders may vote to mint new tokens for those that lose them triggering the ESM, there is no guarantee of this.
An entity wishing to trigger the ESM but possessing insufficient MKR to do so independently must proceed with caution. The entity could simply send MKR to the ESM to signal its desire and hope others join in; this, however, is poor strategy. Governance, whether honest or malicious, will see this, and likely move to de-authorize the ESM before the tipping point can be reached. It is clear why malicious governance would do so, but honest governance might act in a similar fashion—e.g. to prevent the system from being shut down by trolls or simply to maintain a constant threshold for ESM activation. (Honest governance, or even deceptive malicious governance, would be expected to then replace the ESM.) If governance succeeds in this, the entity has simply lost MKR without accomplishing anything.
If an entity with insufficient MKR wishes to trigger the ESM, it is better off first coordinating with others either off-chain or ideally via a trustless smart contract.. If a smart contract is used, it would be best if it employed zero-knowledge cryptography and other privacy-preserving techniques (such as transaction relayers) to obscure information such as the current amount of MKR committed and the addresses of those in support.
If an entity thinks others will join in before governance can react (e.g. if the delay for governance actions is very long), it is still possible that directly sending insufficient MKR to the ESM may work, but it carries a high degree of risk. Governance could even collude with miners to prevent cage
calls, etc if they suspect an ESM triggering is being organized and wish to prevent it.
The ESM itself does not have an isolated failure mode, but if the other parts of the system do not have proper authorization configurations (e.g. the End contract does not authorize the ESM to call cage()
), then the ESM's fire()
method may be unable to trigger the Shutdown process even if sufficient MKR has been committed to the contract.
In-line comments on the Maker Protocol's Core Smart Contracts
Understanding the various terms used in our smart contracts can involve a significant time investment for developers looking at the codebase for the first time. Due to this fact, we need resources in various formats to ensure they can get up and running quickly. We believe, in order to complement the technical documentation on docs.makerdao.com, that highlighting sections of the codebase and annotating it with comments can surface relevant information developers need while reading the raw contracts in order to understand the contracts better.
Examples of how annotations may be useful for developers:
Highlight terms to display their definitions
Ex: ink
, art
Explain various input parameters in-depth
Ex: Explain the precise role of users defined as gal
or guy
within input parameters.
Link to sections of other smart contracts from where function calls typically originate from
Ex: heal
in Vat
is called from Vow
Additional context to Maker specific design practices that other smart contract developers might not be accustomed to
Ex: Show the event log signature produced by the note
modifier on a function.
Help developers navigate through scenarios that result in calls across multiple smart contracts
Ex: Keepers participating in collateral auctions kick the process with bite
on Cat
, bad debt is settled in Vat
, accounted for in Vow
, and a series of transactions on flip
until they receive collateral they purchased for a discount.
Superimposing the operational view of the system on all functions will help developers build richer mental models of the Maker Protocol.
Ex: Annotating a function's auth
modifier and mentioning the smart contracts authorized to call it.
Annotations will also serve as an open and collaborative discussion layer that helps developers discuss and evolve their understanding of the smart contracts over time.
Hypothesis annotations can be viewed by expanding a sidebar on the top right corner of your browser window.
There are three options to view annotations on a webpage:
Public MakerDAO group that is open for everyone to both read and contribute.
Shutdown
Contract Name: end.sol
Type/Category: DSS
The End
's purpose is to coordinate Shutdown. In short, Shutdown closes down the system and reimburses Dai holders. This process can occur during upgrades (Dai iterations), as well as for security reasons in the event that implementation flaws arise in both in the code and in the design.
cage
- Locks the system and initiates shutdown. This is done by freezing the user-facing actions, canceling flap
and flop
auctions, locking the rest of the system's contracts, disabling certain governance actions that could interfere with the settlement process, and starting the cool-down period.
cage(ilk)
- Tags the Ilk prices / Sets the final price for an ilk (tag
).
skim
- Settles a Vault at the tagged price / Cancels owed Dai from the Vault
free
- Remove (remaining) collateral from a settled Vault. This occurs only after there is no debt in the Vault.
thaw
- Fixes the Dai supply after all Skims / Fixes the total outstanding supply of stablecoin.
flow
- Calculates the fixed price for an ilk, possibly adjusting the cage
price with surplus/deficit.
pack
- Locks Dai ahead of Cash / Puts some stablecoin into a bag
in preparation for cash
.
cash
- Exchange pack
ed Dai for collateral / Exchange some Dai from bag
for a given gem
, share proportional to bag
size.
file
- The Governance configuration—sets various parameter values.
skip
- optionally cancel live auctions.
wards(usr: address)
- Auth Mechanism
vat
- Vat contract
cat
- Cat contract
vow
- Vow contract
spot
- Spotter contract
live
- Cage flag
"Live" contracts have live
= 1, indicating the system is running normally. Thus, when cage()
is invoked, it sets the flag to 0. This includes the End
contract, which means that cage()
can only be invoked once and the subsequent functions cannot be invoked until we are "dead" and in the End process
ilk
- A collateral type
when
- Time of cage / the time of settlement.
wait
- Processing cooldown duration / the length of processing cooldown.
debt
- Outstanding Dai after processing / outstanding stablecoin supply, after system surplus/deficit has been absorbed.
tag
- Cage price / price per collateral type at time of settlement.
gap
- Collateral shortfall / shortfall per collateral considering undercollateralised Vaults.
Art
- Total debt per Ilk/outstanding stablecoin debt.
fix
- Final cash price / the cash price for an ilk (amount per stablecoin).
bag(usr: address)
- Dai packed for cash
/ nontransferable stablecoins ready to exchange for collateral.
out
- Cash out / the amount of already exchanged stablecoin for a given address.
skip
- Optionally cancel live auctions.
wad
- Some quantity of tokens, usually as a fixed point integer with 10^18 decimal places.
urn
- A specific Vault.
tend
- To make a bid, increasing the bid size.
bid
- The quantity being offered for the lot
.
lot
- The quantity up for auction.
dent
- To make a bid, decreasing the lot size.
The cage
is the most complex mechanism within the Maker Protocol. This is because the cage
must alter the behavior of almost every component of the system as well as perform under a variety of possible undercollateralization regimes. Listed below are a number of key properties, such as Dai and Vault parity, or the lack of race conditions, which are desirable (nice-to-have) properties of Shutdown, and are not, in fact, all satisfied by the real-world implementation of Shutdown.
Dai Parity - Assuming there is enough collateral in the system, it is the sum of the values of each collateral redeemed from 1 Dai equal to the target price (i.e., $1.00 ), as judged by the collateral price used by cage
.
Vault Parity - Where each Vault is settled at the collateral price during the time of global settlement. For example, the value of the collateral left in every Vault after cage
will be the equity value of the Vault before cage
, as judged by the collateral price used by cage
, or zero, whichever is greater.
Dai no-race condition - Where every Dai holder will be able to redeem the same quantity of each type of collateral, regardless of when they interact with the contract. This is the most important property, as it ensures fairness for all Dai holders.
Near-immediate Dai redemption - where all Dai can be redeemed for collateral immediately after cage
.
Near-immediate Vault redemption - Where all free collateral can be retrieved immediately after cage
.
No off-chain calculations - where the system does not require the cage
authority to supply any off-chain calculated values. For example, it can rely entirely on the last OSM price feed values.
Dai no-race condition - every dai holder will be able to redeem the same quantity of collateral, regardless of when they interact with the contract.
Vault Parity - Vault Owners are prioritized and are allowed to redeem their excess collateral before Dai holders.
At the time of Emergency Shutdown (ES), individual Vaults, entire collateral types, or the Maker protocol can be undercollateralized, which is when the value of debt exceeds the value of collateral ("negative equity").
Maker's current implementation favors Vaults owners in all cases by allowing them to free their entire amount of excess collateral. Thus, in the low likelihood event that Vaults become undercollateralized, the Dai holders receive a "haircut" to their claim on collateral. In other words, Dai holders’ claim may be less than a dollar’s worth of collateral.
Immediate Vault redemption - After ES is initiated, Vault owners are allowed to free their collateral immediately, provided that they execute all contract calls atomically.
No off-chain calculations - The system does not require the cage authority to supply any off-chain calculated values (e.g. it can rely entirely on the last OSM feed prices).
Vow Buffer Assistance - After ES is initiated, any surplus (and bad debt) in the buffer acts as a reward (and penalty) distributed pro-rata to all Dai Holders. e.g. if 10% of total system debt is in the form of net surplus in the Vow, then Dai holders receive 10% more collateral.
Dai Redemption vs. Vault Redemption Discussion
Since in some edge cases it will not be possible to satisfy all desirable properties at once, a choice must be made about which to prioritize. For example, in the presence of Vaults that have become less than 100% collateralized, a choice must be made between prioritizing Dai holders and Vault holders. If Vault holders are prioritized, those with over-collateralized Vaults keep their excess collateral, while Dai holders receive less than $1 of value per Dai. If Dai holders are prioritized, some collateral must be taken from over-collateralized Vaults to ensure Dai holders receive as close to $1 per Dai as possible. When choosing between Dai vs. Vault priority, Vault priority was chosen because we want to first prioritize Vault holders, meaning that even with a processing period for auction settlement, all Vault holders above their Liquidation Ratio (LR) should be allowed to retrieve their over-collateralization (This is accomplished by calling skim
first on the Vault to remove the debt and the backing collateral and then calling free
to release the remaining collateral from the Vault).
There is a time delay in Shutdown that is configurable by governance. The time delay must expire before any cashing can take place. The general guidance is that it should be long enough to ensure all auctions either finish or get skipped, but there is no guarantee of this in the code. Note that anyone can cancel a flip
auction at any time by calling skip(ilk, auction-id)
after the ilk
has been cage
d (with cage(ilk)
). Flap and flop auctions are frozen by the initial cage()
. Both Flap and Flop auctions can be yank
ed to return the bids to the last bidder.
It’s important to note that auction cancellation is not an immediate process as ecosystem participants must call skip
for flip auctions or call yank
directly for flap and flop auctions. If no one calls these functions, the auctions will not be canceled.
As mentioned above, the End
's purpose is to coordinate the Shutdown of the system. This is an involved and stateful process that takes place over the nine following main steps.
cage()
:The process begins with freezing the system and locking the prices down for each collateral type (ilk
). This is done by freezing the following user entry points:
Halt the ability to deposit collateral and draw Dai from Vaults
Flap/Flop Auctions
Dai Savings Rate (DSR)
Governance entry points like rely/deny and file
Next, the system will stop all of the current flop/flap auctions allowing individual auctions to be cancelled with calls to yank
on the respective auction contract. One reason these auctions get frozen and canceled is because the shutdown process was designed to pass along the system surplus or system debt to Dai holders. Additionally, there are no guarantees regarding the value of MKR during a shutdown, so mechanisms that rely on MKR's market value cannot be relied upon, which means there is no reason to keep running the auctions that impact MKR supply. More specifically, the reason for flop
and flap
auctions getting canceled is as follows:
flap
auctions will no longer serve their purpose. This is because, after a shutdown, the surplus is designed to be allocated to Dai holders. Thus, canceling flap
auctions during shutdown allows the system to return the surplus Dai back to the Vow
’s balance and ultimately back to Dai holders.
flop
auctions also stop serving their purpose. This is because the bad debt is passed as a haircut (lower-than-market-value placed on an asset being used as collateral in a Vault) back to Dai holders if there is no other system surplus available.
As for flip
auctions, they are not immediately canceled (but can be canceled by any user) because they are still tied to the valuable collateral in the system. Collateral auctions continue to run and Keepers can continue to bid on them, and if not, the auctions can be skipped
.
Despite the fact that auctions can continue to run, this does not guarantee that all of the remaining Vaults are overcollaterlized. There is also nothing that can prevent the undercollateralized and unbitten Vaults from existing at the moment cage()
is called.
During this time, cat.bite
cannot be called as the function requires live == 1
, disabling liquidations after shutdown. Additionally, after the End begins, all vaults must be skim
med and then free
d.
Overall, this results in flip auctions being able to continue during Shutdown or by having them reversed by a user by using skip()
(similar logic to flap
auctions). If an auction is skipped, the bids are returned to bidders and collateral is returned to the original Vault (with the liquidation penalty applied in the form of increased debt).
Other notes regarding flip
:
End calls yank
on the Flipper.
yank
closes a tend
-phase (allows bids to be made, thereby increasing the bid
size) of the auction by returning the guy's Dai bid and moving the Gems from the Flipper to the End.
dent
phase auctions (allows bids to be made, decreasing the lot
size) continue to the deal
phase as they have already raised the necessary Dai and are already in the process of returning Gems to the original Vault holder.
Notes:
MKR could still have value if the same token is tied to another deployment of the system. Note that the system makes no assumptions about the economic value of MKR post-Shutdown.
It is important to note that on-auction debt and surplus are canceled and balances are transferred to the End
contract. The last step in this process is to begin the cooldown period.
cage(ilk)
:The cage(ilk)
works by setting the cage
price for each ilk
. It does this by reading off of the price feed. This is required as we must first process the system state before it is possible to calculate the final Dai/collateral price. In particular, we need to determine two things:
(a) The gap
, which is the collateral shortfall per collateral type by considering under-collateralized Vaults. (b) The debt
, which is the outstanding Dai supply after including the system surplus/deficit.
We first determine (a) by processing all Vaults with the skim
function described below. Next, you can see how (b) unfolds below.
skim(ilk, urn)
The skim(ilk)
function works to cancel all of the owed DAI from the Vault. Any excess collateral remains within the Vault(s) for the owner(s) to claim. Then, the backing collateral is taken.
We then determine debt (b) by processing the ongoing Dai generation processes of the auctions. This is done to ensure that the auctions will not generate any further Dai income. This guarantees that ongoing auctions will not change the total debt of the system. This includes the two-way auction (Flip
) model not allowing for any more Dai to be generated. This means that the Dai generation comes from tend
auctions. Thus, if everything is in dent
we know the generation is over. This occurs when all auctions are in the reverse (dent) phase. In addition to ensuring that the auctions will not generate any further Dai, the Dai Savings Rate (pot.drip
) must also be shut off during the End so that the total debt does not change.
Example:
In terms of user scenarios, this means that the process begins with users starting to bid more and more Dai until reaching the debt. Next, they start offering less and less collateral.
The auctions that are in the second phase (dent
- reverse auctions) no longer affect any more of the total debt, as the Dai was already recovered. Lastly, for the auctions in the first phase, they can be canceled and return the collateral and debt to the Vault.
There are two methods of ensuring this:
By using wait
; or
By using skip
.
wait
or skip
wait
sets the cooldown period. The time duration of wait
only needs to be long enough to be able to skim
all of the undercollateralized Vaults and skip
all tend
-phase auctions. This means that it can, in fact, be quite short (for example 5 minutes). However, due to the possibility of scenarios such as network congestion occurring, it may be set longer.
When using skip
, it will cancel all ongoing auctions and seize the collateral. This allows for faster processing of the auctions at the expense of more processing calls. This option allows Dai holders to retrieve their collateral much faster. The skip(ilk, id)
will then proceed to cancel each of the individual flip auctions in the forward phase (tend
) and retrieve all of the collateral and return Dai to the bidder. After this occurs, the reverse phase (dent
) auctions can continue as they normally would, by performing either wait
or skip
.
Note that both of these options are available in this implementation, with skip
being enabled on a per-auction basis. When a Vault has been processed and has no debt remaining, the remaining collateral can be removed.
free(ilk)
:The free(ilk)
method will then remove the collateral from the caller's Vault. After skim
has been called, free(ilk)
allows the owner to call as they need. It will remove all of the collateral remaining after step 3, basically, all of the collateral that was not backing the debt. If you did not have debt in your Vault at the time of the End
you do not need to do step 3 and can proceed directly to this step to free your collateral.
thaw()
After the processing period has elapsed, the calculation of the final price for each collateral type is possible using the thaw
function. The assumption is that all under-collateralized Vaults are processed and all auctions have unwound. The purpose of thaw
is to fix the total outstanding supply of Dai. Note that it may also require extra Vault processing to cover the vow
surplus. The vat.dai(vow) == 0
requirement is what guarantees that the vow surplus has been taken into account, which means that before you can thaw
, you must skim
as many Vaults as needed in order to cancel any Dai surplus in the vow. Canceling Dai surplus is done by calling vow.heal
before thaw
.
flow(ilk)
The flow(ilk)
function will calculate the cash price for a given ilk (fix
) and adjusts the fix
in the case of deficit/surplus. At this point in the mechanism, we have computed the final price for each collateral type and Dai holders can now turn their Dai into collateral. Each unit of Dai can claim a fixed basket of collateral. Dai holders must first pack
some Dai into a bag
. Once packed, Dai cannot be unpacked and is not transferable. More Dai can be added to a bag later.
pack(wad)
The pack(wad)
will place Dai into a bag in preparation for cash
, which dispenses collateral to bag
holders. The bigger the bag
, the more collateral can be released.
cash(ilk, wad)
Lastly, we use cash(ilk, wad)
to exchange some of the Dai from your bag
for gems from a specific ilk. Note that the number of gems will be limited by how much packed Dai you have (how big your bag
is).
We expect Keepers to buy up Dai from smallholders in order to claim collateral.
This is because a majority of Dai holders are uncertain on how to do perform specific actions during the End
process. Due to this fact, we depend on third parties to buy up post-cage Dai to use for reclaiming large portions of Dai. Overall, there will be large amounts of Dai leftover in the system.
cash
At the end of the Global Settlement process, users will get a share of each collateral type. This will require them to call cash
through each ilk in the system to completely cash out their Dai.
Example: Users will need to call cash(ilk, wad)
to redeem the proportional amount of the specified collateral that corresponds to the amount of Dai that was pack
’ ed, where the pack
function is used to aid with the redeeming of the different collaterals in different transactions. For example, let’s say you have 1000 Dai. You first pack
for the respective collateral types (ilks
), then for each cash
call, you will redeem what the 1000 Dai represents from the total Dai supply. In return, you will get the same proportion of that same collateral that was locked for all Dai holders. Therefore, the best approach a Dai holder can take is to cash
every collateral type (ilk
).
An additional thing to note is that if any ilks
are undercollateralized, Dai holders will end up taking a bit of a cut as a result. This is because other ilks
will not be used to "cover for" an underwater collateral type.
In order to prevent a DOS attack, whatever entity calls the thaw
function should ensure that Vow.heal()
is called within the same transaction.
Example: An attacker can send small amounts of Dai in the vat
to the vow
. This would prevent thaw
from being called and thus, End from progressing. To prevent this, we would call heal
to clear out that excess Dai and proceed with thaw
.
It is important to set the correct wait
period. If you set an incorrect wait
/cooldown period (if this is set early on) then auctions are later extended and this is not reset.
It is important to note that the main problem to point out here is that if the wait
allows thaw
to be called too early, all the Flip
auctions may not have completed and the system may have an incorrect accounting of total debt
.
The Cooldown period's purpose is so that auctions can be skip
ped and skim
applies to all Vaults (not just the undercollateralized ones).
Once the time period between global settlement and the cool-down period has passed, Dai holders are exposed to the ability to redeem their Dai for collateral.
Therefore the wait
value should not be too large, so governance should advise for this at least.
Vault (Skim/End) Keeper - is a tool to skim underwater Vaults if not all undercollateralized Vaults are accounted for. This Keeper could be used by Maker Stakeholders such as large Dai holders/custodians, MKR governors, Redemption keepers and more.
Since End
will read the Collateral price from the pip
, this can result in the collateral price being only as accurate as the last recorded price. If pip
returns a bad price due to oracles getting hacked, the End
will be affected.
If a bad price is queued up in the OSM, we need to make sure to fix the tag
before the price is called on Global Settlement.
Example Scenario: If a bad price goes through the median
, it takes approximately 30 min for the OSM, and then the Global Settlement process takes over an hour to work. Therefore, by the time it triggers, you will have a bad price in the pip
and this will cause the system to fail.
Saving this from happening depends on how quickly you react when it comes to an oracles attack as well as overall governance decisions.
We do not believe Global Settlement is a viable solution to bad Oracles. They impact the system too quickly for Global Settlement to help.
An Oracle attack can be caused by two main events:
Low prices, which makes liquidations easy.
During Global Settlement, setting fake low prices would allow Dai holders to get too much collateral for their Dai, making this attack profitable.
High prices, which helps with buying a lot of Dai.
When paired with a subsequent Global Settlement, this could be used to steal a lot / all of the collateral as that Dai would then be used to cash out.
Example: If a user is able to push up the price of a collateral type, it would allow them to mint a larger amount of Dai, resulting in a larger share of the Dai pool. Thus, they could claim a larger proportional share of the collateral whether it was of one type or a slice of all types. They could then readjust the manipulated prices before that collateral slice was fixed in the End
.
End.wait
when set to maximum can result in it not being possible to call thaw
and therefore resulting in the Shutdown not being able to proceed.
End.wait
, when set to the minimum, can result in thaw
being called before all auctions have finished, resulting in debt being calculated incorrectly and ultimately setting a wrong collateral price.
When End.cage
is called, all Dai holders are left holding an unstable asset in place of their desired stable asset. This could result in a market price crash across all collateral due to liquidations & sell-offs.
A list of words, terms, variables, functions and more relating to the Maker Protocol
guy
, usr
: some address
wad
: some quantity of tokens, usually as a fixed point integer with 18 decimal places.
ray
: a fixed point integer, with 27 decimal places.
rad
: a fixed point integer, with 45 decimal places.
file
: administer some configuration value
auth
: check whether an address can call this method
ward
: an address that is allowed to call auth'ed methods
rely
: allow an address to call auth'ed methods
deny
: disallow an address from calling auth'ed methods
Authority
- checks whether an address can call this method
Kiss
- cancels out surplus and on-auction debt
gem
: collateral tokens.
dai
: stablecoin tokens.
sin
: unbacked stablecoin (system debt, not belonging to any urn
).
ilk
: a collateral type.
rate
: stablecoin debt multiplier (accumulated stability fees).
take
: collateral balance multiplier.
Ink
: total collateral balance.
Art
: total normalized stablecoin debt.
init
: create a new collateral type.
urn
: a specific Vault.
ink
: collateral balance.
art
: normalized outstanding stablecoin debt.
slip
: modify a user's collateral balance.
flux
: transfer collateral between users.
move
: transfer stablecoin between users.
grab
: liquidate a Vault.
heal
: create / destroy equal quantities of stablecoin and system debt (vice
).
fold
: modify the debt multiplier, creating / destroying corresponding debt.
toll
: modify the collateral multiplier, creating / destroying corresponding collateral.
suck
: mint unbacked stablecoin (accounted for with vice
).
spot
: collateral price with safety margin, i.e. the maximum stablecoin allowed per unit of collateral.
line
: the debt ceiling for a specific collateral type.
Line
: the total debt ceiling for all collateral types.
dust
: the minimum possible debt of a Vault.
frob
: modify a Vault.
lock
: transfer collateral into a Vault.
free
: transfer collateral from a Vault.
draw
: increase Vault debt, creating Dai.
wipe
: decrease Vault debt, destroying Dai.
dink
: change in collateral.
dart
: change in debt.
calm
: true when the Vault remains under both collateral and total debt ceilings.
cool
: true when the stablecoin debt does not increase.
firm
: true when the collateral balance does not decrease.
safe
: true when the Vault's ratio of collateral to debt is above the collateral's liquidation ratio.
fork
: to split a Vault - binary approval or splitting/merging Vaults.
dink
: amount of collateral to exchange.
dart
: amount of stablecoin debt to exchange.
wish
: check whether an address is allowed to modify another address's gem or dai balance.
hope
: enable wish
for a pair of addresses.
nope
: disable wish
for a pair of addresses.
Note: art
and Art
represent normalized debt, i.e. a value that when multiplied by the correct rate gives the up-to-date, current stablecoin debt.
debt
: the sum of all dai
(the total quantity of dai issued).
vice
: the sum of all sin
(the total quantity of system debt).
ilk.Art
: the sum of all art
in the urn
s for that ilk
.
debt
: is vice
plus the sum of ilk.Art * ilk.rate
across all ilk
's.
gem
: can always be transferred to any address by it's owner.
dai
: can only move with the consent of it's owner / can always be transferred to any address by it's owner.
Other
LogNote
: a general purpose log that can be added to any function from a contract.
Ilk
: contains two uint256
values—duty
, the collateral-specific risk premium, and rho
, the timestamp of the last fee update
VatLike
: mock contract to make Vat interfaces callable from code without an explicit dependency on the Vat contract itself
wards
: mapping(address => uint)
that indicates which addresses may call administrative functions
ilks
: mapping (bytes32 => Ilk)
that stores an Ilk
struct for each collateral type
vow
: the address
of the Vow contract
base
: a uint256
that specifies a fee applying to all collateral types
These methods require wards[msg.sender] == 1
(i.e. only authorized users may call them).
rely
/deny
: add or remove authorized users (via modifications to the wards
mapping)
init(bytes32)
: start stability fee collection for a particular collateral type
file(bytes32, bytes32, uint)
: set duty
for a particular collateral type
file(bytes32, data)
: set the base
value
file(bytes32, address)
: set the vow
value
drip(bytes32)
: collect stability fees for a given collateral type
mat
: the liquidation ratio
chop
: the liquidation penalty
lump
: the liquidation quantity, i.e. the fixed debt quantity to be covered by any one liquidation event
bite
: initiate liquidation of a Vault
flip
: liquidate collateral from a Vault to cover a fixed quantity of debt
sin
: the system debt queue.
Sin
: the total amount of debt in the queue.
Ash
: the total amount of on-auction debt.
wait
: length of the debt queue
sump
: debt auction bid size, i.e. the fixed debt quantity to be covered by any one debt auction
dump
: debt auction lot size, i.e. the starting amount of MKR offered to cover the lot
/sump
bump
: surplus auction lot size, i.e. the fixed surplus quantity to be sold by any one surplus auction
hump
: surplus buffer, must be exceeded before surplus auctions are possible
Other terms included in Vow documentation:
move
: transfers stablecoin between users.
kick
: starts an auction.
woe
: indicates specifically bad debt, or be used as a variable name for any amount of debt.
wards [usr: address]
, rely
/deny
/auth
: Auth mechanisms
Bid
: State of a specific Auction {bid
, lot
, guy
, tic
, end
, usr
, gal
, tab
}
bid
: Bid amount (DAI)/ DAI paid
lot
: quantity up for auction / collateral gems for sale
guy
: high bidder (address)
tic
: Bid expiry
end
: when the auction will finish / max auction duration
usr
: address of the Vault being auctioned. Receives gems during the dent
phase
gal
: recipient of auction income / receives dai income (this is the Vow contract)
tab
: total dai wanted from the auction / total dai to be raised (in flip auction)
bids[id: uint]
: storage of all bids
vat
: storage of the Vat's address
ilk
: id of the Ilk for which the Flipper is responsible
beg
: minimum bid increase (default: 5%)
ttl
: bid duration (default: 3 hours)
tau
: auction length (default: 2 days)
kicks
: Total auction count, used to track auction id
s
kick
: function used by Cat
to start an auction / Put collateral up for auction
tick
: restart an auction if there have been 0 bids and the end
has passed
tend
: first phase of an auction. Increasing Dai bid
s for a set lot
of Gems
dent
: second phase of an auction. Set Dai bid
for a decreasing lot
of Gems
file
: function used by governance to set beg
, ttl
, and tau
deal
: claim a winning bid / settles a completed auction
yank
: used during Global Settlement to move tend
phase auctions to the End
by retrieving the collateral and repaying dai to the highest bidder.
Flap
: surplus auction (selling stablecoins for MKR) [contract]
wards [usr: address]
: rely
/deny
/auth
Auth Mechanisms [uint]
Bid
: State of a specific Auction[Bid]
bid
: quantity being offered for the lot
(MKR) [uint]
lot
: lot amount (DAI) [uint]
guy
: high bidder [address]
tic
: Bid expiry [uint48]
end
: when the auction will finish [uint48]
bids (id: uint)
: storage of all Bid
s by id
[mapping]
vat
: storage of the Vat's address [address]
ttl
: bid lifetime / max bid duration (default: 3 hours) [uint48]
lot
: lot amount (DAI) [uint]
beg
: minimum bid increase (default: 5%) [uint]
tau
: maximum auction duration (default: 2 days) [uint48]
kick
: start an auction / put up a new DAI lot
for auction [function]
tend
: make a bid, thus increasing the bid size / submit an MKR bid (increasing bid
) [function]
deal
: claim a winning bid / settling a completed auction [function]
gem
: MKR Token [address]
kicks
: total auction count [uint]
live
: cage flag [uint]
file
: used by governance to set beg
, ttl
, and tau
[function]
yank
: is used during Global Settlement to move tend
phase auctions to the End
by retrieving the collateral and repaying DAI to the highest bidder. [function]
tick()
: resets the end
value if there has been 0 bids and the original end
has passed.
flop
: debt auction (covering debt by inflating MKR and selling for stablecoins)
lot
: quantity up for auction / gems for sale (MKR)
guy
: high bidder (address)
gal
: recipient of auction income / receives dai income (this is the Vow contract)
ttl
: bid lifetime (Max bid duration / single bid lifetime)
beg
: minimum bid decrease
pad
: Increase for lot
size during tick
(default to 50%)
tau
: maximum auction duration
end
: when the auction will finish / max auction duration
kick
: start an auction / Put up a new MKR bid
for auction
dent
: make a bid, decreasing the lot size (Submit a fixed DAI bid
with decreasing lot
size)
deal
: claim a winning bid / settles a completed auction
vat
: the Vat's address
gem
: MKR Token (address)
kicks
: Total auction count, used to track auction id
s
live
: Cage flag
wards [usr: address]
, rely
/deny
/auth
: Auth mechanisms
Bid
: State of a specific Auction {bid
, lot
, guy
, tic
, end
}
bid
: Bid amount inDAI / DAI paid
tic
: Bid expiry
tick
: restarts an auction
cage
: Locks the system and initiates shutdown. This is done by freezing the user-facing actions, canceling flap
and flop
auctions, locking the rest of the system's contracts, disabling certain governance actions that could interfere with the settlement process, and starting the cool-down period.
cage(ilk)
: Tags the Ilk prices / Sets the final price for an ilk (tag
).
skim
: Settles a Vault at the tagged price / Cancels owed Dai from the Vault
free
: Remove (remaining) collateral from a settled Vault. This occurs only after there is no debt in the Vault.
thaw
: Fixes the Dai supply after all Skims / Fixes the total outstanding supply of stablecoin.
flow
: Calculates the fixed price for an ilk, possibly adjusting the cage
price with surplus/deficit.
pack
: Locks Dai ahead of Cash / Puts some stablecoin into a bag
in preparation for cash
.
cash
: Exchange pack
ed Dai for collateral / Exchange some Dai from bag
for a given gem
, share proportional to bag
size.
file
: The Governance configuration—sets various parameter values.
skip
: optionally cancel live auctions.
wards(usr: address)
: Auth Mechanism
vat
: Vat contract
cat
: Cat contract
vow
: Vow contract
spot
: Spotter contract
live
: Cage flag
"Live" contracts have live
= 1, indicating the system is running normally. Thus, when cage()
is invoked, it sets the flag to 0. This includes the End
contract, which means that cage()
can only be invoked once and the subsequent functions cannot be invoked until we are "dead" and in the End process
ilk
: A collateral type
when
: Time of cage / the time of settlement.
wait
: Processing cooldown duration / the length of processing cooldown.
debt
: Outstanding Dai after processing / outstanding stablecoin supply, after system surplus/deficit has been absorbed.
tag
: Cage price / price per collateral type at time of settlement.
gap
: Collateral shortfall / shortfall per collateral considering undercollateralised Vaults.
Art
: Total debt per Ilk/outstanding stablecoin debt.
fix
: Final cash price / the cash price for an ilk (amount per stablecoin).
bag(usr: address)
: Dai packed for cash
/ nontransferable stablecoins ready to exchange for collateral.
out
: Cash out / the amount of already exchanged stablecoin for a given address.
skip
: Optionally cancel live auctions.
wad
: Some quantity of tokens, usually as a fixed point integer with 10^18 decimal places.
urn
: A specific Vault.
tend
: To make a bid, increasing the bid size.
bid
: The quantity being offered for the lot
.
lot
: The quantity up for auction.
dent
: To make a bid, decreasing the lot size.
vat
: storage of the Vat
’s address.
ilk
: id of the Ilk for which a GemJoin
is created for.
gem
: the address of the ilk
for transferring.
dai
: the address of the dai
token.
one
: a 10^27 uint used for math in DaiJoin
.
live
: an access flag for the join
adapter.
dec
: decimals for the Gem.
mul(uint, uint)
/rmul(uint, uint)
: will revert on overflow or underflow
bite(bytes32, address)
: will revert if lot
or art
are larger than or equal to 2^255.
bite
: will not leave a Vault with debt and no collateral.
wards
: are allowed to call protected functions (Administration and cage()
)
ilks
: stores Ilk
structs
Ilk
is the struct with the address of the collateral auction contract (flip
), the penalty for that collateral to be liquidated (chop
) and the maximum size of collateral that can be auctioned at once (lump
).
live
: must be 1
for the Cat
to bite
(see cage
in mechanisms)
vat
: address that conforms to a VatLike
interface (see vat
documentation [TODO - Link] for more info). It is set during the constructor and cannot be changed.
vow
: address that conforms to a VowLike
interface (see vow
documentation [TODO - Link] for more info).
Events
Bite
: emitted when a bite(bytes32, address)
is successfully executed. Contains:
ilk
: Collateral
urn
: Vault address
ink
: see lot
in bite
art
: see art
in bite
tab
: see tab
in bite
flip
: address of the auction contract
id
: ID of the auction in the Flipper
ilk
: a given collateral type
ilk.pip
: the contract which holds the current price of a given ilk
ilk.mat
: the liquidation ratio for a given ilk
vat
: the core of the mcd system
par
: the relationship between DAI and 1 unit of value in the price. (Similar to TRFM)
Collateral
Only authorized users can update any variables in contract
Math
mul(uint, uint)
, rmul(uint, uint)
, add(uint, uint)
& sub(uint, uint)
: will revert on overflow or underflow
rpow(uint x, uint n, uint base)
: used for exponentiation in drip
, is a fixed-point arithmetic function that raises x
to the power n
.
Auth
wards
: are allowed to call protected functions (Administration)
Storage
pie
: stores the address' Pot
balance.
Pie
: stores the total balance in the Pot
.
dsr
: the dai savings rate
. It starts as 1
(ONE = 10^27
), but can be updated by governance.
chi
: the rate accumulator. This is the always increasing value which decides how much dai
: given when drip()
is called.
vat
: an address that conforms to a VatLike
interface. It is set during the constructor and cannot be changed.
vow
: an address that conforms to a VowLike
interface. Not set in constructor. Must be set by governance.
rho
: the last time that drip is called.
name
: Dai Stablecoin
symbol
: DAI
version
: 1
decimals
: 18
wad
: fixed point decimal with 18 decimals (for basic quantities, e.g. balances).
totalSupply
: Total DAI Supply
balanceOf(usr: address)
: User balance
allowance(src: address, dst: address)
: Approvals
nonces(usr: address)
: Permit nonce
The State of the Maker Protocol
Module Name: Vault Core Module
Type/Category: Vault Core Module —> ( Vat.sol, Spot.sol )
Contract Sources:
The Core Module is crucial to the system as it contains the entire state of the Maker Protocol and controls the central mechanisms of the system while it is in the expected normal state of operation.
Vat
- The core Vault, Dai, and collateral state is kept in the Vat
. The Vat
contract has no external dependencies and maintains the central "Accounting Invariants" of Dai.
Spot
- poke
is the only non-authenticated function in spot
. The function takes in a bytes32
of the ilk
to be "poked". poke
calls two external
functions, peek
and file
.
The methods in the Vat
are written to be as generic as possible and as such have interfaces that can be quite verbose. Care should be taken that you have not mixed the order of parameters. Any module that is auth
ed against the Vat
has full root access, and can, therefore, steal all collateral in the system. This means that the addition of a new collateral type (and associated adapter) carries considerable risk.
The methods in the spotter
are relatively basic compared to most other portions of dss
. There is not much room for user error in the single unauthed method poke
. If an incorrect bytes32
is supplied the call will fail. Any module that is authed against the spot
has full root access, and can, therefore, add and remove which ilks
can be "poked". While not completely breaking the system, this could cause considerable risk.
Vat
- A bug in the Vat
could be catastrophic and could lead to the loss (or locking) of all Dai and Collateral in the system. It could become impossible to modify Vault's or to transfer Dai. Auctions could cease to function. Shutdown could fail.
Spot
- A bug in spot
would most likely result in the prices for collaterals not being updated anymore. In this case, the system would need to authorize a new spot
which would then be able to update the prices. Overall this is not a catastrophic failure as this would only pause all price fluctuation for some period.
Vat
- relies upon a set of trusted oracles to provide price data. Should these price feeds fail, it would become possible for unbacked Dai to be minted, or safe Vaults could be unfairly liquidated.
Spot
- relies upon a set of trusted oracles to provide price data. Should these price feeds fail, it would become possible for unbacked Dai to be minted, or safe Vaults could be unfairly liquidated.
Vat
- Governance can authorize new modules against the Vat
. This allows them to steal collateral (slip
) or mint unbacked Dai (suck
/addition of worthless collateral types). Should the crypto economic protections that make doing so prohibitively expensive fail, the system may be vulnerable and left open for bad actors to drain collateral.
You can configure the behavior of Dai.js by passing different arguments to Maker.create
. The first argument is the name of a preset, and the second is an options object.
'browser'
Use this preset when using the library in a browser environment. It will attempt to connect using window.ethereum
or window.web3
.
'http'
Connect to a JSON-RPC node. Requires url
to be set in the options.
'test'
Use a local node (e.g. Ganache) running at http://127.0.0.1:2000
, and sign transactions using node-managed keys.
privateKey
Optional. The private key used to sign transactions. If this is omitted, the first account available from the Ethereum provider will be used. Only used with the 'http'
preset.
url
The URL of the node to connect to. Only used with the 'http'
preset.
web3.transactionSettings
Object containing transaction options to be applied to all transactions sent through the library.
Default value: { gasLimit: 4000000 }
web3.confirmedBlockCount
Default value: 5
web3.inject
For advanced users. You can inject your own custom instance of a Web3 provider with this, instead of using the default HttpProvider.
log
Set this to false
to reduce the verbosity of logging.
autoAuthenticate
Set this to false
to create the Maker instance without connecting yet. If so, you must run await maker.authenticate()
before using any other methods.
Returns: service object
Return a service instance that was included in this instance of maker
.
As mentioned above, the Maker
instance can be used in read-only mode, if you just want to read data from the blockchain without signing any transactions. Simply omit the privateKey
option.
Dai.js is the JavaScript library that helps developers easily build DeFi applications on top of the Maker Protocol, MakerDAO's platform of smart contracts.
The library features a pluggable, service-based architecture, which allows users to easily integrate Maker functionality into their own apps. It also includes convenient configuration presets for out-of-the-box usability and support for both front-end and back-end applications, plus plugins for integrating with Maker governance, hardware wallets, and both Single-Collateral and Multi-Collateral Dai.
Install the package with npm in your terminal:
npm install @makerdao/dai
Once it's installed, import the module into your project as shown below.
npm install @makerdao/dai-plugin-mcd
(Note the .default
at the end of the line when using require
.)
UMD
The code below opens a Vault, locks ETH into it, and draws out Dai.
Since this code sends transactions, it requires an account that can sign transactions. The simplest way to do this is to provide a privateKey
configuration option as shown below, but you can also connect to Metamask or other browser-based providers, or connect to hardware wallets.
Developers can build a variety of experiences using one or more components of the Maker Protocol. This repo contains guides and tutorials to help you understand various approaches to integrate with the Maker Protocol and our partners by interfacing with smart contracts, SDKs, APIs, and products.
All guides are organized in sections and by proficiency levels within each section.
In order to ensure that integration partners can get up and running quickly, relevant documentation for specific partner types have been compiled in a series of guides.
We welcome submissions of guides and tutorials that cover new types of integrations! Following these guidelines will help us maintain consistency,
Create a folder with one markdown file using the same name
Append a number if a guide needs to be split into multiple parts
Adapters and Auction contracts for each specific collateral type
Module Name: Collateral Module
Type/Category: DSS —> join.sol, clip.sol
Contract Sources:
The collateral module is deployed for every new ilk
(collateral type) added to Vat
. It contains all the adapters and auction contracts for one specific collateral type.
For other information related to the collateral module, read the following resources:
The Collateral Module has 3 core components consisting of the join
, and flip
contracts.
Join
- adapters that are used to deposit/withdraw unlocked collateral into the Vat
. Join contains three smart contracts:
GemJoin
ETHJoin
DaiJoin
.
Each of the join
contracts are specifically used for the given token type to be join
'ed to the vat
. Due to this fact, each join
contract has slightly different logic to account for the different types of tokens within the system.
Join
contracts help the MCD system operate?Join
- the purpose of join adapters is to retain the security of the system, allowing only trusted smart contracts to add/remove value to/from the Vat
. The location of collateral deposited/locked in Vaults is in the respective Join adapter.
When a user desires to enter the system and interact with the dss
contracts, they must use one of the join
contracts.
If there was a contract bug in a join
contract and a user was to call join
by accident, they can still retrieve their tokens back through the corresponding exit
call on the given join
contract.
There could potentially be a vat
upgrade that would require new join
contracts to be created
If a gem
contract were to go through a token upgrade or have the tokens frozen while a user's collateral was in the system, there could potentially be a scenario in which the users were unable to redeem their collateral after the freeze or upgrade was finished. This seems to be a small risk though because it would seem likely that the token going through this upgrade would want to work alongside the maker community to be sure this was not an issue.
Potential Phishing Attacks
As the MCD system evolves, we will see many more join
contracts, user interfaces, etc. This surfaces the potential for a user to have their funds stolen by a malicious join
contract which would send tokens to an external contract or wallet, instead of the vat
.
The CDP Manager was created as a way to enable Vaults to be treated more like assets that can be exchanged. Originally, the core contracts did not have the functionality to enable transferring Vault positions. The CDP Manager was created to wrap this functionality and enable transferring between users.
As mentioned above in the summary, the core contracts originally did not have the functionality to enable the transfer of Vault positions. Since then, the core contracts have also implemented a native transfer functionality called fork
which allows the transferring of a Vault to another address. However, there is a restriction, which is that the address owner that will be receiving the Vault needs to provide authorization that they do in fact want to receive it. This was created for the situation when a user is transferring the collateral that is locked as well as the debt generated. If you are simply moving collateral to another address, there is no issue but in the case that you are also transferring the debt generated, there is a chance of putting a perfectly safe Vault in a risky position. This makes the contract functionality a little more restrictive. Therefore, the CDP manager is a good option to keep a simple way of transferring Vaults and recognizing them via a numeric ID.
When open
is executed, a new urn
is created and a cdpId
is assigned to it for a specific owner
. If the user uses join
to add collateral to the urn
immediately after the transaction is mined, there is a chance that a reorganization of the chain occurs. This would result in the user losing the ownership of that cdpId
/urn
pair, therefore losing their collateral. However, this issue can only arise when avoiding the use of the via a as the user will open
the cdp
and join
collateral in the same transaction.
The DsrManager
provides an easy to use smart contract that allows service providers to deposit/withdraw dai into the DSR contract , and activate/deactivate the Dai Savings Rate to start earning savings on a pool of dai in a single function call. To understand the DsrManager, it is necessary to have an understanding of the first. The DSR is set by Maker Governance, and will typically be less than the base stability fee to remain sustainable. The purpose of DSR is to offer another incentive for holding Dai.
Mainnet:
Kovan:
Ropsten:
Reference the ds-proxy for more information
is a web-based tool that we use to annotate the codebase hosted on our Github org.
Check out the annotations of the Vat smart contract
Install the from the web store.
Follow instructions to setup a
Append to a URL.
MakerDAO
restricted group- everyone is allowed to read annotations but only approved members can contribute .
For example: Calling Global Settlement because an oracle is getting attacked, we must make sure the oracles attack won’t affect the Global Settlement price because End
reads the price off of the pip
(for reference, this occurs on of end.sol
).
Catastrophic Scenario: End.vat
/ End.vow
/ End.cat
/ - when set to attacker (address
: set to attacker-controlled address), can cause shutdown to fail. This is unfixable. For this scenario to occur, the malicious entity (governance or otherwise) would need to be auth
'ed on the End
.
vat
: a VatLike
that points the the system's contract
When the Cat
is upgraded, there are multiple references to it that must be updated at the same time (End
, Vat.rely
, Vow.rely
). It must also rely on the End
, the system's pause.proxy()
. Read more .
If this is omitted and the provider does not have an unlocked account, the maker
object will start in .
Number of blocks to wait after a transaction has been mined when calling confirm
. See for further explanation.
The MCD plugin defines several services for working with Multi-Collateral Dai. Review to see how to add the plugin.
: for working with Vaults.
: for reading parameters and live data (totals and prices) for collateral types.
: for working with the Dai Savings Rate.
: for reading system-wide parameters.
You can start in read-only mode and still with the ability to sign transactions later on.
The provides valuable information for developers interacting with Maker Protocol deployments. This includes deployments on the following networks:
Ethereum Mainnet ()
Ethereum Goerli Testnet ()
The Chainlog is also available as an onchain smart contract directory () that allows developers to programatically fetch Maker Protocol contract addresses directly in a smart contract. This is useful for integrators that want to future-proof implementations in case a Maker module is updated. You can
is dedicated to providing transparency to our community with respect to the results of our MCD Audits, our Bug Bounty Program, and Formal Verification. It is important to note that this release is the first version and we will continue to add information as it becomes available.
July 24, 2019
October 23, 2019
December 19, 2019
is a JavaScript library that makes it easy to build applications on top of MakerDAO's platform of smart contracts. You can use Maker's contracts to open Vaults (formerly known as CDPs), deposit collateral and generate Dai, trade tokens on decentralized exchanges, and more.
If you have questions regarding Dai.js that are not answered in the following pages, please reach out to us on the channel on or create a .
Multi-Collateral Dai support in Dai.js is implemented as a . This may change in the future. The MCD Plugin is also available as an package and its source code can be found on .
This library is also usable as a , which you can build with npm run build:frontend
.
This code uses to look up a vault that was created in the UI. Since this code is only reading data, not creating any transactions, it is not necessary to provide a private key or connect a wallet.
In the next section, learn more about how to configure the Maker instance with Maker.create
. Or jump to learning more about , , , and .
For larger examples of integrating this library, check out this and the .
Include all the sections present in this
Use for ensuring a consistent style in the documents. Rules are found in .markdownlint.json
root folder.
Use for Math notations.
Clipper Contract - see
Deposit the specified amount of Dai into the Dai Savings Rate (DSR) contract.
Withdraw the specified amount of Dai from the DSR contract.
Withdraw all Dai owned by the current account from the DSR contract.
Return the amount of Dai in the DSR contract owned by the specified address.
Get the total amount of Dai in the DSR contract for all users.
Get the current annual savings rate.
Returns the base rate applied to all collateral types, in addition to their individual risk premiums.
Returns the debt ceiling for the entire system.
Returns a boolean that is true if emergency shutdown has been triggered.
Methods that take numerical values as input can also take instances of token classes that the library provides. These are useful for managing precision, keeping track of units, and passing in wei values. Most methods that return numerical values return them wrapped in one of these classes. There are two types:
currency units, which represent an amount of a type of currency
price units, aka currency ratios, which represent an exchange rate between two currencies.
The classes that begin with USD
are price units; e.g. USD_ETH
represents the price of ETH in USD. Useful instance methods:
toNumber: return the raw JavaScript value. This may fail for very large numbers.
isEqual: compare the values and symbols of two different instances.
toString: show the value in human-readable form, e.g. "500 USD/ETH".
The methods below are all asynchronous.
Return an array describing the vaults owned by the specified address. Note that if the vaults were created in Oasis Borrow, the address of the proxy contract should be used.
A note on caching: When a vault instance is created, its data is pre-fetched from the blockchain, allowing the properties below to be read synchronously. This data is cached in the instance. To refresh this data, do the following:
The USD price of collateral at which the Vault becomes unsafe.
Whether the Vault is currently safe or not.
Deposit the specified amount of collateral.
Generate the specified amount of Dai.
Deposit some collateral and generate some Dai in a single transaction.
Pay back the specified amount of Dai.
Pay back all debt. This method ensures that dust amounts do not remain.
Withdraw the specified amount of collateral.
Pay back some debt and withdraw some collateral in a single transaction.
Pay back all debt, ensuring dust amounts do not remain, and withdraw a specified amount of collateral in a single transaction.
Look up the proxy contract owned by address
and transfer ownership of this vault to that proxy.
The DSProxyService
includes all the functionality necessary for interacting with both types of proxy contracts used in Maker products: profile proxies and forwarding proxies.
Params: None
Returns: promise (resolves to address or null
)
If the currentAccount
(according the Web3Service
) has already deployed a DSProxy, currentProxy()
returns its address. If not, it returns null
. It will update automatically in the event that the active account is changed. This function should be used to check whether a user has a proxy before attempting to build one.
Params: None
Returns: TransactionObject
build
will deploy a copy of DSProxy owned by the current account. This transaction will revert if the current account already owns a profile proxy. By default, build()
returns after the transaction is mined.
This convenience function will either return an existing proxy or create one.
Params: Address (optional)
Returns: promise (resolves to contract address)
getProxyAddress
will query the proxy registry for the profile proxy address associated with a given account. If no address is provided as a parameter, the function will return the address of the proxy owned by the currentAccount
.
Params: Address
Returns: promise (resolves to address)
getOwner
will query the proxy registry for the owner of a provided instance of DSProxy.
Params: Address of new owner, DSProxy address (optional)
Returns: TransactionObject
setOwner
can be used to give a profile proxy to a new owner. The address of the recipient account must be specified, but the DSProxy address will default to currentProxy
if the second parameter is excluded.
The transactionManager
service is used to track a transaction's status as it propagates through the blockchain.
Methods in Dai.js that start transactions are asynchronous, so they return promises. These promises can be passed as arguments to the transaction manager to set up callbacks when transactions change their status to pending
, mined
, confirmed
or error
.
Pass the promise to transactionManager.listen
with callbacks, as shown below.
There are functions such as lockEth()
which are composed of several internal transactions. These can be more accurately tracked by accessing tx.metadata
in the callback which contains both the contract
and the method
the internal transactions were created from.
A TransactionObject
also has a few methods to provide further details on the transaction:
hash
: transaction hash
fees()
: amount of ether spent on gas
timeStamp()
: timestamp of when transaction was mined
timeStampSubmitted()
: timestamp of when transaction was submitted to the network
Use the 'mcd:savings'
service to work with the Dai Savings Rate system. In the code, this is called .
All the methods below are asynchronous. join
, exit
, and exitAll
use a .
Return the amount of Dai in the DSR contract owned by the . Strictly speaking, this method returns the amount of Dai owned by the proxy contract for the current address. to work with the methods above.
Use the mcd:systemData
service to look up system-wide parameters. In the code, this is called .
for using Trezor with dai.js in a browser environment.
for using Ledger in a browser environment.
for working with the governance contracts.
for atomic trading on maker OTC (Oasis).
for interacting with the maker OTC contract (Oasis).
for interacting with the multi-collateral dai contracts.
for interacting with the single-collateral dai contracts.
Check out the .
toBigNumber: return the raw value as a .
If you would like to use these helper classes outside of Dai.js, check out .
The vault manager works with vaults that are owned by the contract, which is also used by Oasis Borrow. This intermediary contract allows the use of incrementing integer IDs for vaults, familiar to users of Single-Collateral Sai, as well as other conveniences.
In the code, this is called .
Get an existing vault by its numerical ID. Returns a .
Open a new vault with the specified . Will create a if one does not already exist. Returns a . Works with the .
Open a new vault, then lock and/or draw in a single transaction. Will create a if one does not already exist. Returns a .
In the code, these are called .
The amount of collateral tokens locked, as a .
The USD value of collateral locked, given the current price according to the price feed, as a .
The amount of Dai drawn, as a .
All of the methods below are asynchronous and work with the . Amount arguments should be , e.g.:
Transfer ownership of this vault to address
. Note that if the new owner plans to use this vault with Oasis Borrow, it should be transferred to their proxy with instead.
Forwarding proxies are simple contracts that aggregate function calls in the body of a single method. These are used in the and in order to allow users to execute multiple transactions atomically, which is both safer and more user-friendly than implementing several steps as discrete transactions.
Forwarding proxies are meant to be as simple as possible, so they lack some features that could be important if they are to be used as interfaces for more complex smart contract logic. This problem can be solved by using profile proxies (i.e. copies of ) to execute the functionality defined in the forwarding proxies.
The first time an account is used to interact with any Maker application, the user will be prompted to deploy a profile proxy. This copy of DSProxy can be used in any product, including dai.js, by way of a universal . Then, the calldata from any function in the forwarding proxy can be passed to DSProxy's execute()
method, which runs the provided code in the context of the profile proxy.
This makes it possible for users' token allowances to persist from one Maker application to another, and it allows users to mistakenly sent to the proxy's address. Many of the functions in DSProxyService
will only be relevant to power users
. All that is strictly required to automatically generate a function's calldata and find the correct profile proxy is the inclusion of { dsProxy: true }
in the options object for any transaction — provided the user has already deployed a profile proxy. If that's not certain, it may also be necessary to query the registry to determine if a user already owns a proxy, and to build
one if they do not.
Note that the confirmed
event will not fire unless transactionManager.confirm
is called. This async function waits a number of blocks (default 5) after the transaction has been mined to resolve. To change this globally, set the confirmedBlockCount
attribute in Maker . To change it for just one call, pass the number of blocks to wait as the second argument:
The event pipeline allows developers to easily create real-time applications by letting them listen for important state changes and lifecycle events.
An event name passed to any event emitter method can contain a wildcard (the *
character). A wildcard may appear as foo/*
, foo/bar/*
, or simply *
.
*
matches one sub-level.
e.g. price/*
will trigger on both price/USD_ETH
and price/MKR_USD
but not price/MKR_USD/foo
.
**
matches all sub-levels.
e.g. price/**
will trigger on price/USD_ETH
, price/MKR_USD
, and price/MKR_USD/foo
.
Event Object
Triggered events will receive the object shown on the right.
<event_type>
- the name of the event
<event_payload>
- the new state data sent with the event
<event_sequence_number>
- a sequentially increasing index
<latest_block_when_emitted>
- the current block at the time of the emit
Event Name
Payload
price/ETH_USD
{ price }
price/MKR_USD
{ price }
price/WETH_PETH
{ ratio }
Event Name
Payload
web3/INITIALIZED
{ provider: { type, url } }
web3/CONNECTED
{ api, network, node }
web3/AUTHENTICATED
{ account }
web3/DEAUTHENTICATED
{ }
web3/DISCONNECTED
{ }
Event Name
Payload
COLLATERAL
{ USD, ETH }
DEBT
{ dai }
This page applies only to Single-Collateral Dai.
Retrieve the ETH CDP Service through Maker.service('cdp'). The ETH CDP Service exposes risk parameter information for the Ether CDP type (in single-collateral Dai, this is the only CDP Type).
Params: none
Returns: promise (resolves to liquidation ratio)
getLiquidationRatio()
returns a decimal representation of the liquidation ratio, e.g. 1.5
Params: none
Returns: promise (resolves to liquidation penalty)
getLiquidationPenalty()
returns a decimal representation of the liquidation penalty, e.g. 0.13
Params: none
Returns: promise (resolves to yearly governance fee)
getAnnualGovernanceFee()
returns a decimal representation of the annual governance fee, e.g. 0.005.
Note: This is often referred to as the Stability Fee
, even though technically the Stability Fee
is the fee that is paid in Sai, and the Governance Fee
is the fee that is paid in MKR. But since fees are only paid in MKR in Single-Collateral Dai, and only paid in Dai in Multi-Collateral Dai, the fee in Single-Collateral Sai is often referred to as the Stability Fee
to be consistent with the term that will be used in Multi-Collateral Dai and to avoid unduly confusing regular users.
npm install @makerdao/dai-plugin-scd
The code below creates a CDP, locks ETH into it, and draws out Sai.
The services and objects below are used to work with Single-Collateral Sai.
Returns: promise (resolves to new CDP object once mined)
openCdp()
will create a new CDP, and then return the CDP object, which can be used to access other CDP functionality. The promise will resolve when the transaction is mined.
Returns: promise (resolves to CDP object)
getCdp(id)
creates a CDP object for an existing CDP. The CDP object can then be used to interact with your CDP.
💡Note that this page applies only to Single-Collateral Sai.
id
Returns: promise (resolves to the amount of outstanding debt)
Returns: promise (resolves to the value of the accrued governance fee in USD)
Note: this is often referred to as the Stability Fee
, even though technically the Stability Fee
is the fee that is paid in Dai, and the Governance Fee
is the fee that is paid in MKR. But since fees are only paid in MKR in Single-Collateral Dai, and only paid in Dai in Multi-Collateral Dai, the fee in Single-Collateral Dai is often referred to as the Stability Fee
to be consistent with the term that will be used in Multi-Collateral Dai and to avoid unduly confusing regular users.
Params: none
Returns: promise (resolves to the collateralization ratio)
cdp.getCollateralizationRatio()
returns the USD value of the collateral in the CDP divided by the USD value of the Dai debt for the CDP, e.g. 2.5
.
Params: none
Returns: promise (resolves to the liquidation price)
Returns: promise (resolves to collateral amount)
Params: none
Returns: promise (resolves to boolean)
cdp.isSafe()
returns true if the cdp is safe, that is, if the USD value of its collateral is greater than or equal to USD value of the its debt multiplied by the liquidation ratio.
Params: amount of Sai to wipe
Returns: promise (resolves to boolean)
cdp.enoughMkrToWipe(dai)
returns true if the current account owns enough MKR to wipe the specified amount of Sai from the CDP.
Params: amount to lock in the CDP, in units defined by the price service.
cdp.lockEth(eth)
abstracts the token conversions needed to lock collateral in a CDP. It first converts the ETH to WETH, then converts the WETH to PETH, then locks the PETH in the CDP.
Params: amount to draw (in Sai, as string)
cdp.drawSai(sai)
withdraws the specified amount of Sai as a loan against the collateral in the CDP. As such, it will fail if the CDP doesn't have enough PETH locked in it to remain at least 150% collateralized.
Params: amount to repay (in Sai, as string)
cdp.wipeSai(sai)
sends Sai back to the CDP in order to repay some (or all) of its outstanding debt.
Note: CDPs accumulate MKR governance debt over their lifetime. This must be paid when wiping sai debt, and thus MKR must be acquired before calling this method.
Params: amount of Peth collateral to free from the CDP, in units defined by the price service.
cdp.freePeth(peth)
withdraws the specified amount of PETH and returns it to the owner's address. As such, the contract will only allow you to free PETH that's locked in excess of 150% of the CDP's outstanding debt.
Params: Ethereum address (string)
cdp.give(address)
transfers ownership of the CDP from the current owner to the address you provide as an argument.
Params: none
cdp.shut()
wipes all remaining sai, frees all remaining collateral, and deletes the CDP. This will fail if the caller does not have enough DAI to wipe all the sai debt and enough MKR to pay for all the accrued stability fee
Params: none
cdp.bite()
will initiate the liquidation process of an undercollateralized CDP
The name of the collateral type as a string, e.g. "ETH-A".
The total amount of collateral locked in all vaults of this collateral type.
The total Dai debt drawn against all vaults of this collateral type.
The debt ceiling for this collateral type.
Vaults of this type become unsafe (subject to liquidation) when their ratio between USD value of collateral and Dai drawn is less than or equal to this amount.
The penalty added to the Dai amount to be raised at auction when a vault is liquidated, as a percentage. e.g. if the penalty is 13%, this value will be BigNumber(0.13)
.
When a vault instance is created, its data is pre-fetched from the blockchain, allowing the properties below to be read synchronously. This data is cached in the instance. To refresh this data, do the following:
To refresh the data for all collateral type instances at once:
A Python API for the Maker Smart Contracts
The Maker Protocol incentivizes external agents, called keepers, to automate certain operations around the Ethereum blockchain. In order to ease their development, an API around most of the Maker contracts has been created. It can be used not only by keepers, but may also be found useful by authors of some other, unrelated utilities aiming to interact with these contracts.
You only need to install this project directly if you want to build your own keepers, or if you want to play with this API library itself. If you just want to install one of reference keepers, go to one of the repositories linked above and start from there. Each of these keepers references some version of pymaker
via a Git submodule.
This project uses Python 3.6.6.
In order to clone the project and install required third-party packages please execute:
In order for the secp256k
Python dependency to compile properly, following packages will need to be installed:
(for Ubuntu 18.04 Server)
and set the LDFLAGS
environment variable before you run pip3 install -r requirements.txt
:
The current version provides APIs around:
ERC20Token
,
0x v2
APIs around the following functionality have not been implemented:
Global Settlement (End)
Governance (DSAuth, DSChief, DSGuard, DSSpell, Mom)
Contributions from the community are much appreciated!
Below you can find some code snippets demonstrating how the API can be used both for developing your own keepers and for creating some other utilities interacting with the Maker Protocol ecosystem contracts.
This snippet demonstrates how to transfer some SAI from our default address. The SAI token address is discovered by querying the Tub
, so all we need as a Tub
address:
This snippet demonstrates how to update a DSValue
with the ETH/USD rate pulled from CryptoCompare:
This snippet demonstrates how to fetch data from Tub
and Tap
contracts:
This snippet demonstrates how to create a CDP and draw Dai.
This snippet demonstrates how multiple token transfers can be executed asynchronously:
This snippet demonstrates how multiple token transfers can be executed in one Ethereum transaction. A TxManager
instance has to be deployed and owned by the caller.
Prerequisites:
In order to be able to run tests, please install development dependencies first by executing:
You can then run all tests with:
Get a token object through the getToken(tokenSymbol)
function on the tokenService.
The list of tokens that can be passed into getToken()
are: SAI, MKR, WETH, PETH, ETH.
This list can also be obtained with tokenService.getTokens()
. This function returns a string representation of the token symbol, e.g. 'SAI', which can also be passed into getToken
.
When the Multi-Collateral Dai plugin is in use, getToken('DAI')
will return a token object for Dai.
Most of the methods below can be called on any token object. deposit
and withdraw
are for WETH only, and join
and exit
are for PETH only.
Params:
tokenOwner
- address of token owner
spender
- address of token spender
Returns: promise (resolves to token allowance)
Params: none
Returns: promise (resolves balance of current account)
Params: address to check
Returns: promise (resolves balance of address)
Params: none
Returns: promise (resolves total supply of token)
Params:
spender - address of token spender
amount - amount of token to allow
approve
approves the spending address to spend up to amount
of msg.sender
's tokens.
Params: address of token spender
approveUnlimited
approves the spending address to spend the maximum amount of msg.sender
's tokens.
Params:
to - address to send to
amount - amount of token to send
transfer
transfers amount
of token to to
address.
Params:
from - address to send tokens from
to - address to send to
amount - amount of token to send
transferFrom()
transfers amount
of token from from
address to to
address. Transaction will fail if msg.sender
does not have allowance to transfer the amount of tokens from
the from address.
Params: amount of Eth to deposit
deposit
converts amount
of Eth to amount
of Weth.
Params: amount of Weth to withdraw
withdraw
converts amount
of Weth to amount
of Eth.
Params: amount of Weth to join
Params: amount of Peth to exit
Single-Collateral Dai support in Dai.js is implemented as a . The SCD plugin is also available as an package and its source code can be found on .
Once you have an instance of a CDP, you can use to read its state and perform actions.
After or , you can use the returned cdp
object to call functions on it.
This is the ID of the CDP object. You can pass this ID to .
Params: (optional)
cdp.getDebtValue()
returns the amount of debt that has been borrowed against the collateral in the CDP. By default it returns the amount of Dai as a , but can return the equivalent in USD if the first argument is Maker.USD
.
Params: (optional)
cdp.getGovernanceFee()
returns the value of the accrued governance fee. By default it returns the amount of MKR as a , but can return the equivalent in USD if the first argument is Maker.USD
.
cdp.getLiquidationPrice()
returns the price of Ether in USD that causes the CDP to become unsafe (able to be liquidated), all other factors constant. It returns a USD_ETH
.
Params: (optional)
cdp.getCollateralValue()
returns the value of the collateral in the CDP. By default it returns the amount of ETH as a , but can return the equivalent in PETH or USD depending on the first argument.
Returns: promise (resolves to once mined)
Note: this process is not atomic, so it's possible for some of the transactions to succeed but not all three. See for executing multiple transactions atomically.
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Use the 'mcd:cdpType'
service to look up parameters for different collateral types in the system. In the code, this is called .
This is a list of , initialized during Maker.create
.
Return the for the specified currency and/or .
The USD price of this collateral type's token, using recent price feed data, as a . (See "A note on caching" above).
The annual stability fee (risk premium) of the collateral type, not including the , as a BigNumber. e.g. if the rate is 5%, this value will be BigNumber(0.05)
.
Based on the , a set of reference Maker keepers is being developed. They all used to reside in this repository, but now each of them has an individual one: (SCD only), , (MCD only), (SCD only), .
In order for the Python requirements to install correctly on macOS, please install openssl
, libtool
, pkg-config
and automake
using :
Tub
, Tap
,Top
and Vox
(),
Vat
, Cat
, Vow
, Jug
, Flipper
, Flapper
, Flopper
()
SimpleMarket
, ExpiringMarket
and MatchingMarket
(),
TxManager
(),
DSGuard
(),
DSToken
(),
DSEthToken
(),
DSValue
(),
DSVault
(),
EtherDelta
(),
0x v1
(, ),
Dai Savings Rate (Pot)
()
6.2.5
(using npm, sudo npm install -g ganache-cli@6.2.5
)
This project uses for unit testing. Testing of Multi-collateral Dai is performed on a Dockerized local testchain included in tests\config
.
If you have questions regarding Pymaker, please reach out to us on the channel on .
allowance
returns a representing the token allowance.
balance
returns a representing the token balance of the current account
balanceOf
returns a representing the token balance of the supplied account.
totalSupply
returns a representing the total token supply
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
join
converts amount
of Weth to Peth, at the .
Returns: promise (resolves to once mined)
withdraw
converts amount
of Peth to Weth, at the .
A big part of the Maker Protocol is the incentivization of external agents, called Keepers (which can be human but are typically automated bots). Market Maker Keepers work by creating a series of orders in so-called bands (defined later), which are configured with a JSON file containing parameters like spreads, maximum engagement, etc. In short, the market-maker-keeper
repository is a set of Keepers that facilitate market making on exchanges. For example, trading Dai motivated by the expected long-term convergence toward the indicated Target Price
. This guide is dedicated to showing you how to create your very own Market Maker Keeper Bot as well as educate the community and help both users and developers understand the value of this incredible software. We are proud to say that all of the code needed to get a Market Maker Keeper bot up and running is open-sourced.
Visit the guide below if you are interested in getting a Market Maker Keeper Bot Set up up and running!
The Maker Protocol, which powers Multi Collateral Dai (MCD), is a smart contract based system that backs and stabilizes the value of Dai through a dynamic combination of Vaults (formerly known as CDPs), autonomous feedback mechanisms, and incentivized external actors. To keep the system in a stable financial state, it is important to prevent both debt and surplus from building up beyond certain limits. This is where Auctions and Auction Keepers come in. The system has been designed so that there are three types of Auctions in the system: Surplus Auctions, Debt Auctions, and Collateral Auctions. Each auction is triggered as a result of specific circumstances.
Auction Keepers are external actors that are incentivized by profit opportunities to contribute to decentralized systems. In the context of the Maker Protocol, these external agents are incentivized to automate certain operations around the Ethereum blockchain. This includes:
Seeking out opportunities and starting new auctions
Detect auctions started by other participants
Bid on auctions by converting token prices into bids
More specifically, Keepers participate as bidders in the Debt and Collateral Auctions when Vaults are liquidated and auction-keeper enables the automatic interaction with these MCD auctions. This process is automated by specifying bidding models that define the decision making process, such as what situations to bid in, how often to bid, how high to bid etc. Note that bidding models are created based on individually determined strategies.
For all interested in setting up their own Auction Keeper Bot, please visit the guide below.
Introducing the Shutdown Mechanism of the Maker Protocol
The Maker Protocol, which powers Multi Collateral Dai, is a smart-contract system that backs and stabilizes the value of Dai through a dynamic combination of Vaults, autonomous system of smart contracts, and appropriately incentivized external actors. The Dai Target Price is 1 US Dollar, translating to a 1:1 US Dollar soft peg. Shutdown is a process that can be used as a last resort to directly enforce the Target Price to holders of Dai and Vaults, and protect the Maker Protocol against attacks on its infrastructure.
Shutdown stops and gracefully settles the Maker Protocol while ensuring that all users, both Dai holders and Vault holders, receive the net value of assets they are entitled to.
In short, it allows Dai holders to directly redeem Dai for collateral after an Emergency Shutdown processing period.
The process of initiating Emergency Shutdown is decentralized and controlled by MKR voters, who can trigger it by depositing MKR into the Emergency Shutdown Module.
Emergency Shutdown is triggered in the case of serious emergencies, such as long-term market irrationality, hacks, or security breaches.
Emergency Shutdown stops and gracefully settles the Maker Protocol while ensuring that all users, both Dai holders and Vault users, receive the net value of assets they are entitled to.
Vault owners can retrieve excess collateral from their Vaults immediately after initialization of Emergency Shutdown. They can do this via Vault frontends, such as Oasis Borrow, that have Emergency Shutdown support implemented, as well as via command-line tools.
Dai holders can, after a waiting period determined by MKR voters, swap their Dai for a relative share of all types of collateral in the system. The Maker Foundation will initially offer a web page for this purpose.
Dai holders always receive the same relative amount of collateral from the system, whether they are among the first or last people to process their claims.
Dai holders may also sell their Dai to Keepers (if available) to avoid self-management of the different collateral types in the system.
The Maker Protocol's Rate Accumulation Mechanism
Module Name: Rates Module
Type/Category: Rates
Contract Sources:
A fundamental feature of the MCD system is to accumulate stability fees on Vault debt balances, as well as interest on Dai Savings Rate (DSR) deposits.
The mechanism used to perform these accumulation functions is subject to an important constraint: accumulation must be a constant-time operation with respect to the number of Vaults and the number of DSR deposits. Otherwise, accumulation events would be very gas-inefficient (and might even exceed block gas limits).
For both stability fees and the DSR, the solution is similar: store and update a global "cumulative rate" value (per-collateral for stability fees), which can then be multiplied by a normalized debt or deposit amount to give the total debt or deposit amount when needed.
This can be described more concretely with mathematical notation:
Discretize time in 1-second intervals, starting from t_0;
Let the (per-second) stability fee at time t have value F_i (this generally takes the form 1+x, where x is small)
Let the initial value of the cumulative rate be denoted by R_0
Let a Vault be created at time t_0 with debt D_0 drawn immediately; the normalized debt A (which the system stores on a per-Vault basis) is calculated as D_0/R_0
Then the cumulative rate R at time T is given by:
And the total debt of the Vault at time t would be:
In the actual system, R is not necessarily updated with every block, and thus actual R values within the system may not have the exact value that they should in theory. The difference in practice, however, should be minor, given a sufficiently large and active ecosystem.
Detailed explanations of the two accumulation mechanisms may be found below.
The Vat stores, for each collateral type, an Ilk
struct that contains the cumulative rate (rate
) and the total normalized debt associated with that collateral type (Art
). The Jug stores the per-second rate for each collateral type as a combination of a base
value that applies to all collateral types, and a duty
value per collateral. The per-second rate for a given collateral type is the sum of its particular duty
and the global base
.
Calling Jug.drip(bytes32 ilk)
computes an update to the ilk's rate
based on duty
, base
, and the time since drip
was last called for the given ilk (rho
). Then the Jug invokes Vat.fold(bytes32 ilk, address vow, int rate_change)
which:
adds rate_change
to rate
for the specified ilk
increases the system's total debt (i.e. issued Dai) by Art*rate_change
.
Each individual Vault (represented by an Urn
struct in the Vat) stores a "normalized debt" parameter called art
. Any time it is needed by the code, the Vault's total debt, including stability fees, can be calculated as art*rate
(where rate
corresponds to that of the appropriate collateral type). Thus an update to Ilk.rate
via Jug.drip(bytes32 ilk)
effectively updates the debt for all Vaults collateralized with ilk
tokens.
Suppose at time 0, a Vault is opened and 20 Dai is drawn from it. Assume that rate
is 1; this implies that the stored art
in the Vault's Urn
is also 20. Let the base
and duty
be set such that after 12 years, art*rate
= 30 (this corresponds to an annual stability of roughly 3.4366%). Equivalently, rate
= 1.5 after 12 years. Assuming that base + duty
does not change, the growth of the effective debt can be graphed as follows:
Now suppose that at 12 years, an additional 10 Dai is drawn. The debt vs time graph would change to look like:
What art
would be stored in the Vat to reflect this change? (hint: not 30!) Recall that art
is defined from the requirement that art * rate
= Vault debt. Since the Vault's debt is known to be 40 and rate
is known to be 1.5, we can solve for art
: 40/1.5 ~ 26.67.
The art
can be thought of as "debt at time 0", or "the amount of Dai that if drawn at time zero would result in the present total debt". The graph below demonstrates this visually; the length of the green bar extending upwards from t = 0 is the post-draw art
value.
Some consequences of the mechanism that are good to keep in mind:
There is no stored history of draws or wipes of Vault debt
There is no stored history of stability fee changes, only the cumulative effective rate
The rate
value for each collateral perpetually increases (unless the fee becomes negative at some point)
drip
?The system relies on market participants to call drip
rather than, say, automatically calling it upon Vault manipulations. The following entities are motivated to call drip
:
Keepers seeking to liquidate Vaults (since the accumulation of stability fees can push a Vault's collateralization ratio into unsafe territory, allowing Keepers to liquidate it and profit in the resulting collateral auction)
Vault owners wishing to draw Dai (if they don't call drip
prior to drawing from their Vault, they will be charged fees on the drawn Dai going back to the last time drip
was called—unless no one calls drip
before they repay their Vault, see below)
MKR holders (they have a vested interest in seeing the system work well, and the collection of surplus in particular is critical to the ebb and flow of MKR in existence)
Despite the variety of incentivized actors, calls to drip
are likely to be intermittent due to gas costs and tragedy of the commons until a certain scale can be achieved. Thus the value of the rate
parameter for a given collateral type may display the following time behavior:
Debt drawn and wiped between rate
updates (i.e. between drip
calls) would have no stability fees assessed on it. Also, depending on the timing of updates to the stability fee, there may be small discrepancies between the actual value of rate
and its ideal value (the value if drip
were called in every block). To demonstrate this, consider the following:
at t = 0, assume the following values:
in a block with t = 28, drip
is called—now:
in a block with t = 56, the fee is updated to a new, different value:
in a block with t = 70, drip
is called again; the actual value of rate
that obtains is:
however, the "ideal" rate
(if drip
were called at the start of every block) would be:
Depending on whether f > g or g > f, the net value of fees accrued will be either too small or too large. It is assumed that drip
calls will be frequent enough such inaccuracies will be minor, at least after an initial growth period. Governance can mitigate this behavior by calling drip
immediately prior to fee changes. The code in fact enforces that drip
must be called prior to a duty
update, but does not enforce a similar restriction for base
(due to the inefficiency of iterating over all collateral types).
The per-second (or "instantaneous") savings rate is stored in the dsr
parameter (analogous to base+duty
in the stability fee case). The chi
parameter as a function of time is thus (in the ideal case of drip
being called every block) given by:
where chi_0 is simply chi(t_0).
Suppose a user joins N Dai into the Pot at time t_0. Then, their internal savings Dai balance is set to:
The total Dai the user can withdraw from the Pot at time t is:
Thus we see that updates to chi
effectively increase all Pot balances at once, without having to iterate over all of them.
After updating chi
, Pot.drip
then calls Vat.suck
with arguments such that the additional Dai created from this savings accumulation is credited to the Pot contract while the Vow's sin
(unbacked debt) is increased by the same amount (the global debt and unbacked debt tallies are increased as well). To accomplish this efficiently, the Pot keeps track of a the total sum of all individual pie[usr]
values in a variable called Pie
.
The following points are useful to keep in mind when reasoning about savings accumulation (all have analogs in the fee accumulation mechanism):
if drip
is called only infrequently, the instantaneously value of chi
may differ from the ideal
the code requires that drip
be called prior to dsr
changes, which eliminates deviations of chi
from its ideal value due to such changes not coinciding with drip
calls
chi
is a monotonically increasing value unless the effective savings rate becomes negative (dsr
< ONE
)
There is no stored record of depositing or withdrawing Dai from the Pot
There is no stored record of changes to the dsr
drip
?The following economic actors are incentivized (or forced) to call Pot.drip
:
any user withdrawing Dai from the Pot (otherwise they lose money!)
any user putting Dai into the Pot—this is not economically rational, but is instead forced by smart contract logic that requires drip
to be called in the same block as new Dai is added to the Pot (otherwise, an economic exploit that drains system surplus is possible)
any actor with a motive to increase the system debt, for example a Keeper hoping to trigger flop (debt) auctions
Let's see how to set a rate value in practice. Suppose it is desired to set the DSR to 0.5% annually. Assume the real rate will track the ideal rate. Then, we need a per-second rate value r such that (denoting the number of seconds in a year by N):
An arbitrary precision calculator can be used to take the N-th root of the right-hand side (with N = 31536000 = 3652460*60), to obtain:
The dsr
parameter in the Pot implementation is interpreted as a ray
, i.e. a 27 decimal digit fixed-point number. Thus we multiply by 10^27 and drop anything after the decimal point:
The dsr
could then be set to 0.5% annually by calling:
Pot.file("dsr", 1000000000158153903837946258)
Retrieve the PriceService through Maker.service('price')
. The PriceService exposes the collateral and governance tokens' price information that is reported by the oracles in the Maker system.
Set the current USD price of ETH. This requires the necessary permissions and will only be useful in a testing environment.
Set the current USD price of the governance token MKR. This requires the necessary permissions and will only be useful in a testing environment.
Returns the current WETH to PETH ratio.
The Multi-Collateral Dai (MCD) system within the MakerDAO Protocol is a smart contract platform on Ethereum that backs and stabilizes the value of our stablecoin, Dai. It does this through a dynamic system of Vaults, autonomous feedback mechanisms, and appropriately incentivized external actors.
In this document, we explain the auction mechanisms within the system, as well as particular types of external actors, called Keepers, that bid on the Auctions.
The system protects against debt creation by overcollateralization. Under ideal circumstances and with the right risk parameters, the debt for an individual Vault can be covered by the collateral deposited in that Vault. If the price of that collateral drops to the point where a Vault no longer sustains the required collateralization ratio, then the system automatically liquidates the Vault and sells off the collateral until the outstanding debt in the Vault (and a liquidation penalty), is covered. This is done through a Collateral Auction.
Further, if, for example, the collateral price drops sharply or no one wants to buy the collateral, there may be debt in the liquidated Vault that cannot be repaid through a collateral auction and must be addressed by the system. The first course of action is to cover this debt using surplus from stability fees, if there is any surplus to cover it. If there is not, then the system initiates a Debt Auction, whereby the winning bidder pays Dai to cover the outstanding debt and in return receives an amount of newly minted MKR, increasing the amount of MKR in circulation.
Surplus Auction: The winning bidder pays MKR for surplus Dai from stability fees. The MKR received is burnt, thereby reducing the amount of MKR in circulation.
Collateral Auction: The winning bidder pays Dai for collateral from a liquidated Vault. The Dai received is used to cover the outstanding debt in the liquidated Vault
Debt Auction: The winning bidder pays Dai for MKR to cover outstanding debt that Collateral Auctions haven’t been able to cover. MKR is minted by the system, thereby increasing the amount of MKR in circulation.
The actors that bid on these Auctions are called Keepers.
With all information published on the Ethereum blockchain, anyone can access or monitor price feeds and data on individual Vaults, thereby determining whether certain Vaults are in breach of the Liquidation Ratio. The system incentivizes these market participants (which can be human or automated bot), known as “keepers,” to monitor the MCD System and trigger liquidation when the Liquidation Ratio is breached.
In the context of Multi-Collateral Dai, Keepers may participate in auctions as a result of liquidation events and thereby acquire collateral at attractive prices. Keepers can also perform other functions, including trading Dai motivated by the expected long-term convergence toward the Target Price.
We will now go into more detail about how the Auctions work.
Various considerations were taken into account when designing the auction mechanisms. For example, from a systems point of view, it’s best to complete the auction as soon as possible to keep the system in a steady state, so the auction mechanism incentivizes early bidders. Another consideration is that the Auctions are executed on-chain, minimizing the number of required transactions and reducing associated fees.
Risk parameters. In general, the following parameters are used across all of the auction types:
beg
: Minimum bid increase (for example, 3%).
ttl
: Bid duration (for example, 6 hours). The auction ends if no new bid is placed during this time.
tau
: Auction duration (for example, 24 hours). The auction ends after this period under all circumstances.
The values of the risk parameters are determined by Maker Governance voters (MKR holders) per auction type. Note that there are different Collateral Auction risk parameters for each type of collateral used in the system.
Auction and bid information. The following information is always available during an active auction:
lot
: Amount of asset that is up for auction/sale.
bid
: Current highest bid.
guy
: Highest bidder.
tic
: Bid expiry date/time (empty if zero bids).
end
: Auction expiry date/time.
During an auction, bid amounts will increase by a percentage with each new bid. This is the beg
at work. For example, the beg
could be set to 3%, meaning if the current bidder has placed a bid of 100 Dai, then the next bid must be at least 103 Dai. Overall, the purpose of the bid increment system is to incentivize early bidding and make the auction process move quickly.
Bidders send DAI or MKR tokens from their addresses to the system/specific auction. If one bid is beat by another, the losing bid is refunded back to that bidder’s address. It’s important to note, however, that once a bid is submitted, there is no way to cancel it. The only possible way to have that bid returned is if it is outbid.
Now, let’s review the mechanisms of the three different auction types.
Summary: A Surplus Auction is used to auction off a fixed amount of surplus Dai in the system in exchange for MKR. This surplus Dai will generally come from accumulated stability fees. In this auction, bidders compete with increasing bids of MKR. Once the auction has ended, the auctioned Dai is sent to the winning bidder, and the system burns the MKR received from the winning bidder.
High-level Mechanism Process:
Maker Governance voters determine the amount of surplus allowed in the system at any one time. A Surplus auction is triggered when the system has a Dai surplus over the pre-determined amount as set by MKR governance.
To determine whether the system has a net surplus, accrued stability fees and debt in the system must be added together. Any user can do this by sending the heal
transaction to the system contract called Vow.
Provided there is a net surplus, the Surplus Auction is triggered when any user sends the flap
transaction to the Vow contract.
When the auction begins, a fixed amount (lot
) of Dai is put up for sale. Bidders then bid with MKR in increments greater than the minimum bid increase amount. The auction officially ends when the bid duration ends (ttl
) without another bid OR when the auction duration (tau
) has been reached. Once the auction ends, the MKR received for the surplus Dai is then sent to be burnt, thereby contracting the system’s MKR supply.
Summary: Collateral Auctions serve as a means to recover debt in liquidated Vaults. Those Vaults are being liquidated because the value of the Vault collateral has fallen below a certain limit determined by the Maker Governance voters.
High-level Mechanism Process:
For each type of collateral, MKR holders approve a specific risk parameter called the liquidation ratio. This ratio determines the amount of overcollaterization a Vault requires to avoid liquidation. For example, if the liquidation ratio is 150%, then the value of the collateral must always be one and a half times the value of the Dai generated. If the value of the collateral falls below the liquidation ratio, then the Vault becomes unsafe and is liquidated by the system. The system then takes over the collateral and auctions it off to cover both the debt in the Vault and an applied liquidation penalty.
The Collateral Auction is triggered when a Vault is liquidated.
Any user can liquidate a Vault that is unsafe by sending the bite transaction identifying the Vault. This will launch a collateral auction.
If the amount of collateral in the Vault being “bitten” is less than the lot size for the auction, then there will be one auction for all collateral in the Vault.
If the amount of collateral in the Vault being “bitten” is larger than the lot size for the auction, then an auction will launch with the full lot size of collateral, and the Vault can be “bitten” again to launch another auction until all collateral in the Vault is up for bidding in Collateral Auctions.
An important aspect of a Collateral Auction is that the auction expiration and bid expiration parameters are dependent on the specific type of collateral, where more liquid collateral types have shorter expiration times and vice-versa.
Once the auction begins, the first bidder can bid any amount of Dai to aquire the collateral amount (lot
). Other bidders can raise that bid offering more Dai for the same collateral amount (lot
) until there is a bid that covers the outstanding debt. If and when there is a bid that covers the outstanding debt, the auction will turn into a reverse auction, where a bidder bids on accepting smaller parts of the collateral for the fixed amount of Dai that covers the outstanding debt. The auction ends when the bid duration (ttl
) has passed OR when the auction duration (tau
) has been reached. Again, this process is designed to encourage early bidding. Once the auction is over, the system sends the collateral to the winning bidder’s address.
Summary: Debt Auctions are used to recapitalize the system by auctioning off MKR for a fixed amount of Dai. In this process, bidders compete with their willingness to accept decreasing amounts of MKR for the fixed Dai they will have to pay.
High-level Mechanism Process:
Debt Auctions are triggered when the system has Dai debt that has passed the specified debt limit.
Maker Governance voters determine the debt limit. The Debt auction is triggered when the system has a debt in Dai below that limit.
In order to determine whether the system has a net debt, the accrued stability fees and debt in the system must be added together. Any user can do this by sending the heal
transaction to the system contract named Vow.
Provided there is a sufficiently sized net debt, the debt auction is triggered when any user sends the flop
transaction to the Vow contract
This is a reverse auction, where Keepers bid on how little MKR they are willing to accept for the fixed Dai amount (lot
) they have to pay at auction settlement. The auction ends when the bid duration (ttl
) has passed OR when the auction duration (tau
) has been reached. Once the auction is over, the Dai, paid into the system by bidders in exchange for newly minted MKR, reduces the original debt balance in the system.
Walkthrough how to set up your own Auction Keeper
Level: Intermediate
Estimated Time: 60 minutes
Audience: Developers
The Maker Protocol, which powers Multi Collateral Dai (MCD), is a smart contract based system that backs and stabilizes the value of Dai through a dynamic combination of Vaults (formerly known as CDPs), autonomous feedback mechanisms, and incentivized external actors. To keep the system in a stable financial state, it is important to prevent both debt and surplus from building up beyond certain limits. This is where Auctions and Auction Keepers come in. The system has been designed so that there are three types of Auctions in the system: Surplus Auctions, Debt Auctions, and Collateral Auctions. Each auction is triggered as a result of specific circumstances.
Auction Keepers are external actors that are incentivized by profit opportunities to contribute to decentralized systems. In the context of the Maker Protocol, these external agents are incentivized to automate certain operations around the Ethereum blockchain. This includes:
Seeking out opportunities and starting new auctions
Detect auctions started by other participants
Bid on auctions by converting token prices into bids
More specifically, Keepers participate as bidders in the Debt and Collateral Auctions when Vaults are liquidated and auction-keeper enables the automatic interaction with these MCD auctions. This process is automated by specifying bidding models that define the decision making process, such as what situations to bid in, how often to bid, how high to bid etc. Note that bidding models are created based on individually determined strategies.
This guide's purpose is to provide a walkthrough of how to use auction-keeper
and interact with a Kovan deployment of the Multi Collateral Dai (MCD) smart contracts. More specifically, the guide will showcase how to set up and run an Auction Keeper bot for yourself. After going through this guide, you will achieve the following:
Learn about Auction Keepers and how they interact with the Maker Protocol
Understand bidding models
Get your own auction keeper bot running on the Kovan testnet
This guide will show how to use the auction-keeper to interact with the Kovan deployment of the MCD smart contracts. More specifically, the guide will showcase how to go through the following stages of setting up and running an Auction Keeper bot:
Introduction
Bidding Models
Starting and stopping bidding models
Communicating with bidding models
Setting up the Keeper Bot (Flip Auction Keeper)
Prerequisites
Installation
Running your Keeper Bot (Usage)
Keeper Limitations
Accounting
Getting MCD K-DAI
Getting MCD K-MKR
Getting MCD Collateral Tokens
Testing
Support
We are proud to say that since the Maker Protocol is an open-source platform, all of the code we have created to run the Keeper bot is free and accessible to all.
Auction Keepers participate in auctions as a result of liquidation events and thereby acquire collateral at attractive prices. An auction-keeper
can participate in three different types of auctions:
Auction Keepers have the unique ability to plug in external bidding models, which communicate information to the Keeper on when and how high to bid (these types of Keepers can be left safely running in the background). Shortly after an Auction Keeper notices or starts a new auction, it will spawn a new instance of a bidding model and act according to its specified instructions. Bidding models will be automatically terminated by the Auction Keeper the moment the auction expires.
Note:
Auction Keepers will automatically call deal
(claiming a winning bid / settling a completed auction) if the Keeper's address won the auction.
As mentioned above, Auction Keepers directly interact with Flipper
, Flapper
and Flopper
auction contracts deployed to the Ethereum mainnet. All decisions which involve pricing details are delegated to the bidding models. The Bidding models are simply executable strategies, external to the main auction-keeper
process. This means that the bidding models themselves do not have to know anything about the Ethereum blockchain and its smart contracts, as they can be implemented in basically any programming language. However, they do need to have the ability to read and write JSON documents, as this is how they communicate/exchange with auction-keeper
. It's important to note that as a developer running an Auction Keeper, it is required that you have basic knowledge on how to properly start and configure the auction-keeper. For example, providing startup parameters as keystore / password are required to setup and run a Keeper. Additionally, you should be familiar with the MCD system, as the model will receive auction details from auction-keeper in the form of a JSON message containing keys such as lot, beg, guy, etc.
Simple Bidding Model Example:
A simple bidding model could be a shell script which echoes a fixed price (further details below).
The main purpose of Auction Keepers are:
To discover new opportunities and start new auctions.
To constantly monitor all ongoing auctions.
To detect auctions started by other participants.
To Bid on auctions by converting token prices into bids.
To ensure that instances of bidding model are running for each auction type as well as making sure the instances match the current status of their auctions. This ensure that Keepers are bidding according to decisions outlined by the bidding model.
The auction discovery and monitoring mechanisms work by operating as a loop, which initiates on every new block and enumerates all auctions from 1
to kicks
. When this occurs, even when the bidding model decides to send a bid, it will not be processed by the Keeper until the next iteration of that loop. It's important to note that the auction-keeper
not only monitors existing auctions and discovers new ones, but it also identifies and takes opportunities to create new auctions.
Auction Keeper maintains a collection of child processes, as each bidding model is its own dedicated process. New processes (new bidding model instances) are spawned by executing a command according to the --model
command-line parameter. These processes are automatically terminated (via SIGKILL
) by the keeper shortly after their associated auction expires. Whenever the bidding model process dies, it gets automatically re-spawned by the Keeper.
Example:
Auction Keepers communicate with bidding models via their standard input/standard output. Once the process has started and every time the auction state changes, the Keeper sends a one-line JSON document to the standard input of the bidding model.
A sample JSON message sent from the keeper to the model looks like the:
id
- auction identifier.
flipper
- Ethereum address of the Flipper
contract (only for flip
auctions).
flapper
- Ethereum address of the Flapper
contract (only for flap
auctions).
flopper
- Ethereum address of the Flopper
contract (only for flop
auctions).
bid
- current highest bid (will go up for flip
and flap
auctions).
lot
- amount being currently auctioned (will go down for flip
and flop
auctions).
tab
- bid value (not to be confused with the bid price) which will cause the auction to enter the dent
phase (only for flip
auctions).
beg
- minimum price increment (1.05
means minimum 5% price increment).
guy
- Ethereum address of the current highest bidder.
era
- current time (in seconds since the UNIX epoch).
tic
- time when the current bid will expire (None
if no bids yet).
end
- time when the entire auction will expire (end is set to 0
is the auction is no longer live).
price
- current price being tendered (can be None
if price is infinity).
Bidding models should never make an assumption that messages will be sent only when auction state changes. It is perfectly fine for the auction-keeper
to periodically send the same message(s) to bidding models.
At the same time, the auction-keeper
reads one-line messages from the standard output of the bidding model process and tries to parse them as JSON documents. It will then extract the two following fields from that document:
price
- the maximum (for flip
and flop
auctions) or the minimum (for flap
auctions) price the model is willing to bid.
gasPrice
(optional) - gas price in Wei to use when sending a bid.
An example of a message sent from the Bidding Model to the Auction Keeper may look like:
In the case of when Auction Keepers and Bidding Models communicate in terms of prices, it is the MKR/DAI price (for flap
and flop
auctions) or the collateral price expressed in DAI for flip
auctions (for example, OMG/DAI).
Any messages written by a Bidding Model to stderr (standard error) will be passed through by the Auction Keeper to its logs. This is the most convenient way of implementing logging from Bidding Models.
Git
Clone the auction-keeper
repository:
Switch into the auction-keeper
directory:
Install required third-party packages:
Set up the virtual env and activate it:
5. Install requirements:
Needing to upgrade pip version to 19.2.2:
Fix by running pip install --upgrade pip
.
To change to your chosen version of the kovan release, copy/paste your preferred contract addresses in kovan-addresses.json
in lib/pymaker/config/kovan-addresses.json
The stdout (standard output) provides a price for the collateral (for flip
auctions) or MKR (for flap
and flop
auctions). The sleep
locks the price in place for a minute, after which the keeper will restart the price model and read a new price (consider this your price update interval).
The simplest possible bidding model you can set up is when you use a fixed price for each auction. For example:
Once you have created your bidding model, save it as model-eth.sh
(or whatever name you feel seems appropriate).
Collateral Auctions will be the most common type of auction that the community will want to create and operate Auction keepers for. This is due to the fact that Collateral auctions will occur much more frequently than Flap and Flop auctions.
Example (Flip Auction Keeper):
This example/process assumes that the user has an already existing shell script that manages their environment and connects to the Ethereum blockchain and that you have some Dai and Kovan ETH in your wallet. If you don't have any balance, check the section below on how to get some.
An example on how to set up your environment: as my_environment.sh
SERVER_ETH_RPC_HOST
- Should not be an infura node, as it doesn't provide all the functionality that the python script needs
ACCOUNT_KEY
- Should have the absolute path to the keystore and password file. Define the path as shown above, as the python script will parse through both the keystore and password files.
Once finalized, you should save your script to run your Auction Keeper as flip-eth-a.sh
(or something similar to identify that this Auction Keeper is for a Flip Auction). In addition, make sure to verify the above copy+pasted script doesn't create extra spaces or characters on pasting+saving in your editor. You will notice an error when running it later below otherwise.
Important Note about Running Auction Keepers on the Ethereum Mainnet!
If you get to the point where the auction keeper bot is not accepting mainnet as a valid argument, this is because there is no network
parameter. To fix this, just omit that parameter.
Other Notes:
All Collateral types (ilk
's) combine the name of the token and a letter corresponding to a set of risk parameters. For example, as you can see above, the example uses ETH-A. Note that ETH-A and ETH-B are two different collateral types for the same underlying token (WETH) but have different risk parameters.
For the MCD addresses, we simply pass --network mainnet|kovan
in and it will load the required JSON files bundled within auction-keeper (or pymaker).
Confirm that both your bidding model (model-eth.sh) and your script (flip-eth-a.sh) to run your Auction Keeper are saved.
The next step is to chmod +x
both of them.
Lastly, run flip-eth-a.sh model-eth.sh
to pass your bidding model into your Auction Keeper script.
Example of a working keeper:
After running the ./flip-eth-a.sh model-eth.sh
command you will see an output like this:
Now the keeper is actively listening for any action. If it sees an undercollateralized position, then it will try to bid for it.
To participate in all auctions, a separate keeper must be configured for flip
of each collateral type, as well as one for flap
and another for flop
.
--type
- the type of auction the keeper is used for. In this particular scenario, it will be set to flip
.
--ilk
- the type of collateral.
--addresses
- .json of all of the addresses of the MCD contracts as well as the collateral types allowed/used in the system.
--vat-dai-target
- the amount of DAI which the keeper will attempt to maintain in the Vat, to use for bidding. It will rebalance it upon keeper startup and upon deal
ing an auction.
--model
- the bidding model that will be used for bidding.
--from-block
to the block where the first urn was created to instruct the keeper to use logs published by the vat contract to bulid a list of urns, and then check the status of each urn.
Call bin/auction-keeper --help
for a complete list of arguments.
If an auction starts before the auction Keeper has started, the Keeper will not participate in the auction until the next block has been mined.
Keepers do not explicitly handle global settlement (End
). If global settlement occurs while a winning bid is outstanding, the Keeper will not request a yank
to refund the bid. The workaround is to call yank
directly using seth
.
There are some Keeper functions that incur gas fees regardless of whether a bid is submitted. This includes, but is not limited to, the following actions:
Submitting approvals.
Adjusting the balance of surplus to debt.
Biting a CDP or starting a flap or flop auction, even if insufficient funds exist to participate in the auction.
The Keeper will not check model prices until an auction officially exists. As such, it will kick
, flap
, or flop
in response to opportunities regardless of whether or not your DAI or MKR balance is sufficient to participate. This imposes a gas fee that must be paid.
After procuring more DAI, the Keeper must be restarted to add it to the Vat
.
The Auction contracts exclusively interact with DAI (for all auctions types) and collateral (for flip
auctions) in the Vat
. More explicitly speaking:
The DAI that is used to bid on auctions is withdrawn from the Vat
.
The Collateral and surplus DAI won at auction end is placed in the Vat
.
By default, all the DAI and collateral within your eth-from
account is exit
'ed from the Vat and added to your account token balance when the Keeper is shut down. Note that this feature may be disabled using the keep-dai-in-vat-on-exit
and keep-gem-in-vat-on-exit
switches, respectively. The use of an eth-from
account with an open CDP is discouraged, as debt will hinder the auction contracts' ability to access your DAI, and the auction-keeper
's ability to exit
DAI from the Vat
.
When running multiple Auction Keepers using the same account, the balance of DAI in the Vat
will be shared across all of the Keepers. If using this feature, you should set --vat-dai-target
to the same value for each Keeper, as well as sufficiently high in order to cover total desired exposure.
Note:
MKR used to bid on flap
auctions is directly withdrawn from your token balance. The MKR won at flop
auctions is directly deposited to your token balance.
Contract address: 0xb64964e9c0b658aa7b448cdbddfcdccab26cc584
Log into your MetaMask account from the browser extension. Add or confirm that the custom MCD K-DAI token is added to your list of tokens.
This done by selecting "Add Token" and then by adding in the details under the "Custom token" option.
Confirm that you are in fact on the Kovan Network before proceeding.
Connect your MetaMask account.
Approve the MetaMask connection.
Below the "Overview" button, find and select the plus sign button to start setting up your CDP.
Select the collateral type you want to proceed with and click "Continue".
e.g. ETH-A
Deposit your K-ETH and generate K-DAI by selecting and inputing an amount of K-ETH and the amount of K-DAI you would like to generate. To proceed, click "Continue".
e.g. Deposit 0.5 K-ETH and generate 100 DAI.
Click on the checkbox to confirm that you have read and accepted the Terms of Service then click the "Create CDP" button.
Approve the transaction in your MetaMask extension.
Click the "Exit" button and wait for your CDP to be created.
After all of these steps have been completed, you will have the generated MCD K-DAI and it will be present within your wallet. You can easily payback your DAI or generate more.
Contract address: 0xaaf64bfcc32d0f15873a02163e7e500671a4ffcd
Run the following command in Seth:
Address information:
The 0x94598157fcf0715c3bc9b4a35450cce82ac57b20
address is the faucet that issues 1 MKR per request.
The 0xaaf64bfcc32d0f15873a02163e7e500671a4ffcd
address is that of the MCD K-MKR token. It will issue 1 MKR.
For more information about the Shutdown of the Maker Protocol, read the below End - Detailed Documentation as well as the
Stability fee accumulation in MCD is largely an interplay between two contracts: the (the system's central accounting ledger) and the (a specialized module for updating the cumulative rate), with the involved only as the address to which the accumulated fees are credited.
increases the 's surplus by Art*rate_change
DSR accumulation is very similar to stability fee accumulation. It is implemented via the , which interacts with the Vat (and again the Vow's address is used for accounting for the Dai created). The Pot tracks normalized deposits on a per-user basis (pie[usr]
) and maintains a cumulative interest rate parameter (chi
). A drip
function analogous to that of Jug is called intermittently by economic actors to trigger savings accumulation.
Get the current USD price of ETH, as a USD_ETH
.
Get the current USD price of the governance token MKR, as a USD_MKR
.
Get the current USD price of PETH (pooled ethereum), as a USD_PETH
.
When everything in the system is going well, Dai accrues through collected from Vaults. Whenever the net surplus from stability fees reaches a certain limit, that surplus in Dai is auctioned off to external actors for MKR which subsequently is burnt, thereby reducing the amount of MKR in circulation. This is done through a Surplus Auction.
This project requires virtualenv to be installed if you want to use Maker's python tools. This helps to ensure that you are running the right version of python as well as check that all of the pip packages that are installed in the are in the right place and have the correct versions.
(for Macs)
For other known Ubuntu and macOS issues please visit the README.
Head to Oasis Borrow .
This requires familiarity with Seth as well as having the tool set up on your local machine. If unfamiliar, use guide to install and set it up.
Important Note: The faucet address and token addresses often change with each dss deployment. The current addresses displayed above are from the 0.2.12 Release. Please visit for the most updated release version.
Please refer to this to obtain collateral test tokens for Kovan.
To help with the testing of your Auction Keeper, we have created a collection of python and shell scripts herein that may be used to test auction-keeper
, pymaker
's auction facilities, and relevant smart contracts in dss
. For more information about testing your Auction Keeper with your own testchain visit .
We welcome any questions or concerns about the Auction Keepers in the channel in the Maker Chat.
Keeper that lifts the hat and streamlines executive actions
Its purpose is to lift the hat
in DSChief as well as streamline executive actions.
To lift
a spell, that spell must have more approvals than the current hat
. The approvals of this spell can fluctuate and be surpassed by other spells, some of which could be malicious. This keeper "guards" the hat
by ensuring the spell with the most approval is always the hat
. The chief-keeper
does this in order to maximize the barrier of entry (approval) to lift
a spell to the hat, thus acting as a "guard" against malicious governance actions.
While in operation, the chief-keeper
:
Monitors each new block for a change in the state of executive votes
lift
s the hat for the spell (yay
) most favored (approvals[yay]
)
Schedules spells in the GSM by calling DSSSpell.schedule()
Executes spells after their eta
has elapsed in the GSM by calling DSSSpell.cast()
chief-keeper
interacts directly with the DS-Chief
and DSSSpell
s.
This keeper is run continuously, and saves a local database of yays
(spell addresses) and an yay:eta
dictionary to reduce chain state reads. If you'd like to create your own database from scratch, first delete src/database/db_mainnet.json
before running bin/chief-keeper
; the initial query could take up to 15 minutes.
Prerequisites:
This project requires virtualenv to be installed if you want to use Maker's python tools. This helps with making sure that you are running the right version of python and checks that all of the pip packages that are installed in the install.sh are in the right place and have the right versions.
In order to clone the project and install required third-party packages please execute:
If tinydb
isn't visible/installed through ./install.sh
, simply run pip3 install tinydb
after the commands above.
Make a run-chief-keeper.sh to easily spin up the chief-keeper.
In order to be able to run tests, please install development dependencies first by executing:
You can then run all tests with:
Level: Intermediate
Estimated-Time: 30 minutes
Emergency Shutdown (ES) is the last resort to protect the Maker Protocol against a serious threat, such as but not limited to governance attacks, long-term market irrationality, hacks and security breaches. The Emergency Shutdown Module (ESM) is responsible for coordinating emergency shutdown, the process used to gracefully shutdown the Maker Protocol and properly allocate collateral to both Vault users and Dai holders. This guide outlines the steps and procedures necessary to check, interact with and trigger the ESM.
Installation
Contract Address Setup
Commands and Explanations
Checking your MKR balance
Checking and setting your MKR approval
Checking the live() flag
Checking the ESM threshold
Deposit a trial amount of MKR into the ESM
Depositing MKR into the ESM
Checking how much MKR is in the ESM
Checking whether the ESM has been triggered
Triggering the ESM
Before depositing your MKR into the ESM contract, first check your address MKR balance:
In order to execute the contract functions of the MKR token it is required that approvals be set on the token. The first step is to check if the ESM contract is allowed to withdraw from your address:
If the ESM contract is not allowed to withdraw from your address, the following can be used to set the allowance on the MKR token. This will approve the ESM to withdraw from the user's wallet:
Following which we again check to confirm that the ESM is allowed to withdraw from the user's account. This action will return uint256 to confirm the allowance to withdraw.
Live contracts have live
= 1, indicating that the system is running normally. Thus when cage()
is invoked, it sets the flag to 0.
In order to check the min
value, you can call:
To deposit a small amount of MKR into the esm contract to test correct deposit function, we use the join
function and specify a small amount.
To check for the total amount of MKR that has been added to the ESM we call the Sum()
function
To check how much MKR you have included in the ESM we can call lowercase sum()
with the user address as an argument:
To deposit MKR into the esm contract we use the join
function and specify the amount.
Please specify the amount of MKR that you intend to deposit into the ESM.
To validate that the Emergency Shutdown has been triggered, the fired()
function can be called which will return a boolean.
In order for the emergency shutdown to trigger, it is required that the Sum()
is greater than the min()
. Only then can the fire()
function be executed successfully.
Note: If triggering the ESM is not successful, ensure gas is set at an appropriate level
Note: The triggering of the ESM is not to be taken lightly; for a full explanation of the implications please review the below documentation.
Level: Intermediate
Estimated Time: 60minutes
This guide describes how users can interact with the Maker protocol through proxy contracts to redeem Dai and any excess collateral if the Maker system has entered into emergency shutdown. We will define the setup process, including proxy contract setup, followed by seth calls to; redeem collateral as a Dai Holder, and free excess collateral as a Vault Owner.
To redeem Dai and/or excess collateral in the event of Emergency Shutdown
Setup Process
Installation
Contract Address Setup
Dai Holders to Redeem Collateral
Check user Dai holdings
Approve a Proxy
Create Calldata
Execute Calldata using the MYPROXY Contract
Call cashETH or cashGEM functions
Using cashETH
Define calldata for our function
Execute cashETHcalldata
Alternative from step (6), Using cashGEM
Define calldata for our function
Call execute in MYPROXY
Vault Owners to Redeem Excess Collateral
Vault Holder State
Redeeming ETH using the freeETH function
2.1. Set Call Data
2.2 Execute this calldata
Redeeming ETH using the freeGEM function
3.1 Set Calldata
3.2 Execute this calldata
Conclusion
There are two functions to be called in order to retrieve the end collateral. The first step is pack
and the second step is cashETH
or cashGem
depending on the leftover amount of each collateral type in the system.
Depositing Dai tokens into the system can be done using the PROXY_ACTIONS_END
contract library and the pack
function. This function efficiently bundles together three parameters, including three parameters; the Dai(join)
adapter, the end
contract and the amount of Dai tokens you wish to redeem for allowed collateral in one go.
The user can check their Dai Token balance and subsequently save it in the wad
variable so that it can be later used in the proxy function.
The user needs to approve MYPROXY
in order to withdraw Dai from their wallet by using the following function.
Next it is necessary to bundle together the function definitions and parameters that the user needs to execute. This is done by preparing a function call to MYPROXY
, defined as calldata.
MYPROXY
contractThe user is able to call the execute
function and utilize the PROXY_ACTIONS_END.pack()
function within the environment of MYPROXY
. This approves the proxy to take Dai tokens from the user's wallet into the proxy address and deposits it into the end
contract, where a proportionate amount of collateral can later be claimed. Once the DAI is packed, it cannot be unpacked.
cashETH
or cashGEM
functionsUsers will be able to withdraw collateral depending on the collateral that is in the VAT at the time of shutdown. For example 1 Dai will be able to claim a portion of ETH and BAT (and any other accepted collateral) which when combined will be approximately worth 1 USD. This process is completed by calling cashETH
or cashGEM
.
cashETH
Next, we again define the calldata for our function by bundling together the cashETH
parameters shown above.
cashETHcalldata
Finally, executing the cashETHcalldata
in the execute
function of the user's MYPROXY
contract will redeem ETH for DAI, and place this ETH into the user's ETH wallet.
cashGEM
It is also possible to use the cashGEM
function in order to redeem different collateral types. In the below example we are referencing gemJoin as it relates to BAT.
Similarly, as done in step (7), the user needs to define the calldata to interact with cashGEM
MYPROXY
Finally, executing the cashBATcalldata
in the execute
function of the user's MYPROXY
contract will redeem BAT for DAI, and place this BAT into the user's ETH wallet.
Likewise, a vault owner can use the freeETH
or freeGEM
proxy actions function to retrieve any excess collateral they may have locked in the system.
There are some constraints for vault holders to be aware of. For example, if a user’s Vault is under-collateralised then they will not have any excess collateral to claim. Likewise, if the user’s Vault is currently in a flip auction at the time of emergency shutdown, it will be necessary for the Vault holder to cancel the auction by calling skip(ilk, id)
before calling free__()
.
Similarly, these functions have been completed using Maker proxy contract calls. There may be other scenarios in which 3rd party front ends such as InstaDApp have their own proxies, which will require users to exit from their proxy in order to use the below.
freeETH
functionDepending on how many vaults the user has, it will be necessary to repeat this process for each vault ID.
Executing the MYPROXY
contract will redeem ETH and place it into the users address.
freeGEM
functionDepending on how many vaults the user has, it will be necessary to repeat this process for each vault ID.
Executing the MYPROXY
contract will redeem BAT (or other collateral types) and place them into the users address.
The above outlines how to redeem Dai and excess Vault collateral using the command line.
In summary, we showed how to check your Dai holdings, how to approve a proxy to withdraw Dai from your wallet and then to use cashETH/GEM
functions to withdraw collateral into the user’s ETH wallet using the MYPROXY
contract . For Vault owners, we showed how to redeem collateral by using the MYPROXY
contract and the freeGEM
function.
In the event of emergency shutdown we envision that it will still be possible to sell Dai on the open market as well as by making use of economically incentivized redemption keepers to meet market needs for both Dai owners and Vaults holders.
You can take advantage of the pluggable architecture of this library by choosing different implementations for services, and/or adding new service roles altogether. A service is just a Javascript class that inherits from either PublicService
, PrivateService
, or LocalService
, and contains public method(s).
Here are the steps to add a new service called ExampleService:
(1) In the src directory, create an ExampleService.js file in one of the subdirectories.
(2) In src/config/DefaultServiceProvider.js, import ExampleService.js and add it to the _services array
(3) Create a class called ExampleService in ExampleService.js
(4) The service must extend one of:
PrivateService - requires both a network connection and authentication
PublicService - requires just a network connection
LocalService - requires neither
See the Service Lifecycle section below for more info
(5) In the constructor, call the parent class's constructor with the following arguments:
The name of the service. This is how the service can be referenced by other services
An array of the names of services to depend on
(6) Add the necessary public methods
(7) If your service will be used to replace a default service (the full list of default service roles can be found in src/config/ConfigFactory), then skip this step. Otherwise, you'll need to add your new service role (e.g. "example") to the ServiceRoles array in src/config/ConfigFactory.
(8) Create a corresponding ExampleService.spec.js file in the test directory. Write a test in the test file that creates a Maker object using your service.
(9) (Optional) Implement the relevant service lifecycle functions (initialize(), connect(), and authenticate()). See the Service Lifecycle section below for more info
(10) (Optional) Allow for configuration. Service-specific settings can be passed into a service by the Maker config file or config options. These service-specific settings can then be accessed from inside a service as the parameter passed into the initialize function (see the Service Lifecycle section below)
The three kinds of services mentioned in step 4 above follow the following state machine diagrams in the picture below.
To specify what initializing, connecting and authenticating entails, implement the initialize(), connect(), and authenticate() functions in the service itself. This will be called while the service's manager brings the service to the corresponding state.
A service will not finish initializing/connecting/authenticating until all of its dependent services have completed the same state (if applicable - for example a LocalService is considered authenticated/connected in addition to initialized, if it has finished initializing). The example code here shows how to wait for the service to be in a certain state.
One way to add an event is to “register” a function that gets called on each new block, using the event service's registerPollEvents() function. For example, here is some code from the price service. this.getEthPrice() will be called on each new block, and if the state has changed from the last call, a price/ETH_USD event will be emitted with the payload { price: [new_price] }.
Another way to an add an event is to manually emit an event using the event service's emit function. For example, when the Web3Service initializes, it emits an event that contains info about the provider.
Note that calling registerPollEvents and emit() directly on the event service like in the previous two examples will register events on the "default" event emitter instance. However, you can create a new event emitter instance for your new service. For example, the CDP object defines it's own event emitter, as can be seen here, by calling the event service's buildEmitter() function.
The Maker Protocol, which powers Multi-Collateral Dai (Dai), backs and stabilizes the value of Dai to a Target Price of 1 US Dollar, translating to a 1:1 US Dollar soft peg. The stabilization mechanism is handled through an autonomous system of smart contracts, a dynamic combination of Vaults, and appropriately incentivized external actors, such as Keepers.
The Maker Protocol has an Emergency Shutdown (ES) procedure that can be triggered as a last resort to protect the system and its users against a serious threat or to facilitate a Protocol upgrade.
Emergency Shutdown is intended to be triggered in the case of a system upgrade or serious emergencies, such as long-term market irrationality, a hack, or a security breach. When triggered, ES stops and shuts down the Maker Protocol while ensuring that all users, both Dai holders, and Vault users, receive the net value of assets they are entitled to under the Protocol’s smart contract logic. However, the value of Collateral that Dai holders can redeem may vary, depending on the system surplus or deficit at the time of ES. It is, therefore, possible that Dai holders will receive less or more than 1 USD worth of Collateral for 1 Dai.
Dai no-race condition: Every Dai holder will be able to redeem the same relative quantity of collateral proportional to their Dai holdings, regardless of when they interact with the contract.
Vault Parity: Vault Owners are prioritized, allowing them to withdraw their excess Collateral before Dai holders are able to access Collateral.
At the time of ES, individual Vaults, entire collateral types, or the Maker Protocol can be undercollateralized, which is when the value of debt exceeds the value of the Collateral ("negative equity"). Thus, the value of Collateral that Dai holders can redeem may vary, depending on the system surplus or deficit at the time of ES. It is, therefore, possible that Dai holders will receive less or more than 1 USD worth of Collateral for 1 Dai.
Immediate Vault redemption: After ES is initiated, Vault owners are allowed to free their Collateral immediately, provided that they execute all contract calls atomically.
No off-chain calculations: The system does not require the cage authority to supply any off-chain calculated values (i.e., it can rely entirely on the last OSM feed prices).
Vow Buffer Assistance: After ES is initiated, any surplus or bad debt in the buffer acts as a reward or penalty distributed pro-rata to all Dai holders. For example, if 10% of total system debt is in the form of net surplus in the Vow, then Dai holders receive 10% more Collateral.
Dai holders can, after a waiting period (for processing) determined by MKR voters, barter their Dai for a relative pro-rata share of all types of Collateral in the system. The amount of Collateral that can be claimed during this period is determined by the Maker Oracles at the time ES is triggered. It's important to note that Dai holders will always receive the same relative pro-rata amount of Collateral from the system, whether their claims are among the first or last to be processed. The Maker Foundation will initially offer a web page for this purpose to make the process easier for Dai holders.
The prioritization of Vault Owners over Dai Holders during ES can be broken down into three main points:
Overcollateralized Vaults do not subsidize the Maker Protocol for undercollateralized Vaults during the current operation of the system, so it's consistent for ES to have the same behavior. The main difference is that a potential haircut is transferred from MKR holders to DAI holders, as no assumptions can be made about the value of MKR after a shutdown.
Giving priority to Vault owners to recover their excess Collateral (if their Vault is not undercollateralized) incentivizes them to maintain overcollateralization. This is important because the incentive remains even if an ES seems likely, which ultimately makes the Protocol more resilient.
Stability fees accrued pre-ES are not waived by ES. Vault owners may accept higher fees if they know they are protected from the collateralization levels of others, potentially resulting in a higher surplus during ES scenarios as well as allowing for a higher DSR during normal operation.
There is a time delay in the Emergency Shutdown that is determined by governance. The delay must expire before any exchange of Dai for Collateral can take place. The general guidance is that the delay should be long enough to ensure all auctions either finish or get skipped; but, there is no guarantee of this in the code. Importantly, anyone can cancel a collateral (Flip) auction at any time, whereas Surplus (Flap) and Debt (Flop) auctions are frozen by the initial calling of Cage. Both Flap and Flop auctions can be called to return the bids to the last bidder.
Note also that auction cancellation is not an immediate process, as ecosystem participants must cancel all ongoing collateral auctions to appropriate the Collateral and return it to the collateral pool. This allows for faster processing of the auctions at the expense of more processing calls. As for the surplus and debt auctions, they must also be called. If no one calls these functions, the auctions will not be canceled.
Emergency Shutdown may take two main forms. For one, ES may be triggered, and the system is terminated without a future plan for redeployment. This allows users to claim excess Collateral or claim Collateral from their Dai.
On the other hand, Emergency Shutdown may be initiated with a Redeployment Scenario. This situation may arise when the system has been triggered into a shutdown event. Still, MKR token holders, or a third party, have decided to redeploy the system with necessary changes to rerun the system. This will allow users to open new Vaults and have a new Dai token while claiming Collateral from the old system.
During an Emergency Shutdown, each of the various Maker Ecosystem Stakeholders should act accordingly:
In the case of Emergency Shutdown, service providers may follow the actions recommended below.
Recommended Procedure
Alert users to the current situation and provide guidance on the right action(s) to take. Depending on the ES scenario, Shutdown, or redeployment, advise them to act accordingly.
Give users options to withdraw their Dai/MKR from the exchange, or inform them that the exchange/wallet will handle the Emergency Shutdown process on their behalf.
Scenario: Shutdown
Choose one of the following options:
Choose one of the following:
Distribute Collateral to users.
Get withdrawal address from users for collateral types not supported on the exchange.
Keep the Collateral (to sell off, for example) and update user internal fiat balances to reflect their entitled amount.
Scenario: Redeployment
Update token address(es) in your system.
In case of Emergency Shutdown, non-custodial wallet providers should alert your user base about ES and provide public links for more information. You may follow the recommended procedures listed below in the case of Emergency Shutdown.
Scenario: Shutdown
Scenario: Redeployment
Add featured support for new token(s).
As a decentralized exchange, you can inform users with a banner about the current status of the Maker Protocol and direct them toward relevant communication channels to find out more. You may choose one of the two following options to allow your users to carry out the ES redemption process:
Build an interface to handle the ES process on your platform, inform your users, and have them act accordingly.
Scenario: Shutdown
Scenario: Redeployment
Add new token(s) to the exchange.
As a Vault integrator, it is very important that you integrate with Maker Protocol contracts (more specifically, end.sol). This crucial integration will allow you to quickly create a reactive logic that will handle the post-ES process for your users. If you are a custodial service, such as a centralized exchange, please inform your users in advance about your plan on handling the Emergency Shutdown event. You may follow the recommended procedures listed below in the case of Emergency Shutdown.
Recommended Procedure
Scenario: Shutdown
Scenario: Redeployment
Dapps are suggested to integrate with Maker Protocol contracts (end.sol), which effectively provides a notification system that shows if Emergency Shutdown has been triggered. In terms of preparation, when ES has been triggered, have the following ready for your users:
A UI interface that alerts and informs users about the event.
If your Dapp uses a proxy, you will need to enable users to exit from the proxy in order to use the migration app/portal.
As a market maker during ES, you may provide liquidity in the market so that Dai holders can exchange their Dai for other assets. After there is no market to cover, you can act as a Dai holder and start migrating Dai to new Dai in case of system redeployment or claim equivalent Dai collateral in case of a system-wide shutdown.
This is an involved and stateful process that involves the following 9 steps.
Locking the prices down for each collateral type is done by freezing the following user entry points:
Vault creation
Surplus/Debt Auctions
Dai Savings Rate (DSR)
Governance entry points
Next, the system will stop all of the current debt/surplus auctions, allowing individual auctions to be canceled by calling a function that moves the first phase of a collateral auction to the End. This process is completed by retrieving the Collateral and repaying Dai to the highest bidder of the respective auction contract. One reason these auctions are frozen and canceled is that the Emergency Shutdown process is designed to pass along the system surplus or system debt to Dai holders. In general, there are no guarantees regarding the value of MKR during a Shutdown and the mechanisms that typically rely on MKR's market value cannot be relied upon, ultimately resulting in there being no reason to keep running the auctions that impact MKR supply. More specifically, the reasons debt and surplus auctions get canceled are as follows:
Surplus auctions no longer serve their purpose. This is because, after a shutdown, the surplus is designed to be allocated to Dai holders. Thus, canceling surplus auctions during Shutdown allows the system to return the surplus Dai back to the Settlement engines balance and ultimately back to Dai holders.
Debt auctions also stop serving their purpose. This is because the bad debt is passed as a haircut (lower-than-market-value placed on an asset being used as Collateral in a Vault) back to Dai holders if there is no other system surplus available.
As for collateral auctions, they are not immediately canceled (but can be canceled by any user) because they are still tied to the valuable Collateral in the system. Collateral auctions continue to run, and Keepers can continue to bid on them. If there are no bidders, the live auctions can also be canceled.
Despite the fact that auctions can continue to run, this does not guarantee that all of the remaining Vaults are overcollateralized. There is also nothing to prevent the undercollateralized and unbitten Vaults from existing at the moment cage is called.
During this time, the function that adds the debt (total Dai wanted from the auction) cannot be called, as the function requires the system to be running normally, disabling liquidations after Shutdown. Additionally, after the End begins, all Vaults must be settled at the tagged price, and then the remaining Collateral from a settled Vault must be removed.
Overall, this results in collateral auctions being able to continue during Shutdown or by having them reversed by a user by canceling live auctions (similar logic to the surplus auctions). If an auction is canceled, the bids are returned to bidders, and Collateral is returned to the original Vault (with the liquidation penalty applied in the form of increased debt).
Notes regarding collateral auctions:
End moves the first phase of collateral auctions to the End by retrieving the Collateral and repaying Dai to the highest bidder.
The second phase of auctions allows bids to be made, while decreasing the quantity up for auction. During this phase, completed auctions are settled as they have already raised the necessary Dai and are already in the process of returning the Collateral to the original Vault holder.
Other Notes:
MKR could still have value if the current MKR token is tied to another deployment of the system. Note that the system makes no assumptions about the economic value of MKR post-Shutdown.
It is important to note that on-auction debt and surplus are canceled, and balances are transferred to the End contract. The last step in this process is to begin the cooldown period.
This process is completed by setting the system shutdown price for each collateral type. The final prices are determined by reading the price feeds from the Maker Oracles. This is required, as the system must first process the system state before it is possible to calculate the final Dai/collateral price. In particular, we need to determine two things:
(a) The shortfall per collateral type considering undercollateralized Vaults.
(b) The total quantity of Dai issued (total debt), which is the outstanding Dai supply after including the system surplus/deficit.
Firstly, this is determined (a) by processing all Vaults with a function that cancels owed Dai from the Vault (described below). Next, (b) unfolds as described below.
Next, the system will allow for the canceling of all the owed Dai from the Vault(s) in the system. Any excess collateral remains within the Vault(s) for the owner(s) to claim. Then, the backing collateral is taken.
Next, the debt is determined (b) by processing the ongoing Dai generation operations of the auctions. Processing the ongoing Dai generation ensures that the auctions will not generate any further Dai income. This guarantees that ongoing auctions will not change the total debt of the system, which also includes the two-way auction (collateral auction) model not allowing for any more Dai to be generated. Due to this, the Dai generation comes from the first phase of collateral auctions. Thus, if everything is in the second phase of an auction (bidding on the decreasing quantity up for auction), we know the generation is over. Generation is over when all auctions are in the reverse/second phase. In addition to ensuring that the auctions will not generate any further Dai, the Dai Savings Rate (DSR) must also be shut off during the End so that the total debt does not change.
Example:
In terms of user scenarios, the process begins with users bidding more and more Dai until the debt is covered. Next, they start offering less and less Collateral.
The auctions that are in the second phase (reverse auctions) no longer affect any more of the total debt, as the Dai was already recovered. Lastly, for the auctions in the first phase, they can be canceled, and the Collateral and debt returned to the Vault.
One of two methods can ensure that Collateral and debt are returned to the Vault:
The processing cooldown duration (length of the debt queue); or
By canceling live auctions.
Set the cooldown period. The duration of the cooldown period only needs to be long enough to cancel owed Dai from the undercollateralized Vaults, and cancel the live first phase auctions. This means that it can, in fact, be quite short (i.e., 5 minutes). However, due to the possibility of scenarios such as network congestion occurring, it may be set longer.
Canceling a live auction will cancel all ongoing auctions and seize the Collateral. This allows for faster processing of the auctions at the expense of more processing calls. This option allows Dai holders to retrieve their Collateral much faster. The next procedure is to cancel each of the individual Collateral (Flip) auctions in the forward first phase auctions and retrieve all of the Collateral and return Dai to the bidder. After this occurs, the second phase—reverse auctions—can continue as they usually would, by setting the cooldown period or canceling the live auctions.
Note that both of these options are available in this implementation, with the canceling of the live auctions being enabled on a per-auction basis. When a Vault has been processed and has no debt remaining, the remaining Collateral can be removed.
Next, the system will remove the Collateral from the Vault. After the Vaults have been settled at the set final price and the owed Dai from the Vault has been canceled, the Vault owner can call this process as needed. It will remove all of the Collateral remaining after step 3—basically, all of the Collateral that was not backing the debt. If the user did not have debt in a Vault at the time of the End, he can bypass steps 3 and 4 and can proceed directly to this step to free his Collateral.
After the processing period has elapsed, the calculation of the final price for each collateral type is possible using the thaw function. The assumption is that all under-collateralized Vaults are processed, and all auctions have unwound. The purpose of this function is to stabilize the total outstanding supply of Dai. Note that it may also require extra Vault processing to cover the system's surplus. Checking that the amount of Dai surplus in the core Vault engine is 0 is a requirement during this phase. This requirement is what guarantees that the system surplus has been taken into account. Furthermore, this means that before you can stabilize the total outstanding supply of Dai, you must cancel the owed Dai from as many Vaults as needed to cancel any Dai surplus in the Vow. Canceling Dai surplus is done by canceling out the surplus and debt in the system's balance sheet before you can stabilize the total outstanding supply of Dai.
In this step, the calculation of the exchange price for a given collateral type is determined, as well as the potential adjustment of that final exchange price in the case of deficit/surplus. At this point in the mechanism, the final price for each collateral type has been computed; Dai holders can now turn their Dai into Collateral. Each unit of Dai can claim a fixed basket of Collateral. Dai holders must first lock their Dai so that they can be ready to exchange it for Collateral. Once the Dai is locked, it cannot be unlocked and is not transferable. More Dai can be locked later, as well.
This step is when Collateral is dispensed to the Dai holders who have already locked their Dai in to be exchanged. The larger the amount of Dai locked in, the more Collateral can be released to the Dai holders.
Lastly, the system will allow the exchange of some of the Dai that has been locked for specific collateral types. Note that the number of collateral tokens will be limited by how much locked Dai users have.
#general
#dev
#governance-and-risk
#help
A powerful command line tool created to interface with the Ethereum blockchain
From the Dapp Tools page:
If you are running GNU/Linux or macOS you can take advantage of our all in one installer.
$ curl https://dapp.tools/install | sh
This script downloads the Nix package manager, setups binary cache with Cachix and installs our most used tools.
If you have issues using the script above, you can try the manual installation on the aforementioned website. A this point in time you can do a manual installation by running the following scripts:
In order to test if the tools have been installed correctly, check the current version of Seth with the following command:
$ seth --version
If Seth has been installed correctly, the command should produce the following output:
seth 0.7.0
At the time of writing, seth 0.7.0 is thus the latest version, however in the future the output might have a different versioning number.
You can quickly set up a local private network with the Dapp tool, which will also create accounts and their keystore files with empty strings for passwords by default. Setting up a local network can be handy when developing dapps for a quick an easy and way to deploy and test functionality without the need of acquiring test-net Ether for contract deployment.
Open a new terminal and execute the following command, and keep it running in the background during the tutorial:
$ dapp testnet
Copy your account address from the output.
Then in a separate terminal let’s create an empty password file:
$ touch pass
And let’s create our environment variables:
Seth can connect to the Kovan Ethereum testnet through a default remote node provided by Infura. This is the most convenient way to do so. You can either create a new account or use the existing one created by the testnet. If you decide to use the existing one, you only need to change the chain parameter:
$ export SETH_CHAIN=kovan
Then you have to set up the same variables:
For the first two operations you can use either your own testnet or the Kovan testnet - try both if you want to!
Checking ETH balances is pretty straight forward. It can be done with the balance subcommand, then specifying the address as a parameter:
$ seth balance $ETH_FROM
Upon execution you should see something like the following:
This indicates that the transaction was successful.
Since we don't have any contracts deployed to our private network, let's use Kovan from now on. Let’s use one of the simplest contracts possible: an ERC-20 token contract. In this example, we are going to use a test collateral token (COL1). You can save its address in a variable with the following command:
$ export COL1=0x911eb92e02477a4e0698790f4d858e09dc39468a
You can read the output of a public function of a contract using the call subcommand, the contract’s address, and the name of the function.
Let's check out the number of decimals of this token:
seth call $COL1 'decimals()'
The output is:
0x0000000000000000000000000000000000000000000000000000000000000012
Now don't let this fool you. Seth queries contract data in a low level manner, and returns the value in hexadecimal, as it is represented in the contract, but you can convert it using:
$ seth --to-dec $(seth call $COL1 'decimals()')
The output is:
18
You can send a transaction to a contract with the same send command, by adding a couple of extra parameters. Just like with call, you need to specify the contract address and the function we are calling. Let’s get some COL1 tokens from a previously set up faucet:
$ export FAUCET=0xe8121d250973229e7988ffa1e9330b420666113a
$ seth send $FAUCET ‘gulp(address)’ $COL1
Now you can check your COL1 balance. This time you will need to present a parameter for the ‘balanceOf’ method of the ERC-20 contract. You can do this by first defining the type, the function takes in its parentheses, and then putting the input parameter after the method:
$ seth --to-dec $(seth call $COL1 'balanceOf(address)' $ETH_FROM)
The output is:
500000000000000000000
Now, that's a rather large value we got. The reason for this is that the contract stores the balances in wei unit (10^-18), which is why we have to convert it to get the actual number of COL1 we own:
$ seth --from-wei $(seth --to-dec $(seth call $COL1 'balanceOf(address)' $ETH_FROM)) eth
The output is:
50.000000000000000000
With seth block, we are capable of querying any information about an Ethereum block. Here is the usage from the help option $ seth block --help
:
What can come in handy is the fact that in place of a block number, we can also use earliest, latest or pending. So if we would like to query the current block gas limit (I have tried this with seth configured for the kovan testnet) we can do the following:
$ seth block latest gasLimit
Output:
8000000
seth estimate
can give an estimation for the gas usage of a transaction. The syntax is pretty much the same as for seth send
, but seth estimate
will not actually send the transaction.
When you want to send a transaction to a contract function with Seth, you have to provide the function signature and the parameters in order after the signature. The ERC-20 transfer function signature looks like the following in Solidity:
transfer(address _to, uint256 _value) public returns (bool success)
The signature part that Seth needs from this is 'transfer(address, uint)' and the parameters are the recipient address and the amount. The contract needs to receive the amount in hexadecimal representation of the number in wei unit, which is why we need those conversions.
Now, to estimate the gas usage of an ERC-20 token transfer let’s execute the following:
Output:
37240
With seth receipt and seth tx, we can query every single detail imaginable about a transaction. They both take a transaction (tx) hash as an input parameter. The main difference between the two, is that the receipt, which contains the results of the transaction, is only constructed after the transaction gets mined, while the output of seth tx only contains the basic parameters of the transaction before it takes effect.
You can try them for example by first executing a transaction to have a transaction hash:
Output:
Now you can try the discussed commands (use your own tx hash from the previous tx):
$ seth receipt 0x58ba3980775741aecaf8435646a003bff3395d7d4e00c8f7a32ad1fa0ce64e01
$ seth tx 0x58ba3980775741aecaf8435646a003bff3395d7d4e00c8f7a32ad1fa0ce64e01
These both generate a pretty long output, but we can filter each query with an optional extra parameter. For example let's see, how accurate was our previous estimation for the gas consumption (it was perfectly accurate):
$ seth receipt 0x58ba3980775741aecaf8435646a003bff3395d7d4e00c8f7a32ad1fa0ce64e01 gasUsed
Output:
37240
Issues with MacOS Mojave
The Maker Protocol Upgrade Contract
Contract Name: scd-mcd-migration.sol
Type/Category: Migration
The Migration contract's purpose is to allow moving SAI and CDPs from the SCD system to the MCD system, thus becoming DAI and Vaults. It also allows users to move SAI/DAI in both directions should they want to exit MCD and go back to SCD.
swapSaiToDai
- Takes Sai (ERC-20 DAI from Single Collateral System) returns DAI (ERC-20 DAI from MultiCollateral System).
swapDaiToSai
- Takes DAI (ERC-20 DAI from MultiCollateral System) returns Sai (ERC-20 DAI from Single Collateral System)
migrate
- Moves a Vault from SCD to MCD by closing the SCD one and opening a corresponding MCD one.
tub
: SCD Tub contract address
vat
: MCD Vat Contract address
cdpManager
: MCD CDP manager
saiJoin
: SAI collateral adapter for MCD
wethJoin
: WETH collateral adapter for MCD
daiJoin
: DAI join adapter for MCD
Overall this contract has two primary purposes:
Two-way exchange for SAI and DAI
One-way transfer of Vaults
constructor
(setup)swapSaiToDai
Users of the current DAI system will want to move to the new MCD system. This function allows DAI holders to seamlessly convert their DAI. They will need to approve
the migration contract on the SAI ERC-20 contract so that it can perform the transferFrom
. The migration contract holds a Vault in MCD that takes SAI as collateral and allows it to exit MCD-DAI, which it does and returns to the msg.sender
.
swapDaiToSai
In case a user wants to go back to SAI, this function allows them to turn in their DAI in exchange for SAI. This requires the user approve
s the migration contract on the DAI ERC-20 contract so that it can transferFrom
then join
the DAI back into MCD. This pays back the "debt" in its SAI-MCD Vault and allows it to retrieve the SAI "collateral" and return it to the msg.sender
.
migrate
This function is meant to be used in combination with the MigrationProxyActions
as it requires the migration contract owns the SCD-Vault (cup
) already and that the migration contract has enough MKR to pay the stability fees. The MigrationProxyActions
migrate
function transferFrom
s the msg.sender
to the migration contract so that the migration contract has enough MKR to pay the stability fee and close the cup
.
The migration contract first draws its own SAI out of its MCD contract and uses that to pay back the debt for the cup
(along with the MKR it has from the proxy action to pay the fee). Then it withdraws the PETH as WETH.
Next the migration contract opens a Vault using the MCD CDP manager and join
s its WETH into its new Vault and withdraws enough DAI from the new Vault (and pays back its Vault) to compensate for the SAI it drew earlier in this step.
Lastly, the migration contract gives the MCD-Vault to the msg.sender
.
Any special/unique information about the specific contract
Anything that it may rely on, especially if it is not obvious
Sources of user error if not explicitly defined
swapSaiToDai
The wad
amount has to be below the debt ceiling for both the overall MCD system and the SAI collateral type, otherwise the frob
will fail. This means that these governance parameters can impact the speed of the transition from SAI to DAI.
swapDaiToSai
The wad
amount has to be below the amount of SAI collateral in the migration contract's Vault. If a user with DAI wants to move to SAI but no SAI users have already moved to DAI, then this will fail.
migrate
Because the migration contract will have to first draw SAI from its MCD collateral, the system will have to be seeded with SAI in the migration contract's Vault in an amount that exceeds the SAI debt for the cup
being migrated.
If a user holds both a cup
and SAI, they should decide whether it makes sense to:
Pay back the cup
in SCD, then migrate
their cup
to MCD (essentially just transfer the collateral to a new MCD Vault).
migrate
their cup
with the debt in place, then use swapSaiToDai
to get DAI which they can then use as an ERC-20 or payback their MCD debt.
One additional consideration, to close or migrate a cup
, a user will have to purchase MKR in order to pay the stability fee and be able to exit
the SCD system. However, once in MCD, new fees will be accrued (and have to be paid) in DAI. If a user's converted SAI does not cover their MCD debt + stability fee, they may have to purchase DAI on the open market.
Before SCD shutdown: Users who took out a Vault in SCD and then used the DAI to purchase something will either have to buy SAI on the open market to pay back their SCD debt or they will have to migrate their collateral to MCD.
Potential for error: Governance parameters around SAI collateral
Collateralization ratio has to be set to a very low number
Both ilks["sai"].duty
and Jug.base
have to be set to 0
during the migration period
Auth errors on Sai Join
Excess Sai in MCD (i.e. more cup
s are lost/not migrated than lost/not migrated Sai): results in an auction and possibly MKR auction to cover bad debt.
Migration
Sai debt ceiling to 0
MCD.ilks[sai] debt ceiling to SCD.totalDai
DSR value competitive with Compound to encourage migration
To override the address of one of the contracts used by Dai.js or a plugin, you can pass the smartContract.addressOverrides
option. You need to know the key of the contract in the addresses file to override it.
A Marketing Making Bot Set up Guide
Level: Intermediate
Estimated Time: 60 minutes
Audience: Developers
Introduction
Prerequisites
Installation
Testing
Bands and Bands Configuration
Example
Order Rate Limitation
Data Templating Language
Price Feed Configuration
Example
Running Keepers
Example (Oasis Market Maker Keeper)
Support Information
This guide is dedicated to showing you how to create your very own Market Maker Keeper Bot as well as educate the community about Market Maker Keeper bots and help both users and developers understand the value of this incredible software. We are proud to say that all of the code needed to get a Market Maker Keeper bot up and running is open-sourced.
OasisDEX (oasis-market-maker-keeper
)
EtherDelta (etherdelta-market-maker-keeper
)
RadarRelay and ERCdEX (0x-market-maker-keeper
)
Paradex (paradex-market-maker-keeper
)
DDEX (ddex-market-maker-keeper
)
Ethfinex (ethfinex-market-maker-keeper
)
GoPax (gopax-market-maker-keeper
)
OKEX (okex-market-maker-keeper
)
TheOcean (theocean-market-maker-keeper
)
Git
1. Clone the market-maker-keeper
repository and switch into its directory:
2. Initializing the git submodules that will bring in both the pymaker and the pyexchange library:
3. Set up the virtual env and activate it:
4. Check to make sure you have the correct version (Python 3.6.6) of Python by running:
5. Install Requirements:
Note: This command is (used in place of pip install -r requirements.txt
) for iterating through all the dependencies in the lib directory to grab all of the requirements needed.
Needing to upgrade to pip version 19.2.2.
Run: pip install --upgrade pip
to fix.
Installing jsonnet (if running macOS Mojave)
To fix, run the following:
xcode-select --install
open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg
pip3 install jsonnet==0.9.5
Re-run: pip3 install $(cat requirements.txt $(find lib -name requirements.txt | sort) | sort | uniq | sed 's/ *== */==/g')
There is quite a lot of value in running all the unit tests to make sure market-maker keeper
has been installed properly. After the repository has been cloned and the installation has been completed, you can run unit tests by executing the following commands.
Firstly, the following command will install the libraries required to run unit tests:
To run the unit tests (py.test, etc..), use the following script:
Example output:
The Bands configuration file is directly related to how your Market Maker Keeper will work. As mentioned in the introduction, these Keepers continuously monitor and adjust their positions in the order book, maintaining open buy and sell orders in multiple bands at the same time. For each buy
and sell
band, the Keepers aim to have open orders for at least the minAmount
. In both cases, they will ensure the price of open orders stay within the <minMargin, maxMargin>
range from the current price. When running, Keepers place orders for the average amounts (avgAmount
) in each band by using use avgMargin
to calculate the order price.
As long as the price of orders stays within the set band(s) (i.e. it is in between the <minMargin,maxMargin>
range from the current price), the Keepers keep them open/running. If some orders leave the band, they either enter another adjacent band or fall outside all bands. In the case of the latter, they would get immediately canceled. In case of the former, Keepers can keep these orders open as long as their amount is within the <minAmount,maxAmount>
ranges for the band they just entered. If it is above the maximum, some of the open orders will get canceled and potentially a new one will be created to bring the total amount back within the range. If it is below the minimum, a new order gets created for the remaining amount so that the total amount of orders in this band is equal to avgAmount
. The same process will happen if the total amount of open orders in a band falls below the minAmount
as a result of other market participants taking these orders. In this case, a new order gets created for the remaining amount so the total amount of orders in this band is equal to avgAmount
. There are some Keepers that will constantly use gas to cancel orders (ex: OasisDEX, EtherDelta and 0x) and create new ones (OasisDEX) as the price changes. Gas usage can be limited by setting the margin and amount ranges wide enough but also by making sure that the bands are always adjacent to each other and that their <min,max>
amount ranges overlap.
The bands configuration file consists of two main sections:
buyBands
sellBands
Note: Each section is an array containing one object per each band.
The minMargin
and maxMargin
fields in each band object represent the margin (spread) range of that band. These ranges may not overlap for bands of the same type (buy
or sell
), and should be adjacent to each other for better Keeper performance (where fewer orders will get canceled if the bands are adjacent to each other). The avgMargin
represents the margin (spread) of newly created orders within a band.
minAmount
- the minimum amount for keeper engagement for a band.
avgAmount
- the target amount for keeper engagement for a band.
maxAmount
- the maximum amount for keeper engagement for a band.
dustCutoff
- a field for the minimum amount of every single order created in each individual band (expressed in buy tokens for buy bands and in sell tokens for sell bands).
Setting this to a non-zero value prevents Keepers from creating a lot of very tiny orders, which can cost a lot of gas. For example, in the case of OasisDEX, it can result in an order that is too small getting rejected by other exchanges.
To start, take the sample configuration file below and copy-paste it to a .json
file within the root directory of your market-maker-keeper
folder. For ease of use, we sugges to name it bands.json
. This bands file will get configured as a command-line argument when we start up the Market Maker Keeper.
A Sample bands.json file containing two Bands:
This example shows bands for the ETH-DAI pair, where ETH represents the base currency and DAI as the quote currency:
This bands.json file should be adequate enough for you to paste and run it as is. Of course, you are free to configure it however you would like.
You will need to set this up if you are going to be trading small amounts to start. This will need to be set up such that those amounts are sufficiently meaningful on the exchange you want to work with (based on the rules of the exchanges that you want to work. For example, their specificly set dust limits).
As mentioned above, there is one parameter in the configuration file (dustCutoff
) that will be used to determine the minAmount
of trade quantity. The dustCutoff
will need to be set higher than/or at the minAmount
trade quantity of the specific exchange. This is to make sure you don't create trades that are less than an exchanges' minimum trade requirements. Note that in your configuration file, you can also lower the required quantities to make it easier on yourself. Reducing the K-ETH amounts will be helpful in reducing wait times as you likely won't want to wait long periods to get enough K-ETH to run your Keeper).
Using Band 1
If we look at the first buy band, the initial buy order will be 30 DAI (avgAmount
) with the price of -> price - (price * avgMargin)
-> 0.1 - (0.1 * 0.01)
-> 0.099 ETH per Dai
.
If the buy
order listed above (30 DAI @ 0.099 ETH) gets partially filled (15 DAI are purchased), then we will have (15 DAI remaining in the order). However, this amount is below the band's minAmount
(20 DAI), therefore, another whole order of 15 DAI will be placed on the exchange at the same price of 0.099 ETH.
In addition to the buy
orders, when the Market Maker Keeper starts up, two sell
orders will also be placed.
Using Band 2
For ease of explanation, let's assume we are selling ETH priced at 100.00 DAI (5 ETH @ 101 DAI and 6 ETH @ 102.5 DAI).
Now imagine a situation where the price of ETH suddenly drops to 97.50 DAI, pushing the bands down. In this scenario, the second band will start working and will become responsible for both of the sell
orders, as they fit in between the second band's minMargin
and maxMargin
.
The Market Maker Keeper will now reset it's bands by performing the following:
Creating an order in Band 1 (5 ETH @ 98.475 DAI) using avgMargin
and avgAmount
.
Cancelling the second order (5 ETH @ 102.5 DAI) (which is now in Band 2) becuase maxMargin
has been breached (when price + (price * maxMargin) = orderPrice
-> 97.5 + (97.5 * 0.05)
-> 102.375 > 102.5).
Keep the first order (5 ETH @ 101 DAI), which is now in Band 2 because it is within minMargin
and maxMargin
of Band 2.
Creating an order in Band 2 (1 ETH @ 99.937 DAI) using avgMargin
and avgAmount
.
This results in a total of 3 orders:
Band 1 -> (5 ETH @ 98.475 DAI)
Band 2 -> (5 ETH @ 101 DAI)
Band 2 -> (1 ETH @ 99.837 DAI)
Next, we will want to add the Rate Limitation to the configurations file. This will make sure that we don't constantly churn out old orders as well as help manage gas consumption. We do this because we want the period
and the amount
to be set to a low amount when we start out. This is done because we don't want new users' Market Maker Keeper bots to be frantically trading all of the time. The goal here is that we want to set up our initial states such that it is only placing an order every 5 min or so (or whatever time amount you decide on).
There are two (optional) limit sections (buyLimits
and sendLimits
) that can be used for limiting the maximum rate of orders created by Market Maker Keepers. They both use the same format.
Example of order rate limits:
The period
defines the amount of time that the limit should be applied over.
The amount
is the maximum amount of orders that should be placed during the set period
amount.
In the example above, the period
s are set to 1-hour and 1-day and the amount
s are set to 50.0 orders and 200.0 orders. This means that over the course of 1-hour, only 50.0 orders can be placed and over the course of 1-day, only 200.0 orders can be placed. The amounts will be expressed either in terms of the buy
or the sell
token, this will depend on the section. Note that the above snippet imposes a limit of 50.0 buy
token within each 60-minute window. Additionally, a maximum of 200.0 buy
tokens within each 24-hour window. Note that the supported time units are s
, m
, h
, d
, and w
.
In the case of the data templating language, think of this like a pre-processing language for parsing the file. The whole purpose of the jsonnet is to set up a configuration file such that you can have it increment based on a price. Therefore, in addition to the price feed, you can also base percentages away from the market price. As you can see below, there is a hardcoded amount/price and then the amounts below it which are dependent on the price.
Note: If you are working with a token that's price does not fluctuate wildly, you do not need to incorporate relative qualities for your amount. This is typically for people who want Market Maker Keepers open for months at a time and don't want to worry about having to change any of their configurations.
Another thing to note about these files is that the Market Maker Keeper reloads the configuration files automatically when it detects a change in them. This makes it easier as you don't have to constantly restart your Keeper bot when you change your band configurations. In short, this works by periodically taking a hash of the configuration file and comparing that hash with the current version. When it sees a change in that hash of the file, it will reload the configuration file and cancel orders as necessary to maintain the newly updated bands.
The price feed is one of the most important determining factors of success in a Market Maker Keeper. If you have the bands set up the way you want, the price feed will help make sure your bands are set at meaningful levels relative to the inside market. If you have wide bands and your strategy is to add liquidity to handle market imbalances, then the price feed is not as important. However, as you tighten up the spreads, the price feed is a crucial component to ensure that you are going to profit in the market.
Below, we list some of the existing public feeds. You can also use web sockets if you have your own price feed that you want to use. In short, it works by each Market Maker Keeper taking in a --price-feed
command-line argument which then determines the price used for market-making.
As of today, these are the possible values of this argument that we list some of the existing public feeds:
fixed:200
- uses a fixed price, (1.56
in this example). See below for a more in-depth example. When on mainnet, you typically won't use a fixed amount but it is an ideal example for this walkthrough as there aren't price feeds for Kovan.
eth_dai
- uses the price from the GDAX WebSocket ETH/USD price feed.
eth_dai-setzer
- uses the average of Kraken and Gemini ETH/USD prices.
eth_dai-tub
- uses the price feed from Tub
(only works for keepers being able to access an Ethereum node).
dai_eth
- inverse of the eth_dai
price feed.
dai_eth-setzer
- inverse of the eth_dai-setzer
price feed.
dai_eth-tub
- inverse of the eth_dai-tub
price feed.
btc_dai
- uses the price from the GDAX WebSocket BTC/USD price feed.
dai_btc
- inverse of the btc_dai
price feed.
ws://...
or wss://...
- uses a price feed advertised over a WebSocket connection (custom protocol).
Note: The --price-feed
command line argument can also contain a comma-separated list of several different price feeds. In this case, if one of them becomes unavailable, the next one in the list will be used instead. All listed price feeds will be constantly running in the background, the second one and following ones ready to take over when the first one becomes unavailable. In the example below (in the Running Keepers section), you can see an example of how to use a fixed price amount.
Each Market Maker Keeper is a command-line tool which takes in generic command-line arguments (such as --config
, --price-feed
, --price-feed-expiry
, --debug
, etc.) as well as some arguments which are specific to that particular Keeper (such as Ethereum node parameters, addresses, exchange API keys, etc.). All accepted command-line arguments are listed in the example section below. They can also be discovered by trying to start a Market Maker Keeper with the --help
argument.
In order to run oasis-market-maker-keeper
, you will need to go through the following process:
Firstly, you would deploy an Ethereum node (we recommend Parity).
Generate an account in it.
Permanently unlock that account.
Transfer some tokens to it.
Lastly, you can run the keeper (as seen below).
The below file should be copy and pasted into a new file within the root directory of the repository (market-maker-keeper
). This should be placed within the same folder where you put the bands.json
file.
Make sure that you retrieve and paste the correct contract addresses when using the above snippet.
--eth-key ${ACCOUNT_KEY}
- includes both the .json
file (account.json) of your account and a .pass
file (ex: account.pass) that contains your password in plaintext.
List of required Kovan Addresses for the above :
General Notes:
The OASIS_SERVER1_KEY
is simply your Kovan account private key (point this to your ETH accounts key file) and password file. If you do not have this, please set up a file with your password (in plain text).
ETH From is the address location where the market-maker-keeper is going to get the tokens that it uses to participate and place orders.
Example: Since Oasis is a decentralized exchange (dex), it is on-chain, so you need to provide all of the relevant addresses to the dex. Most DEX's are like this because when you are configuring with a dex you need to pass many addresses in, whereas, with a centralized exchange you are generally giving an API key, and username and password (see below for an example of how the process differs for centralized exchanges differ versus decentralized exchanges).
This Oasis example is currently for Single Collateral DAI (SCD), where we configure the TUB_ADDRESS. However, as we move over to Multi-Collateral DAI (MCD) the TUB_ADDRESS will be changed to the PIP_ADDRESS for MCD.
Open up your terminal
Run chmod +x <the file name of your version of the above bin/oasis-market-maker-keeper snippet>
Run ./<the file name of your version of the above bin/oasis-market-maker-keeper snippet>
That's it, your Market Maker Keeper should now be running!
In the situation where you want to use a centralized exchange vs. a decentralized exchange, the process differs a little:
You would need to have an existing account or create an account (on the exchange itself).
Get the set of API keys with trading permissions (will usually need to be generated as well).
Deposit tokens in your account on the exchange (as the keepers do not handle deposits and withdrawals themselves).
Run the Market Maker Keeper.
Level: Intermediate
Estimated Time: 30 minutes
Audience: Technical and commercial teams with partners and Dai holders
Therefore, users and partners interacting with Single-Collateral Dai (SCD) must migrate their existing Single Collateral Dai tokens (Sai) to Multi Collateral Dai tokens (Dai) and CDPs to the new system. Additionally, companies or projects integrated with Sai and CDPs must update their codebases to point to the new smart contracts, and refactor their code to support the updated functions.
This guide will focus on the Dai and CDP migration with a high level overview of the upgrade process for different actors in the Maker ecosystem.
The steps necessary to migrate from Single-Collateral Dai (SCD) to Multi-Collateral Dai (MCD) differ depending on your platform and use case for Dai, so the guide is split into sections for different user and partner types.
In this guide we refer to the Single Collateral Dai system as SCD, and the Multi-Collateral Dai system as MCD. We refer to the Single Collateral Dai token (the old, currently existing Dai) as Sai, and the new Multi-Collateral Dai token as Dai.
Knowledge on how migration to MCD will work
Best practices for migration for different users and partners
Where to find guides on specific migration scenarios
The following section will outline a recommended migration process for different actors in the Maker ecosystem.
You control your private key
The following figure outlines the migration flow:
You don’t control your private key
With MCD you can deposit your Dai into the Dai Savings Rate smart contract which will earn you accrued annual savings. Find more info at makerdao.com at launch.
You can also choose to manually close your CDP by paying back your debt and redeeming your Ether, and use your redeemed collateral to open a new MCD CDP.
Notes on Instadapp
Notes on MyEtherWallet
Once upgraded, you can start using Dai Savings Rate by locking your Dai into the Dai Savings Rate smart contract and receive accrued savings. Find more info on makerdao.com at launch.
We recommend you take the following steps for upgrading to MCD:
On November 18: Rename Single-Collateral Dai to “Sai”. This is being coordinated with all Maker partners and serves to avoid users depositing the wrong token into your system.
On December 2: Perform upgrade of user balances.
Inform your users as soon as possible about the dates. For users wanting to delay their upgrade, this allows them to opt-out by withdrawing Sai from your exchange before the date.
Proposed process for the December 2 upgrade:
Freeze Sai deposits/withdrawals
Rename listing/token to "Dai"
Unfreeze Dai deposits/withdrawals.
Optional: Choose one of the following:
Integrate Dai Savings Rate and distribute revenue to your users.
Integrate Dai Savings Rate in your exchange and keep accrued savings in your own balance sheet.
This approach will result in the following user journey for the exchange/wallet user:
We recommend you take the following steps for upgrading to MCD:
On November 18: Rename Single-Collateral Dai to “Sai” and ticker "SAI". This is being coordinated with all Maker partners and serves to avoid users attempting to deposit the wrong token into your system.
On the date of your own Dai listing: Add support for the new Dai token on your platform. The new Dai token should be named Dai and have the ticker "DAI". Deactivate Sai trading in your frontend UI, but allow users to cancel orders and withdraw balances.
Inform users that they can redeem Sai for Dai at migrate.makerdao.com
Optional: Provide a UI in your own interface for token migration through the migration contract.
Inform users about Dai Savings Rate, which allows Dai holders to earn savings.
Optional: Build a UI that facilitates the usage of the Dai Savings Rate service for your users in your exchange, where users will keep the accrued savings themselves.
If you are a creator of a wallet that allows users to be in control of their private keys we recommend you do the following:
On November 18: Rename Single-Collateral Dai to “Sai”
Inform your users as soon as possible about the timeline for your own upgrade to MCD.
Support balances of both Sai and Dai for a period until Sai demand diminishes.
Optional: Provide a UI in your own interface for token migration through the migration contract.
Inform users about Dai Savings Rate, which allows Dai holders to earn savings.
Optional: Create a UI where users can activate Dai Savings Rate.
Optional: Implement paying the gas cost of Dai transactions on behalf of your users.
Upgrading
We expect to release a Python library for working with Auctions before MCD launch. This will be the recommended way to bid in Auctions.
We encourage you to market make on Multi-Collateral Dai as soon as your exchange partners add support for it.
If your exchange partners keep their Sai listing concurrently with their Dai listing, we encourage you to market make on both tokens for the remaining lifetime of Sai.
If your exchange partners will use a different ticker for Dai than Sai, you should update your tools accordingly.
Custodial CDP service
On November 18: Rename Single-Collateral Dai to “Sai”
Select a future date between November 18-25 to execute the upgrade to MCD.
Inform your users as soon as possible about the date.
On the chosen date:
Freeze access to CDP service for your users
Migrate all CDPs to MCD. See “Migration App” section below.
List the Multi-Collateral Dai token as "Dai"
Unfreeze access to CDP service
Optional: Implement support for added collateral types in MCD
If it is relevant to your service, inform users about Dai Savings Rate
Optional: Implement UI for locking Dai in the Dai Savings Rate smart contract.
Non-Custodial CDP service
On November 18: Rename Single-Collateral Dai to “Sai”
Select a future date between November 18-25 to execute the upgrade to MCD.
Inform your users as soon as possible about the timeline for your own upgrade to MCD.
Inform your users about MCD and the migration process of CDPs.
On the selected launch date:
Launch upgrade to your service that supports the new CDP core.
List the Multi-Collateral Dai token as "Dai"
Choose one of the following:
Option B: Create your own UI for migration, by creating a frontend to interact with the migration contract (see section below on Migration Contract).
Optional: Help your users migrate their CDP to MCD
Option B: Implement your own migration UI in your app, connecting to the migration contract described in a section below.
Option C: If your app is not compatible with migrate.makerdao.com, you can guide your users in how to export their CDP from your app to a compatible wallet.
Optional: Implement support for new MCD functionality
Add support for new collateral types.
Add support for Dai Savings Rate.
If you have integrated directly with the smart contracts, you must add support for the new Maker core smart contracts. Since the smart contracts have been completely rewritten, most function calls have been changed.
Implement support for the MCD smart contracts
Custodial Service
On November 18: Rename Single-Collateral Dai to “Sai”
Select a future date between November 18-25 to execute the upgrade to MCD.
Inform your users as soon as possible about the date.
On the chosen date:
Stop lending (deposits) and borrowing (withdrawals) of Sai
Open for lending (deposits) and borrowing (withdrawals) of Dai
For outstanding loans in Sai, choose one of the following:
Accept payback of loans in Sai.
Continuously migrate paybacks of old positions of Sai to Dai yourself.
Inform your users that you can no longer pay back Sai, but that they should migrate their Sai to Dai through migrate.makerdao.com before paying back a loan.
Non-Custodial Service
On November 18: Rename Single-Collateral Dai to “Sai”
Select a future date between November 18-25 to execute the upgrade to MCD.
Inform your users as soon as possible about the timeline for your own upgrade to MCD.
Inform users about potential cutoff dates for shutdown of SCD.
At launch:
Launch support for Dai loans.
Stop creation of loans in Sai
Let existing loans in Sai run until they expire or are paid back
Optional:
Create a UI for users to migrate their balances from Sai to Dai.
On November 18: Rename Single-Collateral Dai to “Sai”
Select a future date between November 18-25 to execute the upgrade to MCD.
Inform your users as soon as possible about the timeline for your own upgrade to MCD.
On the chosen date:
Update code base to support the use of the new Dai token at launch.
Optional: Implement paying gas cost of Dai transactions in Dai.
If you have a product using Sai:
Shutdown functionality of Sai at a cut-off date, communicated well in advance to your users.
Inform your users about potential confusion regarding Sai and Dai.
Optional: Create a UI for carrying out the migration from Sai to Dai.
Landing Page that will show you possible migrations for the connected wallet.
Wizard for migrating Sai to Dai.
Wizard for migrating an SCD CDP to MCD CDP.
If you have created CDPs using third party services that do not use Maker proxies to interact with the CDP core, the migration contract might not work. Instead, you can perform your own manual migration by simply closing down your SCD CDP and moving the ETH to an MCD CDP.
The functionality of the Migration App outlined in the above section is handled by a Migration Contract that will be deployed at MCD launch in order to support a smooth transition from Single Collateral Dai to Multi Collateral Dai. The contract will make the redemption of Single Collateral Dai tokens (Sai) for Multi Collateral Dai tokens (Dai), and the migration of CDPs to the new CDP engine of MCD, an easy task. This section will outline how the migration contract works in order to help super users and partners prepare for MCD migration.
In the src
folder, the smart contract source code can be found. The main contract is the ScdMcdMigration.sol
which contains the migration functionality that you will interact with.
It contains three main functions:
swapSaiToDai
- a function that upgrades Sai to Dai
swapDaiToSai
- a function that allows you to exchange your Dai back to Sai
migrate
- a function that allows you to migrate your SCD CDP to MCD.
The following sections will go deeper into these function calls. The Migration App will present this functionality in an easy to use UI, so a regular user will not have to deal with these function calls directly. We will however dive into them in the following sections to dissect how migration works, and outline the process for power users or partners, who want to carry out migration themselves.
From the user perspective, this function simply upgrades a specified amount of Sai to Dai.
Behind the scenes, deposited Sai tokens are used to create a collective CDP in MCD for all migrating users which Dai is minted from. The migration contract will thus take the Sai tokens from your account, and deposit them into the Sai token adapter, which allows the CDP engine Vat to utilize the tokens for CDP actions. The migration contract will then invoke Vat to lock Sai and issue Dai to the Dai adapter. The migration contract will then exit the Dai tokens from the Dai adapter, which is carried out by invoking a mint function on the Dai token contract which will generate new Dai for the originator’s Ethereum address. The reason Sai to Dai migration is carried out using the CDP core (vat) of the new system, is that this is the only component that has the authority to mint new Dai. The process and function calls are outlined in the diagram below.
The following diagram outlines what happens when migrating 10 Sai to 10 Dai.
However, this operation requires a surplus of Sai already deposited in the migration contract. Therefore there must be at least an equivalent amount of Sai deposited in the contract, to the amount of Dai you want to redeem.
However, in order to close down the CDP, a stability fee in MKR must be paid, so you need to grant the migration contract approval to spend MKR from you account to carry out the migration.
If you have created CDPs using third party services that do not use Maker proxies to interact with the CDP core, the migration contract might not work. Instead, you can perform your own manual migration by simply closing down your SCD CDP and moving the ETH to an MCD CDP.
In this guide, we introduced you to the steps of how to upgrade to Multi-Collateral Dai. We have provided you with guidelines for different types of platforms using Dai and for regular Dai holders. As we approach the launch of Multi-Collateral Dai, more details will be made available.
If you encounter any issues with your upgrade process, don’t hesitate to contact us.
Rocket chat - #dev channel
After finishing this guide we think you’ll enjoy these next guides:
Information:
Other Guides:
Source code/wiki:
The Maker Protocol's Collateral Auction House
Contract Name: flip.sol
Type/Category: DSS —> Collateral Auction Module
Etherscan
Summary: Collateral Auctions are used to sell collateral from Vaults that have become undercollateralized in order to preserve the collateralization of the system. The Cat sends bitten collateral to the Flip module to be auctioned off to keepers. The collateral auction has two phases: tend
and dent
.
wards [usr: address]
, rely
/deny
/auth
- Auth mechanisms
Bid
- State of a specific Auction {bid
, lot
, guy
, tic
, end
, usr
, gal
, tab
}
bid
- Bid amount (DAI)/ DAI paid
lot
- quantity up for auction / collateral gems for sale
guy
- high bidder (address)
tic
- Bid expiry
end
- when the auction will finish / max auction duration
usr
- address of the Vault being auctioned. Receives gems during the dent
phase
gal
- recipient of auction income / receives dai income (this is the Vow contract)
tab
- total dai wanted from the auction / total dai to be raised (in flip auction)
bids[id: uint]
- storage of all bids
vat
- storage of the Vat's address
ilk
- id of the Ilk for which the Flipper is responsible
beg
- minimum bid increase (default: 5%)
ttl
- bid duration (default: 3 hours)
tau
- auction length (default: 2 days)
kicks
- Total auction count, used to track auction id
s
kick
- function used by Cat
to start an auction / Put collateral up for auction
tick
- restart an auction if there have been 0 bids and the end
has passed
tend
- first phase of an auction. Increasing Dai bid
s for a set lot
of Gems
dent
- second phase of an auction. Set Dai bid
for a decreasing lot
of Gems
file
- function used by governance to set beg
, ttl
, and tau
deal
- claim a winning bid / settles a completed auction
yank
- used during Global Settlement to move tend
phase auctions to the End
by retrieving the collateral and repaying dai to the highest bidder.
claw
: reduces the amount of litter in the Cat's box
file
)beg
ttl
tau
Also, Cat
's dunk
and chop
also inform how Flip
works as the dunk
becomes the Bid.lot
and influences, along with the chop
, the Bid.tab
.
vat
ilk
Both of these are set in the constructor and cannot be changed. If the Vat address is changed and each time a new collateral is added to the system, a new Flip will need to be deployed.
The Flipper must be Vat.wish
'ed on by the Cat
in order to flux
during kick
.
The End
must be rely
'ed on by the Flipper to allow for yank
.
The Cat
must be rely
'ed on by the Flipper to allow for kick
.
The Flip auction process begins with Maker Governance voters determining the collateral's minimum collateralization ratio (Spot.Ilk.mat
) which is then tested against the Vault's state (collateral price, total debt owed) to determine whether the Vault is safe (See Cat
documentation for more information on the bite
process). The last step of a bite
is to kick
a Flip
auction for that specific collateral. Note that the liquidation penalty gets added to the tab
when the Flip
auction gets kicked. This only determines when the auction switches from tend
to dent
. However, this amount is not added to the total debt amount (only to the part that is being partially liquidated) unless everything has in fact been liquidated.
Governance also determines the size of the lot
(where a lot
is the quantity of collateral gems up for auction in a flip
auction) when a Vault gets bitten. This allows for partial liquidations of large Vaults. Partial liquidations make auctions more flexible and less likely to impact the base collateral price by creating a single large auction. They also allow large Vaults to become safe again if the price recovers before the Vault is fully liquidated. Keepers will want to keep this in mind when biting unsafe Vaults as well since they will have a choice to start one or many partial liquidation auctions.
Starting in the tend
-phase, bidders compete for a fixed lot
amount of Gem with increasing bid
amounts of Dai. Once tab
amount of Dai has been raised, the auction moves to the dent
-phase. The point of the tend
phase is to raise Dai to cover the system's debt.
During the dent
-phase bidders compete for decreasing lot
amounts of Gem for the fixed tab
amount of Dai. Forfeited Gem is returned to the liquidated Urn for the owner to retrieve. The point of the dent
phase is to return as much collateral to the Vault holder as the market will allow.
Once the auction's last bid has expired or the auction itself has reached the end
anyone can call deal
to payout the highest bidder (Bid.guy
). This moves Gem's from the Flipper's balance in the Vat to the bidder's balance.
This failure mode is due to the fact that there is nothing the system can do to stop a user from paying significantly more than the fair market value for the token in an auction (this goes for all auction types, flip, flop, and flap
).
Keepers that are performing badly are primarily at risk during the dent
phase since they could return too much collateral to the original Vault and end up overpaying (i.e. pay too much Dai (bid
) for too few gems (lot
)).
During tend
, bid
amounts will increase by a beg
percentage with each new tend
. The bidder must know the auction's id
, specify the right amount of lot
for the auction, bid at least beg
% more than the last bid but not more than tab
and must have a sufficient Vat.dai
balance.
During dent
, lot
amounts will decrease by a beg
percentage with each new dent
. The bidder must know the auction's id
, specify the right amount of bid
for the auction and offer to take beg
% less lot
than the last bid.
When a tend
bid is beaten out by another bidder, the new winner's internal DAI balance is used to refund the previous winning bidder. When a dent
bid is beaten out by another bidder, the Flipper's gem balance is used to refund the Vault holder. Once placed, bids cannot be canceled.
Illustration of the bidding flow:
Cat kick
s a new Flip Auction. The Cat emits a bite
event with the Flipper's address and the auction id
. The Flipper emits a kick
event with the id
and other auction details.
Start tend
auction:
Bidder 1 makes a bid that increases the bid
size by beg
. Bidder 1's DAI balance in the Vat is decreased by bid
and the Vow's DAI balance in the Vat is increased by bid
.
Bidder 2 makes a bid that increases Bidder 1's bid
by at least beg
. Bidder 2's DAI balance in the Vat is decreased by bid
and Bidder 1's DAI balance in the Vat is increased by bid
(thereby refunding Bidder 1 for their now-losing bid). Bidder 2's DAI balance in the Vat is decreased by bid
- Bidder 1's bid
and the Vow
's DAI balance is increased by the same number. tic
is reset to now
+ ttl
Bidder 1 makes a bid that increases Bidder 2's bid
by at least beg
. Bidder 1's DAI = Vat.dai[bidder1]
- Bidder 2's previous bid
; Bidder 2's DAI = Vat.dai[bidder2]
+ Bidder 2's previous bid
. Then Bidder 1's DAI = Vat.dai[bidder1] - (bid - Bidder 2's bid)
and Vow's DAI = Vat.dai[bidder1] + (bid - Bidder 2's bid)
. tic
is reset to now
+ ttl
Once a new bid
comes in that is equal to the tab
the tend
phase is complete.
Start dent
auction:
Note: This phase must start before tic
expires and before bid.end
is passed.
Bidder 2 (and all the other bidders within the auction) decide it is no longer worth it to continue to increase their bid
s, so they stop bidding. Once the Bid.tic
expires, Bidder 1 calls deal
and the gem tokens are sent to their Vat balance.
Note: An auction can also end in the tend
phase by not reaching tab
before the tic
or end
are reached. If this happens, then the winning bidder is awarded using the deal
function and the difference between the final bid
and the tab
stays as bad debt in the Vow
to be dealt with during a Flop
auction.
In the case of Global Settlement, the End
is able to call yank
on the Flipper. Yank
closes a tend
-phase auction by returning the guy
's Dai bid and moving the Gems from the Flipper to the End
. dent
-phase auctions can continue to the deal
phase as they have already raised the necessary Dai and are in the process of returning Gems to the original Vault holder.
Because Flip.tend
compares the bidder's bid
with the previous bid * beg
, it will compare the two numbers at 10^63 precision (rad * wad
). This means that any bid
that is greater than 115,792,089,237,316 will cause an overflow. Governance should endeavour to not set beg
or lot
(via Cat.ilks[ilk].dunk
) so that it is likely that an auction keeper would end up bid
'ding that much DAI during the tend
phase. This is very unlikely so long as the target price of Dai remains 1 USD, but is included here for awareness.
Auction Grinding
Auction grinding allows an attacker to generate debt, allow their Vault to be bitten, win their own auction to get their collateral back at a discount. This type of failure is most possible when the liquidation penalty is set too low.
Get the token conversion service with maker.service('conversion').
The token conversion service offers functions to convert between Eth, Weth and Peth, handling allowances when necessary.
Params: amount of Eth to convert
Returns: promise (resolves to transactionObject once mined)
Note: this is the same as weth.deposit
convertEthToWeth
deposits ETH into the WETH contract
Params: amount of Weth to convert
Returns: promise (resolves to transactionObject once mined)
convertWethToPeth
joins WETH into PETH, first giving token allowance if necessary.
Params: amount of Eth to convert
Returns: promise (resolves to transactionObject once mined)
convertEthToPeth
awaits convertEthToWeth, then calls convertWethToPeth
Note: this process is not atomic, so it's possible for some of the transactions to succeed but not all. See Using DsProxy for executing multiple transactions atomically.
Params: amount of Weth to convert
Returns: promise (resolves to transactionObject once mined)
convertWethToEth
withdraws Eth from Weth contract
Note: this is the same as weth.withdraw
Params: amount of Peth to convert
Returns: promise (resolves to transactionObject once mined)
convertPethToWeth
exits PETH into WETH, first giving token allowance if necessary
Note: this process is not atomic if a token allowance needs to be set, so it's possible for one of the transactions to succeed but not both. See Using DsProxy for executing multiple transactions atomically. Also, peth.exit can be called instead if you do not want the allowance to be set first automatically.
Params: amount of Peth to convert
Returns: promise (resolves to transactionObject once mined)
convertPethToEth
awaits convertPethToWeth, then calls convertWethToEth
Note: this process is not atomic, so it's possible for some of the transactions to succeed but not all. See Using DsProxy for executing multiple transactions atomically.
Retrieve the OasisExchangeService (or alternative implementation) through maker.service('exchange')
. The exchange service allows to buy and sell DAI, MKR, and other tokens. The default OasisExchangeService implementation uses the OasisDEX OTC market for this.
Sell a set amount of DAI and receive another token in return.
Parameters
daiAmount
- Amount of DAI to sell.
tokenSymbol
- Token to receive in return.
minFillAmount
- Minimum amount to receive in return.
Buy a set amount of DAI and give another token in return.
Parameters
daiAmount
- Amount of DAI to buy.
tokenSymbol
- Token to give in return.
minFillAmount
- Maximum amount to give in return.
OasisOrders
have a few methods: fillAmount
: amount of token received in exchange fees()
: amount of ether spent on gas created()
: timestamp of when transaction was mined
To access system status information, retrieve the ETH CDP Service through Maker.service('cdp').
Params: none
Returns: promise (resolves to system collateralization ratio)
getSystemCollateralization()
returns the collateralization ratio for the entire system, e.g. 2.75
Params: none
Returns: promise (resolves to target price)
Dai.js supports the use of multiple accounts (i.e. private keys) with a single Maker instance. Accounts can be specified in the options for Maker.create
or with the addAccount
method.
Call useAccount
to switch to using an account by its name, or useAccountWithAddress
to switch to using an account by its address, and subsequent calls will use that account as the transaction signer.
When the Maker instance is first created, it will use the account named default
if it exists, or the first account in the list otherwise.
You can check the current account with currentAccount
and currentAddress
:
In addition to the privateKey
account type, there are two other built-in types:
provider
: Get the first account from the provider (e.g. the value from getAccounts
).
browser
: Get the first account from the provider in the browser (e.g. MetaMask), even if the Maker instance is configured to use a different provider.
Plugins can add additional account types. There are currently two such plugins for hardware wallet support:
The chief-keeper
monitors and interacts with and DSSSpells, which is the executive voting contract and a type of proposal object of the .
The following section assumes familiarity with the , DSSSpells, and (Governance Security Module), as well as the processes within .
For some known Ubuntu and macOS issues see the README.
Download
This project uses for unit testing. Testing of Multi-collateral Dai is performed on a Dockerized local testchain included in tests\config
.
See file.
If you have questions regarding Cage Keepers, please reach out to us on the channel on .
In order to interface with the Ethereum blockchain, the user needs to install seth, a command line tool as part of the toolset. We also provide further . Once the user has installed and configured correctly to use the main Ethereum network and the address which holds their MKR they can query contract balances, approvals and transfers.
.
In order to interface with the Ethereum blockchain, the user needs to install seth, a command line tool as part of the toolset. We also provide further . Once the user has installed and configured [seth](<https://dapp.tools/>)
correctly to use the main Ethereum network and the address which holds their MKR, they can query contract balances, approvals and transfers.
The user will require the following contract addresses, shown below as mainnet addresses. Rest of mainnet or testnet addresses are accessible at which can be verified on . Similarly, additional information on the commands described below can be found in the and the . These should be setup in the following manner and pasted into the terminal line by line:
The following function cashETH
is referenced as part of the calldata
function and should be referenced .
It can depend on other services through our built-in , and can also be configured through the Maker config file / config options.
The information below is written for developers who want to add to dai.js, but you can also add services through the use of .
play a critical role in maintaining the health of the system and Dai stability. In March 2020, the Maker Foundation the need for a more developed Keeper ecosystem. An increase in Keeper participation would ultimately improve the health and function of the Maker Protocol. For more information on how to get a Keeper up and running, see this.
The process of initiating ES is decentralized and controlled by MKR voters, who can trigger it by depositing MKR into the, a contract with the ability to call or by an Executive Vote. In the case of the ESM method, 50,000 MKR must be deposited into the ESM to trigger ES. Additionally, once users deposit MKR into the ESM, it is immediately burned. Whether through the ESM or Executive Vote method, when Cage is called, it must alter the behavior of almost every component of the system, as well as perform under a variety of possible undercollateralization scenarios.
Vault owners can retrieve excess Collateral from their Vaults immediately after the initialization of ES. They can do this via Vault frontends, such as , that have ES support implemented, or via command-line tools.
If your wallet has the viable interface to claim Collateral or migrate your Dai, or it has a Dapp browser built into it, you may use the to claim Collateral and/or migrate. If your wallet does not support the above functionality, you must transfer your Dai to a new wallet that enables the functionality before proceeding to use the .
If you use to manage your Vault, proceed to the and follow the outlined emergency redemption process.
If you are a user of a third-party interface, such as or , verify that they have Emergency Shutdown Interfaces built-in before proceeding. If so, use their interface to claim the excess Collateral or migrate to a newly deployed system. If the third-party provider does not have the redemption process built-in, transfer to the if possible.
MKR holders may vote on polls and executive votes as it relates to the Emergency Shutdown triggering process. This is done in the Emergency Shutdown Module (ESM) frontend or directly through the . Additionally, MKR holders may also vote as it relates to a future redeployment of the Maker Protocol on the .
Option 1: Let users withdraw Dai and MKR from the platform, and then guide them to the for the redemption process.
Option 2: Claim Dai equivalent in Collateral on behalf of users using the .
Migrate Dai holdings to new Dai token on behalf of users using the .
Alternatively, carry out-migration by interacting directly with the migration contracts using CLI tools. See .
If applicable, migrate MKR token holdings on behalf of users using the
Redirect users to the to claim their Dai equivalent in Collateral, or create an interface to handle the process locally.
Inform users to migrate their Dai on the , or create an internal interface to handle the process locally.
Direct them to the , where they can start the claiming process for their Dai.
Inform users to claim equivalent value of Dai in Collateral on the or create an interface to handle the process locally.
Inform users to migrate their Dai to the new Dai (and MKR if applicable) on the , or create an interface to handle the process on your platform.
As a dapp browser, please make sure to alert your user base about ES and provide links to more information (e.g., or). In case of either an emergency system shutdown or system redeployment after ES is triggered, redirect your users to the to claim their Collateral.
Claim users’ funds through the or by direct interaction with the migration contracts, and make them available in their accounts.
Migrate users’ funds to a new redeployed system using the or by interacting directly with the migration contracts.
As a non-custodial Vault integrator, please make sure to integrate with the Maker Protocol contracts (end.sol). This allows you to be notified at the exact moment the Shutdown has been triggered. Otherwise, it is suggested that you inform your users on how they can free Collateral in Vaults. This can either be done in the non-custodial Vault integrator’s UI or you can direct them to if the users need to migrate their Vault. If you do decide to use your own services, you will need a UI that allows users to withdraw their Vaults from a proxy contract so it shows up on the . Direct your users there. Alternatively, you may create an interface that will help users migrate their Dai in case of a new redeployment, or allow users to claim their Collateral in case of an only shutdown scenario.
Provide official communication channels for more information as well as a link to the for Dai and Vault redemption.
If you control access to the smart contracts backing your Dapp, it is suggested to allow your users to retrieve Dai or access their Vaults from their personal wallet as well as direct them to the for the ES redemption process. Alternatively, you may claim Dai collateral or claim excess Collateral from Vaults on behalf of your users at the, and proceed to distribute it to your users, ensuring that they successfully retrieve it.
If you don’t control the smart contracts backing your Dapp directly, then you may direct your users to the for Dai and Vault redemption. Alternatively, you may create an interface that allows your users to claim a Dai equivalent in Collateral, or claim excess Collateral from Vaults in case of a system shutdown. Additionally, if there's a redeployment of the system, migrate Dai to the new redeployed system and/or claim excess Collateral from Vaults.
. Support channels include but are not limited to:
Seth is a simple, but powerful command line tool created to interface with the Ethereum blockchain. It is part of the toolset along with other tools for Ethereum. Its two main functionalities among others are performing calls (queries of the Ethereum blockchain - a “read” operation) and sending transactions (writing into the blockchain, changing its state). It also provides conversion between data of Ethereum’s own specific formats and the more widespread, usual data formats.
In the following section we will go through the installation and setup of Seth. These steps only work on Unix-based systems (i.e. Linux and macOS), however on Windows, you can try with an emulator, like or , the in Windows 10, a virtual machine or a container.
Seth can be installed as a part of the Dapp Tools suite, which is a collection of blockchain tools created with the Unix philosophy in mind. The most convenient way to do this, is to install Dapp Tools with the one line script provided on the . Here is how to do it:
If the above command does not work, or you had trouble installing it may be due to Mac OSX Mojave, as we have experienced various issues with the tools nix and cachix not working correctly on this OS, specifically due to a multi-user bug. If you happen to have more user accounts on your Mac, and experience errors running this guide, might help you resolve the issue. If this does not resolve the issue, you are more than welcome to ask for help on in the #help channel.
Configuring Seth can be done with environment variables or command line options. Environment variables can be generally used in two ways: you can save them in a configuration file named .sethrc in specific locations, like your home folder, or just set them only for the current terminal session. In this guide we will use environment variables with the latter approach for simplicity’s sake, however for ease-of-use in the future, we strongly encourage to save the variables in your project folder. Follow to do so.
If you decide to create a new account, an easy method is using the "create new wallet" option in MEW: . It is also possible to use Parity or Geth to create a new account or you can use an existing keystore file for a Parity or Geth account. You are also going to need to save the password of your keystore file in a plain text file (Never use this keystore file for real ETH - saving the password for your keystore file in plain text would be very unsafe for a real account! This also goes for the testnet account!).
You will need Kovan ETH for gas, you can get some by following the guide here:
Let’s send Kovan or private net ETH to an address. You can choose any valid address - in this example I am going to use :
$ seth send --value 0.1
Like any other Seth command, this command depends on Ethereum JSON RPC calls, which are part of the interface of any Ethereum client. You can dive into the corresponding documentation () to learn more about it.
$ seth estimate $COL1 'transfer(address, uint)'
$(seth --to-uint256 $(seth --to-wei 0.1 ether))
$ seth send $COL1 'transfer(address, uint)'
$(seth --to-uint256 $(seth --to-wei 0.1 ether))
This guide was written based on the official documentation in the Github repository of Seth. You can find additional information over there:
This project requires virtualenv to be installed if you want to use Maker's python tools. This helps with making sure that you are running the right version of python and checks that all of the pip packages that are installed in the are in the right place and have the right versions.
(for macs)
Read the following document for other known Ubuntu and macOS issues ().
Note: Since this example will be focused on getting a Market Maker Keeper set up on Kovan, you need to make certain that you have enough Kovan ETH (K-Eth) to get your Keeper up and running. To receive Kovan ETH, join the following Gitter Channel: and post your ETH address from your MetaMask account to the main chat. The Kovan faucet will then populate your wallet with the test funds (note that this could take a couple of minutes or a couple of hours as it is done manually by the channel’s administrator).
Here, we will be going over some example interactions using the described above. These examples assume that it is denominated in DAI and the price of 10 DAI is 1 ETH.
The data templating language that can be used for the configuration file.
Additionally, we have a Uniswap price feed that can be used by Market Maker Keepers: .
If you do not have an account, you can use on Kovan and export the account details (by means of the Keystore file method). Make sure that you download the .json file to your local machine as this is what you will need to set up the account.
We are here to help! We welcome any questions about market making in the channel in the Maker Chat.
The upcoming version of the Maker system, Multi-Collateral Dai, brings a lot of new and exciting features, such as support for new Vault collateral types and Dai Savings Rate. In order to support the new functionality, the whole Maker core of smart contracts has been rewritten. The new smart contracts addresses and ABIs can be found here:
Basic knowledge of the MakerDAO: Dai and/or Vault system.
If you hold your Sai in a wallet where you control your private keys, then head to (available at MCD launch) and follow the instructions to upgrade your Sai to Dai and optionally activate the Dai Savings Rate smart contract, which allows you to earn savings.
If your Sai is deposited in an exchange or centralized wallet or locked in a dApp smart contract, you can follow the instructions these platforms are providing or withdraw the Sai and complete the upgrade yourself at
As a SCD CDP owner you can move your CDP to the MCD CDP core through the Migration App at at launch. The following diagram shows the flow for CDP migration.
If you have a large SCD CDP, the migration contract might not have enough Sai liquidity to carry out the migration. In that case, feel free to contact for assistance. You can read more about migration in the section later in this guide.
If you have created your CDP through the Instadapp service, you need to withdraw ownership of the CDP from the service back to you. To do this, you need to navigate to the and click “Withdraw” on your CDP in the tab “Debt Positions”. This will give you custody of the CDP, which will make it visible at where you will be able to carry out CDP migration.
If you have created your CDP on MyEtherWallet then you can migrate your CDP using the Migration App at . (However, if the private key used with MyEtherWallet is stored in a local file or another unsupported format, you must first import your key to a wallet with Web3 support.)
Use the Migration App/contract to upgrade all user holdings of Sai to Dai. (See more details in the / sections below.)
Point codebase to new Dai token contract address. The new token is deployed at - use the for the new Dai token.
Inform users about , which allows Dai holders to earn savings.
Select a date between November 18-25 to list Multi-Collateral Dai. The new token is deployed at - use the for the new Dai token. Logo for Sai should remain the yellow diamond.
Optional: Link users to to activate Dai Savings Rate.
Select a future date between November 18-25 to execute the upgrade to support Multi-Collateral Dai, which should be listed as "Dai". The new token is deployed at - use the for the new Dai token. Logo for Sai should remain the yellow diamond.
Inform your users that they will be able to swap Sai for Dai at .
Optional: Link users to to activate Dai Savings Rate.
Get acquainted with the updates to Keepers and Auctions in MCD with .
Alternatively, if you’re willing to do some additional work and work with a lower level interface, you can interact with Auction contracts directly (, , ). Note that future collateral types may come with custom auction formats. More documentation will be available before launch.
Launch upgrade to your service that supports the new CDP core.
If you are using Dai.js for your CDP integration, see “” below for how to upgrade your implementation to MCD.
If you have integrated directly with the CDP smart contracts, see “” below for how to upgrade your implementation to MCD.
If you are using Dai.js for your CDP integration, see “” below for how to upgrade your implementation to MCD.
If you have integrated directly with the CDP smart contracts, see “” below for how to upgrade your implementation to MCD.
Option A: Point your users to at MCD launch date for CDP migration on their CDP dashboard. See also the Migration App section below.
If you have integrated CDPs using the , ensure you have updated the library to the latest version.
Update your codebase to support the functionality of the . At launch this plugin will be bundled into the Dai.js library as default.
Option A: Point users to if your app is Web3 compatible.
Get acquainted with the
Point codebase to the new
List the Multi-Collateral Dai token as "Dai". The new token is deployed at - use the for the new Dai token.
List the Multi-Collateral Dai token as "Dai". The new token is deployed at - use the for the new Dai token.
Point users to for Sai migration
List the Multi-Collateral Dai token as "Dai". The new token is deployed at - use the for the new Dai token.
Inform your users that they can migrate Sai to Dai at
Please reach out to and we are happy to discuss your migration scenario.
Upon release of MCD, the Migration App at will allow you to carry out Dai and CDP migration through an intuitive web UI in just a few clicks. By logging in with your favourite wallet solution, the app will scan your wallet for any recommended migrations and showcase them in the UI (seen in picture below). This migration scan feature is planned to be continually supported going forward, ensuring that users are always using an up-to-date version of the Maker platform.
The Migration App uses a proxy contract to carry out the CDP migration. Consequently, the app can also only be used for CDPs that have been created through a Maker proxy contract. This happens automatically if you have opened your CDP at .
The migration smart contracts are open source and can be found here:
In order to upgrade your Dai to MCD, you must use the function in the migration contract. First you must approve that the migration contract can transfer Sai tokens from your account. Then you are ready to invoke the swap by calling the function specifying the amount of Sai you want to upgrade to Dai. After the function call is mined, the upgraded Dai is sent to the Ethereum address initiating the upgrade. A detailed walk-through using CLI tools to carry out these functions can .
The migration contract also allows users to “go back” by swapping Dai for Sai, using the function . In this case, the CDP operation is reversed, as Dai is paid back to the system and Sai is released, just like the repayment of a normal CDP, except with no stability fee cost.
This function call is very similar to the former, except this time Dai is deposited to the CDP, and Sai collateral released. This requires you to approve that the migration contract can transfer Dai from your wallet, and then you invoke the swapDaiToSai function with the specified amount of Dai you want to redeem. You can check out for a more detailed look into how you call the functions.
The migration contract also allows users to migrate their CDPs from the SCD core to the MCD core. This is done through the function . The function essentially tries to close your CDP, using excess Sai deposited in the migration contract (by other users who have been upgrading Sai to Dai) to pay your outstanding CDP debt. In order to do so, you need to transfer the control of the CDP to the migration contract. The migration contract will then pay back the debt using Sai deposited in the contract, redeem the ETH collateral, create a new CDP in the MCD system, lock in the ETH collateral, and payback the debt using the generated Dai, resulting in an equivalent CDP debt in MCD.
The migration contract uses a proxy contract to carry out all the above steps in one go. Consequently, the contract can also only be used for CDPs that have been created through a Maker proxy contract. This happens automatically if you have opened your CDP at . Therefore, you must utilize the contract to carry out the .
To migrate your CDP, your are also dependant on excess liquidity of Sai in the migration contract to be used to close your CDP. If you have a 10,000 Sai debt CDP you want to migrate, there must be at least 10,000 Sai deposited in the Sai MCD CDP owned by the migration contract (from users upgrading Sai to Dai), to carry out the CDP migration. The migration cannot be carried out partially, why the whole debt of the CDP must be covered by Sai liquidity in the contract to carry out the migration. If you have a large CDP and are concerned about migration, feel free to reach out to the Integrations Team at
Contact integrations team -
Learn about our progress towards the launch of .
In the context of running a keeper (more info ) to perform bids within an auction, a primary failure mode would occur when a keeper specifies an unprofitable price for the collateral.
For the full details about this risk, reference @livnev's Paper .
Note: this process is not atomic if a token allowance needs to be set, so it's possible for one of the transactions to succeed but not both. See for executing multiple transactions atomically. Also, can be called instead if you do not want the allowance to be set first automatically.
Requires one of the exchange to be in use.
Returns: promise (resolves to once mined)
Returns: promise (resolves to once mined)
getTargetPrice()
returns the target price of Sai in USD, that is, the value to which Sai is soft-pegged, which historically has been 1. It returns a USD_SAI
.
Install the to see this functionality in action.
A Keeper to facilitate Emergency Shutdown
Processing Period - Next, Vault owners interact with End to settle their Vault and withdraw excess collateral. Auctions are left to conclude or are yanked before Dai redemption.
Dai Redemption - After the processing period duration End.wait
has elapsed, Vault settlement and all Dai generating processes (auctions) are assumed to have concluded. At this point, Dai holders can begin to claim a proportional amount of each collateral type at a fixed rate.
To prevent a race-condition for Dai holders during Step 3, it's imperative that any Vaults having a collateralization ratio of less than 100% at Step 1 must be processed during Step 2. The owner of an underwater Vault would not receive excess collateral, so they lack an incentive to skim
their position in the End
contract. Thus, it is the responsibility of a MakerDAO Stakeholder (MKR holders, large Dai holders, etc) to ensure the system facilitates a Dai redemption phase without a time variable. The cage-keeper
is a tool to help stakeholders carry out this responsibility.
To be consistent with the Protocol's technical terminology for the rest of this description:
urn
= Vault
ilk
= Collateral Type
The cage-keeper
directly interacts with the End
, Flopper
and Flapper
contracts.
The central goal of the cage-keeper
is to process all under-collateralized urns
. This accounting step is performed within End.skim()
, and since it is surrounded by other required/important steps in the Emergency Shutdown, a first iteration of this keeper will help to call most of the other public function calls within the End
contract.
As can be seen in the above flowchart, the keeper checks if the system has been caged before attempting to skim
all underwater urns and skip
all flip auctions. After the processing period has been facilitated and the End.wait
wait time has been reached, it will transition the system into the Dai redemption phase of Emergency Shutdown by calling End.thaw()
and End.flow()
. This first iteration of this keeper is naive, as it assumes it's the only keeper and attempts to account for all urns, ilks, and auctions. Because of this, it's important that the keeper's address has enough ETH to cover the gas costs involved with sending numerous transactions. Any transaction that attempts to call a function that's already been invoked by another Keeper/user would simply fail.
This keeper can either run continuously on a local/virtual machine or be run when the operator becomes aware of Emergency Shutdown. A sample startup script is shown below. The keeper's Ethereum address should have enough ETH to cover gas costs and is a function of the protocol's state at the time of shutdown (i.e. more urns to skim
means more required ETH to cover gas costs). When new collateral types are added to the protocol, the operator should pull the latest version of the keeper, which would include contracts associated with the aforementioned collateral types.
After the cage-keeper
facilitates the processing period, it can be turned off until End.wait
is nearly reached. Then, at that point, the operator would pass in the --previous-cage
argument during keeper start in order to bypass the feature that supports the processing period.
This project uses Python 3.6.2.
In order to clone the project and install required third-party packages please execute:
Make a run-cage-keeper.sh to easily spin up the cage-keeper.
Prerequisites:
In order to be able to run tests, please install development dependencies first by executing:
You can then run all tests with:
By reading this guide, you’ll gain:
Better understanding of Arbitrage, how it is achieved and the role it plays in financial markets
An overview of the simple arbitrage keeper and common structure across the Maker Keeper framework
Insight into the operation of the simple arbitrage keeper and ways to improve its code
At least Python 3.6.2 and some python experience
Linux, macOS, or Cygwin (on Windows)
Some general experience with Ethereum Development (account management, contract interaction, node hosting)
Arbitrage is the process of simultaneously purchasing an asset on one exchange and selling a similar, if not identical, asset on another exchange at a higher price. Conceptually, this trading strategy can be broken down into two legs, the first buying leg and the second selling leg. As the timestamp of these legs converge, the risk of asset exposure approaches zero. For example, if both trades are not executed in perfect synchrony, there is a chance that the second trade is completed by another actor, forcing the arbitrageur to hold an asset until another opportunity arises.
Operation of the Simple Arbitrage Keeper
The step for each process in simple-arbitrage-keeper is as follows:
Keeper Entry -> enter into SimpleArbitrageKeeper class
Startup -> make all necessary approvals between tokens, exchanges, and tx-manager
Evaluate Market -> pull price data from Oasis REST API and Uniswap exchange contracts
Profitable Opportunity Found? ->check if profit opportunity exists with a given max engagement and min profit
Execute multi-step trade -> with use of TxManager, buy asset on exchange A and sell on exchange B within one Ethereum transaction
To operate the keeper, call the /bin/simple-arbitrage-keeper
script with the required arguments. This adds the pyexchange and pymaker modules to the python path and initializes the SimpleArbitrageKeeper class. It's convenient to write a shell script that can be easily rerun after argument changes, but before we begin, let's prepare our Keeper ethereum address, deploy our TxManager, find the Uniswap exchange addresses and MatchingMarket address.
Keeper Installation
This project uses Python 3.6.2. In order to clone the project and install required third-party packages please execute:
Install the DappHub Toolkit
If you are running Linux or macOS you can take advantage of our all in one installer.
Keeper Ethereum Address
Deploy TxManager
The output of dapp create
is the address to your TxManager
Deploy/Find a parity node
Uniswap Exchange Addresses
Shell Script
As was mentioned before, let's make a shell script so that it's easy to run this Bot. For example, we're using Kovan Sai for the entry-token since we won't be exposed to the arb token's price exposure in between trades. Moreover, for kovan testing, we will be using the WETH/DAI pair, since liquidity is thin across the Kovan versions of MatchingMarket and Uniswap exchanges. Any NoneType
errors are likely due to a lack of liquidity on either exchange and can be resolved once liquidity is added. As mentioned earlier, the min-profit and max-engagement arguments define how the keeper will operate. Maximum engagement refers to the amount of entry-tokens you would like to sell within the first leg of the trade, whereas minimum profit refers to the amount of profit gained from executing the arbitrage; both arguments are denominated in entry-tokens and in units of ether (in the below script, 1 SAI of min-profit would be 1 SAI). For example, the below script will trade with a max-engagement of 10 SAI, but only if the returned value from the trade results in at least 11 SAI.
Create a file called like below, insert the arguments relevant to your environment, make it executable, then run it!
In your run-simple-keeper-kovan.sh file:
Make your Keeper executable, and run it!
Initial Startup
During the initial startup, several approval transactions will be sent to the tx-manager, and relevant exchanges. When complete, you'll see a “Watching for new blocks” message that indicates that the bot is now looping through the lifecycle.process_block()
method; following that message and with every new block is a "Best Trade regardless of profit" message that acts as a "heartbeat" to the Keeper.
Nominal Operation
After the initial approval phase the Keeper's startup will look something like the below output and will continue to print "Best Trade ... " messages with every new block witnessed. When the keeper publishes a transaction, all relevant info will be logged and printed to the console. Finally, when you’d like to shut down the keeper, simply click CTRL-C, and it will gracefully shutdown.
During keeper operation, if the following error is shown, just CTRL-C and rerun:
Here are some suggestions to improve the usability, versatility, and profitability of this keeper. We hope that everyone tinkers with the parameters, runs the keeper for their own benefit, and eventually upgrades the bot to meet their risk tolerance and profit appetite.
Develop unit tests for each method
Use the Uniswap Factory contract to query the uniswap-entry-exchange and uniswap-arb-exchange addresses rather than requiring it as an argument
Increase scope
Monitor more than one pair
Monitor more than two decentralized exchanges
Implement support for centralized exchanges
Increase number of intermediary arb tokens
(e.x. DAI → WETH → BAT → DAI)
Improve efficiency of TxManager
Do we really need to send all of our token balance to the contract during every atomic transaction?
Read the state of the MatchingMarket (OasisDex) contract rather than using the Oasis REST API
Send trade updates through a text/email Python API
Welcome to the MCD CLI
Then install the mcd
package:
Similar to Seth, mcd
also supports transaction signing with Ledger hardware wallets and can run against both local and remote nodes.
Since mcd
will always be used against a known deployment of the system, defaults can be loaded wherever possible. In most cases the only required configuration parameter is the -C, --chain=<chain>
(MCD_CHAIN
) option and -F, --from=<address>
(ETH_FROM
) sender account when not using a testnet.
Example ~/.sethrc
:
Kovan
Run against the latest Kovan deployment by setting the -C, --chain
option to kovan
. Specify a sender account when sending transactions using the -F, --from
option, or via the ETH_FROM
env variable.
Remote testchain
Run agaist remote testchain deployments by setting the -C, --chain
option to the remote testchain Id. Mcd will auto-configure account settings via the testchain api so that no further configuration is required. To view a list of available testchains run:
Then set the chain option, or the chain env variable to the appropriate testchain Id.
Local testnet
By default, Mcd assumes that the output of the testchain deployment script is available at ~/.dapp/testnet/8545/config/addresses.json
. Configuration addresses can be loaded from a different location by setting the --config
(MCD_CONFIG
) option.
Ilks are collateral types with corresponding risk parameters which have been approved by system governance. Use the ilks
command to view the list off available Ilks.
Each Ilk has its own set of configuration parameters which can be viewed via the ilk
command. The I, --ilk=<id>
option is used to scope commands to a particular Ilk:
Individial ilk values can be retrieved by adding the parameter name as an argument to the ilk
command:
Gems are collateral tokens. Collateral is added and removed from the via adapters, which abstract away the differences between various token behaviours. Use gem [<subcommand>]
to manage collateral balances for any given Ilk.
The join
command can add collateral from the sender account to any specified Urn. The exit
command can remove collateral from a specified Urn, provided that the sender controls the private key associated with that Urn.
By default, ETH_FROM
is used to determine which Urn should be credited with collateral. Use U, --urn=<address>
to optionally credit an Urn other than the default.
The exit
command can remove collateral from a specified Urn, provided that the sender controls the private key associated with that Urn. The exit
command can also withdraw collateral to an account other than ETH_FROM
buy passing the destination address as an additional argument:
Urns represent Cdp state for any given Urn address.
Use the urn
command to view Urn state for any given Ilk:
By default, ETH_FROM
is used to determine which Urn to query. Use the U, --urn=<address>
option to query Urns at other indexes.
Urn management
Urn state (urn.ink
and urn.art
) is managed via the frob <dink> <dart>
command, where dink
and dart
are delta amounts by which ink
(Locked collateral) and art
(Outstanding debt) should be changed. For example, to lock 100 WETH and draw 400 Dai on the ETH-A Ilk:
To reduce outstanding debt by 200 Dai whilst keeping the amount of locked collateral constant:
Similar to Gem adapters, a Dai adapter is used to exchange Vat Dai for ERC20 token Dai which can then be used outside the system. Use dai [<subcommand>]
to manage dai balances.
Once Dai has been drawn on an Urn, it can be withdrawn for use outside the system using dai exit
. Dai can be returned to repay Urn debt via dai join
.
The dai balance
command displays the internal system (vat) balance and the external (ext) token balance:
Individial balance values can be retrieved by adding vat
or ext
as an argument to the balance
command:
Note: examples assume that ETH_FROM
is set to an address controlled by the user, and that the MCD_CHAIN
env variable has been set to a vaild chain identifier.
Note: The system doesn't handle ETH directly but instead uses WETH to represent ETH collateral. For convenience, the wrap
and unwrap
commands are provided for exchanging ETH to WETH and visa versa.
The cage-keeper
is used to help facilitate of the . Emergency shutdown is an involved, deterministic process, requiring interaction from all user types: Vault owners, Dai holders, Redemption keepers, MKR governors, and other Maker Protocol Stakeholders. A high level overview is as follows:
System Caged - The Emergency Security Module calls End.cage()
function, which freezes the USD price for each collateral type as well as many parts of the system.
The following section assumes familiarity with Emergency Shutdown. Good places to start is the Emergency Shutdown Module in Section 3 and Section 4 of the as well as a more thorough, . Functions mentioned are from the implementation contained by the End
contract, which is .
For some known Ubuntu and macOS issues see the README.
Download
This project uses for unit testing. Testing of Multi-collateral Dai is performed on a Dockerized local testchain included in tests\config
.
See file
If you have questions regarding Cage Keepers, please reach out to us on the channel on .
Typically in the form of an automated bot, Keepers are external actors that participate in , , , and system upkeep within the Maker protocol and the greater Ethereum ecosystem. Under a pure economic assumption, they are solely incentivized by profits or a vested interest. However, their activity provides indirect services such as increased liquidity and price consistency across various financial markets. This guide presents a simple arbitrage keeper, and structure thereof, that can be used out of the box to participate in wildly volatile markets. The purpose of the guide is to motivate the user in improving its functionality to participate in less volatile markets, leading to a more efficient financial ecosystem.
With advancement in technology, this trading strategy is typically automated and can detect, as well as, profit from price deviations in a matter or seconds. Arbitrage opportunities exist because of , such as a temporary shortage of liquidity. As a result, arbitrage provides a mechanism to ensure prices on one exchange/market do not deviate far from the .
The is an arbitrage keeper for OasisDex and Uniswap. Given a minimum profit per trade and maximum trade size, as defined by the user and in units of entry_token
, the Keeper will scan for arbitrage opportunities. When running, it monitors the arb_token/entry_token
price on both exchanges, and when a discrepancy is detected, it executes an atomic multi-step trade in a single Ethereum transaction. The multi-step trade is made up of two legs: the first leg is the purchasing of arb_token
with the entry_token
on the start_exchange
, while the second leg is the selling of the arb_token
with the entry_token
on the end_exchange
. The type of entry_token
and arb_token
is typically of a stable and volatile type, respectively; since arbitrage opportunities can be scarce, the Keeper sometimes rests in a default state, holding the entry_token
for extended periods of time as it looks for new opportunities. Exposure only to a stable token is especially important in this default state. If any leg of the trade fails, the entire trade reverts, thereby upholding the "risk-free" attribute of true arbitrage.
It inherits a system structure that is consistent with other keepers in the Maker Keeper Framework. Apart from libraries typical to python projects, it relies on and , both of which are highly applicable Python APIs for the Maker Protocol Contracts and cryptocurrency exchanges, respectively. As can be seen in the flowchart, the ‘Lifecycle box’ is a that helps define the proper life cycle of a keeper; it consists of keeper startup, timers and/or subscriptions to Web3 events, and a keeper shutdown phase. The ‘Process block’ box is executed when the Keeper's node receives a new block. It generally contains the logic to query the blockchain state, evaluate any profit opportunities, and when applicable, publish a transaction.
General high level information and comments for each class/method are in the .
The usage of the bot can be found in the of the README. Here are some preparation steps in running on Kovan:
For some known Ubuntu and macOS issues see the .
If you're having issues with installation, head over to the
Your Keeper will publish transactions through this address. Your ethereum address needs to be accessible by , in order to deploy TxManager through . Furthermore, when the Keeper is deployed and in operation, this address must contain some ETH for transaction costs as well as an amount of entry-tokens equal to the max-engagement set by the User. Follow to ensure seth can see your keystore and passphrase files.
If you'd like to start from scratch, and use geth account new
, to create a new account, set a passphrase, and place the passphrase into a <address>.txt
file in the ~/Library/Ethereum/passphrase/
directory; the corresponding keystore file can be found in the ~/Library/Ethereum/keystore/
directory. Both the keystore and passphrase files will later be passed into the Keeper as an argument. Make a note of your Keeper Ethereum Address, its keystore file location and passphrase file location.
A must be deployed and linked with the Keeper ethereum address in order to bundle multiple contract calls in a single ethereum transaction; this is what we call an atomic transaction, where the entire call set is successful, or the state remains unaffected. Once the DappHub Toolkit is installed, execute the following commands to deploy TxManager:
You need access to a JSON-RPC host that’s connected to a parity node. Unfortunately, Infura APIs are not connected to parity nodes, so you will need to find access to a remote endpoint (one example is ) or run a node on your local machine. An example of a remote node could be
You'll need to find the address to the Uniswap exchange corresponding to the entry-token as well as arb-token. To do that, call the getExchange(TokenAddress)
at the Uniswap Factory contract to query the exchange addresses. . For example, you can use the Kovan WETH token address and call getExchange(0xd0A1E359811322d97991E03f863a0C30C2cF029C)
Rather than the default gas price implement a
To save gas, and by ID rather than use MatchingMarket.offer(...)
and the on-chain matching engine
First install :
Mcd is built on and uses the same network configuration options, which just like Seth, can be defined in the ~/sethrc
initialisation file.
Run against a locally running instance of where the system has been deployed by setting the C, --chain
option to testnet
. Mcd will auto-configure account testings for dapp testnet
so that no further configuration is required.
The cdp
command provides compatability with CDPs managed via the CDP Portal and uses the same proxy contract and font-end. This allows CDPs to be managed via a unique integer identifier rather than the I, --ilk
and U, --urn
options.
The Maker Protocol's Liquidation Agent
Contract Name: cat.sol
Type/Category: DSS
The Cat
is the system's liquidation agent, it enables keepers to mark positions as unsafe and sends them to auction.
mul(uint, uint)
/rmul(uint, uint)
- will revert on overflow or underflow
bite(bytes32, address)
will revert if lot
or art
are larger than or equal to 2^255.
bite
will not leave a Vault with debt and no collateral.
wards
are allowed to call protected functions (Administration and cage()
)
ilks
stores Ilk
structs
Ilk
is the struct with the address of the collateral auction contract (flip
), the penalty for that collateral to be liquidated (chop
) and the maximum size of collateral that can be auctioned at once (dunk
).
live
must be 1
for the Cat
to bite
. (see cage
in mechanisms)
box
the limit on the debt and penalty fees available for auction. [RAD]
dunk
("debt chunk") amount of debt plus penalty fee per auction, in Dai. [RAD]
The values of all parameters here (except vat
) are changeable by an address that is rely
ed on. For instance, the End
module should be auth
ed to allow for it to call cage()
and update live
from 1 to 0. Governance (through an auth
ed address) should be able to add collateral types to Cat
, update their parameters, and change the vow
.
bite
can be called at anytime but will only succeed if the Vault is unsafe. A Vault is unsafe when its locked collateral (ink
) times its collateral's liquidation price (spot
) is less than its debt (art
times the fee for the collateral rate
). Liquidation price is the oracle-reported price scaled by the collateral's liquidation ratio.
Bite
: emitted when a bite(bytes32, address)
is successfully executed. Contains:
ilk
: Collateral
urn
: Vault address
ink
: see lot
in bite
art
: see art
in bite
tab
: see tab
in bite
flip
: address of the auction contract
id
: ID of the auction in the Flipper
auth
Once live=0
it cannot be set back to 1.
bytes32 ilk
parameter represents the collateral type that is being bitten.
address urn
the address that identifies the Vault being bitten.
returns uint id
which is the ID of the new auction in the Flipper
.
bite
checks if the Vault is in an unsafe position and if it is, it starts a Flip auction for a piece of the collateral to cover a share of the debt.
The following is a line-by-line explanation of what bite
does.
Various file function signatures for administering Cat
:
Setting new vow (file(bytes32, address)
)
Setting new collateral (file(bytes32, bytes32, address)
)
Setting penalty or dunk size for collateral (file(bytes32, bytes32, uint)
)
The primary usage will be for keepers to call bite
on a Vault they believe to be unsafe in order to start the auction process.
When the Cat
is upgraded, there are multiple references to it that must be updated at the same time (End
, Vat.rely
, Vow.rely
). It must also rely on the End
, the system's pause.proxy()
A Vat
upgrade will require a new Cat
The Cat stores each Ilk
's liquidation penalty and maximum auction size.
Each ilk will be initiated with the file
for their Flipper
; however, auctions cannot start until file
is also called to set the chop
and the dunk
. Without these auctions for either 0 gems or 0 dai would be created by calling bite
on an unsafe Vault.
bite
needs to be called n
times where n = urn.ink / ilks[ilk].dunk
if n > 1
. This allows for the possibility that the Vault becomes safe between bite
calls either through increased collateral (in value or quantity), or decreased debt.
Calling bite
returns the auction id
and emits and event with the id
. This is required to bid in the Flipper
on the auction.
A bug in the Cat
could lead to loss (or locking) of Dai and Collateral by assigning it to an address that cannot recover it (i.e. a bad Vow address or an incorrectly programmed Flipper). The main coding failure mode of Cat
is if it has a bug that causes auctions to cease to function. This would require upgrading the system to a corrected Cat
contract. If there is a bug in Cat
that reverts on cage
then it would cause Shutdown could fail (until a correct Cat
is launched).
The Cat
relies indirectly on the price Feeds as it looks to the Vat
's tracking of the collateral prices (spot
) to determine Vault safety. If this system breaks down, it could lead to theft of collateral (too low spot
) or unbacked Dai (incorrectly high spot
).
Governance can authorize and configure new collaterals for Cat
. This could lead to misconfiguration or inefficiencies in the system. Misconfiguration could cause Cat
not to operate properly or at all. For instance, if an Ilk.dunk
is set to be greater than 2**255 could allow for very, very large Vaults to be un-bite
-able.
The Cat
relies on an external Flipper contract to run the auction and moves the collateral from the Cat
to the Flipper
contracts in the Vat
. A faulty collateral auction contract could result in the loss of collateral or dai or non-functioning auctions.
vat
address that conforms to a VatLike
interface (see for more info). It is set during the constructor and cannot be changed.
vow
address that conforms to a VowLike
interface (see for more info).
sets live
to 0 (prevents bite
). See for further description.
Inefficiencies in the dunk
or chop
could affect auctions. For instance, a dunk
that is too large or too small could lead to disincentives for keepers to participate in auctions. A chop
that is too small would not sufficiently dis-incentivize risky Vaults and too large could lead to it being converted to bad debt. Further discussion of how the parameters could lead to system attacks is described in this .