From e35b0e3a5c0f791eaf5167f1424216e630b5a2dc Mon Sep 17 00:00:00 2001 From: Daniel Larimer Date: Fri, 4 May 2018 09:52:07 -0400 Subject: [PATCH] fix pricing model on RAM --- contracts/eosio.system/delegate_bandwidth.cpp | 79 +++++++------------ contracts/eosio.system/eosio.system.cpp | 20 ++++- contracts/eosio.system/eosio.system.hpp | 2 + contracts/eosio.system/exchange_state.cpp | 79 +++++++++++++++++++ contracts/eosio.system/exchange_state.hpp | 40 ++++++++++ contracts/eosio.system/voting.cpp | 4 +- unittests/eosio.system_tests.cpp | 13 +-- 7 files changed, 179 insertions(+), 58 deletions(-) create mode 100644 contracts/eosio.system/exchange_state.cpp create mode 100644 contracts/eosio.system/exchange_state.hpp diff --git a/contracts/eosio.system/delegate_bandwidth.cpp b/contracts/eosio.system/delegate_bandwidth.cpp index b9cd9aa59..d29aa72d0 100644 --- a/contracts/eosio.system/delegate_bandwidth.cpp +++ b/contracts/eosio.system/delegate_bandwidth.cpp @@ -35,7 +35,7 @@ namespace eosiosystem { account_name owner; asset net_weight; asset cpu_weight; - uint64_t storage_bytes = 0; + int64_t storage_bytes = 0; uint64_t primary_key()const { return owner; } @@ -102,7 +102,7 @@ namespace eosiosystem { user_resources_table userres( _self, newact); - auto r = userres.emplace( newact, [&]( auto& res ) { + userres.emplace( newact, [&]( auto& res ) { res.owner = newact; }); @@ -117,21 +117,12 @@ namespace eosiosystem { * This action will buy an exact amount of ram and bill the payer the current market price. */ void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) { - const double system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; - const double unstaked_token_supply = system_token_supply - _gstate.total_storage_stake.amount; - - const double R = unstaked_token_supply; - const double C = _gstate.free_ram() + bytes; - const double F = _gstate.storage_reserve_ratio / 10000.0; - const double T = bytes; - const double ONE(1.0); - - double E = -R * (ONE - std::pow( ONE + T/C, F ) ); - - int64_t tokens_out = int64_t(E*1.0105); - print( "desired ram: ", bytes, "\n" ); - - buyram( payer, receiver, asset(tokens_out) ); + auto itr = _rammarket.find(S(4,RAMEOS)); + auto tmp = *itr; + auto eosout = tmp.convert( asset(bytes,S(0,RAM)), S(4,EOS) ); + print( "eosout: ", eosout, "\n" ); + + buyram( payer, receiver, eosout ); } @@ -154,20 +145,15 @@ namespace eosiosystem { { payer, N(eosio), quant, std::string("buy ram") } ); } - const double system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; - const double unstaked_token_supply = system_token_supply - _gstate.total_storage_stake.amount; + print( "free ram: ", _gstate.free_ram(), "\n"); - print( "free ram: ", _gstate.free_ram(), " tokens: ", system_token_supply, " unstaked: ", unstaked_token_supply, "\n" ); + int64_t bytes_out; - const double E = quant.amount; - const double R = unstaked_token_supply - E; - const double C = _gstate.free_ram(); //free_ram; - const double F = 1./(_gstate.storage_reserve_ratio/10000.0); /// 10% reserve ratio pricing, assumes only 10% of tokens will ever want to stake for ram - const double ONE(1.0); + auto itr = _rammarket.find(S(4,RAMEOS)); + _rammarket.modify( itr, 0, [&]( auto& es ) { + bytes_out = es.convert( quant, S(0,RAM) ).amount; + }); - double T = C * (std::pow( ONE + E/R, F ) - ONE); - T *= .99; /// 1% fee on every conversion - int64_t bytes_out = static_cast(T); print( "ram bytes out: ", bytes_out, "\n" ); eosio_assert( bytes_out > 0, "must reserve a positive amount" ); @@ -180,11 +166,11 @@ namespace eosiosystem { if( res_itr == userres.end() ) { res_itr = userres.emplace( receiver, [&]( auto& res ) { res.owner = receiver; - res.storage_bytes = uint64_t(bytes_out); + res.storage_bytes = bytes_out; }); } else { userres.modify( res_itr, receiver, [&]( auto& res ) { - res.storage_bytes += uint64_t(bytes_out); + res.storage_bytes += bytes_out; }); } set_resource_limits( res_itr->owner, res_itr->storage_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); @@ -197,35 +183,30 @@ namespace eosiosystem { * and selling ram. */ void system_contract::sellram( account_name account, uint32_t bytes ) { + require_auth( account ); + user_resources_table userres( _self, account ); auto res_itr = userres.find( account ); eosio_assert( res_itr != userres.end(), "no resource row" ); eosio_assert( res_itr->storage_bytes >= bytes, "insufficient quota" ); - const double system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; - const double unstaked_token_supply = system_token_supply - _gstate.total_storage_stake.amount; - - const double R = unstaked_token_supply; - const double C = _gstate.free_ram() + bytes; - const double F = _gstate.storage_reserve_ratio / 10000.0; - const double T = bytes; - const double ONE(1.0); - - double E = -R * (ONE - std::pow( ONE + T/C, F ) ); - - E *= .99; /// 1% fee on every conversion, - /// let the system contract profit on speculation while preventing abuse caused by rounding errors - - int64_t tokens_out = int64_t(E); - eosio_assert( tokens_out > 0, "must free at least one token" ); + asset tokens_out; + auto itr = _rammarket.find(S(4,RAMEOS)); + _rammarket.modify( itr, 0, [&]( auto& es ) { + tokens_out = es.convert( asset(bytes,S(0,RAM)), S(4,EOS) ); + print( "out: ", tokens_out, "\n" ); + }); _gstate.total_storage_bytes_reserved -= bytes; - _gstate.total_storage_stake.amount -= tokens_out; + _gstate.total_storage_stake.amount -= tokens_out.amount; + + //// this shouldn't happen, but just in case it does we should prevent it + eosio_assert( _gstate.total_storage_stake.amount >= 0, "error, attempt to unstake more tokens than previously staked" ); userres.modify( res_itr, account, [&]( auto& res ) { res.storage_bytes -= bytes; }); - set_resource_limits( res_itr->owner, res_itr->storage_bytes, uint64_t(res_itr->net_weight.amount), uint64_t(res_itr->cpu_weight.amount) ); + set_resource_limits( res_itr->owner, res_itr->storage_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); if( N(eosio) != account ) { INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)}, @@ -281,7 +262,7 @@ namespace eosiosystem { }); } - set_resource_limits( tot_itr->owner, tot_itr->storage_bytes, uint64_t(tot_itr->net_weight.amount), uint64_t(tot_itr->cpu_weight.amount) ); + set_resource_limits( tot_itr->owner, tot_itr->storage_bytes, tot_itr->net_weight.amount, tot_itr->cpu_weight.amount ); if( N(eosio) != from) { INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {from,N(active)}, diff --git a/contracts/eosio.system/eosio.system.cpp b/contracts/eosio.system/eosio.system.cpp index 96d8c4f47..9cf2cc31d 100644 --- a/contracts/eosio.system/eosio.system.cpp +++ b/contracts/eosio.system/eosio.system.cpp @@ -4,6 +4,7 @@ #include "delegate_bandwidth.cpp" #include "producer_pay.cpp" #include "voting.cpp" +#include "exchange_state.cpp" namespace eosiosystem { @@ -12,10 +13,27 @@ namespace eosiosystem { :native(s), _voters(_self,_self), _producers(_self,_self), - _global(_self,_self) + _global(_self,_self), + _rammarket(_self,_self) { print( "construct system\n" ); _gstate = _global.exists() ? _global.get() : get_default_parameters(); + + auto itr = _rammarket.find(S(4,RAMEOS)); + + if( itr == _rammarket.end() ) { + auto system_token_supply = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount; + itr = _rammarket.emplace( _self, [&]( auto& m ) { + m.supply.amount = 100000000000000ll; + m.supply.symbol = S(4,RAMEOS); + m.base.balance.amount = int64_t(_gstate.free_ram()); + m.base.balance.symbol = S(0,RAM); + m.quote.balance.amount = system_token_supply / 1000; + m.quote.balance.symbol = S(4,EOS); + }); + } else { + print( "ram market already created" ); + } } eosio_global_state system_contract::get_default_parameters() { diff --git a/contracts/eosio.system/eosio.system.hpp b/contracts/eosio.system/eosio.system.hpp index d5cab5ee9..a4b639f7d 100644 --- a/contracts/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/eosio.system.hpp @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -116,6 +117,7 @@ namespace eosiosystem { global_state_singleton _global; eosio_global_state _gstate; + rammarket _rammarket; public: system_contract( account_name s ); diff --git a/contracts/eosio.system/exchange_state.cpp b/contracts/eosio.system/exchange_state.cpp new file mode 100644 index 000000000..90a945ff6 --- /dev/null +++ b/contracts/eosio.system/exchange_state.cpp @@ -0,0 +1,79 @@ +#include + +namespace eosiosystem { + asset exchange_state::convert_to_exchange( connector& c, asset in ) { + + real_type R(supply.amount); + real_type C(c.balance.amount+in.amount); + real_type F(c.weight/1000.0); + real_type T(in.amount); + real_type ONE(1.0); + + real_type E = -R * (ONE - std::pow( ONE + T / C, F) ); + //print( "E: ", E, "\n"); + int64_t issued = int64_t(E); + + supply.amount += issued; + c.balance.amount += in.amount; + + return asset( issued, supply.symbol ); + } + + asset exchange_state::convert_from_exchange( connector& c, asset in ) { + eosio_assert( in.symbol== supply.symbol, "unexpected asset symbol input" ); + + real_type R(supply.amount - in.amount); + real_type C(c.balance.amount); + real_type F(1000.0/c.weight); + real_type E(in.amount); + real_type ONE(1.0); + + + real_type T = C * (std::pow( ONE + E/R, F) - ONE); + //print( "T: ", T, "\n"); + int64_t out = int64_t(T); + + supply.amount -= in.amount; + c.balance.amount -= out; + + return asset( out, c.balance.symbol ); + } + + asset exchange_state::convert( asset from, symbol_type to ) { + auto sell_symbol = from.symbol; + auto ex_symbol = supply.symbol; + auto base_symbol = base.balance.symbol; + auto quote_symbol = quote.balance.symbol; + + //print( "From: ", from, " TO ", asset( 0,to), "\n" ); + //print( "base: ", base_symbol, "\n" ); + //print( "quote: ", quote_symbol, "\n" ); + //print( "ex: ", supply.symbol, "\n" ); + + if( sell_symbol != ex_symbol ) { + if( sell_symbol == base_symbol ) { + from = convert_to_exchange( base, from ); + } else if( sell_symbol == quote_symbol ) { + from = convert_to_exchange( quote, from ); + } else { + eosio_assert( false, "invalid sell" ); + } + } else { + if( to == base_symbol ) { + from = convert_from_exchange( base, from ); + } else if( to == quote_symbol ) { + from = convert_from_exchange( quote, from ); + } else { + eosio_assert( false, "invalid conversion" ); + } + } + + if( to != from.symbol ) + return convert( from, to ); + + return from; + } + + + +} /// namespace eosiosystem diff --git a/contracts/eosio.system/exchange_state.hpp b/contracts/eosio.system/exchange_state.hpp new file mode 100644 index 000000000..3705a9b8b --- /dev/null +++ b/contracts/eosio.system/exchange_state.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include + +namespace eosiosystem { + using eosio::asset; + using eosio::symbol_type; + + typedef double real_type; + + /** + * Uses Bancor math to create a 50/50 relay between two asset types. The state of the + * bancor exchange is entirely contained within this struct. There are no external + * side effects associated with using this API. + */ + struct exchange_state { + asset supply; + + struct connector { + asset balance; + double weight = .5; + + EOSLIB_SERIALIZE( connector, (balance)(weight) ) + }; + + connector base; + connector quote; + + uint64_t primary_key()const { return supply.symbol; } + + asset convert_to_exchange( connector& c, asset in ); + asset convert_from_exchange( connector& c, asset in ); + asset convert( asset from, symbol_type to ); + + EOSLIB_SERIALIZE( exchange_state, (supply)(base)(quote) ) + }; + + typedef eosio::multi_index rammarket; + +} /// namespace eosiosystem diff --git a/contracts/eosio.system/voting.cpp b/contracts/eosio.system/voting.cpp index 30ab32fd0..fda3c8cbb 100644 --- a/contracts/eosio.system/voting.cpp +++ b/contracts/eosio.system/voting.cpp @@ -39,7 +39,7 @@ namespace eosiosystem { * @pre authority of producer to register * */ - void system_contract::regproducer( const account_name producer, const public_key& producer_key, const std::string& url ) { //, const eosio_parameters& prefs ) { + void system_contract::regproducer( const account_name producer, const eosio::public_key& producer_key, const std::string& url ) { //, const eosio_parameters& prefs ) { eosio_assert( url.size() < 512, "url too long" ); //eosio::print("produce_key: ", producer_key.size(), ", sizeof(public_key): ", sizeof(public_key), "\n"); require_auth( producer ); @@ -67,7 +67,7 @@ namespace eosiosystem { const auto& prod = _producers.get( producer ); _producers.modify( prod, 0, [&]( producer_info& info ){ - info.producer_key = public_key(); + info.producer_key = eosio::public_key(); }); } diff --git a/unittests/eosio.system_tests.cpp b/unittests/eosio.system_tests.cpp index f3eb71ba7..640bca806 100644 --- a/unittests/eosio.system_tests.cpp +++ b/unittests/eosio.system_tests.cpp @@ -37,18 +37,18 @@ public: create_accounts( { N(eosio.token) } ); produce_blocks( 100 ); - set_code( config::system_account_name, eosio_system_wast ); - set_abi( config::system_account_name, eosio_system_abi ); - set_code( N(eosio.token), eosio_token_wast ); set_abi( N(eosio.token), eosio_token_abi ); - create_currency( N(eosio.token), config::system_account_name, asset::from_string("1000000000.0000 EOS") ); - issue(config::system_account_name, "100000000.0000 EOS"); + create_currency( N(eosio.token), config::system_account_name, asset::from_string("10000000000.0000 EOS") ); + issue(config::system_account_name, "1000000000.0000 EOS"); + BOOST_REQUIRE_EQUAL( asset::from_string("1000000000.0000 EOS"), get_balance( "eosio" ) ); + + set_code( config::system_account_name, eosio_system_wast ); + set_abi( config::system_account_name, eosio_system_abi ); produce_blocks(); - BOOST_REQUIRE_EQUAL( asset::from_string("1000000000.0000 EOS"), get_balance( "eosio" ) ); create_account_with_resources( N(alice), N(eosio), asset::from_string("1.0000 EOS"), false );//{ N(alice), N(bob), N(carol) } ); create_account_with_resources( N(bob), N(eosio), asset::from_string("0.4500 EOS"), false );//{ N(alice), N(bob), N(carol) } ); create_account_with_resources( N(carol), N(eosio), asset::from_string("1.0000 EOS"), false );//{ N(alice), N(bob), N(carol) } ); @@ -313,6 +313,7 @@ BOOST_FIXTURE_TEST_CASE( stake_unstake, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), buyram( "alice", "bob", "200.0000 EOS" ) ); BOOST_REQUIRE_EQUAL( success(), buyrambytes( "alice", "bob", 100 ) ); BOOST_REQUIRE_EQUAL( success(), sellram( "bob", 100 ) ); + BOOST_REQUIRE_EQUAL( success(), buyrambytes( "alice", "bob", 10000 ) ); -- GitLab