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...
Loading...
Getting Started with Maker Protocol
The Maker Protocol is the platform through which anyone, anywhere can generate the Dai stablecoin against crypto collateral assets. Learn how it works.
Introductory @ MakerDAO.world
@ manual.makerdao.com
@ docs.makerdao.com ← You are here
@ collateral.makerdao.com
@ mips.makerdao.com
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)
To open a Vault, head to
To use the DSR, head to Oasis Earn
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
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
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 authed 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.
When the Cat is upgraded, there are multiple references to it that must be updated at the same time (End, Vat.rely
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.
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.
Clipper Contract - see
Join - adapters that are used to deposit/withdraw unlocked collateral into the Vat. Join contains three smart contracts:
GemJoin
ETHJoin
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 Maker Protocol's liaison between the Oracles and Core Contracts
Contract Name: spot.sol
Type/Category: DSS —> Core Module
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.
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.
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.
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.
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.
ilkpokeexternalpeekfileVow.relyEndpause.proxy()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.
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.
const service = Maker.create('test' {
smartContract: {
addressOverrides: {
PROXY_REGISTRY: '0xYourAddress'
}
}
});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.
USD_ETH price unit.Get the current USD price of the governance token MKR, as a USD_MKR price unit.
Get the current USD price of PETH (pooled ethereum), as a USD_PETH price unit.
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.
const service = maker.service('cdp');const ratio = await service.getLiquidationRatio();const penalty = await service.getLiquidationPenalty();const fee = await service.getAnnualGovernanceFee();const price = maker.service('price');const ethPrice = await price.getEthPrice();const mkrPrice = await price.getMkrPrice();await pethPrice = price.getPethPrice();await price.setEthPrice(475);await price.setMkrPrice(950.00);await price.getWethToPethRatio();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:
peek calls the OSM 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.
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 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
(referenced in Join - Detailed Documentation)
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.
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.
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.
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
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
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 caged by an authed 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
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 Chainlog provides valuable information for developers interacting with Maker Protocol deployments. This includes deployments on the following networks:
Ethereum Mainnet (latest release)
Ethereum Goerli Testnet ()
Each release includes the following information:
Contract addresses with link to Etherscan
ABIs
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
Use the mcd:systemData service to look up system-wide parameters. In the code, this is called SystemDataService.
const service = maker.service('mcd:systemData');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.
Dai.js supports plugins, which allow a developer to add functionality (hardware wallet support, exchange support, etc.) for specific needs without increasing the size and dependency list of the core library.
Trezor Plugin 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 .
Use the 'mcd:savings' service to work with the Dai Savings Rate system. In the code, this is called SavingsService.
All the methods below are asynchronous. join, exit, and exitAll use a proxy contract.
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 . Strictly speaking, this method returns the amount of Dai owned by the proxy contract for the current address. to work with the methods above.
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.
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.
For more information about the Shutdown of the Maker Protocol, read the below End - Detailed Documentation as well as the
To access system status information, retrieve the ETH CDP Service through Maker.service('cdp').
const service = 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)
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 .
Ensuring the Security of the Maker Protocol and Multi Collateral Dai
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.
Note that the confirmed event will not fire unless transactionManager.confirm
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
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:
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:
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.
Requires one of the exchange to be in use.
const service = maker.service('mcd:savings');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.
dai - the address of the dai token
one - a 10^27 uint used for math in DaiJoin
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.
MCD Bug Bounty Announcement and Security Roadmap Update July 24, 2019
MCD Security Roadmap Update October 23, 2019
Publication of the Runtime Verification Audit December 19, 2019

const base = await service.getAnnualBaseRate();const line = await service.getSystemWideDebtCeiling();const systemRatio = await service.getSystemCollateralization();confirmedBlockCountThere are functions such as lockEth() which are composed of several internal transactions. These can be more accurately tracked by accessing tx.metadatain 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
defaultYou 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:
Install the multiple accounts demo app to see this functionality in action.
toNumber: return the raw JavaScript value. This may fail for very large numbers.
toBigNumber: return the raw value as a BigNumber.
isEqual: compare the values and symbols of two different instances.
toString: show the value in human-readable form, e.g. "500 USD/ETH".
If you would like to use these helper classes outside of Dai.js, check out @makerdao/currency.
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.
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 DsProxy for executing multiple transactions atomically. Also, peth.join can be called instead if you do not want the allowance to be set first automatically.
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.
const dead = await service.isGlobalSettlementInvoked();await service.join(DAI(1000));const targetPrice = await service.getTargetPrice();const txMgr = maker.service('transactionManager');
// instance of transactionManager
const open = maker.service('cdp').openCdp();
// open is a promise--note the absence of `await`txMgr.listen(open, {
pending: tx => {
// do something when tx is pending
},
mined: tx => {
// do something when tx is mined
},
confirmed: tx => {
// do something when tx is confirmed
},
error: tx => {
// do someting when tx fails
}
});
await txMgr.confirm(open);
// 'confirmed' callback will fire after 5 blocksawait txMgr.confirm(open, 3);const lock = cdp.lockEth(1);
txMgr.listen(lock, {
pending: tx => {
const {contract, method} = tx.metadata;
if(contract === 'WETH' && method === 'deposit') {
console.log(tx.hash); // print hash for WETH.deposit
}
}
})const maker = await Maker.create({
url: 'http://localhost:2000',
accounts: {
other: {type: privateKey, key: someOtherKey},
default: {type: privateKey, key: myKey}
}
});
await maker.addAccount('yetAnother', {type: privateKey, key: thirdKey});
const cdp1 = await maker.openCdp(); // owned by "default"
maker.useAccount('other');
const cdp2 = await maker.openCdp(); // owned by "other"
maker.useAccount('yetAnother');
const cdp3 = await maker.openCdp(); // owned by "yetAnother"
await maker.addAccount({type: privateKey, key: fourthAccount.key}); // the name argument is optional
maker.useAccountWithAddress(fourthAccount.address);
const cdp4 = await maker.openCdp(); //owned by the fourth account> maker.currentAccount()
{ name: 'other', type: 'privateKey', address: '0xfff...' }
> maker.currentAddress()
'0xfff...'const maker = await Maker.create({
url: 'http://localhost:2000',
accounts: {
// this will be the first account from the provider at
// localhost:2000
first: {type: 'provider'},
// this will be the current account in MetaMask, and it
// will send its transactions through MetaMask
second: {type: 'browser'},
// this account will send its transactions through the
// provider at localhost:2000
third: {type: 'privateKey', key: myPrivateKey}
}
})import TrezorPlugin from '@makerdao/dai-plugin-trezor-web';
import LedgerPlugin from '@makerdao/dai-plugin-ledger-web';
const maker = await Maker.create({
plugins: [
TrezorPlugin,
LedgerPlugin,
],
accounts: {
// default derivation path is "44'/60'/0'/0/0"
myTrezor: {type: 'trezor', path: derivationPath1},
myLedger: {type: 'ledger', path: derivationPath2}
}
});import Maker from '@makerdao/dai';
// Multi-Collateral Dai
import { ETH, BAT, DAI } from '@makerdao/dai-plugin-mcd';
const maker = await Maker.create(...);
const mgr = maker.service('mcd:cdpManager');
// lock BAT into a new vault and draw Dai
const vault = await mgr.openLockAndDraw(
'BAT-A',
BAT(100),
DAI(100)
);
// Single-Collateral Sai
const {
MKR,
SAI,
ETH,
WETH,
PETH,
USD_ETH,
USD_MKR,
USD_SAI
} = Maker;
// These are all identical:
// each method has a default type
cdp.lockEth(0.25);
cdp.lockEth('0.25');
// you can pass in a currency unit instance
cdp.lockEth(ETH(0.25));
// currency units have convenient converter methods
cdp.lockEth(ETH.wei(250000000000000000));
const eth = ETH(5);
eth.toString() == '5.00 ETH';
const price = USD_ETH(500);
price.toString() == '500.00 USD/ETH';
// multiplication handles units
const usd = eth.times(price);
usd.toString() == '2500.00 USD';
// division does too
const eth2 = usd.div(eth);
eth2.isEqual(eth);const conversionService = maker.service('conversion');return await conversionService.convertEthToWeth(ETH(10));return await conversionService.convertWethToPeth(WETH(10));return await conversionService.convertEthToPeth(ETH(10));return await conversionService.convertconvertWethToEth(WETH(10));return await conversionService.convertPethToWeth(PETH(10));return await conversionService.convertPethToEth(PETH(10));ilkdai - 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.
vatCageburnVat.daiDaiJoinVatDai.totalSupplyVat.dai(DaiJoin)DaiJoincageauthexitSell 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.
Returns: promise (resolves to once mined)
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.
Returns: promise (resolves to once mined)
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
Use the 'mcd:cdpType' service to look up parameters for different collateral types in the system. In the code, this is called CdpTypeService.
This is a list of , initialized during Maker.create.
Return the for the specified currency and/or .
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 USD price of this collateral type's token, using recent price feed data, as a . (See "A note on caching" above).
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).
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).
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:
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)
allowance returns a representing the token allowance.
Params: none
Returns: promise (resolves balance of current account)
balance returns a representing the token balance of the current account
Params: address to check
Returns: promise (resolves balance of address)
balanceOf returns a representing the token balance of the supplied account.
Params: none
Returns: promise (resolves total supply of token)
totalSupply returns a representing the total token supply
Params:
spender - address of token spender
amount - amount of token to allow
Returns: promise (resolves to once mined)
approve approves the spending address to spend up to amount of msg.sender's tokens.
Params: address of token spender
Returns: promise (resolves to once mined)
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
Returns: promise (resolves to once mined)
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
Returns: promise (resolves to once mined)
deposit converts amount of Eth to amount of Weth.
Params: amount of Weth to withdraw
Returns: promise (resolves to once mined)
withdraw converts amount of Weth to amount of Eth.
Params: amount of Weth to join
Returns: promise (resolves to once mined)
join converts amount of Weth to Peth, at the .
Params: amount of Peth to exit
Returns: promise (resolves to once mined)
withdraw converts amount of Peth to Weth, at the .
The Maker Protocol's Oracles
Module Name: Oracle Module
Type/Category: Oracles —> OSM.sol & Median.sol
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).
It can depend on other services through our built-in , and can also be configured through the Maker config file / config options.
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:
Allowing MKR users to vote with a hot or cold wallet using a proxy voting identity
Contract Name: VoteProxy.sol
Type/Category: Proxy Module
const exchange = maker.service('exchange');// Sell 100.00 DAI for 0.30 WETH or more.
const sellOrder = await exchange.sellDai('100.0', 'WETH', '0.30');// Buy 100.00 DAI for 0.30 WETH or less.
const buyOrder = await exchange.buyDai('100.0', 'WETH', '0.35');const buyOrder = await exchange.buyDai('100.0', 'WETH', '0.35');
const fillAmount = buyOrder.fillAmount();
const gasPaid = buyOrder.fees();
const created = buyOrder.created();const service = maker.service('mcd:cdpType');const tokenService = maker.service('token');
const sai = tokenService.getToken('SAI');
const weth = tokenService.getToken('WETH');
const peth = tokenService.getToken('PETH');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 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 here.
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:
Install the chrome extension from the web store.
Follow instructions to setup a bookmarklet
Append https://via.hypothes.is to a URL.
MakerDAO restricted group- everyone is allowed to read annotations but only approved members can contribute here.
Public MakerDAO group that is open for everyone to both read and contribute.

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.
Once you have an instance of a CDP, you can use CDP instance methods to read its state and perform actions.
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 }
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.
service.cdpTypes.forEach(type => console.log(type.ilk));
// ETH-A
// BAT-A
// USDC-A// this will error if more than one type is defined for ETH
const type = service.getCdpType(ETH);
// disambiguate using the ilk name string:
const ethA = service.getCdpType(null, 'ETH-A');console.log(type.price.toString()); // "9000.01 ETH/USD"vault.reset();
await vault.prefetch();service.resetAllCdpTypes();
await service.prefetchAllCdpTypes();const allowance = await dai.allowance('0x...owner', '0x...spender');const balance = await dai.balance();const balanceOf = await dai.balanceOf('0x...f00');const totalSupply = await dai.totalSupply();return await dai.approve('0x...f00', DAI(10));return await dai.approveUnlimited('0x...f00');return await dai.transfer('0x...f00', DAI(10));return await dai.transferFrom('0x...fr0m', '0x...t0', DAI(10));return await weth.deposit(ETH(10));return await weth.withdraw(WETH(10));return await peth.join(WETH(10));return await peth.exit(PETH(10));import { ScdPlugin } from '@makerdao/dai-plugin-scd';
// or
const { ScdPlugin } = require('@makerdao/dai-plugin-scd');import Maker from '@makerdao/dai';
import { ScdPlugin } from '@makerdao/dai-plugin-scd';
async function openLockDraw() {
const maker = await Maker.create("http", {
plugins: [ScdPlugin],
privateKey: YOUR_PRIVATE_KEY,
url: 'https://kovan.infura.io/v3/YOUR_INFURA_PROJECT_ID'
});
await maker.authenticate();
const cdpService = await maker.service('cdp');
const cdp = await cdpService.openCdp();
await cdp.lockEth(0.25);
await cdp.drawSai(50);
const debt = await cdp.getDebtValue();
console.log(debt.toString); // '50.00 SAI'
}
openLockDraw();const cdpService = await maker.service('cdp');
const newCdp = await cdpService.openCdp();const cdpService = await maker.service('cdp');
const cdp = await cdpService.getCdp(614);{
type: <event_type>,
payload: <event_payload>, /* if applicable */
index: <event_sequence_number>,
block: <latest_block_when_emitted>
}maker.on('price/ETH_USD', eventObj => {
const { price } = eventObj.payload;
console.log('ETH price changed to', price);
})maker.on('web3/AUTHENTICATED', eventObj => {
const { account } = eventObj.payload;
console.log('web3 authenticated with account', account);
})cdp.on('DEBT', eventObj => {
const { dai } = eventObj.payload;
console.log('Your cdp now has a dai debt of', dai);
})//example code in ExampleService.js for steps 3-6
import PublicService from '../core/PublicService';
export default class ExampleService extends PublicService {
constructor (name='example') {
super(name, ['log']);
}
test(){
this.get('log').info('test');
}//example code in ExampleService.spec.js for step 8
import Maker from '../../src/index';
//step 8: a new service role ('example') is used
test('test 1', async () => {
const maker = await Maker.create('http', {example: "ExampleService"});
const exampleService = customMaker.service('example');
exampleService.test(); //logs "test"
});
//step 8: a custom service replaces a default service (Web3)
test('test 2', async () => {
const maker = await Maker.create('http', {web3: "MyCustomWeb3Service"});
const mycustomWeb3Service = maker.service('web3');
});//step 10: in ExampleService.spec.js
const maker = await Maker.create('http', {
example: ["ExampleService", {
exampleSetting: "this is a configuration setting"
}]
});
//step 10: accessing configuration settings in ExampleService.js
initialize(settings) {
if(settings.exampleSetting){
this.get('log').info(settings.exampleSetting);
}
}//example initialize() function in ExampleService.js
initialize(settings) {
this.get('log').info('ExampleService is initializing...');
this._setSettings(settings);
}const maker = await Maker.create('http', {example: "ExampleService"});
const exampleService = customMaker.service('example');
//wait for example service and its dependencies to initialize
await exampleService.manager().initialize();
//wait for example service and its dependencies to connect
await exampleService.manager().connect();
//wait for example service and its dependencies to authenticate
await exampleService.manager().authenticate();
//can also use callback syntax
exampleService.manager().onConnected(()=>{
/*executed after connected*/
});
//wait for all services used by the maker object to authenticate
maker.authenticate();//in PriceService.js
this.get('event').registerPollEvents({
'price/ETH_USD': {
price: () => this.getEthPrice()
}
});
//in Web3Service.js
this.get('event').emit('web3/INITIALIZED', {
provider: { ...settings.provider }
});
//in the constructor in the Cdp.js
this._emitterInstance = this._cdpService.get('event').buildEmitter();
this.on = this._emitterInstance.on;
this._emitterInstance.registerPollEvents({
COLLATERAL: {
USD: () => this.getCollateralValueInUSD(),
ETH: () => this.getCollateralValueInEth()
},
DEBT: {
dai: () => this.getDebtValueInDai()
}
});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.
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 DSValue 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.
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 poked, 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.
In relation to the Spot the oracle module handles how market prices are recorded on the blockchain. The Spotter 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 spotter.
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.
Read more
'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.
If this is omitted and the provider does not have an unlocked account, the maker object will start in read-only mode.
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
Number of blocks to wait after a transaction has been mined when calling confirm. See for further explanation.
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.
The MCD plugin defines several services for working with Multi-Collateral Dai. Review Getting started to see how to add the plugin.
'mcd:cdpManager': for working with Vaults.
'mcd:cdpType': for reading parameters and live data (totals and prices) for collateral types.
'mcd:savings': for working with the Dai Savings Rate.
: for reading system-wide parameters.
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.
You can start in read-only mode and still add an account with the ability to sign transactions later on.
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 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).
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
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
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 vand creating dai for user w.
Vaults are confiscated via grab(i, u, v, w, dink, dart), which modifies the Vault of user u
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 authed 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.
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.
Keeper that lifts the hat and streamlines executive actions
The chief-keeper monitors and interacts with DSChief and DSSSpells, which is the executive voting contract and a type of proposal object of the Maker Protocol.
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
lifts the hat for the spell (yay) most favored (approvals[yay])
Schedules spells in the GSM by calling DSSSpell.schedule()
The following section assumes familiarity with the , DSSSpells, and (Governance Security Module), as well as the processes within .
chief-keeper interacts directly with the DS-Chief and DSSSpells.
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.
For some known Ubuntu and macOS issues see the README.
Make a run-chief-keeper.sh to easily spin up the chief-keeper.
Download
This project uses for unit testing. Testing of Multi-collateral Dai is performed on a Dockerized local testchain included in tests\config.
In order to be able to run tests, please install development dependencies first by executing:
You can then run all tests with:
See file.
If you have questions regarding Cage Keepers, please reach out to us on the channel on .
A Keeper to facilitate Emergency Shutdown
The cage-keeper is used to help facilitate Emergency Shutdown of the Maker Protocol. 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.
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.
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 .
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:
For some known Ubuntu and macOS issues see the README.
Make a run-cage-keeper.sh to easily spin up the cage-keeper.
Prerequisites:
Download
This project uses for unit testing. Testing of Multi-collateral Dai is performed on a Dockerized local testchain included in tests\config.
In order to be able to run tests, please install development dependencies first by executing:
You can then run all tests with:
See file
If you have questions regarding Cage Keepers, please reach out to us on the channel on .
The MKR Governance Token Implementation
Contract Name: token.sol
Type/Category: MKR Module
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.
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.
Further information about the ERC20 Token standard can be found .
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.
The MKR token has 3 methods of use within the Maker Protocol (reference ):
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.
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.
Multi-Collateral Dai support in Dai.js is implemented as a plugin. This may change in the future. The MCD Plugin is also available as an npm package and its source code can be found on Github.
npm install @makerdao/dai-plugin-mcd
(Note the .default at the end of the line when using require.)
UMD
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.
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.
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 .
The simplest way to integrate DSR in smart contracts
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.
The Maker Protocol's Governance Contracts
Module Name: Governance Module
Type/Category: Governance —> Chief.sol, Pause.sol, Spell.sol
The Maker Protocol Upgrade Contract
Contract Name: scd-mcd-migration.sol
Type/Category: Migration
The Dai Token Contract
Contract Name: dai.sol
Type/Category: DSS —> Dai Module
const makerBrowser = await Maker.create('browser');
const makerHttp = await Maker.create('http', {
url: 'https://kovan.infura.io/v3/YOUR_INFURA_PROJECT_ID'
});
const makerTest = await Maker.create('test');// It doesn't necessarily make sense to set all these
// options at the same time (e.g. `url` and `inject`),
// this is just meant to illustrate the shape of the
// options object.
const maker = await Maker.create('http', {
privateKey: YOUR_PRIVATE_KEY, // '0xabc...'
url: 'http://some-ethereum-rpc-node.net',
web3: {
statusTimerDelay: 2000,
confirmedBlockCount: 8
transactionSettings: {
gasPrice: 12000000000
},
inject: someProviderInstance
},
log: false,
autoAuthenticate: false
});const accountsService = maker.service('accounts');import Maker from '@makerdao/dai';
// or
const Maker = require('@makerdao/dai');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 rays 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 rays 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 joins 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 exits 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)


idThis is the ID of the CDP object. You can pass this ID to Maker.getCdp.
Params: currency unit (optional)
Returns: promise (resolves to the amount of outstanding debt)
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 currency unit, but can return the equivalent in USD if the first argument is Maker.USD.
Params: currency unit (optional)
Returns: promise (resolves to the value of the accrued governance fee in USD)
cdp.getGovernanceFee() returns the value of the accrued governance fee. By default it returns the amount of MKR as a currency unit, but can return the equivalent in USD if the first argument is Maker.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)
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 price unit.
Params: currency unit (optional)
Returns: promise (resolves to collateral amount)
cdp.getCollateralValue() returns the value of the collateral in the CDP. By default it returns the amount of ETH as a currency unit, but can return the equivalent in PETH or USD depending on the first argument.
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.
Returns: promise (resolves to transactionObject once mined)
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.
Note: this process is not atomic, so it's possible for some of the transactions to succeed but not all three. See Using DsProxy for executing multiple transactions atomically.
Params: amount to draw (in Sai, as string)
Returns: promise (resolves to transactionObject once mined)
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)
Returns: promise (resolves to transactionObject once mined)
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.
Returns: promise (resolves to transactionObject once mined)
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)
Returns: promise (resolves to transactionObject once mined)
cdp.give(address) transfers ownership of the CDP from the current owner to the address you provide as an argument.
Params: none
Returns: promise (resolves to transactionObject once mined)
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
Returns: promise (resolves to transactionObject once mined)
cdp.bite() will initiate the liquidation process of an undercollateralized CDP
import { McdPlugin } from '@makerdao/dai-plugin-mcd';
// or
const { McdPlugin } = require('@makerdao/dai-plugin-mcd');<script src="./dai.js" />
<script>
// once the script loads, window.Maker is available
</script>// you provide these values
const infuraKey = 'your-infura-api-key';
const ownerAddress = '0xf00...';
const maker = await Maker.create('http', {
plugins: [McdPlugin],
url: `https://mainnet.infura.io/v3/${infuraKey}`
});
const manager = maker.service('mcd:cdpManager');
const proxyAddress = maker.service('proxy').getProxyAddress(ownerAddress);
const data = await manager.getCdpIds(proxyAddress); // returns list of { id, ilk } objects
const vault = await manager.getCdp(data[0].id);
console.log([
vault.collateralAmount, // amount of collateral tokens
vault.collateralValue, // value in USD, using current price feed values
vault.debtValue, // amount of Dai debt
vault.collateralizationRatio, // collateralValue / debt
vault.liquidationPrice // vault becomes unsafe at this price
].map(x => x.toString());import Maker from '@makerdao/dai';
import { McdPlugin, ETH, DAI } from '@makerdao/dai-plugin-mcd';
// you provide these values
const infuraKey = 'your-infura-api-key';
const myPrivateKey = 'your-private-key';
const maker = await Maker.create('http', {
plugins: [McdPlugin],
url: `https://mainnet.infura.io/v3/${infuraKey}`,
privateKey: myPrivateKey
});
// verify that the private key was read correctly
console.log(maker.currentAddress());
// make sure the current account owns a proxy contract;
// create it if needed. the proxy contract is used to
// perform multiple operations in a single transaction
await maker.service('proxy').ensureProxy();
// use the "vault manager" service to work with vaults
const manager = maker.service('mcd:cdpManager');
// ETH-A is the name of the collateral type; in the future,
// there could be multiple collateral types for a token with
// different risk parameters
const vault = await manager.openLockAndDraw(
'ETH-A',
ETH(50),
DAI(1000)
);
console.log(vault.id);
console.log(vault.debtValue); // '1000.00 DAI'const daiDebt = await cdp.getDebtValue();
const usdDebt = await cdp.getDebtValue(Maker.USD);const mkrFee = await cdp.getGovernanceFee();
const usdFee = await cdp.getGovernanceFee(Maker.USD);const ratio = await cdp.getCollateralizationRatio(); const ratio = await cdp.getLiquidationPrice();const ethCollateral = await cdp.getCollateralValue();
const pethCollateral = await cdp.getCollateralValue(Maker.PETH);
const usdCollateral = await cdp.getCollateralValue(Maker.USD);const ratio = await cdp.isSafe();const enoughMkrToWipe = await cdp.enoughMkrToWipe(10000000000000000000, SAI.wei);return await cdp.lockEth(10000000000000000000, ETH.wei);
// or equivalently
return await cdp.lockEth(100, ETH);return await cdp.drawSai(10000000000000000000, SAI.wei);
// or equivalently
return await cdp.drawSai(100, SAI);return await cdp.wipeSai(10000000000000000000, SAI.wei);
// or equivalently
return await cdp.wipeSai(100, SAI);return await cdp.freePeth(100, PETH);
// or equivalently
return await cdp.freePeth(10000000000000000000, PETH.wei);return await cdp.give('0x046ce6b8ecb159645d3a605051ee37ba93b6efcc');return await cdp.shut();return await cdp.bite();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.
arturnIlkdebt is vice plus the sum of Ilk.Art * Ilk.rate across all ilks.
has line - debt ceiling
has dust - debt floor
gemvsinwgrabsinSin 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 subing the sin.
The quantity dai can be transferred between users with move.
Executes spells after their eta has elapsed in the GSM by calling DSSSpell.cast()
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.
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
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.
Read more
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 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
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.
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 approves 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.
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 transferFroms 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 joins 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
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.
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.
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 cups 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
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).
For the most part, dai.sol functions as a typical ERC20 token. These tokens have been already been heavily documented here and it is recommended to read through that documentation for the core functions of an ERC20 token.
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
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.
DAI is also susceptible to the known ERC20 race condition, 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.
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
Contract Name: OSM
Type/Category: Oracles - Price Feed Module
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.
stopped : flag (uint256) that disables price feed updates if non-zero
src : address of DSValue that the OSM will read from
ONE_HOUR
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)
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()
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.
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.
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)
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
The only solution to these issues is diligence and care regarding the wards of the OSM.
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
wards are allowed to call protected functions (Administration)
pie - stores the address' Pot balance.
Pie - stores the total balance in the Pot.
dsr
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.
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.
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
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.
Accumulation of Stability Fees for Collateral Types
Contract Name: Jug
Type/Category: DSS —> Rates Module
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 ).
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
vat : a VatLike that points the the system's contract
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
dripdrip(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;
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.
rpowrpow(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
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 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.
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
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.
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
Enables any user to execute a flash mint of Dai.
Contract Name: flash.sol
Type/Category: DSS
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.
The Maker Protocol's Debt Auction House
Contract Name: flop.sol
Type/Category: DSS —> System Stabilizer Module
The DSProxyService includes all the functionality necessary for interacting with both types of proxy contracts used in Maker products: profile proxies and forwarding proxies.
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.
git clone https://github.com/makerdao/chief-keeper.git
cd chief-keeper
git submodule update --init --recursive
./install.sh#!/bin/bash
/full/path/to/chief-keeper/bin/chief-keeper \
--rpc-host 'sample.ParityNode.com' \
--network 'kovan' \
--eth-from '0xABCAddress' \
--eth-key 'key_file=/full/path/to/keystoreFile.json,pass_file=/full/path/to/passphrase/file.txt' \
--chief-deployment-block 14374534pip3 install -r requirements-dev.txt./test.shgit clone https://github.com/makerdao/cage-keeper.git
cd cage-keeper
git submodule update --init --recursive
./install.sh#!/bin/bash
/full/path/to/cage-keeper/bin/cage-keeper \
--rpc-host 'sample.ParityNode.com' \
--network 'kovan' \
--eth-from '0xABCAddress' \
--eth-key 'key_file=/full/path/to/keystoreFile.json,pass_file=/full/path/to/passphrase/file.txt' \
--vat-deployment-block 14374534pip3 install -r requirements-dev.txt./test.sh

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:
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 recover any funds 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.
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.
pragma solidity ^0.8.0;
import "./interfaces/IERC20.sol";
import "./interfaces/IERC3156FlashBorrower.sol";
import "./interfaces/IERC3156FlashLender.sol";
contract FlashBorrower is IERC3156FlashBorrower {
enum Action {NORMAL, OTHER}
IERC3156FlashLender lender;
constructor (
IERC3156FlashLender lender_
) public {
lender = lender_;
}
/// @dev ERC-3156 Flash loan callback
function onFlashLoan(
address initiator,
address token,
uint256 amount,
uint256 fee,
bytes calldata data
) external override returns (bytes32) {
require(
msg.sender == address(lender),
"FlashBorrower: Untrusted lender"
);
require(
initiator == address(this),
"FlashBorrower: Untrusted loan initiator"
);
(Action action) = abi.decode(data, (Action));
if (action == Action.NORMAL) {
require(IERC20(token).balanceOf(address(this)) >= amount);
// make a profitable trade here
IERC20(token).transfer(initiator, amount + fee);
} else if (action == Action.OTHER) {
// do another
}
return keccak256("ERC3156FlashBorrower.onFlashLoan");
}
/// @dev Initiate a flash loan
function flashBorrow(
address token,
uint256 amount
) public {
bytes memory data = abi.encode(Action.NORMAL);
uint256 _allowance = IERC20(token).allowance(address(this), address(lender));
uint256 _fee = lender.flashFee(token, amount);
uint256 _repayment = amount + _fee;
IERC20(token).approve(address(lender), _allowance + _repayment);
lender.flashLoan(this, token, amount, data);
}
}pragma solidity ^0.6.12;
import "dss-interfaces/dss/VatAbstract.sol";
import "./interfaces/IERC3156FlashLender.sol";
import "./interfaces/IVatDaiFlashBorrower.sol";
contract FlashBorrower is IVatDaiFlashBorrower {
enum Action {NORMAL, OTHER}
VatAbstract vat;
IVatDaiFlashLender lender;
constructor (
VatAbstract vat_,
IVatDaiFlashLender lender_
) public {
vat = vat_;
lender = lender_;
}
/// @dev Vat Dai Flash loan callback
function onVatDaiFlashLoan(
address initiator,
uint256 amount,
uint256 fee,
bytes calldata data
) external override returns (bytes32) {
require(
msg.sender == address(lender),
"FlashBorrower: Untrusted lender"
);
require(
initiator == address(this),
"FlashBorrower: Untrusted loan initiator"
);
(Action action) = abi.decode(data, (Action));
if (action == Action.NORMAL) {
// do one thing
} else if (action == Action.OTHER) {
// do another
}
// Repay the loan amount + fee
// Be sure not to overpay as there are no safety guards for this
vat.move(address(this), lender, amount + fee);
return keccak256("VatDaiFlashBorrower.onVatDaiFlashLoan");
}
/// @dev Initiate a flash loan
function vatDaiFlashBorrow(
uint256 amount
) public {
bytes memory data = abi.encode(Action.NORMAL);
lender.vatDaiFlashLoan(this, amount, data);
}
}const service = maker.service('proxy');// Forwarding proxy
function lockAndDraw(address tub_, bytes32 cup, uint wad) public payable {
lock(tub_, cup);
draw(tub_, cup, wad);
}// Calling the forwarding proxy with dai.js
function lockAndDraw(tubContractAddress, cdpId, daiAmount, ethAmount) {
const saiProxy = maker.service('smartContract').getContractByName('SAI_PROXY');
return saiProxy.lockAndDraw(
tubContractAddress,
cdpId,
daiAmount,
{
value: ethAmount,
dsProxy: true
}
);
}async function getProxy() {
return maker.service('proxy').currentProxy();
}async function buildProxy() {
const proxyService = maker.service('proxy');
if (!proxyService.currentProxy()) {
return proxyService.build();
}
}const proxyAddress = await maker.service('proxy').ensureProxy();const proxy = await maker.service('proxy').getProxyAddress('0x...');const owner = await maker.service('proxy').getOwner('0x...');await maker.service('proxy').setOwner(newOwner, proxyAddress);Permit()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
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)
Calling disruptive functions like stop and void inappropriately
xbasebase == 100rpow(210, 2, 100)basexrpowrayrpowdai savings rate1ONE = 10^27chi - 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.
drip has to be called before a user joins 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.
the msg.sender's pie amount is updated to include the wad.
the total Pie amount is also updated to include the wad.
msg.senderpieThe msg.senders pie amount is updated by subtracting the wad.
The total Pie amount is also updated by subtracting the wad.
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 DSS-Proxy-Actionsilks[ilk].rho to be equal to the current timestamp.rpowlift- 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.
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.
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 DsrManager provides an easy to use smart contract that allows service providers to deposit/withdraw dai into the contract pot, 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.
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
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 oasis.app/save)
Read more .
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)
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.
Read more .
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.
Read more .
CDP Manager
Potential Issues around Chain Reorganization
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 .
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.
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,
Include all the sections present in this sample guide
Create a folder with one markdown file using the same name
Append a number if a guide needs to be split into multiple parts
Use markdownlint for ensuring a consistent style in the documents. Rules are found in .markdownlint.json root folder.
Use for Math notations.
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 ids
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 Auction process begins with Maker Governance voters determining the system debt limit (Vow.sump). 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 Vow.sol. 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.
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 kicked, 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 kicks 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 lots, 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.
In the context of running a keeper (more info here) to perform bids within an auction, a primary failure mode would occur when a keeper specifies an unprofitable price for MKR.
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.
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
Note: dst refers to the destination address.
vat : core contract address that holds the Vaults.
cdpi: Auto incremental id.
urns: Mapping CDPId => UrnHandler
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.
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
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.
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 joining of collateral to that Vault.
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.
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.
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.
**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
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:
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.
DSRolesSee for inherited features.
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 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.
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.
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.
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
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.
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
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.
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
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.
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.
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).
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 ).
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.
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.
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.
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
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
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.
The Maker Protocol's Surplus Auction House
Contract Name: flap.sol
Type/Category: DSS —> System Stabilizer Module
Keeping the Maker Protocol Stable
Module Name: System Stabilizer
Type/Category: DSS —> System Stabilizer Module (Vow.sol, Flap.sol, Flop.sol)
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.tic: Bid expiry date/time (empty if zero bids).
end: Auction expiry date/time.
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.
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
* The user will require the following contract addresses; MCD_END and MCD_ESM accessible at [Changelog.makerdao.com](https://changelog.makerdao.com) as well as the Maker contract address, to be added in place of MKR_ADR below, which can be verified on [Etherscan](https://etherscan.io/token/0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2).
* These should be setup in the following manner:export MCD_END= 0xab14d3ce3f733cacb76ec2abe7d2fcb00c99f3d5
export MCD_ESM= 0x0581a0abe32aae9b5f0f68defab77c6759100085
export MKR_ADR= <MKR ADDRESS from Etherscan.io>
export MY_ADR= <USER ADDRESS>
#example values for depositing into the ESM
export TRIAL_AMOUNT=$(seth --to-uint256 $(seth --to-wei 0.1 eth))
export REMAINING_AMOUNT=$(seth --to-uint256 $(seth --to-wei 50000 eth))seth --from-wei $(seth call $MKR_ADR "balanceOf(address)" $MY_ADR | seth --to-dec)
# 100000.000000000000000000 seth call $MKR_ADR "allowance(address,address)" $MY_ADR $MCD_ESM
# 0x0000000000000000000000000000000000000000000000000000000000000000 -> not allowedseth send $MKR_ADR "approve(address)" $MCD_ESMseth call $MKR_ADR "allowance(address,address)" $MY_ADR $MCD_ESM
# 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -> allowedseth call $MCD_END "live()" | seth --to-dec
# 1 -> system is running normallyseth --from-wei $(seth call $MCD_ESM "min()" | seth --to-dec)
# 50000.000000000000000000seth send $MCD_ESM "join(uint256)" $TRIAL_AMOUNT seth --from-wei $(seth call $MCD_ESM "Sum()" | seth --to-dec)
# 50050.000000000000000000seth --from-wei $(seth call $MCD_ESM "sum(address)" $MY_ADR | seth --to-dec)
# 50.000000000000000000seth send $MCD_ESM "join(uint256)" $REMAINING_AMOUNT seth call $MCD_ESM "fired()" | seth --to-dec
# 0 -> ES has not been triggeredseth send $MCD_ESM "fire()"joinRead more here.
Read more here (link)
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.
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
urnmanagerThe 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.
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.
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.
As mentioned above in the summary, the dss 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.
uintdeposits: 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.
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.
done - indicates that the spell has been called successfully.
castcastdoneeta: first possible time of execution (as seconds since unix epoch)
A plan can be executed by anyone
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]
bids (id: uint) - storage of all Bids 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]
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 no system debt and has accumulated enough DAI to exceed the Surplus auction size (bump) plus the buffer (hump)
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 heal transaction 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 Vow contract.
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.
In the context of running a keeper (more info here) in order to perform bids within an auction, a primary failure mode could occur when a keeper specifies an unprofitable price for MKR.
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 bids, 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 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.
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.
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 here).
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.
Audit Section Source: Timestamp dependence in auctions enables denial of service (Page 8)
Transaction-reordering / Front Running Attacks on Auctions
Front running attacks are possible. In order to mitigate this possibility, we reviewed and evaluated other auction options such as dutch 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.
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
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.
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.
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 kicks 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
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 bids, 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.
For the full details about this risk, reference @livnev's Paper .
The vault manager works with vaults that are owned by the CdpManager 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 CdpManager.
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.
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 .
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 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 .
The USD price of collateral at which the Vault becomes unsafe.
Whether the Vault is currently safe or not.
All of the methods below are asynchronous and work with the . Amount arguments should be , e.g.:
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.
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.
Look up the proxy contract owned by address and transfer ownership of this vault to that proxy.

The Maker Protocol's Balance Sheet
Contract Name: vow.sol
Type/Category: DSS —> System Stabilizer Module
A generalized wrapper for the Maker Protocol
Contract Name: DssProxyActions.sol
Type/Category: Proxy Module
The Maker Protocol's Rate Accumulation Mechanism
Module Name: Rates Module
Type/Category: Rates
const mgr = maker.service('mcd:cdpManager');



const proxyAddress = await maker.service('proxy').currentProxy();
const data = await mgr.getCdpIds(proxyAddress);
const { id, ilk } = data[0];
// e.g. id = 5, ilk = 'ETH-A'const vault = await mgr.getCdp(111);const txMgr = maker.service('transactionManager');
const open = await mgr.open('ETH-A');
txMgr.listen(open, {
pending: tx => console.log('tx pending: ' + tx.hash)
});
const vault = await open;const vault = await mgr.openLockAndDraw(
'BAT-A',
BAT(1000),
DAI(100)
);vault.reset();
await vault.prefetch();import { ETH, DAI } from '@makerdao/dai-plugin-mcd';
await vault.lockAndDraw(ETH(2), DAI(20));tic - Bid expiry [uint48]
end - when the auction will finish [uint48]
ticendusrgaltabbid - 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 ids
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 bids 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
bidbidbidVowticnowttlBidder 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.
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 Flopper will then send the received Dai to the Vow in order to cancel its debt. Lastly, the Flopper 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 Flapper 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
When a Vault is liquidated (bite - documentation), 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 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
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 flogs off the queue and is eligible for Flop auction) is the amount of time that
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 Vows each with their own sins
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 Vows).
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 flapor 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 kicked 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.
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
Reference the ds-proxy for more information here.
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.
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.
Stability fee accumulation in MCD is largely an interplay between two contracts: the Vat (the system's central accounting ledger) and the Jug (a specialized module for updating the cumulative rate), with the Vow involved only as the address to which the accumulated fees are credited.
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 Vow's surplus by Art*rate_change
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)
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).
DSR accumulation is very similar to stability fee accumulation. It is implemented via the Pot, 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.
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
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)
proxy.execute(dssProxyActions, abi.encodeWithSignature("open(address,bytes32,address)", address(manager), bytes32("ETH-A"), address(0x123)))











Sinsumpwoevat.sin[vow]vow.Sinvow.Ashvat.sin(vow)vow.Sinvow.Ashvat.sin(vow)- total bad debt
vow.Sin - debt blocked
vow.Ash - debt in auctions
FlipFlipVowVatThe 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.
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).
The values of all parameters here (except vat) are changeable by an address that is relyed on. For instance, the End module should be authed to allow for it to call cage() and update live from 1 to 0. Governance (through an authed 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
auth
sets live to 0 (prevents bite). See for further description.
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.
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
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.
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 .
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.
Typically in the form of an automated bot, Keepers are external actors that participate in market making, auctions, market arbitrage, 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.
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.
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 .
Operation of the Simple Arbitrage Keeper
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
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.
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
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:
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:
For some known Ubuntu and macOS issues see the .
Install the DappHub Toolkit
If you are running Linux or macOS you can take advantage of our all in one installer.
If you're having issues with installation, head over to the
Keeper Ethereum Address
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.
Deploy TxManager
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:
The output of dapp create is the address to your TxManager
Deploy/Find a parity node
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
Uniswap Exchange Addresses
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)
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
Rather than the default gas price implement a
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
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
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:
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.
cashETHThe following function cashETH is referenced as part of the calldata function and should be referenced .
Next, we again define the calldata for our function by bundling together the cashETH parameters shown above.
cashETHcalldataFinally, 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.
cashGEMIt 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
MYPROXYFinally, 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.
Welcome to the MCD CLI
First install dapp tools:
Then install the mcd package:
Mcd is built on and uses the same network configuration options, which just like Seth, can be defined in the ~/sethrc initialisation file.
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
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.
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:
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.
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.
$ curl https://dapp.tools/install | sh$ dapp pkg install mcd








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
export DAI=0x6B175474E89094C44Da98b954EedeAC495271d0F
export PROXY_ACTIONS_END=0x069B2fb501b6F16D1F5fE245B16F6993808f1008
export MCD_END=0xaB14d3CE3F733CACB76eC2AbE7d2fcb00c99F3d5
export CDP_MANAGER=0x5ef30b9986345249bc32d8928B7ee64DE9435E39
export PROXY_REGISTRY=0x4678f0a6958e4D2Bc4F1BAF7Bc52E8F3564f3fE4
export MCD_JOIN_ETH=0x2F0b23f53734252Bda2277357e97e1517d6B042A
export MCD_JOIN_BAT=0x3D0B1912B66114d4096F48A8CEe3A56C231772cA
export MCD_JOIN_DAI=0x9759A6Ac90977b93B58547b4A71c78317f391A28
export MYPROXY=$(seth call $PROXY_REGISTRY 'proxies(address)(address)' $ETH_FROM)
# This creates a unique proxy address by calling the proxy registry using the users Ethereum address.
export ilk=$(seth --to-bytes32 $(seth --from-ascii ETH-A))
export ilkBAT=$(seth --to-bytes32 $(seth --from-ascii BAT-A))
# Here we have defined two ilk (collateral types) ETH and BAT.
# The number of ilk types needed will depend on the types of collateral vaults that the user had open.
export ETH_GAS=4000000
export ETH_GAS_PRICE=2500000000
# Typically gas costs are slightly increased when dealing with proxy contracts to prevent failed transactions.
export cdpId=$(seth --to-dec $(seth call $CDP_MANAGER 'last(address)' $MYPROXY))
# This is a call to the CDP Manager responsible for making the users CDP ID.
# Note, if user created multiple vaults they will have multiple CDP IDs, all of which must be referenced to retrieve collateral.function pack(
address daiJoin,
address end,
uint wad
) public {
daiJoin_join(daiJoin, address(this), wad);
VatLike vat = DaiJoinLike(daiJoin).vat();
// Approves the end to take out DAI from the proxy's balance in the vat
if (vat.can(address(this), address(end)) == 0) {
vat.hope(end);
}
EndLike(end).pack(wad);
}export balance=$(seth --from-wei $(seth --to-dec $(seth call $DAI 'balanceOf(address)' $ETH_FROM)))
export wad=$(seth --to-uint256 $(seth --to-wei 13400 eth))
# in the above, 13400 is an example Dai balanceseth send $DAI 'approve(address,uint)' $MYPROXY $(seth --to-uint256 $(mcd --to-hex -1))export calldata=$(seth calldata 'pack(address,address,uint)' $MCD_JOIN_DAI $MCD_END $wad)
.
.
.
# 0x33ef33d6000000000000000000000000fc0b3b61407cdf5f583b5b1e08514e68ecee4a73000000000000000000000000d9026db5ca822d64a6ba18623d0ff2bb07ad162c0000000000000000000000000000000000000000000002d66a5b4bc1da600000seth send $MYPROXY 'execute(address,bytes memory)' $PROXY_ACTIONS_END $calldata
# [example](<http://ethtx.info/kovan/0x8f4021e46b1a6889ee7045ba3f3fae69dee7ef130dbb447d4cc724771e04bcd6>) transaction showing actions involved in 'packing' the user's Dai.function cashETH(
address ethJoin,
address end,
bytes32 ilk,
uint wad
) public {
EndLike(end).cash(ilk, wad);
uint wadC = mul(wad, EndLike(end).fix(ilk)) / RAY;
// Exits WETH amount to proxy address as a token
GemJoinLike(ethJoin).exit(address(this), wadC);
// Converts WETH to ETH
GemJoinLike(ethJoin).gem().withdraw(wadC);
// Sends ETH back to the user's wallet
msg.sender.transfer(wadC);
}export cashETHcalldata=$(seth calldata 'cashETH(address,address,bytes32,uint)' $MCD_JOIN_ETH $MCD_END $ilk $wad)seth send $MYPROXY 'execute(address,bytes memory)' $PROXY_ACTIONS_END $cashETHcalldata
# [example](<http://ethtx.info/kovan/0x323ab9cd9817695089aea31eab369fa9f3c9b1a64743ed4c5c1b3ec4d7218cf8>) successful transactionfunction cashGem(
address gemJoin,
address end,
bytes32 ilk,
uint wad
) public {
EndLike(end).cash(ilk, wad);
// Exits token amount to the user's wallet as a token
GemJoinLike(gemJoin).exit(msg.sender, mul(wad, EndLike(end).fix(ilk)) / RAY);
}export cashBATcalldata=$(seth calldata 'cashETH(address,address,bytes32,uint)' $MCD_JOIN_BAT $MCD_END $ilkBAT $wad)seth send $MYPROXY 'execute(address,bytes memory)' $PROXY_ACTIONS_END $cashBATcalldatafunction freeETH(
address manager,
address ethJoin,
address end,
uint cdp
) public {
uint wad = _free(manager, end, cdp);
// Exits WETH amount to proxy address as a token
GemJoinLike(ethJoin).exit(address(this), wad);
// Converts WETH to ETH
GemJoinLike(ethJoin).gem().withdraw(wad);
// Sends ETH back to the user's wallet
msg.sender.transfer(wad);
}export freeETHcalldata=$(seth calldata 'freeETH(address,address,address,uint)' $CDP_MANAGER $MCD_JOIN_ETH $MCD_END $cdpId )seth send $MYPROXY 'execute(address,bytes memory)' $PROXY_ACTIONS_END $freeETHcalldatafunction freeGem(
address manager,
address gemJoin,
address end,
uint cdp
) public {
uint wad = _free(manager, end, cdp);
// Exits token amount to the user's wallet as a token
GemJoinLike(gemJoin).exit(msg.sender, wad);
}export freeBATcalldata=$(seth calldata 'freeETH(address,address,address,uint)' $CDP_MANAGER $MCD_JOIN_BAT $MCD_END $cdpId )seth send $MYPROXY 'execute(address,bytes memory)' $PROXY_ACTIONS_END $freeBATcalldataMCD - Multi-collateral Dai
Usage: mcd [<options>] <command> [<args>]
or: mcd help [<command>]
Commands:
bite Trigger liquidation of an unsafe Urn
bites Recent bites
cdp CDP managerment
dai Dai management
debt Total dai issuance
drip Trigger stability fee accumulation
flap Trigger a flap auction
flips View flips and kick-off auctions
flog Release queued bad-debt for auction
flop Trigger a flop auction
frob Urn management
frobs Recent frobs
gem Collateral management
help Print help about mcd or one of its subcommands
ilk Ilk (collateral type) parameters
line Total debt ceiling
live Liveness flag
poke Update the spot price for a given Ilk
unwrap Unwrap WETH to ETH
urn CDP state
vice Total bad debt
vow Liquidator balances
wrap Wrap ETH to WETH#!/usr/bin/env bash
export ETH_FROM=0x4Ffa8667Fe2db498DCb95A322b448eA688Ce430c
export MCD_CHAIN=kovan$ export ETH_FROM=0x4Ffa8667Fe2db498DCb95A322b448eA688Ce430c
$ mcd --chain=kovan dai join 100$ mcd testnet chains`$ export MCD_CHAIN=12899149080555595289
$ mcd dai join 100$ export MCD_CONFIG=~/testchain-deployment-scripts/out/addresses.json
$ mcd -C testnet dai join 100$ mcd ilks
ILK GEM DESC
ETH-A WETH Ethereum
ETH-B WETH Ethereum
REP-A REP Augur$ mcd --ilk=ETH-A ilk
Art 40.000000000000000000 Total debt (DAI)
rate 1.000080370887129123082627939 WETH DAI exchange rate
spot 99.333333333333333333333333333 WETH price with safety mat (USD)
line 1000.0000000000000000000000000000000000000 Debt ceiling (DAI)
dust 0.0000000000000000000000000000000000000000 Debt floor (DAI)
flip 0x9d905effff127a01da3b38124f8da88e766eb8dd Flip auction contract
chop 1.000000000000000000000000000 Liquidation penalty
lump 10000.000000000000000000 Flip auction lot size
tax 1.000000000782997609082909351 Stability fee
rho 1552802862 Last drip timestamp
pip 0x98312e16f5b2c0def872a1f7484a8456e5a67a3b Price feed contract
mat 1.500000000000000000000000000 Liquidation ratio$ mcd --ilk=ETH-A ilk spot
99.333333333333333333333333333gem --ilk=<id> symbol Gem symbol e.g. WETH
gem --ilk=<id> balance Print balances for a given urn (default: ETH_FROM)
gem --ilk=<id> join <wad> Add collateral to a given Urn (default: ETH_FROM)
gem --ilk=<id> exit <wad> [<guy>] Remove collateral from an Urn (default: ETH_FROM)$ mcd --ilk=ETH-A --urn=0x123456789abcdef0123456789abcdef012345678 join 100$ mcd --ilk=ETH-A exit 100 0xDecaf00000000000000000000000000000000000ilk ETH-A Collateral type
urn 0xC93C178EC17B06bddBa0CC798546161aF9D25e8A Urn handler
ink 45.000000000000000000 Locked collateral (WETH)
art 120.000000000000000000 Issued debt (Dai)
tab 120.000244107582797248312544980 Outstanding debt (Dai)
rap 0.000244107582797248312544980 Accumulated stability fee (Dai)
--> 37.24 Collateralization ratio
spot 99.333333333333333333333333333 WETH price with safety mat (USD)
rate 1.000002034229856643749638820 WETH DAI exchange rate$ mcd --ilk=ETH-A frob 100 400$ mcd --ilk=ETH-A frob -- 0 -200dai balance Print balances for a given urn (default: ETH_FROM)
dai join <wad> Exchange DSToken Dai for Vat Dai
dai exit <wad> Exchange Vat Dai for DSToken Dai$ mcd dai balance
vat 1030.003120998308631176024235912000000000000000000 Vat balance
ext 0.000000000000000000 ERC20 balance$ mcd dai balance vat
1030.003120998308631176024235912000000000000000000Usage: mcd cdp [<id>] [<command>]
Commands: ls [<owner>] List Cdps
count [<owner>] Cdp count
open Open a new Cdp
<id> urn Cdp state
<id> lock <wad> Join & lock collateral
<id> free <wad> Free & exit collateral
<id> draw <wad> Draw & exit dai
<id> wipe <wad> Join & wipe dai# i) Wrap
$ mcd wrap 100
eth 900.000000000000000000
weth 100.000000000000000000
# ii) Gem join
$ mcd --ilk=ETH-A gem join 100
$ Grant approval to move WETH to the Vat? [Y/n]: Y
vat 100.000000000000000000 Free collateral (WETH)
ink 0.000000000000000000 Locked collateral (WETH)
ext 0.000000000000000000 External account balance (WETH)
ext 900.000000000000000000 External account balance (ETH)
# iii) Lock & Draw
$ mcd --ilk=ETH-A frob 100 500
ilk ETH-A Collateral type
urn 0xC93C178EC17B06bddBa0CC798546161aF9D25e8A Urn handler
ink 100.000000000000000000 Locked collateral (WETH)
art 500.000000000000000000 Issued debt (Dai)
tab 500.000244107582797248312544980 Outstanding debt (Dai)
rap 0.000244107582797248312544980 Accumulated stability fee (Dai)
--> 19.86 Collateralization ratio
# iv) Withdraw Dai
$ mcd dai exit 500
vat 0.000060682318362511884962000000000000000000000 Vat balance
ext 500.000000000000000000 ERC20 balance# i) Open
$ mcd --ilk=REP-A cdp open
mcd-cdp-open: Waiting for transaction receipt...
0x800e5578d3ac4b77b7ada1aba48cf80d0d238d4392d2676d79159eac2c2cdd73
Opened: cdp 19
# ii) Lock
$ mcd --ilk=REP-A cdp 19 lock 100
seth-send: Published transaction with 260 bytes of calldata.
seth-send: 0x4d30cb4863ca997d24ff2346c9a92e86648369ce7b4a86ed004c73b8d4ef299a
seth-send: Waiting for transaction receipt...
seth-send: Transaction included in block 333.
ilk REP-A Collateral type
urn 0x4518c4709a50C915b7996A0e6Dfb38c67248BBcF Urn handler
ink 100.000000000000000000 Locked collateral (REP)
art 0.000000000000000000 Issued debt (Dai)
tab 0 Outstanding debt (Dai)
rap 0 Accumulated stability fee (Dai)
--> 0 Collateralization ratio
# iii) Draw
$ mcd --ilk=REP-A cdp 19 draw 500
seth-send: Published transaction with 260 bytes of calldata.
seth-send: 0xd5fb7ddf94bb910fbba2af118ecde88a03a13129b2e1979238236afe672781c3
seth-send: Waiting for transaction receipt...
seth-send: Transaction included in block 335.
ilk REP-A Collateral type
urn 0x4518c4709a50C915b7996A0e6Dfb38c67248BBcF Urn handler
ink 100.000000000000000000 Locked collateral (REP)
art 49.999505439113270178 Issued debt (Dai)
tab 50.000000000000000000000000000 Outstanding debt (Dai)
rap 0.000494560886729822000020743 Accumulated stability fee (Dai)
--> 16.66 Collateralization ratiobite will not leave a Vault with debt and no collateral.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]
vat address that conforms to a VatLike interface (see vat documentation for more info). It is set during the constructor and cannot be changed.
vow address that conforms to a VowLike interface (see vow documentation for more info).
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
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 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.
entry_tokenExecute multi-step trade -> with use of TxManager, buy asset on exchange A and sell on exchange B within one Ethereum transaction
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
To save gas, get all active orders and take specific orders by ID rather than use MatchingMarket.offer(...) and the on-chain matching engine
Send trade updates through a text/email Python API

A powerful command line tool created to interface with the Ethereum blockchain
Seth is a simple, but powerful command line tool created to interface with the Ethereum blockchain. It is part of the Dapp.Tools 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:
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.
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.
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
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!).
Then you have to set up the same variables:
You will need Kovan ETH for gas, you can get some by following the guide here:
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
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
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:
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.
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:
$ seth estimate $COL1 'transfer(address, uint)' $(seth --to-uint256 $(seth --to-wei 0.1 ether))
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:
$ seth send $COL1 'transfer(address, uint)' $(seth --to-uint256 $(seth --to-wei 0.1 ether))
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
This guide was written based on the official documentation in the Github repository of Seth. You can find additional information over there:
Issues with MacOS Mojave



function bite(bytes32 ilk, address urn) public returns (uint id) {
// Get the rate, spot, and dust from the ilk in the vat.
(,uint256 rate,uint256 spot,,uint256 dust) = vat.ilks(ilk);
// get the ink and art from the urn from the Vat.
(uint256 ink, uint256 art) = vat.urns(ilk, urn);
// ensure End has not happened
require(live == 1);
// require the Vault to be unsafe (see definition above).
require(spot > 0 && mul(ink, spot) < mul(art, rate), "Cat/not-unsafe");
// Loads the `ilk` data into memory as an optimization.
Ilk memory milk = ilks[ilk];
// Declares a variable that will be assigned in the following scope.
uint256 dart;
// Defines a new scope, this prevents a stack too deep error in solidity
{
// Calculate the available space in the box
uint256 room = sub(box, litter);
// test whether the remaining space in the litterbox is dusty
require(litter < box && room >= dust, "Cat/liquidation-limit-hit");
// Sets the amount of debt to be covered by the auction.
// (smaller of either the amount of normalized debt, maximum debt chunk size, or space in the box)
// divided by the rate, divided by the penalty fee.
dart = min(art, mul(min(milk.dunk, room), WAD) / rate / milk.chop);
}
// Takes the minimum of the collateral balance or the
// amount of collateral represented by the debt to be covered
uint256 dink = min(ink, mul(ink, dart) / art);
// Prevents no-collateral auctions
require(dart > 0 && dink > 0 , "Cat/null-auction");
// Protects against int overflow when converting from uint to int
require(dart <= 2**255 && dink <= 2**255, "Cat/overflow" );
// Called in this way, vat.grab will:
// - Remove the dink and the dart from the bitten Vault (urn)
// - Adds the collateral (dink) to the Cat's gem
// - Adds the debt (dart) to the Vow's debt (vat.sin[vow])
// - Increases the total unbacked dai (vice) in the system
// This may leave the CDP in a dusty state
vat.grab(
ilk, urn, address(this), address(vow), -int256(dink), -int256(dart)
);
// Adds the debt to the debt-queue in Vow (Vow.Sin and Vow.sin[now])
vow.fess(mul(dart, rate));
{ // Avoid stack too deep
// This calcuation will overflow if dart*rate exceeds ~10^14,
// i.e. the maximum dunk is roughly 100 trillion DAI.
// Multiplies the accumulated rate by the normalized debt to be covered
// to get the total debt tab (debt + stability fee + liquidation penalty) for the auction.
uint256 tab = mul(mul(dart, rate), milk.chop) / WAD;
// Updates the amount of litter in the box
litter = add(litter, tab);
// Calls kick on the collateral's Flipper contract.
// tab is the total debt to be sent to auction
// gal: address(vow) sets up the Vow as the recipient of the Dai income for this auction
// bid: 0 indicates that this is the opening bid
// This moves the collateral from the Cat's gem to the Flipper's gem in the Vat
id = Kicker(milk.flip).kick({
urn: urn,
gal: address(vow),
tab: tab,
lot: dink,
bid: 0
});
}
// Emits an event about the bite to notify actors (for instance keepers) about the new auction
emit Bite(ilk, urn, dink, dart, mul(dart, rate), milk.flip, id);
}$ git clone https://github.com/makerdao/simple-arbitrage-keeper.git
$ cd simple-arbitrage-keeper
$ git submodule update --init --recursive
$ pip3 install -r requirements.txt$ curl https://dapp.tools/install | sh$ git clone https://github.com/makerdao/tx-manager.git
$ cd tx-manager
$ dapp update
$ dapp --use solc:0.4.25 build --extract
$ export SETH_CHAIN=kovan
$ export ETH_FROM=<your Keeper Ethereum Address e.g. 0xABC>
$ export ETH_GAS=2000000
$ dapp create TxManager --password /path/to/passphraseOfAddress.txt
$ vim run-simple-keeper-kovan.sh#!/bin/bash
/full/path/to/githubClone/simple-arbitrage-keeper/bin/simple-arbitrage-keeper \
--rpc-host 'kovan.sampleparitynode.com' \
--eth-from '0xABC' \
--eth-key 'key_file=/path/to/keystore.file,pass_file=/path/to/passphrase.txt' \
--uniswap-entry-exchange '0x47D4Af3BBaEC0dE4dba5F44ae8Ed2761977D32d6' \
--uniswap-arb-exchange '0x1D79BcC198281C5F9B52bf24F671437BaDd3a688' \
--oasis-address '0x4A6bC4e803c62081ffEbCc8d227B5a87a58f1F8F' \
--oasis-api-endpoint 'https://kovan-api.oasisdex.com' \
--tx-manager '0xABC' \
--entry-token '0xC4375B7De8af5a38a93548eb8453a498222C4fF2' \
--arb-token '0xd0A1E359811322d97991E03f863a0C30C2cF029C' \
--arb-token-name 'WETH' \
--min-profit 1 \
--max-engagement 10 \$ chmod +x run-simple-keeper-kovan.sh
$ ./run-simple-keeper-kovan.sh
Kentons-Macbook:scripts kentonprescott$ ./run-simple-arbitrage-keeper-mainnet-XYZ-XYZ.sh
2019-10-19 17:21:57,463 INFO Keeper connected to RPC connection https://...
2019-10-19 17:21:57,463 INFO Keeper operating as 0xABCD
2019-10-19 17:21:58,523 INFO Executing keeper startup logic
2019-10-19 17:22:04,820 INFO Watching for new blocks
2019-10-19 17:22:13,983 INFO Best trade regardless of profit/min-profit: -1.243077085275947008 DAI from Uniswap to OasisValueError: {'code': -32010, 'message': 'Transaction with the same hash was already imported.'}$ curl https://nixos.org/nix/install | sh
$ . "$HOME/.nix-profile/etc/profile.d/nix.sh"
$ nix-env -if https://github.com/cachix/cachix/tarball/master --substituters https://cachix.cachix.org --trusted-public-keys cachix.cachix.org-1:eWNHQldwUO7G2VkjpnjDbWwy4KQ/HNxht7H4SSoMckM=
$ cachix use dapp
$ git clone --recursive [https://github.com/dapphub/dapptools](https://github.com/dapphub/dapptools) $HOME/.dapp/dapptools
$ nix-env -f $HOME/.dapp/dapptools -iA dapp seth solc hevm ethsign$ export ETH_PASSWORD=$PWD/pass
$ export ETH_KEYSTORE=~/.dapp/testnet/8545/keystore
$ export ETH_FROM=<your ethereum account address>$ export ETH_KEYSTORE=<path to your keystore folder>
$ export ETH_PASSWORD=<path and filename to the text file containing the password for your account e.g: /home/one1up/MakerDAO/415pass >
$ export ETH_FROM=<your ethereum account address>
$ export SETH_CHAIN=kovanseth-send: warning: `ETH_GAS' not set; using default gas amount
seth-send: Published transaction with 0 bytes of calldata.
seth-send: 0x000000…
seth-send: Waiting for transaction receipt.......
seth-send: Transaction included in block xxxxxx.Usage: seth block [-j|--json] <block> [<field>]
Print a table of information about <block>.
If <field> is given, print only the value of that field.seth-send: Published transaction with 68 bytes of calldata.
seth-send: 0x58ba3980775741aecaf8435646a003bff3395d7d4e00c8f7a32ad1fa0ce64e01
seth-send: Waiting for transaction receipt....
seth-send: Transaction included in block 9704345.
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.
Based on the Pymaker API, 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: bite-keeper (SCD only), arbitrage-keeper, auction-keeper (MCD only), cdp-keeper (SCD only), market-maker-keeper.
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)
In order for the Python requirements to install correctly on macOS, please install openssl, libtool, pkg-config and automake using :
and set the LDFLAGS environment variable before you run pip3 install -r requirements.txt:
The current version provides APIs around:
ERC20Token,
Tub, Tap,Top and Vox (),
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:
6.2.5
(using npm, sudo npm install -g [email protected])
This project uses for unit testing. Testing of Multi-collateral Dai is performed on a Dockerized local testchain included in tests\config.
In order to be able to run tests, please install development dependencies first by executing:
You can then run all tests with:
If you have questions regarding Pymaker, please reach out to us on the channel on .
Shutdown
Contract Name: end.sol
Type/Category: DSS





SimpleMarket, ExpiringMarket and MatchingMarket (https://github.com/makerdao/maker-otc),
TxManager (https://github.com/makerdao/tx-manager),
DSGuard (https://github.com/dapphub/ds-guard),
DSToken (https://github.com/dapphub/ds-token),
DSEthToken (https://github.com/dapphub/ds-eth-token),
DSValue (https://github.com/dapphub/ds-value),
DSVault (https://github.com/dapphub/ds-vault),
EtherDelta (https://github.com/etherdelta/etherdelta.github.io),
0x v2
Dai Savings Rate (Pot)(https://github.com/makerdao/pymaker/blob/master/tests/manual_test_dsr.py#L29)
git clone https://github.com/makerdao/pymaker.git
cd pymaker
pip3 install -r requirements.txtsudo apt-get install build-essential automake libtool pkg-config libffi-dev python-dev python-pip libsecp256k1-devbrew install openssl libtool pkg-config automakeexport LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" from web3 import HTTPProvider, Web3
from pymaker import Address
from pymaker.token import ERC20Token
from pymaker.numeric import Wad
from pymaker.sai import Tub
web3 = Web3(HTTPProvider(endpoint_uri="http://localhost:8545"))
tub = Tub(web3=web3, address=Address(' 0xb7ae5ccabd002b5eebafe6a8fad5499394f67980'))
sai = ERC20Token(web3=web3, address=tub.sai())
sai.transfer(address=Address(' 0x0000000000111111111100000000001111111111'),
value=Wad.from_number(10)).transact()import json
import urllib.request
from web3 import HTTPProvider, Web3
from pymaker import Address
from pymaker.feed import DSValue
from pymaker.numeric import Wad
def cryptocompare_rate() -> Wad:
with urllib.request.urlopen("https://min-api.cryptocompare.com/data/price?fsym=ETH&tsyms=USD") as url:
data = json.loads(url.read().decode())
return Wad.from_number(data['USD'])
web3 = Web3(HTTPProvider(endpoint_uri="http://localhost:8545"))
dsvalue = DSValue(web3=web3, address=Address(' 0x038b3d8288df582d57db9be2106a27be796b0daf'))
dsvalue.poke_with_int(cryptocompare_rate().value).transact()from web3 import HTTPProvider, Web3
from pymaker import Address
from pymaker.token import ERC20Token
from pymaker.numeric import Ray
from pymaker.sai import Tub, Tap
web3 = Web3(HTTPProvider(endpoint_uri="http://localhost:8545"))
tub = Tub(web3=web3, address=Address(' 0x448a5065aebb8e423f0896e6c5d525c040f59af3'))
tap = Tap(web3=web3, address=Address(' 0xbda109309f9fafa6dd6a9cb9f1df4085b27ee8ef'))
sai = ERC20Token(web3=web3, address=tub.sai())
skr = ERC20Token(web3=web3, address=tub.skr())
gem = ERC20Token(web3=web3, address=tub.gem())
print(f"")
print(f"Token summary")
print(f"-------------")
print(f"SAI total supply : {sai.total_supply()} SAI")
print(f"SKR total supply : {skr.total_supply()} SKR")
print(f"GEM total supply : {gem.total_supply()} GEM")
print(f"")
print(f"Collateral summary")
print(f"------------------")
print(f"GEM collateral : {tub.pie()} GEM")
print(f"SKR collateral : {tub.air()} SKR")
print(f"SKR pending liquidation: {tap.fog()} SKR")
print(f"")
print(f"Debt summary")
print(f"------------")
print(f"Debt ceiling : {tub.cap()} SAI")
print(f"Good debt : {tub.din()} SAI")
print(f"Bad debt : {tap.woe()} SAI")
print(f"Surplus : {tap.joy()} SAI")
print(f"")
print(f"Feed summary")
print(f"------------")
print(f"REF per GEM feed : {tub.pip()}")
print(f"REF per SKR price : {tub.tag()}")
print(f"GEM per SKR price : {tub.per()}")
print(f"")
print(f"Tub parameters")
print(f"--------------")
print(f"Liquidation ratio : {tub.mat()*100} %")
print(f"Liquidation penalty : {tub.axe()*100 - Ray.from_number(100)} %")
print(f"Stability fee : {tub.tax()} %")
print(f"")
print(f"All cups")
print(f"--------")
for cup_id in range(1, tub.cupi()+1):
cup = tub.cups(cup_id)
print(f"Cup #{cup_id}, lad={cup.lad}, ink={cup.ink} SKR, tab={tub.tab(cup_id)} SAI, safe={tub.safe(cup_id)}")import sys
from web3 import Web3, HTTPProvider
from pymaker import Address
from pymaker.deployment import DssDeployment
from pymaker.keys import register_keys
from pymaker.numeric import Wad
web3 = Web3(HTTPProvider(endpoint_uri="https://localhost:8545",
request_kwargs={"timeout": 10}))
web3.eth.defaultAccount = sys.argv[1] # ex: 0x0000000000000000000000000000000aBcdef123
register_keys(web3, [sys.argv[2]]) # ex: key_file=~keys/default-account.json,pass_file=~keys/default-account.pass
mcd = DssDeployment.from_json(web3=web3, conf=open("tests/config/kovan-addresses.json", "r").read())
our_address = Address(web3.eth.defaultAccount)
# Choose the desired collateral; in this case we'll wrap some Eth
collateral = mcd.collaterals['ETH-A']
ilk = collateral.ilk
collateral.gem.deposit(Wad.from_number(3)).transact()
# Add collateral and allocate the desired amount of Dai
collateral.approve(our_address)
collateral.adapter.join(our_address, Wad.from_number(3)).transact()
mcd.vat.frob(ilk, our_address, dink=Wad.from_number(3), dart=Wad.from_number(153)).transact()
print(f"CDP Dai balance before withdrawal: {mcd.vat.dai(our_address)}")
# Mint and withdraw our Dai
mcd.approve_dai(our_address)
mcd.dai_adapter.exit(our_address, Wad.from_number(153)).transact()
print(f"CDP Dai balance after withdrawal: {mcd.vat.dai(our_address)}")
# Repay (and burn) our Dai
assert mcd.dai_adapter.join(our_address, Wad.from_number(153)).transact()
print(f"CDP Dai balance after repayment: {mcd.vat.dai(our_address)}")
# Withdraw our collateral
mcd.vat.frob(ilk, our_address, dink=Wad(0), dart=Wad.from_number(-153)).transact()
mcd.vat.frob(ilk, our_address, dink=Wad.from_number(-3), dart=Wad(0)).transact()
collateral.adapter.exit(our_address, Wad.from_number(3)).transact()
print(f"CDP Dai balance w/o collateral: {mcd.vat.dai(our_address)}")from web3 import HTTPProvider
from web3 import Web3
from pymaker import Address, synchronize
from pymaker.numeric import Wad
from pymaker.sai import Tub
from pymaker.token import ERC20Token
web3 = Web3(HTTPProvider(endpoint_uri="http://localhost:8545"))
tub = Tub(web3=web3, address=Address(' 0x448a5065aebb8e423f0896e6c5d525c040f59af3'))
sai = ERC20Token(web3=web3, address=tub.sai())
skr = ERC20Token(web3=web3, address=tub.skr())
synchronize([sai.transfer(Address(' 0x0101010101020202020203030303030404040404'), Wad.from_number(1.5)).transact_async(),
skr.transfer(Address(' 0x0303030303040404040405050505050606060606'), Wad.from_number(2.5)).transact_async()])from web3 import HTTPProvider
from web3 import Web3
from pymaker import Address
from pymaker.approval import directly
from pymaker.numeric import Wad
from pymaker.sai import Tub
from pymaker.token import ERC20Token
from pymaker.transactional import TxManager
web3 = Web3(HTTPProvider(endpoint_uri="http://localhost:8545"))
tub = Tub(web3=web3, address=Address(' 0x448a5065aebb8e423f0896e6c5d525c040f59af3'))
sai = ERC20Token(web3=web3, address=tub.sai())
skr = ERC20Token(web3=web3, address=tub.skr())
tx = TxManager(web3=web3, address=Address(' 0x57bFE16ae8fcDbD46eDa9786B2eC1067cd7A8f48'))
tx.approve([sai, skr], directly())
tx.execute([sai.address, skr.address],
[sai.transfer(Address(' 0x0101010101020202020203030303030404040404'), Wad.from_number(1.5)).invocation(),
skr.transfer(Address(' 0x0303030303040404040405050505050606060606'), Wad.from_number(2.5)).invocation()]).transact()import asyncio
from random import randint
from web3 import Web3, HTTPProvider
from pymaker import Address
from pymaker.gas import FixedGasPrice
from pymaker.oasis import SimpleMarket
web3 = Web3(HTTPProvider(endpoint_uri=f"http://localhost:8545"))
otc = SimpleMarket(web3=web3, address=Address(' 0x375d52588c3f39ee7710290237a95C691d8432E7'))
async def bump_with_increasing_gas_price(order_id):
gas_price = FixedGasPrice(gas_price=1000000000)
task = asyncio.ensure_future(otc.bump(order_id).transact_async(gas_price=gas_price))
while not task.done():
await asyncio.sleep(1)
gas_price.update_gas_price(gas_price.gas_price + randint(0, gas_price.gas_price))
return task.result()
bump_task = asyncio.ensure_future(bump_with_increasing_gas_price(otc.get_orders()[-1].order_id))
event_loop = asyncio.get_event_loop()
bump_result = event_loop.run_until_complete(bump_task)
print(bump_result)
print(bump_result.transaction_hash)pip3 install -r requirements-dev.txt./test.shThe 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 packed 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 caged (with cage(ilk)). Flap and flop auctions are frozen by the initial cage(). Both Flap and Flop auctions can be yanked 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.
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 skimmed and then freed.
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.
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.
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 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.
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.
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.
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.
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.
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.
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 skipped 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.
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 line 261 of end.sol).
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.
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.
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.
Keepers play a critical role in maintaining the health of the system and Dai stability. In March 2020, the Maker Foundation underlined 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 guide.
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.
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.
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.
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.
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:
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 .
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:
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 .
Scenario: Redeployment
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
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
Redirect users to the to claim their Dai equivalent in Collateral, or create an interface to handle the process locally.
Scenario: Redeployment
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:
Direct them to the , where they can start the claiming process for their Dai.
Build an interface to handle the ES process on your platform, inform your users, and have them act accordingly.
Scenario: Shutdown
Inform users to claim equivalent value of Dai in Collateral on the or create an interface to handle the process locally.
Scenario: Redeployment
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.
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
Claim users’ funds through the or by direct interaction with the migration contracts, and make them available in their accounts.
Scenario: Redeployment
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.
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.
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.
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.
. Support channels include but are not limited to:
#general
#dev
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.
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.
Inform users to migrate their Dai on the migration portal, or create an internal interface to handle the process locally.
Add featured support for new token(s).
Inform users to migrate their Dai to the new Dai (and MKR if applicable) on the migration portal, or create an interface to handle the process on your platform.
Add new token(s) to the exchange.
Migrate users’ funds to a new redeployed system using the migration portal or by interacting directly with the migration contracts.
#help
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.
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
gem: collateral tokens.
dai: stablecoin tokens.
sin: unbacked stablecoin (system debt, not belonging to any urn).
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
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
vat: a VatLike that points the the system's contract
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
sin: the system debt queue.
Sin: the total amount of debt in the queue.
Ash: the total amount of on-auction debt.
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
Flap: surplus auction (selling stablecoins for MKR) [contract]
wards [usr: address]: rely/deny/auth Auth Mechanisms [uint]
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)
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 packed 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
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.
Events
Bite: emitted when a bite(bytes32, address) is successfully executed. Contains:
ilk: Collateral
urn
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
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
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
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
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.
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
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
This project requires
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.
For other known Ubuntu and macOS issues please visit the README.
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.
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.
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.
Head to Oasis Borrow .
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
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.
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.
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.
rad: a fixed point integer, with 45 decimal places.
file: administer some configuration value
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
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.
arturnilkdebt: is vice plus the sum of ilk.Art * ilk.rate across all ilk's.
bite: initiate liquidation of a Vault
flip: liquidate collateral from a Vault to cover a fixed quantity of 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
ticendusrgaltabbid: 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 ids
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 bids 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.
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 Bids 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.
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 ids
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
ilkdai: 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.
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).
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
vat: the core of the mcd system
par: the relationship between DAI and 1 unit of value in the price. (Similar to TRFM)
dai savings rate1ONE = 10^27chi: 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.


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
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.
Flapperflapflopper - 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).
X-code (for Macs)
--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 dealing 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.
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.
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.
bin/auction-keeper --model '../my-bidding-model.sh' [...]{"id": "6", "flapper": " 0xf0afc3108bb8f196cf8d076c8c4877a4c53d4e7c ", "bid": "7.142857142857142857", "lot": "10000.000000000000000000", "beg": "1.050000000000000000", "guy": " 0x00531a10c4fbd906313768d277585292aa7c923a ", "era": 1530530620, "tic": 1530541420, "end": 1531135256, "price": "1400.000000000000000028"} {"price": "150.0", "gasPrice": 7000000000}git clone https://github.com/makerdao/auction-keeper.gitcd auction-keepergit submodule update --init --recursivepython3 -m venv _virtualenv
source _virtualenv/bin/activatepip3 install -r requirements.txt #!/usr/bin/env bash
echo "{\"price\": \"150.0\"}" # put your desired fixed price amount here
sleep 60 # locking the price for a 60 seconds periodSERVER_ETH_RPC_HOST=https://your-ethereum-node
SERVER_ETH_RPC_PORT=8545
ACCOUNT_ADDRESS=0x16Fb96a5f-your-eth-address-70231c8154saf
ACCOUNT_KEY="key_file=/Users/username/Documents/Keeper/accounts/keystore,pass_file=/Users/username/Documents/keeper/accounts/pass"#!/bin/bash
dir="$(dirname "$0")"
source my_environment.sh # Set the RPC host, account address, and keys.
source _virtualenv/bin/activate # Run virtual environment
# Allows keepers to bid different prices
MODEL=$1
bin/auction-keeper \
--rpc-host ${SERVER_ETH_RPC_HOST:?} \
--rpc-port ${SERVER_ETH_RPC_PORT?:} \
--rpc-timeout 30 \
--eth-from ${ACCOUNT_ADDRESS?:} \
--eth-key ${ACCOUNT_KEY?:} \
--type flip \
--ilk ETH-A \
--from-block 14764534 \
--vat-dai-target 1000 \
--model ${dir}/${MODEL} \
2> >(tee -a -i auction-keeper-flip-ETH-A.log >&2)019-10-31 13:33:08,703 INFO Keeper connected to RPC connection https://parity0.kovan.makerfoundation.com:8545
2019-10-31 13:33:08,703 INFO Keeper operating as 0x16Fb96a5fa0427Af0C8F7cF1eB4870231c8154B6
2019-10-31 13:33:09,044 INFO Executing keeper startup logic
2019-10-31 13:33:09,923 INFO Sent transaction DSToken('0x1D7e3a1A65a367db1D1D3F51A54aC01a2c4C92ff').approve(address,uint256)('0x9E0d5a6a836a6C323Cf45Eb07Cb40CFc81664eec', 115792089237316195423570985008687907853269984665640564039457584007913129639935) with nonce=1257, gas=125158, gas_price=default (tx_hash=0xc935e3a95e5d0839e703dd69b6cb2d8f9a9d3d5cd34571259e36e771ce2201b7)
2019-10-31 13:33:12,964 INFO Transaction DSToken('0x1D7e3a1A65a367db1D1D3F51A54aC01a2c4C92ff').approve(address,uint256)('0x9E0d5a6a836a6C323Cf45Eb07Cb40CFc81664eec', 115792089237316195423570985008687907853269984665640564039457584007913129639935) was successful (tx_hash=0xc935e3a95e5d0839e703dd69b6cb2d8f9a9d3d5cd34571259e36e771ce2201b7)
2019-10-31 13:33:13,152 WARNING Insufficient balance to maintain Dai target; joining 91.319080635247876480 Dai to the Vat
2019-10-31 13:33:13,751 INFO Sent transaction <pymaker.dss.DaiJoin object at 0x7fa6e91baf28>.join('0x16Fb96a5fa0427Af0C8F7cF1eB4870231c8154B6', 91319080635247876480) with nonce=1258, gas=165404, gas_price=default (tx_hash=0xcce12af8d27f9d6185db4b359b8f3216ee783250a1f3b3921256efabb63e22b0)
2019-10-31 13:33:16,491 INFO Transaction <pymaker.dss.DaiJoin object at 0x7fa6e91baf28>.join('0x16Fb96a5fa0427Af0C8F7cF1eB4870231c8154B6', 91319080635247876480) was successful (tx_hash=0xcce12af8d27f9d6185db4b359b8f3216ee783250a1f3b3921256efabb63e22b0)
2019-10-31 13:33:16,585 INFO Dai token balance: 0.000000000000000000, Vat balance: 91.319080635247876480133691494546726938904901298
2019-10-31 13:33:16,586 INFO Watching for new blocks
2019-10-31 13:33:16,587 INFO Started 1 timer(s)seth send 0xcbd3e165ce589657fefd2d38ad6b6596a1f734f6 'gulp(address)' 0xaaf64bfcc32d0f15873a02163e7e500671a4ffcdA 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)
Git
This project requires
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:
Read the following document for other known Ubuntu and macOS issues ().
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.
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.
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).
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).
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.
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.
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).
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 periods are set to 1-hour and 1-day and the amounts 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
The data templating language that can be used for the configuration file.
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
Additionally, we have a Uniswap price feed that can be used by Market Maker Keepers: .
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.
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.
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.
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).
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>
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.
We are here to help! We welcome any questions about market making in the channel in the Maker Chat.
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)
X-code (for macs)
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')
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.
In addition to the buy orders, when the Market Maker Keeper starts up, two sell orders will also be placed.
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.
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).
Lastly, you can run the keeper (as seen below).
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.
git clone [email protected]:makerdao/market-maker-keeper.git
cd market-maker-keeper git submodule update --init --recursive python3 -m venv _virtualenv
source _virtualenv/bin/activate python3 -Vpip3 install $(cat requirements.txt $(find lib -name requirements.txt | sort) | sort | uniq | sed 's/ *== */==/g')pip3 install -r requirements-dev.txt
./test.sh
===================================== test session starts =====================================
platform darwin -- Python 3.6.6, pytest-3.3.0, py-1.8.0, pluggy-0.6.0
rootdir: /Users/charlesst.louis/market-maker-keeper, inifile:
plugins: timeout-1.2.1, mock-1.6.3, cov-2.5.1, asyncio-0.8.0
collected 97 items
tests/test_airswap_market_maker_keeper.py ................ [ 16%]
tests/test_band.py ...... [ 22%]
tests/test_etherdelta_market_maker_keeper.py .......................... [ 49%]
tests/test_feed.py . [ 50%]
tests/test_limit.py ....... [ 57%]
tests/test_oasis_market_maker_cancel.py ... [ 60%]
tests/test_oasis_market_maker_keeper.py ................... [ 80%]
tests/test_price_feed.py ............ [ 92%]
tests/test_reloadable_config.py ....... [100%]
---------- coverage: platform darwin, python 3.6.6-final-0 -----------
Name Stmts Miss Cover
---------------------------------------------------------------------------
market_maker_keeper/__init__.py 0 0 100%
market_maker_keeper/airswap_market_maker_keeper.py 252 142 44%
market_maker_keeper/band.py 260 37 86%
market_maker_keeper/bibox_market_maker_keeper.py 93 93 0%
market_maker_keeper/bitinka_market_maker_keeper.py 96 96 0%
market_maker_keeper/bittrex_market_maker_keeper.py 96 96 0%
market_maker_keeper/coinbase_market_maker_keeper.py 103 103 0%
market_maker_keeper/coinbene_market_maker_keeper.py 95 95 0%
market_maker_keeper/control_feed.py 7 5 29%
market_maker_keeper/ddex_market_maker_keeper.py 126 126 0%
market_maker_keeper/ercdex_market_maker_keeper.py 12 12 0%
market_maker_keeper/etherdelta_market_maker_keeper.py 193 142 26%
market_maker_keeper/ethfinex_market_maker_keeper.py 94 94 0%
market_maker_keeper/feed.py 84 46 45%
market_maker_keeper/gas.py 20 10 50%
market_maker_keeper/gateio_market_maker_keeper.py 106 106 0%
market_maker_keeper/gopax_market_maker_keeper.py 99 99 0%
market_maker_keeper/hitbtc_market_maker_keeper.py 98 98 0%
market_maker_keeper/idex_market_maker_keeper.py 193 193 0%
market_maker_keeper/imtoken_pricing_server.py 51 51 0%
market_maker_keeper/imtoken_utils.py 97 97 0%
market_maker_keeper/kucoin_market_maker_keeper.py 108 108 0%
market_maker_keeper/limit.py 46 0 100%
market_maker_keeper/liquid_market_maker_keeper.py 97 97 0%
market_maker_keeper/mpx_market_maker_keeper.py 137 137 0%
market_maker_keeper/oasis_market_maker_cancel.py 38 22 42%
market_maker_keeper/oasis_market_maker_keeper.py 133 94 29%
market_maker_keeper/okex_market_maker_keeper.py 92 92 0%
market_maker_keeper/order_book.py 219 188 14%
market_maker_keeper/order_history_reporter.py 38 26 32%
market_maker_keeper/paradex_market_maker_keeper.py 131 131 0%
market_maker_keeper/price_feed.py 187 86 54%
market_maker_keeper/reloadable_config.py 67 3 96%
market_maker_keeper/setzer.py 24 17 29%
market_maker_keeper/spread_feed.py 7 5 29%
market_maker_keeper/tethfinex_market_maker_keeper.py 149 149 0%
market_maker_keeper/theocean_market_maker_keeper.py 129 129 0%
market_maker_keeper/util.py 8 4 50%
market_maker_keeper/zrx_market_maker_keeper.py 177 177 0%
market_maker_keeper/zrxv2_market_maker_keeper.py 26 26 0%
---------------------------------------------------------------------------
TOTAL 3988 3232 19%
================================== 97 passed in 4.04 seconds ==================================
{
"_buyToken": "DAI",
"buyBands": [
{
"minMargin": 0.005,
"avgMargin": 0.01,
"maxMargin": 0.02,
"minAmount": 20.0,
"avgAmount": 30.0,
"maxAmount": 40.0,
"dustCutoff": 0.0
},
{
"minMargin": 0.02,
"avgMargin": 0.025,
"maxMargin": 0.03,
"minAmount": 40.0,
"avgAmount": 60.0,
"maxAmount": 80.0,
"dustCutoff": 0.0
}
],
"buyLimits": [],
"_sellToken": "ETH",
"sellBands": [
{
"minMargin": 0.005,
"avgMargin": 0.01,
"maxMargin": 0.02,
"minAmount": 2.5,
"avgAmount": 5.0,
"maxAmount": 7.5,
"dustCutoff": 0.0
},
{
"minMargin": 0.02,
"avgMargin": 0.025,
"maxMargin": 0.05,
"minAmount": 4.0,
"avgAmount": 6.0,
"maxAmount": 8.0,
"dustCutoff": 0.0
}
],
"sellLimits": []
}"buyLimits": [
{
"period": "1h",
"amount": 50.0
},
{
"period": "1d",
"amount": 200.0
}
]{
"_price": 10,
"_buyToken": "DAI",
"buyBands": [
{
"minMargin": 0.020,
"avgMargin": 0.050,
"maxMargin": 0.075,
"minAmount": 0.05 * $._price,
"avgAmount": 0.25 * $._price,
"maxAmount": 0.35 * $._price,
"dustCutoff": 0.1
}
],
"_sellToken": "ETH",
"sellBands": []
}#!/bin/bash
bin/oasis-market-maker-keeper \
--rpc-host 127.0.0.1 \
--rpc-port \
--rpc-timeout 10 \
--eth-from [address of your generated Ethereum account] \
--eth-key ${ACCOUNT_KEY} \
--tub-address 0x448a5065aebb8e423f0896e6c5d525c040f59af3 \
--oasis-address 0x14fbca95be7e99c15cc2996c6c9d841e54b79425 \
--price-feed fixed:200 \
--buy-token-address [address of the quote token, could be DAI] \
--sell-token-address [address of the base token, could be WETH] \
--config [path to the json bands configuration file, e.g bands.json] \
--smart-gas-price \
--min-eth-balance 0.001
V2_OASIS_SERVER1_ADDRESS=
V2_OASIS_SERVER1_KEY="key_file=/home/ed/Projects/member-account.json,pass_file=/home/ed/Projects/member-account.pass"
TUB_ADDRESS=0xa71937147b55deb8a530c7229c442fd3f31b7db2 # tub-address
SAI_ADDRESS=0xc4375b7de8af5a38a93548eb8453a498222c4ff2 # buy-token-address
WETH_ADDRESS=0xd0a1e359811322d97991e03f863a0c30c2cf029c # sell-token-address
OASIS_ADDRESS_NEW=0x4a6bc4e803c62081ffebcc8d227b5a87a58f1f8f # oasis-address
Level: Intermediate
Estimated Time: 30 minutes
Audience: Technical and commercial teams with partners and Dai holders
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:
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
Basic knowledge of the MakerDAO: Dai and/or Vault system.
The following section will outline a recommended migration process for different actors in the Maker ecosystem.
You control your private key
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.
The following figure outlines the migration flow:
You don’t control your private key
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
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.
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.
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.
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.
Notes on Instadapp
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.
Notes on MyEtherWallet
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.)
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.
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.
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.
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.
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”
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 as soon as possible about the timeline for your own upgrade to MCD.
Get acquainted with the updates to Keepers and Auctions in MCD with .
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.
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.
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:
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.
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.
Optional: Help your users migrate their CDP to MCD
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.
Get acquainted with the
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:
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.
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:
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.
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.
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 .
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.
The migration smart contracts are open source and can be found here:
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.
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 .
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.
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.
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.
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.
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.
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 .
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.
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
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.
Contact integrations team -
Rocket chat - #dev channel
After finishing this guide we think you’ll enjoy these next guides:
Learn about our progress towards the launch of .
Information:
Other Guides:
Source code/wiki:
Freeze Sai deposits/withdrawals
Use the Migration App/contract to upgrade all user holdings of Sai to Dai. (See more details in the Migration App/contract sections below.)
Point codebase to new Dai token contract address. The new token is deployed at 0x6b175474e89094c44da98b954eedeac495271d0f - use the updated logos found here for the new Dai token.
Rename listing/token to "Dai"
Unfreeze Dai deposits/withdrawals.
Inform users about Dai Savings Rate, which allows Dai holders to earn savings.
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.
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.
Optional: Link users to oasis.app to activate Dai Savings Rate.
Inform your users that they will be able to swap 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: Create a UI where users can activate Dai Savings Rate.
Optional: Link users to oasis.app to activate Dai Savings Rate.
Optional: Implement paying the gas cost of Dai transactions on behalf of your users.
Freeze access to CDP service for your users
Launch upgrade to your service that supports the new CDP core. The smart contract addresses and ABIs can be found here.
If you are using Dai.js for your CDP integration, see “Using Dai.js” 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.
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.
On the selected launch date:
Launch upgrade to your service that supports the new CDP core.
If you are using Dai.js for your CDP integration, see “Using Dai.js” below for how to upgrade your implementation to MCD.
If you have integrated directly with the CDP smart contracts, see “Direct integration with smart contracts” below for how to upgrade your implementation to MCD.
List the Multi-Collateral Dai token as "Dai"
Choose one of the following:
Option A: Point your users to migrate.makerdao.com at MCD launch date for CDP migration on their CDP dashboard. See also the Migration App section below.
Option B: Create your own UI for migration, by creating a frontend to interact with the migration contract (see section below on Migration Contract).
Option A: Point users to migrate.makerdao.com if your app is Web3 compatible.
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.
Point codebase to the new MCD smart contracts
Stop lending (deposits) and borrowing (withdrawals) of Sai
List the Multi-Collateral Dai token as "Dai". The new token is deployed at 0x6b175474e89094c44da98b954eedeac495271d0f - use the updated logos found here for the new Dai token.
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.
At launch:
List the Multi-Collateral Dai token as "Dai". The new token is deployed at 0x6b175474e89094c44da98b954eedeac495271d0f - use the updated logos found here for the new Dai token.
Launch support for Dai loans.
Stop creation of loans in Sai
Point users to for Sai migration
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.
List the Multi-Collateral Dai token as "Dai". The new token is deployed at 0x6b175474e89094c44da98b954eedeac495271d0f - use the updated logos found here for the new Dai token.
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.
Inform your users that they can migrate Sai to Dai at migrate.makerdao.com
Optional: Create a UI for carrying out the migration from Sai to Dai.
The Maker Protocol's Collateral Auction House (Liquidation System 2.0)
Contract Source(s): clip.sol, dog.sol, abacus.sol
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
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
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]
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
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.
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.
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,
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
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 (
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
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).
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.
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
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
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
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 ilks 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.
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
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 ClipperThe 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).
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
Holebarkilk.dirtilkilk.holebarkilkusr: the address to return any excess collateral tokpr: the address where DAI incentives will be sent
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
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.
2Clipper.kickClipper.redoliquidations disabled(3): This means no new liquidations (Clipper.kick), no takes (Clipper.take), and no resets (Clipper.redo).
maxwho: 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]
that the caller's specified maximum price is at least the current auction price
tailcusptopClipper.takeClipper.redoupdates several fields of the existing auction
tic to reset the auction start time
top with the current OSM price and buf percent
vat.sucks DAI to the kpr as an incentive if eligible
fires the Redo() event
fires the Yank() event
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.
clipperCall(
address, // recipient of DAI
uint256, // owe [rad]
uint256, // slice [wad]
bytes calldata
)function wards(address) external view returns (uint256);
function rely(address) external;
function deny(address) external;function vat() external view returns (address);
function vow() external view returns (address);function ilks(bytes32 ilk) external view returns (
address clip,
uint256 chop, // wad
uint256 hole, // rad
uint256 dirt); // radfunction live() external view returns (uint256);function Hole() external view returns (uint256);
function Dirt() external view returns (uint256);function file(bytes32 what, address data) external;
function file(bytes32 what, uint256 data) external;
function file(bytes32 ilk, bytes32 what, address data) external;
function file(bytes32 ilk, bytes32 what, uint256 data) external;function chop(bytes32 ilk) external view returns (uint256);function bark(bytes32 ilk, address urn, address kpr)
external returns (uint256 id);function digs(bytes32 ilk, uint256 rad) external;function cage() external;function wards(address) external view returns (uint256);
function rely(address) external;
function deny(address) external;function ilk() external view returns (bytes32);function dog() external view returns (address);
function vow() external view returns (address);
function spotter() external view returns (address);function calc() external view returns (address);function buf() external view returns (uint256); // ray
function tail() external view returns (uint256); // seconds
function cusp() external view returns (uint256); // ray
function chip() external view returns (uint256); // wad
function tip() external view returns (uint256); // radfunction chost() external view returns (uint256); // radfunction kicks() external view returns (uint256);function active(uint256 pos) external view returns (uint256);function sales(uint256) external view returns (
uint256 pos,
uint256 tab, // rad
uint256 lot, // wad
address usr,
uint96 tic, // Unix epoch
uint256 top); // rayfunction stopped() external view returns (uint256);function file(bytes32 what, address data) external;
function file(bytes32 what, uint256 data) external;function kick(uint256 tab, uint256 lot, address usr, address kpr)
external returns (uint256 id);function redo(uint256 id, address kpr) external;function take(
uint256 id,
uint256 amt, // wad
uint256 max, // ray
address who,
bytes calldata data) external;function list() external view returns (uint256[] memory);
function count() external view returns (uint256);function getStatus(uint256 id) external view returns (bool needsRedo, uint256 price);function upchost() external;function yank(uint256 id) external;sum_over_all_ilks(Dog.ilk.dirt) == Dog.Dirt (c26.1)sum_over_auction_ids(Clipper.sales[id].lot) <= Vat.gem(Clipper) (c27.1)sum_over_auctions_ids_and_ilks(Dog.ilk.clip.sales[id].tab) == Dog.Dirt (c28.1)sum_over_auctions_ids(Dog.ilk.clip.sales[id].tab) == Dog.ilk.dirt (c28.2)