EOS REX security series starts from source code and plays REX (1)

By SlowMist team

 

Foreword

With the REX proposal finally being voted by BP, the REX, which has been speculating for half a year, has finally come online. This investment project, which claims to be profitable, has attracted the attention of many people and has also occupied the headlines of major blockchain media. It is as hot as the platform currency, and a lot of money is poured into the line. But what exactly is REX? What is the use of REX? This series is based on the rex1.6.0-rc2 source code for analysis, giving relevant details and answers.

What is REX

REX, the full name of Resource Exchange, is a resource exchange, in order to provide a better resource leasing platform, alleviate the high resource usage cost of EOS, and exchange more resources with less EOS . At the same time, it can also increase user voting and promote the healthy operation of the EOS system. There are many resources on the market to rent DApps, and the purpose is to alleviate the problem of CPU shortage. Like these platforms, REX acts as a leasing platform. The difference is that the resource lender is no longer a DApp, but each EOS holder can become a resource lender and enjoy the benefits. The key point to be stated here is that REX is not a token, but a resource leasing platform ! The REX purchased by the user is only a pass that flows to the REX rental platform to prove that the user has rented the resource. The pass itself cannot be circulated and cannot be traded. Similar to national debt, REX is the bond in hand. To distinguish between these two concepts, the REX resource leasing platform is collectively referred to as REX. The pass that the user buys is called rex.

More detailed information can be found in BM's own article: https://medium.com/@bytemaster/proposal-for-eos-resource-renting-rent-distribution-9afe8fb3883a

REX Raiders

For the average user, buying and selling rex only requires access to the following interfaces:

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

The deposit function is the first interface for users to participate in REX. As the name implies, the user recharges for later purchase of rex. Just like going to the game hall to recharge the game currency, first change the renminbi into the game hall and rush into the card, and then use this card for subsequent games. All subsequent costs are based on this card. The same is true for REX, where all subsequent trading operations are based on this reserve account. The concrete implementation of the deposit function is as follows:

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() ) ); }

We don't need to understand the specific implementation of each line, but the general reason is that we need to understand. The deposit function does the following: 1. First, the user's permission is verified in the third line. It is not always possible to let others buy rex for themselves and bypass their will.

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

The withdraw function is the reverse interface of the deposit function, which is used to transfer the balance in the reserve account to the user's EOS account, just like you have enough in the game hall, there are points in the card, or you can win the game by playing the game. In the card, you can exchange the points in the card for the RMB. The next time you come back, the reason of the withdraw function is the same. The specific implementation of the withdraw function is as follows:

 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" } );
}

Similar to the deposit function, the withdraw function also checks the information of the EOS token. Unlike the deposit function, the order in which the withdraw function calls the update_rex_account interface and the transfer_from_fund interface is different from the deposit function. But the purpose is to process the user's rex sell order and attribute the proceeds to the reserve account. Used to withdraw or purchase rex separately. The detailed details of the analysis here will be included in the follow-up article.

Buyrex function

After tossing for so long, how to recharge the reading, how to withdraw and read it, the following is the problem we are most concerned about, that is how to buy the problem. The interface to buy rex calls is the buyrex function. The specific implementation of the function is as follows:

 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 ) );
}

Like the previous two functions, the buyrex function also verifies the information about the token, and then uses the transfer_from_fund function to deduct the corresponding amount from the user's reserve. In addition, we should also pay attention to the other three functions, check_voting_requirement, add_to_rex_pool and add_to_rex_balance. These three functions are used to check whether the user votes, calculate the number of rex that can be purchased, add the corresponding amount of rex to the rexpool, record the rex information purchased by the user, and calculate the unlock time of the rex purchased by the user. So how do we calculate the number of rex we can get? From the source code we can see that the number of rex is calculated by calling the add_to_rex_pool function. Therefore, the following will focus on the add_to_rex_pool function.

Add_to_rex_pool function

The add_to_rex_pool function is used to put the rex purchased by the user into the rex_pool and calculate the number of rex the user can purchase based on the information in the rex_pool. First let's take a look at the definition of the rex_pool table.

 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;

First, let's see how the rex we can buy is calculated. When rex_pool ushers in the first user to purchase rex, the rex acquisition ratio is 1:10000, that is, one EOS is replaced by 10000 rex, and the later rex is purchased according to the formula ((uint128_t(S1) * R0) ) / S0) – R0 calculates the rex that can be obtained.

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

Well, now that the process is here, the rest is the problem of calculating revenue. The implementation details of the calculations used to process the revenue generated by the user renting EOS resources are all in the sellrex function. The following is a concrete implementation of the 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 ) );
}
}

This sellrex function has a lot of knowledge. It may not be that this short analysis can be written in its entirety, but it can analyze the problem we are most concerned with, that is, how the gains obtained are calculated. First of all, let's take a look at what else we did before actually calculating the revenue, regardless of other details. Mainly divided into the following steps:

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 };
}

Similarly, similar to add_to_rex_pool, we can also throw away other details and hit the core income calculation formula, which is the calculation formula of line 6. (uint128_t(rex.amount) * S0) / R0, although this function looks equally complicated, we can simplify it in the same way. First we convert some of the formula into rex.amount / R0 * S0, add a parenthesis, and become rex.amount * (R0 / S0), that is, the rex you can earn is the rex you want to sell multiplied by rex_pool The ratio between the total amount of rex and the total EOS total assets in rex_pool, which is a constant 10,000:1 without third-party funds such as name bid and ram fee.

What do we know?

Speaking a lot in one breath, you may still have a bit of sorrow when you see it here, maybe just remember the transformation of the two formulas, not tight. Let me summarize the harvest of this article. Through the above analysis, we know that buying rex and selling rex are calculated based on the ratio between the total amount of rex and the total funds of EOS in rex_pool, that is, without the participation of third-party funds, the user's EOS is always pressed. The ratio of 1:10000 becomes rex, and then becomes EOS by the ratio of 10000:1. This shows that in the absence of third-party resources, rex and EOS are always exchanged according to a certain proportion, which is why REX claims to be profitable. At the same time, when there is third-party funds entering, the ratio of R0 / S0 will become smaller, which means that the proportion of S0 / R0 will become larger, although the rex purchased by the same funds becomes less, but sold out. The proportion becomes larger and the gains obtained become more.

The entire process of participation is as follows:

 


REX security analysis

As a system contract of EOS itself, REX must be comprehensive in its security protection. Once a problem occurs, it will have a catastrophic impact. The REX contract has been audited by the EOS Authority team for a compulsory security audit, but as a security officer, the author also has an in-depth reflection on the entire architecture of REX. The article will analyze the interfaces mentioned in each article. Suggestions for security or security enhancements.

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

REX is a huge system. There is no single sentence to analyze all the details. The article does not analyze too many technical details. It only analyzes the approximate role of each function and introduces the core of REX revenue. Friends who want to know the details can continue to pay attention to our series of articles – the next article will continue to explain the more fun details between these functions! The article may have something wrong, welcome everyone to give pointers.

statement

This article is for technical reference only and does not constitute any investment advice. Investors should make rational investments based on a full understanding of the risks involved.