See it on github

All about the MOC Stablecoin Platform

  1. Introduction
  2. Main Concepts
  3. Bucket
  4. Coverage
  5. Tokens
  6. Leveraged Instruments
  7. System states
  8. Architecture
  9. Public actions
  10. User actions
    1. Minting
    2. Redeeming
  11. Process actions
    1. Interest payments
    2. Settlement
    3. Liquidation
  12. Contracts
  13. MoC
  14. MoCState
  15. MoCBucketContainer
  16. MoCSettlement
  17. MoCBurnout
  18. MoCHelperLib
  19. MoCLibConnection
  20. MoCConverter
  21. MoCExchange
  22. MoCConnector
  23. MoCBProxManager
  24. MoCInrate
  25. MoCWhitelist
  26. MoCBase
  27. OwnerBurnableToken
  28. BProToken
  29. DocToken
  30. BtcPriceProvider
  31. Contracts Mocks
  32. Relevant patterns and choices
  33. Safe Math and precision
  34. Inheritance, Composition and Contract whitelisting
  35. Governance and Upgradability
  36. Block gas limit prevention
  37. Data Dictionary
  38. Getting started

Introduction

Money On Chain is a suite of smart contracts dedicated to providing a bitcoin-collateralized stable-coin, Dollar On Chain, (DoC); a passive income hodler-targeted token, BitPro (BPRO), and a leveraged Bitcoin investment instrument (BTCX series).
The rationale behind this is that deposits of Rootstock-BTC (RBTC) help collateralize the DoCs, BitPro absorbs the USD-BTC rate fluctuations, and BTC2X is leveraged borrowing value from BitPro and DoC holders, with a daily interest rate being paid to the former.

Main Concepts

Bucket

A bucket (MoCBucket struct) is a Tokens/RBTC grouping abstraction that represents certain state and follows certain rules.
It's identified by a name (currently C0 and X2).
It has a "balance" of RBTC, DoC, and BitPro.
If it's a leverage (X) bucket, it also stores the balances of the leveraged token (currently only BTC2X) holders (bproxBalances and activeBalances).
If it's instead a base bucket, it has a RBTC balance (inrateBag) from interests accumulated by leveraged instruments allocations, daily processing will move the corresponding daily payment from this "bag" to base bucket balance.
Balance accounting between buckets is articulated by a series of Smart Contracts that constitute the MOC ecosystem.

Coverage

Is the ratio between the RBTC locked (backing DoCs) and the total amount of RBTC, be it in a particular bucket or the whole system (usually referred as global).
Locked RBTC amount is a result of the amount of DoCs and their price in BTC (BTC/USD rate).

Tokens

DoC

Its value is pegged to one dollar, in the sense that the SC (using Oracle's btc/usd price) will always[^1] return the equivalent amount of Bitcoin to satisfy that convertibility.
It's targeted towards users seeking to avoid crypto's market volatility.
It's implemented as an ERC20 token, it can be traded freely, but minted/burned only by the Moc system.
The more DocS minted, the more BTC2X can be minted, since they are used for leverage.

[^1]: Needs sufficient collateral (coverage > 1) and redeems are only processed during Settlements

BitPro

It's targeted towards users seeking to hodl Bitcoins and also receive a passive income from it.
It's implemented as an ERC20 token, it can be traded freely, but minted/burned only by the Moc system.
The more BitPros minted (introducing RBTC to the system), the more coverage the system has, since they add value to the system without locking any.

Leveraged instruments

BTC2X

It's targeted towards users looking to profit from long positions in bitcoin, with two times the risk and reward.
Leveraged instruments borrows capital from base bucket (50% in a X2) and pay a daily[^1] rate to it as return.
It can not be traded freely and does not have an ERC20 interface. BTCX positions can be canceled any time though.

[^1]: Actually uses X amount of block that, given the network, will approximate daily intervals.

Oracle

It's crucial to the system workflow to have an up to date BTC-USD rate price feed to relay on. This is currently achieved by a separate contract so that it can be easily replaced in the future without affecting the MOC system. See BtcPriceProvider.

System states

System state is ruled by the global Coverage value, and it's relation with the Objective Coverage (Cobj), currently set to 4.

Healthy state of the system. Every Token can be minted, and every Token (besides non-free DoCs) can be redeemed.

When the coverage falls below a certain threshold (Cobj), BitPros can no longer be redeemed as a measure to keep the coverage as high as possible.
Additionally, DoCs can't be minted.

When the coverage falls below the next threshold (uTPDU, currently 1.6), BitPros are sold at a discounted price with the intention of pumping more bitcoins into the system.

If the former measures fail and the coverage falls below last threshold ( currently 1.04), the contracts are locked allowing only the redemption of remaining DoCs at the last available price.
Although DoC Tokens can still be transferred freely, BitPro Token on the other hand is permanently paused, as it has lost all of its value.
This state is irreversible, once the liquidation state is achieved on the contract, there is no coming back even if the price and/or coverage recovers.

Public Actions

We distinguish three types of public interactions with the SC:

User actions

Minting

Token emission (minting/burning) is only allowed to be done through MOC system, users cannot create or destroy tokens directly on the ERC20 contracts.

BitPro

Can only be minted in exchange for RBTC.
Given an amount of RBTC paid to the contract, the system calculates the corresponding BitPro amount to mint, RBTC and Bitpro balances are added to the base bucket and the new Tokens are sent to the user.
There's a discount sale, below a certain level of coverage (uTPDU, currently 1.6).
This increases coverage, and allows to mint (sent_btc/target_coverage)*btc_price extra DoCs, assuming the system is in the 'above coverage' state.

DoC

Can only be minted in exchange for RBTC.
Given an amount of RBTC paid to the contract, the system calculates the corresponding DoCs amount to mint [^1], RBTC and DoC balances are added to the base bucket and the new Tokens are sent to the user.

[^1]: The contract must be in the 'Above coverage' state, but given the minting itself lowers coverage, the amount of DoCs to be minted is limited by the preservation of this state. (See globalMaxDoc)

BTC2X

Can only be "minted" in exchange of RBTC.
The process for the minting is as follows:

The interests are discounted from the sent BTC, that is, if a user sends X BTC, they'll be assigned X - interests BTC2X equivalent.

Redeeming

BitPro

A user can "sell" their BitPro back to the contract and recover the corresponding amount of RBTC.
The contract must be in the 'Above coverage' state.
The BitPros and RBTC are simply discounted from the base bucket.

DoC

On settlement

A DoC redeem request can be created to redeem any amount of DoCs, but this will be processed on next settlement.
The intended amount can be greater than user's balance at request time, allowing to, for example, redeem all future user's DoCs regardless of whether their balance increases.
The redemption of DoCs at the settlement is explained in detail in its own section

Outside of settlement

Only free DoCs can be redeemed outside of the settlement.
Free DoCs are those that remain in the base bucket, that is, they were not transferred to another to provide leverage.
Tokens and their equivalent in RBTC are simply subtracted from the base bucket.

BTC2X

RBTC deposited are sent back to the user, alongside the refunded interests (waiting in inrateBag) for the remaining time until the settlement (not yet charged).
Associated DoCs are moved back to the base bucket.

Process Actions

Interest-payments

Leveraged positions are charged by an interest as return for the borrowed value.
On each investment, the total interest to be paid is calculated and pre-allocated in an common interest "bag" (inrateBag) for all payments.
Once a day[^1] the smart contract allows a transaction to move certain amount of RBTC from that bag, to the base bucket itself.
Once executed for the current block span, it gets blocked "waiting" for blocks to be mined until the conditions are meet again.

[^1]: based on a given number of blocks dependent on the network's mining rate.

Settlement

Similar to daily payments, settlements are a time based recurrent process which relies on block number to allow/reject execution. This is currently intended to execute on 90 day intervals, although it can be adjusted on deploy.
During settlement, two important events take place: deleveraging of leveraged positions, and Doc redeem request processing.

As this process involves array loops, it might require more than on call to complete. That's why it is wrapped on a TASK and the step amount parameter indicates the maximum number of iterations to be performed on each call. Take in account that during the execution (in between calls) some other functions won't be available to execute.

Deleveraging

Although the name deleveraging evokes a broader process, in this case it just refers to the "settlement" of all individual leveraged position. At this time, all interests should had been payed (as they depends inversely to days to settlement), so it's a matter of converting the remaining open positions to RBTC and give back the corresponding DoCs borrowed. Under the hood, it simply invokes redeem for each user account. Market will determine whether they had lost or earned money.

DoC redeem requests

As explained in the redeem section, docs are not entirely liquid but need to be programmed to be redeemed (burn DoC and retrieve RBTC). Users enter a list (currently called queue in anticipation of future pagination) waiting to be executed during settlement.
This events simply goes through the aforementioned collection burning the corresponding DoC amount[^1], sending the equivalent RBTC (at the current BTC-USD rate) and obviously updating the bucket balances in the process.
Said collection should be empty at the end of the process.

[^1]: Note that the intended amount it's not validated until processing, so obviously that amount would only be fulfilled if the user actually owns that amount of DoCs. If he has less, all of them will be redeemed.

System Liquidation

If the BTC/USD price drops drastically, an none of the incentive mechanisms along the coverage dropping prevents it to cross the liquidation threshold (currently: coverage < 1.04) the system will enter the liquidation state and the liquidation function will be available to be executed.
Although there is an specific method to evaluate liquidation (evalLiquidation), to guarantee this process is executed, the same logic is evaluated and, if needed, executed in every MoC state changing method. For example, mintDoC, redeemBPro, etc or even settlement itself; every that has the transitionState modifier actually.
Liquidation process will invalidate the BitPro Token (it cannot be transfer any more) as a precaution measure as it has no more RBTC backing it, it has no value.
It will go thru the burnout address collection and transfer the corresponding RBTC amount to each one accordingly to theirs DoC balances.

As this process involves array loops, it might require more than on call to complete. That's why it is wrapped on a TASK and the step amount parameter indicates the maximum number of iterations to be performed on each call.

Bucket Liquidation

Leveraged buckets will get to critical coverage values quicker than the system as a whole, so they will potentially cross liquidation threshold (currently liq = 1.04) sooner.
Bucket liquidation process is verified on every bucket State Transition (bucketStateTransition modifier), that being mint/redeem of leveraged (X) instruments. And there is also a public function to evaluate (and trigger if needed): evalBucketLiquidation.
For a leveraged bucket, reaching coverage liquidation point (~1) means that user's funds had absorbed all the price dropping, so this process just needs to return the borrowed value (to base bucket) and dissolve the position.

Commission splitting

By adding the CommissionSplitter contract and set it as the destination of Money on Chain commissions (just as a normal commission destination address), the splitting process can be made.

The CommissionSplitter contract will accummulate commissions until the split() function is called. At that moment a part of the commissions will be added to Money on Chain reserves using the Collateral Injection functionality and the other part will be sent to a final destination address.

Collateral injection

Collateral injection is the operation of adding reserveTokens to the system's reserves without minting RiskPro. This come in handy when reserves are runnning low and there is a need of Stable token minting.

This injection is made by sending funds directly to the main MoC Contract, this will result in executing the fallback function which will update the internal values according to the sent value.

Contracts architecture

MoC system is a network of cooperative contracts working together to ultimately provide an US dollar pegged ERC20 Token (DoC). In this sense, be can categorize then into 4 categories:

MoC

MoC is the main contract of the MoC ecosystem, it's the entry point of almost every public interaction with it and it articulates the main logic and relations between the rest of the contracts.
It is also the only one that receives RBTC and holds the actual value of the system. The only methods marked as payable belongs to this contract and corresponds with the actual two ways of adding "value" to the system minting BitPro and DoC: - function mintBPro() public payable transitionState() { ... } - function mintDoc() public payable transitionState() atLeastState(MoCState.States.AboveCobj) { ... }
You'll also notice that many of it's methods just "redirects" to a more specif contract, abstracting it from the msg.sender and msg.value; for example:

  /**
  * @dev Creates or updates the amount of a Doc redeem Request from the msg.sender
  * @param docAmount Amount of Docs to redeem on settlement [using dollarPrecision]
  */
  function redeemDocRequest(uint256 docAmount) public {
    settlement.addRedeemRequest(docAmount, msg.sender);
  }

MoC also handles the [System states][#system-states] by a series of modifiers:

  modifier atState(MoCState.States _state) {
    require(mocState.state() == _state, "Function cannot be called at this state.");
    _;
  }
  modifier atLeastState(MoCState.States _state) {
    require(mocState.state() >= _state, "Function cannot be called at this state.");
    _;
  }
  modifier bucketStateTransition(string bucket) {
    evalBucketLiquidation(bucket);
    _;
  }
  modifier transitionState()
  {
    mocState.nextState();
    if (mocState.state() == MoCState.States.Liquidated)
      liquidation();
    else
      _;
  }

CommissionSplitter

The CommissionSplitter is the contract used to implement Commission Splitting functionality.
The contract have two main properties that can be modified by Governance:

MoCState

It also defines the State enum options:

  enum States {
    // State 0
    Liquidated,
    // State 1
    BProDiscount,
    // State 2
    BelowCobj,
    // State 3
    AboveCobj
  }

MoCBucketContainer

MoCSettlement

MoCBurnout

[^1]: See # Well known issue and planned improvements for detail on vulnerabilities for array iterations.

MoCHelperLib

MoCLibConnection

MoCConverter

MoCExchange

MoCConnector

MoCBProxManager

MoCInrate

MoCWhitelist

MoCBase

OwnerBurnableToken

DocToken

BProToken

BtcPriceProvider

Contract Mocks

Mocks are for testing purposes, inheriting from MoC contracts and overriding of certain methods allows to expose or manipulates data that wouldn't be possible to unit test instead.

Relevant patterns and choices

Safe Math and precision

MoC system requires many mathematical operations, in this model just the 2 basic operations are used (addition/subtraction, multiplication/division). To protect against overflows, OpenZeppelin SaveMath library is used on any of this operations.
As current RSK EVM version does not "support" decimal values, it's also important to point out that in MoC every value that is mathematically a decimal, it's represented as an integer adjusted by a given value, which is called precision.

For example, let's take coverage formula:
cob = nB / lB
and suppose that on a given time nB=35 and lB=10, then
cob = 35 / 10 = 3.5
if we look at the MoCHelperLib coverage method:

/**
  Coverage = nB / LB

  @dev Calculates Coverage
  @param nB Total BTC amount [using reservePrecision]
  @param lB Locked bitcoins amount [using reservePrecision]
  @return Coverage [using coveragePrecision]
**/
function coverage(MocLibConfig storage libConfig, uint256 nB, uint256 lB) public view
  returns(uint256) {
  if (lB == 0) {
    return UINT256_MAX;
  }

  return nB.mul(libConfig.coveragePrecision).div(lB);
}

We notice that:

In our example, supposing coveragePrecision = 1*10^18

cob = nB.mul(1*10^18).div(lB) = 35*10^17

If we invert the operation order:

cob = nB.div(lB).mul(1*10^18) = 3*10^18 ===> loosing precision !!!

There are many different precision values, depending on the value "denomination" being used, all of them are statically defined on the Library:

contract MoCHelperLibMock {
  ...
  constructor() public {
    mocLibConfig.dollarPrecision = 10 ** 18;
    mocLibConfig.reservePrecision = 10 ** 18;
    mocLibConfig.coveragePrecision = 10 ** 18;
    mocLibConfig.ratePrecision = 10 ** 18;
    mocLibConfig.dayPrecision = 1;
  }

Even if many are equal, we keep them on separate variables to be able to adjust them accordantly on future formula changes.
Using unsigned int256 as the norm for all values, sets an upper limit to ~76 decimal places, so even if it's ok to multiply 18 precision values a couple of times, we need to be careful not to overflow nor lose precision.
Most of MoC methods signatures specify the expected precision of the given input and return values, as well of internal operation precision cancelling, for example:

  // [DISCOUNT] * [COV] / [COV] = [DISCOUNT]
  return bproLiqDiscountRate.mul(utpduCovDiff).div(utpduLiqDiff);

where the convention is to indicate the short description of the precision on brackets ([COV] = coveragePrecision).

Inheritance, Composition and Contract whitelisting

In general programming, Composition is preferred upon Inherence, but solidity presents new consideration to keep in mind when choosing each pattern. As composition means a new Contract, and that means intra-contracts calls, meaning public methods and confusing msg.sender scopes; one might be turn into diminish this and prioritize inherence. The problem then arises when contract code grows and deploy fees goes beyond block gas limit.
Managing this equilibrium was not easy, and we think we are far from having the ultimate clearer and efficient solution yet, but using the whitelisted contract address network pattern, gave us an easy and scalable way on which to relay for contract inter-dependencies.
We understand that this solution makes the system vulnerable on deploy stage, as there is a post deploy initilization phase that needs to be atomically completed in other to have the whole system ready, a hook in this process might be able to compromise it. A post deploy integrity check script might a good option to solve this in the future.

Block gas limit prevention

Although not recomended, dynamic array looping is needed to be performed on certain functions:

That's why this functions are wrapped on TASKs and might requires more than one call to complete. They receive a step amount parameter indicating the maximum number of iterations to be performed on each call.

Note: Some model simulations had shown ~100 items is close to RSK block gas limit.

Governance and Upgradability

MoC contracts subscribes to a governance implementation that allows an external contract to authorize changers to:

For further detail on Governance mechanism refer to Moc Governance project

Data Dictionary

getting started

Install dependencies

Run Ganache-cli

npm install -g ganache-cli;
ganache-cli
docker pull trufflesuite/ganache-cli;
docker run -d -p 8545:8545 trufflesuite/ganache-cli:latest

Run Rsk Local Node

Run Tests

With Coverage

Deploy

(Truffle suit)[https://github.com/trufflesuite/truffle] is recommended to compile and deploy the contracts. There are a set of scripts to easy this process for the different known environments. Each environment (and network) might defer in its configuration settings, you can adjust this values in the the migrations/config/config.json file.

At the end of the deployment the addresses of the most relevant contracts will be displayed. If you are interested in another contracts you should look inside some files depending if the contracts is upgradeable or not.

The addresses of the deployed proxies will be in a file called zos.<network-id>.json . There you will have to look inside the proxies object and look for the address of the proxy you are interested in. If you are interested in the address of a contract that has not a proxy you should look for it in the prints of the deployment or inside the builds/<contract-name>.json file.

Settings