Skip to content
This repository has been archived by the owner on Aug 30, 2022. It is now read-only.

Distribute REX returns over time to REX owners #412

Merged
merged 23 commits into from
Dec 13, 2019
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 61 additions & 16 deletions contracts/eosio.system/include/eosio.system/eosio.system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,10 @@ namespace eosiosystem {

static constexpr uint32_t seconds_per_year = 52 * 7 * 24 * 3600;
static constexpr uint32_t seconds_per_day = 24 * 3600;
static constexpr uint32_t seconds_per_hour = 3600;
static constexpr int64_t useconds_per_year = int64_t(seconds_per_year) * 1000'000ll;
static constexpr int64_t useconds_per_day = int64_t(seconds_per_day) * 1000'000ll;
static constexpr int64_t useconds_per_hour = int64_t(seconds_per_hour) * 1000'000ll;
static constexpr uint32_t blocks_per_day = 2 * seconds_per_day; // half seconds per day

static constexpr int64_t min_activated_stake = 150'000'000'0000;
Expand Down Expand Up @@ -333,6 +335,45 @@ namespace eosiosystem {

typedef eosio::multi_index< "rexpool"_n, rex_pool > rex_pool_table;

// `rex_return_pool` structure underlying the rex return pool table. A rex return pool table entry is defined by:
// - `version` defaulted to zero,
// - `last_dist_time` the last time proceeds from renting, ram fees, and name bids were added to the rex pool,
// - `pending_bucket_time` timestamp of the pending 12-hour return bucket,
// - `oldest_bucket_time` cached timestamp of the oldest 12-hour return bucket,
// - `pending_bucket_proceeds` proceeds in the pending 12-hour return bucket,
// - `current_rate_of_increase` the current rate per dist_interval at which proceeds are added to the rex pool,
// - `proceeds` the maximum amount of proceeds that can be added to the rex pool at any given time
struct [[eosio::table,eosio::contract("eosio.system")]] rex_return_pool {
uint8_t version = 0;
time_point_sec last_dist_time;
time_point_sec pending_bucket_time = time_point_sec::maximum();
time_point_sec oldest_bucket_time = time_point_sec::min();
int64_t pending_bucket_proceeds = 0;
int64_t current_rate_of_increase = 0;
int64_t proceeds = 0;

static constexpr uint32_t total_intervals = 30 * 144; // 30 days
static constexpr uint32_t dist_interval = 10 * 60; // 10 minutes
static constexpr uint8_t hours_per_bucket = 12;
static_assert( total_intervals * dist_interval == 30 * seconds_per_day );

uint64_t primary_key()const { return 0; }
};

typedef eosio::multi_index< "rexretpool"_n, rex_return_pool > rex_return_pool_table;

// `rex_return_buckets` structure underlying the rex return buckets table. A rex return buckets table is defined by:
// - `version` defaulted to zero,
// - `return_buckets` buckets of proceeds accumulated in 12-hour intervals
struct [[eosio::table,eosio::contract("eosio.system")]] rex_return_buckets {
uint8_t version = 0;
std::map<time_point_sec, int64_t> return_buckets;

uint64_t primary_key()const { return 0; }
};

typedef eosio::multi_index< "retbuckets"_n, rex_return_buckets > rex_return_buckets_table;

// `rex_fund` structure underlying the rex fund table. A rex fund table entry is defined by:
// - `version` defaulted to zero,
// - `owner` the owner of the rex fund,
Expand Down Expand Up @@ -430,22 +471,24 @@ namespace eosiosystem {
class [[eosio::contract("eosio.system")]] system_contract : public native {

private:
voters_table _voters;
producers_table _producers;
producers_table2 _producers2;
global_state_singleton _global;
global_state2_singleton _global2;
global_state3_singleton _global3;
global_state4_singleton _global4;
eosio_global_state _gstate;
eosio_global_state2 _gstate2;
eosio_global_state3 _gstate3;
eosio_global_state4 _gstate4;
rammarket _rammarket;
rex_pool_table _rexpool;
rex_fund_table _rexfunds;
rex_balance_table _rexbalance;
rex_order_table _rexorders;
voters_table _voters;
producers_table _producers;
producers_table2 _producers2;
global_state_singleton _global;
global_state2_singleton _global2;
global_state3_singleton _global3;
global_state4_singleton _global4;
eosio_global_state _gstate;
eosio_global_state2 _gstate2;
eosio_global_state3 _gstate3;
eosio_global_state4 _gstate4;
rammarket _rammarket;
rex_pool_table _rexpool;
rex_return_pool_table _rexretpool;
rex_return_buckets_table _rexretbuckets;
rex_fund_table _rexfunds;
rex_balance_table _rexbalance;
rex_order_table _rexorders;

public:
static constexpr eosio::name active_permission{"active"_n};
Expand Down Expand Up @@ -1121,6 +1164,7 @@ namespace eosiosystem {

// defined in rex.cpp
void runrex( uint16_t max );
void update_rex_pool();
void update_resource_limits( const name& from, const name& receiver, int64_t delta_net, int64_t delta_cpu );
void check_voting_requirement( const name& owner,
const char* error_msg = "must vote for at least 21 producers or for a proxy before buying REX" )const;
Expand All @@ -1142,6 +1186,7 @@ namespace eosiosystem {
static time_point_sec get_rex_maturity();
asset add_to_rex_balance( const name& owner, const asset& payment, const asset& rex_received );
asset add_to_rex_pool( const asset& payment );
void add_to_rex_return_pool( const asset& fee );
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 );
Expand Down
3 changes: 2 additions & 1 deletion contracts/eosio.system/src/eosio.system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ namespace eosiosystem {
_global4(get_self(), get_self().value),
_rammarket(get_self(), get_self().value),
_rexpool(get_self(), get_self().value),
_rexretpool(get_self(), get_self().value),
_rexretbuckets(get_self(), get_self().value),
_rexfunds(get_self(), get_self().value),
_rexbalance(get_self(), get_self().value),
_rexorders(get_self(), get_self().value)
{
//print( "construct system\n" );
_gstate = _global.exists() ? _global.get() : get_default_parameters();
_gstate2 = _global2.exists() ? _global2.get() : eosio_global_state2{};
_gstate3 = _global3.exists() ? _global3.get() : eosio_global_state3{};
Expand Down
152 changes: 144 additions & 8 deletions contracts/eosio.system/src/rex.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ namespace eosiosystem {

using eosio::current_time_point;
using eosio::token;
using eosio::seconds;

void system_contract::deposit( const name& owner, const asset& amount )
{
Expand Down Expand Up @@ -452,15 +453,13 @@ namespace eosiosystem {
*/
void system_contract::add_loan_to_rex_pool( const asset& payment, int64_t rented_tokens, bool new_loan )
{
add_to_rex_return_pool( payment );
_rexpool.modify( _rexpool.begin(), same_payer, [&]( auto& rt ) {
// add payment to total_rent
rt.total_rent.amount += payment.amount;
// move rented_tokens from total_unlent to total_lent
rt.total_unlent.amount -= rented_tokens;
rt.total_lent.amount += rented_tokens;
// add payment to total_unlent
rt.total_unlent.amount += payment.amount;
rt.total_lendable.amount = rt.total_unlent.amount + rt.total_lent.amount;
// increment loan_num if a new loan is being created
if ( new_loan ) {
rt.loan_num++;
Expand Down Expand Up @@ -513,6 +512,8 @@ namespace eosiosystem {
{
check( rex_system_initialized(), "rex system not initialized yet" );

update_rex_pool();

const auto& pool = _rexpool.begin();

auto process_expired_loan = [&]( auto& idx, const auto& itr ) -> std::pair<bool, int64_t> {
Expand Down Expand Up @@ -616,6 +617,108 @@ namespace eosiosystem {

}

/**
* @brief Adds returns from the REX return pool to the REX pool
*/
void system_contract::update_rex_pool()
{
auto get_elapsed_intervals = [&]( const time_point_sec& t1, const time_point_sec& t0 ) -> uint32_t {
return ( t1.sec_since_epoch() - t0.sec_since_epoch() ) / rex_return_pool::dist_interval;
};

const time_point_sec ct = current_time_point();
const uint32_t cts = ct.sec_since_epoch();
const time_point_sec effective_time{cts - cts % rex_return_pool::dist_interval};

const auto ret_pool_elem = _rexretpool.begin();
const auto ret_buckets_elem = _rexretbuckets.begin();

if ( ret_pool_elem == _rexretpool.end() || effective_time <= ret_pool_elem->last_dist_time ) {
return;
}

const int64_t current_rate = ret_pool_elem->current_rate_of_increase;
const uint32_t elapsed_intervals = get_elapsed_intervals( effective_time, ret_pool_elem->last_dist_time );
int64_t change_estimate = current_rate * elapsed_intervals;

{
const bool new_return_bucket = ret_pool_elem->pending_bucket_time <= effective_time;
int64_t new_bucket_rate = 0;
time_point_sec new_bucket_time = time_point_sec::min();
_rexretpool.modify( ret_pool_elem, same_payer, [&]( auto& rp ) {
if ( new_return_bucket ) {
int64_t remainder = rp.pending_bucket_proceeds % rex_return_pool::total_intervals;
new_bucket_rate = ( rp.pending_bucket_proceeds - remainder ) / rex_return_pool::total_intervals;
new_bucket_time = rp.pending_bucket_time;
rp.current_rate_of_increase += new_bucket_rate;
change_estimate += remainder + new_bucket_rate * get_elapsed_intervals( effective_time, rp.pending_bucket_time );
rp.pending_bucket_proceeds = 0;
rp.pending_bucket_time = time_point_sec::maximum();
if ( new_bucket_time < rp.oldest_bucket_time ) {
rp.oldest_bucket_time = new_bucket_time;
}
}
rp.proceeds -= change_estimate;
rp.last_dist_time = effective_time;
});

if ( new_return_bucket ) {
_rexretbuckets.modify( ret_buckets_elem, same_payer, [&]( auto& rb ) {
rb.return_buckets[new_bucket_time] = new_bucket_rate;
});
}
}

const time_point_sec time_threshold = effective_time - seconds(rex_return_pool::total_intervals * rex_return_pool::dist_interval);
if ( ret_pool_elem->oldest_bucket_time <= time_threshold ) {
int64_t expired_rate = 0;
int64_t surplus = 0;
_rexretbuckets.modify( ret_buckets_elem, same_payer, [&]( auto& rb ) {
auto& return_buckets = rb.return_buckets;
auto iter = return_buckets.begin();
while ( iter != return_buckets.end() && iter->first <= time_threshold ) {
auto next = iter;
++next;
const uint32_t overtime = get_elapsed_intervals( effective_time,
iter->first + seconds(rex_return_pool::total_intervals * rex_return_pool::dist_interval) );
surplus += iter->second * overtime;
expired_rate += iter->second;
return_buckets.erase(iter);
iter = next;
}
});

_rexretpool.modify( ret_pool_elem, same_payer, [&]( auto& rp ) {
if ( !ret_buckets_elem->return_buckets.empty() ) {
rp.oldest_bucket_time = ret_buckets_elem->return_buckets.begin()->first;
} else {
rp.oldest_bucket_time = time_point_sec::min();
}
if ( expired_rate > 0) {
rp.current_rate_of_increase -= expired_rate;
}
if ( surplus > 0 ) {
change_estimate -= surplus;
rp.proceeds += surplus;
}
});
}

if ( change_estimate > 0 && ret_pool_elem->proceeds < 0 ) {
_rexretpool.modify( ret_pool_elem, same_payer, [&]( auto& rp ) {
change_estimate += rp.proceeds;
rp.proceeds = 0;
});
}

if ( change_estimate > 0 ) {
_rexpool.modify( _rexpool.begin(), same_payer, [&]( auto& pool ) {
pool.total_unlent.amount += change_estimate;
pool.total_lendable = pool.total_unlent + pool.total_lent;
});
}
}

template <typename T>
int64_t system_contract::rent_rex( T& table, const name& from, const name& receiver, const asset& payment, const asset& fund )
{
Expand Down Expand Up @@ -678,7 +781,7 @@ namespace eosiosystem {
asset stake_change( 0, core_symbol() );
bool success = false;

const int64_t unlent_lower_bound = ( uint128_t(2) * rexitr->total_lent.amount ) / 10;
const int64_t unlent_lower_bound = rexitr->total_lent.amount / 10;
const int64_t available_unlent = rexitr->total_unlent.amount - unlent_lower_bound; // available_unlent <= 0 is possible
if ( proceeds.amount <= available_unlent ) {
const int64_t init_vote_stake_amount = bitr->vote_stake.amount;
Expand Down Expand Up @@ -818,10 +921,7 @@ namespace eosiosystem {
{
#if CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX
if ( rex_available() ) {
_rexpool.modify( _rexpool.begin(), same_payer, [&]( auto& rp ) {
rp.total_unlent.amount += amount.amount;
rp.total_lendable.amount += amount.amount;
});
add_to_rex_return_pool( amount );
// inline transfer to rex_account
token::transfer_action transfer_act{ token_account, { from, active_permission } };
transfer_act.send( from, rex_account, amount,
Expand Down Expand Up @@ -961,6 +1061,42 @@ namespace eosiosystem {
return rex_received;
}

/**
* @brief Adds an amount of core tokens to the REX return pool
*
* @param fee - amount to be added
*/
void system_contract::add_to_rex_return_pool( const asset& fee )
{
update_rex_pool();
if ( fee.amount <= 0 ) {
return;
}

const time_point_sec ct = current_time_point();
const uint32_t cts = ct.sec_since_epoch();
const uint32_t bucket_interval = rex_return_pool::hours_per_bucket * seconds_per_hour;
const time_point_sec effective_time{cts - cts % bucket_interval + bucket_interval};
const auto return_pool_elem = _rexretpool.begin();
if ( return_pool_elem == _rexretpool.end() ) {
_rexretpool.emplace( get_self(), [&]( auto& rp ) {
rp.last_dist_time = effective_time;
rp.pending_bucket_proceeds = fee.amount;
rp.pending_bucket_time = effective_time;
rp.proceeds = fee.amount;
});
_rexretbuckets.emplace( get_self(), [&]( auto& rb ) { } );
} else {
_rexretpool.modify( return_pool_elem, same_payer, [&]( auto& rp ) {
rp.pending_bucket_proceeds += fee.amount;
rp.proceeds += fee.amount;
if ( rp.pending_bucket_time == time_point_sec::maximum() ) {
rp.pending_bucket_time = effective_time;
}
});
}
}

/**
* @brief Updates owner REX balance upon buying REX tokens
*
Expand Down
42 changes: 42 additions & 0 deletions tests/eosio.system_tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -638,6 +638,48 @@ class eosio_system_tester : public TESTER {
return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "rex_pool", data, abi_serializer_max_time );
}

fc::variant get_rex_return_pool() const {
vector<char> data;
const auto& db = control->db();
namespace chain = eosio::chain;
const auto* t_id = db.find<eosio::chain::table_id_object, chain::by_code_scope_table>( boost::make_tuple( config::system_account_name, config::system_account_name, N(rexretpool) ) );
if ( !t_id ) {
return fc::variant();
}

const auto& idx = db.get_index<chain::key_value_index, chain::by_scope_primary>();

auto itr = idx.lower_bound( boost::make_tuple( t_id->id, 0 ) );
if ( itr == idx.end() || itr->t_id != t_id->id || 0 != itr->primary_key ) {
return fc::variant();
}

data.resize( itr->value.size() );
memcpy( data.data(), itr->value.data(), data.size() );
return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "rex_return_pool", data, abi_serializer_max_time );
}

fc::variant get_rex_return_buckets() const {
vector<char> data;
const auto& db = control->db();
namespace chain = eosio::chain;
const auto* t_id = db.find<eosio::chain::table_id_object, chain::by_code_scope_table>( boost::make_tuple( config::system_account_name, config::system_account_name, N(retbuckets) ) );
if ( !t_id ) {
return fc::variant();
}

const auto& idx = db.get_index<chain::key_value_index, chain::by_scope_primary>();

auto itr = idx.lower_bound( boost::make_tuple( t_id->id, 0 ) );
if ( itr == idx.end() || itr->t_id != t_id->id || 0 != itr->primary_key ) {
return fc::variant();
}

data.resize( itr->value.size() );
memcpy( data.data(), itr->value.data(), data.size() );
return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "rex_return_buckets", data, abi_serializer_max_time );
}

void setup_rex_accounts( const std::vector<account_name>& accounts,
const asset& init_balance,
const asset& net = core_sym::from_string("80.0000"),
Expand Down
Loading