受限于个人工作及时间原因,有月余未曾动笔,在群内和伙伴们的交流也变少了,还好对于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资源做了简单的介绍,后续等版本的完善,我们继续聊。