bitshares研究系列【费用分配】

bitshares基本概念详解【帐户】中谈到过注册人、引荐人等的费用奖励,也知道每个operation都有费用,那么我们来看一下代码是怎么实现这些费用的统计和分配的!

费用统计对象

account_statistics_object对象会在创建帐号同时创建,有专门统计处理费用的变量和方法,如下:

   /**
    * @class account_statistics_object
    * @ingroup object
    * @ingroup implementation
    *
    * This object contains regularly updated statistical data about an account. It is provided for the purpose of
    * separating the account data that changes frequently from the account data that is mostly static, which will
    * minimize the amount of data that must be backed up as part of the undo history everytime a transfer is made.
    */
   class account_statistics_object : public graphene::db::abstract_object<account_statistics_object>
   {
      public:
         static const uint8_t space_id = implementation_ids;
         static const uint8_t type_id  = impl_account_statistics_object_type;

         account_id_type  owner;

         /**
          * Keep the most recent operation as a root pointer to a linked list of the transaction history.
          */
         account_transaction_history_id_type most_recent_op;
         /** Total operations related to this account. */
         uint32_t                            total_ops = 0;
         /** Total operations related to this account that has been removed from the database. */
         uint32_t                            removed_ops = 0;

         /**
          * When calculating votes it is necessary to know how much is stored in orders (and thus unavailable for
          * transfers). Rather than maintaining an index of [asset,owner,order_id] we will simply maintain the running
          * total here and update it every time an order is created or modified.
          */
         share_type total_core_in_orders;

         /**
          * Tracks the total fees paid by this account for the purpose of calculating bulk discounts.
          */
         share_type lifetime_fees_paid;

         /**
          * Tracks the fees paid by this account which have not been disseminated to the various parties that receive
          * them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid
          * doing massive amounts of uint128 arithmetic on each and every operation.
          *
          * These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance
          * interval.
          */
         share_type pending_fees;
         /**
          * Same as @ref pending_fees, except these fees will be paid out as pre-vested cash-back (immediately
          * available for withdrawal) rather than requiring the normal vesting period.
          */
         share_type pending_vested_fees;

         /// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees
         void process_fees(const account_object& a, database& d) const;

         /**
          * Core fees are paid into the account_statistics_object by this method
          */
         void pay_fee( share_type core_fee, share_type cashback_vesting_threshold );
   };

代码注释中也说明了这个统计对象是用来防止经常变化的数据直接计到account数据中,减少account数据的实时变化。

与费用相关的两个变量:

pending_fees:这个费用包括注册人、引荐人、终身会员引荐人、网络费用,需要等待一定周期才能提取。

pending_vested_fees:这个费用和pending_fees一样,不过这个费用可以直接提取。

void account_statistics_object::pay_fee( share_type core_fee, share_type cashback_vesting_threshold )
{
   if( core_fee > cashback_vesting_threshold )
      pending_fees += core_fee;
   else
      pending_vested_fees += core_fee;
}

在统计对象的pay_fee()函数中,看到只有core_fee大于现金返还的阈值才计入pending_fees,否则直接计入pending_vested_fees。

付费怎么加到统计对象

费用是个很底层的操作,在evaluator.hpp中定义:

   void generic_evaluator::pay_fee()
   { try {
      if( !trx_state->skip_fee ) {
         database& d = db();
         /// TODO: db().pay_fee( account_id, core_fee );
         d.modify(*fee_paying_account_statistics, [&](account_statistics_object& s)
         {
            s.pay_fee( core_fee_paid, d.get_global_properties().parameters.cashback_vesting_threshold );
         });
      }
   } FC_CAPTURE_AND_RETHROW() }

以上代码显示实际缺省调用到account_statistics_object的pay_fee函数,继承generic_evaluator的子对象也可以覆盖此方法。

现时的cashback_vesting_threshold参数值如下:

    "cashback_vesting_threshold": 10000000,

也就是说大于100BTS才会计入pending_fees。

在apply每个operation时会调用pay_fee,如下:

      virtual operation_result apply(const operation& o) final override
      {
         auto* eval = static_cast<DerivedEvaluator*>(this);
         const auto& op = o.get<typename DerivedEvaluator::operation_type>();

         convert_fee();
         pay_fee();

         auto result = eval->do_apply(op);

         db_adjust_balance(op.fee_payer(), -fee_from_account);

         return result;
      }

我们看到core_fee_paid是真正付费值,这个core_fee_paid在哪设置的呢?

   void generic_evaluator::prepare_fee(account_id_type account_id, asset fee)
   {
      const database& d = db();
      fee_from_account = fee;
      FC_ASSERT( fee.amount >= 0 );
      fee_paying_account = &account_id(d);
      fee_paying_account_statistics = &fee_paying_account->statistics(d);

      fee_asset = &fee.asset_id(d);
      fee_asset_dyn_data = &fee_asset->dynamic_asset_data_id(d);

      ...
      if( fee_from_account.asset_id == asset_id_type() )
         core_fee_paid = fee_from_account.amount;
      else
      {
         asset fee_from_pool = fee_from_account * fee_asset->options.core_exchange_rate;
         FC_ASSERT( fee_from_pool.asset_id == asset_id_type() );
         core_fee_paid = fee_from_pool.amount;
         FC_ASSERT( core_fee_paid <= fee_asset_dyn_data->fee_pool, "Fee pool balance of '${b}' is less than the ${r} required to convert ${c}",
                    ("r", db().to_pretty_string( fee_from_pool))("b",db().to_pretty_string(fee_asset_dyn_data->fee_pool))("c",db().to_pretty_string(fee)) );
      }
   }

就在prepare_fee()函数中,如果不是核心资产会通过core_exchange_rate进行转换。

注意在这不是核心资产时有个fee_pool的概念,需要fee_pool数量足够,fee_pool在资产的dynamic_asset_data_id数据中。

费用分配

费用都记录到pending_fees和pending_vested_fees变量后,什么时候处理这些费用?这些费用怎么分配呢?

void account_statistics_object::process_fees(const account_object& a, database& d) const
{
   if( pending_fees > 0 || pending_vested_fees > 0 )
   {
      auto pay_out_fees = [&](const account_object& account, share_type core_fee_total, bool require_vesting)
      {
         // Check the referrer -- if he's no longer a member, pay to the lifetime referrer instead.
         // No need to check the registrar; registrars are required to be lifetime members.
         if( account.referrer(d).is_basic_account(d.head_block_time()) )
            d.modify(account, [](account_object& a) {
               a.referrer = a.lifetime_referrer;
            });

         share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage);
         assert( network_cut <= core_fee_total );

#ifndef NDEBUG
         const auto& props = d.get_global_properties();

         share_type reserveed = cut_fee(network_cut, props.parameters.reserve_percent_of_fee);
         share_type accumulated = network_cut - reserveed;
         assert( accumulated + reserveed == network_cut );
#endif
         share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage);
         share_type referral = core_fee_total - network_cut - lifetime_cut;

         d.modify(asset_dynamic_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) {
            d.accumulated_fees += network_cut;
         });

         // Potential optimization: Skip some of this math and object lookups by special casing on the account type.
         // For example, if the account is a lifetime member, we can skip all this and just deposit the referral to
         // it directly.
         share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage);
         share_type registrar_cut = referral - referrer_cut;

         d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting);
         d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting);
         d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting);

         assert( referrer_cut + registrar_cut + accumulated + reserveed + lifetime_cut == core_fee_total );
      };

      pay_out_fees(a, pending_fees, true);
      pay_out_fees(a, pending_vested_fees, false);

      d.modify(*this, [&](account_statistics_object& s) {
         s.lifetime_fees_paid += pending_fees + pending_vested_fees;
         s.pending_fees = 0;
         s.pending_vested_fees = 0;
      });
   }
}

从上面代码可以看出费用可分为网络费用、终身会员引荐人费用、其它费用,再细分如下:

1、 网络费用(network_fee_percentage):保留费用(reserve_percent_of_fee) + 累积费用

累计费用会计入资产属性accumulated_fees,如下:

get_object 2.3.0
[{
    "id": "2.3.0",
    "current_supply": "263632122726104",
    "confidential_supply": "28514606718",
    "accumulated_fees": 13894043,
    "fee_pool": 128957918
  }
]

2、 终身会员引荐人费用(lifetime_referrer_fee_percentage)

3、 其它费用:引荐人费用(referrer_rewards_percentage) + 注册人费用

1、2、3的比例是按所有费用的比例计算,细分费用按子类费用比例计算。

在执行链维护函数perform_chain_maintenance()时会调用process_fees,还有就是帐号升级account_upgrade_evaluator::do_apply()时会调用process_fees。

至于 deposit_cashback 后的 Vesting_balance,见bitshares基本概念详解【vesting balance】


感谢您阅读 @chaimyu 的帖子,期待您能留言交流!

H2
H3
H4
Upload from PC
Video gallery
3 columns
2 columns
1 column
1 Comment