Skip to content

Instantly share code, notes, and snippets.

@redjade
Last active November 23, 2018 14:14
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save redjade/bdbcb10708e165b29d0301c5defa1d83 to your computer and use it in GitHub Desktop.
Save redjade/bdbcb10708e165b29d0301c5defa1d83 to your computer and use it in GitHub Desktop.
on_ramcost.md

Intro

Please remind that this gist is for documenting and understanding what is going on current ram cost approaches and its consequences technically.

Ideal state after eosio resignation below:

  • EOS token stat
    • max_supply : 10000000000
    • supply : 1000000000
  • RAM Bancor state
    • quote.balance.amount = 1000000000.0000 / 1000

Suppose we all agree that ideal state after resignation of system accounts is same with above.

life cycle of _rammarket database

Code below:

      auto itr = _rammarket.find(S(4,RAMCORE));

      if( itr == _rammarket.end() ) {
         auto system_token_supply   = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount;
         if( system_token_supply > 0 ) {
            itr = _rammarket.emplace( _self, [&]( auto& m ) {
               m.supply.amount = 100000000000000ll;
               m.supply.symbol = S(4,RAMCORE);
               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 = CORE_SYMBOL;
            });
         }
      } else {
         //print( "ram market already created" );
      }
   }
  • via system_contract::system_contract() in eosio.system.cpp
  • rammarket is a eosio::multi_index named rammarket.
  • when eosio.system contract is setcodeed, _rammarket is initialized,
  • once initialized, rammarket will be never initialized again in case of another setcode of eosio.system.

Let's check what will happen to three important balances of bancor which make ram market of EOSIO.

  • suppy.amount for RAMCORE, a relay token, which represents supply of relay token of bancor.
  • base.balance.amount for RAM which represents real ram availble to buy and is RAM connector balance of bancor.
  • quote.balance.amount for EOS which represents EOS connector balance of bancor.

supply.amount

In current EOSIO, relay token RAMCORE is used only in conversion which is only between EOS and RAM. Therefore supply of RAMCORE never changes as long as this example of conversion is unchanged.

base.balance.amount

base.balance.amount for RAM which represents real ram availble to buy and is RAM connector balance of bancor.

// in eosio.system.hpp
      uint64_t free_ram()const { return max_ram_size - total_ram_bytes_reserved; }

      uint64_t             max_ram_size = 64ll*1024 * 1024 * 1024;
      uint64_t             total_ram_bytes_reserved = 0;
      int64_t              total_ram_stake = 0;
  • eosio_global_state is eosio::singleton which is a single table on database.
  • With setram action in eosio.system, it can be changed easily.
  • When block producers are going to increase RAM capacity of EOS blockchain, it is conceivable that setram will be pushed through consensus of block producers.

quote.balance.amount

system_token_supply is the trickest part.

// in eosio.token.hpp
         struct currency_stats {
            asset          supply;
            asset          max_supply;
            account_name   issuer;

            uint64_t primary_key()const { return supply.symbol.name(); }
         };

         typedef eosio::multi_index<N(accounts), account> accounts;
         typedef eosio::multi_index<N(stat), currency_stats> stats;
  • stats is a eosio::multi_index named stat
  • max_supply is determined only when creating a token.
  • supply starts at 0 and is increased by token::issue.
   asset token::get_supply( symbol_name sym )const
   {
      stats statstable( _self, sym );
      const auto& st = statstable.get( sym );
      return st.supply;
   }
  • Yes, get_supply returns current accummulated issued amount.
  • And there is NO action or method to decrease the supply of a token at this time.
  • get_supply is used via system_contract::claimrewards for claiming rewards and provides current EOS supply amount to calculate inflation.

an example of rammarket following eos-bios boot-sequence.yml

Please keep in mind that these descriptions below are intended to explain, not to criticize a specific method.

highlight from file /files/boot_sequence.yaml

  • system.setcode of eosio.token contract on account eosio.token
  • token.create with 10000000000.0000 EOS : Should work with 5% inflation, for the next 50 years (end of uint32 block_num anyway)
  • token.issue with 1000011821.0000 EOS : 1B coins, as per distribution model + gift of RAM to new users.
  • system.setcode of eosio.system contract on account eosio for the first time.
  • snapshot.create_accounts with buy_ram_bytes 8192.
  • system.setcode of eosio.bios contract on account eosio.
  • system.setcode of eosio.system contract on account eosio for the second time.
  • system.resign_accounts for resignation of system accounts.

let's see what happens inside token stats and bancor state.

  • token creation
    • when creating a EOS token with 10000000000.0000, max_supply in token stats is set as 10000000000.0000 EOS.
    • when issuing 1000011821.0000 EOS, then supply in token stats is set as 1000011821.0000 EOS.
  • first eosio.system setcode
    • rammarket is created with bancor state below:
      • RAMCORE : supply.amount = 100000000000000ll
      • RAM : base.balance.amount = current available RAM bytes via base.balance.amount = int64_t(_gstate.free_ram());
      • EOS(CORE) : quote.balance.amount = 1000011821.0000 / 1000 via codes below.
auto system_token_supply   = eosio::token(N(eosio.token)).get_supply(eosio::symbol_type(system_token_symbol).name()).amount;

m.quote.balance.amount = system_token_supply / 1000;
  • snapshot.create_accounts
    • no changes in RAMCORE supply.amount
    • RAM base.balance.amount decreases according to conversion of ram buys.
    • EOS(CORE) quote.balance.amount increases according to ram buys.
    • RAM price is determined with starting EOS quote.balance.amount (1000011821.0000 / 1000), not (1000000000.0000 / 1000).
  • second eosio.system setcode
    • Because rammarket already exists, its values is not initialized again.
  • resignation of system accounts
    • This step disables authorization for system accounts.
    • But resignation doesn't change EOS supply value of token stats
    • Also doesn't change EOS balance of bancor state either.

Results

  • effects to token stats
    • EOS token supply is 1000011821.0000.
    • slightly increased supply is used when calculating inflation.
  • effects to bancor
    • quote.balance.amount starts with (1000011821.0000 / 1000), not (1000000000.0000 / 1000)
    • Because of this, price of RAM starts slightly higher and grows slightly steeper.

To get the ideal state

To get the ideal state, it is necessary to change eosio.token and eosio.system accordingly.

  • EOS token supply correction, which is similar what @nsrempel suggested on EOS Validation (EMLG) telegram channel, but not with each account but once with total delta.
    • See comment below for patch of unissue on eosio master (commit id 379cb1a9e2a6557221d946eef4c5c83ddd156799, Tue Jun 5 18:00:26 2018 -0500).
Considering validation tools already check every account balance i think something simpler like

void token::unissue(quantity){
statstable.modify( st, 0, [&]( auto& s ) {
       s.supply -= quantity;
    });
}

would be preferable in the eosio.token contract, then reconfirm balances = total supply

It seems that difficulty of implementation and inclusion into the boot sequence is not that high.

Reference

Credits

I totally trust and rely on two wonderful mathematicians, @sergioyuhjtman from EOS Argentina and @hyunwoongJi from EOSeoul. We discussed and examined Bancor and its implementation in EOSIO thoroughly for weeks with mutual respect and understanding.

I hope future reviewers including mathematicians, developers and economists interested in EOSIO can join this discussion and conclude what is desirable.

@redjade
Copy link
Author

redjade commented Jun 6, 2018

Here comes token::unissue patch on EOSIO master (commit id 379cb1a9e2a6557221d946eef4c5c83ddd156799, Tue Jun 5 18:00:26 2018 -0500)

diff --git a/contracts/eosio.token/eosio.token.abi b/contracts/eosio.token/eosio.token.abi
index d769deb33..21aa17a0c 100644
--- a/contracts/eosio.token/eosio.token.abi
+++ b/contracts/eosio.token/eosio.token.abi
@@ -28,6 +28,13 @@
         {"name":"quantity", "type":"asset"},
         {"name":"memo", "type":"string"}
      ]
+  },{
+     "name": "unissue",
+     "base": "",
+     "fields": [
+        {"name":"quantity", "type":"asset"},
+        {"name":"memo", "type":"string"}
+     ]
   },{
       "name": "account",
       "base": "",
@@ -52,6 +59,10 @@
       "name": "issue",
       "type": "issue",
       "ricardian_contract": ""
+    }, {
+      "name": "unissue",
+      "type": "unissue",
+      "ricardian_contract": ""
     }, {
       "name": "create",
       "type": "create",
diff --git a/contracts/eosio.token/eosio.token.cpp b/contracts/eosio.token/eosio.token.cpp
index d85c23620..a5e8a7f63 100644
--- a/contracts/eosio.token/eosio.token.cpp
+++ b/contracts/eosio.token/eosio.token.cpp
@@ -59,6 +59,33 @@ void token::issue( account_name to, asset quantity, string memo )
     }
 }
 
+/* 
+ * Only to correct supply of a token.
+ */
+void token::unissue( asset quantity, string memo )
+{
+    auto sym = quantity.symbol;
+    eosio_assert( sym.is_valid(), "invalid symbol name" );
+    eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" );
+
+    auto sym_name = sym.name();
+    stats statstable( _self, sym_name );
+    auto existing = statstable.find( sym_name );
+    eosio_assert( existing != statstable.end(), "token with symbol does not exist, create token before unissue" );
+    const auto& st = *existing;
+
+    require_auth( st.issuer );
+    eosio_assert( quantity.is_valid(), "invalid quantity" );
+    eosio_assert( quantity.amount > 0, "must issue positive quantity" );
+
+    eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
+    eosio_assert( quantity.amount <= st.supply.amount, "quantity exceeds available supply");
+
+    statstable.modify( st, 0, [&]( auto& s ) {
+       s.supply -= quantity;
+    });
+}
+
 void token::transfer( account_name from,
                       account_name to,
                       asset        quantity,
@@ -117,4 +144,4 @@ void token::add_balance( account_name owner, asset value, account_name ram_payer
 
 } /// namespace eosio
 
-EOSIO_ABI( eosio::token, (create)(issue)(transfer) )
+EOSIO_ABI( eosio::token, (create)(issue)(transfer)(unissue) )
diff --git a/contracts/eosio.token/eosio.token.hpp b/contracts/eosio.token/eosio.token.hpp
index 158751347..f2dd92db0 100644
--- a/contracts/eosio.token/eosio.token.hpp
+++ b/contracts/eosio.token/eosio.token.hpp
@@ -26,6 +26,8 @@ namespace eosio {
 
          void issue( account_name to, asset quantity, string memo );
 
+         void unissue( asset quantity, string memo );
+
          void transfer( account_name from,
                         account_name to,
                         asset        quantity,

@ramtej
Copy link

ramtej commented Jun 6, 2018

Impressive analysis - thank you. Do you see the additional RAM supply as a critical issue? To print some additional EOS Tokens is indeed not elegant. What would be your proposal? Thank you again.

Cheers,
Jiri

@redjade
Copy link
Author

redjade commented Jun 7, 2018

Thank you for comment, @ramtej.

If community consensus goes with RAM cost gift to genesis user accounts, to print some additional EOS Tokens is inevitable.

For me, variables effecting price of RAM and inflation is quite critical.

My primary focus of this gist notes and two patches is on price of RAM and inflation. Because if there were wrong initial values, price paying ram by users goes wrong and inflation/rewards goes wrong too. Discussions so far didn't grasp RAM price and associated mechanisms, thus I wrote this gist.

Some told me that 'floating EOS' is also a problem.

  • "floating EOS that isn't accounted for by the suppy" is because token.supply != (sum of user accounts and system accounts)
  • "floating EOS that isn't accounted for by the suppy" is because there is some EOS on system accounts

So I'm working on this issue too.

Kyunghwan Kim
EOSeoul

@redjade
Copy link
Author

redjade commented Jun 7, 2018

Finally, done and tested local.

https://github.com/eoseoul/eos-ramcost

@DutchEOS
Copy link

Great analysis. Thank you for that.
Do you have also any idea why the start balance of "m.quote.balance.amount" should be equal to "system_token_supply / 1000"? Is there any logic to this? Why not "system_token_supply / 10000" or "1" or "0" ?
Any views would be much appreciated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment