delegate_bandwidth.cpp 16.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 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 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
   /**
    *  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);

      auto r = userres.emplace( newact, [&]( auto& res ) {
        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 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
   /**
    *  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) );
   }
136

D
Daniel Larimer 已提交
137 138 139 140 141 142 143 144 145 146

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

D
Daniel Larimer 已提交
152 153 154 155
      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 已提交
156 157

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

160 161
      print( "free ram: ", _gstate.free_ram(),  "   tokens: ", system_token_supply,  "  unstaked: ", unstaked_token_supply, "\n" );

D
Daniel Larimer 已提交
162 163
      const double E = quant.amount;
      const double R = unstaked_token_supply - E;
D
Daniel Larimer 已提交
164
      const double C = _gstate.free_ram();   //free_ram;
165
      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
D
Daniel Larimer 已提交
166 167 168 169 170
      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);
171
      print( "ram bytes out: ", bytes_out, "\n" );
D
Daniel Larimer 已提交
172 173 174

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

D
Daniel Larimer 已提交
175 176
      _gstate.total_storage_bytes_reserved += uint64_t(bytes_out);
      _gstate.total_storage_stake.amount   += quant.amount;
D
Daniel Larimer 已提交
177 178 179 180 181 182 183 184 185 186 187 188

      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);
            });
189
      }
190
      set_resource_limits( res_itr->owner, res_itr->storage_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount );
D
Daniel Larimer 已提交
191
   }
192

193

D
Daniel Larimer 已提交
194 195 196 197 198
   /**
    *  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.
    */
199
   void system_contract::sellram( account_name account, uint32_t bytes ) {
D
Daniel Larimer 已提交
200 201 202 203
      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" );
204

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

D
Daniel Larimer 已提交
208
      const double R = unstaked_token_supply;
D
Daniel Larimer 已提交
209
      const double C = _gstate.free_ram() + bytes;
210
      const double F = _gstate.storage_reserve_ratio / 10000.0;
D
Daniel Larimer 已提交
211 212
      const double T = bytes;
      const double ONE(1.0);
213

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

D
Daniel Larimer 已提交
216 217 218 219 220
      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" );
221

D
Daniel Larimer 已提交
222 223
      _gstate.total_storage_bytes_reserved -= bytes;
      _gstate.total_storage_stake.amount   -= tokens_out;
D
Daniel Larimer 已提交
224 225 226 227 228 229

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

D
Daniel Larimer 已提交
230 231 232 233
      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 已提交
234 235 236 237 238 239 240 241
   }

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

244 245
      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 已提交
246

247 248
      auto total_stake = stake_cpu_quantity.amount + stake_net_quantity.amount;
      eosio_assert( total_stake > 0, "must stake a positive amount" );
249

250
      print( "deltable" );
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267
      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;
            });
      }

268
      print( "totals" );
D
Daniel Larimer 已提交
269
      user_resources_table   totals_tbl( _self, receiver );
270 271 272 273 274 275 276 277 278 279 280 281 282 283
      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 已提交
284
      set_resource_limits( tot_itr->owner, tot_itr->storage_bytes, uint64_t(tot_itr->net_weight.amount), uint64_t(tot_itr->cpu_weight.amount) );
285

D
Daniel Larimer 已提交
286 287 288 289
      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") } );
      }
290 291 292 293 294 295 296 297 298 299 300 301 302 303

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

305 306
      print( "voteproducer" );
      voteproducer( from, from_voter->proxy, from_voter->producers );
307 308
   } // delegatebw

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

      require_auth( from );

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

322 323 324 325 326
      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);
      });
327

328 329

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

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

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

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

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

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

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

370 371
      const auto& fromv = _voters.get( from );
      voteproducer( from, fromv.proxy, fromv.producers );
372 373 374 375 376 377 378 379 380 381 382 383 384 385
   } // 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.

386 387
      INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio),N(active)},
                                                    { N(eosio), req->owner, req->amount, std::string("unstake") } );
388 389 390 391

      refunds_tbl.erase( req );
   }

D
Daniel Larimer 已提交
392
   void system_contract::setparams( uint64_t max_storage_size, uint32_t storage_reserve_ratio ) {
393
      require_auth( _self );
D
Daniel Larimer 已提交
394

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

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

399 400 401
      _gstate.max_storage_size = max_storage_size;
      _gstate.storage_reserve_ratio = storage_reserve_ratio;
      _global.set( _gstate, _self );
D
Daniel Larimer 已提交
402 403
   }

404
} //namespace eosiosystem