REX:EOS资源租赁平台详解

关键字: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]

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_unlenttotal_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_balancerex_maturities字段。这些成熟桶们分别记录着不同购买来源的REX距离可售卖的倒计时时间(称作成熟期),例如4天,3天,2天...。已成熟的REX将没有成熟期,可以随时被卖出,这部分REX被存储于账户的rex_balancematured_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_infostaked字段来更新用户的投票权重,同时对应的超级节点的选票也会更新,到此就全部执行完毕,删除该订单。
/**
 * @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_loantotal_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_loantotal_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_unlenttotal_rent来决定,即bancor算法的两个连接器,或者说两个条件值是total_unlenttotal_rent,一个是REX池中所有的未出借主币数量,一个是已出借主币数量。计算得到的total_staked的金额是从total_unlent转入到total_rent的数量,并且支付的主币也会被添加到total_rent。REX资源租赁被创建后,支付的主币会被添加到REX池的total_lendable,同时total_unlent因此被增加了REX币以及增加了可供租借的主币。资源贷款期限是30天,到期时会从receiver的资源中减去对应的抵押金额total_stakedtotal_staked会从total_lent迁回到total_unlenttotal_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池的余额也会被更新。一个租赁单子(由rentcpurentnet产生)的拥有者可以投资一笔租赁,以loan_num作为id鉴别,使用动作fundcpuloanfundnetloan。owner也可以从loan余额中提取,使用动作defundcpuloandefundnetloan

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的最新动作。

更多文章请转到一面千人的博客园

posted @ 2019-04-07 16:20 一面千人 阅读(...) 评论(...) 编辑 收藏