steemit / steem

The blockchain for Smart Media Tokens (SMTs) and decentralized applications.
https://steem.com
Other
1.95k stars 792 forks source link

Numerical stability of SMT vesting #2212

Closed theoreticalbts closed 6 years ago

theoreticalbts commented 6 years ago

Allowing SMT vesting raises some potential problems in numerical stability. Basically, vesting parameters need to be "just right," or you will get Problems. I use the terminology from the Steem intro in #2160:

pot = total_vesting_fund_steem
claims = VESTS
value of claims = Steem Power

For Steem, emission to SP did not occur until a pre-programmed time / block height STEEM_START_VESTING_BLOCK, and we made sure to power up a good amount of Steem before that time. This solved (a), with manual intervention.

Additionally, in a hardfork we did a 1 to 1 million split of VESTS early in Steem's history to avoid problem (c).

Here are some questions we need to think about:

Kiwonik commented 6 years ago

@theoreticalbts I've found at least a partial answer to question a) in dynamic_global_property_object::get_vesting_share_price. There's a check whether the pot (or total number of claims) is zero. Interesting though that its sister method get_reward_vesting_share_price has no similar check. Any idea why?

Kiwonik commented 6 years ago

@youkaicountry @mvandeberg @theoreticalbts There's an important question here, although not expressed explicitly yet:

And if the answer is positive we get immediately more questions:

theoreticalbts commented 6 years ago

As implemented in #2233 the get_vesting_share_price() makes no provision to be stable for small values.

As @kiwonik mentions above, we also need to more clearly specify whether the initial ratio is satoshi-for-satoshi, token-for-token, or something else. This also falls under #2212.

This is distinct from vesting precision:

The current code in #2233 implements satoshi-for-satoshi, and it is desirable to have token-for-token. But I suspect stability concerns will place restrictions on the ratio. We need to carefully analyze stability and overflow issues before we can definitively say that token-for-token is OK.

mvandeberg commented 6 years ago

When Steem was launched VESTS were satoshi-for-satoshi and quickly needed to be changed to 1 STEEM : 1000000 VESTS due to the high inflation. We might be able to get away with something in between for SMTs (1 : 1000). What is the maximum inflation rate allowed by the SMT spec? We may have to come up with a some automatic share split logic for the extremes.

theoreticalbts commented 6 years ago

What is the maximum inflation rate allowed by the SMT spec?

"Inflation rate" is a not a concept that exists at the blockchain level. "Emission schedule" would be a more accurate term for what the blockchain actually does. Every time some event occurs (block production for Steem, an automatic action for SMT's), it triggers a state update which adds new tokens to some balance(s). (How many new tokens are created by the update is according to a function of time and existing supply.)

"Inflation rate" is a concept which describes exponential growth (in economic or financial contexts, usually normalized to a one-year time-scale). A nonzero inflation rate is a consequence of an emission schedule where the function which determines the number of tokens to create is chosen to be proportional to the current supply.

Maybe the question you're trying to formulate is, what is the relationship between:

Specifically:

mvandeberg commented 6 years ago

What is the maximum inflation rate allowed by the SMT spec?

"Inflation rate" is a not a concept that exists at the blockchain level.

Let me be more specific then because I think this is a relatively simple question. What is the maximum annual SMT supply growth rate given the existing parameterization?

The reason I ask this question is because I want to define the extreme corner cases of numerical instability so that we can formulate a solution that adequately addresses the cases. I am not suggesting we necessarily even need to restrict the current parameterization to avoid such a scenario. All I want to know is how extreme of a scenario we currently allow.

mvandeberg commented 6 years ago

The reason I asked the question using the term "inflation rate" is because since we allow a percent based emission scheduled, the absolute emission rate is unbounded. In which case the percent based rate is a better way to bound the emission rate.

theoreticalbts commented 6 years ago

Your intuition for thinking about this in percentage terms is good. Because it is fundamentally a geometric phenomenon. A contribution which increases the pot by K% also reduces the redemption ratio by K%.

The problem is that "a K% increase" can have K be enormous if the base amount is small in absolute terms.

To be more detailed, let p be the pot, and c be the claims. Let r(p, c) = c/p be the exchange rate (number of claims per pot token). Suppose the pot increases by Δp = kp. Then:

r(p + Δp, c) = c/(p + Δp)
             = c/(p + kp)
             = c/((1+k)p)
             = (1/(1+k)) (c/p)
             = r(p, c) / (1+k)

To keep the percentage increases bounded, we must remove small-p situations from occurring. To do this, I propose initializing the pot with some "ballast" of claims and tokens, c_0 and p_0, respectively. The purpose of the ballast is to provide stability to the calculations by bounding the possible percentage increase of the pot.

The ballast is not owned by any user, and cannot be withdrawn. Since the balance tokens can never "escape" the pot, they can never be directly interacted with. The ballast will cause effects on the vesting exchange rate, supply limitation, and percent-of-supply emissions. Whether the ballast "exists" is actually a semantic question that depends on precisely what is meant by the word "exists."

What is the worst-case exchange rate? The worst-case exchange rate occurs when the remaining tokens, max_supply - p_0, are emitted into the pot. In this case, we have an exchange rate of r(max_supply, c_0) = c_0 / max_supply. If we want to limit the worst-case exchange rate to some value r_min, we must set r_min = c_0 / max_supply. Since r_min and max_supply are settable parameters, this implies c_0 = r_min * max_supply.

But of course we cannot allow c_0 to become arbitrarily large, since the worst-case number of claims occurs when the remaining tokens, max_supply - p_0, are emitted into user balances, and the users then vest them at the large initial exchange rate r(p_0, c_0) = p_0 / c_0. Since the exchange rate doesn't change, we have r(max_supply, c) = r(p_0, c_0), which can be re-written as c / max_supply = c_0 / p_0 and solved to find c = max_supply * c_0 / p_0. Substituting c_0 = r_min * max_supply, we find c = max_supply^2 * r_min. If our integer data type cannot hold integers greater than max_value, this means we have the following constraint:

r_min * max_supply^2 <= max_value

The upshot of this is that claims need to use 128-bit values in general case, and max_supply cannot be too close to 2^64 if we want r_min to be large.

Based on the analysis in this ticket, I recommend the following:

mvandeberg commented 6 years ago

This is a reasonable proposal and is actually what we did for STEEM.

price get_vesting_share_price() const
{
   if ( total_vesting_fund_steem.amount == 0 || total_vesting_shares.amount == 0 )
      return price ( asset( 1000, STEEM_SYMBOL ), asset( 1000000, VESTS_SYMBOL ) );

   return price( total_vesting_shares, total_vesting_fund_steem );
}

In effect, c_0 = 1000000 and p_0 = 1000. What made the price so unstable so quickly was how high the inflation rate was in the initial weeks of Steem. In effect, we implemented the proposal above without changing how the actual supply is calculated.

Where this implementation changes behavior is in the extreme case where all claims are withdrawn and the pot is still increasing from emissions. For STEEM this is not an issue because the block producer is paid in VESTS every block, so it is impossible to reach a point where there are 0 claims. This is not necessarily the case with an SMT. If c_0 and p_0 are actual tokens then any emissions to the pot when there 0 user claims go entirely to c_0 and are effectively burned. If c_0 and p_0 don't actually exist but are only used for the base price, then all emissions when there are no claims go to the next user to create any claims against the pot. They will buy in at r(p_0, c_0) but their claims will instantly be worth the entire pot.

youkaicountry commented 6 years ago

Closed, issue #2455 created.