BCSkill (Block chain skill )
区块链中文技术社区

只讨论区块链底层技术
遵守一切相关法律政策!

EOS标准货币体系与源码实现分析

EOS智能合约中包含一个exchange合约,它支持用户创建一笔交易,是任何两个基本货币类型之间的交易。这个合约的作用是跨不同币种(都是EOS上的标准货币类型)的,通过各自与EOS主链价值进行锚定,然后再相互发起交易兑换。要搞清楚的是,这与区块链“传统的”交易所并不一样,那个主要是集中在交易撮合上面,而且必须是同一币种。

标准货币体系

上面一直提到标准货币(standard currency),在EOS.io中也针对这一标准货币的设计给出了官方文档,本章先来弄清楚标准货币的概念,然后再继续分析exchange。
文章的名字叫《Pegged Derivative Currency Design》

Pegged Currency的含义是什么?

Currency pegging是用来固定汇率的一种方式,通过匹配当前货币与其他货币的价值锚定,或者其他价值度量方式,例如黄金和白银。所以Pegged Currency可以理解为汇率稳定的货币。
所以,这篇文章的题目可以翻译为:一种价值稳定的衍生货币设计。
目前市面已有的Pegged Derivative 货币,例如BitUSD,比特美元,它是一种以加密货币作为抵押。发行人是短线持有美元而额外长地持有加密货币。而购买者是单纯地长期持有美元。

原始设计

比特股BitShares创造了第一个可行的价值稳定的资产系统:
允许任何人获得一个最小的抵押状况是,公布抵押物和获得的BitUSD的价值比率在最小的1.5:1(抵押物:负债率)。
最少的抵押状况下,要强迫BitUSD持有人在任何市场价格下跌超过美元几个百分点以下的时候的流动性(如果BitUSD持有人选择使用强制清算是不允许的)。换句话说,就是在当前货币下跌的时候,也要保证货币流通性,这是为了货币状况健康运营而考虑。

为了防止价格补偿(直接通过增发和卖出来控制价格)的滥用,所有的强制清算会被推迟。当发生“黑天鹅”事件(极小可能性发生,但实际上却发生了)的时候,所有的短线会通过价格补偿拥有他们自己的清算状况,而所有的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智能合约的主要功能。

exchange.abi

abi文件是通过eosiocpp工具通过exchange.cpp生成的

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

然后,我们再打开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的灵活的通证模型。

转载自:www.cnblogs.com/Evsward

EOS系统账号私钥归谁

问题:(来自eosfans)

像eosio.token, eosio.ram,eosio.names等这些系统账户是怎么来的?由谁创建呢,它们有对应的私钥吗?如果有这些私钥由谁管理?

回答:

eosio.token, eosio.ram,eosio.names,由最开始的创始账号eosio创建,主网启动后,这类系统账号权限转交给了BP去管理。
可通过查看账户验证

root@iZj6cbx3duprxf6dasczbpZ:~# cleos -u http://eu1.eosdac.io get account eosio.token
permissions: 
     owner     1:    1 eosio@active, 
        active     1:    1 eosio@active, 

root@iZj6cbx3duprxf6dasczbpZ:~# cleos -u http://eu1.eosdac.io get account eosio.ram
permissions: 
     owner     1:    1 eosio@active, 
        active     1:    1 eosio@active,

root@iZj6cbx3duprxf6dasczbpZ:~# cleos -u http://eu1.eosdac.io get account eosio.names
permissions: 
     owner     1:    1 eosio@active, 
        active     1:    1 eosio@active, 

得到,账户都归属于eosio
查询eosio账户

root@iZj6cbx3duprxf6dasczbpZ:~# cleos -u http://eu1.eosdac.io get account eosio
privileged: true
permissions: 
     owner     1:    1 eosio.prods@active, 
        active     1:    1 eosio.prods@active, 

得到eosio账户归属于eosio.prods
而eosio.prods即为BP节点

root@iZj6cbx3duprxf6dasczbpZ:~# cleos -u http://eu1.eosdac.io get account eosio.prods
permissions: 
     owner     1:    
        active    15:    1 argentinaeos@active, 1 bitfinexeos1@active, 1 eos42freedom@active, 1 eosamsterdam@active, 1 eosauthority@active, 1 eosbeijingbp@active, 1 eosbixinboot@active, 1 eoscanadacom@active, 1 eosdacserver@active, 1 eosfishrocks@active, 1 eoshuobipool@active, 1 eosisgravity@active, 1 eoslaomaocom@active, 1 eosnewyorkio@active, 1 eosriobrazil@active, 1 eosswedenorg@active, 1 helloeoscnbp@active, 1 libertyblock@active, 1 starteosiobp@active, 1 teamgreymass@active, 1 zbeosbp11111@active, 
           prod.major    11:    1 argentinaeos@active, 1 bitfinexeos1@active, 1 eos42freedom@active, 1 eosamsterdam@active, 1 eosauthority@active, 1 eosbeijingbp@active, 1 eosbixinboot@active, 1 eoscanadacom@active, 1 eosdacserver@active, 1 eosfishrocks@active, 1 eoshuobipool@active, 1 eosisgravity@active, 1 eoslaomaocom@active, 1 eosnewyorkio@active, 1 eosriobrazil@active, 1 eosswedenorg@active, 1 helloeoscnbp@active, 1 libertyblock@active, 1 starteosiobp@active, 1 teamgreymass@active, 1 zbeosbp11111@active, 
              prod.minor     8:    1 argentinaeos@active, 1 bitfinexeos1@active, 1 eos42freedom@active, 1 eosamsterdam@active, 1 eosauthority@active, 1 eosbeijingbp@active, 1 eosbixinboot@active, 1 eoscanadacom@active, 1 eosdacserver@active, 1 eosfishrocks@active, 1 eoshuobipool@active, 1 eosisgravity@active, 1 eoslaomaocom@active, 1 eosnewyorkio@active, 1 eosriobrazil@active, 1 eosswedenorg@active, 1 helloeoscnbp@active, 1 libertyblock@active, 1 starteosiobp@active, 1 teamgreymass@active, 1 zbeosbp11111@active,

结论

eosio, eosio.token, eosio.ram,eosio.names等 系统账号归BP节点管理

EOS智能合约禁止更新,监管升级权限

最近EOS版的Fomo 3D狼人杀游戏骗局引发了大家对EOS智能合约的安全性的大讨论。
和以太坊智能合约的不可升级不同,EOS智能合约可升级,因而保存在智能合约中的数据称不上去中心化,因为智能合约的管理员可偷偷的升级智能合约来修改合约里的任何数据。最直接的示例是EOS代币,代币分发给用户后,用户的代币持有量仍旧可以被管理员修改,那这个持有量是不安全的。由于智能合约可升级,开发者往往也没有了开源的动力,因为反正可以升级,开源又有什么意义呢?你此时看到的源码下一刻就可能被修改掉了。所以说目前所有涉及到资金的EOS代币及智能合约大家都要小心。

干掉升级

那智能合约的中心化如何来解决呢?难道EOS的智能合约只是个玩偶?显然不是,解决的方法只有一个,就是“自宫”,开发人员自己禁用掉合约的升级功能,核心就是是更改智能合约账号的权限,就像EOS启动阶段eosio.system智能合约的权限变更一样(具体可看我的文章)将智能合约权限更改为权威多签权限(eosio, eosio.prods)或者未知私钥对应的公钥。所以说其实官方已经给出了示例的,只是开发人员没跟上。

权威多签权限eosio.prods

21个超级节点多签管理权限

未知私钥

比如EOS1111111111111111111111111111111114T1Anm这个公钥,它的公钥是0值加检验数据生成的,任何人都不知道它的私钥。这个公钥在超级节点竞选时临时用过,当时只有genisisblock生产区块,eosio.system智能合约处于临时冻结状态。

组建第三方权威认证机构(DAppStore)

智能合约将权限转交给第三方权威认证机构DAppStore,该机构由顶尖技术和生态大佬组成,采取投票方式是否同意某一智能合约升级。
比如该权限,智能合约拥有者账号拥有100张投票权,第三方权威机构有100张投票权(100个成员),所以必须得到智能合约拥有者同意且50%第三方权威认证机构票数认可(即150张票)才可以升级

代码开源

自宫之后,才能谈开源,目前eospark已经支持智能合约源码验证,开发者可以将源码发布到eospark验证。这样用户能看到合约的代码,且永远是这个代码。

升级功能什么时候用

既然智能合约都要去掉升级功能,那还要升级功能干嘛?其实可以这样用

  • 只要服务不涉及到资金的智能合约可以使用
  • 测试阶段使用。测试阶段收集反馈,发现有问题通过升级解决。运行一段时间后,固化智能合约。

转载自:blog.csdn.net/itleaks

可以获取账户所以 actions 的主网 rpc 地址

root@iZj6cgy98yiuluw41gz0aoZ:~# cleos -u http://eu1.eosdac.io get actions bcskillsurou
#  seq  when                              contract::action => receiver      trx id...   args

发帖时可用。
数据来源: eosfans

安装EOSTracker 获取 EOS MongoDB 数据

本篇文章演示基于EOS Tracker获取和展示EOS MongoDb 数据。
所需的EOS Tracker为一修改版本,支持MongoDb,原版发帖时只支持MySql。

安装php 7.2

sudo add-apt-repository ppa:ondrej/php
sudo apt-get update
sudo apt-get install php7.2

安装PHP扩展

sudo apt-get install php7.2-mongodb
sudo apt-get install php7.2-cli
sudo apt-get install php7.2-xml
sudo apt-get install php7.2-mbstring php7.2-intl php7.2-redis -y
apt-get install composer

获取代码

git clone https://github.com/FeeSimple/eos-tracker.git

开始部署EOS Tracker API

进入eos-tracker/eos-tracker-api,更新安装EOS Tracker API子项目
composer update
composer install
修改MongDB for eos-tracker-api的配置文件
app/config/parameters.yml

# This file is auto-generated during the composer install parameters:
secret: 123 //一般默认为空
mongodb_server: 'mongodb://localhost:27017'
db_name: EOStest
开始运行
php bin/console server:run 0.0.0.0:8000

开始部署EOS Tracker前端项目

进入eos-tracker/eos-tracker-frontend
安装项目依赖

npm i
npm install -g @angular/cli@6.0.8

EOS Tracker是一个基于Angular4的前端,连接到EOS Tracker API。
用于与eos-tracker-api交互的eos-tracker-frontend的配置文件:

src/environments/environment.ts

    export const environment = {
        production: false,
        walletUrl: '//www.bcskill.com',
        appName: 'EOS Tracker',
        logoUrl: '/assets/logo.png',
        apiUrl: '//www.bcskill.com:8000', //修改为与上面API地址和端口一致
        blockchainUrl: '//rpc.bcskill.com:8888' //修改为已同步全部EOS区块的RPC服务器地址
    };

开始启动

ng serve --host 0.0.0.0 --port 4200

常见问题

  • Cannot read property 'write' of undefined
    npm uninstall -g @angular/cli
    npm install -g @angular/cli@6.0.8
  • add-apt-repository: command not found
    sudo apt-get install python-software-properties
    sudo apt-get update
    sudo apt install software-properties-common 
    sudo apt-get update

    参考:stackoverflow

  • ubuntu npm 升级到新版
    (打开连接)