delegate_bandwidth.cpp 15.0 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;
38
      int64_t       ram_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
43
      EOSLIB_SERIALIZE( user_resources, (owner)(net_weight)(cpu_weight)(ram_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
        res.owner = newact;
      });

      set_resource_limits( newact, 
110
                          0,//  r->ram_bytes, 
111 112 113 114 115
                           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
      auto itr = _rammarket.find(S(4,RAMEOS));
      auto tmp = *itr;
      auto eosout = tmp.convert( asset(bytes,S(0,RAM)), S(4,EOS) );
123
      print( "eosout: ", eosout, " ", tmp.base.balance, " ", tmp.quote.balance, "\n" );
D
Daniel Larimer 已提交
124 125

      buyram( payer, receiver, eosout );
126
   }
127

D
Daniel Larimer 已提交
128 129 130 131 132 133

   /**
    *  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.
    *
134
    *  RAM is a scarce resource whose supply is defined by global properties max_ram_size. RAM is
D
Daniel Larimer 已提交
135 136 137
    *  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" );

161 162
      _gstate.total_ram_bytes_reserved += uint64_t(bytes_out);
      _gstate.total_ram_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;
169
               res.ram_bytes = bytes_out;
D
Daniel Larimer 已提交
170 171 172
            });
      } else {
         userres.modify( res_itr, receiver, [&]( auto& res ) {
173
               res.ram_bytes += bytes_out;
D
Daniel Larimer 已提交
174
            });
175
      }
176
      set_resource_limits( res_itr->owner, res_itr->ram_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.
    */
D
Daniel Larimer 已提交
185
   void system_contract::sellram( account_name account, uint64_t bytes ) {
D
Daniel Larimer 已提交
186 187
      require_auth( account );

D
Daniel Larimer 已提交
188 189 190
      user_resources_table  userres( _self, account );
      auto res_itr = userres.find( account );
      eosio_assert( res_itr != userres.end(), "no resource row" );
191
      eosio_assert( res_itr->ram_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

200 201
      _gstate.total_ram_bytes_reserved -= bytes;
      _gstate.total_ram_stake.amount   -= tokens_out.amount;
D
Daniel Larimer 已提交
202 203

      //// this shouldn't happen, but just in case it does we should prevent it
204
      eosio_assert( _gstate.total_ram_stake.amount >= 0, "error, attempt to unstake more tokens than previously staked" );
D
Daniel Larimer 已提交
205 206

      userres.modify( res_itr, account, [&]( auto& res ) {
207
          res.ram_bytes -= bytes;
D
Daniel Larimer 已提交
208
      });
209
      set_resource_limits( res_itr->owner, res_itr->ram_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
   }

   void system_contract::delegatebw( account_name from, account_name receiver,
                                     asset stake_net_quantity, 
219
                                     asset stake_cpu_quantity, bool transfer )
D
Daniel Larimer 已提交
220 221 222
                                    
   {
      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 232 233 234 235
      account_name source_stake_from = from;

      if( transfer ) from = receiver;

      del_bandwidth_table     del_tbl( _self, from);
236 237 238 239 240 241 242 243 244 245
      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 {
246
         del_tbl.modify( itr, 0, [&]( auto& dbo ){
247 248 249 250 251
               dbo.net_weight    += stake_net_quantity;
               dbo.cpu_weight    += stake_cpu_quantity;
            });
      }

252
      print( "totals" );
D
Daniel Larimer 已提交
253
      user_resources_table   totals_tbl( _self, receiver );
254 255 256 257 258 259 260 261 262 263 264 265 266 267
      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;
            });
      }

268
      set_resource_limits( tot_itr->owner, tot_itr->ram_bytes, tot_itr->net_weight.amount, tot_itr->cpu_weight.amount );
269

270
      if( N(eosio) != source_stake_from ) {
D
Daniel Larimer 已提交
271
         INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {from,N(active)},
272
                                                       { source_stake_from, N(eosio), asset(total_stake), std::string("stake bandwidth") } );
D
Daniel Larimer 已提交
273
      }
274

275
      print( "voters \n" );
276 277
      auto from_voter = _voters.find(from);
      if( from_voter == _voters.end() ) {
278
         print( " create voter \n" );
279 280
         from_voter = _voters.emplace( from, [&]( auto& v ) {
            v.owner  = from;
D
Daniel Larimer 已提交
281
            v.staked = total_stake;
282
            print( "    vote weight: ", v.last_vote_weight, "\n" );
283 284 285
         });
      } else {
         _voters.modify( from_voter, 0, [&]( auto& v ) {
D
Daniel Larimer 已提交
286
            v.staked += total_stake;
287
            print( "    vote weight: ", v.last_vote_weight, "\n" );
288 289
         });
      }
290

291 292 293 294
      print( "voteproducer\n" );
      if( from_voter->producers.size() || from_voter->proxy ) {
         voteproducer( from, from_voter->proxy, from_voter->producers );
      }
295 296
   } // delegatebw

297

D
Daniel Larimer 已提交
298
   void validate_b1_vesting( int64_t stake ) {
299 300 301
      const int64_t seconds_per_year = 60*60*24*365;
      const int64_t base_time = 1527811200; /// 2018-06-01
      const int64_t max_claimable = 100'000'000'0000ll;
D
Daniel Larimer 已提交
302
      const int64_t claimable = int64_t(max_claimable * double(now()-base_time) / (10*seconds_per_year) );
303 304 305 306

      eosio_assert( max_claimable - claimable <= stake, "b1 can only claim their tokens over 10 years" );
   }

D
Daniel Larimer 已提交
307 308
   void system_contract::undelegatebw( account_name from, account_name receiver,
                                       asset unstake_net_quantity, asset unstake_cpu_quantity )
309
   {
310 311
      eosio_assert( unstake_cpu_quantity >= asset(), "must unstake a positive amount" );
      eosio_assert( unstake_net_quantity >= asset(), "must unstake a positive amount" );
312 313 314 315 316

      require_auth( from );

      del_bandwidth_table     del_tbl( _self, from );
      const auto& dbw = del_tbl.get( receiver );
317 318
      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" );
319

320 321 322
      auto total_refund = unstake_cpu_quantity.amount + unstake_net_quantity.amount;

      _voters.modify( _voters.get(from), 0, [&]( auto& v ) {
D
Daniel Larimer 已提交
323
         v.staked -= total_refund;
324 325 326
         if( from == N(b1) ) {
            validate_b1_vesting( v.staked );
         }
327
      });
328

329 330

      eosio_assert( total_refund > 0, "must unstake a positive amount" );
331 332 333 334

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

D
Daniel Larimer 已提交
337 338
      user_resources_table totals_tbl( _self, receiver );

339 340 341 342
      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;
343
      });
344

345
      set_resource_limits( receiver, totals.ram_bytes, totals.net_weight.amount, totals.cpu_weight.amount );
346 347 348 349 350 351

      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 已提交
352
               r.amount += unstake_net_quantity + unstake_cpu_quantity;
353 354 355 356 357
               r.request_time = now();
            });
      } else {
         refunds_tbl.emplace( from, [&]( refund_request& r ) {
               r.owner = from;
D
Daniel Larimer 已提交
358
               r.amount = unstake_net_quantity + unstake_cpu_quantity;
359 360 361
               r.request_time = now();
            });
      }
362

363
      //create or replace deferred transaction
364 365
      //refund act;
      //act.owner = from;
366
      eosio::transaction out;
367
      out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from );
368 369
      out.delay_sec = refund_delay;
      out.send( from, receiver );
370

371
      const auto& fromv = _voters.get( from );
372 373 374 375

      if( fromv.producers.size() || fromv.proxy ) {
         voteproducer( from, fromv.proxy, fromv.producers );
      }
376 377 378 379 380 381 382 383 384 385 386 387 388 389
   } // 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.

390 391
      INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
                                                    { N(eosio), req->owner, req->amount, std::string("unstake") } );
392 393 394 395

      refunds_tbl.erase( req );
   }

D
Daniel Larimer 已提交
396

397
} //namespace eosiosystem