-
Notifications
You must be signed in to change notification settings - Fork 84
Move VRGDA logic into a lib #14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
0xBeans
wants to merge
8
commits into
transmissions11:master
Choose a base branch
from
0xBeans:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
8ea9f05
fix underflow
0xBeans 3e87d11
uncheck
0xBeans 9f3cd40
unchecked block
0xBeans dca5b24
move vrgda logic over to lib
0xBeans 20fb7c8
merge
0xBeans 5f48932
merge
0xBeans 9c2b70f
remove comment
0xBeans 32f975b
remove base vrgda impl
0xBeans File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,18 +1,18 @@ | ||
| LinearNFTTest:testCannotUnderpayForNFTMint() (gas: 37841) | ||
| LinearNFTTest:testMintManyNFT() (gas: 4177040) | ||
| LinearNFTTest:testMintNFT() (gas: 83907) | ||
| LinearVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 10109, ~: 10109) | ||
| LinearVRGDATest:testPricingBasic() (gas: 9972) | ||
| LinearVRGDATest:testTargetPrice() (gas: 10749) | ||
| LogisticNFTTest:testCannotMintMoreThanMax() (gas: 4409801) | ||
| LogisticNFTTest:testCannotUnderpayForNFTMint() (gas: 38508) | ||
| LogisticNFTTest:testMintAllNFT() (gas: 4399038) | ||
| LogisticNFTTest:testMintNFT() (gas: 84596) | ||
| LogisticVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 11692, ~: 11692) | ||
| LogisticVRGDATest:testFailOverflowForBeyondLimitTokens(uint256,uint256) (runs: 256, μ: 10278, ~: 10278) | ||
| LogisticVRGDATest:testGetTargetSaleTimeDoesNotRevertEarly() (gas: 6147) | ||
| LogisticVRGDATest:testGetTargetSaleTimeRevertsWhenExpected() (gas: 8533) | ||
| LogisticVRGDATest:testNoOverflowForAllTokens(uint256,uint256) (runs: 256, μ: 11229, ~: 11229) | ||
| LogisticVRGDATest:testNoOverflowForMostTokens(uint256,uint256) (runs: 256, μ: 11385, ~: 11528) | ||
| LogisticVRGDATest:testPricingBasic() (gas: 10762) | ||
| LogisticVRGDATest:testTargetPrice() (gas: 12246) | ||
| LinearNFTTest:testCannotUnderpayForNFTMint() (gas: 41772) | ||
| LinearNFTTest:testMintManyNFT() (gas: 4325717) | ||
| LinearNFTTest:testMintNFT() (gas: 87846) | ||
| LinearVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 14854, ~: 14854) | ||
| LinearVRGDATest:testPricingBasic() (gas: 14050) | ||
| LinearVRGDATest:testTargetPrice() (gas: 15494) | ||
| LogisticNFTTest:testCannotMintMoreThanMax() (gas: 4576176) | ||
| LogisticNFTTest:testCannotUnderpayForNFTMint() (gas: 42690) | ||
| LogisticNFTTest:testMintAllNFT() (gas: 4563891) | ||
| LogisticNFTTest:testMintNFT() (gas: 88788) | ||
| LogisticVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 16765, ~: 16765) | ||
| LogisticVRGDATest:testFailOverflowForBeyondLimitTokens(uint256,uint256) (runs: 256, μ: 14359, ~: 14359) | ||
| LogisticVRGDATest:testGetTargetSaleTimeDoesNotRevertEarly() (gas: 9460) | ||
| LogisticVRGDATest:testGetTargetSaleTimeRevertsWhenExpected() (gas: 11677) | ||
| LogisticVRGDATest:testNoOverflowForAllTokens(uint256,uint256) (runs: 256, μ: 15489, ~: 15489) | ||
| LogisticVRGDATest:testNoOverflowForMostTokens(uint256,uint256) (runs: 256, μ: 15662, ~: 15788) | ||
| LogisticVRGDATest:testPricingBasic() (gas: 15022) | ||
| LogisticVRGDATest:testTargetPrice() (gas: 17319) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,44 +1,55 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity >=0.8.0; | ||
|
|
||
| import {unsafeWadDiv} from "./utils/SignedWadMath.sol"; | ||
|
|
||
| import {VRGDA} from "./VRGDA.sol"; | ||
| import {wadExp, wadLn, wadMul, unsafeWadMul, unsafeWadDiv, toWadUnsafe} from "./utils/SignedWadMath.sol"; | ||
|
|
||
| /// @title Linear Variable Rate Gradual Dutch Auction | ||
| /// @author transmissions11 <[email protected]> | ||
| /// @author FrankieIsLost <[email protected]> | ||
| /// @notice VRGDA with a linear issuance curve. | ||
| abstract contract LinearVRGDA is VRGDA { | ||
| library LinearVRGDA { | ||
| /*////////////////////////////////////////////////////////////// | ||
| PRICING PARAMETERS | ||
| PRICING LOGIC | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @dev The total number of tokens to target selling every full unit of time. | ||
| /// @dev Represented as an 18 decimal fixed point number. | ||
| int256 internal immutable perTimeUnit; | ||
|
|
||
| /// @notice Sets pricing parameters for the VRGDA. | ||
| /// @param _targetPrice The target price for a token if sold on pace, scaled by 1e18. | ||
| /// @param _priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18. | ||
| /// @param _perTimeUnit The number of tokens to target selling in 1 full unit of time, scaled by 1e18. | ||
| constructor( | ||
| int256 _targetPrice, | ||
| int256 _priceDecayPercent, | ||
| int256 _perTimeUnit | ||
| ) VRGDA(_targetPrice, _priceDecayPercent) { | ||
| perTimeUnit = _perTimeUnit; | ||
| /// @notice Calculate the price of a token according to the VRGDA formula. | ||
| /// @param timeSinceStart Time passed since the VRGDA began, scaled by 1e18. | ||
| /// @param targetPrice The target price for a token if sold on pace, scaled by 1e18. | ||
| /// @param priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18. | ||
| /// @param perTimeUnit The number of tokens to target selling in 1 full unit of time, scaled by 1e18. | ||
| /// @param sold The total number of tokens that have been sold so far. | ||
| /// @return The price of a token according to VRGDA, scaled by 1e18. | ||
| function getVRGDAPrice( | ||
| int256 timeSinceStart, | ||
| int256 targetPrice, | ||
| int256 priceDecayPercent, | ||
| int256 perTimeUnit, | ||
| uint256 sold | ||
| ) public pure returns (uint256) { | ||
| unchecked { | ||
| // prettier-ignore | ||
| return uint256(wadMul(targetPrice, wadExp(unsafeWadMul(computeDecayConstant(priceDecayPercent), | ||
| // Theoretically calling toWadUnsafe with sold can silently overflow but under | ||
| // any reasonable circumstance it will never be large enough. We use sold + 1 as | ||
| // the VRGDA formula's n param represents the nth token and sold is the n-1th token. | ||
| timeSinceStart - getTargetSaleTime(toWadUnsafe(sold + 1), perTimeUnit) | ||
| )))); | ||
| } | ||
| } | ||
|
|
||
| /*////////////////////////////////////////////////////////////// | ||
| PRICING LOGIC | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @dev Given a number of tokens sold, return the target time that number of tokens should be sold by. | ||
| /// @param sold A number of tokens sold, scaled by 1e18, to get the corresponding target sale time for. | ||
| /// @param perTimeUnit The number of tokens to target selling in 1 full unit of time, scaled by 1e18. | ||
| /// @return The target time the tokens should be sold by, scaled by 1e18, where the time is | ||
| /// relative, such that 0 means the tokens should be sold immediately when the VRGDA begins. | ||
| function getTargetSaleTime(int256 sold) public view override returns (int256) { | ||
| function getTargetSaleTime(int256 sold, int256 perTimeUnit) public pure returns (int256) { | ||
| return unsafeWadDiv(sold, perTimeUnit); | ||
| } | ||
|
|
||
| /// @dev Calculate constant that allows us to rewrite a pow() as an exp(). | ||
| /// @param priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18. | ||
| /// @return The computed constant represented as an 18 decimal fixed point number. | ||
| function computeDecayConstant(int256 priceDecayPercent) public pure returns (int256) { | ||
| return wadLn(1e18 - priceDecayPercent); | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,65 +1,66 @@ | ||
| // SPDX-License-Identifier: MIT | ||
| pragma solidity >=0.8.0; | ||
|
|
||
| import {wadLn, unsafeDiv, unsafeWadDiv} from "./utils/SignedWadMath.sol"; | ||
| import {wadExp, wadLn, wadMul, unsafeWadMul, unsafeWadDiv, unsafeDiv, toWadUnsafe} from "./utils/SignedWadMath.sol"; | ||
|
|
||
| import {VRGDA} from "./VRGDA.sol"; | ||
|
|
||
| /// @title Logistic Variable Rate Gradual Dutch Auction | ||
| /// @title Linear Variable Rate Gradual Dutch Auction | ||
| /// @author transmissions11 <[email protected]> | ||
| /// @author FrankieIsLost <[email protected]> | ||
| /// @notice VRGDA with a logistic issuance curve. | ||
| abstract contract LogisticVRGDA is VRGDA { | ||
| /// @notice VRGDA with a linear issuance curve. | ||
| library LogisticVRGDA { | ||
| /*////////////////////////////////////////////////////////////// | ||
| PRICING PARAMETERS | ||
| PRICING LOGIC | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @dev The maximum number of tokens of tokens to sell + 1. We add | ||
| /// 1 because the logistic function will never fully reach its limit. | ||
| /// @dev Represented as an 18 decimal fixed point number. | ||
| int256 public immutable logisticLimit; | ||
|
|
||
| /// @dev The maximum number of tokens of tokens to sell + 1 multiplied | ||
| /// by 2. We could compute it on the fly each time but this saves gas. | ||
| /// @dev Represented as a 36 decimal fixed point number. | ||
| int256 public immutable logisticLimitDoubled; | ||
|
|
||
| /// @dev Time scale controls the steepness of the logistic curve, | ||
| /// which affects how quickly we will reach the curve's asymptote. | ||
| /// @dev Represented as an 18 decimal fixed point number. | ||
| int256 internal immutable timeScale; | ||
|
|
||
| /// @notice Sets pricing parameters for the VRGDA. | ||
| /// @param _targetPrice The target price for a token if sold on pace, scaled by 1e18. | ||
| /// @param _priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18. | ||
| /// @param _maxSellable The maximum number of tokens to sell, scaled by 1e18. | ||
| /// @param _timeScale The steepness of the logistic curve, scaled by 1e18. | ||
| constructor( | ||
| int256 _targetPrice, | ||
| int256 _priceDecayPercent, | ||
| int256 _maxSellable, | ||
| int256 _timeScale | ||
| ) VRGDA(_targetPrice, _priceDecayPercent) { | ||
| // Add 1 wad to make the limit inclusive of _maxSellable. | ||
| logisticLimit = _maxSellable + 1e18; | ||
|
|
||
| // Scale by 2e18 to both double it and give it 36 decimals. | ||
| logisticLimitDoubled = logisticLimit * 2e18; | ||
|
|
||
| timeScale = _timeScale; | ||
| /// @notice Calculate the price of a token according to the VRGDA formula. | ||
| /// @param timeSinceStart Time passed since the VRGDA began, scaled by 1e18. | ||
| /// @param targetPrice The target price for a token if sold on pace, scaled by 1e18. | ||
| /// @param priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18. | ||
| /// @param maxSellable The maximum number of tokens to sell, scaled by 1e18. | ||
| /// @param timeScale The steepness of the logistic curve, scaled by 1e18. | ||
| /// @param sold The total number of tokens that have been sold so far. | ||
| /// @return The price of a token according to VRGDA, scaled by 1e18. | ||
| function getVRGDAPrice( | ||
| int256 timeSinceStart, | ||
| int256 targetPrice, | ||
| int256 priceDecayPercent, | ||
| int256 maxSellable, | ||
| int256 timeScale, | ||
| uint256 sold | ||
| ) public pure returns (uint256) { | ||
| unchecked { | ||
| // prettier-ignore | ||
| return uint256(wadMul(targetPrice, wadExp(unsafeWadMul(computeDecayConstant(priceDecayPercent), | ||
| // Theoretically calling toWadUnsafe with sold can silently overflow but under | ||
| // any reasonable circumstance it will never be large enough. We use sold + 1 as | ||
| // the VRGDA formula's n param represents the nth token and sold is the n-1th token. | ||
| timeSinceStart - getTargetSaleTime(toWadUnsafe(sold + 1), maxSellable, timeScale) | ||
| )))); | ||
| } | ||
| } | ||
|
|
||
| /*////////////////////////////////////////////////////////////// | ||
| PRICING LOGIC | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @dev Given a number of tokens sold, return the target time that number of tokens should be sold by. | ||
| /// @param sold A number of tokens sold, scaled by 1e18, to get the corresponding target sale time for. | ||
| /// @param maxSellable The maximum number of tokens to sell, scaled by 1e18. | ||
| /// @param timeScale The steepness of the logistic curve, scaled by 1e18. | ||
| /// @return The target time the tokens should be sold by, scaled by 1e18, where the time is | ||
| /// relative, such that 0 means the tokens should be sold immediately when the VRGDA begins. | ||
| function getTargetSaleTime(int256 sold) public view override returns (int256) { | ||
| function getTargetSaleTime( | ||
| int256 sold, | ||
| int256 maxSellable, | ||
| int256 timeScale | ||
| ) public pure returns (int256) { | ||
| int256 logisticLimit = maxSellable + 1e18; | ||
|
|
||
| unchecked { | ||
| return -unsafeWadDiv(wadLn(unsafeDiv(logisticLimitDoubled, sold + logisticLimit) - 1e18), timeScale); | ||
| return -unsafeWadDiv(wadLn(unsafeDiv(logisticLimit * 2e18, sold + logisticLimit) - 1e18), timeScale); | ||
| } | ||
| } | ||
|
|
||
| /// @dev Calculate constant that allows us to rewrite a pow() as an exp(). | ||
| /// @param priceDecayPercent The percent price decays per unit of time with no sales, scaled by 1e18. | ||
| /// @return The computed constant represented as an 18 decimal fixed point number. | ||
| function computeDecayConstant(int256 priceDecayPercent) public pure returns (int256) { | ||
| return wadLn(1e18 - priceDecayPercent); | ||
| } | ||
| } |
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,7 +13,7 @@ import {LinearVRGDA} from "../LinearVRGDA.sol"; | |
| /// @author FrankieIsLost <[email protected]> | ||
| /// @notice Example NFT sold using LinearVRGDA. | ||
| /// @dev This is an example. Do not use in production. | ||
| contract LinearNFT is ERC721, LinearVRGDA { | ||
| contract LinearNFT is ERC721 { | ||
| /*////////////////////////////////////////////////////////////// | ||
| SALES STORAGE | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
@@ -22,6 +22,21 @@ contract LinearNFT is ERC721, LinearVRGDA { | |
|
|
||
| uint256 public immutable startTime = block.timestamp; // When VRGDA sales begun. | ||
|
|
||
| /*////////////////////////////////////////////////////////////// | ||
| PRICING PARAMETERS | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
||
| /// @dev The total number of tokens to target selling every full unit of time. | ||
| /// @dev Represented as an 18 decimal fixed point number. | ||
| int256 internal immutable perTimeUnit; | ||
|
|
||
| /// @dev The percent price decays per unit of time with no sales, scaled by 1e18 | ||
| int256 internal immutable priceDecayPercent; | ||
|
|
||
| /// @notice Target price for a token, to be scaled according to sales pace. | ||
| /// @dev Represented as an 18 decimal fixed point number. | ||
| int256 public immutable targetPrice; | ||
|
|
||
| /*////////////////////////////////////////////////////////////// | ||
| CONSTRUCTOR | ||
| //////////////////////////////////////////////////////////////*/ | ||
|
|
@@ -31,12 +46,11 @@ contract LinearNFT is ERC721, LinearVRGDA { | |
| "Example Linear NFT", // Name. | ||
| "LINEAR" // Symbol. | ||
| ) | ||
| LinearVRGDA( | ||
| 69.42e18, // Target price. | ||
| 0.31e18, // Price decay percent. | ||
| 2e18 // Per time unit. | ||
| ) | ||
| {} | ||
| { | ||
| targetPrice = 69.42e18; | ||
| priceDecayPercent = 0.31e18; | ||
| perTimeUnit = 2e18; | ||
| } | ||
|
|
||
| /*////////////////////////////////////////////////////////////// | ||
| MINTING LOGIC | ||
|
|
@@ -45,7 +59,13 @@ contract LinearNFT is ERC721, LinearVRGDA { | |
| function mint() external payable returns (uint256 mintedId) { | ||
| unchecked { | ||
| // Note: By using toDaysWadUnsafe(block.timestamp - startTime) we are establishing that 1 "unit of time" is 1 day. | ||
| uint256 price = getVRGDAPrice(toDaysWadUnsafe(block.timestamp - startTime), mintedId = totalSold++); | ||
| uint256 price = LinearVRGDA.getVRGDAPrice( | ||
| toDaysWadUnsafe(block.timestamp - startTime), | ||
| targetPrice, | ||
| priceDecayPercent, | ||
| perTimeUnit, | ||
| mintedId = totalSold++ | ||
| ); | ||
|
|
||
| require(msg.value >= price, "UNDERPAID"); // Don't allow underpaying. | ||
|
|
||
|
|
||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
might as well move this to a library, no? it's the same in
LogisticVRGDA.sol