【劉文彬】【精解】EOS標準貨幣體系與源碼實現分析

原文連接:醒者呆的博客園,www.cnblogs.com/Evsward/p/e…html

EOS智能合約中包含一個exchange合約,它支持用戶建立一筆交易,是任何兩個基本貨幣類型之間的交易。這個合約的做用是跨不一樣幣種(都是EOS上的標準貨幣類型)的,經過各自與EOS主鏈價值進行錨定,而後再相互發起交易兌換。要搞清楚的是,這與區塊鏈「傳統的」交易所並不同,那個主要是集中在交易撮合上面,並且必須是同一幣種。java

關鍵字:EOS token 經濟模型,exchange,Pegged Currency,LTV,cmake,跨token交易,ubuntu編譯boost庫,通證模型,抵押資產,token價值轉換c++

標準貨幣體系

上面一直提到標準貨幣(standard currency),在EOS.io中也針對這一標準貨幣的設計給出了官方文檔,本章先來弄清楚標準貨幣的概念,而後再繼續分析exchange。算法

文章的名字叫《Pegged Derivative Currency Design》數據庫

Pegged Currency的含義是什麼?
複製代碼

Currency pegging是用來固定匯率的一種方式,經過匹配當前貨幣與其餘貨幣的價值錨定,或者其餘價值度量方式,例如黃金和白銀。因此Pegged Currency能夠理解爲匯率穩定的貨幣。express

因此,這篇文章的題目能夠翻譯爲:一種價值穩定的衍生貨幣設計。編程

目前市面已有的Pegged Derivative 貨幣,例如BitUSD,比特美圓,它是一種以加密貨幣做爲抵押。發行人是短線持有美圓而額外長地持有加密貨幣。而購買者是單純地長期持有美圓。ubuntu

原始設計

比特股BitShares創造了第一個可行的價值穩定的資產系統:緩存

容許任何人得到一個最小的抵押情況是,公佈抵押物和得到的BitUSD的價值比率在最小的1.5:1(抵押物:負債率)。
複製代碼

最少的抵押情況下,要強迫BitUSD持有人在任何市場價格下跌超過美圓幾個百分點如下的時候的流動性(若是BitUSD持有人選擇使用強制清算是不容許的)。換句話說,就是在當前貨幣下跌的時候,也要保證貨幣流通性,這是爲了貨幣情況健康運營而考慮。bash

爲了防止價格補償(直接經過增發和賣出來控制價格)的濫用,全部的強制清算會被推遲。當發生「黑天鵝」事件(極小可能性發生,但實際上卻發生了)的時候,全部的短線會經過價格補償擁有他們本身的清算情況,而全部的BitUSD的持有者只能承諾一個固定的贖回率(清算時固定一個贖回率)。

這種設計的問題體如今:

  • 在BitUSD/BitShares市場體系普遍傳播下,其實流通性是很低的。
  • 短線持股人會承擔全部風險,只有在BitShares上漲時纔會賺取利潤。
  • 黑天鵝(BlackSwans)總會出現,而且伴隨巨大破壞力。
  • 這是一我的人爲己的一個模型。
  • 因爲風險收益率,供給會被限制。
  • 抵押物的苛刻要求限制了槓桿leverage。

新的想法

這是一種讓短線持股人經過提供一種高流通性的價值穩定的資產來穩訂貨幣資產。他們會經過鼓勵人們交易他們的穩值資產來獲益,賺取交易費而不是在投機市場尋求高槓杆。也會經過賺取短線持股的利息。

實施步驟

一個初始用戶存儲了一個可擔保貨幣(C)到一個智能合約,而且提供一個初始化價格補償。經過C:D等於1.5:1的價格補償率發行一個新的負債token(D),這個token會被存儲在Bancor市場製造商。

Bancor是爲以太坊的代幣的價值判斷以及流通機制。經過智能合約,可將這些代幣做爲儲備金,讓任何人隨時隨地經過智能合約進行快速兌換,銷燬代幣,提升代幣流通性。
複製代碼

這樣一來,由於沒有D token被賣出,因此市場製造商是0槓桿。那個初始用戶也會收到交換token(E)從市場製造商那裏。

咱們繼續,人們如今能夠購買E或者D token,Bancor算法會提供基於C,E,D之間的流通性。因爲市場製造商的收費,E的價值將隨着C而增加。

C = 智能代幣或者母貨幣儲備
D或E = 獎勵代幣(發放給初期持有人,以及社區貢獻者)
抵押率C:D = 價值C(抵押物)借款D(負債)比(反過來就是LTV,loan to value)
複製代碼

價值維穩

咱們作了這麼多工做,其實目的就是要保障D這種token(token自己就是衍生貨幣)是符合Pegged Currency的設定。最直接的指標就是與美圓價值(C就能夠是這個角色)的錨定浮動範圍,要儘量小,這個範圍的浮動要小到讓更多的人(套匯者)願意持有和交易D token。下面有幾種可能性:

  • D的價值超過美圓5%
    • 抵押物價值(原值)增長,C:D>1.5,這時候要發行更多的D token,來下降比率到1.5
    • 原值下降,C:D<1.5,調整token體量(減小市面流通量)以下降贖回價格(持有人不肯賠錢硬拋)來下降D token的價值到與美圓一致。

市場體量 = 聯結者體量(Bancor) 贖回價格:在到期以前,發行人能夠回購持有者的token。

  • D的價值少於美圓5%
    • 原值增長,C:D>1.5,調整token體量擡高贖回價格(持有人願意被贖回),從而提升市面上token的價值,最終遇上美圓。
    • 原值下降,C:D<1.5,這個比較複雜,由於token的價格要低於美圓,同時它的原值也低於負債,說明這個token已經真的價值下降了。那麼就須要增資補償
      • 停止其餘token,例如E到C和E到D的交易。
      • 在C到E和D到E的交易中間提供獎金(用來補償)。
      • 在D到E的轉化上會在循環外收到D,而不是加到市場製造商那裏。
      • 在C與D相互轉化上不作任何改變。
      • 停止嘗試調整製造商比率來防衛價格補償,使價格上漲至高於美圓1%(這是比較健康的)

exchange

基於上面標準貨幣體系,咱們能夠看到在EOS上面的token的經濟模型,這是一個頗有競爭力的模型,能夠保證每一個token的價值穩定,而不是狂漲狂跌,真正使EOS上的經濟生態健康穩定運轉起來。下面來研究exchange智能合約的主要功能。

CMakeLists

首先來看CMake設置,上文《【精解】EOS智能合約演練》中也有CMake的應用,但並無搞太清楚,這裏在討論exchange的CMakeLists配置以前,咱們先來搞定cmake自己。

cmake

CMake於C++ 相似maven於java的存在,它能夠用來對軟件進行構建、測試以及打包等工做。咱們在研究C++ 項目的時候,CMake是很好的構建工具,必定要熟悉掌握。
複製代碼

正如maven的配置文件主要是經過pom.xml同樣,CMake的工做是經過CMakeLists.txt文件來描述的。因此掌握CMakeLists.txt文件的配置方法是必要的。

  • cmake_minimum_required,這個配置位於第一行,指定了項目構建所需的CMake的最低版本。
  • project(banner),括號內填寫當前項目名。
  • set(MY_VAR "hello"),CMake能夠經過set關鍵字來設置文本變量。(至關於全局變量)
  • set (OTHER_VAR "{MY_VAR} world!"),能夠經過「{}」引用上面定義過的變量內容。

咱們在IDE中,也能夠像直接經過項目中的pom文件導入maven項目那樣,經過項目中的CMakelists.txt文件導入CMake項目。

像以上這種設置命令有不少,咱們能夠參照《CMake官方文檔:命令解釋》來查閱相關命令的含義以及使用。

exchange cmake

file(GLOB ABI_FILES "*.abi")
add_wast_executable(TARGET exchange
  INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}"
  LIBRARIES libc++ libc eosiolib
  DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR}
)
configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY)
add_dependencies( exchange currency )

add_executable(test_exchange test_exchange.cpp )
#bfp/lib/pack.c bfp/lib/posit.cpp bfp/lib/util.c bfp/lib/op2.c)
target_link_libraries( test_exchange fc )
target_include_directories( test_exchange PUBLIC fixed_point/include )
複製代碼
  • file是文件操做命令,它的參數:
    • GLOB,經過文件匹配找到文件列表而後將他們存入變量中。
    • ABI_FILES,變量名,會將匹配到的文件內容存儲在該變量中。
    • ".abi",globbing expressions,文件名匹配表達式。例如 「.vt?」,「f[3-5].txt」
  • add_wast_executable,cmake的自定義module。

咱們在源碼位置eos/CMakeModules中能夠找到wasm.cmake文件,進去之後能夠發現

macro(add_wast_executable)

自定義module也是以宏(命令集對外爲單一命令)的形式(Excel中我以前寫過宏腳本,也是同一個詞macro)。這一段add_wast_executable內容不少,我就不粘貼了,主要內容是爲了wasm環境構建代碼,包括對打包內容的描述,對狀態的判斷處理等各類命令的集合,其中又包含了不少module宏。

  • configure_file,複製一個文件到另外一個位置,並修改其內容。COPYONLY只複製不修改。
  • add_executable,增長依賴。
  • add_executable,添加一個可執行的項目使用指定的源文件。
  • target_link_libraries,link一個庫到target(target是第一個參數)。
  • target_include_directories,include目錄添加到target。

exchange.abi

abi文件是經過eosiocpp工具經過exchange.cpp生成的,具體可參照《EOS智能合約演練》。

exchange_accounts

從這裏開始咱們來詳細分析exchange合約的源碼內容,exchange.cpp須要引用exchange_accounts, exchange_state以及market_state這三個庫。其中market_state又依賴兩外兩個庫,所以咱們先從比較獨立的exchange_accounts入手。

exchange_accounts.hpp

#pragma once
#include <eosiolib/asset.hpp>
#include <eosiolib/multi_index.hpp>

namespace eosio {

   using boost::container::flat_map;// 至關於java中的import,下面能夠直接使用flat_map方法。

   /**
    *  每一個用戶都有他們本身的帳戶,而且這個帳戶是帶有exchange合約的。這可讓他們保持跟蹤一個用戶是如何抵押各類擴展資產類型的。假定存儲一個獨立的flat_map,包含一個特定用戶的全部餘額,這個用戶比起打破使用擴展標識來排序的多重索引表,將更加實際的
    */
   struct exaccount {
      account_name                         owner;// uint64_t類型,64位無符號整型
      flat_map<extended_symbol, int64_t>   balances;// extended_symbol是asset.hpp中定義的

      uint64_t primary_key() const { return owner; }// 結構體包含一個primary_key方法是不可變的,const,實現也有了,直接是返回owner。
      EOSLIB_SERIALIZE( exaccount, (owner)(balances) )// EOSLIB_SERIALIZE這是一種自定義的模板,是一種反射機制,能夠給結構體賦值,第一個參數爲結構體名字,後面的參數用括號分別括起來,傳入當前兩個成員變量。
   };

   typedef eosio::multi_index<N(exaccounts), exaccount> exaccounts;// multi_index是一個類,這行定義了一個變量exaccounts,它的類型是一種多重索引。


   /**
    *  提供一個抽象接口,爲用戶的餘額排序。這個類緩存了數據表,以提供高效地多重訪問。
    */
   struct exchange_accounts {
      exchange_accounts( account_name code ):_this_contract(code){}// 給私有成員賦值

      void adjust_balance( account_name owner, extended_asset delta, const string& reason = string() );//調整餘額,傳入owner、擴展資產,reason。exchange\_accounts.cpp會實現該函數。 

      private: 
         account_name _this_contract;// 私有成員 \_this\_contract
         /**
          *  保留一個緩存,用來存儲咱們訪問的全部用戶表
          */
         flat_map<account_name, exaccounts> exaccounts_cache;// flat_map類型的緩存exaccounts_cache,存儲的是帳戶名和以上結構體exaccounts。
   };
} /// namespace eosio
複製代碼

multi_index這裏再簡單介紹一下。它的模板定義是

template<uint64_t TableName, typename T, typename... Indices>

泛型中第一個參數是表名,第二個是多重索引。

N函數的源碼:

/**
    * @brief 用來生成一個編譯時間,它是64位無符號整型。傳入的參數X是一個base32編碼的字符串的解釋。
    * @ingroup types
    */
   #define N(X) ::eosio::string_to_name(#X)
複製代碼

可參考文章《EOS技術研究:合約與數據庫交互》,下面來看一下exchange_accounts.cpp源碼:

#include <exchange/exchange_accounts.hpp>

namespace eosio {

   void exchange_accounts::adjust_balance( account_name owner, extended_asset delta, const string& reason ) {
      (void)reason;// reason當作一個備註,不可修改的。

      auto table = exaccounts_cache.find( owner );//經過account\_name查找到對應的exaccount結構體對象數據。
      if( table == exaccounts_cache.end() ) {// 若是這個數據是最後一個,則將當前數據從新包裝放入exaccounts_cache,同時將exaccounts_cache第一位的數據從新賦值給table
         table = exaccounts_cache.emplace( owner, exaccounts(_this_contract, owner )  ).first;
      }
      auto useraccounts = table->second.find( owner );//table如今有值了,在table下一個位置查找owner
      if( useraccounts == table->second.end() ) {// 若是這個用戶是table下一個位置的結尾數據,則將owner從新組裝數據放入table
         table->second.emplace( owner, [&]( auto& exa ){
           exa.owner = owner;
           exa.balances[delta.get_extended_symbol()] = delta.amount;
           eosio_assert( delta.amount >= 0, "overdrawn balance 1" );//斷言,當extended_assert資產的數目小於0時,打印日誌:透支餘額1
         });
      } else {// 若是該用戶不是table下一個位置的結尾數據,則修改以該用戶爲key的數據,
         table->second.modify( useraccounts, 0, [&]( auto& exa ) {
           const auto& b = exa.balances[delta.get_extended_symbol()] += delta.amount;// 擴展標識的餘額加上extended_assert資產的數目爲b
           eosio_assert( b >= 0, "overdrawn balance 2" );// 斷言,當b小於0時,打印日誌:透支餘額2
         });
      }
   }

} /// namespace eosio
複製代碼

它實現了adjust_balance函數。這個函數主要實現了對帳戶數據的管理,餘額的判斷與處理。

exchange_state

exchange_state庫的源碼我就不張貼了,這裏進行一個總結:

  • exchange_state.hpp,頭文件中主要聲明瞭一些變量結構體,
    • 包括邊緣狀態margin_state,返回的是一個extended_asset
    • interest_shares,全部的給那些借出人分配的共享空間,當某人未借款,他們能夠得到total_lendable * user_interest_shares / interest_shares。當付過利息之後,會顯示在變量total_lendable。
    • exchange_state結構體是使用bancor數學建立一個在兩種資產類型中的50/50的中繼。這個bancor的狀態,exchange是徹底包含在這個結構體中。這個API沒有額外的影響和使用。
  • exchange_state.cpp,源文件中主要實現了頭文件中這幾個結構體中的一些函數,包括
    • convert_to_exchange,經過傳入一種extended_asset資產,將它轉換成exchange token,至關於token在原有發行量的基礎上,按照新的extended_asset資產抵押發行了新的token。
    • convert_from_exchange,經過傳入必定量的exchange token(注意exchange token也是extended_asset資產類型),將其轉化爲其餘extended_asset資產,至關於回購了部分token,下降了token保有量。
    • convert,傳入一個extended_asset資產參數,以及一個extended_symbol參數,經過判斷symbol的種類,調用以上convert_to_exchange或convert_from_exchange函數進行轉換處理,最終將傳入的extended_asset資產轉換爲攜帶extended_symbol。
    • requires_margin_call,傳入一個connector,connector在以上轉換函數中都做爲參數而且在轉換過程當中發生了做用,這裏是對connector參數進行判斷,是否須要調用邊緣處理(即與值peer_margin.total_lent.amount做比較)

下面是connector的源碼部分:

struct connector {
 extended_asset balance;// 餘額
 uint32_t       weight = 500;// 權重

 margin_state   peer_margin; /// peer_connector 抵押借貸餘額,margin_state類型

 EOSLIB_SERIALIZE( connector, (balance)(weight)(peer_margin) )仍是那個初始化工具。
};
複製代碼

exchange_state庫中最重要的函數就是上面這幾個轉換函數,掌握這些函數都能幹哪些事,將來咱們能夠直接測試調用或者在其餘源碼中出現繼續分析。

market_state

這是基於以上exchange_accounts以及exchange_state兩個庫的庫,它的內容也不少,不適宜所有粘貼出來。

  • market_state.hpp,該頭文件中包含告終構體
    • margin_position,咱們針對每個market/borrowed_symbol/collateral_symbol類型的數據計算了一個惟一的做用域,而後例舉了一個邊緣位置表,經過這個表,每一個用戶能夠明確地擁有一個位置,所以owner能夠做爲主鍵。
    • loan_position,借貸位置。
    • market_state(C++ 語法補充:結構體中也能夠有private成員,這跟類很類似了其實)。與邊緣位置或者限制單數一塊兒維護了一個狀態
  • market_state.cpp,源文件中實現了不少函數。這些函數實現了市場借貸關係,餘額數量等操做處理,具體咱們在exchange主庫中經過具體業務進行介紹。

exchange

這是整個exchange合約的主庫(一般我會將一個名字的頭文件加源文件合併稱爲一個庫,這也是C++ 的命名習慣)。

exchange.hpp

頭文件,主要聲明瞭一個類exchange,這裏麪包含了三個私有成員,以及七個公有函數,還有三個公有結構體,下面貼一下源碼吧:

#include <eosiolib/types.hpp>
#include <eosiolib/currency.hpp>
#include <boost/container/flat_map.hpp>
#include <cmath>
#include <exchange/market_state.hpp>

namespace eosio {

   /**
    *  這個合約可讓用戶在任意一對標準貨幣類型之間建立一個exchange,這個exchange是基於一個在購買方和發行方雙邊的價值等額的條件下而建立的。爲了預防舍入偏差,初始化金額應該包含大量的base以及quote貨幣的數量,而且exchange 共享應該在最大初始化金額的100倍的數量。用戶在他們經過exchange交易前,必須先存入資金到exchange。每次一個exchange建立一個新的貨幣時,相應的交易市場製造商也會被建立。貨幣供應以及貨幣符號必須是惟一的而且它使用currency合約的表來管理。
    */
   class exchange {
      private:
         account_name      _this_contract;// 私有,帳戶名
         currency          _excurrencies;// 貨幣
         exchange_accounts _accounts;// exchange的帳戶

      public:
         exchange( account_name self )
         :_this_contract(self),
          _excurrencies(self),
          _accounts(self)
         {}
         // 建立
         void createx( account_name    creator,
                       asset           initial_supply,
                       uint32_t        fee,
                       extended_asset  base_deposit,
                       extended_asset  quote_deposit
                     );
         // 訂金
         void deposit( account_name from, extended_asset quantity );
         // 提現
         void withdraw( account_name  from, extended_asset quantity );
         // 借出
         void lend( account_name lender, symbol_type market, extended_asset quantity );

         // 不借?
         void unlend(
            account_name     lender,
            symbol_type      market,
            double           interest_shares,
            extended_symbol  interest_symbol
         );

         // 邊緣覆蓋結構體
         struct covermargin {
            account_name     borrower;
            symbol_type      market;
            extended_asset   cover_amount;
         };
    
         // 上側邊緣
         struct upmargin {
            account_name     borrower;
            symbol_type      market;
            extended_asset   delta_borrow;
            extended_asset   delta_collateral;
         };
         // 交易結構體
         struct trade {
            account_name    seller;
            symbol_type     market;
            extended_asset  sell;
            extended_asset  min_receive;
            uint32_t        expire = 0;
            uint8_t         fill_or_kill = true;
         };

         // 函數名根據參數列表方法重載,在xxx上執行exchange
         void on( const trade& t    );
         void on( const upmargin& b );
         void on( const covermargin& b );
         void on( const currency::transfer& t, account_name code );

         // 應用
         void apply( account_name contract, account_name act );
   };
} // namespace eosio
複製代碼

exchange.cpp

該源文件中實現了以上頭文件中定義的全部公有方法。

測試

先定義兩個標準貨幣base和quote,他們都是exchange_state類型:

exchange_state state;
state.supply = 100000000000ll;// 發行量
//state.base.weight  = state.total_weight / 2.;
state.base.balance.amount = 100000000;
state.base.balance.symbol = "USD";
state.base.weight = .49;
//state.quote.weight = state.total_weight / 2.;
state.quote.balance.amount = state.base.balance.amount;
state.quote.balance.symbol = "BTC";
state.quote.weight = .51;

print_state( state );
複製代碼
插曲:ubuntu編譯boost庫
複製代碼

首先在boost官網下載最新庫文件,目前我下載的版本是boost_1_67_0.tar.bz2。

  • 下載好壓縮包,解壓縮tar --bzip2 -xf boost_1_67_0.tar.bz2
  • 解壓後的文件夾轉移到本身的習慣位置管理好,而後進入該目錄
  • 先執行./booststrap.sh進行boost庫編譯。
  • 再執行sudo ./b2 install進行命令安裝。

而後,咱們再打開CLion,CMake自動編譯項目eos,會發現console中已經顯式編譯成功的字樣。

接下來繼續咱們的測試。直接run 主函數,首先打印出來的是"USD"和"BTC"的發行信息,

-----------------------------
supply: 1e+11
base: 1e+08 USD
quote: 1e+08 BTC

-----------------------------
複製代碼

能夠看到,這與代碼中定義的總髮行量以及包含的兩種符號類型的token的各自發行量,都是準確的。

自定義數字資產類型

exchange_state是在測試類中咱們自定義的數字資產類型,下面是它的結構:

struct exchange_state {
   token_type  supply;// 發行量
   symbol_type symbol = exchange_symbol;// exchange符號

   // 兩個鏈接器base和quote
   connector  base;
   connector  quote;
   // 交易
   void transfer( account_name user, asset q ) {
      output[balance_key{user,q.symbol}] += q.amount;
   }
   map<balance_key, token_type> output; 
   vector<margin>               margins;
};
複製代碼

exchange_state數字資產中,包含一個總髮行量,兩個成員資產base和quote,他們是connector類型,這個類型也是自定義的(與上面介紹的源碼稍有不一樣,稍後在測試完成之後會總結他們的區別),交易函數以及一個自定義集合output和margins,下面來看connector的定義:

struct connector {
   asset      balance; // asset資產類型
   real_type  weight = 0.5;
   token_type total_lent; /// 發行商從用戶的貸款
   token_type total_borrowed; /// 發行商借給用戶
   token_type total_available_to_lend; /// 可借出的有效數量
   token_type interest_pool; /// 利息池,是所得到的總利息,但不必定每一個用戶均可以申請使用

   // 如下三個方法都在本文件下被實現了。
   void  borrow( exchange_state& ex, const asset& amount_to_borrow ); 
   asset convert_to_exchange( exchange_state& ex, const asset& input );
   asset convert_from_exchange( exchange_state& ex, const asset& input );
};
複製代碼

這個connector有一個餘額,一個權重(可理解爲佔有exchange_state數字資產的比例),它的一些銀行資產功能屬性,貸款拆借利息等,以及connector自己做爲資產能夠與其餘exchange_state數字資產進行轉換,拆借等功能。餘額成員是asset資產類型,這個類型也是一個自定義結構體:

struct asset {
   token_type amount;
   symbol_type symbol;
};
複製代碼

它具有一個總數量和符號兩個成員。因此以上咱們給exchange_state數字資產定義了兩個connector,「BTC」和「USD」以及它們各自的發行量,正是採用這個asset的結構進行賦值的。

打印出state內容之後,顯示的是兩種token"USD"和"BTC"的發行信息,接下來,咱們利用exchange中的一些函數功能進行兩種token之間的轉換及交易。

auto new_state = convert(state, "dan", asset{100, "USD"}, asset{0, "BTC"});
print_state(new_state);
複製代碼

看一下這裏面的convert函數的聲明:

/**
 *  經過給出的一個當前state,計算出一個新的state返回。
 */
exchange_state convert( const exchange_state& current,// 當前state
                        account_name user,// 用戶
                        asset        input,// 輸入資產
                        asset        min_output,// 最小輸出資產
                        asset*       out = nullptr) {
複製代碼

因此咱們來解讀第一行convert代碼的意思爲:

一個名爲「dan」的用戶,現有資產狀態爲上面已打印的state,輸入資產爲100個USD,最小輸出資產爲0個BTC(注意輸入資產和最小輸出資產必須是不一樣的,不然沒法轉化)。
複製代碼

下面看輸出print_\state結果:

-----------------------------
supply: 1e+11
base: 1e+08 USD
quote: 9.99999e+07 BTC
dan  96.0783 BTC
dan  0 EXC
dan  -100 USD
複製代碼

結果解讀:

  • supply和base的數量都沒變
  • quote的數量少了100個BTC(0.00001e+07)
  • dan的BTC多出來96.0783個。
  • dan的EXC爲0(本次交易中沒有涉及到,EXC是默認token符號)
  • dan的USD少了100個。

從新解讀這一行convert代碼的意思爲:

state數字資產(咱們最先設置的),dan根據state資產的格式拿出來本身帳戶中的100個USD(dan自己沒有USD,因此是欠款狀態)做爲抵押想exchange BTC,,而BTC是quote(base和quote也能夠理解爲用戶)的符號,因此quote的數量少了相應的100個BTC。最後,要將這100個BTC打入dan的帳戶裏面,而爲何編程了96.0783個而不是100個呢?
複製代碼

調試

上面咱們將問題拋了出來,下面咱們對代碼進行debug,來分析這100個BTC在發放給用戶的時候是如何轉變的?咱們打個斷點,開始運行程序,走到convert函數中,因爲咱們的USD等於base的符號,因此執行到了convert_to_exchange函數。

asset connector::convert_to_exchange(exchange_state &ex, const asset &input) {

    real_type R(ex.supply);// 1e+11
    real_type S(balance.amount + input.amount); //100000100,等於state資產得新發行100個USD
    real_type F(weight);//0.489999999999999991118,USD所佔比重,state初始化時已經設置好
    real_type T(input.amount);//100
    real_type ONE(1.0);//1

    auto E = R * (ONE - std::pow(ONE + T / S, F));// 根據這個算法獲得對應的state資產的增髮量的值。pow是cmath庫的一個函數,有兩個參數,返回結果爲第一個參數爲底,第二個參數爲冪值的結果。
    // (1e+11)*(1-(1+100/100000100)^+0.489999999999999991118),這得藉助計算器了,算出結果爲:-48999.9385,約等於程序執行結果-48999.938505084501827。

    token_type issued = -E; //48999.9385,增發100個USD,實際上要增發state這麼多。

    ex.supply += issued;// 更新總髮行量,加入以上計算的值。
    balance.amount += input.amount;//state的USD connector(可理解爲基於某穩值數字資產下的token)的餘額能夠增長100個USD了。

    return asset{issued, exchange_symbol};// 最後是以EXC資產增發48999.9385個的形式返回。
}
複製代碼
EXC是state的「原值」符號,USD和BTC是基於EXC抵押資產發行的數字token。
複製代碼

繼續調試,回到convert函數中。咱們得到了要對應增發EXC的數量,那麼要具體執行影響到state數字資產,是經過:

result.output[balance_key{user, final_output.symbol}] += final_output.amount;// 將增發EXC的數量添加至state的output集合中。

output存放形式:

  • 集合中一個元素位置,下標爲0開始存儲。
  • 每一個元素是一個pair類型。不懂C++ 中pair類型的能夠參考《C++ 語法》。能夠理解爲一個元組,包含一對值first和second。
  • first是一個自定義結構體balance_key,包含一個帳戶名稱成員和一個符號成員。這裏對應的是"dan","EXC"。
  • second是一個增髮量48999.9385。

結果就是EXC總帳戶經過dan增發了48999.9385,而後接下來繼續,

result.output[balance_key{user, input.symbol}] -= input.amount;
複製代碼

這是給dan帳戶進行減持,一樣的,咱們列出output的存放形式:

  • 下標1
  • pair類型,first,second
  • first是"dan","USD"
  • second是一個銷燬量100個。

結果就是dan我的帳戶欠了100個USD,dan在調用convert的時候,要求最小輸出資產是BTC類型的,而如今針對輸入資產類型USD以及EXC相應的操做已經作完。下面要作的是EXC和BTC的convert。

if (min_output.symbol != final_output.symbol) {// 當計算的最終輸出資產的符號與傳入的最小輸出資產不一致時,要調用自己convert來轉換。
    return convert(result, user, final_output, min_output, out);
}
複製代碼

攜帶新的參數EXC和BTC再次進入convert函數時,state數字資產已經發生了變化,它的總髮行量變爲100000048999.93851,base的USD的餘額變爲100000100,quote的BTC的餘額不變,仍舊爲1億。咱們新帶過來的參數是:

  • 48999.938505084501個EXC做爲輸入資產
  • 最小輸出資產仍舊爲第一次調用convert的0個BTC

因爲咱們這一次處理的輸入資產類型就是state的默認符號EXC,因此會走另一個處理分支,根據最小輸出資產類型會執行convert_from_exchange函數:

initial_output = result.quote.convert_from_exchange(result, initial_output);

convert_from_exchange函數源碼:

asset connector::convert_from_exchange(exchange_state &ex, const asset &input) {

    real_type R(ex.supply - input.amount);// 先找回原值:1e+11
    real_type S(balance.amount);// BTC餘額不變,仍爲1億個1e+8
    real_type F(weight);// 權重爲0.51
    real_type E(input.amount);// EXC的輸入數量48999.93851
    real_type ONE(1.0);

    real_type T = S * (std::pow(ONE + E / R, ONE / F) - ONE);// 1e+8*((1+48999.93851/1e+11)^(1/0.51)-1),經過科學計算器了,算出結果爲:96.07833342,約等於程序執行結果96.0783334103356735645。
    // 這是經過抵押資產EXC的增髮量來反推對應的BTC的增髮量。

    auto out = T;

    ex.supply -= input.amount;// 將EXC增發的部分減掉,實際上是維持了原有增髮量1e+11不變。
    balance.amount -= token_type(out);// BTC的總量減小了96.07833342(這部分發給dan了),變爲99999903.921666592。
    return asset{token_type(out), balance.symbol};//最終以BTC減掉(發放出去)96.07833342的形式返回。
}
複製代碼

它的函數體與上面的convert_to_exchange函數很類似,但細一看會發現裏面的某些數值運算髮生了變化。而後,咱們繼續回到二重convert函數中,BTC發給dan的部分(實際上從dan的角度上來說,能夠是BTC增發)具體執行爲:

result.output[balance_key{user, final_output.symbol}] += final_output.amount;// 將發給dan的96.07833342加到dan的帳戶裏。

結果就是dan帳戶中多了96.07833342個BTC。而後對做爲輸入資產的EXC進行處理:

result.output[balance_key{user, input.symbol}] -= input.amount;

結果就是EXC總帳戶經過dan減持掉48999.9385。此時,因爲上面的convert_from_exchange函數返回的是BTC的資產,與原始最小輸出資產類型相同,因此沒必要要再次進入一個convert嵌套。直接返回state,包含以上四個加粗信息,這裏再從新列出來:

  1. EXC總帳戶經過dan增發了48999.9385
  2. dan我的帳戶欠了100個USD
  3. dan帳戶中多了96.07833342個BTC
  4. EXC總帳戶經過dan減持掉48999.9385

1和4互相抵消,等於state的總髮行量不變,仍舊爲原始的1e+11。因此state中會新增帳戶dan的信息,它的USD和BTC以及EXC(中間涉及到了中轉交易,EXC至關於一箇中間價值錨定,用來創建兩種token交易的通道)。最終達到了與程序輸出相等的結果:

-----------------------------
supply: 1e+11
base: 1e+08 USD
quote: 9.99999e+07 BTC
dan  96.0783 BTC
dan  0 EXC
dan  -100 USD

-----------------------------
複製代碼

總結

咱們經過一個簡單的測試完成了對exchange合約的學習,exchange合約教會咱們能夠經過EOS創建本身的生態模型,通證模型,咱們能夠錨定抵押資產,發行token,經過權重的形式發行多個token等等,很是靈活,這與本篇文章前半部分所描述的那種價值穩定的數字貨幣的設計是吻合的。在測試程序中,咱們簡單實現了exchange源碼中的convert函數,各類自定義結構體,例如connector,exchange_state等等,基本上全部測試文件中的函數與結構均可以在exchange源碼中找到。咱們在上面源碼分析的過程當中還比較混沌,但經過測試文件的梳理,再回頭去看上面的源碼分析,會有新的體會。源碼中各類結構以及函數是更加精密與強壯的,可是測試文件和exchange源碼相同的是:他們的通證模型是相同的。咱們經過測試和源碼更加充分理解了EOS的靈活的通證模型。有任何問題,歡迎來討論。

參考資料

  • EOS源碼

相關文章和視頻推薦

圓方圓學院聚集大批區塊鏈名師,打造精品的區塊鏈技術課程。 在各大平臺都長期有優質免費公開課,歡迎報名收看。

公開課地址:ke.qq.com/course/3451…

相關文章
相關標籤/搜索