eos源码赏析(二十五):管中窥“Rex”(上)

2021-11-23 10:42:21 浏览数 (1)

受限于个人工作及时间原因,有月余未曾动笔,在群内和伙伴们的交流也变少了,还好对于EOS这个项目的求知欲没有放下。这段时间被讨论的较多的便是资源的紧张,主要体现在CPU资源的紧张,我们知道任何交易信息的发生都会相应的“消耗”CPU资源,而这个资源是可以慢慢恢复的,然而由于某些原因导致无论如何都跨不过CPU资源这道坎,因此形形色色的CPU租赁平台出现了,为资源使用者提供您CPU资源,为租户提供一定的收益。而在EOS系统中也酝酿着资源租赁的影子,我们今天就从已经出现在系统合约中的内容来聊聊资源租赁,主要是系统中的Rex。

在聊Rex之前我们先来看看BM在Medium中对Rex的定义:

https://medium.com/@bytemaster/proposal-for-eos-resource-renting-rent-distribution-9afe8fb3883a

英文阅读稍有障碍的可以参考eosnewyork关于Rex的解读。

BM提出了一个很好的解决方案,即REX(EOS Resource Exchange/EOS资源交易所)。REX旨在解决以下问题:

  • 创建一个市场(即资源交换所):相比较购买EOS,只需用购买价的一小部分就可以租得对应EOS抵押获取的资源。
  • 创造收益:EOS的出租方获得租金和EOS网络费用(即eosio.ramfee and eosio.names账户里的钱)
  • 屏蔽币价波动:资源的使用方(即租用资源的人)可以不受币价波动的影响。
  • 提高网络安全:参与REX需要至少投票给21个节点或者委托给一个投票代理投票。

那么,我们来结合代码看看,Rex的雏形是什么样的,有人租赁定然有人出租,出租者将一定金额的EOS代币抵押到Rex基金账户,如下:

代码语言:javascript复制
   /**
    * @brief 充值eos代币到Rex基金账户
    * @param 充值的用户
    * @param 充值的数量
    */
    void system_contract::deposit(const name& owner, const asset& amount)
    {
        require_auth(owner);
        check(amount.symbol == core_symbol(), "must deposit core token");
        check(0 < amount.amount, "must deposit a positive amount");
        INLINE_ACTION_SENDER(eosio::token, transfer)( token_account,
            { owner, active_permission },
            { owner, rex_account, amount, "deposit to REX fund" });
        transfer_to_fund(owner, amount);
        update_rex_account(owner, asset(0, core_symbol()), asset(0, core_symbol()));
    }

用户可以充值,相反的过程即提现也应当存在,如下:

代码语言:javascript复制
    /**
     * @brief 从Rex基金账户提现充值的EOS代币
     * @param owner - Rex的用户
     * @param amount - 用户提现的金额
     */
    void system_contract::withdraw(const name& owner, const asset& amount)
    {
        require_auth(owner);

        check(amount.symbol == core_symbol(), "must withdraw core token");
        check(0 < amount.amount, "must withdraw a positive amount");
        update_rex_account(owner, asset(0, core_symbol()), asset(0, core_symbol()));
        transfer_from_fund(owner, amount);
        INLINE_ACTION_SENDER(eosio::token, transfer)( token_account,
            { rex_account, active_permission },
            { rex_account, owner, amount, "withdraw from REX fund" });
    }

抵押和赎回的过程都很清晰明了:

  • 用户权限校验
  • 代币符号校验
  • 抵押或者赎回的金额校验
  • 转入或者从Rex基金账户转出

然后我们可以看到update_rex_account和transfer_to_fund或者transfer_to_fund这几个函数,一个个来看:

代码语言:javascript复制
 /**
        * @brief 处理用户的Rex账户操作同时更新该用户的投票权重。
        * 检查用户当前是否有已提交的出售Rex的订单,等订单结束之后并删除,把这些详细记录到Rex中并更新用户的投票权限,当然也可以通过一些参数的设定跳过某些内容。
        * 该函数只有用户触发的才会被调用
        * @param owner - 用户名
        * @param proceeds - 用户账户和Rex基金账户之间的处理
        * @param delta_stake - 更新用户的投票权重
        * @param force_vote_update - 如果是true的情况,投票权限会被更新即使投票抵押未变化
        * @return asset - 由于是测试代码,我们先不关心返回值 
        */
    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, core_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;
    }

该函数的核心便是查询_rexorders这张表中是否存在有该用户的相关信息,如果交易记录表中该用户已委托订单且处于open状态,则处理该订单的信息,如果订单已结束则更新这个用户的的Rex基金的余额信息以及抵押的金额信息,同时将该订单移除。当然如注释中提到的,如果处于强制更新或者抵押的EOS代币金额不为0的情况下,则对用户的投票权重做出相应的更新,最后返回Rex的订单信息。那么订单信息这种表中都包含有哪些信息呢,我们来看:

代码语言:javascript复制
struct [[eosio::table, eosio::contract("eosio.system")]] rex_order {
        uint8_t             version = 0;//版本号
        name                owner;//订单的用户
        asset               rex_requested;//rex订单的金额
        asset               proceeds;//rex处理的金额
        asset               stake_change;//订单完成先后用户抵押资源的变化
        eosio::time_point   order_time;//订单时间
        bool                is_open = true;//订单状态,默认为open,交易完成之后变成close

        void close()                { is_open = false; }
        uint64_t primary_key()const { return owner.value; }
        uint64_t by_time()const     { return is_open ? order_time.elapsed.count() : std::numeric_limits<uint64_t>::max(); }
    };

上面便描述了一个普通的用户如何在Rex中的角色以及抵押EOS代币及赎回EOS代币中Rex订单如何变化以及订单中记录了我们的哪些操作信息。这些都是从出租者的角度来看的,那么如果要租赁资源呢,我们继续来看:

代码语言:javascript复制
 /**
        * @brief 使用EOS代币购买Rex资源
        *
        * @param from - 购买者账户名
        * @param amount - 购买的金额
        */
    void system_contract::buyrex(const name& from, const asset& amount)
    {
        require_auth(from);

        check(amount.symbol == core_symbol(), "asset must be core token");
        check(0 < amount.amount, "must use positive amount");
        check_voting_requirement(from);
        transfer_from_fund(from, amount);
        const asset rex_received    = add_to_rex_pool(amount);
        const asset delta_rex_stake = add_to_rex_balance(from, amount, rex_received);
        runrex(2);
        update_rex_account(from, asset(0, core_symbol()), delta_rex_stake);
    }

我们可以使用我们自由流动的EOS代币来购买Rex资源,那么我们能否使用已经抵押的EOS代币来获取Rex资源呢,答案是肯定的:

代码语言:javascript复制
/**
        * @brief 使用已抵押的代币来获取Rex资源
        *
        * @param owner - 抵押用户的账户名
        * @param receiver - 被抵押用户的用户名
        * @param from_net - 用来购买Rex资源所使用的已抵押的net资源的代币
        * @param from_cpu - 用来购买Rex资源所使用的已抵押的cpu资源的代币
        */
    void system_contract::unstaketorex(const name& owner, const name& receiver, const asset& from_net, const asset& from_cpu)
    {
        require_auth(owner);

        check(from_net.symbol == core_symbol() && from_cpu.symbol == core_symbol(), "asset must be core token");
        check( (0 <= from_net.amount) && (0 <= from_cpu.amount) && (0 < from_net.amount || 0 < from_cpu.amount),
            "must unstake a positive amount to buy rex");
        check_voting_requirement(owner);

        {
            del_bandwidth_table dbw_table(_self, owner.value);
            auto del_itr = dbw_table.require_find(receiver.value, "delegated bandwidth record does not exist");
            check(from_net.amount <= del_itr->net_weight.amount, "amount exceeds tokens staked for net");
            check(from_cpu.amount <= del_itr->cpu_weight.amount, "amount exceeds tokens staked for cpu");
            dbw_table.modify( del_itr,
                same_payer,
                [&](delegated_bandwidth& dbw) {
                    dbw.net_weight.amount -= from_net.amount;
                    dbw.cpu_weight.amount -= from_cpu.amount;
                });
            if (del_itr->is_empty()) {
                dbw_table.erase(del_itr);
            }
        }

        update_resource_limits(name(0), receiver, -from_net.amount, -from_cpu.amount);

        const asset payment = from_net   from_cpu;
        INLINE_ACTION_SENDER(eosio::token, transfer)( token_account,
            { stake_account, active_permission },
            { stake_account, rex_account, payment, "buy REX with staked tokens" });
        const asset rex_received = add_to_rex_pool(payment);
        add_to_rex_balance(owner, payment, rex_received);
        runrex(2);
        update_rex_account(owner, asset(0, core_symbol()), asset(0, core_symbol()), true);
    }

可以看出为了获取Rex资源我们不仅可以使用持有的EOS代币还可以使用已经抵押的cpu或者net资源来换取,不管是为自己抵押的还是为别人抵押的,我们来看看两者的异同,不管是使用哪种方式,都要求购买Rex资源的人给前21个节点投票或者进行代理投票,其具体实现如下:

代码语言:javascript复制
    void check_voting_requirement( const name& owner,
            const char* error_msg = "must vote for at least 21 producers or for a proxy before buying REX")const;

    void system_contract::check_voting_requirement(const name& owner, const char* error_msg)const
    {
        auto vitr = _voters.find(owner.value);
        check(vitr != _voters.end() && (vitr->proxy || 21 <= vitr->producers.size()), error_msg);
    }

对购买者的投票权限校验完成之后,便可以对用户的Rex账户进行相关操作了,由于我们上面提到的是购买Rex,我们会使用到transfer_from_fund,这里又进行了什么操作呢?

代码语言:javascript复制
/**
            * @brief 从用户的Rex中取出对应金额的资源
            *
            * @pre - 首先要求用户的Rex基金账户中有足够的资源
            *
            * @param owner - 购买资源账户名
            * @param amount - 购买资源的金额(EOS代币)
            */
    void system_contract::transfer_from_fund(const name& owner, const asset& amount)
    {
        check( 0 < amount.amount && amount.symbol == core_symbol(),
            "must transfer positive amount from REX fund");
        auto itr = _rexfunds.require_find(owner.value, "must deposit to REX fund first");
        check(amount <= itr->balance, "insufficient funds");
        _rexfunds.modify( itr,
            same_payer,
            [&](auto& fund) {
                fund.balance.amount -= amount.amount;
            });
    }

也就是用户在购买Rex资源之前必须在Rex基金中抵押一部分代币,且本次操作获取到的Rex资源对应的EOS代币的量不能大于在Rex基金中抵押的EOS代币的金额,这些判断完成之后对Rex基金账户进行相应的操作,即减掉相应的金额。我们获取到资源之后,还需要对整个Rex的资金池进行操作,这个函数的实现过长,建议朋友们自行去了解一下,不再细说。购买完成之后,还会对Rex中用户的相应信息作出更改,来看:

代码语言:javascript复制
  /**
        * @brief 更新用户的Rex余额信息
        *
        * @param owner 资源的用户名
        * @param payment - 用户购买Rex的EOS代币的金额
        * @param rex_received - 用户购买到的Rex
        *
        * @return asset - 更新用户的投票信息
        */
    asset system_contract::add_to_rex_balance(const name& owner, const asset& payment, const asset& rex_received)
    {
        asset init_rex_stake(0, core_symbol());
        asset current_rex_stake(0, core_symbol());
        auto bitr = _rexbalance.find(owner.value);
        if (bitr == _rexbalance.end()) {
            bitr = _rexbalance.emplace( owner,
                [&](auto& rb) {
                    rb.owner       = owner;
                    rb.vote_stake  = payment;
                    rb.rex_balance = rex_received;
                });
            current_rex_stake.amount = payment.amount;
        }
        else {
            init_rex_stake.amount = bitr->vote_stake.amount;
            _rexbalance.modify( bitr,
                same_payer,
                [&](auto& rb) {
                    rb.rex_balance.amount  = rex_received.amount;
                    rb.vote_stake.amount   = (uint128_t(rb.rex_balance.amount) * _rexpool.begin()->total_lendable.amount) 
                                             / _rexpool.begin()->total_rex.amount;
                });
            current_rex_stake.amount = bitr->vote_stake.amount;
        }

        process_rex_maturities(bitr);
        const time_point_sec maturity = get_rex_maturity();
        _rexbalance.modify( bitr,
            same_payer,
            [&](auto& rb) {
                if (!rb.rex_maturities.empty() && rb.rex_maturities.back().first == maturity) {
                    rb.rex_maturities.back().second  = rex_received.amount;
                }
                else {
                    rb.rex_maturities.emplace_back(maturity, rex_received.amount);
                }
            });

        return current_rex_stake - init_rex_stake;
    }

此中涉及到多张表的操作,在本版本中应该充斥着大量的测试代码,我们不再细说,我们前面还谈到可以用户已经抵押的net或者cpu资源来换取Rex,那么这种换取Rex和正常的购买Rex有什么区别呢,用户如何来出售Rex资源呢?由于内容较多,我们在下一篇文章中继续聊。

当前形势是资源紧缺,而各种租赁平台雨后春笋般的出现,我们也可以先使用这些平台来获取cpu资源或者将闲置的EOS代币出租出去,稳定的获取相应的收益。在这里推荐下由EOS42节点提供的Chintai租赁平台,从安全性、方便性、收益率来看,都是不错的选择,当然也期待Chintai的UI、续租、糖果等功能方面能做的越来越好。

本文结合BM在Medium中的观点和版本中存在的潜在的Rex的代码对抵押、赎回、购买Rex资源做了简单的介绍,后续等版本的完善,我们继续聊。

0 人点赞