delegate_bandwidth.cpp 12.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/**
 *  @file
 *  @copyright defined in eos/LICENSE.txt
 */
#include "eosio.system.hpp"

#include <eosiolib/eosio.hpp>
#include <eosiolib/print.hpp>
#include <eosiolib/datastream.hpp>
#include <eosiolib/serialize.hpp>
#include <eosiolib/multi_index.hpp>
#include <eosiolib/privileged.h>
#include <eosiolib/transaction.hpp>

#include <eosio.token/eosio.token.hpp>

D
Daniel Larimer 已提交
17 18

#include <cmath>
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
#include <map>

namespace eosiosystem {
   using eosio::asset;
   using eosio::indexed_by;
   using eosio::const_mem_fun;
   using eosio::bytes;
   using eosio::print;
   using eosio::permission_level;
   using std::map;
   using std::pair;

   static constexpr time refund_delay = 3*24*3600;
   static constexpr time refund_expiration_time = 3600;

D
Daniel Larimer 已提交
34
   struct user_resources {
35 36 37 38 39 40 41
      account_name  owner;
      asset         net_weight;
      asset         cpu_weight;
      uint64_t      storage_bytes = 0;

      uint64_t primary_key()const { return owner; }

A
Anton Perkov 已提交
42
      // explicit serialization macro is not necessary, used here only to improve compilation time
D
Daniel Larimer 已提交
43
      EOSLIB_SERIALIZE( user_resources, (owner)(net_weight)(cpu_weight)(storage_bytes) )
44 45 46 47 48 49 50 51 52 53 54 55 56 57
   };


   /**
    *  Every user 'from' has a scope/table that uses every receipient 'to' as the primary key.
    */
   struct delegated_bandwidth {
      account_name  from;
      account_name  to;
      asset         net_weight;
      asset         cpu_weight;

      uint64_t  primary_key()const { return to; }

A
Anton Perkov 已提交
58
      // explicit serialization macro is not necessary, used here only to improve compilation time
D
Daniel Larimer 已提交
59
      EOSLIB_SERIALIZE( delegated_bandwidth, (from)(to)(net_weight)(cpu_weight) )
60 61 62 63 64 65 66 67 68 69

   };

   struct refund_request {
      account_name  owner;
      time          request_time;
      eosio::asset  amount;

      uint64_t  primary_key()const { return owner; }

A
Anton Perkov 已提交
70
      // explicit serialization macro is not necessary, used here only to improve compilation time
71 72 73
      EOSLIB_SERIALIZE( refund_request, (owner)(request_time)(amount) )
   };

D
Daniel Larimer 已提交
74 75 76 77
   /**
    *  These tables are designed to be constructed in the scope of the relevant user, this 
    *  facilitates simpler API for per-user queries
    */
D
Daniel Larimer 已提交
78
   typedef eosio::multi_index< N(userres), user_resources>      user_resources_table;
79 80 81
   typedef eosio::multi_index< N(delband), delegated_bandwidth> del_bandwidth_table;
   typedef eosio::multi_index< N(refunds), refund_request>      refunds_table;

D
Daniel Larimer 已提交
82 83 84 85 86 87 88 89 90 91 92


   /**
    *  When buying ram the payer irreversiblly transfers quant to system contract and only
    *  the receiver may reclaim the tokens via the sellram action. The receiver pays for the
    *  storage of all database records associated with this action.
    *
    *  RAM is a scarce resource whose supply is defined by global properties max_storage_size. RAM is
    *  priced using the bancor algorithm such that price-per-byte with a constant reserve ratio of 100:1. 
    */
   void system_contract::buyram( account_name payer, account_name receiver, asset quant ) 
93
   {
D
Daniel Larimer 已提交
94 95
      require_auth( payer );
      eosio_assert( quant.amount > 0, "must purchase a positive amount" );
96

D
Daniel Larimer 已提交
97 98 99 100
      INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)},
                                                    { 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;
D
Daniel Larimer 已提交
101
      const double unstaked_token_supply = system_token_supply - _gstate.total_storage_stake.amount;
D
Daniel Larimer 已提交
102 103 104

      const double E = quant.amount;
      const double R = unstaked_token_supply - E;
D
Daniel Larimer 已提交
105
      const double C = _gstate.free_ram();   //free_ram;
D
Daniel Larimer 已提交
106 107 108 109 110 111 112 113 114
      const double F = .10; /// 10% reserve ratio pricing, assumes only 10% of tokens will ever want to stake for ram
      const double ONE(1.0);

      double T = C * (std::pow( ONE + E/R, F ) - ONE);
      T *= .99; /// 1% fee on every conversion
      int64_t bytes_out = static_cast<int64_t>(T);

      eosio_assert( bytes_out > 0, "must reserve a positive amount" );

D
Daniel Larimer 已提交
115 116
      _gstate.total_storage_bytes_reserved += uint64_t(bytes_out);
      _gstate.total_storage_stake.amount   += quant.amount;
D
Daniel Larimer 已提交
117 118 119 120 121 122 123 124 125 126 127 128

      user_resources_table  userres( _self, receiver );
      auto res_itr = userres.find( receiver );
      if( res_itr ==  userres.end() ) {
         res_itr = userres.emplace( receiver, [&]( auto& res ) {
               res.owner = receiver;
               res.storage_bytes = uint64_t(bytes_out);
            });
      } else {
         userres.modify( res_itr, receiver, [&]( auto& res ) {
               res.storage_bytes += uint64_t(bytes_out);
            });
129
      }
D
Daniel Larimer 已提交
130 131
      set_resource_limits( res_itr->owner, res_itr->storage_bytes, uint64_t(res_itr->net_weight.amount), uint64_t(res_itr->cpu_weight.amount) );
   }
132

133

D
Daniel Larimer 已提交
134 135 136 137 138 139 140 141 142 143
   /**
    *  While buying ram uses the current market price according to the bancor-algorithm, selling ram only
    *  refunds the purchase price to the account. In this way there is no profit to be made through buying
    *  and selling ram.
    */
   void system_contract::sellram( account_name account, uint64_t bytes ) {
      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" );
144

D
Daniel Larimer 已提交
145
      const double system_token_supply   = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount;
D
Daniel Larimer 已提交
146
      const double unstaked_token_supply = system_token_supply - _gstate.total_storage_stake.amount;
D
Daniel Larimer 已提交
147

D
Daniel Larimer 已提交
148
      const double R = unstaked_token_supply;
D
Daniel Larimer 已提交
149
      const double C = _gstate.free_ram() + bytes;
D
Daniel Larimer 已提交
150 151 152
      const double F = .10; 
      const double T = bytes;
      const double ONE(1.0);
153

D
Daniel Larimer 已提交
154
      double E = -R * (ONE - std::pow( ONE + T/C, F ) );
155

D
Daniel Larimer 已提交
156 157 158 159 160
      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" );
161

D
Daniel Larimer 已提交
162 163
      _gstate.total_storage_bytes_reserved -= bytes;
      _gstate.total_storage_stake.amount   -= tokens_out;
D
Daniel Larimer 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185

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

      INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
                                                    { N(eosio), account, asset(tokens_out), std::string("sell ram") } );
   }

   void system_contract::delegatebw( account_name from, account_name receiver,
                                     asset stake_net_quantity, 
                                     asset stake_cpu_quantity )
                                    
   {
      require_auth( from );

      eosio_assert( stake_cpu_quantity.amount >= 0, "must stake a positive amount" );
      eosio_assert( stake_net_quantity.amount >= 0, "must stake a positive amount" );

      asset total_stake = stake_cpu_quantity + stake_net_quantity;
      eosio_assert( total_stake.amount > 0, "must stake a positive amount" );
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203

      del_bandwidth_table     del_tbl( _self, from );
      auto itr = del_tbl.find( receiver );
      if( itr == del_tbl.end() ) {
         del_tbl.emplace( from, [&]( auto& dbo ){
               dbo.from          = from;
               dbo.to            = receiver;
               dbo.net_weight    = stake_net_quantity;
               dbo.cpu_weight    = stake_cpu_quantity;
            });
      }
      else {
         del_tbl.modify( itr, from, [&]( auto& dbo ){
               dbo.net_weight    += stake_net_quantity;
               dbo.cpu_weight    += stake_cpu_quantity;
            });
      }

D
Daniel Larimer 已提交
204
      user_resources_table   totals_tbl( _self, receiver );
205 206 207 208 209 210 211 212 213 214 215 216 217 218
      auto tot_itr = totals_tbl.find( receiver );
      if( tot_itr ==  totals_tbl.end() ) {
         tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) {
               tot.owner = receiver;
               tot.net_weight    = stake_net_quantity;
               tot.cpu_weight    = stake_cpu_quantity;
            });
      } else {
         totals_tbl.modify( tot_itr, from == receiver ? from : 0, [&]( auto& tot ) {
               tot.net_weight    += stake_net_quantity;
               tot.cpu_weight    += stake_cpu_quantity;
            });
      }

D
Daniel Larimer 已提交
219
      set_resource_limits( tot_itr->owner, tot_itr->storage_bytes, uint64_t(tot_itr->net_weight.amount), uint64_t(tot_itr->cpu_weight.amount) );
220 221 222

      INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {from,N(active)},
                                                    { from, N(eosio), total_stake, std::string("stake bandwidth") } );
223

D
Daniel Larimer 已提交
224
      adjust_voting_power( from, (stake_net_quantity.amount + stake_cpu_quantity.amount) );
225 226
   } // delegatebw

D
Daniel Larimer 已提交
227 228
   void system_contract::undelegatebw( account_name from, account_name receiver,
                                       asset unstake_net_quantity, asset unstake_cpu_quantity )
229 230 231 232 233 234 235 236 237 238 239 240 241
   {
      eosio_assert( unstake_cpu_quantity.amount >= 0, "must unstake a positive amount" );
      eosio_assert( unstake_net_quantity.amount >= 0, "must unstake a positive amount" );

      require_auth( from );

      //eosio_assert( is_account( receiver ), "can only delegate resources to an existing account" );

      del_bandwidth_table     del_tbl( _self, from );
      const auto& dbw = del_tbl.get( receiver );
      eosio_assert( dbw.net_weight >= unstake_net_quantity, "insufficient staked net bandwidth" );
      eosio_assert( dbw.cpu_weight >= unstake_cpu_quantity, "insufficient staked cpu bandwidth" );

D
Daniel Larimer 已提交
242
      eosio::asset total_refund = unstake_cpu_quantity + unstake_net_quantity;
243 244 245 246 247 248 249 250

      eosio_assert( total_refund.amount > 0, "must unstake a positive amount" );

      del_tbl.modify( dbw, from, [&]( auto& dbo ){
            dbo.net_weight -= unstake_net_quantity;
            dbo.cpu_weight -= unstake_cpu_quantity;
         });

D
Daniel Larimer 已提交
251 252
      user_resources_table totals_tbl( _self, receiver );

253 254 255 256 257 258
      const auto& totals = totals_tbl.get( receiver );
      totals_tbl.modify( totals, 0, [&]( auto& tot ) {
            tot.net_weight -= unstake_net_quantity;
            tot.cpu_weight -= unstake_cpu_quantity;
         });

D
Daniel Larimer 已提交
259
      set_resource_limits( totals.owner, totals.storage_bytes, uint64_t(totals.net_weight.amount), uint64_t(totals.cpu_weight.amount) );
260 261 262 263 264 265

      refunds_table refunds_tbl( _self, from );
      //create refund request
      auto req = refunds_tbl.find( from );
      if ( req != refunds_tbl.end() ) {
         refunds_tbl.modify( req, 0, [&]( refund_request& r ) {
D
Daniel Larimer 已提交
266
               r.amount += unstake_net_quantity + unstake_cpu_quantity;
267 268 269 270 271
               r.request_time = now();
            });
      } else {
         refunds_tbl.emplace( from, [&]( refund_request& r ) {
               r.owner = from;
D
Daniel Larimer 已提交
272
               r.amount = unstake_net_quantity + unstake_cpu_quantity;
273 274 275 276
               r.request_time = now();
            });
      }
      //create or replace deferred transaction
277 278
      //refund act;
      //act.owner = from;
279
      eosio::transaction out;
280
      out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from );
281 282
      out.delay_sec = refund_delay;
      out.send( from, receiver );
283

D
Daniel Larimer 已提交
284
      adjust_voting_power( from, -(unstake_net_quantity.amount + unstake_cpu_quantity.amount) );
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299

   } // undelegatebw


   void system_contract::refund( const account_name owner ) {
      require_auth( owner );

      refunds_table refunds_tbl( _self, owner );
      auto req = refunds_tbl.find( owner );
      eosio_assert( req != refunds_tbl.end(), "refund request not found" );
      eosio_assert( req->request_time + refund_delay <= now(), "refund is not available yet" );
      // Until now() becomes NOW, the fact that now() is the timestamp of the previous block could in theory
      // allow people to get their tokens earlier than the 3 day delay if the unstake happened immediately after many
      // consecutive missed blocks.

300 301
      INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
                                                    { N(eosio), req->owner, req->amount, std::string("unstake") } );
302 303 304 305

      refunds_tbl.erase( req );
   }

D
Daniel Larimer 已提交
306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321

   void system_contract::setparams( uint64_t max_storage_size, uint32_t storage_reserve_ratio ) {
         require_auth( _self );

         eosio_assert( storage_reserve_ratio > 0, "invalid reserve ratio" );

         global_state_singleton gs( _self, _self );
         auto parameters = gs.exists() ? gs.get() : get_default_parameters();

         eosio_assert( max_storage_size > parameters.total_storage_bytes_reserved, "attempt to set max below reserved" );

         parameters.max_storage_size = max_storage_size;
         parameters.storage_reserve_ratio = storage_reserve_ratio;
         gs.set( parameters, _self );
   }

322
} //namespace eosiosystem