EOS的RAM机制解读

5月12日,公布了dawn 4.0,dan 提到:

eos社区有人担心,会有人出于投机需要,主网上线时候抢先购买便宜的ram,之后再用高价格卖出。dan的回应是,没有人能够买到便宜的ram,或者得到免费的利益。他的建议是,一开始对ram的供应量进行限制,比如限定为32GB,在随后几个月时间里,将ram增长到1tb。这样的话,ram的价格会迅速下降,降到初始价格的3%左右。这一方式,可以抑制投机。只有非常需要ram的人,才会购买初始的RAM.

那么,在eos合约之中,如何买卖ram,ram的机制是怎样的,这篇文章之中我们一起来看一下。

本文主要讲述如下几个方面:

  • RAM的分配机制
  • 购买RAM
  • 出售RAM
  • 其他的相关问题

RAM的分配机制

在Dawn 4.0中,RAM的定价机制有了比较大的变化, 改为了市场机制决定RAM的价格。

在Dawn 3.0时,ram的售价是你当初的买价。这是为了防止投机而设定的。但是这样的坏处在于,购买了ram的人,没有动力去释放ram,随着ram的价格上涨,同样数量的ram,当时的价格是2000元,现在是2万,但是卖出的价格却只能够按照原先的买价来计算,只能够卖2000元,很少有持有ram的人会愿意卖出的。

在4.0,则是随行就市,不论是两块钱买的ram,还是20块买的ram,如果现在价格涨到了2万,就是按照2万卖出。通过市场机制调节了ram的供需关系。

这里为了方便理解,我用了元作为单位,实际上,在eos中ram的买卖是用eos作为单位的。

ram的价格估算

BM认为,根据摩尔定律,出块节点供应的ram数量会扩展升级,甚至能够提供16TB的RAM, 供应量的增加,会降低RAM的市场价格。

BM 对RAM的成本价格做了估算,在文章之中他提到,假设eos的价格是20美元,ram总量为1tb,那么,如果所有持有eos的人都对ram有要求,则每字节的成本是0.018美元。

不过大部分的持有者并不需要ram,所以,实际的字节成本会低一些,dan预计,大约是0.000018美元。

如果按照原先的Dawn 3.0的方式,理论上是有可能将系统的RAM耗尽的,只要花eos买光所有的RAM就可以了;但是在Dawn 4.0中,随着RAM占用的增加, RAM的供应量会减少,则RAM的价格会持续上涨,直到无法成承受的高价。因此,EOSIO的区块链系统,不会出现RAM耗尽的情况。

设定和修改RAM总量

调用 system_contract::setram,设定ram数量的最大值。传入的变量max_ram_size,不得大于1024Tb,这样的数量明显不合现实;也必须大于已经占用(reserved)的ram bytes的总量。

然后,根据当前ram的最大值,和新设定的最大值之间的差值(delta),更新RAM的供应量,并且,重新设置全局的ram最大值的数值。

在记录eosio系统全局状态的结构体_gstate中,有一个属性 max_ram_size会更新;
而在 rammarket 的表中,会将ram的供应量也更新,即下文代码之中的这一行:

      _rammarket.modify( itr, 0, [&]( auto& m ) {
         m.base.balance.amount += delta;
      });

设定和修改ram数量的方法,如下所示:

   void system_contract::setram( uint64_t max_ram_size ) {
      require_auth( _self );

      eosio_assert( max_ram_size < 1024ll*1024*1024*1024*1024, "ram size is unrealistic" );
      eosio_assert( max_ram_size > _gstate.total_ram_bytes_reserved, "attempt to set max below reserved" );

      auto delta = int64_t(max_ram_size) - int64_t(_gstate.max_ram_size);
      auto itr = _rammarket.find(S(4,RAMEOS));

      /**
       *  Increase or decrease the amount of ram for sale based upon the change in max
       *  ram size.
       */
      _rammarket.modify( itr, 0, [&]( auto& m ) {
         m.base.balance.amount += delta;
      });

      _gstate.max_ram_size = max_ram_size;
      _global.set( _gstate, _self );
   }

购买RAM

购买ram,调用的是 system_contract::buyram_rates的方法,buy ram rates 方法,主要做了两件事情:

首先,根据bancor算法,将ram换算为对应的eos的数额。调用的是exchange::convert方法。
其次, 运行system_contract::buyram方法,根据换算得到的eos的数额,进行操作和记录修改。

system_contract::buyram的操作

  1. 转账 eos

如果购买者不是eosio账号(即系统账号),则需要得到购买者的active权限授权,从购买者转账对应数额的eos到eosio这个账户上,备注是“buy ram”. 发送这一操作,是通过inline action的类型来完成的(调用了INLINE_ACTION_SENDER的方法)

在这里,再一次调用exchange::convert方法,计算这一部分数额的EOS能够购买到的RAM字节的数量。并且打印在log中。

  1. 记录eosio系统中全局状态的_gstate, 会更新两个参数:

_gstate.total_ram_bytes_reserved, 会增加所购买的RAM的数额,比如200MB;
_gstate.total_ram_stake.amount,会增加所购买RAM所用的EOS的数量,比如20 EOS(随意的例子)

一笔交易完成之后,所抵押的EOS的数量,跟RAM的数量,都更新了。

  1. 更新用户资源表 userres中,用户的ram总量。如果没有记录,则新创建一条记录,总数记录为刚购买的ram的数量;如果在资源表中有该用户的记录,则增加用户的ram的数量。对应的数据列是ram_bytes.

  2. 更新用户的ram_bytes之后,执行 set_resource_lmits 的操作,更新用户可用的速率等信息。

代码如下:


   void system_contract::buyram( account_name payer, account_name receiver, asset quant ) 
   {
      print( "\n payer: ", eosio::name{payer}, " buys ram for ", eosio::name{receiver}, " with ", quant, "\n" );
      require_auth( payer );
      eosio_assert( quant.amount > 0, "must purchase a positive amount" );

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

      print( "free ram: ", _gstate.free_ram(),  "\n");

      int64_t bytes_out;

      auto itr = _rammarket.find(S(4,RAMEOS));
      _rammarket.modify( itr, 0, [&]( auto& es ) {
          bytes_out = es.convert( quant,  S(0,RAM) ).amount;
      });

      print( "ram bytes out: ", bytes_out, "\n" );

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

      _gstate.total_ram_bytes_reserved += uint64_t(bytes_out);
      _gstate.total_ram_stake.amount   += quant.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 );
   }

出售RAM

理解了购买 RAM的过程,自然也容易理解如何出售 RAM了。

  1. 同样的,调用convert方法,将所出售的ram的字节数,根据市场价格换算为EOS的数量(tokens_out变量来表示)。

  2. 更新全局变量

从total_ram_bytes_reserved和total_ram_stake这两个数据列中,分别减去所出售而进入流通的RAM 的字节量,以及所抵押的token的量。


      _gstate.total_ram_bytes_reserved -= bytes;
      _gstate.total_ram_stake.amount   -= tokens_out.amount;
  1. 更新用户的资源表
    在userress表中,更新对应用户的ram_bytes的数据,减去所售出的ram数量。
    并且,调用 set_resource_limits 方法,根据用户所持有的资源,来决定速率等的限制。

  2. 将 eos 转回到用户的账号中

调用eosio.token合约,从eosio账户中将对应数额的 eos 转回到用户的账户之中,转账的memo为: sell ram.


   void system_contract::sellram( account_name account, uint64_t bytes ) {
      require_auth( account );

      user_resources_table  userres( _self, account );
      auto res_itr = userres.find( account );
      eosio_assert( res_itr != userres.end(), "no resource row" );
      eosio_assert( res_itr->ram_bytes >= bytes, "insufficient quota" );

      asset tokens_out;
      auto itr = _rammarket.find(S(4,RAMEOS));
      _rammarket.modify( itr, 0, [&]( auto& es ) {
          tokens_out = es.convert( asset(bytes,S(0,RAM)),  S(4,EOS) );
          print( "out: ", tokens_out, "\n" );
      });

      _gstate.total_ram_bytes_reserved -= bytes;
      _gstate.total_ram_stake.amount   -= tokens_out.amount;

      //// this shouldn't happen, but just in case it does we should prevent it
      eosio_assert( _gstate.total_ram_stake.amount >= 0, "error, attempt to unstake more tokens than previously staked" );

      userres.modify( res_itr, account, [&]( auto& res ) {
          res.ram_bytes -= bytes;
      });
      set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount );

      if( N(eosio) != account ) {
         INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
                                                       { N(eosio), account, asset(tokens_out), std::string("sell ram") } );
      }
   }

RAM的价格和bancor 算法

在dawn 4.0 这一新发布的版本中,采用了banchor算法来计算eos的市场价格。具体是怎么计算的呢?

  1. 建立rameos的市场

在rammarket的表中,系统合约 system_contract会设定如下三个部分:
supply的amount和代号(RAMEOS);
base方资产的数量,为系统中闲置的ram数量(free_ram), 代号为RAM;
quote方资产的数量,为系统的token总供应量,符号是EOS;

         if( system_token_supply > 0 ) {
            itr = _rammarket.emplace( _self, [&]( auto& m ) {
               m.supply.amount = 100000000000000ll;
               m.supply.symbol = S(4,RAMEOS);
               m.base.balance.amount = int64_t(_gstate.free_ram());
               m.base.balance.symbol = S(0,RAM);
               m.quote.balance.amount = system_token_supply / 1000;
               m.quote.balance.symbol = S(4,EOS);
            });

exchange_state::convert方法,使用bancor算法,将ram换算为eos的数量。

   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( "quote: ", quote_symbol, "\n" );
      //print( "ex: ", supply.symbol, "\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;
   }

bancor算法这部分我理解的还不够准确,后续会再研究一下。

你可能感兴趣的:(EOS的RAM机制解读)