【刘文彬】【精解】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…

相关文章
相关标签/搜索