unlock-protocol / locked.fyi

A basic notes application where notes are stored on IPFS and only visible by members of a lock
https://locked.fyi/
MIT License
21 stars 7 forks source link

Implement Bonding Curve Pricing #43

Closed nfurfaro closed 4 years ago

nfurfaro commented 4 years ago

Related to #34 I've done some initial reading on bonding curves in general, focusing on finding the best way to meet the requirements for Locked.fyi. I've not yet settled on a solution, but have learned a few things of interest:

1.) While reading up on bonding curves I found this note to developers: "Be warned, implementing complex math formulas in Solidity is gas expensive and becomes very complicated very quickly. If you want to implement a complex curve I would recommend looking at Vyper." While I'm not writing off using Solidity for this yet, this is all the more reason to try to keep this very simple. If it turns out that we can't meet our needs with Solidity we may have to consider Vyper, but I don't think this will be the case yet.

2.) (P=price, S=supply) With a purely log-function based curve (P = log(S)), we get mostly desirable outcomes for key price at a given supply. The second key is free, the third costs $0.31, the billionth key costs $9.00, the trillionth costs $12, etc. No upper limit on supply, but each costs less than the last. The first key is a problem (when supply = 0) because the price at this point is approaching negative infinity. This is just a property of the graph of a log function. We could do 3 things here.

I'm looking for pre-existing solidity bonding-curve implementations to start with, and have found some examples to look over. There are also a few advanced math libraries to investigate that seem promising at first glance. The simplest solution may be to use an existing Solidity math Lib as an internal library and write our own curve function using it. Our curve will be a one-way curve, correct? That is, generally with bonding-curves, you can purchase tokens, driving the price up, or sell tokens, driving the price down. For Locked.fyi, tokens would never be sold back to the lock contract, so the price will never go down. Also, if lock members withdraw from the DAO, the reserve gets depleted but doesn't (shouldn't) affect the token price. Does this sound accurate?

3.) Accuracy: There seems to be a tradeoff between accuracy and gas-efficiency with implementing complex math in solidity (as I would expect). My gut feeling on this is that we could lean more towards performance than accuracy (basically calculate fewer decimal points to save gas)

Anyway, I just wanted to share some initial findings before going too deep down too many rabbit-holes.

cc @julien51

julien51 commented 4 years ago

Hey!

I think the log function is indeed the best approach!

The good news is that there are already a few keys sold, so the math should work right outside of the box? (basically no need to worry about supply being 0 or 1 ;)) Also, yes I think the price should only go up. Every new key sold (even for renewals) pushes the price up a little bit.

Agreed about leaning toward less gas than more accuracy, especially once we are below 1ct of difference.

nfurfaro commented 4 years ago

Ok cool. with a few keys sold already things are simplified a bit. As for the log function: I'm attempting to find and use a pre-existing solidity library that implements this for us. I've found a couple of options. Using a log function with a base of 10 gets us what we want, in that the Billionth key would cost $10, but there aren't any libs that implement this yet. There are some implementations using a log function with a base of 2, so something like this could work: assuming we're using a binary logarithm like image

p = log(S) / 3 This gets us very close if we divide the result by 3 (otherwise the billionth key costs almost $30). for: s=2, p=0.33 s=10, p=1.1 s=1000, p=3.32 s=1M, p=6.64 s=1B, p=9.965

nfurfaro commented 4 years ago

This is one of the first articles I found when looking for fixed point math libs: https://medium.com/cementdao/fixed-point-math-in-solidity-616f4508c6e8

Then in this follow-up, the author of the first lib recommends a better one here: https://medium.com/hackernoon/10x-better-fixed-point-math-in-solidity-32441fd25d43

10x better sounds like a good starting point, so I'm proceeding with the recommended lib as a starting point: https://github.com/abdk-consulting/abdk-libraries-solidity/blob/master/ABDKMath64x64.md

And here, some guidance on using the lib with arbitrarily large numbers: https://medium.com/@mikhail.vladimirov/the-author-of-the-library-is-here-regarding-2%E2%81%B6%E2%81%B4-limitation-72060d7c760

https://medium.com/@albertocuestacanada/hi-mikhail-thanks-for-your-feedback-7355b3432658

nfurfaro commented 4 years ago

For now I've settled on this formula, pending some gas tests: P=log2(S)/3.321 (This is because the library I'm using, for now, seems to be the most performant, but doesn't implement a base-10 log function, only a base-2 version of log().)

For a comparison to a base-10 log function see below.

Where P= price & S = supply:

-------------------log10(s)-----log2(s)/3.321
s = 1 -------------p = 0.301-----p = 0.301
s = 10 ------------p = 1---------p = 1.00027
s = 100 -----------p = 2--------p = 2.00055
s = 1000----------p = 3--------p = 3.00083
s = 1000000------p = 6--------p = 6.00167
s = 1000000000-- p = 9-------p = 9.00251
julien51 commented 4 years ago

That's good enough!

julien51 commented 4 years ago

Note that in math logA(x) = logB(x)/logB(A), in other words, log10(s) = log2(s)/log2(10) and log2(10) can be approximated is actually 3.321928094887362.

In practice, I think it's better to "hardcode" 3.321928094887362 rather than cmpute it (gas!)

nfurfaro commented 4 years ago

Agreed about saving gas by hardcoding the value. I had hardcoded 3.321already, I'll modify it to be 3.321928094887362 as the extra precision doesn't cost us anything.

// The 64.64-bit representation of 3.321
  // (61261637068789420000 / 2^64 = 3.321)
  // The bonding curve we use here is P=log2(S)/3.321,
  // which aproximates a base-10 logarithm closely.
  int128 private constant CURVE_MODIFER = 61261637068789420000;
nfurfaro commented 4 years ago

Closing as this is done.