-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Premium Price with EDA integration (#33)
* Testing Complete for Exponential Premium Price Oracle * Fixed * premiumprice-eda * Read me * Read me font fixed
- Loading branch information
1 parent
6af8674
commit 6b02e66
Showing
7 changed files
with
256 additions
and
84 deletions.
There are no files selected for viewing
This file contains 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
This file contains 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 |
---|---|---|
@@ -0,0 +1,54 @@ | ||
import math | ||
import matplotlib.pyplot as plt | ||
|
||
PRECISION = 10 ** 18 | ||
SECONDS_PER_DAY = 86400 | ||
|
||
def decayed_premium(start_premium, elapsed_seconds, seconds_in_period, per_period_decay_percent_wad): | ||
ratio = elapsed_seconds / seconds_in_period | ||
|
||
percent_wad_remaining_per_period = (PRECISION - per_period_decay_percent_wad) / PRECISION | ||
multiplier = (percent_wad_remaining_per_period ** ratio) | ||
|
||
price = (start_premium * multiplier) | ||
return price | ||
|
||
def calculate_prices(start_premium, end_value, num_days, seconds_in_period, per_period_decay_percent_wad): | ||
elapsed_times = [] | ||
prices = [] | ||
|
||
for day in range(num_days): | ||
for i in range(10): | ||
elapsed_time = (day + i / 10) * SECONDS_PER_DAY | ||
|
||
premium = decayed_premium(start_premium, elapsed_time, seconds_in_period, per_period_decay_percent_wad) | ||
|
||
price = max(premium, end_value) / PRECISION | ||
elapsed_times.append(day + i / 10) | ||
prices.append(price) | ||
|
||
return elapsed_times, prices | ||
|
||
start = int(input("Input a value for the start premium: ")) | ||
total = int(input("How many days would it take for the price to reach its end value: ")) | ||
num = int(input("Input a value for the number of days over which the price should decay: ")) | ||
|
||
start_premium = start * (10 ** 18) | ||
total_days = total | ||
seconds_in_period = SECONDS_PER_DAY | ||
per_period_decay_percent = 50 | ||
|
||
per_period_decay_percent_wad = int(per_period_decay_percent * PRECISION / 100) | ||
end_value = start_premium >> total_days | ||
|
||
num_days = num | ||
|
||
elapsed_times, prices = calculate_prices(start_premium, end_value, num_days, seconds_in_period, per_period_decay_percent_wad) | ||
plt.figure(figsize=(start, num)) | ||
plt.plot(elapsed_times, prices, marker='o') | ||
plt.xlabel('Elapsed Time (days)') | ||
plt.ylabel('Premium Price') | ||
plt.title('Pricing Chart') | ||
plt.grid(True) | ||
plt.xticks(range(num_days + 1)) | ||
plt.show() |
This file contains 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,116 +1,48 @@ | ||
//SPDX-License-Identifier: MIT | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ~0.8.17; | ||
|
||
import "./StablePriceOracle.sol"; | ||
import {GRACE_PERIOD} from "src/util/Constants.sol"; | ||
import {EDAPrice} from "src/lib/EDAPrice.sol"; | ||
import "solady/utils/FixedPointMathLib.sol"; | ||
import {Test, console} from "forge-std/Test.sol"; | ||
|
||
contract ExponentialPremiumPriceOracle is StablePriceOracle { | ||
uint256 immutable startPremium; | ||
uint256 immutable endValue; | ||
uint256 public immutable startPremium; | ||
uint256 public immutable endValue; | ||
uint256 constant PRECISION = 1e18; | ||
|
||
constructor(uint256[] memory rentPrices, uint256 startPremium_, uint256 totalDays) StablePriceOracle(rentPrices) { | ||
startPremium = startPremium_; | ||
endValue = startPremium_ >> totalDays; | ||
endValue = startPremium >> totalDays; | ||
} | ||
|
||
uint256 constant PRECISION = 1e18; | ||
uint256 constant bit1 = 999989423469314432; // 0.5 ^ 1/65536 * (10 ** 18) | ||
uint256 constant bit2 = 999978847050491904; // 0.5 ^ 2/65536 * (10 ** 18) | ||
uint256 constant bit3 = 999957694548431104; | ||
uint256 constant bit4 = 999915390886613504; | ||
uint256 constant bit5 = 999830788931929088; | ||
uint256 constant bit6 = 999661606496243712; | ||
uint256 constant bit7 = 999323327502650752; | ||
uint256 constant bit8 = 998647112890970240; | ||
uint256 constant bit9 = 997296056085470080; | ||
uint256 constant bit10 = 994599423483633152; | ||
uint256 constant bit11 = 989228013193975424; | ||
uint256 constant bit12 = 978572062087700096; | ||
uint256 constant bit13 = 957603280698573696; | ||
uint256 constant bit14 = 917004043204671232; | ||
uint256 constant bit15 = 840896415253714560; | ||
uint256 constant bit16 = 707106781186547584; | ||
|
||
/** | ||
* @dev Returns the pricing premium in internal base units. | ||
*/ | ||
|
||
function _premium(string memory, uint256 expires, uint256) internal view override returns (uint256) { | ||
expires = expires + GRACE_PERIOD; | ||
if (expires > block.timestamp) { | ||
return 0; | ||
} | ||
|
||
uint256 elapsed = block.timestamp - expires; | ||
uint256 premium = decayedPremium(elapsed); | ||
if (premium > endValue) { | ||
return premium - endValue; | ||
} | ||
return 0; | ||
} | ||
|
||
/** | ||
* @dev Returns the premium price at current time elapsed | ||
* @param elapsed time past since expiry | ||
*/ | ||
function decayedPremium(uint256 elapsed) public view returns (uint256) { | ||
uint256 daysPast = (elapsed * PRECISION) / 1 days; | ||
uint256 intDays = daysPast / PRECISION; | ||
uint256 premium = startPremium >> intDays; | ||
uint256 partDay = (daysPast - intDays * PRECISION); | ||
uint256 fraction = (partDay * (2 ** 16)) / PRECISION; | ||
uint256 totalPremium = _addFractionalPremium(fraction, premium); | ||
return totalPremium; | ||
} | ||
|
||
function _addFractionalPremium(uint256 fraction, uint256 premium) internal pure returns (uint256) { | ||
if (fraction & (1 << 0) != 0) { | ||
premium = (premium * bit1) / PRECISION; | ||
} | ||
if (fraction & (1 << 1) != 0) { | ||
premium = (premium * bit2) / PRECISION; | ||
} | ||
if (fraction & (1 << 2) != 0) { | ||
premium = (premium * bit3) / PRECISION; | ||
} | ||
if (fraction & (1 << 3) != 0) { | ||
premium = (premium * bit4) / PRECISION; | ||
} | ||
if (fraction & (1 << 4) != 0) { | ||
premium = (premium * bit5) / PRECISION; | ||
} | ||
if (fraction & (1 << 5) != 0) { | ||
premium = (premium * bit6) / PRECISION; | ||
} | ||
if (fraction & (1 << 6) != 0) { | ||
premium = (premium * bit7) / PRECISION; | ||
} | ||
if (fraction & (1 << 7) != 0) { | ||
premium = (premium * bit8) / PRECISION; | ||
} | ||
if (fraction & (1 << 8) != 0) { | ||
premium = (premium * bit9) / PRECISION; | ||
} | ||
if (fraction & (1 << 9) != 0) { | ||
premium = (premium * bit10) / PRECISION; | ||
} | ||
if (fraction & (1 << 10) != 0) { | ||
premium = (premium * bit11) / PRECISION; | ||
} | ||
if (fraction & (1 << 11) != 0) { | ||
premium = (premium * bit12) / PRECISION; | ||
} | ||
if (fraction & (1 << 12) != 0) { | ||
premium = (premium * bit13) / PRECISION; | ||
} | ||
if (fraction & (1 << 13) != 0) { | ||
premium = (premium * bit14) / PRECISION; | ||
} | ||
if (fraction & (1 << 14) != 0) { | ||
premium = (premium * bit15) / PRECISION; | ||
} | ||
if (fraction & (1 << 15) != 0) { | ||
premium = (premium * bit16) / PRECISION; | ||
} | ||
function decayedPremium(uint256 elapsed) public view returns (uint256) { | ||
/// @dev The half-life of the premium price decay | ||
uint256 secondsInPeriod = 1 days; | ||
/// @dev 50% decay per period in wad format | ||
uint256 perPeriodDecayPercentWad = FixedPointMathLib.WAD / 2; | ||
uint256 premium = EDAPrice.currentPrice(startPremium, elapsed, secondsInPeriod, perPeriodDecayPercentWad); | ||
return premium; | ||
} | ||
} |
This file contains 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 |
---|---|---|
@@ -0,0 +1,39 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity >=0.8.0; | ||
|
||
import {Test, console} from "forge-std/Test.sol"; | ||
import "solady/utils/FixedPointMathLib.sol"; | ||
|
||
library EDAPrice { | ||
/// @notice returns the current price of an exponential price decay auction defined by the passed params | ||
|
||
/// @dev reverts if perPeriodDecayPercentWad >= 1e18 | ||
/// @dev reverts if uint256 secondsInPeriod = 0 | ||
/// @dev reverts if startPrice * multiplier overflows | ||
/// @dev reverts if lnWad(percentWadRemainingPerPeriod) * ratio) overflows | ||
|
||
/// @param startPrice the starting price of the auction | ||
/// @param secondsElapsed the seconds elapsed since auction start | ||
/// @param secondsInPeriod the seconds over which the price should decay perPeriodDecayPercentWad | ||
/// @param perPeriodDecayPercentWad the percent the price should decay during secondsInPeriod, 100% = 1e18 | ||
|
||
/// @return price the current auction price | ||
|
||
function currentPrice( | ||
uint256 startPrice, | ||
uint256 secondsElapsed, | ||
uint256 secondsInPeriod, | ||
uint256 perPeriodDecayPercentWad | ||
) internal pure returns (uint256) { | ||
uint256 ratio = FixedPointMathLib.divWad(secondsElapsed, secondsInPeriod); | ||
uint256 percentWadRemainingPerPeriod = FixedPointMathLib.WAD - perPeriodDecayPercentWad; | ||
|
||
// percentWadRemainingPerPeriod can be safely cast because < 1e18 | ||
// ratio can be safely cast because will not overflow unless ratio > int256.max, | ||
// which would require secondsElapsed > int256.max, i.e. > 5.78e76 or 1.8e69 years | ||
|
||
int256 multiplier = FixedPointMathLib.powWad(int256(percentWadRemainingPerPeriod), int256(ratio)); | ||
uint256 price = (startPrice * uint256(multiplier)) / FixedPointMathLib.WAD; | ||
return price; | ||
} | ||
} |
37 changes: 37 additions & 0 deletions
37
test/ExponentialPremiumPriceOracle/ExponentialPremiumFuzzTest.t.sol
This file contains 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 |
---|---|---|
@@ -0,0 +1,37 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ~0.8.17; | ||
|
||
import {Test, console} from "forge-std/Test.sol"; | ||
import {ExponentialPremiumOracleBase} from "./ExponentialPremiumOracleBase.t.sol"; | ||
import "solady/utils/FixedPointMathLib.sol"; | ||
|
||
contract ExponentialPremiumFuzzTest is ExponentialPremiumOracleBase { | ||
function test_decayedPremium_decreasingPrices(uint256 elapsed) public view { | ||
vm.assume(elapsed <= 365 days); | ||
uint256 actualPremium = oracle.decayedPremium(elapsed); | ||
assert(actualPremium <= startPremium); | ||
} | ||
|
||
function test_decayedPremium_boundaryValues(uint256 elapsed) public view { | ||
uint256[] memory boundaryValues = new uint256[](3); | ||
boundaryValues[0] = 0; | ||
boundaryValues[1] = 1 days; | ||
boundaryValues[2] = 365 days; | ||
|
||
for (uint256 i = 0; i < boundaryValues.length; i++) { | ||
elapsed = boundaryValues[i]; | ||
uint256 actualPremium = oracle.decayedPremium(elapsed); | ||
assert(actualPremium <= startPremium); | ||
} | ||
} | ||
|
||
function test_decayedPremium_alwaysDecreasing(uint256 elapsed1, uint256 elapsed2) public view { | ||
vm.assume(elapsed1 <= elapsed2); | ||
vm.assume(elapsed2 <= 365 days); | ||
|
||
uint256 premium1 = oracle.decayedPremium(elapsed1); | ||
uint256 premium2 = oracle.decayedPremium(elapsed2); | ||
|
||
assert(premium1 >= premium2); | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
test/ExponentialPremiumPriceOracle/ExponentialPremiumOracleBase.t.sol
This file contains 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 |
---|---|---|
@@ -0,0 +1,51 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ~0.8.17; | ||
|
||
import {Test, console} from "forge-std/Test.sol"; | ||
import {StablePriceOracle} from "src/L2/StablePriceOracle.sol"; | ||
import {ExponentialPremiumPriceOracle} from "src/L2/ExponentialPremiumPriceOracle.sol"; | ||
|
||
contract ExponentialPremiumOracleBase is Test { | ||
ExponentialPremiumPriceOracle oracle; | ||
|
||
uint256 rent1; | ||
uint256 rent2; | ||
uint256 rent3; | ||
uint256 rent4; | ||
uint256 rent5; | ||
uint256 rent10; | ||
|
||
uint256 startPremium = 1e18; | ||
uint256 totalDays = 365 days; | ||
|
||
function setUp() public { | ||
uint256[] memory rentPrices = new uint256[](6); | ||
|
||
rent1 = 1e18; | ||
rent2 = 2e18; | ||
rent3 = 3e18; | ||
rent4 = 4e18; | ||
rent5 = 5e18; | ||
rent10 = 6e18; | ||
|
||
rentPrices[0] = rent1; | ||
rentPrices[1] = rent2; | ||
rentPrices[2] = rent3; | ||
rentPrices[3] = rent4; | ||
rentPrices[4] = rent5; | ||
rentPrices[5] = rent10; | ||
|
||
oracle = new ExponentialPremiumPriceOracle(rentPrices, startPremium, totalDays); | ||
} | ||
|
||
function test_constructor() public view { | ||
assertEq(oracle.startPremium(), startPremium); | ||
assertEq(oracle.endValue(), startPremium >> totalDays); | ||
assertEq(oracle.price1Letter(), rent1); | ||
assertEq(oracle.price2Letter(), rent2); | ||
assertEq(oracle.price3Letter(), rent3); | ||
assertEq(oracle.price4Letter(), rent4); | ||
assertEq(oracle.price5Letter(), rent5); | ||
assertEq(oracle.price10Letter(), rent10); | ||
} | ||
} |
This file contains 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 |
---|---|---|
@@ -0,0 +1,29 @@ | ||
//SPDX-License-Identifier: MIT | ||
pragma solidity ~0.8.17; | ||
|
||
import {Test, console} from "forge-std/Test.sol"; | ||
import {ExponentialPremiumOracleBase} from "./ExponentialPremiumOracleBase.t.sol"; | ||
import "solady/utils/FixedPointMathLib.sol"; | ||
|
||
contract DecayedPremium is ExponentialPremiumOracleBase { | ||
function test_decayedPremium_zeroElapsed() public view { | ||
uint256 elapsed = 0; | ||
uint256 expectedPremium = startPremium; | ||
uint256 actualPremium = oracle.decayedPremium(elapsed); | ||
assertEq(actualPremium, expectedPremium); | ||
} | ||
|
||
function test_decayedPremium_halfPeriod() public view { | ||
uint256 elapsed = 1 days / 2; | ||
uint256 expectedPremium = 707106781186547524; // Calculated expected value for premium price after 1/2 day | ||
uint256 actualPremium = oracle.decayedPremium(elapsed); | ||
assertEq(actualPremium, expectedPremium); | ||
} | ||
|
||
function test_decayedPremium_threePeriods() public view { | ||
uint256 elapsed = 3 days; | ||
uint256 expectedPremium = 124999999999999999; // Calculated expected value for premium price after 3 days | ||
uint256 actualPremium = oracle.decayedPremium(elapsed); | ||
assertEq(actualPremium, expectedPremium); | ||
} | ||
} |