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的操作
- 转账 eos
如果购买者不是eosio账号(即系统账号),则需要得到购买者的active权限授权,从购买者转账对应数额的eos到eosio这个账户上,备注是“buy ram”. 发送这一操作,是通过inline action的类型来完成的(调用了INLINE_ACTION_SENDER的方法)
在这里,再一次调用exchange::convert方法,计算这一部分数额的EOS能够购买到的RAM字节的数量。并且打印在log中。
- 记录eosio系统中全局状态的_gstate, 会更新两个参数:
_gstate.total_ram_bytes_reserved, 会增加所购买的RAM的数额,比如200MB;
_gstate.total_ram_stake.amount,会增加所购买RAM所用的EOS的数量,比如20 EOS(随意的例子)
一笔交易完成之后,所抵押的EOS的数量,跟RAM的数量,都更新了。
更新用户资源表 userres中,用户的ram总量。如果没有记录,则新创建一条记录,总数记录为刚购买的ram的数量;如果在资源表中有该用户的记录,则增加用户的ram的数量。对应的数据列是ram_bytes.
更新用户的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了。
同样的,调用convert方法,将所出售的ram的字节数,根据市场价格换算为EOS的数量(tokens_out变量来表示)。
更新全局变量
从total_ram_bytes_reserved和total_ram_stake这两个数据列中,分别减去所出售而进入流通的RAM 的字节量,以及所抵押的token的量。
_gstate.total_ram_bytes_reserved -= bytes;
_gstate.total_ram_stake.amount -= tokens_out.amount;
更新用户的资源表
在userress表中,更新对应用户的ram_bytes的数据,减去所售出的ram数量。
并且,调用set_resource_limits
方法,根据用户所持有的资源,来决定速率等的限制。将 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的市场价格。具体是怎么计算的呢?
- 建立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算法这部分我理解的还不够准确,后续会再研究一下。