Dolomite Margin - Glossary

To understand how Dolomite Margin works, it's essential to understand the following glossary of terms, which makes up its architecture.

Accounts

DolomiteMargin is Account based. Each Account is referenced by its owner wallet address and an account number unique to that owner address. Accounts have balances on each asset supported by DolomiteMargin, which can be either positive (indicating a net supply of the asset) or negative (indicating a net borrow of an asset). Accounts must maintain a certain level of collateralization, or they will be liquidated.

library Account {
    struct Info {
        address owner;
        uint256 number;
    }
}

Example

Wallet address 0x6b5Bb4E60821eCE7363CaFf836Be1A4f9e3559B3 has the following balances in its account number 123456:

  • ETH: 1,000

  • DAI: -10,000

  • USDC: -5,000

This account is borrowing 10,000 DAI and 5,000 USDC. These borrows are collateralized by 1,000 ETH. The account will earn interest on ETH and pay interest on DAI and USDC.

Markets

Please see the Markets page of the Gitbook.

Interest

Interest rates in DolomiteMargin are dynamic and set per Market. Each interest rate is automatically and algorithmically set based on the ratio of (total borrow) / (total supply) of that Market. Account balances either continuously earn (if positive) or pay (if negative) interest.

Interest is earned / paid continuously (down to the second). Rates on the protocol are denominated in interest paid per second, in APR.

library Interest {
    struct Rate {
        uint256 value;
    }
}
  • value: A uint256 integer that represents the interest per second, with 18 decimals of precision. Meaning a value of 3,170,979,198 equals ~10% APR. This is exemplified by the math below: 10% or 100000000000000000 = 100000000000000000 / 365 days per year / 86400 seconds per day

Accounting - Wei & Par

There are two types of balances amounts on DolomiteMargin: Wei and Par.

Wei

Wei refers to the actual token amount of an asset held in or owed by an account. Wei amounts are constantly changing as interest accrues on the balance. For example, if Bob deposits 10 DAI to a DolomiteMargin Account, its Wei balance would initially be 10. The balance would start increasing every second as Bob started earning interest on his DAI.

Likely, most times you will want to use Wei balances when reading data from DolomiteMargin.

library Types {
    struct Wei {
        bool sign; // true if positive
        uint256 value;
    }
}
  • sign: A boolean flag that indicates whether the value is positive (true) or negative (false). This is relevant for tracking whether a value represents a net deposit (positive) or a borrowed amount (negative).

  • value: A uint256 integer that holds the magnitude of the value in Wei. This field captures the actual amount of Wei being represented. The number of decimals corresponds with the corresponding token's number of decimals

Par

Par refers to an interest adjusted amount that is static and does not change on the protocol. These are the balances that are actually stored on the protocol smart contracts. The protocol uses the current market index (see below) to transform Par to Wei values.

library Types {
    struct Par {
        bool sign; // true if positive
        uint128 value;
    }
}
  • sign: A boolean flag that indicates whether the value is positive (true) or negative (false). This is relevant for tracking whether a value represents a net deposit (positive) or a borrowed amount (negative).

  • value: A uint256 integer that holds the magnitude of the value in Par. This field captures the actual amount of Par being represented. The number of decimals corresponds with the corresponding token's number of decimals

Amounts

Amounts in DolomiteMargin are denominated by 3 things:

  • value - the numerical value of the Amount

  • reference - one of:

    • AmountReference.Delta Indicates an amount relative to the existing balance

    • AmountReference.Target Indicates an absolute amount

  • denomination - one of:

    • AmountDenomination.Wei Indicates the amount is denominated in the actual units of the token being transferred ( See Wei)

    • AmountDenomination.Par Indicates the amount is denominated in "principal". DolomiteMargin uses these types of amounts in its internal accounting, and they do not change over time (See Par)

A very important thing to note is that amounts are always relative to how the balance of the Account being Operated on will change, not the amount of the Action occurring. So, for example you'd say withdraw(-10), because when you Withdraw, the balance of the Account will decrease. Attempting to do withdraw(+10) will cause DolomiteMargin to throw an error.

Index

Each Market has a global borrow index and supply index. These indexes are used to transform Par <-> Wei values using the formula:

Borrow Wei Balance = (Borrow Par Balance) * (Borrow Market Index)

and

Supply Wei Balance = (Supply Par Balance) * (Supply Market Index)

Indexes start at 1 upon the addition of the Market to the protocol. They increase based on how much interest has accrued for that asset. For example upon adding USDC both the borrow index and supply index for USDC were 1. Say over the next month 2% interest accrues to borrowers and 1% interest accrues to lenders (based on the interest rates and time that has passed). After this, the supply index will be 1.01, and the borrow index will be 1.02. These indexes will keep increasing based on interest accrued, forever.

Example

Alice deposits 10 DAI to the protocol (10 DAI in Wei). The supply index on DAI is currently 2. Using Supply Par Balance = (Supply Wei Balance) / (Supply Market Index) = 10 / 2 = 5, the protocol credits 5 Par balance to Alice's account.

Later, interest has accrued for DAI on the protocol, and now the supply index for DAI is 3. Now, Alice goes to withdraw her DAI. Her DAI Par balance is still 5 (Par does not change over time). Now the protocol calculates Supply Wei Balance = (Supply Par Balance) * (Supply Market Index) = 5 * 3 = 15, and sends Alice 15 DAI.

library Interest {
    struct Index {
        uint96 borrow;
        uint96 supply;
        uint32 lastUpdate;
    }
}
  • borrow: A uint96 integer representing the scaled index value for borrowed assets in the market. This value reflects the current state of borrowing activity within the market.

  • supply: A uint96 integer representing the scaled index value for supplied assets in the market. This value reflects the current state of asset supply within the market.

  • lastUpdate: A uint32 integer representing the timestamp of the last update to the interest index. This timestamp indicates when the index was last modified or recalculated.

Operators

Operators are entities that perform actions on behalf of an account. They come in three forms:

  1. The calling address (msg.sender) which interacts with DolomiteMargin

  2. A local operator, which is set when an Account calls setOperators(...) on DolomiteMargin. This is similar to setting a token approval, where an Account specifies which contracts (operators) can interact on their behalf.

    library Types {
        struct OperatorArg {
            address operator;
            bool trusted;
        }
    }
    
    function setOperators(
        Types.OperatorArg[] calldata args
    ) external;
  3. A global operator, which is set by the owner of DolomiteMargin to automatically allow a contract to interact on behalf of any account on DolomiteMargin. Global operators generally exist for two reasons:

    1. To provide liquidation smart contracts for underwater accounts

    2. To offer a smoother UX, since less set up needs to be taken per-user to enable certain features on the platform.

Actions

All state changes to an account happen through Actions. Actions can modify the balances of 1 or more Accounts. There is no such thing as a "Borrow" action on DolomiteMargin. Rather, actions can automatically borrow funds if Account balances decrease a user's balance below 0. The following Actions are supported by DolomiteMargin:

Deposit

Deposit funds into an Account. Funds are moved from the sender or an approved address to DolomiteMargin, and the Account's balance is incremented.

struct DepositArgs {
    Types.AssetAmount amount;
    Account.Info account;
    uint256 market;
    address from;
}
  • amount: An instance of the Types.AssetAmount struct, which represents the amount of assets to be deposited. If using Delta the amount must be positive.

  • account: An instance of the Account.Info struct that identifies the account to which the deposit will be made. This includes the account owner's address and a unique account number. The sender must be an approved operator of account.owner.

  • market: A uint256 integer representing the ID of the market into which the deposit will occur. This market ID specifies the particular asset market to interact with.

  • from: The address from which the assets for the deposit will be sourced. A token approval must be made from this address to DolomiteMargin

Withdraw

Withdraw funds from an Account. Funds are sent from DolomiteMargin to a specified address and the Account's balance is decremented.

struct WithdrawArgs {
    Types.AssetAmount amount;
    Account.Info account;
    uint256 market;
    address to;
}
  • amount: An instance of the Types.AssetAmount struct, which represents the amount of assets to be withdrawn. If using Delta the amount must be negative.

  • account: An instance of the Account.Info struct that identifies the account from which the withdrawal will be performed. This includes the account owner's address and a unique account number. The sender must be an approved operator of account.owner.

  • market: A uint256 integer representing the ID of the market from which the withdrawal will occur. This market ID specifies the particular asset market to interact with.

  • to: The address to which the withdrawn assets will be transferred.

Transfer

Transfer funds internally between two DolomiteMargin accounts. This action can only be invoked by an approved operator for both accounts.

struct TransferArgs {
    Types.AssetAmount amount;
    Account.Info accountOne;
    Account.Info accountTwo;
    uint256 market;
}
  • amount: An instance of the Types.AssetAmount struct, which represents the amount of assets to be transferred. This includes information about the value and whether it is positive (transfer from accountOne to accountTwo) or negative (transfer from accountTwo to accountOne).

  • accountOne: An instance of the Account.Info struct that identifies the source account from which the assets will be transferred. This includes the account owner's address and a unique account number. The sender must be an approved operator of accountOne.owner.

  • accountTwo: An instance of the Account.Info struct that identifies the destination account to which the assets will be transferred. This includes the account owner's address and a unique account number. The sender must be an approved operator of accountTwo.owner.

  • market: A uint256 integer representing the ID of the market in which the transfer will occur. This market ID specifies the particular asset market in which the transfer will take place.

Buy

Buy an asset on a decentralized exchange using another asset. Uses an Exchange Wrapper to interact with different decentralized exchanges. Causes the bought asset's balance to go up, and the asset used to do the buy's balance to go down. Example: Buy 1 WETH on Uniswap using DAI.

This action is analogous to swapTokensForExactTokens on Uniswap. To do swapExactTokensForTokens, refer to Sell, below.

There are no fees charged by Dolomite for performing this action. Performing a Buy action that reduces the user's balance to less than 0 (therefore making the Buy a Flash Loan) do not incur any Dolomite-specific fees. Users are still responsible for paying the fees charged by the external decentralized exchange they are using.

struct BuyArgs {
    Types.AssetAmount amount;
    Account.Info account;
    uint256 makerMarket;
    uint256 takerMarket;
    address exchangeWrapper;
    bytes orderData;
}
  • amount: An instance of the Types.AssetAmount struct, representing the amount of the asset that the buyer wants to exchange. This amount must resolve to a positive number.

  • account: An instance of the Account.Info struct, identifying the account initiating the Buy Action. This includes the account owner's address and a unique account number. The sender must be an approved operator of account.owner.

  • makerMarket: A uint256 integer indicating the market ID of the outputMarket.

  • takerMarket: A uint256 integer representing the market ID of the inputMarket.

  • exchangeWrapper: The address of the exchange wrapper contract that facilitates the trade execution. This is the contract responsible for interacting with the external protocol to execute the trade. Must conform to the IExchangeWrapper interface.

  • orderData: Additional data that might be needed for the trade execution. This can include order parameters specific to the exchange being used.

Sell

Sell an asset on a decentralized exchange for another asset. Uses an instance of IExchangeWrapper to interact with different decentralized exchanges. Causes the sold asset's balance to go down, and the received assets balance to go up. Example: Sell 1 WETH on Uniswap for DAI.

This action is analogous to swapExactTokensForTokens on Uniswap.

There are no fees charged by Dolomite for performing this action. Performing a Sell action that reduces the user's balance to less than 0 (therefore making the Sell a Flash Loan) do not incur any Dolomite-specific fees. Users are still responsible for paying the fees charged by the external decentralized exchange they are using.

struct SellArgs {
    Types.AssetAmount amount;
    Account.Info account;
    uint256 takerMarket;
    uint256 makerMarket;
    address exchangeWrapper;
    bytes orderData;
}
  • amount: An instance of the Types.AssetAmount struct, representing the amount of the asset that the buyer wants to exchange. This amount must resolve to a negative number.

  • account: An instance of the Account.Info struct, identifying the account initiating the Sell Action. This includes the account owner's address and a unique account number. The sender must be an approved operator of account.owner.

  • takerMarket: A uint256 integer indicating the market ID of the inutMarket.

  • makerMarket: A uint256 integer representing the market ID of the outputMarket.

  • exchangeWrapper: The address of the exchange wrapper contract that facilitates the trade execution. This is the contract responsible for interacting with the external protocol to execute the trade. Must conform to the IExchangeWrapper interface.

  • orderData: Additional data that might be needed for the trade execution. This can include order parameters specific to the exchange being used.

Trade

Trade assets with another account on DolomiteMargin internally. No actual tokens are moved, but Account balances are updated. Uses the IAutoTrader interface, which allows a smart contract to be specified which is called to determine the price of the trade.

The fees charged by Dolomite for performing this action depend on the implementation of the specific IAutoTrader contract. For example, the simple x * y = k AMM pools used by Dolomite charge a 0.3% fee for trades, which is factored into the trade price. If the instance of IAutoTrader is set to special on Dolomite Margin, then the instance of IAutoTrader can only be interacted with via a Global Operator.

struct TradeArgs {
    Types.AssetAmount amount;
    Account.Info takerAccount;
    Account.Info makerAccount;
    uint256 inputMarket;
    uint256 outputMarket;
    address autoTrader;
    bytes tradeData;
}
  • amount: An instance of the Types.AssetAmount struct, indicating the amount of the asset being traded. Can resolve to a positive or negative number. If using AssetReference.Target, it is calculated using the balances of the makerAccount.

  • takerAccount: An instance of the Account.Info struct, identifying the account that is initiating the trade as the taker. This includes the account owner's address and a unique account number. The sender must be an approved operator of takerAccount.owner.

  • makerAccount: An instance of the Account.Info struct, identifying the account that is providing the assets for the trade as the maker. This includes the account owner's address and a unique account number. The autoTrader must be an approved operator of makerAccount.owner.

  • inputMarket: A uint256 integer representing the market ID of the asset being given as input to the trade. This specifies the market where the taker's asset is listed.

  • outputMarket: A uint256 integer indicating the market ID of the asset to be received as output from the trade. This specifies the market where the maker's asset is listed.

  • autoTrader: The address of the auto-trading contract responsible for executing the trade between the taker and maker accounts. Must conform to the IAutoTrader interface.

  • tradeData: Additional data that might be required for the trade execution. This can include parameters specific to the auto-trading mechanism being used.

Call

Calls a function specified by the ICallee interface through the context of an Account. Does not modify Account balances. An example of how this can be used is for setting expiration on the Expiry contract.

struct CallArgs {
    Account.Info account;
    address callee;
    bytes data;
}
  • account: An instance of the Account.Info struct, identifying the account that is initiating the call action. This includes the account owner's address and a unique account number. The sender to DolomiteMargin must be an approved operator of account.owner.

  • callee: The address of the smart contract that the call action will be directed to. This contract is the recipient of the function call. Must implement the ICallee interface.

  • data: Additional data that is required for the function call. This can include any arbitrary data needed to execute the call.

Liquidate

Liquidates an undercollateralized Account. Operates on two Accounts: the liquidating Account, and the undercollateralized Account. Does not transfer any tokens, but just internally updates balances of accounts. This action can only be invoked on DolomiteMargin through a Global Operator. Liquidates at the price specified by the oracle. Example:

Starting Account Balances:

Liquidating Account (L): +100 DAI Undercollateralized Account (U): -1 ETH, +140 DAI ETH oracle price: $125 DAI oracle price: $1 Liquidation spread: 5%

The liquidate action causes 1 ETH to be transferred from L -> U, and 1 ETH * (($125/ETH) / ($1/DAI)) * 1.05 = 131.25 DAI to be transferred from U -> L. After the liquidation the balances will be:

Liquidating Account (L): +231.25 DAI, -1 ETH Undercollateralized Account (U): +8.75 DAI

struct LiquidateArgs {
    Types.AssetAmount amount;
    Account.Info solidAccount;
    Account.Info liquidAccount;
    uint256 owedMarket;
    uint256 heldMarket;
}
  • amount: An instance of the Types.AssetAmount struct, representing the amount of owedMarket that will be repaid onliquidAccount. This amount must resolve to a positive number.

  • solidAccount: An instance of the Account.Info struct, identifying the account that is in a healthy (solvent) state and initiating the liquidation action.

  • liquidAccount: An instance of the Account.Info struct, identifying the account that is undercollateralized and subject to being liquidated.

  • owedMarket: The market ID that corresponds to the market from which the undercollateralized account owes tokens.

  • heldMarket: The market ID that corresponds to the market in which the solvent account holds tokens that will be used as collateral.

Vaporize

Pulls extra funds from the insurance fund to re-collateralize an underwater account with only negative balances.

struct VaporizeArgs {
    Types.AssetAmount amount;
    Account.Info solidAccount;
    Account.Info vaporAccount;
    uint256 owedMarket;
    uint256 heldMarket;
}
  • amount: An instance of the Types.AssetAmount struct, representing the amount owedMarket that will be repaid for vaporAccount. The amount must resolve to a positive number.

  • solidAccount: An instance of the Account.Info struct, identifying the account that is in a healthy (solvent) state and initiating the vaporization action. The sender must be an operator of solidAccount.owner.

  • vaporAccount: An instance of the Account.Info struct, identifying the account that has bad debt and is being vaporized.

  • owedMarket: The market ID that corresponds to the market from which the undercollateralized account owes tokens.

  • heldMarket: The market ID that corresponds to the market in which the solvent account holds tokens that will be used as collateral.

Operations

Every state changing action to the protocol occurs through an Operation. Operations contain a series of Actions that each operate on an Account.

Multiple Actions can be strung together in an Operation to achieve more complex interactions with the protocol. For example, taking a short ETH position on DolomiteMargin could be achieved with an Operation containing the following Actions:

Sell ETH for DAI
Deposit DAI

Importantly collateralization is only checked at the end of an operation, so accounts are allowed to be transiently undercollateralized in the scope of one Operation. This allows for Operations like a Sell -> Deposit, where an asset is first sold, and the collateral is locked up as the second Action in the Operation.

The operate function in DolomiteMargin serves as the primary entry point for users and contracts to manage accounts and execute various actions within the DolomiteMargin protocol. This function enables the execution of a sequence of actions on one or more accounts, ensuring that account collateralization is maintained throughout the entire operation. Here's a breakdown of its parameters and functionality:

  • accounts: An array of Account.Info structs that represent the accounts on which the operation will be performed. Each account is specified by its owner's address and a unique account number. The accounts provided here should not contain duplicates, and each action within the actions parameter will refer to these accounts by their index in this array.

  • actions: An ordered array of Actions.ActionArgs structs that define the specific actions to be executed in the operation. The actions are processed sequentially in the order they appear in this array. Each action includes details such as the action type, the account to which it applies (referred to by its index in the accounts array), the market(s) involved, the amount of assets to be operated upon, and additional data specific to the action type.

Upon execution of the operate function, the following rules apply:

  • The caller (msg.sender) must be the owner or operator of all accounts involved in the operation, except for accounts being liquidated, vaporized, or traded with.

  • The series of actions provided in the actions array are executed in the order they are presented, forming a single "operation."

  • Account collateralization is verified and maintained only after the entire operation is completed. This ensures that account health is considered holistically.

library Account {
    struct Info {
        address owner;
        uint256 number;
    }
}

library Actions {
    enum ActionType {
        Deposit,   // Supply tokens
        Withdraw,  // Borrow tokens or withdraw supplied tokens to your wallet
        Transfer,  // Transfer balance between accounts (internally)
        Buy,       // Buy an amount of some token (externally)
        Sell,      // Sell an amount of some token (externally)
        Trade,     // Trade tokens against another account (internall)
        Liquidate, // Liquidate an undercollateralized or expiring account
        Vaporize,  // Use excess tokens to zero-out a completely negative account
        Call       // Send arbitrary data to an address that conforms to ICallee
    }

    struct ActionArgs {
        /// An enum value representing the type of action to be taken. This determines
        /// how the provided data will be parsed and processed by the protocol.
        ActionType actionType;
        /// The index into the `accounts` array where the primary account is located.
        /// This corresponds with `account` for Deposit, Withdraw, Buy, Sell or Call.
        /// This corresponds with `accountOne` for Transfer.
        /// This corresponds with `takerAccount` for Trade.
        /// This corresponds with `solidAccount` for Liquidate or Vaporize.
        uint256 accountId;
        /// The amount to use for this action
        Types.AssetAmount amount;
        /// This corresponds with `market` for Deposit, Withdraw, and Transfer.
        /// This corresponds with `makerMarket` for Buy.
        /// This corresponds with `takerMarket` for Sell.
        /// This corresponds with `inputMarket` for Trade.
        /// This corresponds with `owedMarket` for Liquidate or Vaporize.
        /// This is unused for `Call`.
        uint256 primaryMarketId;
        /// This corresponds with `takerMarket` for Buy.
        /// This corresponds with `makerMarket` for Sell.
        /// This corresponds with `outputMarket` for Trade.
        /// This corresponds with `heldMarket` for Liquidate or Vaporize.
        /// This is unused for Deposit, Withdraw, Transfer, and Call.
        uint256 secondaryMarketId;
        /// The address of the contract being called for this action. The contract
        /// must conform to the corresponding action's interface for it to work 
        /// properly, else it reverts. 
        /// This is unused for Deposit, Withdraw, Transfer, Liquidate, and Vaporize.
        address otherAddress;
        /// Corresponds with `accountTwo` for Transfer.
        /// Corresponds with `makerAccount` for Trade.
        /// Corresponds with `liquidAccount` for Liquidate.
        /// Corresponds with `vaporAccount` for Vaporize.
        /// This is unused for Deposit, Withdraw, Buy, Sell and Call.
        uint256 otherAccountId;
        /// Generic bytes to pass through to otherAddress if used. 
        /// Unused for Deposit, Withdraw, Transfer, Liquidate, and Vaporize.
        bytes data;
    }
}

function operate(
    Account.Info[] calldata accounts,
    Actions.ActionArgs[] calldata actions
) external;

Last updated