EOS/RAM篇——基本概念

RAM兑换公式

RAM 和 EOS 之间的兑换通过一个中间货币来中转,代码里称之为 RAMCORE,我们这里不用太多关注它,之前首页上展示过一个简单的 RAM 兑换 EOS 的公式,下面推导一下这个公式是怎么来的:

变量 含义 初始值
A EOS的发行总量 10亿EOS
B EOS池子里EOS的数量 100万EOS
C RAM池子里可用RAM的容量 64G
X 韭菜准备投入用于购买RAM的EOS数量 -
Y 大佬收割韭菜时出售的RAM容量 -

根据 这里 的代码,翻译成数学语言:

先兑换成中间货币RAMCORE:

\[ RAMCORE =((\frac{X}{X+B}+1)^{0.5/1000}-1)A \]

最后再用 RAMCORE 兑换成想要的 RAM:

\[ RAM = ((\frac{RAMCORE}{A-RAMCORE}+1)^{1000/0.5}-1)C \]

一般情况下 RAMCORE 都是远远小于 A 的,所以为了后续计算方便可以理解为 \(A-RAMCORE≈A\) ,那么将第一个式子代入第二个式子就有:

\[ \begin{aligned} RAM&=(((\frac{X}{X+B}+1){0.5/1000}-1+1){1000/0.5}-1)C\\ &=(((\frac{X}{X+B}+1){0.5/1000}){1000/0.5}-1)C\\ &=(\frac{X}{X+B}+1-1)C\\ &=\frac{XC}{X+B} \end{aligned} \]

同理可知,RAM 兑换 EOS 简要公式推导为:

\[ \begin{aligned} RAMCORE &= ((\frac{Y}{Y+C}+1)^{0.5/1000}-1)A\\ EOS&=((\frac{RAMCORE}{A}+1)^{1000/0.5}-1)B\\ &=(((\frac{Y}{Y+C}+1)^{0.5/1000}-1+1)^{1000/0.5}-1)B\\ &=\frac{YB}{Y+C} \end{aligned} \]

所以,当有用户买入 RAM 时,B 和 C 的变化情况为:

\[ \begin{aligned} B_1 &= B_0 + X_0\\ C_1 &= C_0 - RAM\\ &= C_0 - \frac{X_0C_0}{X_0+B_0}\\ &= \frac{B_0C_0}{X_0+B_0} \end{aligned} \]

那么,就有:

\[ \begin{aligned} B_1C_1 &= (B_0 + X_0)\frac{B_0C_0}{X_0+B_0}\\ &= B_0C_0 \end{aligned} \]

同理也可证明卖出 RAM 也是一样的结论,感兴趣的同学可以去试试~

所以可以得出一个组略的结论:

无论 RAM 市场如何波动,B 和 C 的乘积是 几乎 保持不变的。

这个乘积就是 64 * 1024 * 1024 KiB * 100 万 EOS,即 67108864000000,设为 G,那么:

\[ RAM=\frac{XC}{X+B}=\frac{XC}{X+\frac{G}{C}}=\frac{C}{1+\frac{G}{CX}} \]

所以,最终的 RAM 兑换公式即:

\[ RAM=\frac{C}{1+\frac{67108864000000}{CX}} \]

当计算 1 个 EOS 能兑换多少 RAM 时,X=1,此时 RAM 就是汇率了:

\[ UnitEOSforRAM=\frac{XC}{X+B}=\frac{C}{1+B} \]

考虑到 1 远远小于 B,有:

\[UnitEOSforRAM=\frac{C}{1+B}≈\frac{C}{B}≈\frac{C^2}{G}=\frac{C^2}{67108864000000}\]

同样的,1KB 的 RAM 可兑换的 EOS 数量为:

\[UnitRAMforEOS≈\frac{B^2}{67108864000000}\]

举个例子,假设你现在花 1 个 EOS 去买 RAM 时,此时全网剩余 RAM 是 9481966.148KiB,那么你大约能买到的 RAM 数为:

\[KiBUnitEOSforRAM=9481966.148^2/67108864000000=1.339728863KiB\]

跟现网的数据一对比差不多。

那么精确的 RAM 计算公式是怎样的呢:

\[ \begin{aligned} RAM &= ((\frac{RAMCORE}{A-RAMCORE}+1)^2000-1)C\\ &=((\frac{A}{A-RAMCORE})^2000-1)C\\ &=((\frac{A-RAMCORE}{A})^{-{1000/0.5}}-1)C\\ &=((1-\frac{RAMCORE}{A})^{-{1000/0.5}}-1)C \end{aligned} \]

再次将 RAMCORE 代入有:

\[ \begin{aligned} &=((1-((\frac{X}{X+B}+1)^{\frac{0.5}{1000}}-1))^{-\frac{1000}{0.5}}-1)C\\ &=((1-(\frac{X}{X+B}+1)^{\frac{0.5}{1000}}+1)^{-\frac{1000}{0.5}}-1)C\\ &=((2-(\frac{X}{X+B}+1)^{\frac{0.5}{1000}})^{-\frac{1000}{0.5}}-1)C \end{aligned} \]

……我™实在编不下去了

关于 B 和 C

以上,可以看到能精确买到多少RAM和能卖到多少 EOS 取决于 B 和 C。

B 里的数量代表了全网用户用于购买 RAM 所花费的 EOS 数量,其初始值是当前 EOS 总发行量的千分之一(主网刚启动时,总发行量是 10 亿,后续随着超级节点的申领奖励 EOS 的操作会有增发,每年增发不超过 5%)。每有一个人来购买 RAM,在扣除 0.5% 的手续费后,剩下的 EOS 都会进入到这个 B 里;每有一个人卖出 RAM,也都是从这个 B 里提取 EOS。

事实上,真正涉及到转账相关操作的账户是 eosio.ram,买卖时收取的 0.5% 手续费会打到 eosio.ramfee 账户里,可以看到主网激活不到一个月截止目前为止这个账户里已经有 331,986.3543 个 EOS 被烧掉了。

C 是全网内存总闲置容量,在目前最新版本中(tag: v1.0.7),初始值在 代码 中写死为 64G。每有一个人买入 RAM,都会从 C 里减去买到的容量;每有一个人卖出 RAM,也会相应地加上。

RAM 可以用来干什么呢?对普通用户来说用处不大,但是对于开发者来说,用处就大了,它除了可用于存储账户基本信息外(目前创建一个账户至少需要大约 3KB 的 RAM),也是 DApp 运行时所必须的资源,跟 CPU 和带宽不一样,RAM 用完了只能买,或者通过清理掉旧数据来回收已有的 RAM。

B 和 C 的来源

B 和 C 的值可以从 超级节点 提供的的 HTTP 接口里获取,比如:

1
curl http://api.hkeos.com/v1/chain/get_table_rows -X POST -d '{"scope":"eosio","code":"eosio","table":"rammarket","json":true}'

返回结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"rows":[{
"supply":"10000000000.0000 RAMCORE",
"base":{
"balance":"9309779598 RAM",
"weight":"0.50000000000000000"
},
"quote":{
"balance":"7381484.6562 EOS",
"weight":"0.50000000000000000"
}
}],
"more":false
}

B 就是上面的 quote.balance, C 就是 base.balance。

其实 B 和 C 并不能精确地表示全网总计有多少 EOS 用于购买 RAM,又有多少 RAM 被卖出去了。那有没有这样的值呢?可以通过请求这个接口来查看:

1
curl http://api.hkeos.com/v1/chain/get_table_rows -X POST -d '{"scope":"eosio","code":"eosio","table":"global","json":true}'

返回结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
{
"rows":[{
"max_block_net_usage":1048576,
"target_block_net_usage_pct":1000,
"max_transaction_net_usage":524288,
"base_per_transaction_net_usage":12,
"net_usage_leeway":500,
"context_free_discount_net_usage_num":20,
"context_free_discount_net_usage_den":100,
"max_block_cpu_usage":200000,
"target_block_cpu_usage_pct":1000,
"max_transaction_cpu_usage":150000,
"min_transaction_cpu_usage":100,
"max_transaction_lifetime":3600,
"deferred_trx_expiration_window":600,
"max_transaction_delay":3888000,
"max_inline_action_size":4096,
"max_inline_action_depth":4,
"max_authority_depth":6,
"max_ram_size":"68719476736",
"total_ram_bytes_reserved":"59534183615",
"total_ram_stake":"64815246086",
"last_producer_schedule_update":"2018-07-03T09:03:35.000",
"last_pervote_bucket_fill":"1530608393500000",
"pervote_bucket":243287972,
"perblock_bucket":31676074,
"total_unpaid_blocks":81877,
"total_activated_stake":"2824575718422",
"thresh_activated_stake_time":"1529505892000000",
"last_producer_schedule_size":21,
"total_producer_vote_weight":"8383931808888103936.00000000000000000",
"last_name_close":"2000-01-01T00:00:00.000"}],
"more":false
}

上面的 total_ram_stake 就是全网总计有多少 EOS 用于购买 RAM,单位是 0.0001EOS。total_ram_bytes_reserved 就是全网总计有多少 RAM 被卖出去了,单位是 byte。

上面的 json 里的每一项都是全网的全局变量,每一个变量在整个 EOS 网络中都扮演着重要的角色,以后有机会再介绍

RAM 买卖的手续费

下面以 v1.0.5 版本为例介绍下 0.5% 的手续费怎么收的

购买

\[ fee=\frac{quant + 199}{200} \]

这里之所以不直接写成 \(fee = \frac{quant}{200}\) ,主要是为了防止用户把一大笔数量的 EOS 切分成无数笔小额订单使得整体手续费趋近于 0( 因为 EOS 的精度为 0.0001, 所以当 quant 小于 0.02EOS 的时候算出来的 fee 会等于 0,程序员对这个应该都懂的~ )。

假设你用 100EOS 去买 RAM,那么根据上面的公式 \(fee=\frac{100*10000 + 199}{200}=5000.995\) ,取整之后为 5000,即 0.5EOS。

那么系统会先从你的账户中转 99.5EOS 到 eosio.ram 账户,接着再从你的账户中转 0.5EOS 到 eosio.ramfee,最后再用上面的 EOS 兑换 RAM 公式将 99.5EOS 代进去计算出 RAM 值后,将其充到你的账户下。

售卖

\[ fee=\frac{quant}{200} \]

不过目前最新的代码也已经改成:

\[ fee=\frac{quant+199}{200} \]

值得注意的是,售卖的时候扣除手续费会有一个顺序问题,首先先计算出 RAM 兑换 EOS 的个数为 quant,然后系统会从 eosio.ram 账户中转 quant 个 EOS 给你,最后才会从你的账户中再转 fee 个 EOS 到 eosio.ramfee。

之所以不先扣手续费再给你转账,是因为杜绝你账户里本身就没有 EOS 的情况。