 # Dynamic Issuance Policy v2: From math to code in one post

Some days ago I pointed out some inconsistencies I found in the issuance policy 1hive is using at the moment. After some days of thinking about it, I’d like to share with the community my work until now. This post has two purposes:

• Propose a new issuance policy for 1hive and the rest of gardens
• Educate about the process I’ve followed to obtain a result like this.

## Our new Dynamic Issuance Policy?

As the old policy, the proposed Dynamic Issuance v2 tends over time towards a particular ratio between the token balance of the common pool and the total supply (or circulating supply) of the token.

We call this ratio towards the issuance mints or burns tokens the target ratio, a number between 0 and 1.

The new issuance policy is configured using two parameters, target ratio, already used in the previous version, and recovery time, a new parameter that sets the maximum time that it cost for the ratio to recover the target percent.

The formulas that determine the ratio over time when there are no inflows or outflows on the common pool are as follow:

• When we start with a ratio below the target ratio (we mint tokens into the common pool):
• When we start with a ratio above the target ratio (we burn tokens from the common pool):

Note that both of these formulas only apply within the recovery time, after this period, the new ratio is target ratio.

You can observe how these formulas work in this desmos. You can use target ratio to move the ratio vertically and recovery time to move the point in which curves touch each other.

It’s very unlikely that our common pool is totally full or totally empty at any point, but those equations determine the path that the ratio will travel during time starting from any point. So if we start with a ratio of 20% (current ratio), we see how the curve travels a shorter path (blue lines in the chart above).

The “blue line” formula is a bit more complex as we have to translate the equation horizontally until it matches the current ratio in the Y-axis. It is the following piecewise formula: This can be gracefully translated to Solidity code, as we can see here (or in remix):

``````pragma solidity ^0.4.24;

import "@aragon/os/contracts/lib/math/SafeMath.sol";

contract DynamicIssuance {
using SafeMath for uint256;

uint256 constant public RATIO_PRECISION = 1e10;

uint256 public targetRatio;
uint256 public recoveryTime;

constructor(uint256 _targetRatio, uint256 _recoveryTime) public {
targetRatio = _targetRatio;
recoveryTime = _recoveryTime;
}

function calculateRatio(uint256 _lastRatio, uint256 _time) public view returns (uint256 _ratio) {
uint256 shared;
_ratio = _lastRatio;
if (_ratio < targetRatio) {
// _time < recoveryTime * sqrt(targetRatio * (targetRatio - _ratio))
shared = recoveryTime.mul(_sqrt(targetRatio.mul(targetRatio.sub(_ratio))));
if (_time < shared.div(targetRatio)) {
// (_ratio * recoveryTime ** 2 + 2 * _time * shared - targetRatio * _time ** 2) / (recoveryTime ** 2)
} else {
_ratio = targetRatio;
}
} else if (_ratio > targetRatio) {
// recoveryTime * sqrt((1 - targetRatio) * (_ratio - targetRatio))
shared = recoveryTime.mul(_sqrt(RATIO_PRECISION.sub(targetRatio).mul(_ratio.sub(targetRatio))));
if (_time < shared.div(RATIO_PRECISION.sub(targetRatio))) {
// (_ratio * recoveryTime ** 2 - 2 * _time * shared + (RATIO_PRECISION - targetRatio) * _time ** 2) / (recoveryTime ** 2)
} else {
_ratio = targetRatio;
}
} else {
_ratio = targetRatio;
}
}

function _sqrt(uint256 y) internal pure returns (uint256 z) {
if (y > 3) {
z = y;
uint256 x = y / 2 + 1;
while (x < z) {
z = x;
x = (y / x + x) / 2;
}
} else if (y != 0) {
z = 1;
}
}
}
``````

This is just a proof-of-concept code, the real smart contract has to actually mint or burn tokens to/from the common pool, in a similar way we were doing with the old policy (calling `executeAdjustment()`).

For this particular policy, we will also need to do adjustments every time that the token supply changes (some tokens are minted or burnt), or there are inflows or outflows in the common pool. This means that we will need to register the Dynamic Issuance app as a hook for the Hooked Token Manager in order to be able to act when these events occur.

## How did I arrived to this result

### Obtaining the “pristine” formulas

I think this can be a great opportunity to explain how we can design token policies using simple math models. I recommend using desmos when you do this kind of explorations during the first stage.

We know that we would like a curve, so we start with the formula y=x² because powers and square roots are easy to calculate in Solidity. If we divide the formula by `r`, we will find that the formula then passes by the points `(r,r)` and `(-r, r)`. If we want it to pass by the point `(r, t)`, which is when the recovery time (`r`) ends and the ratio (`x`) is target ratio (`t`), we have to divide by `r` again in order to obtain the point `(r, 1)`, and by `t` to obtain `(r, t)`: We now have a formula that passes by the points `(0,0)` and `(r, t)`, but it doesn’t have the shape that we want, as it should decrease it’s speed of emission of tokens as it reaches the target goal instead of increasing it.

In order to solve that issue we are going to apply some geometrical transformations. We will first flip the chart upside down and then we will move the entire chart from point `(0, 0)` to point `(r, t)`. To flip the chart we just have to multiply the formula by -1: And to move the maximum of the formula, currently at point `(0, 0)`, into the point in which recovery time has passed and we reach the target ratio `(r, t)`, we can do a simple translation, substracting `r` from `x` and `t` from `y`:

The “deflationary” formula can be obtained by replacing the `-t` factor by `1-t`. It inverts the shape of the formula again because `1-t` is positive (knowing that `t≤1`), and we want the formula go from 1 to `t` so its height must be `1-t`:

### Obtaining the “left-shifted” formulas

In order to shift the formulas to the left until they touch the point `(0,c)` so we start at time 0 with a specific ratio (`c`). When you need to work with equations, Wolfram Alpha is your friend.

We isolate x from the “inflationary” formula and we obtain the following result:

We then add on the left side a shift to the point in which `f(0)=c`, and then solve `f(x-c)`:

We do the same for the “deflationary” formula, first isolate x:

And then obtain the “left-shifted” formula:

We also have to take into account that recovery time is going to be less, since we are not starting from 0% or 100%. We have to calculate where the global maximum and minimum of the formulas are in order to avoid going backwards.

The maximum of the pristine “inflationary” formula is `r`, as it is how we defined it from the beginning. The maximum of the “left-shifted” “inflationary” formula can be found equating it’s first derivative to 0. So we know now the point in which we should stop applying the formula and return target ratio instead (used in the piecewise formula above).

We can do the same for the “left-shifted” “deflationary” formula, obtaining the `x` in which: Which is the check that we do in the piecewise formula when the current ratio (`c`) is greater than target ratio (`t`).

### Coding the formula

After defining the formula, it was straightforward to write the Solidity code for it. The only think I had to take into account is that ratios are scaled with a factor of 10^10 in order to be able to operate with Solidity (the language has no native support for decimals).

So when I was dealing with the ratios in the code you have to remember that, and use `RATIO_PRECISION` constant instead of 1 to do the calculations.

Also the use of fixed point arithmetic could interfere with the function `_sqrt(uint256 y)` but we have been lucky and all the square roots we needed to do where already multiplications of two ratio-scaled factors, so the result was also ratio-scaled.

## Conclusion

This post has presented a piecewise formula to model the proposed Dynamic Issuance Policy v2, alongside with a proof-of-concept Solidity smart contract. I think it is a considerable improvement in respect to the previous version, but I would love to hear the comments of the community members in order to go forward with the proposal.

This post has also been useful to explain in a detailed way the process of obtaining the formula. Hopefully this method can be replicated in the future by other token engineers to create similar policies, not only for token issuance but also for other endeavours. I hope it becomes a helpful resource in the process of designing and implementing of a token policy from its inception to the functional code.

Edited 3rd August, 2021: @divine_comedian helped me clarify some terms that were a bit opaque in the first version.

7 Likes

Awesome work @sem. Thanks a lot for taking the time to explain your whole thinking process. I am sure it is going to be super valuable for future designs. I enjoyed playing with the demos playground The approach you took, defining the new policy as a function does a better job of predict what will be the token supply at a certain time in the future. Having this data will be of great value to analyze and create more complex models.

I support going forward with the full implementation! 2 Likes

I’ve created a funding proposal to support this work. Please leave your feedback there as well, and stake your HNY on the honeypot proposal if you like the direction this project is taking. Thank you for your interest!

2 Likes

Is this level of accuracy necessary? Considering it marginally increases the gas of every transfer transaction with Honey/the garden governance token? I’d rather avoid adding hooks wherever possible. The alternative being the current approach where anyone can call the issuance function when they like.

1 Like

The results will not be exactly the same, since having an inflow/outflow/mint/burn in a period of high inflation/deflation changes the time to recover in a different way than when it is done when the inflation/deflation is minimum, or even 0.

But I agree that calling a hook is overkilling, and it can be resolved by packing all those inflows, outflows, mintings and burnings as if they happened the last time the issuance policy was called. It is usually the time in which inflation/deflation is higher, so those changes will affect less the common pool.

Thanks for coming up with this idea, I didn’t thought that the hooks were really not necessary.

1 Like