OpenZeppelin / openzeppelin-contracts

OpenZeppelin Contracts is a library for secure smart contract development.
https://openzeppelin.com/contracts
MIT License
24.9k stars 11.79k forks source link

feat: math lib for dynamic exponential curves #5157

Open 0xneves opened 2 months ago

0xneves commented 2 months ago

🧐 Motivation Inspired by ENS's premium decay curve, which reduces the cost of premium names over time, this feature aims to create a more general-purpose curve that can be used in various applications. Since calculating exponentials in Solidity is not easy because of its fixed-point arithmetic, developers are obliged to use simpler equations for growth or decay, and most times linear. This feature provides a more nuanced control over how parameters evolve over time, leading to more sophisticated applications and user experience.

📝 Details This feature suggests an exponential curve formula designed to handle various time-based events such as token vesting, game mechanics, unlock schedules, and other timestamp-dependent actions. The core functionality is driven by an exponential curve formula allowing for smooth, nonlinear transitions over time, providing a more sophisticated and flexible approach than linear models.

Ascending Curve

$$\frac{\exp(k \cdot \frac{t - t_0}{T - t_0}) - 1}{\exp(k) - 1} \cdot 100$$

Descending Curve

$$\frac{\exp(k \cdot (1 - \frac{t - t_0}{T - t_0})) - 1}{\exp(k) - 1} \cdot 100$$

Where:

The ascending curve starts at 0% and increases to 100% over time, while the descending curve starts at 100% and decreases to 0% over time.

The curvature factor k allows for fine-tuning the curve's shape, providing a wide range of possibilities for customizing the curve's behavior. A higher k value results in a steeper curve, while a lower k value results in a flatter curve. This flexibility enables developers to create complex time-based scenarios with precise control over the curve's progression. For better precision, the curvature factor is an integer with two (2) decimal places, allowing for a range of -100.00 to 100.00.

interface IEXPCurves {
  /**
   * @dev This function calculates the exponential decay value over time.
   *
   * This formula ensures that the value starts at 100%/0% at the beginning (t0)
   * and decreases/increases to 0%/100% at the end (T), following an exponential decay curve.
   *
   * The formula used for the curves difers based on the `ascending` parameter:
   *
   * ascending = ((exp(k * (1 - (t - t0) / (T - t0))) - 1) / (exp(k) - 1)) * 100
   * descenging = ((exp(k * ((t - t0) / (T - t0))) - 1) / (exp(k) - 1)) * 100
   *
   * Where:
   * - t is the current timestamp
   * - t0 is the start timestamp
   * - T is the end timestamp
   * - k is the curvature factor, determining the steepness of the curve (2 decimals precision)
   * - exp() is the exponential function with base 'E' (Euler's number, approximately 2.71828)
   *
   * Requirements:
   *
   * - The initial timestamp must be less than or equal to the current timestamp
   * - The initial timestamp must be less than the final timestamp
   * - The curvature cannot be zero
   * - The curvature cannot be bigger than 10000 or smaller than -10000 (2 decimals precision)
   *
   * NOTE: To avoid precision issues, the formula uses fixed-point math with 18 decimals.
   * When returning this function result, make sure to adjust the output values accordingly.
   *
   * NOTE: Using type uint32 for timestamps since 4294967295 unix seconds will only overflow
   * in the year 2106, which is more than enough for the current use cases.
   *
   * @param currentTimeframe The current timestamp or a point within the spectrum
   * @param initialTimeframe The initial timestamp or the beginning of the curve
   * @param finalTimeframe The final timestamp or the end of the curve
   * @param curvature The curvature factor. Determines the steepness of the curve and can be
   * negative, which will invert the curve's direction.
   * @param ascending The curve direction (ascending or descending)
   * @return int256 The exponential decay value at a specific interval
   */
  function expcurve(
    uint32 currentTimeframe,
    uint32 initialTimeframe,
    uint32 finalTimeframe,
    int16 curvature,
    bool ascending
  ) external pure returns (int256);
}

Definition of Done

My truly honest question here is:

🔗 Reference https://github.com/0xneves/EXPCurves/blob/main/contracts/EXPCurves.sol

ernestognw commented 2 months ago

Hi @0xneves, thanks for opening the issue.

I like the idea of the curve as a generic primitive since it can be used to build interesting DeFi projects like Uniswap Hooks. I'm wondering if the potential users of this function wouldn't otherwise build their own dedicated curve. I have the impression that any curve in a protocol should remain constant, in which case it'd make more sense to implement the function straight.

It'd be great if you have a concrete case where the behavior of the curve is dynamic, so that an audited implementation by OpenZeppelin is required.


Answering your questions:

Does it make sense to be in OpenZeppelin?

So far nobody has requested it, and normally, math-heavy functions have been a good fit for other community-maintained libraries (see prb-math as an example). I see how future plans may benefit from this primitive but that's not the only thing we consider before merging a PR.

If yes, what is the thrill of implementing it? Should be me via PR, or the team?

We expect community involvement and discussion first. A PR is welcome when we have a clear understanding of the problem we're tackling and there's enough community input for us to ship an implementation comfortably. This is documented in our CONTRIBUTING docs

Once we merge and release a piece of code, it's subject to our Backwards Compatibility practices.

I see Oz is accumulating a lot of issues and PRs, is it viable to create a feature this way I'm doing? I see even members of Oz having a hard time getting things to move forward. Can I be of any assistance? For instance, answer issues and call a member of the team to close it. ( there are a few from 2018, c'mon guys )

We're happy to take PRs and guide contributors towards a successful contribution. Not all issues have to be solved and some of them become relevant based on the current market conditions. The best way of helping us moving things forward is to bring new opinions to any of the issues already open, so we can find opportunities to make the library better in a responsible and sustainable way.

0xneves commented 2 months ago

Hey @ernestognw , thank you for the very dedicated response! Really appreciated the time.

Firstly I agree that having the function in a math-heavy environment would be better. The exponential Euler itself came from Paul's repo.

But nonetheless, we need to cease the conversation here! There are a lot of cases that the curve can be a helpful standard for protocols like you said yourself, most of them use constant curves for their means but with every new upgrade in the protocol they have to deploy a new curve with different parameters. ENS is one simple example. Another one could be the Frax Vaults managers or any other protocol that needs to change investment strategies based on liquidity / APY. I've always observed linear or poorly coded curves used in games and Vesting modules because nobody has made them available to the public.

Therefore I see that the industry lacks innovative ideas not because they don't want to, but because is a hard task dealing with fixed-point-arithmetics in Solidity, making it impossible for the regular user to use exponential functions.

Having an easy-to-use programmable curve could shift the public perspective into building new innovative ideas. Although this is not a standard, it is a good-to-have. This could even be a multi-chain deployment on the same address similar to multicall3.

...

Thank you for answering all of my questions, it clarified a lot! As a Solidity developer and web3 dreamer, I use Oz lib in a daily bases, not just to use the lib but especially for references and learning material. Hence I will assist Oz more often with their repo issues. A lot of faces here I see quite often in the Ethereum Magicians so it feels like the community I can most relate to and have a true will to help improve.