关键字: REX, 资源交易, 资源租赁, 系统费用, bancor, 成熟期, EOS,eosio.system,voting
EOSIO 智能合约在 v1.6.0 版本增加了一个 system 合约使用的例子, 可提供 EOS 资源交易. 以供社区评估, 调整和构建. REX 只是智能合约层面提供的功能, 而并没有相应的用户界面, 部署选择等内容.
REX 介绍
按照设计思路, REX 是链上的主币持有者参与的一个 CPU 和网络资源租赁市场, 参与者可以通过买卖 REX 池中的 REX 币来借出或收回他们的现有资源. 下面有几个限制条件:
主币持有者, 只有为 21 个超级节点投票或者通过代理抵押投票的主币持有者, 才能参与从 REX 池中租赁 CPU 和 net 资源以满足他们的需要.
每一笔借出的持续时间被设定为 30 天.
资源借出的价格由自动的市场作价者来决定.
REX 币并不能作为数字货币直接交易, 仅是便于做核算的单位, 并有助于反应租赁活动的情况, 计算确定 REX 持有者的回报率.
可选方案: 未来内存资源的买卖以及账户的拍卖收益均可导入 REX 池, 从而提供更多的来源让 REX 持有者获益.
系统费用转向 REX
系统费用目前包含了内存资源的买卖,(网络 CPU 的抵押费用)以及账户的拍卖费用. 在当前新版本的 eosio.system 合约中, 默认设置将系统费用转由 REX 池收集, 该设置生效以后, 所有新产生的系统费用将由 REX 负责收集, 但这并不影响之前作为管理内存买卖的 eosio.ramfee 账户以及收集账户拍卖费用的 eosio.names 账户的固有资金. 这中设计的目的是为了保持系统的向前兼容.
同时, 超级节点管理者仍然保留了是否切换 REX 的权利, 只需要在 system 合约源代码中修改宏 CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX 的定义.
- // 默认是 1, 由 REX 收集系统费用, 如果想保持原样不使用 REX, 则修改下面的值为 0.
- #define CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX 1
依赖
EOSIO/eos 要部署 v1.7.0 版本及以上
EOSIO/eosio.cdt 要部署 v1.5.0 版本及以上
EOSIO/eosio.contracts 要部署 v1.6.0 版本及以上
部署
REX 功能对 system 系统合约的初始化有了新的要求.
[1]eosio.rex
eosio.rex 账户必须加入原有的系统账户, 在合约部署前要被创建成功, 同时该账户不是一个特权账户.
目前构建用于测试的本地 EOS 链的常用方式是使用 python 脚本 eos/tutorials/bios-boot-tutorial/bios-boot-tutorial.py.
关于链启动时序的内容请转到此处复习一下.
那么按照这个要求, 把 eosio.rex 加入到 bbt 脚本中的系统账户集合中.
- systemAccounts = [
- 'eosio.bpay',
- 'eosio.msig',
- 'eosio.names',
- 'eosio.ram',
- 'eosio.ramfee',
- 'eosio.saving',
- 'eosio.stake',
- 'eosio.token',
- 'eosio.vpay',
- 'eosio.rex'
- ]
[2]eosio::init
system 合约的 eosio::init 接口, 最早于 v1.4.0 版本正式引入, 只在 system 合约首次部署的时候被使用到. 在当前版本, 该接口被修改增加了一个内联调用 eosio.token::open 接口的操作, 用来帮助 eosio.rex 账户开启一个主币余额为 0 的入口.
eosio.token::open 接口最早于 v1.3.0 版本引入, 所以建议先由 eosio.token 账户部署一个最近的版本(指超过 v1.3.0 的, 本篇研究时的环境均为 v1.6.0 版本)eosio.token 合约, 然后再部署 system 合约.
如果是最新版本替换旧版本, 则 eosio::init 动作是不必要的甚至不允许的. 区块生产者可执行 eosio.token::open 动作来帮助 eosio.rex 账户开启一个主币余额为 0 的入口. 所以在 bbt 脚本中无须针对此处做任何修改.
[3]rex.results.abi
ABI 文件 rex.results.abi 需要被账户 eosio.rex 部署, 而相应的 rex.results.wasm 不能被部署. rex.results 合约的接口 buyresult, sellresult, rentresult, 和 orderresult 都没有外部操作. 他们都作为一种内联的操作集成进接口 rentnet, rentcpu, buyrex, unstaketorex, and sellrex. 内联的操作不会造成任何影响, 他们的数据包含在父接口的动作中, 可被追踪.
按照这个要求, 需要在 bbt 脚本中补充:
- def stepSetSystemContract():
- retry(args.cleos + 'set contract eosio.rex' + args.contracts_dir + '/eosio.system/ eosio.system.wasm rex.results.abi')
- retry(args.cleos + 'set contract eosio' + args.contracts_dir + '/eosio.system/')
- ...
- [official PR https://github.com/EOSIO/eos/pull/7074 ]
REX 实现
本节通过以下 12 个方面介绍 REX 实现的详细逻辑.
(一)用户 REX 基金
要想得到 REX 的相关操作, 用户需要首先创建一个 REX 基金, 并且使用主币向该基金充值.
基金不仅用于 burrex 动作, 还用于所有的涉及到修改用户余额的动作. 它也方便退款以及延迟卖单, 因为它可能会被另一个用户所执行.
deposit: 首次调用时, 会为该用户创建一条 rex_fund 记录并通过转入主币数量设置余额. 后续的继续充值的操作会修改 rex_fund 的 balance 字段. 接着会调用内联转账, 从用户的主币余额中真实划账. 下面给测试账户 useraaaaaaaa 创建 rex 基金, 观察余额变化, 账户余额从 100000 减少了 10 块被转到了 REX 基金, 还剩下 99990.:
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 get currency balance eosio.token useraaaaaaaa "SYS"
- 100000.0000 SYS
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex deposit useraaaaaaaa "10.0000 SYS"
- executed transaction: 084717efbf446f25e58226e63ce71b1a17745f85a56cb4dbf663b6e410e2393d 120 bytes 465 us
- # eosio <= eosio::deposit {
- "owner":"useraaaaaaaa","amount":"10.0000 SYS"
- }
- # eosio.token <= eosio.token::transfer {
- "from":"useraaaaaaaa","to":"eosio.rex","quantity":"10.0000 SYS","memo":"deposit to REX fund"
- }
- # useraaaaaaaa <= eosio.token::transfer {
- "from":"useraaaaaaaa","to":"eosio.rex","quantity":"10.0000 SYS","memo":"deposit to REX fund"
- }
- # eosio.rex <= eosio.token::transfer {
- "from":"useraaaaaaaa","to":"eosio.rex","quantity":"10.0000 SYS","memo":"deposit to REX fund"
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 get currency balance eosio.token useraaaaaaaa "SYS"
- 99990.0000 SYS
unstaketorex: 所有 REX 的消费和收益均体现在对 rex_fund 的减少和增加操作, 但其中只要一个例外就是 unstaketorex 动作, 允许用户使用抵押币来购买 REX. 下面通过账户 useraaaaaaaa 的抵押币够买 rex 基金, 观察抵押币的变化, 确实 net 和 CPU 各减少了 100 块, 对上账了(顺便再查看一下账户的余额, 确实没有变化, 仍旧是 99990).
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system listbw useraaaaaaaa
- Receiver Net bandwidth CPU bandwidth
- useraaaaaaaa 193728833.4889 SYS 193728833.4889 SYS
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex unstaketorex useraaaaaaaa useraaaaaaaa "100.0000 SYS" "100.0000 SYS"
- executed transaction: 45ea6749c406f496b5554e3729e8e53fbc848c964635298483a25ec8dc6a6e79 144 bytes 1042 us
- # eosio <= eosio::unstaketorex {
- "owner":"useraaaaaaaa","receiver":"useraaaaaaaa","from_net":"100.0000 SYS","from_cpu":"100.0000 SYS...
- # eosio.token <= eosio.token::transfer {"from":"eosio.stake","to":"eosio.rex","quantity":"200.0000 SYS","memo":"buy REX with staked tokens"...
- # eosio.stake <= eosio.token::transfer {"from":"eosio.stake","to":"eosio.rex","quantity":"200.0000 SYS","memo":"buy REX with staked tokens"...
- # eosio.rex <= eosio.token::transfer {"from":"eosio.stake","to":"eosio.rex","quantity":"200.0000 SYS","memo":"buy REX with staked tokens"...
- # eosio.rex <= eosio.rex::buyresult {"rex_received":"2000000.0000 REX"
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system listbw useraaaaaaaa
- Receiver Net bandwidth CPU bandwidth
- useraaaaaaaa 193728733.4889 SYS 193728733.4889 SYS
withdraw: 允许用户从 rex_fund 中取出主币. 通过调用一个内联 token 转账到用户账户的操作. 下面测试该动作, 首先为账户 useraaaaaaaa 提取 100 块, 但提示资金不足, 说明使用抵押币购买的 REX 基金不能被 withdraw 动作取出, 因此改为提取 1 块, 执行成功, 检查账户余额, 由 99990 变为 99991, 对上账了.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex withdraw useraaaaaaaa "100.0000 SYS"
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: insufficient funds
- pending console output:
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex withdraw useraaaaaaaa "1.0000 SYS"
- executed transaction: 1d24ac5d5e1cce3eb13b108793ea31b8ee5cb73abbf68002d18d7a8196951c7d 120 bytes 644 us
- # eosio <= eosio::withdraw {
- "owner":"useraaaaaaaa","amount":"1.0000 SYS"
- }
- # eosio.token <= eosio.token::transfer {
- "from":"eosio.rex","to":"useraaaaaaaa","quantity":"1.0000 SYS","memo":"withdraw from REX fund"
- }
- # eosio.rex <= eosio.token::transfer {
- "from":"eosio.rex","to":"useraaaaaaaa","quantity":"1.0000 SYS","memo":"withdraw from REX fund"
- }
- # useraaaaaaaa <= eosio.token::transfer {
- "from":"eosio.rex","to":"useraaaaaaaa","quantity":"1.0000 SYS","memo":"withdraw from REX fund"
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 get currency balance eosio.token useraaaaaaaa "SYS"
- 99991.0000 SYS
(二)REX 池余额
REX 池代表了 REX 系统的全局状态, 它由多种余额组成:
total_lendable, 表示 REX 池的总主币价值. 它是购买 REX 的资金, 以及 NET 和 CPU 抵押的资金, 内存交易的资金, 还有账户名拍卖的收益之和(后三项取决于系统费用转向 REX 的设置).
total_rex, 表示该账户所有的 REX 币总量, 取决于买卖 REX 动作的交易量. 任何时候, 账户的 REX 币余额都指的是 total_lendable 除以 total_rex.++ 系统费用转向 REX 有利于增加 REX 币的价值同时可提供更多的主币出租.++
REX pool balances = total_lendable / total_rex
total_unlent, 表示 total_lendable 的其中可以被出租的一部分.
total_rent, 表示 total_lendable 的其中已出租的一部分. 所以按照定义:
- total_lendable = total_unlent + total_lent
- total_rent
total_rent 是一个虚拟余额, 它的初始化值必须是正数, 基于对预期的主币能够在部署后不久就可用的评估得到这个初始化值, 所以出租成本与其他市场类似. total_unlent 和 total_rent 是 Bancor 算法中的两个连接器, 决定了 CPU 和 NET 的出租价格. 为了更好地理解这个算法在 REX 中的应用, 请参照一篇文章.
区块生产者可按需通过 setrex 动作来重置 REX 池的 total_rent 余额. 但这个行为在初始化 REX 系统时并不是必须的, 也不推荐使用超过一次. 这是一个备份机制, 当初始设置有误或者不符合 token 借出的金额时, 可让区块生产者能够平衡租借市场的价格. setrex 动作不会使 total_rent 加入或删除某个真的 token.
(三)余额购买 REX
余额指的是用户的 REX 基金 rex_fund 的余额, 单位是以主币计算, 使用该余额来购买 REX, 可以通过 buyrex 动作.
payment: 是指用户通过提供一定数量的主币, 来交易得到 REX 币.
payment 将被添加到账户的投票抵押, 相应的超级节点的投票数量也会更新, 所以余额购买 REX 币的过程与 CPU.NET 资源抵押的过程非常相似.
该机制也正是为了 decreasing voter apathy(减少选民冷漠)
buyrex: 该动作可以让用户出租他们的主币. REX 币的发行是预先计算好的, 因此在该动作执行前后, total_lendable 除以 total_rex 的比率是一样的. 也就是说, buyrex 动作并未对 REX 的价值造成变化. buyrex 动作强制只有抵押投票或代理参与投票 21 个超级节点的账户才可以调用.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex buyrex useraaaaaaaa "4.0000 SYS"
- executed transaction: c713e98584f971d71d21676a830ac15a8b557d48582677b334cdc34cd12e8860 120 bytes 894 us
- # eosio <= eosio::buyrex {
- "from":"useraaaaaaaa","amount":"4.0000 SYS"
- }
- # eosio.rex <= eosio.rex::buyresult {
- "rex_received":"40000.0000 REX"
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
(四)抵押购买 REX
上面谈了使用在 rex_fund 基金中的主币余额购买 REX 的方法, 用户也可以通过抵押币来购买 REX, 不需要先解除自己的抵押到余额, 这个方法就是在上面已经演示过的 unstaketorex 动作. 该动作会分别减少账户的 CPU 资源抵押额: from_cpu, 和 NET 资源抵押额: from_net, 购买 REX 的总量是 from_net + from_cpu. 同时也要更新对应超级节点的投票数量.
(五)REX 成熟期
购买 REX 后的卖出限制是 4 天, 也就是说 4 天以后才可以卖出你的 REX 币. 根据不同的购买方式, token 将被累计到不同的独立的位置 (称作成熟桶) 记录, 通过账户的 rex_balance 的 rex_maturities 字段. 这些成熟桶们分别记录着不同购买来源的 REX 距离可售卖的倒计时时间(称作成熟期), 例如 4 天, 3 天, 2 天.... 已成熟的 REX 将没有成熟期, 可以随时被卖出, 这部分 REX 被存储于账户的 rex_balance 的 matured_rex 字段, 这种基于成熟期的延迟机制是为了给租赁市场反应时间.
consolidate: 该动作允许用户合并所有的成熟桶以及已成熟的 REX 到一个新的成熟桶内, 且该桶的初始成熟期 4 天重新生效.(为了减少那么多桶, 看着麻烦)
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex consolidate useraaaaaaaa
- executed transaction: 7cc631030d159e21d6327253a5261b04bbb1f5fa73c5455975e616bc9f70c29f 104 bytes 415 us
- # eosio <= eosio::consolidate {
- "owner":"useraaaaaaaa"
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
(六)REX 储蓄桶
正如上面提到的成熟桶, 一个 REX 持有者可以利用一个特殊的桶, 称作储蓄桶. REX 在这个桶中永远不能成熟, 所以不可对外售出.
mvtosavings: 该动作可以让用户将其拥有的其他桶中购买的 REX 转到储蓄桶.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex mvtosavings useraaaaaaaa "10.0000 REX"
- executed transaction: a93fe2254b236d470de8c1366de8eab9aed5230a75bef8b2868daee1003ca889 120 bytes 409 us
- # eosio <= eosio::mvtosavings {
- "owner":"useraaaaaaaa","rex":"10.0000 REX"
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
mvfromsavings: 该动作可以让用户从储蓄桶中移出 REX 币到一个新的成熟桶内, 且该桶的初始成熟期 4 天重新生效.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex mvfromsavings useraaaaaaaa "10.0000 REX"
- executed transaction: 4357c4cac358b66b28e05953603228581f3689a7191bc987d9e26cef7e9b1edf 120 bytes 447 us
- # eosio <= eosio::mvfrsavings {
- "owner":"useraaaaaaaa","rex":"10.0000 REX"
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
(七)卖出 REX
sellrex: 允许用户卖出指定数量的 REX, 换取相应的主币. 这相当于未出借的主币, 如果 total_unlent 中的主币足够用户卖出的数量, 则该笔订单将被处理, 否则订单排队, 直到条件满足. 卖单的价格是在处理时间时决定的, 而不是在订单创建时间(假设这两个时间是不同的). 当订单被填写时, 该用户的投票抵押金额也会更新为当前 REX 币的值. 下面测试该命令的使用方法, 但由于 REX 成熟期限制, 目前还没办法有效卖出 REX.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex sellrex useraaaaaaaa "500.0000 REX"
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: insufficient available rex
- pending console output:
cancelrexorder: 在订单被填写之前, 用户可以通过这个动作取消一个正在排队中的订单. REX 卖出的销售所得将被添加到用户的 rex_fund 中. 下面测试该命令的使用方法, 但由于目前没有排队订单, 所以提示无法成功执行订单取消动作.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex cancelrexorder useraaaaaaaa
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: no sellrex order is scheduled
- pending console output:
(八)处理 REX 卖单
当订单不能被填写时, 会被加入到一个队列, 就是创建一条 rex_order 记录, 该数据结构的字段包括:
owner, 订单拥有者(PK)
rex_requested, 订单要卖出多少的 REX
order_time, 订单时间, 默认是当前时间 current_time_point()
proceeds, 订单收益, 也就是主币结算的数量, 默认是 "0.0000 SYS"
stake_change, 对抵押数量的影响, 默认是 "0.0000 SYS"
is_open, 订单状态是否开放, 默认是 true
函数 fill_rex_order: 用来填写一个订单, 其中成功执行的条件是一些动作提供了足够的未借出主币, 换句话讲, 卖出 REX 换来的主币是从 REX 池中的主币余量结算的, 所以 REX 池要保证有足够的未借出主币. 这个时候, 设置 is_open=flase, 设置 proceeds 为 rex_requested 与当前 REX 与主币的汇率计算的一个主币收益值, 然后计算抵押数量的改变 stake_change, 同时要更新用户的 REX 余额, 减去卖出的 REX 数量, rex_balance -= rex_requested, 然后设置 vote_stake 为当前 rex_balance 的值, rex_pool 的余额也相应改变. 然后通过二级索引 order_time 以及 is_open 两个条件, 该订单将被移到队列的最末端.
- /**
- * @brief 执行一笔 sellrex 订单并返回包含结果的对象
- *
- * 执行一笔刚进来或已在队列中的订单. 如果 REX 池中已有足够的未冻结在资源租赁的主币, 则成功填写该订单.
- * 这种情况下, REX 池总量, 用户的 rex_balance 以及用户 vote_stake 字段都会被更新. 然而, 这个函数不更新
- * 用户的投票权利. 函数返回成功标志, 订单收益, 和投票抵押内容. 这些将在不同的函数中使用到, 用来完成
- * 订单处理, 收益转账到用户的 REX 基金并更新用户的投票权重.
- *
- * @param bitr - 迭代器, 直系想 rex_balance 数据库记录
- * @param rex - 要被卖出的 rex 数量
- *
- * @return 结构体 rex_order_outcome, 包含成功标志位, 订单收益以及投票抵押更改
- */
- rex_order_outcome system_contract::fill_rex_order( const rex_balance_table::const_iterator& bitr, const asset& rex )
- {
- auto rexitr = _rexpool.begin(); // rex 池
- const int64_t S0 = rexitr->total_lendable.amount; // total_lendable 值
- const int64_t R0 = rexitr->total_rex.amount; // total_rex 值
- const int64_t p = (uint128_t(rex.amount) * S0) / R0;
- const int64_t R1 = R0 - rex.amount;
- const int64_t S1 = S0 - p;
- asset proceeds( p, core_symbol() ); // proceeds 资产
- asset stake_change( 0, core_symbol() ); // stake_change 资产
- bool success = false;
- const int64_t unlent_lower_bound = ( uint128_t(2) * rexitr->total_lent.amount ) / 10;
- const int64_t available_unlent = rexitr->total_unlent.amount - unlent_lower_bound; // available_unlent <= 0 is possible
- if ( proceeds.amount <= available_unlent ) { // 余额充足
- const int64_t init_vote_stake_amount = bitr->vote_stake.amount;
- const int64_t current_stake_value = ( uint128_t(bitr->rex_balance.amount) * S0 ) / R0;
- _rexpool.modify( rexitr, same_payer, [&]( auto& rt ) { // 修改 rex 池状态表
- rt.total_rex.amount = R1;
- rt.total_lendable.amount = S1;
- rt.total_unlent.amount = rt.total_lendable.amount - rt.total_lent.amount;
- });
- _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { // 修改_rexbalance 状态表
- rb.vote_stake.amount = current_stake_value - proceeds.amount;
- rb.rex_balance.amount -= rex.amount;
- rb.matured_rex -= rex.amount;
- });
- stake_change.amount = bitr->vote_stake.amount - init_vote_stake_amount;
- success = true;
- } else {
- proceeds.amount = 0;
- }
- return { success, proceeds, stake_change };
- }
函数 update_rex_account: 其余 rex_order 的处理必须由 owner 执行一个动作, 这涉及到将 proceeds 按要求打入用户的 rex_fund, 另外通过增加 stake_change(正负均可)到 voter_info 的 staked 字段来更新用户的投票权重, 同时对应的超级节点的选票也会更新, 到此就全部执行完毕, 删除该订单.
- /**
- * @brief 执行用户已填写的 sellrex 订单并更新投票权重
- *
- * 检查用户是否有在队列内的已填写 sellrex 订单, 执行它然后删除它. 执行时要将订单收益转账给
- * 用户的 REX 基金以及更新用户的投票权重.
- *
- * @param owner - owner EOS 账户
- * @param proceeds - 额外收益, 转给 owner 的 REX 基金
- * @param delta_stake - 额外抵押, 加到 owner 的投票权重
- * @param force_vote_update - 设为 true 的时候, 投票权重被更新即使没变化
- *
- * @return asset - 如果存在订单, owner 未填写的卖单的 REX 数量
- */
- asset system_contract::update_rex_account( const name& owner, const asset& proceeds, const asset& delta_stake, bool force_vote_update )
- {
- asset to_fund( proceeds );
- asset to_stake( delta_stake );
- asset rex_in_sell_order( 0, rex_symbol );
- auto itr = _rexorders.find( owner.value );
- if ( itr != _rexorders.end() ) { // 找到已存在的订单
- if ( itr->is_open ) { // 订单开放状态, 修改卖单价格
- rex_in_sell_order.amount = itr->rex_requested.amount;
- } else { // 未开放则添加至已有订单
- to_fund.amount += itr->proceeds.amount;
- to_stake.amount += itr->stake_change.amount;
- _rexorders.erase( itr );
- }
- }
- if ( to_fund.amount> 0 )
- transfer_to_fund( owner, to_fund );
- if ( force_vote_update || to_stake.amount != 0 )
- update_voting_power( owner, to_stake ); // 更新投票权重
- return rex_in_sell_order;
- }
一个用户可以只有一个开放的 rex_order. 如果该账户有执行了一个新的 sellrex 动作, 这笔订单不会被立即填写, 请求卖出的 REX 数量会被添加到已存在的订单的 rex_requested 字段中去, 相当于在有开放订单的状态下, 新订单会更新该开放订单, 不必冗余生成新的, 节约了订单量.
(九)REX 租赁
REX 租赁就是通过 REX 来租赁资源, 包括 CPU.NET 资源.
rentcpu: 一个用户可以作为 reveiver 通过 rentcpu 动作获得对应支付主币数量的 CPU 资源. 该动作将创建一条 rex_loan 记录在 cpuloan 状态表中. 至于用户获得的资源的数量, 是通过 bancor 算法计算出当前市场价格, 按照支付的主币数量进行核算, 加到用户的资源抵押额中, 同时该值也会记录在 rex_loan 的 total_staked 字段.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex rentcpu useraaaaaaaa useraaaaaaaa "3.0000 SYS" "1.0000 SYS"
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: loan price does not favor renting
- pending console output:
rentnet: 一个用户可以作为 reveiver 通过 rentnet 动作获得对应支付主币数量的 NET 资源. 该动作将创建一条 rex_loan 记录在 netloan 状态表中. 至于用户获得的资源的数量, 是通过 bancor 算法计算出当前市场价格, 按照支付的主币数量进行核算, 加到用户的资源抵押额中, 同时该值也会记录在 rex_loan 的 total_staked 字段.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex rentnet useraaaaaaaa useraaaaaaaa "3.0000 SYS" "1.0000 SYS"
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: loan price does not favor renting
- pending console output:
错误分析
这两个动作的调用都报出了 "loan price does not favor renting" 的错误, 在源码中寻找该错误的解释.
- int64_t rented_tokens = get_bancor_output( pool->total_rent.amount, pool->total_unlent.amount, payment.amount );
- check( payment.amount <rented_tokens, "loan price does not favor renting" );
payment 即第三个参数, 是指用户支付的主币金额, 从 rex_fund 余额中划出. 而 rented_tokens 变量是通过 bancor 算法得到的一个已出租 token 的值, 该值是由 REX 池中的是 total_unlent 和 total_rent 来决定, 即 bancor 算法的两个连接器, 或者说两个条件值是 total_unlent 和 total_rent, 一个是 REX 池中所有的未出借主币数量, 一个是已出借主币数量. 计算得到的 total_staked 的金额是从 total_unlent 转入到 total_rent 的数量, 并且支付的主币也会被添加到 total_rent.REX 资源租赁被创建后, 支付的主币会被添加到 REX 池的 total_lendable, 同时 total_unlent 因此被增加了 REX 币以及增加了可供租借的主币. 资源贷款期限是 30 天, 到期时会从 receiver 的资源中减去对应的抵押金额 total_staked.total_staked 会从 total_lent 迁回到 total_unlent,total_lent 会根据 Bancor 相应更新.
所以分析上面无法调通 rentcpu 以及 rentnet 的原因是 rented_token 在目前的环境下太低所致, bancor 市场没有建立起来, 也就是可租借额度很低, 我们买不到资源, 所以要提高 REX 池的可租借主币的额度. 下面是 get_bancor_output 函数的计算方式.
- /**
- * 该函数通过给定的两个连接器余额, 以及一个输入的金额, 使用 Bancor 算法计算出结果.
- *
- * @param in - 输入的金额
- * @param conin - 输入连接器的余额
- * @param conout - 输出连接器的余额
- *
- * @return int64_t - 转换输出金额
- */
- int64_t get_bancor_output( int64_t conin, int64_t conout, int64_t in )
- {
- const double F0 = double(conin);
- const double T0 = double(conout);
- const double I = double(in);
- auto out = int64_t((I*T0) / (I+F0)); // 公式
- if ( out < 0 ) out = 0;
- return out;
- }
(十)租赁自动更新
在 rentcpu 和 rentnet 动作中, 用户均可提供一个额外的主币金额增加到租赁 balance 字段, 在到期日, 如果有足够的基金该笔租赁单子可以被重新恢复, 即 balance>= payment, 否则租赁关闭, 退还用户所有仍在租赁 balance 中的主币. 如果一笔租赁单子被重新恢复了, total_staked 会使用当前市场价和 receiver 收到影响而更新的资源限制重新计算. REX 池的余额也会被更新. 一个租赁单子 (由 rentcpu 和 rentnet 产生) 的拥有者可以投资一笔租赁, 以 loan_num 作为 id 鉴别, 使用动作 fundcpuloan 和 fundnetloan.owner 也可以从 loan 余额中提取, 使用动作 defundcpuloan 和 defundnetloan.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex fundcpuloan useraaaaaaaa 1 "1.0000 SYS"
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: loan not found
- pending console output:
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex fundnetloan useraaaaaaaa 1 "1.0000 SYS"
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: loan not found
- pending console output:
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex defundcpuloan useraaaaaaaa 1 "1.0000 SYS"
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: loan not found
- pending console output:
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex defundnetloan useraaaaaaaa 1 "1.0000 SYS"
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: loan not found
- pending console output:
由于前面未成功生成 loan 记录, 所以无法得到有效的 loan_num 对应的记录, 这 4 个动作都依赖该 loan 记录, 所以无法成功执行.
查询 loan 租赁记录的方式:
cleos get table eosio eosio cpuloan --index 3 --key-type name -L strarteosfee -U strarteosfee
(十一)REX 维护
在大部分的 REX 动作中, 都是调用 runrex 函数. 它启动固定的 2 笔卖单的进程(默认为 2), 计算着他们的 CPU 以及 NET 租赁的过期时间, 执行着上面描述的 REX 卖单成熟以及租赁到期的工作.
rexexec: 任何账户都能通过执行 rexexec 动作直接调用 runrex 函数, 该函数拿到输入的最大订单量, 网络租赁以及待处理的 CPU 租赁, REX 卖单会比租赁享受更高的优先级. 这意味着当 rex_order 队列不为空时, 没有新的租赁订单会被创建, 也没有已存在的租赁订单被重新恢复.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex rexexec useraaaaaaaa 2
- executed transaction: 5e85255c4dd3e6ed6c98b73eafbc26ed84d4cf05b0c91c2e47e2b447a5653edd 104 bytes 280 us
- # eosio <= eosio::rexexec {
- "user":"useraaaaaaaa","max":2
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
updaterex: 该动作通过当前 REX 余额的主币价值更新了一个用户的投票抵押. 也更新了该用户的投票对象的投票权重.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex updaterex useraaaaaaaa
- executed transaction: e3dc3814dacb85313a76683217b023b010a7c19334f6e67489a2b8c0e94f616b 104 bytes 900 us
- # eosio <= eosio::updaterex {
- "owner":"useraaaaaaaa"
- }
- warning: transaction executed locally, but may not be confirmed by the network yet ]
closerex: 该动作从所有 REX 相关的状态表中删除了一个用户的记录并且释放了已使用的内存资源. 如果该用户的 REX 余额不是 0, 该动作失败, 否则, 该用户的 rex_balance 值将被删除. 如果该用户没有额外的租赁订单并且 rex_fund 为 0, 删除其 rex_fund 字段. 下面测试该命令的使用方法, 执行响应报错表示, 必须用户的 REX 余额为 0 才可以被删除, 所有需要卖光所有 REX 币, 否则该动作失败.
- evsward@evsward-ThinkPad-E480:~$ cleos --wallet-url http://127.0.0.1:6666 system rex closerex useraaaaaaaa
- Error 3050003: eosio_assert_message assertion failure
- Error Details:
- assertion failure with message: account has remaining REX balance, must sell first
- pending console output:
(十二)投票需求
重申一下 REX 系统关于投票的硬性要求, 就是所有持有 REX 的账户必须直接或者通过代理参与了为超级节点的投票.
总结
本文详尽地介绍了 REX 系统的内容. REX 是 2019 年以来 EOS 最新的重大功能发布, 该项目由 BM 牵头提出核心算法分析, 继而由 blockone 公司开发相关功能. 本文从核心的 bancor 算法分析, 到具体的命令, 包括 deposit,buyrex,sellrex,rentcpu,rentnet,closerex 等一系列 REX 动作的分析与实践, 及时同步了 EOS 的最新动作.
更多文章请转到一面千人的博客园.
来源: https://www.cnblogs.com/Evsward/p/rex.html