风险提示:请理性看待区块链,树立正确的货币观念和投资理念,不要盲目跟风投资,本站内容不构成投资建议,请谨慎对待。 免责声明:本站所发布文章仅代表个人观点,与CoinVoice官方立场无关

EOS REX 安全系列之从源码开始玩转 REX(二)

慢雾科技
2019年05月13日

区块链




By SlowMist Team

前言

上一篇文章粗略分析了整个买卖 rex 的流程,由于篇幅的原因,剩下有一些细节没有分析到位。所以,这篇文章将在上一篇文章的基础上对一些细节进行深入的分析。

前情回顾

上一篇介绍了买卖 rex 的流程,涉及到了几个函数,我们一起回顾下:

1、deposit:用于充值,将 EOS 变成 SEOS,也叫预备金。
2、withdraw:用于提现,将 SEOS 换回 EOS。
3、buyrex:用于从用户的预备金中扣除相应的份额,并用于 rex 的购买。
4、sellrex:用于卖出已经结束锁定的 rex,并将本金连带收益一起放进用户的预备金账户中。 
5、add_to_rex_pool:用于将用户购买的 rex 放进 rex_pool 中,并根据 rex_pool 中的相关信息计算出用户能够购买的 rex 的数量,被 buyrex 函数调用。 
6、fill_rex_order:处理用户卖单,计算收益。

以上几个函数除了 sell_rex 和 fill_rex_order 其他函数都介绍得差不多了,本文将重点介绍这两个函数的细节。

sellrex 函数

    void system_contract::sellrex( const name& from, const asset& rex )   {      require_auth( from );
    runrex(2);
    auto bitr = _rexbalance.require_find( from.value, "user must first buyrex" ); check( rex.amount > 0 && rex.symbol == bitr->rex_balance.symbol, "asset must be a positive amount of (REX, 4)" ); process_rex_maturities( bitr ); ///先收获成熟的rex check( rex.amount <= bitr->matured_rex, "insufficient available rex" );///只能卖成熟的rex
    auto current_order = fill_rex_order( bitr, rex );///拿到出租EOS得到的分红 asset pending_sell_order = update_rex_account( from, current_order.proceeds, current_order.stake_change ); //订单状态不成功 if ( !current_order.success ) { /** * REX order couldn't be filled and is added to queue. * If account already has an open order, requested rex is added to existing order. */ auto oitr = _rexorders.find( from.value ); if ( oitr == _rexorders.end() ) { oitr = _rexorders.emplace( from, [&]( auto& order ) { order.owner = from; order.rex_requested = rex; order.is_open = true; order.proceeds = asset( 0, core_symbol() ); order.stake_change = asset( 0, core_symbol() ); order.order_time = current_time_point(); }); } else { _rexorders.modify( oitr, same_payer, [&]( auto& order ) { order.rex_requested.amount += rex.amount; }); } pending_sell_order.amount = oitr->rex_requested.amount; } check( pending_sell_order.amount <= bitr->matured_rex, "insufficient funds for current and scheduled orders" ); // dummy action added so that sell order proceeds show up in action trace if ( current_order.success ) { dispatch_inline( null_account, "sellresult"_n, { }, std::make_tuple( current_order.proceeds ) ); } }

    以上为 sellrex 函数的具体实现,从开头开始一步一步进行分析。首先抛开 runrex 这个函数,这个函数并不属于本次讨论的范围,runrex 函数主要用于处理 rex_pool 的信息,包括处理到期的资源租赁订单,回收用户资源,处理用户的 rex 卖单等,有兴趣的同学可以先自行研究,以后的文章也会进行单独的分析。

    接上篇分析,sellrex 函数我们分析到了 fill_rex_order 函数就没有继续往下分析了,fill_rex_order 函数也只是讲了最核心的收益公式,这次我们来仔细进行分析。sellrex 流程如下: 

    1、经过了一系列的检查之后,获取用于已经解锁的 rex 的数量,调用 fill_rex_order 获取用户的卖单。 
    2、卖单携带着订单的完成状态,这是一个 flag,分为成功和失败两种状态,当状态为失败的时候,进入上文的 if 条件。 

    2.1、订单状态成功 

    订单状态成功的时候 current_order.proceed 的值大于 0,这个时候通过 update_rex_account 将卖 rex 的所得转至用户的储备金账户。用户就可以直接进行提现或者继续下一轮的购买了。 

    2.2、订单状态为失败 

    这个时候创建一个 order,我们这里为了不混淆,不说卖单,而是说为欠条,是一个 REX 平台给你的借条。什么意思呢?打个比方,你去商店订购商品,商品存货不足,这时候怎么办呢?这时候商店就会给你打一个单,这个单记录了你是谁,你要买多少的商品,买的时间等信息,等有货了就会根据这个单给你补上商品。REX 也是同样的道理,用户在卖 rex 的时候,由于 rex_pool 中的资金不足以支付用户的本金 + 收益,就会将用户的订单暂时挂起。这就是 REX 给你打的欠条,当 REX 资金充足的时候,就会把钱还你。当 sellrex 失败的时候,这个借条记录了以下信息: 

    (1)卖 rex 的用户。 
    (2)要卖的 rex 的数量(记录在 rex_requested 字段中)。 
    (3)用户的收益,此时为 0,因为 rex 没有卖出去,收益是不存在的。 
    (4)抵押状态,这个抵押状态是由于 buyrex 的时候,根据购买的数量会产生的相应的票权。 
    (5)这个欠条创建的时间。 

    3、最后,检查挂起的金额有没有超过已经解锁的 rex 的数量。

    以上就把 sellrex 完整的讲完了,但是还有一个疑问,就是为什么会存在资金不足的情况,以及如何判定资金不足?这些秘密都在 fill_rex_order 里面。下面就详细的分析 fill_rex_order 函数。

      rex_order_outcome system_contract::fill_rex_order( const rex_balance_table::const_iterator& bitr, const asset& rex )   {      auto rexitr = _rexpool.begin();      const int64_t S0 = rexitr->total_lendable.amount;      const int64_t R0 = rexitr->total_rex.amount;      const int64_t p  = (uint128_t(rex.amount) * S0) / R0; ///越多人借用资源收益越高      const int64_t R1 = R0 - rex.amount; ///更新rex pool中rex的数量      const int64_t S1 = S0 - p; ///更新rex pool中EOS的数量      asset proceeds( p, core_symbol() ); ///获得的收益      asset stake_change( 0, core_symbol() );      bool  success = false; ///默认订单完成状态为0
      check( proceeds.amount > 0, "proceeds are negligible" );
      const int64_t unlent_lower_bound = rexitr->total_lent.amount; //计算能未质押的rex pool中的EOS的数量,用于接下来观察是否足够支付用户产生的rex利润 const int64_t available_unlent = rexitr->total_unlent.amount - unlent_lower_bound; // available_unlent <= 0 is possible //rexpool中的钱足够支付rex利润 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 ) { rt.total_rex.amount = R1;///更新rex pool中的rex的数量 rt.total_lendable.amount = S1; ///更新lenableEOS数量 rt.total_unlent.amount = rt.total_lendable.amount - rt.total_lent.amount; ///减少unlent数据 }); //对用户的rexbalance账户进行操作 _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { rb.vote_stake.amount = current_stake_value - proceeds.amount; rb.rex_balance.amount -= rex.amount; rb.matured_rex -= rex.amount; ///减少已经成熟的rex的数量 }); stake_change.amount = bitr->vote_stake.amount - init_vote_stake_amount; success = true; ///不够钱支付的情况 } else { proceeds.amount = 0; }
      return { success, proceeds, stake_change }; }

      上一篇文章我们分析了核心的收益公式是怎么计算出来的。这次从11行开始,看看这个函数做了什么: 

      1、首先获取 unlent_lower_bound 的值,即最低未出租 rex_pool 中的 EOS 的数量。这个字段等于用户从 rex_pool 中借用资源的总量,是以 EOS 为单位的。 

      2、计算 available_unlent 的值,这个值有可能为负数,为什么呢?假设一个场景,你是一个投资经理,你手上有很多投资人的钱,然后你把投资人的钱拿出去放贷收取利息,那么请问,这个时候你手上的资金还有多少?答案自然是:投资人的钱 - 放贷资金 + 放贷收益。REX 相当于这个投资经理,用户可以用少量的成本(EOS)换取大量的贷款(资源),这个时候,REX 的资金池中的资金就就变成了:用户的资金 - 租用的金额 + 租用收益。根据前面的描述,用于租用资源的资金总是小于 REX 平台出租出去的资金,也就是说 在持续出租资源的时候,rex_pool 中的资金总是不断变少的(这里不讨论系统收益的情况)。想清楚这一点,就能明白为什么 available_unlent 的值为负数了,当出租出去的资金大于 rex_pool 中当前资金 + 收益的时候,这个值就会为负数。 

      3、判断用户出售的 rex 获得的收益是否小于 rex_pool 中的剩余资金,相当于投资人想要回自己的钱,这个时候分两种情况: 
      3.1、如果资金不够,那么这个订单就会挂起,此时由 sellrex 函数创建一个欠条,这就是订单失败的由来。 
      3.2、如果够的话,则从 rex_pool 资金池中减去用户收回的资金,更新相关的表字段,更新用户的 rex_balance 账户,扣除相应的 rex 解锁金额。

      那么到这里,整个 sellrex 的流程都讲清楚了,流程图是这样子的:

      区块链

      安全性分析

      由于本次没有拓展新的函数,所以安全结论是和上篇是一样的,但是这次我们可以对上次说的安全问题有更深的了解。在原先版本的 rex 合约中,是没有 check( pending_sell_order.amount <= bitr->matured_rex, "insufficient funds for current and scheduled orders" ) 这一个校验的,这会导致什么呢?我们知道,当资金池中的资金不足以支付用于的卖单的时候,将跳过 if 判断下的所有步骤,直接由 sellrex 函数挂起订单,在这种情况下,恶意用户在系统资金池资金不足的时候,就可以一直卖 rex,叠加挂起订单的 rex 金额,直到资金池有足够的资金支付,出售比购买 rex 数量更多的 rex。但是这样操作还是会卖不出去,因为最后更改用户 rex_balance 的时候由于 asset 结构体自带的溢出检测,是不能成功卖出去的。但是这就会让这个订单成为一笔坏账,在这种情况下,因为有未完成的 sellrex order,整个 REX 系统将停止运行。具体原因是什么可以自己去发现,答案会在下一篇文章揭晓。

      文章可能有说得不对或说得不够好的地方,欢迎讨论交流。

      详情参考: 
      https://eosauthority.com/blog/REX_progress_with_testing_and_implementation_details

      往期文章

      EOS REX 安全系列之从源码开始玩转 REX(一) 

      声明

      本文仅用作技术参考,不构成任何投资建议。投资者应在充分了解相关风险的基础上进行理性投资。


      声明:本内容为作者独立观点,不代表 CoinVoice 立场,且不构成投资建议,请谨慎对待,如需报道或加入交流群,请联系微信:VOICE-V。

      #区块链 #EOS #REX

      评论0条

      慢雾科技

      简介:“慢雾”,专注区块链生态安全

      专栏

      更多>>