Skip to content

Commit

Permalink
Premium Price with EDA integration (#33)
Browse files Browse the repository at this point in the history
* Testing Complete for Exponential Premium Price Oracle

* Fixed

* premiumprice-eda

* Read me

* Read me font fixed
  • Loading branch information
Keshavrajsinghal authored Jun 14, 2024
1 parent 6af8674 commit 6b02e66
Show file tree
Hide file tree
Showing 7 changed files with 256 additions and 84 deletions.
32 changes: 31 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,38 @@ The core functionality of Base Usernames should look familiar to anyone that's l

In addition to replicating the base behavior of the ENS protocol, we are offering a series of promotional discounts associcated with various Coinbase product integrations. As such, the Base Usernames ETH Registrar Controller allows users to perform discounted registrations while passing along integration-specific `validationData`.

### Functional Diagrams
## Functional Diagrams

### Premium Pricing Chart

To visualize the pricing decay for premium prices, you can run the Python script located in the py directory. The script generates a pricing chart based on user input.

### Prerequisites

* Python 3.x
* matplotlib library

### Running the Script

1. Navigate to the py directory

```shell
$ cd py
```

2. Run the Python script

```shell
$ python3 Price.py
```

3. Follow the prompts to input the required values:

* Start Premium: The initial premium price
* Total days: The number of days it takes for the price to reach its end value
* Number of days: The number of days over which price should decay

The script will generate and display the pricing chart based on the provided inputs.

## Usage

Expand Down
54 changes: 54 additions & 0 deletions py/Price.py
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()
98 changes: 15 additions & 83 deletions src/L2/ExponentialPremiumPriceOracle.sol
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;
}
}
39 changes: 39 additions & 0 deletions src/lib/EDAPrice.sol
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;
}
}
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);
}
}
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);
}
}
29 changes: 29 additions & 0 deletions test/ExponentialPremiumPriceOracle/decayedPremium.t.sol
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);
}
}

0 comments on commit 6b02e66

Please sign in to comment.