Contract Name:
ExponentialPremiumStablePriceOracle
Contract Source Code:
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "./StablePriceOracle.sol";
contract ExponentialPremiumStablePriceOracle is StablePriceOracle {
uint256 constant GRACE_PERIOD = 90 days;
uint256 constant SECONDS_PER_YEAR = 31556952;
uint256 immutable startPremium;
uint256 immutable endValue;
constructor(
AggregatorInterface _usdOracle,
uint256[] memory _rentPrices,
uint256 _startPremium,
uint256 totalDays
) StablePriceOracle(_usdOracle, _rentPrices) {
startPremium = _startPremium;
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(startPremium, elapsed);
if (premium >= endValue) {
return premium - endValue;
}
return 0;
}
/**
* @dev Returns the pricing discount, 0 - 100.
*/
function _discount(
string memory,
uint256 expires,
uint256 duration
) internal view override returns (uint256) {
expires = expires + GRACE_PERIOD;
if (expires > block.timestamp) {
// for renew, no discount
return 100;
}
uint256 year = duration / SECONDS_PER_YEAR;
if (year < 2) {
// [0,2) => 0%
return 100;
} else if (year < 6) {
// [2,6) => 30%
return 70;
} else if (year < 10) {
// [6,10) => 20%
return 80;
} else {
// 10+ => 10%
return 90;
}
}
/**
* @dev Returns the premium price at current time elapsed
* @param startPremium starting price
* @param elapsed time past since expiry
*/
function decayedPremium(uint256 startPremium, uint256 elapsed)
public
pure
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;
}
return premium;
}
function supportsInterface(bytes4 interfaceID)
public
view
virtual
override
returns (bool)
{
return super.supportsInterface(interfaceID);
}
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
import "./IPriceOracle.sol";
import "../utils/StringUtils.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
interface AggregatorInterface {
function latestAnswer() external view returns (int256);
}
// Set a price in USD, based on an oracle.
contract StablePriceOracle is IPriceOracle {
using StringUtils for *;
// Rent in base price units by length
uint256 public immutable price1Letter;
uint256 public immutable price2Letter;
uint256 public immutable price3Letter;
uint256 public immutable price4Letter;
uint256 public immutable price5Letter;
// Oracle address
AggregatorInterface public immutable usdOracle;
event RentPriceChanged(uint256[] prices);
constructor(AggregatorInterface _usdOracle, uint256[] memory _rentPrices) {
usdOracle = _usdOracle;
price1Letter = _rentPrices[0];
price2Letter = _rentPrices[1];
price3Letter = _rentPrices[2];
price4Letter = _rentPrices[3];
price5Letter = _rentPrices[4];
}
function price(
string calldata name,
uint256 expires,
uint256 duration
) external view override returns (IPriceOracle.Price memory) {
uint256 len = name.strlen();
uint256 basePrice;
if (len == 1) {
basePrice = price1Letter * duration;
} else if (len == 2) {
basePrice = price2Letter * duration;
} else if (len == 3) {
basePrice = price3Letter * duration;
} else if (len == 4) {
basePrice = price4Letter * duration;
} else {
basePrice = price5Letter * duration;
}
basePrice = basePrice * _discount(name, expires, duration) / 100;
return
IPriceOracle.Price({
base: attoUSDToWei(basePrice),
premium: attoUSDToWei(_premium(name, expires, duration))
});
}
/**
* @dev Returns the pricing premium in wei.
*/
function premium(
string calldata name,
uint256 expires,
uint256 duration
) external view returns (uint256) {
return attoUSDToWei(_premium(name, expires, duration));
}
/**
* @dev Returns the pricing premium in internal base units.
*/
function _premium(
string memory,
uint256,
uint256
) internal view virtual returns (uint256) {
return 0;
}
/**
* @dev Returns the pricing discount.
*/
function discount(
string calldata name,
uint256 expires,
uint256 duration
) external view returns (uint256) {
return _discount(name, expires, duration);
}
/**
* @dev Returns the pricing discount, 0 - 100.
*/
function _discount(
string memory,
uint256,
uint256
) internal view virtual returns (uint256) {
return 100;
}
function attoUSDToWei(uint256 amount) internal view returns (uint256) {
uint256 croPrice = uint256(usdOracle.latestAnswer());
return (amount * 1e8) / croPrice;
}
function weiToAttoUSD(uint256 amount) internal view returns (uint256) {
uint256 croPrice = uint256(usdOracle.latestAnswer());
return (amount * croPrice) / 1e8;
}
function supportsInterface(bytes4 interfaceID)
public
view
virtual
returns (bool)
{
return
interfaceID == type(IERC165).interfaceId ||
interfaceID == type(IPriceOracle).interfaceId;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
library StringUtils {
/**
* @dev Returns the length of a given string
*
* @param s The string to measure the length of
* @return The length of the input string
*/
function strlen(string memory s) internal pure returns (uint) {
uint len;
uint i = 0;
uint bytelength = bytes(s).length;
for(len = 0; i < bytelength; len++) {
bytes1 b = bytes(s)[i];
if(b < 0x80) {
i += 1;
} else if (b < 0xE0) {
i += 2;
} else if (b < 0xF0) {
i += 3;
} else if (b < 0xF8) {
i += 4;
} else if (b < 0xFC) {
i += 5;
} else {
i += 6;
}
}
return len;
}
function toLower(string memory s) internal pure returns (string memory) {
bytes memory bstr = bytes(s);
bytes memory blower = new bytes(bstr.length);
for (uint i = 0; i < bstr.length; i++) {
if ((uint8(bstr[i]) >= 65) && (uint8(bstr[i]) <= 90)) {
blower[i] = bytes1(uint8(bstr[i]) + 32);
} else {
blower[i] = bstr[i];
}
}
return string(blower);
}
function toUpper(string memory s) internal pure returns (string memory) {
bytes memory bstr = bytes(s);
bytes memory bupper = new bytes(bstr.length);
for (uint i = 0; i < bstr.length; i++) {
if ((uint8(bstr[i]) >= 97) && (uint8(bstr[i]) <= 122)) {
bupper[i] = bytes1(uint8(bstr[i]) - 32);
} else {
bupper[i] = bstr[i];
}
}
return string(bupper);
}
}
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.4;
interface IPriceOracle {
struct Price {
uint256 base;
uint256 premium;
}
/**
* @dev Returns the price to register or renew a name.
* @param name The name being registered or renewed.
* @param expires When the name presently expires (0 if this is a new registration).
* @param duration How long the name is being registered or extended for, in seconds.
* @return base premium tuple of base price + premium price
*/
function price(
string calldata name,
uint256 expires,
uint256 duration
) external view returns (Price calldata);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}