Admin Privileges

Understanding how the owners of the Dolomite Margin protocol can effectuate changes is important for disclosing risk.

Primer

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 DolomiteMargin protocol require that secondsTimeLocked amount 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 secondsTimeLocked value from seconds to minutes/hours/days on Arbiscan.

Special Function Bypass

Certain functions are able to bypass the secondsTimeLocked restriction 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, DolomiteMargin is exploited, a bug is uncovered, or some other black swan event occurs. The following table showcases which functions can be bypassed.

Risk Limits

DolomiteMargin initializes an immutable struct called RiskLimits upon 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 RiskLimit struct) be changed to a value that exceeds a value set in RiskLimits. The following values are set in RiskLimits:

uint64 marginRatioMax

The highest that the ratio can be for liquidating under-water accounts. Permanently set at 2000000000000000000, which is equal to 200% collateralization.

uint64 liquidationSpreadMax

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.

uint64 earningsRateMax

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.

uint64 marginPremiumMax

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 RiskParams to 2000000000000000000, and the protocol's minimum collateralization is 115%, that particular market will require a min collateralization of 345% in maintain sufficient collateralization.

uint64 spreadPremiumMax

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%.

uint128 minBorrowedValueMax

The highest minimum borrowed value that can be set by the protocol (this is confusingly worded, we know!). This value is permanently set at 100000000000000000000 or $1e-16. Meaning, the minimum borrowed value can never be set beyond 100000000000000000000 / 1e36 dollars.

To verify any of the above values, you can visit Arbiscan and click the Query button for the function getRiskLimits.

Risk Params

DolomiteMargin uses a struct for storing global risk-values called RiskParams which is changeable by the admin of the protocol (subject to any time delays, of course). Many of these values have a counterpart in RiskLimits that enforces limitations on what these numbers can be changed to. These values include the following:

Decimal.D256 marginRatio

The required ratio of over-collateralization. This value is currently set at 150000000000000000 which corresponds with 115% collateralization. This value cannot be set below liquidationSpread nor above marginRatioMax (which is permanently set at 200%) and has a theoretical lower limit of 0 (100% collateralization).

Decimal.D256 liquidationSpread

The liquidation reward paid from liquidated accounts to liquidators. This value is currently set at 50000000000000000 which corresponds with a 5% liquidation reward. This value cannot be set below 0% nor above marginRatio or spreadPremiumMax (which is permanently set at 200%).

Decimal.D256 earningsRate

The percentage of the borrower's interest fee that is passed to the suppliers. This value is currently set at 850000000000000000 which corresponds with 85%. The remaining 10% is paid to the protocol as a fee.

Monetary.Value minBorrowedValue

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).

uint256 accountMaxNumberOfMarketsWithBalances

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 wallet-index[0], wallet-index[1] ... wallet-index[n], etc.


Market-Specific Risk Params

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 marketId from the Markets section of the docs. Alternatively, you can visit the DolomiteMargin smart contract on Arbiscan and get the marketId by calling the getMarketIdByTokenAddress function, passing in the token address whose market ID you're seeking. Then, pass the marketId into the getMarket function or into individual market risk-oriented functions like:

  • getMarketIsClosing

  • getMarketIsRecyclable

  • getMarketPriceOracle

  • getMarketInterestSetter

  • getMarketMarginPremium

  • getMarketSpreadPremium

  • getMarketMaxWei

Add Market

function ownerAddMarket(
    address token,
    IPriceOracle priceOracle,
    IInterestSetter interestSetter,
    Decimal.D256 memory marginPremium,
    Decimal.D256 memory spreadPremium,
    uint256 maxWei,
    bool isClosing,
    bool isRecyclable
);

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 isClosing, priceOracle, interestSetter, marginPremium, spreadPremium, and maxWei.

bool isClosing

This value defaults to false. Setting this value to true disallows 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.

bool isRecyclable

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.

IPriceOracle priceOracle

The contract address of the price oracle for this market. Must conform to the IPriceOracle interface. The price this contract returns has 36 - tokenDecimals decimals. For example, 1000000000000000000000000000000 equals $1.00 for the USDC market, because USDC has 6 decimals.

IInterestSetter interestSetter

Contract address of the interest setter for this market. Must conform to the IInterestSetter interface. 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 325000000 is equivalent to 1.02% APR (325000000 * 86400 * 365). The result has 18 decimals.

Decimal.D256 marginPremium

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 marginPremium of 100000000000000000 (10%) and a marginRatio of 100000000000000000 (110% collateralization) results in that particular market's marginRatio equalling 110% * 1.1 == 121%.

Decimal.D256 spreadPremium

The multiplier to apply to the global liquidationSpread. This value works by increasing the liquidationSpread by (Decimal.one + spreadPremium). Meaning, a spreadPremium of 100000000000000000 (10%) and a liquidationSpread of 50000000000000000, results in that particular market's liquidationSpread equalling 5% * 1.1 == 5.5%.

Types.Wei maxWei

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.

Other Admin Functions

Set Global Operator

What is an operator?

An operator is an external address that has the same permissions to manipulate an account within DolomiteMargin as 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 IAutoTrader interface.

What kinds of operators exist?

There are three kinds of operators that DolomiteMargin supports:

  1. Self operators - analogous to msg.sender or 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

Why do global operators exist?

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.

function ownerSetGlobalOperator(
    address operator,
    bool approved
) public;

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.

Set Auto Trader Special

function ownerSetAutoTraderSpecial(
    address autoTrader,
    bool special
) public;

This function sets or unsets an instance of IAutoTrader from needing to be called by a global operator. This exists to ensure certain contracts, like Expiry can eventually only be called by trusted Keepers on the Chainlink Oracle network.

Withdraw Excess Token

function ownerWithdrawExcessTokens(
    uint256 marketId,
    address recipient
) public;

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.

Withdraw Unsupported Tokens

function ownerWithdrawUnsupportedTokens(
    address token,
    address recipient
) public;

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.

Last updated