From 022c7e818aa3bb937bd4f1f7aa43d6a9876c3c51 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Tue, 20 Sep 2022 20:41:49 +0200 Subject: [PATCH 1/6] added BoundedVRGDA, mock and test --- src/BoundedVRGDA.sol | 55 ++++++++++++++++++++ test/LinearBoundedVRGDA.t.sol | 73 +++++++++++++++++++++++++++ test/mocks/MockLinearBoundedVRGDA.sol | 23 +++++++++ 3 files changed, 151 insertions(+) create mode 100644 src/BoundedVRGDA.sol create mode 100644 test/LinearBoundedVRGDA.t.sol create mode 100644 test/mocks/MockLinearBoundedVRGDA.sol diff --git a/src/BoundedVRGDA.sol b/src/BoundedVRGDA.sol new file mode 100644 index 0000000..9b69aa4 --- /dev/null +++ b/src/BoundedVRGDA.sol @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {wadExp, wadLn, wadMul, unsafeWadMul, toWadUnsafe} from "solmate/utils/SignedWadMath.sol"; + +import {VRGDA} from "./VRGDA.sol"; + +/// @title Bounded Variable Rate Gradual Dutch Auction +/// @author jacopo +/// @author transmissions11 +/// @author FrankieIsLost +/// @notice Sell tokens roughly according to an issuance schedule. + +abstract contract BoundedVRGDA is VRGDA { + /*////////////////////////////////////////////////////////////// + VRGDA PARAMETERS + //////////////////////////////////////////////////////////////*/ + + /// @dev The minimum price to be paid for a token, scaled by 1e18. + /// @dev Represented as an 18 decimal fixed point number. + uint256 public immutable min; + + /// @dev The maximum price to be paid for a token, scaled by 1e18. + /// @dev Represented as an 18 decimal fixed point number. + uint256 public immutable max; + + /// @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 _min minimum price to be paid for a token, scaled by 1e18 + /// @param _max maximum price to be paid for a token, scaled by 1e18 + constructor( + int256 _targetPrice, + int256 _priceDecayPercent, + uint256 _min, + uint256 _max + ) VRGDA(_targetPrice, _priceDecayPercent) { + min = _min; + max = _max; + } + + /*////////////////////////////////////////////////////////////// + PRICING LOGIC + //////////////////////////////////////////////////////////////*/ + + /// @notice Calculate the price of a token according to the VRGDA formula, bounded by min and max values. + /// @param timeSinceStart Time passed since the VRGDA began, 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, uint256 sold) public view virtual override returns (uint256) { + uint256 VRGDAPrice = super.getVRGDAPrice(timeSinceStart, sold); + + return VRGDAPrice < max ? VRGDAPrice > min ? VRGDAPrice : min : max; + } +} diff --git a/test/LinearBoundedVRGDA.t.sol b/test/LinearBoundedVRGDA.t.sol new file mode 100644 index 0000000..12018b7 --- /dev/null +++ b/test/LinearBoundedVRGDA.t.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.15; + +import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol"; + +import {toWadUnsafe, toDaysWadUnsafe, fromDaysWadUnsafe} from "solmate/utils/SignedWadMath.sol"; + +import {MockLinearBoundedVRGDA} from "./mocks/MockLinearBoundedVRGDA.sol"; + +uint256 constant ONE_THOUSAND_YEARS = 356 days * 1000; + +contract LinearBoundedVRGDATest is DSTestPlus { + MockLinearBoundedVRGDA vrgda; + + function setUp() public { + vrgda = new MockLinearBoundedVRGDA( + 69.42e18, // Target price. + 0.31e18, // Price decay percent. + 1e18, // Min price + 100e18, // Max price + 2e18 // Per time unit. + ); + } + + function testTargetPrice() public { + // Warp to the target sale time so that the VRGDA price equals the target price. + hevm.warp(block.timestamp + fromDaysWadUnsafe(vrgda.getTargetSaleTime(1e18))); + + uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), 0); + assertRelApproxEq(cost, uint256(vrgda.targetPrice()), 0.00001e18); + } + + function testPricingBasic() public { + // Our VRGDA targets this number of mints at the given time. + uint256 timeDelta = 120 days; + uint256 numMint = 239; + + hevm.warp(block.timestamp + timeDelta); + + uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint); + assertRelApproxEq(cost, uint256(vrgda.targetPrice()), 0.00001e18); + } + + function testAlwaysTargetPriceInRightConditions(uint256 sold) public { + sold = bound(sold, 0, type(uint128).max); + + assertRelApproxEq( + vrgda.getVRGDAPrice(vrgda.getTargetSaleTime(toWadUnsafe(sold + 1)), sold), + uint256(vrgda.targetPrice()), + 0.00001e18 + ); + } + + function testPricingBasicMin() public { + uint256 timeDelta = 120 days; + uint256 numMint = 216; + + hevm.warp(block.timestamp + timeDelta); + + uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint); + assertEq(cost, uint256(vrgda.min())); + } + + function testPricingBasicMax() public { + uint256 timeDelta = 120 days; + uint256 numMint = 241; + + hevm.warp(block.timestamp + timeDelta); + + uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint); + assertEq(cost, uint256(vrgda.max())); + } +} diff --git a/test/mocks/MockLinearBoundedVRGDA.sol b/test/mocks/MockLinearBoundedVRGDA.sol new file mode 100644 index 0000000..727988b --- /dev/null +++ b/test/mocks/MockLinearBoundedVRGDA.sol @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.0; + +import {unsafeWadDiv} from "solmate/utils/SignedWadMath.sol"; +import {BoundedVRGDA} from "../../src/BoundedVRGDA.sol"; + +contract MockLinearBoundedVRGDA is BoundedVRGDA { + int256 internal immutable perTimeUnit; + + constructor( + int256 _targetPrice, + int256 _priceDecayPercent, + uint256 _min, + uint256 _max, + int256 _perTimeUnit + ) BoundedVRGDA(_targetPrice, _priceDecayPercent, _min, _max) { + perTimeUnit = _perTimeUnit; + } + + function getTargetSaleTime(int256 sold) public view virtual override returns (int256) { + return unsafeWadDiv(sold, perTimeUnit); + } +} From 2696b3201635f512bb223b20f43105cb8bc1b841 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Tue, 20 Sep 2022 20:42:00 +0200 Subject: [PATCH 2/6] updated snapshot --- .gas-snapshot | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gas-snapshot b/.gas-snapshot index 0147c3a..e072219 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,3 +1,8 @@ +LinearBoundedVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 10293, ~: 10293) +LinearBoundedVRGDATest:testPricingBasic() (gas: 10089) +LinearBoundedVRGDATest:testPricingBasicMax() (gas: 9782) +LinearBoundedVRGDATest:testPricingBasicMin() (gas: 9871) +LinearBoundedVRGDATest:testTargetPrice() (gas: 10933) LinearNFTTest:testCannotUnderpayForNFTMint() (gas: 37841) LinearNFTTest:testMintManyNFT() (gas: 4177040) LinearNFTTest:testMintNFT() (gas: 83907) From b96d7de25e15c3572e1ebcd75063fd1b2aca29b2 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Wed, 21 Sep 2022 00:01:29 +0200 Subject: [PATCH 3/6] removed max price --- .gas-snapshot | 9 ++++----- src/BoundedVRGDA.sol | 13 +++---------- test/LinearBoundedVRGDA.t.sol | 11 ----------- test/mocks/MockLinearBoundedVRGDA.sol | 3 +-- 4 files changed, 8 insertions(+), 28 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index e072219..6c7c0ad 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,8 +1,7 @@ -LinearBoundedVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 10293, ~: 10293) -LinearBoundedVRGDATest:testPricingBasic() (gas: 10089) -LinearBoundedVRGDATest:testPricingBasicMax() (gas: 9782) -LinearBoundedVRGDATest:testPricingBasicMin() (gas: 9871) -LinearBoundedVRGDATest:testTargetPrice() (gas: 10933) +LinearBoundedVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 10225, ~: 10225) +LinearBoundedVRGDATest:testPricingBasic() (gas: 10066) +LinearBoundedVRGDATest:testPricingBasicMin() (gas: 9848) +LinearBoundedVRGDATest:testTargetPrice() (gas: 10798) LinearNFTTest:testCannotUnderpayForNFTMint() (gas: 37841) LinearNFTTest:testMintManyNFT() (gas: 4177040) LinearNFTTest:testMintNFT() (gas: 83907) diff --git a/src/BoundedVRGDA.sol b/src/BoundedVRGDA.sol index 9b69aa4..3c0ca96 100644 --- a/src/BoundedVRGDA.sol +++ b/src/BoundedVRGDA.sol @@ -20,36 +20,29 @@ abstract contract BoundedVRGDA is VRGDA { /// @dev Represented as an 18 decimal fixed point number. uint256 public immutable min; - /// @dev The maximum price to be paid for a token, scaled by 1e18. - /// @dev Represented as an 18 decimal fixed point number. - uint256 public immutable max; - /// @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 _min minimum price to be paid for a token, scaled by 1e18 - /// @param _max maximum price to be paid for a token, scaled by 1e18 constructor( int256 _targetPrice, int256 _priceDecayPercent, - uint256 _min, - uint256 _max + uint256 _min ) VRGDA(_targetPrice, _priceDecayPercent) { min = _min; - max = _max; } /*////////////////////////////////////////////////////////////// PRICING LOGIC //////////////////////////////////////////////////////////////*/ - /// @notice Calculate the price of a token according to the VRGDA formula, bounded by min and max values. + /// @notice Calculate the price of a token according to the VRGDA formula, bounded by min value. /// @param timeSinceStart Time passed since the VRGDA began, 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, uint256 sold) public view virtual override returns (uint256) { uint256 VRGDAPrice = super.getVRGDAPrice(timeSinceStart, sold); - return VRGDAPrice < max ? VRGDAPrice > min ? VRGDAPrice : min : max; + return VRGDAPrice > min ? VRGDAPrice : min; } } diff --git a/test/LinearBoundedVRGDA.t.sol b/test/LinearBoundedVRGDA.t.sol index 12018b7..bf8b3b8 100644 --- a/test/LinearBoundedVRGDA.t.sol +++ b/test/LinearBoundedVRGDA.t.sol @@ -17,7 +17,6 @@ contract LinearBoundedVRGDATest is DSTestPlus { 69.42e18, // Target price. 0.31e18, // Price decay percent. 1e18, // Min price - 100e18, // Max price 2e18 // Per time unit. ); } @@ -60,14 +59,4 @@ contract LinearBoundedVRGDATest is DSTestPlus { uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint); assertEq(cost, uint256(vrgda.min())); } - - function testPricingBasicMax() public { - uint256 timeDelta = 120 days; - uint256 numMint = 241; - - hevm.warp(block.timestamp + timeDelta); - - uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint); - assertEq(cost, uint256(vrgda.max())); - } } diff --git a/test/mocks/MockLinearBoundedVRGDA.sol b/test/mocks/MockLinearBoundedVRGDA.sol index 727988b..a3938b5 100644 --- a/test/mocks/MockLinearBoundedVRGDA.sol +++ b/test/mocks/MockLinearBoundedVRGDA.sol @@ -11,9 +11,8 @@ contract MockLinearBoundedVRGDA is BoundedVRGDA { int256 _targetPrice, int256 _priceDecayPercent, uint256 _min, - uint256 _max, int256 _perTimeUnit - ) BoundedVRGDA(_targetPrice, _priceDecayPercent, _min, _max) { + ) BoundedVRGDA(_targetPrice, _priceDecayPercent, _min) { perTimeUnit = _perTimeUnit; } From ecdf0ea2ed44a6185b9d554521d1dc5458b94796 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 22 Jan 2023 14:57:17 +0100 Subject: [PATCH 4/6] comments --- src/BoundedVRGDA.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/BoundedVRGDA.sol b/src/BoundedVRGDA.sol index 3c0ca96..3e0a3b6 100644 --- a/src/BoundedVRGDA.sol +++ b/src/BoundedVRGDA.sol @@ -6,7 +6,6 @@ import {wadExp, wadLn, wadMul, unsafeWadMul, toWadUnsafe} from "solmate/utils/Si import {VRGDA} from "./VRGDA.sol"; /// @title Bounded Variable Rate Gradual Dutch Auction -/// @author jacopo /// @author transmissions11 /// @author FrankieIsLost /// @notice Sell tokens roughly according to an issuance schedule. @@ -16,7 +15,7 @@ abstract contract BoundedVRGDA is VRGDA { VRGDA PARAMETERS //////////////////////////////////////////////////////////////*/ - /// @dev The minimum price to be paid for a token, scaled by 1e18. + /// @dev The minimum sale price of a token. /// @dev Represented as an 18 decimal fixed point number. uint256 public immutable min; From 4d09a7d492199ac9c6c71033019edaa47d1282b8 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 22 Jan 2023 15:08:32 +0100 Subject: [PATCH 5/6] removed unneeded tests --- test/LinearBoundedVRGDA.t.sol | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/test/LinearBoundedVRGDA.t.sol b/test/LinearBoundedVRGDA.t.sol index bf8b3b8..41d79d5 100644 --- a/test/LinearBoundedVRGDA.t.sol +++ b/test/LinearBoundedVRGDA.t.sol @@ -21,14 +21,6 @@ contract LinearBoundedVRGDATest is DSTestPlus { ); } - function testTargetPrice() public { - // Warp to the target sale time so that the VRGDA price equals the target price. - hevm.warp(block.timestamp + fromDaysWadUnsafe(vrgda.getTargetSaleTime(1e18))); - - uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), 0); - assertRelApproxEq(cost, uint256(vrgda.targetPrice()), 0.00001e18); - } - function testPricingBasic() public { // Our VRGDA targets this number of mints at the given time. uint256 timeDelta = 120 days; @@ -40,20 +32,11 @@ contract LinearBoundedVRGDATest is DSTestPlus { assertRelApproxEq(cost, uint256(vrgda.targetPrice()), 0.00001e18); } - function testAlwaysTargetPriceInRightConditions(uint256 sold) public { - sold = bound(sold, 0, type(uint128).max); - - assertRelApproxEq( - vrgda.getVRGDAPrice(vrgda.getTargetSaleTime(toWadUnsafe(sold + 1)), sold), - uint256(vrgda.targetPrice()), - 0.00001e18 - ); - } - - function testPricingBasicMin() public { + function testMinPrice() public { uint256 timeDelta = 120 days; uint256 numMint = 216; + // Warp to a sale time where the decreased VRGDA price should be less than the min. hevm.warp(block.timestamp + timeDelta); uint256 cost = vrgda.getVRGDAPrice(toDaysWadUnsafe(block.timestamp), numMint); From 860546be9f8efd3807df770248c9b28f279d4162 Mon Sep 17 00:00:00 2001 From: jj_ranalli Date: Sun, 22 Jan 2023 15:42:47 +0100 Subject: [PATCH 6/6] snapshot --- .gas-snapshot | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.gas-snapshot b/.gas-snapshot index 2c03956..66d8e94 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,7 +1,5 @@ -LinearBoundedVRGDATest:testAlwaysTargetPriceInRightConditions(uint256) (runs: 256, μ: 10225, ~: 10225) -LinearBoundedVRGDATest:testPricingBasic() (gas: 10066) -LinearBoundedVRGDATest:testPricingBasicMin() (gas: 9848) -LinearBoundedVRGDATest:testTargetPrice() (gas: 10798) +LinearBoundedVRGDATest:testMinPrice() (gas: 9826) +LinearBoundedVRGDATest:testPricingBasic() (gas: 10043) LinearNFTTest:testCannotUnderpayForNFTMint() (gas: 37841) LinearNFTTest:testMintManyNFT() (gas: 4177040) LinearNFTTest:testMintNFT() (gas: 83907) @@ -24,6 +22,6 @@ LogisticVRGDATest:testFailOverflowForBeyondLimitTokens(uint256,uint256) (runs: 2 LogisticVRGDATest:testGetTargetSaleTimeDoesNotRevertEarly() (gas: 6102) LogisticVRGDATest:testGetTargetSaleTimeRevertsWhenExpected() (gas: 8488) LogisticVRGDATest:testNoOverflowForAllTokens(uint256,uint256) (runs: 256, μ: 11161, ~: 11161) -LogisticVRGDATest:testNoOverflowForMostTokens(uint256,uint256) (runs: 256, μ: 11280, ~: 11117) +LogisticVRGDATest:testNoOverflowForMostTokens(uint256,uint256) (runs: 256, μ: 11283, ~: 11117) LogisticVRGDATest:testPricingBasic() (gas: 10718) LogisticVRGDATest:testTargetPrice() (gas: 12157)