EOS REX 安全系列之从源码开始玩转 REX(一)
by SlowMist team
更详细的资料可以参看 BM 自己的文章:
https://medium.com/@bytemaster/proposal-for-eos-resource-renting-rent-distribution-9afe8fb3883a 1、depodit:用于充值,将 EOS 变成 SEOS,也叫预备金。 2、withdraw:用与提现,将 SEOS 换回 EOS。 3、buyrex:用于从用户的预备金中扣除相应的份额,并用于 rex 的购买。 4、sellrex:用于卖出已经结束锁定的 REX,并将本金连带收益一起放进用户的预备金账户中。 5、unstaketorex:将用于抵押中的资源用于 rex 的购买 。 下面,我们一起来看下这几个函数的实现,了解资金的流向。 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" } );///充值进rex账户
transfer_to_fund( owner, amount );///初始化用户余额,不存在用户则新增用户,存在则累加金额
update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ) );
} 2、在第五行和第六行对购买金额和代币的信息进行校验,不能拿假的 EOS 来买,也不能买个负数的,保证 REX 的安全性。 3、把用户的 EOS 打进 eosio.rex 账户,你的钱就从你的口袋,转到了 eosio.rex 系统账户上了。 4、调用 transfer_to_fund 接口,把用户的充值金额用小本本记起来,这相当我们的储备金钱包,在数据体现上是一个表,后续将根据这个表进行 rex 的购买。 5、调用 update_rex_account 接口,这个接口在输入不同的参数的时候有不同的功能,这里是用于处理用户的卖单,把用户卖 rex 得到的收益一并整理进储备金账户中。 check( amount.symbol == core_symbol(), "must withdraw core token" ); ///EOS符号校验
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" } );
} 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 ); //从用户的基金中扣除,需要先通过despoit函数进行充值之后才能进行rex的购买
const asset rex_received = add_to_rex_pool( amount ); //计算能获得的rex的数量
const asset delta_rex_stake = add_to_rex_balance( from, amount, rex_received ); ///更改用户账户中的rex的数量
runrex(2);
update_rex_account( from, asset( 0, core_symbol() ), delta_rex_stake );
// dummy action added so that amount of REX tokens purchased shows up in action trace
dispatch_inline( null_account, "buyresult"_n, { }, std::make_tuple( rex_received ) );
} uint64_t primary_key()const { return 0; }
}; 1、total_lent:用于记录总共被借出了多少的 cpu 资源和 net 资源,这个资源是以 EOS 为单位的。 2、total_unlent:记录 rex_pool 中未用于出借的 EOS 资源。包括用户因为购买 rex 所产生的可用于出租的金额,租用资源的用户的租金。 这其中有一部会因为出租资源而锁定的金额(30 天后自动解锁),是一个 connector,用于 bancor 操作,计算一定数量的 EOS 可租借的资源。 3、total_rent:用于记录用户在租用资源的时候支付的租金,是一个 connector,其反应了租借资源的用户的多少。用于 bancor 操作,计算一定数量的 EOS 可租借的资源。 4、total_lenable:可以说是整个 rex_pool 的所有资金,计算公式为 total_unlent + total_lent。这里的资金来源还包括 name bid 的竞拍费用以及 ram fee。这个参数同时和用户的收益息息相关。 5、total_rex:rex_pool 中 rex 的总量,其来源于用户购买 rex。 6、namebid_proceeds:记录竞拍账户产生的费用。 7、loan_num:记录出租资源的总次数。 明白了以上字段的定义,我们现在正式看看 add_to_rex_pool 函数,以下是函数的具体实现。
return rex_received; 看起来很复杂对不对?我们对公式进行分解下,首先进行以下转换,公式变为(S1 / S0 * R0) - R0,再代入 S1,得到((S0 + payment) / S0 * R0) - R0,最后我们进行分解再去括号,得到 R0 + (payment / S0) * R0 - R0。最后这个公式就变成了(payment / S0) * R0。再变一下,变成 payment * (R0 / S0),即用户用于购买 rex 的资金乘以当前 rex_pool 中的 EOS 总资产与 rex_pool 中的 rex 的总量之间的比例。 这个比例在没有第三方资金如账户竞拍费用,ram fee 等的干扰下是固定不变的,为 1:10000。但是当有第三方资金入场的时候,作为分母的 S0 就会不断变大,那么这个比例就不断变小,同样的金额能买到的 rex 就会越来越少。通过上面的分析,我们知道,在有第三方资金的参与下,rex 买得越早,能买到的数量就越多。rex 的价格与购买的人数无关,而与租借资源的数量,系统竞拍资源产生的收益,以及 ram fee 有关。 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 ) );
}
} 1、检查用户购买了 rex 没有,总不能没买就能卖对吧。 2、通过 process_rex_maturities 函数计算结束锁定的 rex,用户从购买的 rex 到卖 rex 需要 4 天的释放期。 3、检测需要卖出的 rex 的数量是否小于结束锁定的 REX 的数量。 通过以上几步检查之后,就真正进入了结算函数。rex 的收益结算是通过 fill_rex_order 接口实现的。看下具体实现。 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 };
} 整个参与的流程大致如下:
本文粗略介绍了四个接口,分别是 deposit,withdraw,buyrex,sellrex。 从函数实现上来看: 1、每个函数都有对 asset 参数的信息进行校验,包括数量,代币的符号信息是否与系统代币信息一致。防止可能的假充值问题和溢出问题。 2、用户的关键操作都有权限校验,防止越权操作。 同时,文章内介绍的四个接口不存在 EOS 上常见的攻击手法如回滚攻击,排挤攻击,假通知攻击。 但值得注意的是,在这几个函数中,sellrex 函数曾存在一个严重漏洞(现已修复),导致用于可以从 REX 中盗取资产。 详细信息如下:
https://eosauthority.com/blog/REX_progress_with_testing_and_implementation_details 漏洞的成因在于进行 sellrex 操作的时候 REX 系统可能会不够钱支付用户的收益,在这种情况下,用户的卖单就会挂起,如果没有校验订单,恶意用户就能在系统资金不足的情况下一直进行 sellrex 操作,一直增加挂起订单的金额,直到有系统有足够的资源支付用户的收益。前言
什么是 REX
REX 攻略
deposit 函数
void system_contract::deposit( const name& owner, const asset& amount )
{
require_auth( owner );
withdraw 函数
void system_contract::withdraw( const name& owner, const asset& amount )
{
require_auth( owner );
buyrex 函数
void system_contract::buyrex( const name& from, const asset& amount )
{
require_auth( from );
add_to_rex_pool 函数
以上是 rex_pool 表的定义,其中定义了 8 个字段,除去 version 参数,我们分别一个一个解释每个参数的意思struct [[eosio::table,eosio::contract("eosio.system")]] rex_pool {
uint8_t version = 0;
asset total_lent; /// total amount of CORE_SYMBOL in open rex_loans
asset total_unlent; /// total amount of CORE_SYMBOL available to be lent (connector)
asset total_rent; /// fees received in exchange for lent (connector)
asset total_lendable; /// total amount of CORE_SYMBOL that have been lent (total_unlent + total_lent)
asset total_rex; /// total number of REX shares allocated to contributors to total_lendable
asset namebid_proceeds; /// the amount of CORE_SYMBOL to be transferred from namebids to REX pool
uint64_t loan_num = 0; /// increments with each new loan
asset system_contract::add_to_rex_pool( const asset& payment )
{
/**
* If CORE_SYMBOL is (EOS,4), maximum supply is 10^10 tokens (10 billion tokens), i.e., maximum amount
* of indivisible units is 10^14. rex_ratio = 10^4 sets the upper bound on (REX,4) indivisible units to
* 10^18 and that is within the maximum allowable amount field of asset type which is set to 2^62
* (approximately 4.6 * 10^18). For a different CORE_SYMBOL, and in order for maximum (REX,4) amount not
* to exceed that limit, maximum amount of indivisible units cannot be set to a value larger than 4 * 10^14.
* If precision of CORE_SYMBOL is 4, that corresponds to a maximum supply of 40 billion tokens.
*/
const int64_t rex_ratio = 10000;
const int64_t init_total_rent = 20'000'0000; /// base amount prevents renting profitably until at least a minimum number of core_symbol() is made available
asset rex_received( 0, rex_symbol );
auto itr = _rexpool.begin();
if ( !rex_system_initialized() ) {
/// initialize REX pool
_rexpool.emplace( _self, [&]( auto& rp ) {
rex_received.amount = payment.amount * rex_ratio; ///计算能获得的rex的数量
rp.total_lendable = payment;///由于用户 buy rex,使得 rex pool 中有可出租的 EOS,所以 rex_lendable 为首位用户的购买资金
rp.total_lent = asset( 0, core_symbol() );///初始化rex pool,暂时还没有人借资源
rp.total_unlent = rp.total_lendable - rp.total_lent; ///计算还能借的
rp.total_rent = asset( init_total_rent, core_symbol() );
rp.total_rex = rex_received;
rp.namebid_proceeds = asset( 0, core_symbol() );
});
} else if ( !rex_available() ) { /// should be a rare corner case, REX pool is initialized but empty
_rexpool.modify( itr, same_payer, [&]( auto& rp ) {
rex_received.amount = payment.amount * rex_ratio;
rp.total_lendable.amount = payment.amount;
rp.total_lent.amount = 0;
rp.total_unlent.amount = rp.total_lendable.amount - rp.total_lent.amount;
rp.total_rent.amount = init_total_rent;
rp.total_rex.amount = rex_received.amount;
});
} else {
/// total_lendable > 0 if total_rex > 0 except in a rare case and due to rounding errors
check( itr->total_lendable.amount > 0, "lendable REX pool is empty" );
const int64_t S0 = itr->total_lendable.amount;
const int64_t S1 = S0 + payment.amount;
const int64_t R0 = itr->total_rex.amount;
const int64_t R1 = (uint128_t(S1) * R0) / S0;
rex_received.amount = R1 - R0; ///计算能获得的rex
_rexpool.modify( itr, same_payer, [&]( auto& rp ) {
rp.total_lendable.amount = S1;
rp.total_rex.amount = R1;
rp.total_unlent.amount = rp.total_lendable.amount - rp.total_lent.amount;
check( rp.total_unlent.amount >= 0, "programmer error, this should never go negative" );
});
}
sellrex 函数
void system_contract::sellrex( const name& from, const asset& rex )
{
require_auth( from );
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
REX 安全性分析
结语
声明
免责声明:
1.本文内容综合整理自互联网,观点仅代表作者本人,不代表本站立场。
2.资讯内容不构成投资建议,投资者应独立决策并自行承担风险。
- 贝佐斯最后一封股东信:宇宙希望你成为普通人,千万别让它成为现实2021-04-19 17:02
- Props,让互联网与区块链无缝对接的「中间件」2021-04-19 17:02
- Coinbase高管到底卖了多少股票?2021-04-19 16:03
- 通往未来之路:下一代互联网与Metaverse2021-04-19 16:03
- 央行前行长周小川谈比特币:要提醒,要小心2021-04-19 15:03
- 链上新知 |电子图片卖出7000万美金,让马斯克都来站台的NFT究竟是什么?2021-04-19 15:02
- Crypto VC,LP怎么投?2021-04-19 13:03
- 周末比特币融资利率跌至-0.03%低点,为7个月以来最低水平2021-04-19 11:02