delegate_bandwidth.cpp 14.6 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
      account_name  owner;
      asset         net_weight;
      asset         cpu_weight;
D
Daniel Larimer 已提交
38
      int64_t       storage_bytes = 0;
39 40 41

      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 93 94 95 96 97 98 99 100 101 102 103 104
   /**
    *  Called after a new account is created. This code enforces resource-limits rules
    *  for new accounts as well as new account naming conventions.
    *
    *  1. accounts cannot contain '.' symbols which forces all acccounts to be 12
    *  characters long without '.' until a future account auction process is implemented
    *  which prevents name squatting.
    *
    *  2. new accounts must stake a minimal number of tokens (as set in system parameters)
    *     therefore, this method will execute an inline buyram from receiver for newacnt in
    *     an amount equal to the current new account creation fee. 
    */
   void native::newaccount( account_name     creator,
                    account_name     newact
                           /*  no need to parse authorites 
                           const authority& owner,
                           const authority& active,
                           const authority& recovery*/ ) {
      eosio::print( eosio::name{creator}, " created ", eosio::name{newact}, "\n");

      user_resources_table  userres( _self, newact);

D
Daniel Larimer 已提交
105
      userres.emplace( newact, [&]( auto& res ) {
106 107 108 109 110 111 112 113 114 115
        res.owner = newact;
      });

      set_resource_limits( newact, 
                          0,//  r->storage_bytes, 
                           0, 0 );
                    //       r->net_weight.amount, 
                    //       r->cpu_weight.amount );
   }

116 117 118 119
   /**
    *  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 ) {
D
Daniel Larimer 已提交
120 121 122 123 124 125
      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 );
126
   }
127

D
Daniel Larimer 已提交
128 129 130 131 132 133 134 135 136 137

   /**
    *  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 ) 
138
   {
139
      print( "\n payer: ", eosio::name{payer}, " buys ram for ", eosio::name{receiver}, " with ", quant, "\n" );
D
Daniel Larimer 已提交
140 141
      require_auth( payer );
      eosio_assert( quant.amount > 0, "must purchase a positive amount" );
142

D
Daniel Larimer 已提交
143 144 145 146
      if( payer != N(eosio) ) {
         INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)},
                                                       { payer, N(eosio), quant, std::string("buy ram") } );
      }
D
Daniel Larimer 已提交
147

D
Daniel Larimer 已提交
148
      print( "free ram: ", _gstate.free_ram(),  "\n");
D
Daniel Larimer 已提交
149

D
Daniel Larimer 已提交
150
      int64_t bytes_out;
151

D
Daniel Larimer 已提交
152 153 154 155
      auto itr = _rammarket.find(S(4,RAMEOS));
      _rammarket.modify( itr, 0, [&]( auto& es ) {
          bytes_out = es.convert( quant,  S(0,RAM) ).amount;
      });
D
Daniel Larimer 已提交
156

157
      print( "ram bytes out: ", bytes_out, "\n" );
D
Daniel Larimer 已提交
158 159 160

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

D
Daniel Larimer 已提交
161 162
      _gstate.total_storage_bytes_reserved += uint64_t(bytes_out);
      _gstate.total_storage_stake.amount   += quant.amount;
D
Daniel Larimer 已提交
163 164 165 166 167 168

      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;
D
Daniel Larimer 已提交
169
               res.storage_bytes = bytes_out;
D
Daniel Larimer 已提交
170 171 172
            });
      } else {
         userres.modify( res_itr, receiver, [&]( auto& res ) {
D
Daniel Larimer 已提交
173
               res.storage_bytes += bytes_out;
D
Daniel Larimer 已提交
174
            });
175
      }
176
      set_resource_limits( res_itr->owner, res_itr->storage_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount );
D
Daniel Larimer 已提交
177
   }
178

179

D
Daniel Larimer 已提交
180 181 182 183 184
   /**
    *  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.
    */
185
   void system_contract::sellram( account_name account, uint32_t bytes ) {
D
Daniel Larimer 已提交
186 187
      require_auth( account );

D
Daniel Larimer 已提交
188 189 190 191
      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" );
192

D
Daniel Larimer 已提交
193 194 195 196 197 198
      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" );
      });
199

D
Daniel Larimer 已提交
200
      _gstate.total_storage_bytes_reserved -= bytes;
D
Daniel Larimer 已提交
201 202 203 204
      _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" );
D
Daniel Larimer 已提交
205 206 207 208

      userres.modify( res_itr, account, [&]( auto& res ) {
          res.storage_bytes -= bytes;
      });
D
Daniel Larimer 已提交
209
      set_resource_limits( res_itr->owner, res_itr->storage_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount );
D
Daniel Larimer 已提交
210

D
Daniel Larimer 已提交
211 212 213 214
      if( N(eosio) != account ) {
         INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
                                                       { N(eosio), account, asset(tokens_out), std::string("sell ram") } );
      }
D
Daniel Larimer 已提交
215 216 217 218 219 220 221 222
   }

   void system_contract::delegatebw( account_name from, account_name receiver,
                                     asset stake_net_quantity, 
                                     asset stake_cpu_quantity )
                                    
   {
      require_auth( from );
223
      print( "from: ", eosio::name{from}, " to: ", eosio::name{receiver}, " net: ", stake_net_quantity, " cpu: ", stake_cpu_quantity );
D
Daniel Larimer 已提交
224

225 226
      eosio_assert( stake_cpu_quantity >= asset(0), "must stake a positive amount" );
      eosio_assert( stake_net_quantity >= asset(0), "must stake a positive amount" );
D
Daniel Larimer 已提交
227

228 229
      auto total_stake = stake_cpu_quantity.amount + stake_net_quantity.amount;
      eosio_assert( total_stake > 0, "must stake a positive amount" );
230

231
      print( "deltable" );
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248
      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;
            });
      }

249
      print( "totals" );
D
Daniel Larimer 已提交
250
      user_resources_table   totals_tbl( _self, receiver );
251 252 253 254 255 256 257 258 259 260 261 262 263 264
      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 已提交
265
      set_resource_limits( tot_itr->owner, tot_itr->storage_bytes, tot_itr->net_weight.amount, tot_itr->cpu_weight.amount );
266

D
Daniel Larimer 已提交
267 268 269 270
      if( N(eosio) != from) {
         INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {from,N(active)},
                                                       { from, N(eosio), asset(total_stake), std::string("stake bandwidth") } );
      }
271 272 273 274 275 276 277 278 279 280 281 282 283 284

      print( "voters" );
      auto from_voter = _voters.find(from);
      if( from_voter == _voters.end() ) {
         print( " create voter" );
         from_voter = _voters.emplace( from, [&]( auto& v ) {
            v.owner  = from;
            v.staked = uint64_t(total_stake);
         });
      } else {
         _voters.modify( from_voter, 0, [&]( auto& v ) {
            v.staked += uint64_t(total_stake);
         });
      }
285

286 287
      print( "voteproducer" );
      voteproducer( from, from_voter->proxy, from_voter->producers );
288 289
   } // delegatebw

D
Daniel Larimer 已提交
290 291
   void system_contract::undelegatebw( account_name from, account_name receiver,
                                       asset unstake_net_quantity, asset unstake_cpu_quantity )
292
   {
293 294
      eosio_assert( unstake_cpu_quantity >= asset(), "must unstake a positive amount" );
      eosio_assert( unstake_net_quantity >= asset(), "must unstake a positive amount" );
295 296 297 298 299

      require_auth( from );

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

303 304 305 306 307
      auto total_refund = unstake_cpu_quantity.amount + unstake_net_quantity.amount;

      _voters.modify( _voters.get(from), 0, [&]( auto& v ) {
         v.staked -= uint64_t(total_refund);
      });
308

309 310

      eosio_assert( total_refund > 0, "must unstake a positive amount" );
311 312 313 314

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

D
Daniel Larimer 已提交
317 318
      user_resources_table totals_tbl( _self, receiver );

319 320 321 322
      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;
323
      });
324

325
      set_resource_limits( receiver, totals.storage_bytes, totals.net_weight.amount, totals.cpu_weight.amount );
326 327 328 329 330 331

      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 已提交
332
               r.amount += unstake_net_quantity + unstake_cpu_quantity;
333 334 335 336 337
               r.request_time = now();
            });
      } else {
         refunds_tbl.emplace( from, [&]( refund_request& r ) {
               r.owner = from;
D
Daniel Larimer 已提交
338
               r.amount = unstake_net_quantity + unstake_cpu_quantity;
339 340 341
               r.request_time = now();
            });
      }
342

343
      //create or replace deferred transaction
344 345
      //refund act;
      //act.owner = from;
346
      eosio::transaction out;
347
      out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from );
348 349
      out.delay_sec = refund_delay;
      out.send( from, receiver );
350

351 352
      const auto& fromv = _voters.get( from );
      voteproducer( from, fromv.proxy, fromv.producers );
353 354 355 356 357 358 359 360 361 362 363 364 365 366
   } // 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.

367 368
      INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
                                                    { N(eosio), req->owner, req->amount, std::string("unstake") } );
369 370 371 372

      refunds_tbl.erase( req );
   }

D
Daniel Larimer 已提交
373
   void system_contract::setparams( uint64_t max_storage_size, uint32_t storage_reserve_ratio ) {
374
      require_auth( _self );
D
Daniel Larimer 已提交
375

376
      eosio_assert( storage_reserve_ratio > 0, "invalid reserve ratio" );
D
Daniel Larimer 已提交
377

378
      eosio_assert( max_storage_size > _gstate.total_storage_bytes_reserved, "attempt to set max below reserved" );
D
Daniel Larimer 已提交
379

380 381 382
      _gstate.max_storage_size = max_storage_size;
      _gstate.storage_reserve_ratio = storage_reserve_ratio;
      _global.set( _gstate, _self );
D
Daniel Larimer 已提交
383 384
   }

385
} //namespace eosiosystem