eos源码解析(二): bancor算法

2018-07-13  本文已影响745人  荒原葱郁

近来ram经历了大涨大跌,ram的核心是bancor算法,网上有很多对bancor的介绍,这里就不谈了,eos少数派报告的星主Anima对此有深刻的分析。本文直接从源码解析eos的bancor算法。
最开始,,,我新建了一条链,对,是一条新链。然后在完整的64G内存上购买了10000k 内存。下面的命令是新建一个账户并购买10000k的内存。
!!!!直接要结果的,跳过源码,后文自取,不谢不谢!!!!

cleos system newaccount eosio --transfer accountnum11 EOS8mUftJXepGzdQ2TaCduNuSPAfXJHf22uex4u41ab1EVv9EAhWt --stake-net "100000.0000 SYS" --stake-cpu "100000.0000 SYS" --buy-ram-kbytes 10000

在eos的内部,这条命令触发的函数是这样的:

//payer:eosio receiver:accountnum11 bytes:10000
   void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) {
      auto itr = _rammarket.find(S(4,RAMCORE));
      auto tmp = *itr;
      auto eosout = tmp.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL );

      buyram( payer, receiver, eosout );
   }

先调用convert,convert涉及到很多函数,这是bancor真正实现,一并列在下面,我加了打印,后面会放打印结果。。。。

namespace eosiosystem {
   asset exchange_state::convert_to_exchange( connector& c, asset in ) {

      real_type R(supply.amount);
      real_type C(c.balance.amount+in.amount);
      real_type F(c.weight/1000.0);
      real_type T(in.amount);
      real_type ONE(1.0);
       print("\nconvert_to_exchange \n");
       print( "supply.amount ", supply.amount , "\n" );
       print( "c.balance.amount ", c.balance.amount, "\n" );
       print( "c.weight: ", c.weight, "\n" );
       print( "in.amount: ", in.amount, "\n\n" );

      real_type E = -R * (ONE - std::pow( ONE + T / C, F) );
      print( "E: ", E, "\n");
      int64_t issued = int64_t(E);

      supply.amount += issued;
      c.balance.amount += in.amount;

      return asset( issued, supply.symbol );
   }

   asset exchange_state::convert_from_exchange( connector& c, asset in ) {
      eosio_assert( in.symbol== supply.symbol, "unexpected asset symbol input" );

      real_type R(supply.amount - in.amount);
      real_type C(c.balance.amount);
      real_type F(1000.0/c.weight);
      real_type E(in.amount);
      real_type ONE(1.0);

       print("\nconvert_from_exchange \n");
       print( "supply.amount ", supply.amount , "\n" );
       print( "c.balance.amount ", c.balance.amount, "\n" );
       print( "c.weight: ", c.weight, "\n" );
       print( "in.amount: ", in.amount, "\n\n" );

     // potentially more accurate: 
     // The functions std::expm1 and std::log1p are useful for financial calculations, for example, 
     // when calculating small daily interest rates: (1+x)n
     // -1 can be expressed as std::expm1(n * std::log1p(x)). 
     // real_type T = C * std::expm1( F * std::log1p(E/R) );
      
      real_type T = C * (std::pow( ONE + E/R, F) - ONE);
      print( "T: ", T, "\n");
      int64_t out = int64_t(T);

      supply.amount -= in.amount;
      c.balance.amount -= out;

      return asset( out, c.balance.symbol );
   }

   asset exchange_state::convert( asset from, symbol_type to ) {
      auto sell_symbol  = from.symbol;
      auto ex_symbol    = supply.symbol;
      auto base_symbol  = base.balance.symbol;
      auto quote_symbol = quote.balance.symbol;

      print( "From: ", from, " TO ", asset( 0,to), "\n" );
      print( "base: ", base_symbol, "\n" );
      print("base_amount: ", base.balance.amount , "\n");
      print( "quote: ", quote_symbol, "\n" );
       print( "quote_amount: ", quote.balance.amount, "\n" );
      print( "ex: ", supply.symbol, "\n" );
       print( "ex_amuont: ", supply.amount, "\n" );


      if( sell_symbol != ex_symbol ) {
         if( sell_symbol == base_symbol ) {
            from = convert_to_exchange( base, from );
         } else if( sell_symbol == quote_symbol ) {
            from = convert_to_exchange( quote, from );
         } else { 
            eosio_assert( false, "invalid sell" );
         }
      } else {
         if( to == base_symbol ) {
            from = convert_from_exchange( base, from ); 
         } else if( to == quote_symbol ) {
            from = convert_from_exchange( quote, from ); 
         } else {
            eosio_assert( false, "invalid conversion" );
         }
      }

      if( to != from.symbol )
         return convert( from, to );

      return from;
   }
} /// namespace eosiosystem

然后调用buyram

  void system_contract::buyram( account_name payer, account_name receiver, asset quant )
   {
      require_auth( payer );
      eosio_assert( quant.amount > 0, "must purchase a positive amount" );

      auto fee = quant;
      fee.amount = ( fee.amount + 199 ) / 200; /// .5% fee (round up)
      // fee.amount cannot be 0 since that is on近来ram经历了大涨大跌,ram的核心是bancor算法,网上有很多对bancor的介绍,这里就不谈了,eos少数派报告的星主Anima对此有深刻的分析。本文直接从源码解析eos的bancor算法。
最开始,,,我新建了一条链,对,是一条新链。然后在完整的64G内存上购买了10000k 内存。下面的命令是新建一个账户并购买10000k的内存。
!!!!直接要结果的,跳过源码,后文自取,不谢不谢!!!!

cleos system newaccount eosio --transfer accountnum11 EOS8mUftJXepGzdQ2TaCduNuSPAfXJHf22uex4u41ab1EVv9EAhWt --stake-net "100000.0000 SYS" --stake-cpu "100000.0000 SYS" --buy-ram-kbytes 10000

在eos的内部,这条命令触发的函数是这样的:

//payer:eosio receiver:accountnum11 bytes:10000
   void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) {
      auto itr = _rammarket.find(S(4,RAMCORE));
      auto tmp = *itr;
      auto eosout = tmp.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL );

      buyram( payer, receiver, eosout );
   }

先调用convert,convert涉及到很多函数,这是bancor真正实现,一并列在下面,我加了打印,后面会放打印结果。。。。

namespace eosiosystem {
   asset exchange_state::convert_to_exchange( connector& c, asset in ) {

      real_type R(supply.amount);
      real_type C(c.balance.amount+in.amount);
      real_type F(c.weight/1000.0);
      real_type T(in.amount);
      real_type ONE(1.0);
       print("\nconvert_to_exchange \n");
       print( "supply.amount ", supply.amount , "\n" );
       print( "c.balance.amount ", c.balance.amount, "\n" );
       print( "c.weight: ", c.weight, "\n" );
       print( "in.amount: ", in.amount, "\n\n" );

      real_type E = -R * (ONE - std::pow( ONE + T / C, F) );
      print( "E: ", E, "\n");
      int64_t issued = int64_t(E);

      supply.amount += issued;
      c.balance.amount += in.amount;

      return asset( issued, supply.symbol );
   }

   asset exchange_state::convert_from_exchange( connector& c, asset in ) {
      eosio_assert( in.symbol== supply.symbol, "unexpected asset symbol input" );

      real_type R(supply.amount - in.amount);
      real_type C(c.balance.amount);
      real_type F(1000.0/c.weight);
      real_type E(in.amount);
      real_type ONE(1.0);

       print("\nconvert_from_exchange \n");
       print( "supply.amount ", supply.amount , "\n" );
       print( "c.balance.amount ", c.balance.amount, "\n" );
       print( "c.weight: ", c.weight, "\n" );
       print( "in.amount: ", in.amount, "\n\n" );

     // potentially more accurate: 
     // The functions std::expm1 and std::log1p are useful for financial calculations, for example, 
     // when calculating small daily interest rates: (1+x)n
     // -1 can be expressed as std::expm1(n * std::log1p(x)). 
     // real_type T = C * std::expm1( F * std::log1p(E/R) );
      
      real_type T = C * (std::pow( ONE + E/R, F) - ONE);
      print( "T: ", T, "\n");
      int64_t out = int64_t(T);

      supply.amount -= in.amount;
      c.balance.amount -= out;

      return asset( out, c.balance.symbol );
   }

   asset exchange_state::convert( asset from, symbol_type to ) {
      auto sell_symbol  = from.symbol;
      auto ex_symbol    = supply.symbol;
      auto base_symbol  = base.balance.symbol;
      auto quote_symbol = quote.balance.symbol;

      print( "From: ", from, " TO ", asset( 0,to), "\n" );
      print( "base: ", base_symbol, "\n" );
      print("base_amount: ", base.balance.amount , "\n");
      print( "quote: ", quote_symbol, "\n" );
       print( "quote_amount: ", quote.balance.amount, "\n" );
      print( "ex: ", supply.symbol, "\n" );
       print( "ex_amuont: ", supply.amount, "\n" );


      if( sell_symbol != ex_symbol ) {
         if( sell_symbol == base_symbol ) {
            from = convert_to_exchange( base, from );
         } else if( sell_symbol == quote_symbol ) {
            from = convert_to_exchange( quote, from );
         } else { 
            eosio_assert( false, "invalid sell" );
         }
      } else {
         if( to == base_symbol ) {
            from = convert_from_exchange( base, from ); 
         } else if( to == quote_symbol ) {
            from = convert_from_exchange( quote, from ); 
         } else {
            eosio_assert( false, "invalid conversion" );
         }
      }

      if( to != from.symbol )
         return convert( from, to );

      return from;
   }
} /// namespace eosiosystem

然后调用buyram

  void system_contract::buyram( account_name payer, account_name receiver, asset quant )
   {
      require_auth( payer );
      eosio_assert( quant.amount > 0, "must purchase a positive amount" );

      auto fee = quant;
      fee.amount = ( fee.amount + 199 ) / 200; /// .5% fee (round up)
      // fee.amount cannot be 0 since that is only possible if quant.amount is 0 which is not allowed by the assert above.
      // If quant.amount == 1, then fee.amount == 1,
      // otherwise if quant.amount > 1, then 0 < fee.amount < quant.amount.
      auto quant_after_fee = quant;
      quant_after_fee.amount -= fee.amount;
      // quant_after_fee.amount should be > 0 if quant.amount > 1.
      // If quant.amount == 1, then quant_after_近来ram经历了大涨大跌,ram的核心是bancor算法,网上有很多对bancor的介绍,这里就不谈了,eos少数派报告的星主Anima对此有深刻的分析。本文直接从源码解析eos的bancor算法。
最开始,,,我新建了一条链,对,是一条新链。然后在完整的64G内存上购买了10000k 内存。下面的命令是新建一个账户并购买10000k的内存。
!!!!直接要结果的,跳过源码,后文自取,不谢不谢!!!!


cleos system newaccount eosio --transfer accountnum11 EOS8mUftJXepGzdQ2TaCduNuSPAfXJHf22uex4u41ab1EVv9EAhWt --stake-net "100000.0000 SYS" --stake-cpu "100000.0000 SYS" --buy-ram-kbytes 10000

在eos的内部,这条命令触发的函数是这样的:

//payer:eosio receiver:accountnum11 bytes:10000
   void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) {
      auto itr = _rammarket.find(S(4,RAMCORE));
      auto tmp = *itr;
      auto eosout = tmp.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL );

      buyram( payer, receiver, eosout );
   }

先调用convert,convert涉及到很多函数,这是bancor真正实现,一并列在下面,我加了打印,后面会放打印结果。。。。

namespace eosiosystem {
   asset exchange_state::convert_to_exchange( connector& c, asset in ) {

      real_type R(supply.amount);
      real_type C(c.balance.amount+in.amount);
      real_type F(c.weight/1000.0);
      real_type T(in.amount);
      real_type ONE(1.0);
       print("\nconvert_to_exchange \n");
       print( "supply.amount ", supply.amount , "\n" );
       print( "c.balance.amount ", c.balance.amount, "\n" );
       print( "c.weight: ", c.weight, "\n" );
       print( "in.amount: ", in.amount, "\n\n" );

      real_type E = -R * (ONE - std::pow( ONE + T / C, F) );
      print( "E: ", E, "\n");
      int64_t issued = int64_t(E);

      supply.amount += issued;
      c.balance.amount += in.amount;

      return asset( issued, supply.symbol );
   }

   asset exchange_state::convert_from_exchange( connector& c, asset in ) {
      eosio_assert( in.symbol== supply.symbol, "unexpected asset symbol input" );

      real_type R(supply.amount - in.amount);
      real_type C(c.balance.amount);
      real_type F(1000.0/c.weight);
      real_type E(in.amount);
      real_type ONE(1.0);

       print("\nconvert_from_exchange \n");
       print( "supply.amount ", supply.amount , "\n" );
       print( "c.balance.amount ", c.balance.amount, "\n" );
       print( "c.weight: ", c.weight, "\n" );
       print( "in.amount: ", in.amount, "\n\n" );

     // potentially more accurate: 
     // The functions std::expm1 and std::log1p are useful for financial calculations, for example, 
     // when calculating small daily interest rates: (1+x)n
     // -1 can be expressed as std::expm1(n * std::log1p(x)). 
     // real_type T = C * std::expm1( F * std::log1p(E/R) );
      
      real_type T = C * (std::pow( ONE + E/R, F) - ONE);
      print( "T: ", T, "\n");
      int64_t out = int64_t(T);

      supply.amount -= in.amount;
      c.balance.amount -= out;

      return asset( out, c.balance.symbol );
   }

   asset exchange_state::convert( asset from, symbol_type to ) {
      auto sell_symbol  = from.symbol;
      auto ex_symbol    = supply.symbol;
      auto base_symbol  = base.balance.symbol;
      auto quote_symbol = quote.balance.symbol;

      print( "From: ", from, " TO ", asset( 0,to), "\n" );
      print( "base: ", base_symbol, "\n" );
      print("base_amount: ", base.balance.amount , "\n");
      print( "quote: ", quote_symbol, "\n" );
       print( "quote_amount: ", quote.balance.amount, "\n" );
      print( "ex: ", supply.symbol, "\n" );
       print( "ex_amuont: ", supply.amount, "\n" );


      if( sell_symbol != ex_symbol ) {
         if( sell_symbol == base_symbol ) {
            from = convert_to_exchange( base, from );
         } else if( sell_symbol == quote_symbol ) {
            from = convert_to_exchange( quote, from );
         } else { 
            eosio_assert( false, "invalid sell" );
         }
      } else {
         if( to == base_symbol ) {
            from = convert_from_exchange( base, from ); 
         } else if( to == quote_symbol ) {
            from = convert_from_exchange( quote, from ); 
         } else {
            eosio_assert( false, "invalid conversion" );
         }
      }

      if( to != from.symbol )
         return convert( from, to );

      return from;
   }
} /// namespace eosiosystem

然后调用buyram

  void system_contract::buyram( account_name payer, account_name receiver, asset quant )
   {
      require_auth( payer );
      eosio_assert( quant.amount > 0, "must purchase a positive amount" );

      auto fee = quant;
      fee.amount = ( fee.amount + 199 ) / 200; /// .5% fee (round up)
      // fee.amount cannot be 0 since that is onfee.amount == 0 and the next inline transfer will fail causing the buyram action to fail.

      INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)},
         { payer, N(eosio.ram), quant_after_fee, std::string("buy ram") } );

      if( fee.amount > 0 ) {
         INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)},
                                                       { payer, N(eosio.ramfee), fee, std::string("ram fee") } );
      }

      int64_t bytes_out;

      const auto& market = _rammarket.get(S(4,RAMCORE), "ram market does not exist");
      _rammarket.modify( market, 0, [&]( auto& es ) {
          bytes_out = es.convert( quant_after_fee,  S(0,RAM) ).amount;
      });

      eosio_assert( bytes_out > 0, "must reserve a positive amount" );

      _gstate.total_ram_bytes_reserved += uint64_t(bytes_out);
      _gstate.total_ram_stake          += quant_after_fee.amount;

      user_resources_table  userres( _self, receiver );
      auto res_itr = userres.find( receiver );
      if( res_itr ==  userres.end() ) {
         res_itr = userres.emplace( receiver, [&]( auto& res ) {
               res.owner = receiver;
               res.ram_bytes = bytes_out;
            });
      } else {
         userres.modify( res_itr, receiver, [&]( auto& res ) {
               res.ram_bytes += bytes_out;
            });
      }
      set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount );
   }

直接,打印输出:

1786293ms thread-0   apply_context.cpp:28          print_debug          ] 
[(eosio,buyrambytes)->eosio]: CONSOLE OUTPUT BEGIN =====================
From: 10240000. RAM TO 0.0000 SYS
base: 0,RAM
quote: 4,SYS
ex: 4,RAMCORE

convert_to_exchange 
supply.amount 100000000000000
c.balance.amount 68719476736
c.weight: 5.000000000000000e-01
in.amount: 10240000
E: 7.448915928520706e+06

From: 744.8915 RAMCORE TO 0.0000 SYS
base: 0,RAM
quote: 4,SYS
ex: 4,RAMCORE

convert_from_exchange 
supply.amount 100000007448915
c.balance.amount 100 0000 0000
c.weight: 5.000000000000000e-01
in.amount: 7448915
T: 1.489893921873264e+06

From: 148.2443 SYS TO 0. RAM
base: 0,RAM
quote: 4,SYS
ex: 4,RAMCORE

convert_to_exchange 
supply.amount 100 0000 0000 0000
c.balance.amount 100 0000 , 0000
c.weight: 5.000000000000000e-01
in.amount: 1482443
E: 7.410567426369141e+06


From: 741.0567 RAMCORE TO 0. RAM
base: 0,RAM
quote: 4,SYS
ex: 4,RAMCORE

convert_from_exchange 
supply.amount 100000007410567
c.balance.amount 68719476736
c.weight: 5.000000000000000e-01
in.amount: 7410567
T: 1.018576016383362e+07


[(eosio,buyrambytes)->eosio]: CONSOLE OUTPUT END   =====================

这三个在后文中常见:
1, base:RAM
2, quote:SYS
3, ex:RAMCORE

我们根据结果倒推算法。
第1步,调用conver_to_exchange将RAM单位换算成RAMCORE单位

CodeCogsEqn.gif

对应变量:

supply.amoumt  : 10^14
base.balance.amount  :   68719476736  即64G
in.amount   :   10240000
结果 E = 74485915
supply.amount += 74485915
base.balance.amount += 10240000

第2步,conver_frome_exchange函数将RAMCORE换算成SYS

CodeCogsEqn (1).gif

对应变量:

//这是ram的初始资金池容量,100万,之所以是10次方,是因为在eos内部,为取整方便,SYS的单位是“分”,不是“元”。 
quote.balance.amount  :  10^10   
supply.amount -= 7448915
quote.balance.amount -= 1489893
结果T=1489893

上面的分析是从ram-->sys ,意思是要购买10000k的ram,大概需要1489893分钱的SYS即149个EOS。但是像“supply.amount -= ” “quote.balance.amount -=”并不会对supply做真正的修改,只是修改的镜像。在调用buyram时使用“_rammarket.modify”才能修改系统的amount。

以下的两步是在buyram函数中进行的。
下面是大家比较关心的手续费问题:之所以要加上199,是要确保1分钱的交易也是需要手续费的,真是雁过拔毛呀!!!除以200 就是乘以0.005。没毛病。。。话说我算术不好。。。1/200 = 0.005 。

          fee.amount = ( fee.amount + 199 ) / 200;

第3步,将SYS转换成RAMCORE,调用conver_to_exchange函数:


CodeCogsEqn (2).gif

对应变量:

supply.amount : 10^14
in.amount  : 1482443
quote.balance.amount  : 10^10
结果E = 7410567

第4步,调用conver_from_exchange 将RAMCORE换算成RAM


CodeCogsEqn (3).gif

对应变量:

base.banalce.amount : 64G
base.banalce.amount -= 10185760

所以,最后,我们想买10240000byte的ram,结果只买了10185760byte的ram,其余的,扣了手续费。
实际上,卖出ram时,过程和前两步一样,不过在最后收取0.005的手续费。
将上文中,前两步的公式整合,化简,可以发现:

image.png

后两步的公式整合,化简可得到:

image.png

所以,

当前ram的价格 ≈ 当前的剩余容量 / 池子中的EOS总量

呵呵!并没有什么0.5/1000之类的幂,也没了什么RAMCORE。
不知道为啥BM一定要两步转换,强行引入RAMCORE,难道是为了确保RAMCORE的总量不变吗?希望知道的童靴答疑。

上一篇下一篇

猜你喜欢

热点阅读