近来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_fee.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单位
对应变量:
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
对应变量:
//这是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函数:
对应变量:
supply.amount : 10^14
in.amount : 1482443
quote.balance.amount : 10^10
结果E = 7410567
第4步,调用conver_from_exchange 将RAMCORE换算成RAM
对应变量:
base.banalce.amount : 64G
base.banalce.amount -= 10185760
所以,最后,我们想买10240000byte的ram,结果只买了10185760byte的ram,其余的,扣了手续费。
实际上,卖出ram时,过程和前两步一样,不过在最后收取0.005的手续费。
将上文中,前两步的公式整合,化简,可以发现:
后两步的公式整合,化简可得到:
呵呵!并没有什么0.5/1000之类的幂,也没了什么RAMCORE。
不知道为啥BM一定要两步转换,强行引入RAMCORE,难道是为了确保RAMCORE的总量不变吗?希望知道的童靴答疑。