EOS REX security series starts from source code and plays REX (1)
By SlowMist team
Foreword
What is REX
More detailed information can be found in BM's own article: https://medium.com/@bytemaster/proposal-for-eos-resource-renting-rent-distribution-9afe8fb3883a
- Bitcoin fled the period of the flood season, nearly 90% of the arrogant mining machine, what is the meaning?
- 12 hours after the stolen 7000 bitcoin was stolen
- Secret Dark Bitcoin Anti-Tracking Tool
REX Raiders
1, depodit: used for recharge, EOS into SEOS, also known as reserve gold.
2, withdraw: use and withdraw, convert SEOS back to EOS.
3, buyrex: used to deduct the corresponding share from the user's reserve, and used for the purchase of rex.
4, sellrex: used to sell the REX that has ended the lock, and put the principal and the proceeds together into the user's reserve account.
5. unstaketorex: The resources used in the mortgage are used for the purchase of rex.
Below, let's take a look at the implementation of these functions and understand the flow of funds.
Deposit function
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" } );///充值进rex账户transfer_to_fund( owner, amount );///初始化用户余额,不存在用户则新增用户,存在则累加金额update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ) ); }
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" } );///充值进rex账户transfer_to_fund( owner, amount );///初始化用户余额,不存在用户则新增用户,存在则累加金额update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ) ); }
2. In the fifth and sixth rows, the information on the purchase amount and the token is verified. You cannot buy it with a fake EOS, nor can you buy a negative number to ensure the security of REX.
3. Put the user's EOS into the eosio.rex account and your money is transferred from your pocket to the eosio.rex system account.
4, call the transfer_to_fund interface, the user's recharge amount with a small notebook, this is equivalent to our reserve money package, in the data embodiment is a table, follow-up will be based on this table rex purchase.
5, call the update_rex_account interface, this interface has different functions when inputting different parameters, here is used to process the user's sell order, and the proceeds from the user selling rex are sorted into the reserve account.
About function
void system_contract::withdraw( const name& owner, const asset& amount ) { require_auth( owner );
Check( amount.symbol == core_symbol(), "must withdraw core token" ); ///EOS symbol check 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" } );
}
Buyrex function
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 ); / / check whether the user voted transfer_from_fund (from, amount); / / deducted from the user's fund, you need to recharge through the despoit function before rex purchase const asset rex_received = add_to_rex_pool ( amount ); / Calculate the number of rex that can be obtained const asset delta_rex_stake = add_to_rex_balance( from, amount, rex_received ); ///Change the number of rex in the user account 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 ) );
}
Add_to_rex_pool function
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
Uint64_t primary_key()const { return 0; }
}; The above is the definition of the rex_pool table, which defines 8 fields. Except the version parameter, we explain the meaning of each parameter one by one.
1. total_lent: used to record the total number of cpu resources and net resources that have been loaned. This resource is based on EOS.
2. total_unlent: records EOS resources that are not used for lending in rex_pool. This includes the amount of rent available to the user who rented the resource due to the amount of money available to the user for the purchase of rex. One of these is the amount that will be locked out due to rental of resources (automatically unlocked after 30 days), a connector for bancor operations that calculates a certain amount of EOS rentable resources.
3, total_rent: used to record the rent paid by the user when renting the resource, is a connector, which reflects the number of users renting resources. Used for bancor operations to calculate a certain amount of EOS leaseable resources.
4, total_lenable: can be said to be all the funds of the entire rex_pool, the calculation formula is total_unlent + total_lent. The sources of funding here also include the auction fee for name bid and the ram fee. This parameter is also closely related to the user's income.
5, total_rex: The total amount of rex in rex_pool, which is derived from the user purchasing rex.
6, namebid_proceeds: record the cost of the auction account.
7. loan_num: records the total number of leased resources.
Understand the definition of the above fields, we now officially look at the add_to_rex_pool function, the following is the specific implementation of the function.
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), ie, 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" ); }); }
Return rex_received;
It looks complicated, right? After decomposing the formula, we first perform the following conversion, the formula becomes (S1 / S0 * R0) – R0, and then substituting into S1, we get ((S0 + payment) / S0 * R0) – R0, and finally we decompose and then go Parentheses, get R0 + (payment / S0) * R0 – R0. The last formula becomes (payment / S0) * R0 . Change again to become payment * (R0 / S0), which is the ratio of the user's funds used to purchase rex multiplied by the total EOS total assets in the current rex_pool and the total amount of rex in the rex_pool.
This ratio is fixed at 1:10000 without interference from third-party funds such as account bidding fees, ram fees, etc. However, when there is a third-party fund, the S0 as the denominator will continue to grow larger, so the ratio will continue to be smaller, and the same amount of rex will be less and less. Through the above analysis, we know that with the participation of third-party funds, the sooner rex buys, the more you can buy. The price of rex is independent of the number of people purchased, and is related to the amount of rented resources, the revenue generated by the system bidding resources, and the ram fee.
Sellrex function
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 ); ///First harvest mature rex
Check( rex.amount <= bitr->matured_rex, "insufficient available rex" );/// can only sell mature rex
Auto current_order = fill_rex_order( bitr, rex );///Get the dividend paid by renting EOS. pending_sell_order = update_rex_account( from, current_order.proceeds, current_order.stake_change );
/ / Order status is not successful 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. Check that the user has purchased rex. If you can't buy it, you can sell it.
2, through the process_rex_maturities function to calculate the end of the locked rex, the user from the purchase of rex to sell rex requires a four-day release period.
3. Check if the number of rexes that need to be sold is less than the number of REXs that are locked.
After checking through the above steps, I actually entered the settlement function. Rex's revenue settlement is achieved through the fill_rex_order interface. Look at the specific implementation.
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;
/ / Calculate the number of EOS in the unpicked rex pool, used to observe whether it is enough to pay the user generated rex profit const int64_t available_unlent = rexitr->total_unlent.amount – unlent_lower_bound; // available_unlent <= 0 is possible
//The money in rexpool is enough to pay the rex profit if (progress.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;///Update the number of rex in the rex pool rt.total_lendable.amount = S1; ///Update the number of lenableEOS rt.total_unlent.amount = rt.total_lendable.amount – rt.total_lent.amount ; / / reduce unlent data });
// Operate the user's rexbalance account _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; ///Reduce the number of mature rex});
Stake_change.amount = bitr->vote_stake.amount – init_vote_stake_amount;
Success = true;
///There is not enough money to pay for it} else {
Proceeding.amount = 0;
}
Return { success, proceeds, stake_change };
}
The entire process of participation is as follows:
REX security analysis
This article gives a rough introduction to the four interfaces, namely deposit , withdraw , buyrex , and sellrex .
From the point of view of function implementation:
1. Each function has a check on the information of the asset parameter, including the quantity, and the symbol information of the token is consistent with the system token information. Prevent possible false recharge problems and overflow problems.
2. The user's key operations have permission checks to prevent unauthorized operations.
At the same time, the four interfaces introduced in the article do not have common attack methods such as rollback attacks, crowding attacks, and false notification attacks on EOS.
It's worth noting, however, that in these functions, there was a serious vulnerability in the sellrex function (now fixed) that was used to steal assets from REX.
The details are as follows: https://eosauthority.com/blog/REX_progress_with_testing_and_implementation_details
The cause of the vulnerability is that the REX system may not be able to pay the user's revenue when the sellrex operation is performed. In this case, the user's sell order will hang. If the order is not verified, the malicious user can be insufficient in the system. The sellrex operation is always performed, and the amount of the pending order is increased until the system has sufficient resources to pay the user's revenue.
Conclusion
statement
We will continue to update Blocking; if you have any questions or suggestions, please contact us!
Was this article helpful?
93 out of 132 found this helpful
Related articles
- Analysis of the market: Up-and-coming 6000 seccoin money stolen 7000btc, how to develop the market outlook?
- Leading Bitcoin in one step! Bitcoin Cash (BCH) will be hard forked next week to welcome Schnorr signature
- May 8th analysis: amazing and consistent hacking market
- Monthly market report | market value and transaction volume have increased sharply, but only 30% of profitable people
- The currency was stolen again, and the loss was nearly 300 million. How does the exchange protect the security of user assets?
- Analysis of the loss of 7000 bitcoin and the theft of the coin hot wallet
- What is the SAFU fund of the currency security? Is $41 million a small case for it?