diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 000000000..bff172e40 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,23 @@ + + + +## Change Description + + + +## Deployment Changes +- [ ] Deployment Changes + + + + +## API Changes +- [ ] API Changes + + + + +## Documentation Additions +- [ ] Documentation Additions + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 32c57283f..5426805cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ project(eosio_contracts) set(VERSION_MAJOR 1) set(VERSION_MINOR 6) set(VERSION_PATCH 0) -set(VERSION_SUFFIX rc1) +set(VERSION_SUFFIX rc2) if (VERSION_SUFFIX) set(VERSION_FULL "${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH}-${VERSION_SUFFIX}") diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index ff7758a2c..5d452767c 100755 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -13,6 +13,8 @@ #include #include +#include +#include #ifdef CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX #undef CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX @@ -37,6 +39,25 @@ namespace eosiosystem { using eosio::datastream; using eosio::check; + template + static inline auto has_field( F flags, E field ) + -> std::enable_if_t< std::is_integral_v && std::is_unsigned_v && + std::is_enum_v && std::is_same_v< F, std::underlying_type_t >, bool> + { + return ( (flags & static_cast(field)) != 0 ); + } + + template + static inline auto set_field( F flags, E field, bool value = true ) + -> std::enable_if_t< std::is_integral_v && std::is_unsigned_v && + std::is_enum_v && std::is_same_v< F, std::underlying_type_t >, F > + { + if( value ) + return ( flags | static_cast(field) ); + else + return ( flags & ~static_cast(field) ); + } + struct [[eosio::table, eosio::contract("eosio.system")]] name_bid { name newname; name high_bidder; @@ -162,14 +183,20 @@ namespace eosiosystem { bool is_proxy = 0; /// whether the voter is a proxy for others - uint32_t reserved1 = 0; + uint32_t flags1 = 0; uint32_t reserved2 = 0; eosio::asset reserved3; uint64_t primary_key()const { return owner.value; } + enum class flags1_fields : uint32_t { + ram_managed = 1, + net_managed = 2, + cpu_managed = 4 + }; + // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(producers)(staked)(last_vote_weight)(proxied_vote_weight)(is_proxy)(reserved1)(reserved2)(reserved3) ) + EOSLIB_SERIALIZE( voter_info, (owner)(proxy)(producers)(staked)(last_vote_weight)(proxied_vote_weight)(is_proxy)(flags1)(reserved2)(reserved3) ) }; typedef eosio::multi_index< "voters"_n, voter_info > voters_table; @@ -301,6 +328,7 @@ namespace eosiosystem { static constexpr eosio::name names_account{"eosio.names"_n}; static constexpr eosio::name saving_account{"eosio.saving"_n}; static constexpr eosio::name rex_account{"eosio.rex"_n}; + static constexpr eosio::name null_account{"eosio.null"_n}; static constexpr symbol ramcore_symbol = symbol(symbol_code("RAMCORE"), 4); static constexpr symbol ram_symbol = symbol(symbol_code("RAM"), 0); static constexpr symbol rex_symbol = symbol(symbol_code("REX"), 4); @@ -322,6 +350,16 @@ namespace eosiosystem { [[eosio::action]] void setalimits( name account, int64_t ram_bytes, int64_t net_weight, int64_t cpu_weight ); + + [[eosio::action]] + void setacctram( name account, std::optional ram_bytes ); + + [[eosio::action]] + void setacctnet( name account, std::optional net_weight ); + + [[eosio::action]] + void setacctcpu( name account, std::optional cpu_weight ); + // functions defined in delegate_bandwidth.cpp /** @@ -429,6 +467,22 @@ namespace eosiosystem { [[eosio::action]] void consolidate( const name& owner ); + /** + * Moves a specified amount of REX into savings bucket. REX savings bucket + * never matures. In order for it to be sold, it has to be moved explicitly + * out of that bucket. Then the moved amount will have the regular maturity + * period of 4 days starting from the end of the day. + */ + [[eosio::action]] + void mvtosavings( const name& owner, const asset& rex ); + + /** + * Moves a specified amount of REX out of savings bucket. The moved amount + * will have the regular REX maturity period of 4 days. + */ + [[eosio::action]] + void mvfrsavings( const name& owner, const asset& rex ); + /** * Deletes owner records from REX tables and frees used RAM. * Owner must not have an outstanding REX balance. @@ -566,6 +620,9 @@ namespace eosiosystem { void process_rex_maturities( const rex_balance_table::const_iterator& bitr ); void consolidate_rex_balance( const rex_balance_table::const_iterator& bitr, const asset& rex_in_sell_order ); + int64_t read_rex_savings( const rex_balance_table::const_iterator& bitr ); + void put_rex_savings( const rex_balance_table::const_iterator& bitr, int64_t rex ); + void update_rex_stake( const name& voter ); // defined in delegate_bandwidth.cpp void changebw( name from, name receiver, @@ -581,6 +638,38 @@ namespace eosiosystem { double shares_rate, bool reset_to_zero = false ); double update_total_votepay_share( time_point ct, double additional_shares_delta = 0.0, double shares_rate_delta = 0.0 ); + + template + class registration { + public: + template + struct for_each { + template + static constexpr void call( system_contract* this_contract, Args&&... args ) + { + std::invoke( P, this_contract, std::forward(args)... ); + for_each::call( this_contract, std::forward(args)... ); + } + }; + template + struct for_each

{ + template + static constexpr void call( system_contract* this_contract, Args&&... args ) + { + std::invoke( P, this_contract, std::forward(args)... ); + } + }; + + template + constexpr void operator() ( Args&&... args ) + { + for_each::call( this_contract, std::forward(args)... ); + } + + system_contract* this_contract; + }; + + registration<&system_contract::update_rex_stake> vote_stake_updater{ this }; }; } /// eosiosystem diff --git a/contracts/eosio.system/src/delegate_bandwidth.cpp b/contracts/eosio.system/src/delegate_bandwidth.cpp index 1b574da3a..7f4270cf8 100755 --- a/contracts/eosio.system/src/delegate_bandwidth.cpp +++ b/contracts/eosio.system/src/delegate_bandwidth.cpp @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -22,7 +21,6 @@ namespace eosiosystem { using eosio::asset; using eosio::indexed_by; using eosio::const_mem_fun; - using eosio::print; using eosio::permission_level; using eosio::time_point_sec; using std::map; @@ -163,7 +161,13 @@ namespace eosiosystem { res.ram_bytes += bytes_out; }); } - set_resource_limits( res_itr->owner.value, res_itr->ram_bytes + ram_gift_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); + + auto voter_itr = _voters.find( res_itr->owner.value ); + if( voter_itr == _voters.end() || !has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ) ) { + int64_t ram_bytes, net, cpu; + get_resource_limits( res_itr->owner.value, &ram_bytes, &net, &cpu ); + set_resource_limits( res_itr->owner.value, res_itr->ram_bytes + ram_gift_bytes, net, cpu ); + } } /** @@ -201,7 +205,13 @@ namespace eosiosystem { userres.modify( res_itr, account, [&]( auto& res ) { res.ram_bytes -= bytes; }); - set_resource_limits( res_itr->owner.value, res_itr->ram_bytes + ram_gift_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); + + auto voter_itr = _voters.find( res_itr->owner.value ); + if( voter_itr == _voters.end() || !has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ) ) { + int64_t ram_bytes, net, cpu; + get_resource_limits( res_itr->owner.value, &ram_bytes, &net, &cpu ); + set_resource_limits( res_itr->owner.value, res_itr->ram_bytes + ram_gift_bytes, net, cpu ); + } INLINE_ACTION_SENDER(eosio::token, transfer)( token_account, { {ram_account, active_permission}, {account, active_permission} }, @@ -233,8 +243,8 @@ namespace eosiosystem { require_auth( from ); check( stake_net_delta.amount != 0 || stake_cpu_delta.amount != 0, "should stake non-zero amount" ); check( std::abs( (stake_net_delta + stake_cpu_delta).amount ) - >= std::max( std::abs( stake_net_delta.amount ), std::abs( stake_cpu_delta.amount ) ), - "net and cpu deltas cannot be opposite signs" ); + >= std::max( std::abs( stake_net_delta.amount ), std::abs( stake_cpu_delta.amount ) ), + "net and cpu deltas cannot be opposite signs" ); name source_stake_from = from; if ( transfer ) { @@ -285,10 +295,28 @@ namespace eosiosystem { check( 0 <= tot_itr->net_weight.amount, "insufficient staked total net bandwidth" ); check( 0 <= tot_itr->cpu_weight.amount, "insufficient staked total cpu bandwidth" ); - int64_t ram_bytes, net, cpu; - get_resource_limits( receiver.value, &ram_bytes, &net, &cpu ); - - set_resource_limits( receiver.value, std::max( tot_itr->ram_bytes + ram_gift_bytes, ram_bytes ), tot_itr->net_weight.amount, tot_itr->cpu_weight.amount ); + { + bool ram_managed = false; + bool net_managed = false; + bool cpu_managed = false; + + auto voter_itr = _voters.find( receiver.value ); + if( voter_itr != _voters.end() ) { + ram_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ); + net_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::net_managed ); + cpu_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::cpu_managed ); + } + + if( !(net_managed && cpu_managed) ) { + int64_t ram_bytes, net, cpu; + get_resource_limits( receiver.value, &ram_bytes, &net, &cpu ); + + set_resource_limits( receiver.value, + ram_managed ? ram_bytes : std::max( tot_itr->ram_bytes + ram_gift_bytes, ram_bytes ), + net_managed ? net : tot_itr->net_weight.amount, + cpu_managed ? cpu : tot_itr->cpu_weight.amount ); + } + } if ( tot_itr->is_empty() ) { totals_tbl.erase( tot_itr ); @@ -385,6 +413,7 @@ namespace eosiosystem { } } + vote_stake_updater( from ); update_voting_power( from, stake_net_delta + stake_cpu_delta ); } @@ -402,7 +431,7 @@ namespace eosiosystem { }); } - check( 0 <= voter_itr->staked, "stake for voting cannot be negative"); + check( 0 <= voter_itr->staked, "stake for voting cannot be negative" ); if( voter == "b1"_n ) { validate_b1_vesting( voter_itr->staked ); @@ -434,7 +463,7 @@ namespace eosiosystem { check( unstake_net_quantity >= zero_asset, "must unstake a positive amount" ); check( unstake_cpu_quantity.amount + unstake_net_quantity.amount > 0, "must unstake a positive amount" ); check( _gstate.total_activated_stake >= min_activated_stake, - "cannot undelegate bandwidth until the chain is activated (at least 15% of all tokens participate in voting)" ); + "cannot undelegate bandwidth until the chain is activated (at least 15% of all tokens participate in voting)" ); changebw( from, receiver, -unstake_net_quantity, -unstake_cpu_quantity, false); } // undelegatebw @@ -447,7 +476,7 @@ namespace eosiosystem { auto req = refunds_tbl.find( owner.value ); check( req != refunds_tbl.end(), "refund request not found" ); check( req->request_time + seconds(refund_delay_sec) <= current_time_point(), - "refund is not available yet" ); + "refund is not available yet" ); INLINE_ACTION_SENDER(eosio::token, transfer)( token_account, { {stake_account, active_permission}, {req->owner, active_permission} }, diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index 3e8374c3b..4328afc69 100755 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -128,12 +128,155 @@ namespace eosiosystem { void system_contract::setalimits( name account, int64_t ram, int64_t net, int64_t cpu ) { require_auth( _self ); + user_resources_table userres( _self, account.value ); auto ritr = userres.find( account.value ); check( ritr == userres.end(), "only supports unlimited accounts" ); + + auto vitr = _voters.find( account.value ); + if( vitr != _voters.end() ) { + bool ram_managed = has_field( vitr->flags1, voter_info::flags1_fields::ram_managed ); + bool net_managed = has_field( vitr->flags1, voter_info::flags1_fields::net_managed ); + bool cpu_managed = has_field( vitr->flags1, voter_info::flags1_fields::cpu_managed ); + check( !(ram_managed || net_managed || cpu_managed), "cannot use setalimits on an account with managed resources" ); + } + set_resource_limits( account.value, ram, net, cpu ); } + void system_contract::setacctram( name account, std::optional ram_bytes ) { + require_auth( _self ); + + int64_t current_ram, current_net, current_cpu; + get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); + + int64_t ram = 0; + + if( !ram_bytes ) { + auto vitr = _voters.find( account.value ); + check( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::ram_managed ), + "RAM of account is already unmanaged" ); + + user_resources_table userres( _self, account.value ); + auto ritr = userres.find( account.value ); + + ram = ram_gift_bytes; + if( ritr != userres.end() ) { + ram += ritr->ram_bytes; + } + + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, false ); + }); + } else { + check( *ram_bytes >= 0, "not allowed to set RAM limit to unlimited" ); + + auto vitr = _voters.find( account.value ); + if ( vitr != _voters.end() ) { + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, true ); + }); + } else { + _voters.emplace( account, [&]( auto& v ) { + v.owner = account; + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::ram_managed, true ); + }); + } + + ram = *ram_bytes; + } + + set_resource_limits( account.value, ram, current_net, current_cpu ); + } + + void system_contract::setacctnet( name account, std::optional net_weight ) { + require_auth( _self ); + + int64_t current_ram, current_net, current_cpu; + get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); + + int64_t net = 0; + + if( !net_weight ) { + auto vitr = _voters.find( account.value ); + check( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::net_managed ), + "Network bandwidth of account is already unmanaged" ); + + user_resources_table userres( _self, account.value ); + auto ritr = userres.find( account.value ); + + if( ritr != userres.end() ) { + net = ritr->net_weight.amount; + } + + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, false ); + }); + } else { + check( *net_weight >= -1, "invalid value for net_weight" ); + + auto vitr = _voters.find( account.value ); + if ( vitr != _voters.end() ) { + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, true ); + }); + } else { + _voters.emplace( account, [&]( auto& v ) { + v.owner = account; + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::net_managed, true ); + }); + } + + net = *net_weight; + } + + set_resource_limits( account.value, current_ram, net, current_cpu ); + } + + void system_contract::setacctcpu( name account, std::optional cpu_weight ) { + require_auth( _self ); + + int64_t current_ram, current_net, current_cpu; + get_resource_limits( account.value, ¤t_ram, ¤t_net, ¤t_cpu ); + + int64_t cpu = 0; + + if( !cpu_weight ) { + auto vitr = _voters.find( account.value ); + check( vitr != _voters.end() && has_field( vitr->flags1, voter_info::flags1_fields::cpu_managed ), + "CPU bandwidth of account is already unmanaged" ); + + user_resources_table userres( _self, account.value ); + auto ritr = userres.find( account.value ); + + if( ritr != userres.end() ) { + cpu = ritr->cpu_weight.amount; + } + + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, false ); + }); + } else { + check( *cpu_weight >= -1, "invalid value for cpu_weight" ); + + auto vitr = _voters.find( account.value ); + if ( vitr != _voters.end() ) { + _voters.modify( vitr, same_payer, [&]( auto& v ) { + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, true ); + }); + } else { + _voters.emplace( account, [&]( auto& v ) { + v.owner = account; + v.flags1 = set_field( v.flags1, voter_info::flags1_fields::cpu_managed, true ); + }); + } + + cpu = *cpu_weight; + } + + set_resource_limits( account.value, current_ram, current_net, cpu ); + } + void system_contract::rmvproducer( name producer ) { require_auth( _self ); auto prod = _producers.find( producer.value ); @@ -308,7 +451,7 @@ namespace eosiosystem { m.quote.balance.amount = system_token_supply.amount / 1000; m.quote.balance.symbol = core; }); - + INLINE_ACTION_SENDER(eosio::token, open)( token_account, { _self, active_permission }, { rex_account, core, _self } ); } @@ -320,10 +463,11 @@ EOSIO_DISPATCH( eosiosystem::system_contract, // native.hpp (newaccount definition is actually in eosio.system.cpp) (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(canceldelay)(onerror)(setabi) // eosio.system.cpp - (init)(setram)(setramrate)(setparams)(setpriv)(setalimits)(rmvproducer)(updtrevision)(bidname)(bidrefund) + (init)(setram)(setramrate)(setparams)(setpriv)(setalimits)(setacctram)(setacctnet)(setacctcpu) + (rmvproducer)(updtrevision)(bidname)(bidrefund) // rex.cpp (deposit)(withdraw)(buyrex)(unstaketorex)(sellrex)(cnclrexorder)(rentcpu)(rentnet)(fundcpuloan)(fundnetloan) - (defcpuloan)(defnetloan)(updaterex)(consolidate)(rexexec)(closerex) + (defcpuloan)(defnetloan)(updaterex)(consolidate)(mvtosavings)(mvfrsavings)(rexexec)(closerex) // delegate_bandwidth.cpp (buyrambytes)(buyram)(sellram)(delegatebw)(undelegatebw)(refund) // voting.cpp diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index db4360b0f..7a98b74f7 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -7,7 +7,7 @@ namespace eosiosystem { /** - * @brief Deposits SYS tokens to user REX fund + * @brief Deposits core tokens to user REX fund * * @param owner - REX fund owner * @param amount - amount of tokens to be deposited @@ -23,8 +23,9 @@ namespace eosiosystem { transfer_to_fund( owner, amount ); update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ) ); } + /** - * @brief Withdraws SYS tokens from user REX fund + * @brief Withdraws core tokens from user REX fund * * @param owner - REX fund owner * @param amount - amount of tokens to be withdrawn @@ -42,10 +43,10 @@ namespace eosiosystem { } /** - * @brief Buys REX in exchange for SYS tokens taken out of user REX fund + * @brief Buys REX in exchange for core tokens taken out of user REX fund * * @param from - owner account name - * @param amount - amount of SYS tokens to be used for purchase + * @param amount - amount of core tokens to be used for purchase */ void system_contract::buyrex( const name& from, const asset& amount ) { @@ -59,10 +60,12 @@ namespace eosiosystem { 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 ); + // 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 ) ); } /** - * @brief Buys REX using staked SYS tokens + * @brief Buys REX using staked core tokens * * @param owner - owner of staked tokens account name * @param receiver - account name that tokens have previously been staked to @@ -75,7 +78,7 @@ namespace eosiosystem { 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" ); + "must unstake a positive amount to buy rex" ); check_voting_requirement( owner ); { @@ -101,10 +104,12 @@ namespace eosiosystem { add_to_rex_balance( owner, payment, rex_received ); runrex(2); update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ), true ); + // 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 ) ); } /** - * @brief Sells REX in exchange for SYS tokens + * @brief Sells REX in exchange for core tokens * * @param from - owner of REX tokens * @param rex - amount of REX tokens to be sold @@ -117,12 +122,12 @@ namespace eosiosystem { 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)" ); + "asset must be a positive amount of (REX, 4)" ); process_rex_maturities( bitr ); check( rex.amount <= bitr->matured_rex, "insufficient available rex" ); auto current_order = fill_rex_order( bitr, rex ); - update_rex_account( from, current_order.proceeds, current_order.stake_change ); + 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. @@ -130,7 +135,7 @@ namespace eosiosystem { */ auto oitr = _rexorders.find( from.value ); if ( oitr == _rexorders.end() ) { - _rexorders.emplace( from, [&]( auto& order ) { + oitr = _rexorders.emplace( from, [&]( auto& order ) { order.owner = from; order.rex_requested = rex; order.is_open = true; @@ -141,10 +146,14 @@ namespace eosiosystem { } else { _rexorders.modify( oitr, same_payer, [&]( auto& order ) { order.rex_requested.amount += rex.amount; - check( order.rex_requested.amount <= bitr->matured_rex, - "insufficient funds for current and scheduled orders"); }); } + 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 ) ); } } @@ -163,8 +172,8 @@ namespace eosiosystem { } /** - * Rents as many SYS tokens as determined by market price and stakes them for CPU bandwidth - * for the benefit of receiver account. After 30 days the rented SYS delegation of CPU will + * Rents as many core tokens as determined by market price and stakes them for CPU bandwidth + * for the benefit of receiver account. After 30 days the rented core delegation of CPU will * expire or be renewed at new market price depending on available loan fund. * * @brief Rents CPU resources for 30 days in exchange for market-determined price @@ -182,10 +191,10 @@ namespace eosiosystem { int64_t rented_tokens = rent_rex( cpu_loans, from, receiver, loan_payment, loan_fund ); update_resource_limits( from, receiver, 0, rented_tokens ); } - + /** - * Rents as many SYS tokens as determined by market price and stakes them for NET bandwidth - * for the benefit of receiver account. After 30 days the rented SYS delegation of NET will + * Rents as many core tokens as determined by market price and stakes them for NET bandwidth + * for the benefit of receiver account. After 30 days the rented core delegation of NET will * expire or be renewed at new market price depending on available loan fund. * * @brief Rents NET resources for 30 days in exchange for market-determined price @@ -241,12 +250,12 @@ namespace eosiosystem { * * @param from - loan creator * @param loan_num - loan id - * @param amount - tokens to be withdrawn from loan fund + * @param amount - tokens to be withdrawn from loan fund */ void system_contract::defcpuloan( const name& from, uint64_t loan_num, const asset& amount ) { require_auth( from ); - + rex_cpu_loan_table cpu_loans( _self, _self.value ); defund_rex_loan( cpu_loans, from, loan_num, amount ); } @@ -269,7 +278,7 @@ namespace eosiosystem { /** * @brief Updates REX owner vote weight to current value of held REX tokens * - * @param owner - owner of REX tokens + * @param owner - owner of REX tokens */ void system_contract::updaterex( const name& owner ) { @@ -284,7 +293,7 @@ namespace eosiosystem { const int64_t total_rex = rexp_itr->total_rex.amount; const int64_t total_lendable = rexp_itr->total_lendable.amount; const int64_t rex_balance = itr->rex_balance.amount; - + asset current_stake( 0, core_symbol() ); if ( total_rex > 0 ) { current_stake.amount = ( uint128_t(rex_balance) * total_lendable ) / total_rex; @@ -307,7 +316,7 @@ namespace eosiosystem { void system_contract::rexexec( const name& user, uint16_t max ) { require_auth( user ); - + runrex( max ); } @@ -320,14 +329,83 @@ namespace eosiosystem { void system_contract::consolidate( const name& owner ) { require_auth( owner ); - + runrex(2); - + auto bitr = _rexbalance.require_find( owner.value, "account has no REX balance" ); asset rex_in_sell_order = update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ) ); consolidate_rex_balance( bitr, rex_in_sell_order ); } + /** + * @brief Moves a specified amount of REX to savings bucket + * + * @param owner - account name of REX owner + * @param rex - amount of REX to be moved + */ + void system_contract::mvtosavings( const name& owner, const asset& rex ) + { + require_auth( owner ); + + runrex(2); + + auto bitr = _rexbalance.require_find( owner.value, "account has no REX balance" ); + check( rex.amount > 0 && rex.symbol == bitr->rex_balance.symbol, "asset must be a positive amount of (REX, 4)" ); + const asset rex_in_sell_order = update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ) ); + const int64_t rex_in_savings = read_rex_savings( bitr ); + check( rex.amount + rex_in_sell_order.amount + rex_in_savings <= bitr->rex_balance.amount, + "insufficient REX balance" ); + process_rex_maturities( bitr ); + _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { + int64_t moved_rex = 0; + while ( !rb.rex_maturities.empty() && moved_rex < rex.amount) { + const int64_t drex = std::min( rex.amount - moved_rex, rb.rex_maturities.back().second ); + rb.rex_maturities.back().second -= drex; + moved_rex += drex; + if ( rb.rex_maturities.back().second == 0 ) { + rb.rex_maturities.pop_back(); + } + } + if ( moved_rex < rex.amount ) { + const int64_t drex = rex.amount - moved_rex; + rb.matured_rex -= drex; + moved_rex += drex; + check( rex_in_sell_order.amount <= rb.matured_rex, "logic error in mvtosavings" ); + } + check( moved_rex == rex.amount, "programmer error in mvtosavings" ); + }); + put_rex_savings( bitr, rex_in_savings + rex.amount ); + } + + /** + * @brief Moves a specified amount of REX from savings bucket + * + * @param owner - account name of REX owner + * @param rex - amount of REX to be moved + */ + void system_contract::mvfrsavings( const name& owner, const asset& rex ) + { + require_auth( owner ); + + runrex(2); + + auto bitr = _rexbalance.require_find( owner.value, "account has no REX balance" ); + check( rex.amount > 0 && rex.symbol == bitr->rex_balance.symbol, "asset must be a positive amount of (REX, 4)" ); + const int64_t rex_in_savings = read_rex_savings( bitr ); + check( rex.amount <= rex_in_savings, "insufficient REX in savings" ); + process_rex_maturities( bitr ); + _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { + const time_point_sec maturity = get_rex_maturity(); + if ( !rb.rex_maturities.empty() && rb.rex_maturities.back().first == maturity ) { + rb.rex_maturities.back().second += rex.amount; + } else { + rb.rex_maturities.emplace_back( maturity, rex.amount ); + } + }); + put_rex_savings( bitr, rex_in_savings - rex.amount ); + update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ) ); + } + /** * @brief Deletes unused REX-related database entries and frees RAM * @@ -336,12 +414,12 @@ namespace eosiosystem { void system_contract::closerex( const name& owner ) { require_auth( owner ); - + if ( rex_system_initialized() ) runrex(2); - + update_rex_account( owner, asset( 0, core_symbol() ), asset( 0, core_symbol() ) ); - + /// check for any outstanding loans or rex fund { rex_cpu_loan_table cpu_loans( _self, _self.value ); @@ -354,7 +432,7 @@ namespace eosiosystem { auto fund_itr = _rexfunds.find( owner.value ); bool no_outstanding_rex_fund = ( fund_itr != _rexfunds.end() ) && ( fund_itr->balance.amount == 0 ); - + if ( no_outstanding_cpu_loans && no_outstanding_net_loans && no_outstanding_rex_fund ) { _rexfunds.erase( fund_itr ); } @@ -410,33 +488,48 @@ namespace eosiosystem { return; } + user_resources_table totals_tbl( _self, receiver.value ); + auto tot_itr = totals_tbl.find( receiver.value ); + if ( tot_itr == totals_tbl.end() ) { + check( 0 <= delta_net && 0 <= delta_cpu, "logic error, should not occur"); + tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) { + tot.owner = receiver; + tot.net_weight = asset( delta_net, core_symbol() ); + tot.cpu_weight = asset( delta_cpu, core_symbol() ); + }); + } else { + totals_tbl.modify( tot_itr, same_payer, [&]( auto& tot ) { + tot.net_weight.amount += delta_net; + tot.cpu_weight.amount += delta_cpu; + }); + } + check( 0 <= tot_itr->net_weight.amount, "insufficient staked total net bandwidth" ); + check( 0 <= tot_itr->cpu_weight.amount, "insufficient staked total cpu bandwidth" ); + { - user_resources_table totals_tbl( _self, receiver.value ); - auto tot_itr = totals_tbl.find( receiver.value ); - if ( tot_itr == totals_tbl.end() ) { - check( 0 <= delta_net && 0 <= delta_cpu, "logic error, should not occur"); - tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) { - tot.owner = receiver; - tot.net_weight = asset( delta_net, core_symbol() ); - tot.cpu_weight = asset( delta_cpu, core_symbol() ); - }); - } else { - totals_tbl.modify( tot_itr, same_payer, [&]( auto& tot ) { - tot.net_weight.amount += delta_net; - tot.cpu_weight.amount += delta_cpu; - }); + bool net_managed = false; + bool cpu_managed = false; + + auto voter_itr = _voters.find( receiver.value ); + if( voter_itr != _voters.end() ) { + net_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::net_managed ); + cpu_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::cpu_managed ); } - check( 0 <= tot_itr->net_weight.amount, "insufficient staked total net bandwidth" ); - check( 0 <= tot_itr->cpu_weight.amount, "insufficient staked total cpu bandwidth" ); - - if ( tot_itr->is_empty() ) { - totals_tbl.erase( tot_itr ); + + if( !(net_managed && cpu_managed) ) { + int64_t ram_bytes = 0, net = 0, cpu = 0; + get_resource_limits( receiver.value, &ram_bytes, &net, &cpu ); + + set_resource_limits( receiver.value, + ram_bytes, + net_managed ? net : tot_itr->net_weight.amount, + cpu_managed ? cpu : tot_itr->cpu_weight.amount ); } } - int64_t ram_bytes = 0, net = 0, cpu = 0; - get_resource_limits( receiver.value, &ram_bytes, &net, &cpu ); - set_resource_limits( receiver.value, ram_bytes, net + delta_net, cpu + delta_cpu ); + if ( tot_itr->is_empty() ) { + totals_tbl.erase( tot_itr ); + } } void system_contract::check_voting_requirement( const name& owner, const char* error_msg )const @@ -462,7 +555,6 @@ namespace eosiosystem { rt.total_lent.amount -= itr->total_staked.amount; rt.total_lendable.amount = rt.total_unlent.amount + rt.total_lent.amount; }); - bool delete_loan = false; int64_t delta_stake = 0; if ( itr->payment <= itr->balance && rex_loans_available() ) { @@ -489,7 +581,7 @@ namespace eosiosystem { transfer_to_fund( itr->from, itr->balance ); } } - + return { delete_loan, delta_stake }; }; @@ -506,9 +598,9 @@ namespace eosiosystem { rex_cpu_loan_table cpu_loans( _self, _self.value ); auto cpu_idx = cpu_loans.get_index<"byexpr"_n>(); for ( uint16_t i = 0; i < max; ++i ) { - auto itr = cpu_idx.begin(); + auto itr = cpu_idx.begin(); if ( itr == cpu_idx.end() || itr->expiration > current_time_point() ) break; - + auto result = process_expired_loan( cpu_idx, itr ); if ( result.second != 0 ) update_resource_limits( itr->from, itr->receiver, 0, result.second ); @@ -547,11 +639,14 @@ namespace eosiosystem { if ( bitr != _rexbalance.end() ) { // should always be true auto result = fill_rex_order( bitr, oitr->rex_requested ); if ( result.success ) { + const name order_owner = oitr->owner; idx.modify( oitr, same_payer, [&]( auto& order ) { order.proceeds.amount = result.proceeds.amount; order.stake_change.amount = result.stake_change.amount; order.close(); }); + /// send dummy action to show and owner and proceeds of filled sellrex order + dispatch_inline( null_account, "orderresult"_n, { }, std::make_tuple( order_owner, result.proceeds ) ); } } oitr = next; @@ -565,7 +660,7 @@ namespace eosiosystem { { runrex(2); - check( rex_loans_available(), "rex loans are not currently available" ); + check( rex_loans_available(), "rex loans are currently not available" ); check( payment.symbol == core_symbol() && fund.symbol == core_symbol(), "must use core token" ); check( 0 < payment.amount && 0 <= fund.amount, "must use positive asset amount" ); @@ -609,17 +704,20 @@ namespace eosiosystem { 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; - const int64_t S1 = (uint128_t(R1) * S0) / R0; - asset proceeds( S0 - S1, core_symbol() ); + const int64_t S1 = S0 - p; + asset proceeds( p, core_symbol() ); asset stake_change( 0, core_symbol() ); - bool success = false; + bool success = false; + + check( proceeds.amount > 0, "proceeds are negligible" ); - const int64_t unlent_lower_bound = ( uint128_t(2) * rexitr->total_lent.amount ) / 10; + const int64_t unlent_lower_bound = rexitr->total_lent.amount; const int64_t available_unlent = rexitr->total_unlent.amount - unlent_lower_bound; // available_unlent <= 0 is possible - if ( proceeds.amount <= available_unlent ) { + 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; + 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; rt.total_lendable.amount = S1; @@ -676,8 +774,7 @@ namespace eosiosystem { */ 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" ); + 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 ) { @@ -693,14 +790,13 @@ namespace eosiosystem { */ void system_contract::transfer_to_fund( const name& owner, const asset& amount ) { - check( 0 < amount.amount && amount.symbol == core_symbol(), - "must transfer positive amount to REX fund" ); + check( 0 < amount.amount && amount.symbol == core_symbol(), "must transfer positive amount to REX fund" ); auto itr = _rexfunds.find( owner.value ); if ( itr == _rexfunds.end() ) { _rexfunds.emplace( owner, [&]( auto& fund ) { fund.owner = owner; fund.balance = amount; - }); + }); } else { _rexfunds.modify( itr, same_payer, [&]( auto& fund ) { fund.balance.amount += amount.amount; @@ -719,15 +815,15 @@ namespace eosiosystem { * @param owner - owner account name * @param proceeds - additional proceeds to be transfered to owner REX fund * @param delta_stake - additional stake to be added to owner vote weight - * @param force_vote_update - if true, vote weight is updated even if vote stake didn't change + * @param force_vote_update - if true, vote weight is updated even if vote stake didn't change * - * @return asset - REX amount of owner unfilled sell order if one exists + * @return asset - REX amount of owner unfilled sell order if one exists */ 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() ); + asset rex_in_sell_order( 0, rex_symbol ); auto itr = _rexorders.find( owner.value ); if ( itr != _rexorders.end() ) { if ( itr->is_open ) { @@ -750,7 +846,7 @@ namespace eosiosystem { /** * @brief Channels system fees to REX pool * - * @param from - account from which asset is transfered to REX pool + * @param from - account from which asset is transfered to REX pool * @param amount - amount of tokens to be transfered */ void system_contract::channel_to_rex( const name& from, const asset& amount ) @@ -794,7 +890,7 @@ namespace eosiosystem { { const uint32_t num_of_maturity_buckets = 5; static const uint32_t now = current_time_point_sec().utc_seconds; - static const uint32_t r = now % seconds_per_day; + static const uint32_t r = now % seconds_per_day; static const time_point_sec rms{ now - r + num_of_maturity_buckets * seconds_per_day }; return rms; } @@ -806,7 +902,7 @@ namespace eosiosystem { */ void system_contract::process_rex_maturities( const rex_balance_table::const_iterator& bitr ) { - time_point_sec now = current_time_point_sec(); + const time_point_sec now = current_time_point_sec(); _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { while ( !rb.rex_maturities.empty() && rb.rex_maturities.front().first <= now ) { rb.matured_rex += rb.rex_maturities.front().second; @@ -819,11 +915,12 @@ namespace eosiosystem { * @brief Consolidates REX maturity buckets into one * * @param bitr - iterator pointing to rex_balance object - * @param rex_in_sell_order - REX tokens in owner unfilled sell order, if one exists + * @param rex_in_sell_order - REX tokens in owner unfilled sell order, if one exists */ void system_contract::consolidate_rex_balance( const rex_balance_table::const_iterator& bitr, - const asset& rex_in_sell_order ) + const asset& rex_in_sell_order ) { + const int64_t rex_in_savings = read_rex_savings( bitr ); _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { int64_t total = rb.matured_rex - rex_in_sell_order.amount; rb.matured_rex = rex_in_sell_order.amount; @@ -831,8 +928,11 @@ namespace eosiosystem { total += rb.rex_maturities.front().second; rb.rex_maturities.pop_front(); } - rb.rex_maturities.emplace_back( get_rex_maturity(), total ); + if (total > 0 ) { + rb.rex_maturities.emplace_back( get_rex_maturity(), total ); + } }); + put_rex_savings( bitr, rex_in_savings ); } /** @@ -845,22 +945,21 @@ namespace eosiosystem { 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 + * 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 + * 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. + * 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 = 100'000'0000; /// base amount prevents renting profitably until at least a minimum number of core_symbol() is made available + 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; - rp.total_lendable = payment; rp.total_lent = asset( 0, core_symbol() ); rp.total_unlent = rp.total_lendable - rp.total_lent; @@ -870,8 +969,7 @@ namespace eosiosystem { }); } 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; - + 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; @@ -885,9 +983,7 @@ namespace eosiosystem { 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; - _rexpool.modify( itr, same_payer, [&]( auto& rp ) { rp.total_lendable.amount = S1; rp.total_rex.amount = R1; @@ -895,7 +991,7 @@ namespace eosiosystem { check( rp.total_unlent.amount >= 0, "programmer error, this should never go negative" ); }); } - + return rex_received; } @@ -904,7 +1000,7 @@ namespace eosiosystem { * * @param owner - account name of REX owner * @param payment - amount core tokens paid to buy REX - * @param rex_received - amount of purchased REX tokens + * @param rex_received - amount of purchased REX tokens * * @return asset - change in owner REX vote stake */ @@ -924,23 +1020,97 @@ namespace eosiosystem { 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 ) + 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; } + const int64_t rex_in_savings = read_rex_savings( bitr ); process_rex_maturities( bitr ); - const time_point_sec maturity = get_rex_maturity(); _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { + const time_point_sec maturity = get_rex_maturity(); 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 ); } }); - + put_rex_savings( bitr, rex_in_savings ); return current_rex_stake - init_rex_stake; } + /** + * @brief Reads amount of REX in savings bucket and removes the bucket from maturities + * + * Reads and (temporarily) removes REX savings bucket from REX maturities in order to + * allow uniform processing of remaining buckets as savings is a special case. This + * function is used in conjunction with put_rex_savings. + * + * @param bitr - iterator pointing to rex_balance object + * + * @return int64_t - amount of REX in savings bucket + */ + int64_t system_contract::read_rex_savings( const rex_balance_table::const_iterator& bitr ) + { + int64_t rex_in_savings = 0; + static const time_point_sec end_of_days = time_point_sec::maximum(); + if ( !bitr->rex_maturities.empty() && bitr->rex_maturities.back().first == end_of_days ) { + _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { + rex_in_savings = rb.rex_maturities.back().second; + rb.rex_maturities.pop_back(); + }); + } + return rex_in_savings; + } + + /** + * @brief Adds a specified REX amount to savings bucket + * + * @param bitr - iterator pointing to rex_balance object + * @param rex - amount of REX to be added + */ + void system_contract::put_rex_savings( const rex_balance_table::const_iterator& bitr, int64_t rex ) + { + if ( rex == 0 ) return; + static const time_point_sec end_of_days = time_point_sec::maximum(); + _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { + if ( !rb.rex_maturities.empty() && rb.rex_maturities.back().first == end_of_days ) { + rb.rex_maturities.back().second += rex; + } else { + rb.rex_maturities.emplace_back( end_of_days, rex ); + } + }); + } + + /** + * @brief Updates voter REX vote stake to the current value of REX tokens held + * + * @param voter - account name of voter + */ + void system_contract::update_rex_stake( const name& voter ) + { + int64_t delta_stake = 0; + auto bitr = _rexbalance.find( voter.value ); + if ( bitr != _rexbalance.end() && rex_available() ) { + asset init_vote_stake = bitr->vote_stake; + asset current_vote_stake( 0, core_symbol() ); + current_vote_stake.amount = ( uint128_t(bitr->rex_balance.amount) * _rexpool.begin()->total_lendable.amount ) + / _rexpool.begin()->total_rex.amount; + _rexbalance.modify( bitr, same_payer, [&]( auto& rb ) { + rb.vote_stake.amount = current_vote_stake.amount; + }); + delta_stake = current_vote_stake.amount - init_vote_stake.amount; + } + + if ( delta_stake != 0 ) { + auto vitr = _voters.find( voter.value ); + if ( vitr != _voters.end() ) { + _voters.modify( vitr, same_payer, [&]( auto& vinfo ) { + vinfo.staked += delta_stake; + }); + } + } + } + }; /// namespace eosiosystem diff --git a/contracts/eosio.system/src/voting.cpp b/contracts/eosio.system/src/voting.cpp index 153ea9dd7..3d9016be4 100755 --- a/contracts/eosio.system/src/voting.cpp +++ b/contracts/eosio.system/src/voting.cpp @@ -6,7 +6,6 @@ #include #include -#include #include #include #include @@ -21,7 +20,6 @@ namespace eosiosystem { using eosio::indexed_by; using eosio::const_mem_fun; - using eosio::print; using eosio::singleton; using eosio::transaction; @@ -194,6 +192,7 @@ namespace eosiosystem { */ void system_contract::voteproducer( const name voter_name, const name proxy, const std::vector& producers ) { require_auth( voter_name ); + vote_stake_updater( voter_name ); update_votes( voter_name, proxy, producers, true ); auto rex_itr = _rexbalance.find( voter_name.value ); if( rex_itr != _rexbalance.end() && rex_itr->rex_balance.amount > 0 ) { @@ -206,7 +205,6 @@ namespace eosiosystem { if ( proxy ) { check( producers.size() == 0, "cannot vote for producers and proxy at same time" ); check( voter_name != proxy, "cannot proxy to self" ); - require_recipient( proxy ); } else { check( producers.size() <= 30, "attempt to vote for too many producers" ); for( size_t i = 1; i < producers.size(); ++i ) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b973fa6d8..4660381a4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required( VERSION 3.5 ) set(EOSIO_VERSION_MIN "1.4") -set(EOSIO_VERSION_SOFT_MAX "1.5") +set(EOSIO_VERSION_SOFT_MAX "1.6") #set(EOSIO_VERSION_HARD_MAX "") find_package(eosio) diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index 434928d55..cadbf7d86 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -6,6 +6,7 @@ #include #include +#include #include "contracts.hpp" #include "test_symbol.hpp" @@ -312,6 +313,20 @@ class eosio_system_tester : public TESTER { return unstake( acnt, acnt, net, cpu ); } + int64_t bancor_convert( int64_t S, int64_t R, int64_t T ) { return double(R) * T / ( double(S) + T ); }; + + int64_t get_net_limit( account_name a ) { + int64_t ram_bytes = 0, net = 0, cpu = 0; + control->get_resource_limits_manager().get_account_limits( a, ram_bytes, net, cpu ); + return net; + }; + + int64_t get_cpu_limit( account_name a ) { + int64_t ram_bytes = 0, net = 0, cpu = 0; + control->get_resource_limits_manager().get_account_limits( a, ram_bytes, net, cpu ); + return cpu; + }; + action_result deposit( const account_name& owner, const asset& amount ) { return push_action( name(owner), N(deposit), mvo() ("owner", owner) @@ -333,6 +348,22 @@ class eosio_system_tester : public TESTER { ); } + asset get_buyrex_result( const account_name& from, const asset& amount ) { + auto trace = base_tester::push_action( N(eosio), N(buyrex), from, mvo()("from", from)("amount", amount) ); + asset rex_received; + for ( size_t i = 0; i < trace->action_traces.size(); ++i ) { + for ( size_t j = 0; j < trace->action_traces[i].inline_traces.size(); ++j ) { + if ( trace->action_traces[i].inline_traces[j].act.name == N(buyresult) ) { + fc::raw::unpack( trace->action_traces[i].inline_traces[j].act.data.data(), + trace->action_traces[i].inline_traces[j].act.data.size(), + rex_received ); + return rex_received; + } + } + } + return rex_received; + } + action_result unstaketorex( const account_name& owner, const account_name& receiver, const asset& from_net, const asset& from_cpu ) { return push_action( name(owner), N(unstaketorex), mvo() ("owner", owner) @@ -342,6 +373,27 @@ class eosio_system_tester : public TESTER { ); } + asset get_unstaketorex_result( const account_name& owner, const account_name& receiver, const asset& from_net, const asset& from_cpu ) { + auto trace = base_tester::push_action( N(eosio), N(unstaketorex), owner, mvo() + ("owner", owner) + ("receiver", receiver) + ("from_net", from_net) + ("from_cpu", from_cpu) + ); + asset rex_received; + for ( size_t i = 0; i < trace->action_traces.size(); ++i ) { + for ( size_t j = 0; j < trace->action_traces[i].inline_traces.size(); ++j ) { + if ( trace->action_traces[i].inline_traces[j].act.name == N(buyresult) ) { + fc::raw::unpack( trace->action_traces[i].inline_traces[j].act.data.data(), + trace->action_traces[i].inline_traces[j].act.data.size(), + rex_received ); + return rex_received; + } + } + } + return rex_received; + } + action_result sellrex( const account_name& from, const asset& rex ) { return push_action( name(from), N(sellrex), mvo() ("from", from) @@ -349,6 +401,38 @@ class eosio_system_tester : public TESTER { ); } + asset get_sellrex_result( const account_name& from, const asset& rex ) { + auto trace = base_tester::push_action( N(eosio), N(sellrex), from, mvo()("from", from)("rex", rex) ); + asset proceeds; + for ( size_t i = 0; i < trace->action_traces.size(); ++i ) { + for ( size_t j = 0; j < trace->action_traces[i].inline_traces.size(); ++j ) { + if ( trace->action_traces[i].inline_traces[j].act.name == N(sellresult) ) { + fc::raw::unpack( trace->action_traces[i].inline_traces[j].act.data.data(), + trace->action_traces[i].inline_traces[j].act.data.size(), + proceeds ); + return proceeds; + } + } + } + return proceeds; + } + + auto get_rexorder_result( const transaction_trace_ptr& trace ) { + std::vector> output; + for ( size_t i = 0; i < trace->action_traces.size(); ++i ) { + for ( size_t j = 0; j < trace->action_traces[i].inline_traces.size(); ++j ) { + if ( trace->action_traces[i].inline_traces[j].act.name == N(orderresult) ) { + fc::datastream ds( trace->action_traces[i].inline_traces[j].act.data.data(), + trace->action_traces[i].inline_traces[j].act.data.size() ); + account_name owner; fc::raw::unpack( ds, owner ); + asset proceeds; fc::raw::unpack( ds, proceeds ); + output.emplace_back( owner, proceeds ); + } + } + } + return output; + } + action_result cancelrexorder( const account_name& owner ) { return push_action( name(owner), N(cnclrexorder), mvo()("owner", owner) ); } @@ -416,6 +500,14 @@ class eosio_system_tester : public TESTER { return push_action( name(owner), N(consolidate), mvo()("owner", owner) ); } + action_result mvtosavings( const account_name& owner, const asset& rex ) { + return push_action( name(owner), N(mvtosavings), mvo()("owner", owner)("rex", rex) ); + } + + action_result mvfrsavings( const account_name& owner, const asset& rex ) { + return push_action( name(owner), N(mvfrsavings), mvo()("owner", owner)("rex", rex) ); + } + action_result closerex( const account_name& owner ) { return push_action( name(owner), N(closerex), mvo()("owner", owner) ); } @@ -503,6 +595,11 @@ class eosio_system_tester : public TESTER { return abi_ser.binary_to_variant( "rex_order", data, abi_serializer_max_time ); } + fc::variant get_rex_order_obj( const account_name& act ) { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, N(rexqueue), act ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "rex_order", data, abi_serializer_max_time ); + } + fc::variant get_rex_pool() const { vector data; const auto& db = control->db(); diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index bb237b132..2404df530 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -3392,12 +3393,17 @@ BOOST_FIXTURE_TEST_CASE( ram_gift, eosio_system_tester ) try { BOOST_FIXTURE_TEST_CASE( buy_sell_rex, eosio_system_tester ) try { const int64_t ratio = 10000; - const asset init_rent = core_sym::from_string("100000.0000"); + const asset init_rent = core_sym::from_string("20000.0000"); const asset init_balance = core_sym::from_string("1000.0000"); const std::vector accounts = { N(aliceaccount), N(bobbyaccount), N(carolaccount), N(emilyaccount), N(frankaccount) }; account_name alice = accounts[0], bob = accounts[1], carol = accounts[2], emily = accounts[3], frank = accounts[4]; setup_rex_accounts( accounts, init_balance ); + BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( alice, core_sym::from_string("2.5000") ) ); + produce_blocks(2); + produce_block(fc::days(5)); + BOOST_REQUIRE_EQUAL( core_sym::from_string("2.5000"), get_sellrex_result( alice, asset::from_string("25000.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, core_sym::from_string("13.0000") ) ); BOOST_REQUIRE_EQUAL( core_sym::from_string("13.0000"), get_rex_vote_stake( alice ) ); BOOST_REQUIRE_EQUAL( success(), buyrex( alice, core_sym::from_string("17.0000") ) ); @@ -3428,15 +3434,14 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( wasm_assert_msg("asset must be a positive amount of (REX, 4)"), sellrex( bob, asset::from_string("-75.0030 REX") ) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( bob, asset::from_string("750000.0030 REX") ) ); - + auto init_total_rex = rex_pool["total_rex"].as().get_amount(); auto init_total_lendable = rex_pool["total_lendable"].as().get_amount(); - BOOST_REQUIRE_EQUAL( success(), sellrex( bob, asset::from_string("550000.6800 REX") ) ); - BOOST_REQUIRE_EQUAL( asset::from_string("199999.3200 REX"), get_rex_balance(bob) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( bob, asset::from_string("550001.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("199999.0000 REX"), get_rex_balance(bob) ); rex_pool = get_rex_pool(); auto total_rex = rex_pool["total_rex"].as().get_amount(); auto total_lendable = rex_pool["total_lendable"].as().get_amount(); - BOOST_REQUIRE_EQUAL( init_total_rex / init_total_lendable, total_rex / total_lendable ); BOOST_REQUIRE_EQUAL( total_lendable, rex_pool["total_unlent"].as().get_amount() ); BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), rex_pool["total_lent"].as() ); @@ -3446,18 +3451,39 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_rex, eosio_system_tester ) try { } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( unstake_buy_rex, eosio_system_tester, * boost::unit_test::tolerance(1e-10) ) try { +BOOST_FIXTURE_TEST_CASE( buy_sell_small_rex, eosio_system_tester ) try { - auto get_net_limit = [&](account_name a) -> int64_t { - int64_t ram_bytes = 0, net = 0, cpu = 0; - control->get_resource_limits_manager().get_account_limits( a, ram_bytes, net, cpu ); - return net; - }; - auto get_cpu_limit = [&](account_name a) -> int64_t { - int64_t ram_bytes = 0, net = 0, cpu = 0; - control->get_resource_limits_manager().get_account_limits( a, ram_bytes, net, cpu ); - return cpu; - }; + const int64_t ratio = 10000; + const asset init_rent = core_sym::from_string("100000.0000"); + const asset init_balance = core_sym::from_string("1000.0000"); + const std::vector accounts = { N(aliceaccount), N(bobbyaccount), N(carolaccount) }; + account_name alice = accounts[0], bob = accounts[1], carol = accounts[2]; + setup_rex_accounts( accounts, init_balance ); + + const asset payment = core_sym::from_string("10.0000"); + BOOST_REQUIRE_EQUAL( ratio * payment.get_amount(), get_buyrex_result( alice, payment ).get_amount() ); + + produce_blocks(2); + produce_block( fc::days(5) ); + produce_blocks(2); + + asset init_rex_stake = get_rex_vote_stake( alice ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("proceeds are negligible"), sellrex( alice, asset::from_string("0.0001 REX") ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("proceeds are negligible"), sellrex( alice, asset::from_string("0.9999 REX") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0001"), get_sellrex_result( alice, asset::from_string("1.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0001"), get_sellrex_result( alice, asset::from_string("1.9999 REX") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0002"), get_sellrex_result( alice, asset::from_string("2.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0002"), get_sellrex_result( alice, asset::from_string("2.9999 REX") ) ); + BOOST_REQUIRE_EQUAL( get_rex_vote_stake( alice ), init_rex_stake - core_sym::from_string("0.0006") ); + + BOOST_REQUIRE_EQUAL( success(), rentcpu( carol, bob, core_sym::from_string("10.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset::from_string("0.9000 REX") ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("proceeds are negligible"), sellrex( alice, asset::from_string("0.4000 REX") ) ); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE( unstake_buy_rex, eosio_system_tester, * boost::unit_test::tolerance(1e-10) ) try { const int64_t ratio = 10000; const asset zero_asset = core_sym::from_string("0.0000"); @@ -3509,7 +3535,7 @@ BOOST_FIXTURE_TEST_CASE( unstake_buy_rex, eosio_system_tester, * boost::unit_tes BOOST_TEST_REQUIRE( init_prod_info["total_votes"].as_double() == stake2votes( asset( init_voter_info["staked"].as(), symbol{CORE_SYM} ) ) ); produce_block( fc::days(4) ); - BOOST_REQUIRE_EQUAL( success(), unstaketorex( alice, alice, net_stake, cpu_stake ) ); + BOOST_REQUIRE_EQUAL( ratio * tot_stake.get_amount(), get_unstaketorex_result( alice, alice, net_stake, cpu_stake ).get_amount() ); BOOST_REQUIRE_EQUAL( get_cpu_limit( alice ), init_cpu_limit ); BOOST_REQUIRE_EQUAL( get_net_limit( alice ), init_net_limit ); BOOST_REQUIRE_EQUAL( ratio * tot_stake.get_amount(), get_rex_balance( alice ).get_amount() ); @@ -3523,7 +3549,7 @@ BOOST_FIXTURE_TEST_CASE( unstake_buy_rex, eosio_system_tester, * boost::unit_tes stake2votes( asset( current_voter_info["staked"].as(), symbol{CORE_SYM} ) ) ); BOOST_TEST_REQUIRE( init_prod_info["total_votes"].as_double() < current_prod_info["total_votes"].as_double() ); } - + { const asset net_stake = core_sym::from_string("200.5000"); const asset cpu_stake = core_sym::from_string("120.0000"); @@ -3575,18 +3601,6 @@ BOOST_FIXTURE_TEST_CASE( unstake_buy_rex, eosio_system_tester, * boost::unit_tes BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { - auto bancor_convert = [](int64_t S, int64_t R, int64_t T) -> int64_t { return int64_t( double(R * T) / double(S + T) ); }; - auto get_net_limit = [&](account_name a) -> int64_t { - int64_t ram_bytes = 0, net = 0, cpu = 0; - control->get_resource_limits_manager().get_account_limits( a, ram_bytes, net, cpu ); - return net; - }; - auto get_cpu_limit = [&](account_name a) -> int64_t { - int64_t ram_bytes = 0, net = 0, cpu = 0; - control->get_resource_limits_manager().get_account_limits( a, ram_bytes, net, cpu ); - return cpu; - }; - const int64_t ratio = 10000; const asset init_balance = core_sym::from_string("10000.0000"); const asset init_net = core_sym::from_string("70.0000"); @@ -3607,7 +3621,7 @@ BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { const asset init_tot_unlent = rex_pool["total_unlent"].as(); const asset init_tot_lendable = rex_pool["total_lendable"].as(); const asset init_tot_rent = rex_pool["total_rent"].as(); - const int64_t init_stake = get_voter_info(alice)["staked"].as(); + const int64_t init_stake = get_voter_info(alice)["staked"].as(); BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), rex_pool["total_lent"].as() ); BOOST_REQUIRE_EQUAL( ratio * init_tot_lendable.get_amount(), rex_pool["total_rex"].as().get_amount() ); BOOST_REQUIRE_EQUAL( rex_pool["total_rex"].as(), get_rex_balance(alice) ); @@ -3625,7 +3639,7 @@ BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { rex_pool["total_lent"].as().get_amount() ); BOOST_REQUIRE_EQUAL( rex_pool["total_lent"].as() + rex_pool["total_unlent"].as(), rex_pool["total_lendable"].as() ); - + // test that carol's resource limits have been updated properly BOOST_REQUIRE_EQUAL( expected_total_lent, get_cpu_limit( carol ) - init_cpu_limit ); BOOST_REQUIRE_EQUAL( 0, get_net_limit( carol ) - init_net_limit ); @@ -3636,7 +3650,7 @@ BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance(alice) ) ); BOOST_REQUIRE_EQUAL( success(), cancelrexorder( alice ) ); BOOST_REQUIRE_EQUAL( rex_pool["total_rex"].as(), get_rex_balance(alice) ); - + produce_block( fc::days(20) ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance(alice) ) ); BOOST_REQUIRE_EQUAL( success(), cancelrexorder( alice ) ); @@ -3648,7 +3662,7 @@ BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { // test that carol's resource limits have been updated properly when loan expires BOOST_REQUIRE_EQUAL( init_cpu_limit, get_cpu_limit( carol ) ); BOOST_REQUIRE_EQUAL( init_net_limit, get_net_limit( carol ) ); - + rex_pool = get_rex_pool(); BOOST_REQUIRE_EQUAL( 0, rex_pool["total_lendable"].as().get_amount() ); BOOST_REQUIRE_EQUAL( 0, rex_pool["total_unlent"].as().get_amount() ); @@ -3671,9 +3685,68 @@ BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( buy_sell_sell_rex, eosio_system_tester ) try { + + const int64_t ratio = 10000; + const asset init_balance = core_sym::from_string("10000.0000"); + const asset init_net = core_sym::from_string("70.0000"); + const asset init_cpu = core_sym::from_string("90.0000"); + const std::vector accounts = { N(aliceaccount), N(bobbyaccount), N(carolaccount) }; + account_name alice = accounts[0], bob = accounts[1], carol = accounts[2]; + setup_rex_accounts( accounts, init_balance, init_net, init_cpu ); + + const int64_t init_cpu_limit = get_cpu_limit( alice ); + const int64_t init_net_limit = get_net_limit( alice ); + + // alice lends rex + const asset payment = core_sym::from_string("65.0000"); + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, payment ) ); + BOOST_REQUIRE_EQUAL( success(), buyrex( bob, core_sym::from_string("0.0005") ) ); + BOOST_REQUIRE_EQUAL( init_balance - payment, get_rex_fund(alice) ); + auto rex_pool = get_rex_pool(); + const asset init_tot_unlent = rex_pool["total_unlent"].as(); + const asset init_tot_lendable = rex_pool["total_lendable"].as(); + const asset init_tot_rent = rex_pool["total_rent"].as(); + const int64_t init_stake = get_voter_info(alice)["staked"].as(); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), rex_pool["total_lent"].as() ); + BOOST_REQUIRE_EQUAL( ratio * init_tot_lendable.get_amount(), rex_pool["total_rex"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( rex_pool["total_rex"].as(), get_rex_balance(alice) + get_rex_balance(bob) ); + + // bob rents cpu for carol + const asset fee = core_sym::from_string("7.0000"); + BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, carol, fee ) ); + rex_pool = get_rex_pool(); + BOOST_REQUIRE_EQUAL( init_tot_lendable + fee, rex_pool["total_lendable"].as() ); + BOOST_REQUIRE_EQUAL( init_tot_rent + fee, rex_pool["total_rent"].as() ); + + produce_block( fc::days(5) ); + produce_blocks(2); + const asset rex_tok = asset::from_string("1.0000 REX"); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance(alice) - rex_tok ) ); + BOOST_REQUIRE_EQUAL( false, get_rex_order_obj( alice ).is_null() ); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, rex_tok ) ); + BOOST_REQUIRE_EQUAL( sellrex( alice, rex_tok ), wasm_assert_msg("insufficient funds for current and scheduled orders") ); + BOOST_REQUIRE_EQUAL( ratio * payment.get_amount() - rex_tok.get_amount(), get_rex_order( alice )["rex_requested"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( success(), consolidate( alice ) ); + BOOST_REQUIRE_EQUAL( 0, get_rex_balance_obj( alice )["rex_maturities"].get_array().size() ); + + produce_block( fc::days(26) ); + produce_blocks(2); + + BOOST_REQUIRE_EQUAL( success(), rexexec( alice, 2 ) ); + BOOST_REQUIRE_EQUAL( 0, get_rex_balance( alice ).get_amount() ); + BOOST_REQUIRE_EQUAL( 0, get_rex_balance_obj( alice )["matured_rex"].as() ); + const asset init_fund = get_rex_fund( alice ); + BOOST_REQUIRE_EQUAL( success(), updaterex( alice ) ); + BOOST_REQUIRE_EQUAL( 0, get_rex_balance( alice ).get_amount() ); + BOOST_REQUIRE_EQUAL( 0, get_rex_balance_obj( alice )["matured_rex"].as() ); + BOOST_REQUIRE ( init_fund < get_rex_fund( alice ) ); + +} FC_LOG_AND_RETHROW() + + BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { - auto bancor_convert = [](int64_t S, int64_t R, int64_t T) -> int64_t { return int64_t( double(R * T) / double(S + T) ); }; auto within_one = [](int64_t a, int64_t b) -> bool { return std::abs( a - b ) <= 1; }; const asset init_balance = core_sym::from_string("3000000.0000"); @@ -3681,24 +3754,25 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { account_name alice = accounts[0], bob = accounts[1], carol = accounts[2], emily = accounts[3], frank = accounts[4]; setup_rex_accounts( accounts, init_balance ); - const auto purchase1 = core_sym::from_string("880000.0000"); + const auto purchase1 = core_sym::from_string("100000.0000"); const auto purchase2 = core_sym::from_string("471000.0000"); const auto purchase3 = core_sym::from_string("469000.0000"); const auto init_stake = get_voter_info(alice)["staked"].as(); BOOST_REQUIRE_EQUAL( success(), buyrex( alice, purchase1) ); BOOST_REQUIRE_EQUAL( success(), buyrex( bob, purchase2) ); BOOST_REQUIRE_EQUAL( success(), buyrex( carol, purchase3) ); - + BOOST_REQUIRE_EQUAL( init_balance - purchase1, get_rex_fund(alice) ); BOOST_REQUIRE_EQUAL( purchase1.get_amount(), get_voter_info(alice)["staked"].as() - init_stake ); + BOOST_REQUIRE_EQUAL( init_balance - purchase2, get_rex_fund(bob) ); BOOST_REQUIRE_EQUAL( init_balance - purchase3, get_rex_fund(carol) ); - + auto init_alice_rex = get_rex_balance(alice); auto init_bob_rex = get_rex_balance(bob); auto init_carol_rex = get_rex_balance(carol); - BOOST_REQUIRE_EQUAL(core_sym::from_string("100000.0000"), get_rex_pool()["total_rent"].as() ); + BOOST_REQUIRE_EQUAL(core_sym::from_string("20000.0000"), get_rex_pool()["total_rent"].as() ); const asset rent_payment = core_sym::from_string("1100000.0000"); BOOST_REQUIRE_EQUAL( success(), rentcpu( frank, frank, rent_payment, rent_payment ) ); @@ -3706,9 +3780,9 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { const auto init_rex_pool = get_rex_pool(); const int64_t init_alice_rex_stake = ( eosio::chain::uint128_t(init_alice_rex.get_amount()) * init_rex_pool["total_lendable"].as().get_amount() ) / init_rex_pool["total_rex"].as().get_amount(); - + produce_block( fc::days(5) ); - + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( get_rex_balance(alice).get_amount() / 4, symbol(SY(4,REX)) ) ) ); BOOST_TEST_REQUIRE( within_one( 3 * init_alice_rex.get_amount() / 4, get_rex_balance(alice).get_amount() ) ); @@ -3725,7 +3799,7 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( init_bob_rex, get_rex_balance(bob) ); BOOST_REQUIRE_EQUAL( init_carol_rex, get_rex_balance(carol) ); BOOST_REQUIRE_EQUAL( init_alice_rex, get_rex_balance(alice) ); - + // now bob's, carol's and alice's sellrex orders have been queued BOOST_REQUIRE_EQUAL( true, get_rex_order(alice)["is_open"].as() ); BOOST_REQUIRE_EQUAL( init_alice_rex, get_rex_order(alice)["rex_requested"].as() ); @@ -3744,15 +3818,23 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( true, get_rex_order(bob)["is_open"].as() ); BOOST_REQUIRE_EQUAL( true, get_rex_order(carol)["is_open"].as() ); - // wait for 2 more hours, by now frank's loan has expired and there is enough balance in + // wait for 2 more hours, by now frank's loan has expired and there is enough balance in // total_unlent to close some sellrex orders. only two are processed, bob's and carol's // alices's order is still open // an action is needed to trigger queue processing produce_block( fc::hours(2) ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("rex loans are not currently available"), + BOOST_REQUIRE_EQUAL( wasm_assert_msg("rex loans are currently not available"), rentcpu( frank, frank, core_sym::from_string("0.0001") ) ); - - BOOST_REQUIRE_EQUAL( success(), buyrex( frank, core_sym::from_string("0.0001") ) ); + { + auto trace = base_tester::push_action( N(eosio), N(buyrex), frank, + mvo()("from", frank)("amount", core_sym::from_string("0.0001")) ); + auto output = get_rexorder_result( trace ); + BOOST_REQUIRE_EQUAL( output.size(), 2 ); + BOOST_REQUIRE_EQUAL( output[0].first, bob ); + BOOST_REQUIRE_EQUAL( output[0].second, get_rex_order(bob)["proceeds"].as() ); + BOOST_REQUIRE_EQUAL( output[1].first, carol ); + BOOST_REQUIRE_EQUAL( output[1].second, get_rex_order(carol)["proceeds"].as() ); + } BOOST_REQUIRE_EQUAL( true, get_rex_order(alice)["is_open"].as() ); BOOST_REQUIRE_EQUAL( init_alice_rex, get_rex_order(alice)["rex_requested"].as() ); @@ -3765,7 +3847,7 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( false, get_rex_order(carol)["is_open"].as() ); BOOST_REQUIRE_EQUAL( init_carol_rex, get_rex_order(carol)["rex_requested"].as() ); BOOST_REQUIRE ( 0 < get_rex_order(carol)["proceeds"].as().get_amount() ); - + BOOST_REQUIRE_EQUAL( success(), updaterex( bob ) ); BOOST_REQUIRE_EQUAL( 0, get_rex_vote_stake( bob ).get_amount() ); BOOST_REQUIRE_EQUAL( init_stake, get_voter_info( bob )["staked"].as() ); @@ -3773,23 +3855,18 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( 0, get_rex_vote_stake( carol ).get_amount() ); BOOST_REQUIRE_EQUAL( init_stake, get_voter_info( carol )["staked"].as() ); + { + auto trace = base_tester::push_action( N(eosio), N(buyrex), frank, + mvo()("from", frank)("amount", core_sym::from_string("0.0001")) ); + auto output = get_rexorder_result( trace ); + BOOST_REQUIRE_EQUAL( output.size(), 0 ); + } + } FC_LOG_AND_RETHROW() BOOST_FIXTURE_TEST_CASE( rex_loans, eosio_system_tester ) try { - auto bancor_convert = [](int64_t S, int64_t R, int64_t T) -> int64_t { return int64_t( double(R * T) / double(S + T) ); }; - auto get_net_limit = [&](account_name a) -> int64_t { - int64_t ram_bytes = 0, net = 0, cpu = 0; - control->get_resource_limits_manager().get_account_limits( a, ram_bytes, net, cpu ); - return net; - }; - auto get_cpu_limit = [&](account_name a) -> int64_t { - int64_t ram_bytes = 0, net = 0, cpu = 0; - control->get_resource_limits_manager().get_account_limits( a, ram_bytes, net, cpu ); - return cpu; - }; - const int64_t ratio = 10000; const asset init_balance = core_sym::from_string("10000.0000"); const std::vector accounts = { N(aliceaccount), N(bobbyaccount), N(carolaccount), N(emilyaccount), N(frankaccount) }; @@ -3797,7 +3874,7 @@ BOOST_FIXTURE_TEST_CASE( rex_loans, eosio_system_tester ) try { setup_rex_accounts( accounts, init_balance ); BOOST_REQUIRE_EQUAL( success(), buyrex( alice, core_sym::from_string("500.0000") ) ); - + auto rex_pool = get_rex_pool(); const asset payment = core_sym::from_string("30.0000"); const asset zero_asset = core_sym::from_string("0.0000"); @@ -3807,7 +3884,7 @@ BOOST_FIXTURE_TEST_CASE( rex_loans, eosio_system_tester ) try { int64_t expected_stake = bancor_convert( rex_pool["total_rent"].as().get_amount(), rex_pool["total_unlent"].as().get_amount(), payment.get_amount() ); - const int64_t init_stake = get_cpu_limit( frank ); + const int64_t init_stake = get_cpu_limit( frank ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("must use core token"), rentcpu( frank, bob, asset::from_string("10.0000 RND") ) ); @@ -3868,7 +3945,7 @@ BOOST_FIXTURE_TEST_CASE( rex_loans, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( fundcpuloan( frank, 1, amount ), success() ); BOOST_REQUIRE_EQUAL( old_loan_balance + amount, get_cpu_loan(1)["balance"].as() ); cur_frank_balance = get_rex_fund( frank ); - BOOST_REQUIRE_EQUAL( old_frank_balance - amount, cur_frank_balance ); + BOOST_REQUIRE_EQUAL( old_frank_balance - amount, cur_frank_balance ); } // wait for 30 days, frank's loan will be renewed at the current price @@ -3878,14 +3955,14 @@ BOOST_FIXTURE_TEST_CASE( rex_loans, eosio_system_tester ) try { int64_t unlent_tokens = bancor_convert( rex_pool["total_unlent"].as().get_amount(), rex_pool["total_rent"].as().get_amount(), expected_stake ); - - expected_stake = bancor_convert( rex_pool["total_rent"].as().get_amount() - unlent_tokens, + + expected_stake = bancor_convert( rex_pool["total_rent"].as().get_amount() - unlent_tokens, rex_pool["total_unlent"].as().get_amount() + expected_stake, payment.get_amount() ); } - + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset::from_string("1.0000 REX") ) ); - + loan_info = get_cpu_loan(1); BOOST_REQUIRE_EQUAL( payment, loan_info["payment"].as() ); BOOST_REQUIRE_EQUAL( fund - payment, loan_info["balance"].as() ); @@ -3935,7 +4012,7 @@ BOOST_FIXTURE_TEST_CASE( ramfee_namebid_to_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), buyram( bob, carol, core_sym::from_string("70.0000") ) ); BOOST_REQUIRE_EQUAL( cur_ramfee_balance, get_balance( N(eosio.ramfee) ) ); BOOST_REQUIRE_EQUAL( get_balance( N(eosio.rex) ), cur_rex_balance + core_sym::from_string("0.3500") ); - + cur_rex_balance = get_balance( N(eosio.rex) ); auto cur_rex_pool = get_rex_pool(); @@ -3944,7 +4021,7 @@ BOOST_FIXTURE_TEST_CASE( ramfee_namebid_to_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( cur_rex_balance, cur_rex_pool["total_lendable"].as() ); BOOST_REQUIRE_EQUAL( 0, cur_rex_pool["namebid_proceeds"].as().get_amount() ); - // required for closing namebids + // required for closing namebids cross_15_percent_threshold(); produce_block( fc::days(14) ); @@ -3986,12 +4063,12 @@ BOOST_FIXTURE_TEST_CASE( rex_maturity, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), buyrex( alice, core_sym::from_string("18.5000") ) ); produce_block( fc::hours(25) ); BOOST_REQUIRE_EQUAL( success(), buyrex( alice, core_sym::from_string("25.0000") ) ); - + auto rex_balance = get_rex_balance_obj( alice ); BOOST_REQUIRE_EQUAL( 550000 * rex_ratio, rex_balance["rex_balance"].as().get_amount() ); BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); BOOST_REQUIRE_EQUAL( 2, rex_balance["rex_maturities"].get_array().size() ); - + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( alice, asset::from_string("115000.0000 REX") ) ); produce_block( fc::hours( 3*24 + 20) ); @@ -4017,7 +4094,7 @@ BOOST_FIXTURE_TEST_CASE( rex_maturity, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); BOOST_REQUIRE_EQUAL( 0, rex_balance["rex_maturities"].get_array().size() ); } - + { const asset payment1 = core_sym::from_string("14.8000"); const asset payment2 = core_sym::from_string("15.2000"); @@ -4029,22 +4106,22 @@ BOOST_FIXTURE_TEST_CASE( rex_maturity, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), buyrex( bob, payment2 ) ); produce_block( fc::hours(24) ); } - + auto rex_balance = get_rex_balance_obj( bob ); BOOST_REQUIRE_EQUAL( 8 * rex_bucket.get_amount(), rex_balance["rex_balance"].as().get_amount() ); BOOST_REQUIRE_EQUAL( 5, rex_balance["rex_maturities"].get_array().size() ); BOOST_REQUIRE_EQUAL( 3 * rex_bucket.get_amount(), rex_balance["matured_rex"].as() ); - + BOOST_REQUIRE_EQUAL( success(), updaterex( bob ) ); rex_balance = get_rex_balance_obj( bob ); BOOST_REQUIRE_EQUAL( 4, rex_balance["rex_maturities"].get_array().size() ); BOOST_REQUIRE_EQUAL( 4 * rex_bucket.get_amount(), rex_balance["matured_rex"].as() ); - + produce_block( fc::hours(2) ); BOOST_REQUIRE_EQUAL( success(), updaterex( bob ) ); rex_balance = get_rex_balance_obj( bob ); BOOST_REQUIRE_EQUAL( 4, rex_balance["rex_maturities"].get_array().size() ); - + produce_block( fc::hours(1) ); BOOST_REQUIRE_EQUAL( success(), sellrex( bob, asset( 3 * rex_bucket.get_amount(), rex_sym ) ) ); rex_balance = get_rex_balance_obj( bob ); @@ -4064,12 +4141,12 @@ BOOST_FIXTURE_TEST_CASE( rex_maturity, eosio_system_tester ) try { rex_balance = get_rex_balance_obj( bob ); BOOST_REQUIRE_EQUAL( 3, rex_balance["rex_maturities"].get_array().size() ); BOOST_REQUIRE_EQUAL( rex_bucket.get_amount(), rex_balance["matured_rex"].as() ); - + BOOST_REQUIRE_EQUAL( success(), consolidate( bob ) ); rex_balance = get_rex_balance_obj( bob ); BOOST_REQUIRE_EQUAL( 1, rex_balance["rex_maturities"].get_array().size() ); BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); - + produce_block( fc::days(3) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( bob, asset( 4 * rex_bucket.get_amount(), rex_sym ) ) ); @@ -4093,14 +4170,14 @@ BOOST_FIXTURE_TEST_CASE( rex_maturity, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), buyrex( bob, payment2 ) ); produce_block( fc::days(2) ); BOOST_REQUIRE_EQUAL( success(), updaterex( bob ) ); - + auto rex_balance = get_rex_balance_obj( bob ); BOOST_REQUIRE_EQUAL( tot_rex, rex_balance["rex_balance"].as() ); BOOST_REQUIRE_EQUAL( rex_bucket1.get_amount(), rex_balance["matured_rex"].as() ); BOOST_REQUIRE_EQUAL( success(), rentcpu( alice, alice, core_sym::from_string("800000.0000") ) ); BOOST_REQUIRE_EQUAL( success(), sellrex( bob, asset( rex_bucket1.get_amount() - 20, rex_sym ) ) ); rex_balance = get_rex_balance_obj( bob ); - BOOST_REQUIRE_EQUAL( rex_bucket1.get_amount(), get_rex_order( bob )["rex_requested"].as().get_amount() + 20 ); + BOOST_REQUIRE_EQUAL( rex_bucket1.get_amount(), get_rex_order( bob )["rex_requested"].as().get_amount() + 20 ); BOOST_REQUIRE_EQUAL( tot_rex, rex_balance["rex_balance"].as() ); BOOST_REQUIRE_EQUAL( rex_bucket1.get_amount(), rex_balance["matured_rex"].as() ); BOOST_REQUIRE_EQUAL( success(), consolidate( bob ) ); @@ -4115,6 +4192,223 @@ BOOST_FIXTURE_TEST_CASE( rex_maturity, eosio_system_tester ) try { } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( rex_savings, eosio_system_tester ) try { + + const asset init_balance = core_sym::from_string("100000.0000"); + const std::vector accounts = { N(aliceaccount), N(bobbyaccount), N(carolaccount), N(emilyaccount), N(frankaccount) }; + account_name alice = accounts[0], bob = accounts[1], carol = accounts[2], emily = accounts[3], frank = accounts[4]; + setup_rex_accounts( accounts, init_balance ); + + const int64_t rex_ratio = 10000; + const symbol rex_sym( SY(4, REX) ); + + { + const asset payment1 = core_sym::from_string("14.8000"); + const asset payment2 = core_sym::from_string("15.2000"); + const asset payment = payment1 + payment2; + const asset rex_bucket( rex_ratio * payment.get_amount(), rex_sym ); + for ( uint8_t i = 0; i < 8; ++i ) { + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, payment1 ) ); + produce_block( fc::hours(12) ); + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, payment2 ) ); + produce_block( fc::hours(14) ); + } + + auto rex_balance = get_rex_balance_obj( alice ); + BOOST_REQUIRE_EQUAL( 8 * rex_bucket.get_amount(), rex_balance["rex_balance"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( 5, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 4 * rex_bucket.get_amount(), rex_balance["matured_rex"].as() ); + + BOOST_REQUIRE_EQUAL( success(), mvtosavings( alice, asset( 8 * rex_bucket.get_amount(), rex_sym ) ) ); + rex_balance = get_rex_balance_obj( alice ); + BOOST_REQUIRE_EQUAL( 1, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); + produce_block( fc::days(1000) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( alice, asset::from_string( "1.0000 REX" ) ) ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( alice, asset::from_string( "10.0000 REX" ) ) ); + rex_balance = get_rex_balance_obj( alice ); + BOOST_REQUIRE_EQUAL( 2, rex_balance["rex_maturities"].get_array().size() ); + produce_block( fc::days(3) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( alice, asset::from_string( "1.0000 REX" ) ) ); + produce_blocks( 2 ); + produce_block( fc::days(2) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( alice, asset::from_string( "10.0001 REX" ) ) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset::from_string( "10.0000 REX" ) ) ); + rex_balance = get_rex_balance_obj( alice ); + BOOST_REQUIRE_EQUAL( 1, rex_balance["rex_maturities"].get_array().size() ); + produce_block( fc::days(100) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( alice, asset::from_string( "0.0001 REX" ) ) ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( alice, get_rex_balance( alice ) ) ); + produce_block( fc::days(5) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance( alice ) ) ); + } + + { + const asset payment = core_sym::from_string("20.0000"); + const asset rex_bucket( rex_ratio * payment.get_amount(), rex_sym ); + for ( uint8_t i = 0; i < 5; ++i ) { + produce_block( fc::hours(24) ); + BOOST_REQUIRE_EQUAL( success(), buyrex( bob, payment ) ); + } + + auto rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 5 * rex_bucket.get_amount(), rex_balance["rex_balance"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( 5, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); + BOOST_REQUIRE_EQUAL( success(), mvtosavings( bob, asset( rex_bucket.get_amount() / 2, rex_sym ) ) ); + rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 6, rex_balance["rex_maturities"].get_array().size() ); + + BOOST_REQUIRE_EQUAL( success(), mvtosavings( bob, asset( rex_bucket.get_amount() / 2, rex_sym ) ) ); + rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 5, rex_balance["rex_maturities"].get_array().size() ); + produce_block( fc::days(1) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( bob, rex_bucket ) ); + rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 4, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); + BOOST_REQUIRE_EQUAL( 4 * rex_bucket.get_amount(), rex_balance["rex_balance"].as().get_amount() ); + + BOOST_REQUIRE_EQUAL( success(), mvtosavings( bob, asset( 3 * rex_bucket.get_amount() / 2, rex_sym ) ) ); + rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 3, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( bob, rex_bucket ) ); + + produce_block( fc::days(1) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( bob, rex_bucket ) ); + rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 2, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); + BOOST_REQUIRE_EQUAL( 3 * rex_bucket.get_amount(), rex_balance["rex_balance"].as().get_amount() ); + + produce_block( fc::days(1) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( bob, rex_bucket ) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( bob, asset( rex_bucket.get_amount() / 2, rex_sym ) ) ); + rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 1, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); + BOOST_REQUIRE_EQUAL( 5 * rex_bucket.get_amount(), 2 * rex_balance["rex_balance"].as().get_amount() ); + + produce_block( fc::days(20) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( bob, rex_bucket ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient REX in savings"), + mvfrsavings( bob, asset( 3 * rex_bucket.get_amount(), rex_sym ) ) ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( bob, rex_bucket ) ); + BOOST_REQUIRE_EQUAL( 2, get_rex_balance_obj( bob )["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient REX balance"), + mvtosavings( bob, asset( 3 * rex_bucket.get_amount() / 2, rex_sym ) ) ); + produce_block( fc::days(1) ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( bob, rex_bucket ) ); + BOOST_REQUIRE_EQUAL( 3, get_rex_balance_obj( bob )["rex_maturities"].get_array().size() ); + produce_block( fc::days(4) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( bob, rex_bucket ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( bob, rex_bucket ) ); + produce_block( fc::days(1) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( bob, rex_bucket ) ); + rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 1, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( rex_bucket.get_amount() / 2, rex_balance["rex_balance"].as().get_amount() ); + + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( bob, asset( rex_bucket.get_amount() / 4, rex_sym ) ) ); + produce_block( fc::days(2) ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( bob, asset( rex_bucket.get_amount() / 8, rex_sym ) ) ); + BOOST_REQUIRE_EQUAL( 3, get_rex_balance_obj( bob )["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( success(), consolidate( bob ) ); + BOOST_REQUIRE_EQUAL( 2, get_rex_balance_obj( bob )["rex_maturities"].get_array().size() ); + + produce_block( fc::days(5) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), + sellrex( bob, asset( rex_bucket.get_amount() / 2, rex_sym ) ) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( bob, asset( 3 * rex_bucket.get_amount() / 8, rex_sym ) ) ); + rex_balance = get_rex_balance_obj( bob ); + BOOST_REQUIRE_EQUAL( 1, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); + BOOST_REQUIRE_EQUAL( rex_bucket.get_amount() / 8, rex_balance["rex_balance"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( bob, get_rex_balance( bob ) ) ); + produce_block( fc::days(5) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( bob, get_rex_balance( bob ) ) ); + } + + { + const asset payment = core_sym::from_string("20000.0000"); + const int64_t rex_bucket_amount = rex_ratio * payment.get_amount(); + const asset rex_bucket( rex_bucket_amount, rex_sym ); + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, payment ) ); + BOOST_REQUIRE_EQUAL( rex_bucket, get_rex_balance( alice ) ); + BOOST_REQUIRE_EQUAL( rex_bucket, get_rex_pool()["total_rex"].as() ); + + produce_block( fc::days(5) ); + + BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, bob, core_sym::from_string("2000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( 9 * rex_bucket_amount / 10, rex_sym ) ) ); + BOOST_REQUIRE_EQUAL( rex_bucket, get_rex_balance( alice ) ); + BOOST_REQUIRE_EQUAL( success(), mvtosavings( alice, asset( rex_bucket_amount / 10, rex_sym ) ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient REX balance"), + mvtosavings( alice, asset( rex_bucket_amount / 10, rex_sym ) ) ); + BOOST_REQUIRE_EQUAL( success(), cancelrexorder( alice ) ); + BOOST_REQUIRE_EQUAL( success(), mvtosavings( alice, asset( rex_bucket_amount / 10, rex_sym ) ) ); + auto rb = get_rex_balance_obj( alice ); + BOOST_REQUIRE_EQUAL( rb["matured_rex"].as(), 8 * rex_bucket_amount / 10 ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( alice, asset( 2 * rex_bucket_amount / 10, rex_sym ) ) ); + produce_block( fc::days(31) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance( alice ) ) ); + } + + { + const asset payment = core_sym::from_string("250.0000"); + const asset half_payment = core_sym::from_string("125.0000"); + const int64_t rex_bucket_amount = rex_ratio * payment.get_amount(); + const int64_t half_rex_bucket_amount = rex_bucket_amount / 2; + const asset rex_bucket( rex_bucket_amount, rex_sym ); + const asset half_rex_bucket( half_rex_bucket_amount, rex_sym ); + + BOOST_REQUIRE_EQUAL( success(), buyrex( carol, payment ) ); + BOOST_REQUIRE_EQUAL( rex_bucket, get_rex_balance( carol ) ); + auto rex_balance = get_rex_balance_obj( carol ); + + BOOST_REQUIRE_EQUAL( 1, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); + produce_block( fc::days(1) ); + BOOST_REQUIRE_EQUAL( success(), buyrex( carol, payment ) ); + rex_balance = get_rex_balance_obj( carol ); + BOOST_REQUIRE_EQUAL( 2, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, rex_balance["matured_rex"].as() ); + + BOOST_REQUIRE_EQUAL( success(), mvtosavings( carol, half_rex_bucket ) ); + rex_balance = get_rex_balance_obj( carol ); + BOOST_REQUIRE_EQUAL( 3, rex_balance["rex_maturities"].get_array().size() ); + + BOOST_REQUIRE_EQUAL( success(), buyrex( carol, half_payment ) ); + rex_balance = get_rex_balance_obj( carol ); + BOOST_REQUIRE_EQUAL( 3, rex_balance["rex_maturities"].get_array().size() ); + + produce_block( fc::days(5) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("asset must be a positive amount of (REX, 4)"), + mvfrsavings( carol, asset::from_string("0.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("asset must be a positive amount of (REX, 4)"), + mvfrsavings( carol, asset::from_string("1.0000 RND") ) ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( carol, half_rex_bucket ) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient REX in savings"), + mvfrsavings( carol, asset::from_string("0.0001 REX") ) ); + rex_balance = get_rex_balance_obj( carol ); + BOOST_REQUIRE_EQUAL( 1, rex_balance["rex_maturities"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 5 * half_rex_bucket_amount, rex_balance["rex_balance"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( 2 * rex_bucket_amount, rex_balance["matured_rex"].as() ); + produce_block( fc::days(5) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( carol, get_rex_balance( carol) ) ); + } + +} FC_LOG_AND_RETHROW() + + BOOST_FIXTURE_TEST_CASE( update_rex, eosio_system_tester, * boost::unit_test::tolerance(1e-10) ) try { const asset init_balance = core_sym::from_string("10000.0000"); @@ -4154,7 +4448,7 @@ BOOST_FIXTURE_TEST_CASE( update_rex, eosio_system_tester, * boost::unit_test::to BOOST_REQUIRE_EQUAL( success(), vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); - BOOST_TEST_REQUIRE( stake2votes( asset( get_voter_info( alice )["staked"].as(), symbol{CORE_SYM} ) ) + BOOST_TEST_REQUIRE( stake2votes( asset( get_voter_info( alice )["staked"].as(), symbol{CORE_SYM} ) ) == get_producer_info(producer_names[0])["total_votes"].as() ); BOOST_TEST_REQUIRE( stake2votes( asset( get_voter_info( alice )["staked"].as(), symbol{CORE_SYM} ) ) == get_producer_info(producer_names[20])["total_votes"].as() ); @@ -4190,6 +4484,77 @@ BOOST_FIXTURE_TEST_CASE( update_rex, eosio_system_tester, * boost::unit_test::to } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( update_rex_vote, eosio_system_tester, * boost::unit_test::tolerance(1e-10) ) try { + + cross_15_percent_threshold(); + + // create accounts {defproducera, defproducerb, ..., defproducerz} and register as producers + std::vector producer_names; + { + producer_names.reserve('z' - 'a' + 1); + const std::string root("defproducer"); + for ( char c = 'a'; c <= 'z'; ++c ) { + producer_names.emplace_back(root + std::string(1, c)); + } + + setup_producer_accounts(producer_names); + for ( const auto& p: producer_names ) { + BOOST_REQUIRE_EQUAL( success(), regproducer(p) ); + BOOST_TEST_REQUIRE( 0 == get_producer_info(p)["total_votes"].as() ); + } + } + + const asset init_balance = core_sym::from_string("10000.0000"); + const std::vector accounts = { N(aliceaccount), N(bobbyaccount), N(carolaccount), N(emilyaccount), N(frankaccount) }; + account_name alice = accounts[0], bob = accounts[1], carol = accounts[2], emily = accounts[3], frank = accounts[4]; + setup_rex_accounts( accounts, init_balance ); + + const int64_t init_stake_amount = get_voter_info( alice )["staked"].as(); + const asset init_stake( init_stake_amount, symbol{CORE_SYM} ); + + const asset purchase = core_sym::from_string("250.0000"); + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, core_sym::from_string("250.0000") ) ); + BOOST_REQUIRE_EQUAL( purchase, get_rex_pool()["total_lendable"].as() ); + BOOST_REQUIRE_EQUAL( purchase, get_rex_vote_stake(alice) ); + BOOST_REQUIRE_EQUAL( get_rex_vote_stake(alice).get_amount(), get_voter_info(alice)["staked"].as() - init_stake_amount ); + BOOST_REQUIRE_EQUAL( purchase, get_rex_pool()["total_lendable"].as() ); + + BOOST_REQUIRE_EQUAL( success(), + vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); + BOOST_REQUIRE_EQUAL( purchase, get_rex_vote_stake(alice) ); + BOOST_REQUIRE_EQUAL( purchase.get_amount(), get_voter_info(alice)["staked"].as() - init_stake_amount ); + + const auto init_rex_pool = get_rex_pool(); + const asset rent = core_sym::from_string("25.0000"); + BOOST_REQUIRE_EQUAL( success(), rentcpu( frank, bob, rent ) ); + const auto curr_rex_pool = get_rex_pool(); + BOOST_REQUIRE_EQUAL( curr_rex_pool["total_lendable"].as(), init_rex_pool["total_lendable"].as() + rent ); + BOOST_REQUIRE_EQUAL( success(), + vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); + BOOST_REQUIRE_EQUAL( (purchase + rent).get_amount(), get_voter_info(alice)["staked"].as() - init_stake_amount ); + BOOST_REQUIRE_EQUAL( purchase + rent, get_rex_vote_stake(alice) ); + BOOST_TEST_REQUIRE ( stake2votes(purchase + rent + init_stake) == + get_producer_info(producer_names[0])["total_votes"].as_double() ); + BOOST_TEST_REQUIRE ( stake2votes(purchase + rent + init_stake) == + get_producer_info(producer_names[20])["total_votes"].as_double() ); + + const asset to_net_stake = core_sym::from_string("60.0000"); + const asset to_cpu_stake = core_sym::from_string("40.0000"); + transfer( config::system_account_name, alice, to_net_stake + to_cpu_stake, config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), rentcpu( frank, bob, rent ) ); + BOOST_REQUIRE_EQUAL( success(), stake( alice, alice, to_net_stake, to_cpu_stake ) ); + BOOST_REQUIRE_EQUAL( purchase + rent + rent, get_rex_vote_stake(alice) ); + BOOST_TEST_REQUIRE ( stake2votes(init_stake + purchase + rent + rent + to_net_stake + to_cpu_stake) == + get_producer_info(producer_names[0])["total_votes"].as_double() ); + BOOST_REQUIRE_EQUAL( success(), rentcpu( frank, bob, rent ) ); + BOOST_REQUIRE_EQUAL( success(), unstake( alice, alice, to_net_stake, to_cpu_stake ) ); + BOOST_REQUIRE_EQUAL( purchase + rent + rent + rent, get_rex_vote_stake(alice) ); + BOOST_TEST_REQUIRE ( stake2votes(init_stake + purchase + rent + rent + rent) == + get_producer_info(producer_names[0])["total_votes"].as_double() ); + +} FC_LOG_AND_RETHROW() + + BOOST_FIXTURE_TEST_CASE( deposit_rex_fund, eosio_system_tester ) try { const asset init_balance = core_sym::from_string("1000.0000"); @@ -4203,7 +4568,7 @@ BOOST_FIXTURE_TEST_CASE( deposit_rex_fund, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( wasm_assert_msg("must deposit to REX fund first"), withdraw( alice, core_sym::from_string("0.0001") ) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("overdrawn balance"), deposit( alice, init_balance + init_balance ) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("must deposit core token"), deposit( alice, asset::from_string("1.0000 RNDM") ) ); - + asset deposit_quant( init_balance.get_amount() / 5, init_balance.get_symbol() ); BOOST_REQUIRE_EQUAL( success(), deposit( alice, deposit_quant ) ); BOOST_REQUIRE_EQUAL( get_balance( alice ), init_balance - deposit_quant ); @@ -4233,18 +4598,18 @@ BOOST_FIXTURE_TEST_CASE( rex_lower_bound, eosio_system_tester ) try { const asset payment = core_sym::from_string("50.0000"); BOOST_REQUIRE_EQUAL( success(), buyrex( alice, payment ) ); BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, bob, payment ) ); - + const auto rex_pool = get_rex_pool(); const int64_t tot_rex = rex_pool["total_rex"].as().get_amount(); const int64_t tot_unlent = rex_pool["total_unlent"].as().get_amount(); const int64_t tot_lent = rex_pool["total_lent"].as().get_amount(); const int64_t tot_lendable = rex_pool["total_lendable"].as().get_amount(); double rex_per_eos = double(tot_rex) / double(tot_lendable); - int64_t sell_amount = rex_per_eos * ( tot_unlent - 1.9 * tot_lent / 10. ); + int64_t sell_amount = rex_per_eos * ( tot_unlent - 0.99 * tot_lent ); produce_block( fc::days(5) ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( sell_amount, rex_sym ) ) ); BOOST_REQUIRE_EQUAL( success(), cancelrexorder( alice ) ); - sell_amount = rex_per_eos * ( tot_unlent - 2. * tot_lent / 10. ); + sell_amount = rex_per_eos * ( tot_unlent - tot_lent ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( sell_amount, rex_sym ) ) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("no sellrex order is scheduled"), cancelrexorder( alice ) ); @@ -4284,7 +4649,7 @@ BOOST_FIXTURE_TEST_CASE( close_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), deposit( bob, init_balance ) ); BOOST_REQUIRE_EQUAL( success(), buyrex( bob, init_balance ) ); - + BOOST_REQUIRE_EQUAL( success(), rentcpu( carol, emily, init_balance ) ); produce_block( fc::days(20) ); @@ -4296,7 +4661,7 @@ BOOST_FIXTURE_TEST_CASE( close_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), closerex( carol ) ); BOOST_REQUIRE_EQUAL( true, get_rex_balance_obj( carol ).is_null() ); BOOST_REQUIRE_EQUAL( true, get_rex_fund_obj( carol ).is_null() ); - + BOOST_REQUIRE_EQUAL( success(), rentnet( emily, emily, init_balance ) ); BOOST_REQUIRE_EQUAL( success(), closerex( emily ) ); @@ -4314,35 +4679,36 @@ BOOST_FIXTURE_TEST_CASE( close_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( 0, get_rex_pool()["total_rex"].as().get_amount() ); BOOST_REQUIRE_EQUAL( 0, get_rex_pool()["total_lendable"].as().get_amount() ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("rex loans are not currently available"), + BOOST_REQUIRE_EQUAL( wasm_assert_msg("rex loans are currently not available"), rentcpu( frank, frank, core_sym::from_string("1.0000") ) ); } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( setabi_bios, TESTER ) try { - abi_serializer abi_ser(fc::json::from_string( (const char*)contracts::system_abi().data()).template as(), abi_serializer_max_time); - set_code( config::system_account_name, contracts::bios_wasm() ); - set_abi( config::system_account_name, contracts::bios_abi().data() ); - create_account(N(eosio.token)); - set_abi( N(eosio.token), contracts::token_abi().data() ); +BOOST_AUTO_TEST_CASE( setabi_bios ) try { + validating_tester t( validating_tester::default_config() ); + abi_serializer abi_ser(fc::json::from_string( (const char*)contracts::bios_abi().data()).template as(), base_tester::abi_serializer_max_time); + t.set_code( config::system_account_name, contracts::bios_wasm() ); + t.set_abi( config::system_account_name, contracts::bios_abi().data() ); + t.create_account(N(eosio.token)); + t.set_abi( N(eosio.token), contracts::token_abi().data() ); { - auto res = get_row_by_account( config::system_account_name, config::system_account_name, N(abihash), N(eosio.token) ); + auto res = t.get_row_by_account( config::system_account_name, config::system_account_name, N(abihash), N(eosio.token) ); _abi_hash abi_hash; - auto abi_hash_var = abi_ser.binary_to_variant( "abi_hash", res, abi_serializer_max_time ); - abi_serializer::from_variant( abi_hash_var, abi_hash, get_resolver(), abi_serializer_max_time); + auto abi_hash_var = abi_ser.binary_to_variant( "abi_hash", res, base_tester::abi_serializer_max_time ); + abi_serializer::from_variant( abi_hash_var, abi_hash, t.get_resolver(), base_tester::abi_serializer_max_time); auto abi = fc::raw::pack(fc::json::from_string( (const char*)contracts::token_abi().data()).template as()); auto result = fc::sha256::hash( (const char*)abi.data(), abi.size() ); BOOST_REQUIRE( abi_hash.hash == result ); } - set_abi( N(eosio.token), contracts::system_abi().data() ); + t.set_abi( N(eosio.token), contracts::system_abi().data() ); { - auto res = get_row_by_account( config::system_account_name, config::system_account_name, N(abihash), N(eosio.token) ); + auto res = t.get_row_by_account( config::system_account_name, config::system_account_name, N(abihash), N(eosio.token) ); _abi_hash abi_hash; - auto abi_hash_var = abi_ser.binary_to_variant( "abi_hash", res, abi_serializer_max_time ); - abi_serializer::from_variant( abi_hash_var, abi_hash, get_resolver(), abi_serializer_max_time); + auto abi_hash_var = abi_ser.binary_to_variant( "abi_hash", res, base_tester::abi_serializer_max_time ); + abi_serializer::from_variant( abi_hash_var, abi_hash, t.get_resolver(), base_tester::abi_serializer_max_time); auto abi = fc::raw::pack(fc::json::from_string( (const char*)contracts::system_abi().data()).template as()); auto result = fc::sha256::hash( (const char*)abi.data(), abi.size() ); @@ -4377,4 +4743,143 @@ BOOST_FIXTURE_TEST_CASE( setabi, eosio_system_tester ) try { } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( change_limited_account_back_to_unlimited, eosio_system_tester ) try { + BOOST_REQUIRE( get_total_stake( "eosio" ).is_null() ); + + transfer( N(eosio), N(alice1111111), core_sym::from_string("1.0000") ); + + auto error_msg = stake( N(alice1111111), N(eosio), core_sym::from_string("0.0000"), core_sym::from_string("1.0000") ); + auto semicolon_pos = error_msg.find(';'); + + BOOST_REQUIRE_EQUAL( error("account eosio has insufficient ram"), + error_msg.substr(0, semicolon_pos) ); + + int64_t ram_bytes_needed = 0; + { + std::istringstream s( error_msg ); + s.seekg( semicolon_pos + 7, std::ios_base::beg ); + s >> ram_bytes_needed; + ram_bytes_needed += 256; // enough room to cover total_resources_table + } + + push_action( N(eosio), N(setalimits), mvo() + ("account", "eosio") + ("ram_bytes", ram_bytes_needed) + ("net_weight", -1) + ("cpu_weight", -1) + ); + + stake( N(alice1111111), N(eosio), core_sym::from_string("0.0000"), core_sym::from_string("1.0000") ); + + REQUIRE_MATCHING_OBJECT( get_total_stake( "eosio" ), mvo() + ("owner", "eosio") + ("net_weight", core_sym::from_string("0.0000")) + ("cpu_weight", core_sym::from_string("1.0000")) + ("ram_bytes", 0) + ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "only supports unlimited accounts" ), + push_action( N(eosio), N(setalimits), mvo() + ("account", "eosio") + ("ram_bytes", ram_bytes_needed) + ("net_weight", -1) + ("cpu_weight", -1) + ) + ); + + BOOST_REQUIRE_EQUAL( error( "transaction net usage is too high: 128 > 0" ), + push_action( N(eosio), N(setalimits), mvo() + ("account", "eosio.saving") + ("ram_bytes", -1) + ("net_weight", -1) + ("cpu_weight", -1) + ) + ); + + BOOST_REQUIRE_EQUAL( success(), + push_action( N(eosio), N(setacctnet), mvo() + ("account", "eosio") + ("net_weight", -1) + ) + ); + + BOOST_REQUIRE_EQUAL( success(), + push_action( N(eosio), N(setacctcpu), mvo() + ("account", "eosio") + ("cpu_weight", -1) + + ) + ); + + BOOST_REQUIRE_EQUAL( success(), + push_action( N(eosio), N(setalimits), mvo() + ("account", "eosio.saving") + ("ram_bytes", ram_bytes_needed) + ("net_weight", -1) + ("cpu_weight", -1) + ) + ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( buy_pin_sell_ram, eosio_system_tester ) try { + BOOST_REQUIRE( get_total_stake( "eosio" ).is_null() ); + + transfer( N(eosio), N(alice1111111), core_sym::from_string("1020.0000") ); + + auto error_msg = stake( N(alice1111111), N(eosio), core_sym::from_string("10.0000"), core_sym::from_string("10.0000") ); + auto semicolon_pos = error_msg.find(';'); + + BOOST_REQUIRE_EQUAL( error("account eosio has insufficient ram"), + error_msg.substr(0, semicolon_pos) ); + + int64_t ram_bytes_needed = 0; + { + std::istringstream s( error_msg ); + s.seekg( semicolon_pos + 7, std::ios_base::beg ); + s >> ram_bytes_needed; + ram_bytes_needed += ram_bytes_needed/10; // enough buffer to make up for buyrambytes estimation errors + } + + auto alice_original_balance = get_balance( N(alice1111111) ); + + BOOST_REQUIRE_EQUAL( success(), buyrambytes( N(alice1111111), N(eosio), static_cast(ram_bytes_needed) ) ); + + auto tokens_paid_for_ram = alice_original_balance - get_balance( N(alice1111111) ); + + auto total_res = get_total_stake( "eosio" ); + + REQUIRE_MATCHING_OBJECT( total_res, mvo() + ("owner", "eosio") + ("net_weight", core_sym::from_string("0.0000")) + ("cpu_weight", core_sym::from_string("0.0000")) + ("ram_bytes", total_res["ram_bytes"].as_int64() ) + ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg( "only supports unlimited accounts" ), + push_action( N(eosio), N(setalimits), mvo() + ("account", "eosio") + ("ram_bytes", ram_bytes_needed) + ("net_weight", -1) + ("cpu_weight", -1) + ) + ); + + BOOST_REQUIRE_EQUAL( success(), + push_action( N(eosio), N(setacctram), mvo() + ("account", "eosio") + ("ram_bytes", total_res["ram_bytes"].as_int64() ) + ) + ); + + auto eosio_original_balance = get_balance( N(eosio) ); + + BOOST_REQUIRE_EQUAL( success(), sellram( N(eosio), total_res["ram_bytes"].as_int64() ) ); + + auto tokens_received_by_selling_ram = get_balance( N(eosio) ) - eosio_original_balance; + + BOOST_REQUIRE( double(tokens_paid_for_ram.get_amount() - tokens_received_by_selling_ram.get_amount()) / tokens_paid_for_ram.get_amount() < 0.01 ); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END()