Understanding how the owners of the Dolomite Margin protocol can effectuate changes is important for disclosing risk.
In the initial stages of the protocol, the admin rights are owned by a simple multi signature wallet (Gnosis Safe) that is stood up behind a delayed multi signature wallet (custom-made by the dYdX team). The Gnosis Safe is a 2/3 wallet. Meaning, 2 signers are needed out of the 3 owners to execute a transaction.
The delayed multi signature wallet is solely owned by the Gnosis Safe, which means the security and ownership of the delayed multi signature wallet falls back on to the Gnosis Safe. On the other hand, the time delay falls completely on the delayed multi signature wallet; whatever delay is set for the delayed multi signature wallet indiscriminately requires that all transactions sent (except certain whitelisted ones) to it wait the same delay before the transaction can be executed. Meaning, all admin transactions involving the
DolomiteMarginprotocol require that
secondsTimeLockedamount of time must be waited before the transaction effectuates.
At the time of launch, the delay on the delayed multi signature wallet is 1 day (86,400 seconds). The intention is to raise it incrementally until the protocol is more battle-tested and ownership of the protocol becomes much more decentralized.
To check the current delay (in case these docs ever go out of sync!) you can convert the
secondsTimeLockedvalue from seconds to minutes/hours/days on Arbiscan.
Certain functions are able to bypass the
secondsTimeLockedrestriction and be executed immediately. The reason for having this is it allows the administrators to make quick changes in the event market conditions change whimsically,
DolomiteMarginis exploited, a bug is uncovered, or some other black swan event occurs. The following table showcases which functions can be bypassed.
Can Bypass Timelock?
DolomiteMargin initializes an immutable struct called
RiskLimitsupon deployment that defines that absolute maximum that certain parameters can ever be. Meaning, under no possible circumstance can these values change and under no possible circumstance can
RiskParams(the mutable counterpart of the
RiskLimitstruct) be changed to a value that exceeds a value set in
RiskLimits. The following values are set in
The highest that the ratio can be for liquidating under-water accounts. Permanently set at
2000000000000000000, which is equal to 200% collateralization.
The highest that the liquidation rewards can be when a liquidator liquidates an account. Permanently set at
500000000000000000, which is equal to a 50% reward.
The highest that the supply APR can be for a market, as a proportion of the borrow rate. Meaning, a rate of 100% (1e18) would give suppliers all the interest that borrowers are paying. A rate of 90% would give suppliers 90% of the interest that borrowers pay. Permanently set at
1000000000000000000, which is equal to 100% of the borrow rate going to suppliers.
The highest min margin ratio premium that can be applied to a particular market. Meaning, a value of 10% (1e17) would require borrowers to maintain an extra 10% collateral to maintain a healthy margin ratio. This value works by increasing the debt owed and decreasing the supply held for the particular market by this amount, by this the value. Permanently set at
2000000000000000000, which equals 200%. Meaning, if this value were set for an individual market's
2000000000000000000, and the protocol's minimum collateralization is 115%, that particular market will require a min collateralization of 345% in maintain sufficient collateralization.
The highest liquidation reward that can be applied to a particular market. This percentage is applied in addition to the liquidation spread in
RiskParams. This value has 18 decimals, meaning a value of 1e18 is 100%. It is applied to each market as follows:
liquidationSpread * (1 + spreadPremium). This value is permanently set at
2000000000000000000, which equals 200%.
The highest minimum borrowed value that can be set by the protocol (this is confusingly worded, we know!). This value is permanently set at
100000000000000000000or $1e-16. Meaning, the minimum borrowed value can never be set beyond
100000000000000000000 / 1e36dollars.
To verify any of the above values, you can visit Arbiscan and click the
Querybutton for the function
DolomiteMarginuses a struct for storing global risk-values called
RiskParamswhich is changeable by the admin of the protocol (subject to any time delays, of course). Many of these values have a counterpart in
RiskLimitsthat enforces limitations on what these numbers can be changed to. These values include the following:
The required ratio of over-collateralization. This value is currently set at
150000000000000000which corresponds with 115% collateralization. This value cannot be set below
marginRatioMax(which is permanently set at 200%) and has a theoretical lower limit of 0 (100% collateralization).
The liquidation reward paid from liquidated accounts to liquidators. This value is currently set at
50000000000000000which corresponds with a 5% liquidation reward. This value cannot be set below 0% nor above
spreadPremiumMax(which is permanently set at 200%).
The percentage of the borrower's interest fee that is passed to the suppliers. This value is currently set at
850000000000000000which corresponds with 85%. The remaining 10% is paid to the protocol as a fee.
The minimum borrow value that an account may have. This value is measured in dollars and has 36 decimals of precision. This value is currently set at 0 ($0).
The maximum number of markets a user can have a non-zero balance for in a given account. Recall, an account is defined as a user's address, partitioned by an
index. This value is currently set at 32. Meaning, a user can deposit 32 assets into each of
The following functions and parameters can be called or changed by the protocol's admin and are subject to the same, universal, time delays as well as any applicable limits defined above.
To verify any of these parameters for a particular market, you can first get the
marketIdfrom the Markets section of the docs. Alternatively, you can visit the DolomiteMargin smart contract on Arbiscan and get the
marketIdby calling the
getMarketIdByTokenAddressfunction, passing in the token address whose market ID you're seeking. Then, pass the
getMarketfunction or into individual market risk-oriented functions like:
Decimal.D256 memory marginPremium,
Decimal.D256 memory spreadPremium,
This function allows a new market to be added to DolomiteMargin. Upon initialization,
isRecyclable(explanation below) and the token's address cannot be changed. Other values that are initialized and can be changed include
This value defaults to
false. Setting this value to
truedisallows any new borrows from occurring for that market. This value is checked after each interaction with DolomiteMargin settles, meaning funds can still be flash borrowed (as long as they're returned in full), once the interaction settles.
This value cannot be changed after initialization of a new market. This value dictates whether a market is allowed to be removed altogether. This value exists mainly for the efficiency of the protocol, allowing market IDs to be reclaimed. The main use-case for this is for expirable markets, like if an option token is added that expires at a certain timestamp.
The contract address of the price oracle for this market. Must conform to the
IPriceOracleinterface. The price this contract returns has
36 - tokenDecimalsdecimals. For example,
1000000000000000000000000000000equals $1.00 for the USDC market, because USDC has 6 decimals.
Contract address of the interest setter for this market. Must conform to the
IInterestSetterinterface. This contract takes the utilization (the total amount borrowed and the total amount supplied) for a given market and determines the amount of interest paid by borrowers every second. Meaning, a value returned by this contract of
325000000is equivalent to
325000000 * 86400 * 365). The result has 18 decimals.
The multiplier to apply to the global
marginRatio. This value works by increasing the debt owed and decreasing the supply held for the particular market by
1 + marginPremium. Meaning, a
marginPremiumof 100000000000000000 (10%) and a marginRatio of
100000000000000000(110% collateralization) results in that particular market's
110% * 1.1 == 121%.
The multiplier to apply to the global
liquidationSpread. This value works by increasing the
Decimal.one + spreadPremium). Meaning, a
100000000000000000(10%) and a
50000000000000000, results in that particular market's
5% * 1.1 == 5.5%.
The maximum value that can be deposited into DolomiteMargin for this particular market. This allows the protocol to cap any additional risk that is inferred by allowing borrowing against assets with a lower market capitalization or assets with increased volatility. Setting this value to 0 is analogous to having no limit. This value can never be below 0. This value's number of decimals corresponds with the number of decimals this market's token has.
uint memory marketIds,
Removes a market entirely from DolomiteMargin, recovering its
marketId. This function can only be called for markets that have
isRecyclable(defined above) set to
salvagerparameter is used to send any excess tokens to it.
An operator is an external address that has the same permissions to manipulate an account within
DolomiteMarginas the owner of the account. Operators are simply addresses and therefore may either be externally-owned Ethereum accounts (EoAs) OR smart contracts. Operators are also able to act as AutoTrader contracts on behalf of the account owner if the operator is a smart contract and implements the
There are three kinds of operators that
- 1.Self operators - analogous to
msg.senderor the user
- 2.Local operators - users approve each of them manually, like a token approval
- 3.Global operators - users do not need to approve them manually, they have permission automatically
Global operators exist to increase the UX of Dolomite for the users. Without them, users would be forced to manually approve a lot of local operator contracts to achieve a competitive and smooth UX.
This function allows the admin to approve or disapprove a smart contract that has the permission to be an operator for all accounts on DolomiteMargin.A non-exhaustive list of global operators include the LiquidatorProxyV1 and DepositWithdrawalProxy, which exist to simplify the user experience or lower gas costs for Dolomite users by consuming less calldata.
With regard to the safety of user's funds, this function likely poses the largest risk on the system. If a buggy or malicious global operator is ever set, all of the users' funds could be at risk.
Therefore, all global operators must undergo extensive testing and review before being added to the system. Moreover, they should always be stuck behind a time delay to allow users to react to any new global operator contracts being added.
This function sets or unsets an instance of
IAutoTraderfrom needing to be called by a global operator. This exists to ensure certain contracts, like
Expirycan eventually only be called by trusted Keepers on the Chainlink Oracle network.
This function allows the admin to withdraw an ERC20 token for which there is an associated market. Only excess tokens can be withdrawn. The number of excess tokens is calculated by taking the current number of tokens held in DolomiteMargin, adding the number of tokens owed to DolomiteMargin by borrowers, and subtracting the number of tokens owed to suppliers by DolomiteMargin.
This function allows the admin to withdraw an ERC20 token for which there is no associated market. This is analogous to rescuing tokens sent to the protocol by accident.