From 0dd0fbba8d181dff4f41bc94044de2a18f61d3fe Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Fri, 9 Feb 2024 18:46:07 +0000 Subject: [PATCH] (Circles): a pendantic definition of inflationary mint --- specifications/TCIP009-demurrage.md | 111 ++++++++++++++++++++++++++++ src/circles/Circles.sol | 16 ++-- src/hub/Hub.sol | 13 +++- 3 files changed, 131 insertions(+), 9 deletions(-) create mode 100644 specifications/TCIP009-demurrage.md diff --git a/specifications/TCIP009-demurrage.md b/specifications/TCIP009-demurrage.md new file mode 100644 index 0000000..62218d3 --- /dev/null +++ b/specifications/TCIP009-demurrage.md @@ -0,0 +1,111 @@ +# Constant mint under demurrage + +## Mint 1 CRC per hour, always + +In demurraged units, Circles mints one CRC per hour per person, every day - maximally, as people need to +show up to mint. + +We can rewrite for successive days this first constraint with help of a yet unknown function `D(i)`, +we'll call the global demurrage function. +`D(i)` takes values over `i` integer numbers from zero to arbitrary positive N, where `i` is the i-th day +since day zero. We define `D(0)` to be equal to `1`. + +If we assume that `D` is a strict monotonic increasing function, and as a consequence also never +becomes zero, we can write for the (demurraged) mint each day `i`: + + day 0: D(0)/D(0) CRC/hr + day 1: D(1)/D(1) CRC/hr + ... + day N: D(N)/D(N) CRC/hr + +which is by construction, trivially, 1 CRC/hr each day, our first constraint. + +Then we can **define** the "inflationary" mint as: + + day 0: D(0) CRC/hr + day 1: D(1) CRC/hr + ... + day N: D(N) CRC/hr + +and accordingly we define the demurraged balance on day `i`, given an inflationary amount as: + + B(i) = balance(i) = inflationary_balance / D(i) + +We note that this definition of the "demurraged balance" function is linear in sums of the inflationary +balance, so all mints, Circles received and Circles spent are linear under the demurraged balance function. +We can then, without loss of generality, consider a single balance amount, as all operations are +linear combinations on the inflationary amounts. + +## Determining `D(i)` + +Our second constraint is that the demurraged balances have a 7% per annum demurrage, if it is accounted for +on a yearly basis. + +However, we want to correct for the demurrage on a daily basis. To adhere to conventional notations we will +write the conversion out twice, once as standard percentages, and once as a reduction factor, but simply to +pendantically show they are saying the same thing. + +All balances are understood as demurraged balances (as that is our constraint), and denoted with B(time). We denote 7% p.a. demurrage as γ'. + +After one year, our balance is corrected for 7% or γ': + + B(1 yr) = (1 - γ') B(0 yr) + +and the same formula, but if we would adjust the demurrage daily, what would the equivalent demurrage rate be? +We can call this unknown demurrage rate Γ' and write for `N=365.25` (days in a year): + + B(N days) = (1 - Γ'/N)^N B(0 days) + +and we know that the balances in both equations are equal (as we're only rewriting the time unit), so + + Γ' = N(1 - (1-γ')^(1/N)) + +or an equivalent demurrage rate of 7,26% per annum on a daily accounted basis. + +For our purposes we don't need to know the percentage though, we simply need to determine D(i). +If we call `γ = 1 - γ'`, and `Γ = 1- Γ'/N`, then we can rewrite the above equations as + + B(1 yr) = γ B(0 yr) + +and + + B(N days) = Γ^N B(0 days) + +and directly see that + + Γ = γ^(1/N) = 0.99980133200859895743... + +Now we have a formula for the demurraged balances expressed in days: + + B(i + d) = Γ^i B(d) + +for any number `d` and `i` days. Again without loss of generality we can proceed with `d=0` +and write this equation for `i=1, i=2, ...` and remember that + - `B(i) = inflationary.amount / D(i)` + - and this was a linear function, so considering a constant inflationary amount is sufficient, + as any additional mints, or sending and receiving transfers over time can be written as a sum + over which the same argument holds. + +We write: + + 1/D(1) = Γ^1 1/D(0) + 1/D(2) = Γ^2 1/D(0) + ... + 1/D(n) = Γ^n 1/D(0) + 1/D(n+1) = Γ^(n+1) 1/D(0) + +We already defined `D(0) = 1`, and see that `D(n+1) = (1/Γ) D(n)`, so by induction we comclude +that the global demurrage function `D(i)` is + + D(i) = (1/Γ)^i + +## Conclusion + +So we can conclude that if we substitute this in our definition of "inflationary mint" +then one day `i` the protocol should mint as inflationary amounts + + (1/Γ)^i CRC/hour + +and the demurraged balance function can adjust these inflationary amounts as + + B(i) = Γ^i inflationary_balance \ No newline at end of file diff --git a/src/circles/Circles.sol b/src/circles/Circles.sol index b3650ef..c8d59a6 100644 --- a/src/circles/Circles.sol +++ b/src/circles/Circles.sol @@ -89,12 +89,18 @@ contract Circles is ERC1155 { * BETA_64x64 stores the numerator for the signed 128bit 64.64 * fixed decimal point expression: * beta = beta_64x64 / 2**64. - * Expressed in time[second], for 6.7% p.d. inflation: - * mint(t+1y) = (1 + 0.067) * mint(t) - * => beta = (1+ 0.067)^(1/(365.25)) - * = 1.000000002143973196515... + * First calculate the equivalent rate for compounding on a daily basis, + * rather than annually. Let R be the equivalent daily compounding rate per year: + * R = [ (1 + 0.07)^(1/365.25) - 1 ] * 365.25 + * = 0.0676649153805671... + * Expressed in time[second], for 6.8% p.a. inflation (n=365.25): + * mint(t+1y) = (1 + R/n)^(n days/yr * 1yr) * mint(t) + * => beta = (1 + R/n)^(1/(365.25*24*3600)) + * If however, we express per unit of 1 day, 6.8% p.a.: + * => beta = (1 + R)^(1/365) + * = 1.0001792739503774572... * => BETA_64x64 = beta * 2**64 - * = 18446744113258876473 + * = 18450051094391247475 */ int128 public constant BETA_64x64 = int128(0); diff --git a/src/hub/Hub.sol b/src/hub/Hub.sol index f8f1d32..0205124 100644 --- a/src/hub/Hub.sol +++ b/src/hub/Hub.sol @@ -132,14 +132,19 @@ contract Hub is Circles { /** * Constructor for the Hub contract. * @param _hubV1 address of the Hub v1 contract + * @param _demurrage_day_zero timestamp of the start of the global demurrage curve * @param _standardTreasury address of the standard treasury contract * @param _bootstrapTime duration of the bootstrap period (for v1 registration) in seconds * @param _fallbackUri fallback URI string for the ERC1155 metadata, * (todo: eg. "https://fallback.aboutcircles.com/v1/circles/{id}.json") */ - constructor(IHubV1 _hubV1, address _standardTreasury, uint256 _bootstrapTime, string memory _fallbackUri) - Circles(_fallbackUri) - { + constructor( + IHubV1 _hubV1, + uint256 _demurrage_day_zero, + address _standardTreasury, + uint256 _bootstrapTime, + string memory _fallbackUri + ) Circles(_demurrage_day_zero, _fallbackUri) { require(address(_hubV1) != address(0), "Hub v1 address can not be zero."); require(_standardTreasury != address(0), "Standard treasury address can not be zero."); @@ -441,7 +446,7 @@ contract Hub is Circles { // timestamp should be "stepfunction" the timestamp // todo: ask where the best time step is - if (_timestamp < circlesStartTime) _timestamp = block.timestamp; + if (_timestamp < demurrage_day_zero) _timestamp = block.timestamp; // uint256 durationSinceStart = _time - hubV1start; // do conversion