Skip to content
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

Eth registrar #7

Draft
wants to merge 19 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Dotenv file
.env

node_modules/
9 changes: 5 additions & 4 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"solidity.packageDefaultDependenciesContractsDirectory": "./contracts/src",
"solidity.packageDefaultDependenciesDirectory": "./contracts/lib",
"solidity.compileUsingRemoteVersion": "v0.8.24+commit.e11b9ed9"
}
"solidity.packageDefaultDependenciesContractsDirectory": "./contracts/src",
"solidity.packageDefaultDependenciesDirectory": "./contracts/lib",
"solidity.compileUsingRemoteVersion": "v0.8.24+commit.e11b9ed9",
"solidity.defaultCompiler": "localNodeModule"
}
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion contracts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ docs/
node_modules/
cache_hardhat/
artifacts/
typechain-types/
typechain-types/
*.vscode
17 changes: 17 additions & 0 deletions contracts/src/registrar/DummyOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pragma solidity >=0.8.4;

contract DummyOracle {
int256 value;

constructor(int256 _value) public {
set(_value);
}

function set(int256 _value) public {
value = _value;
}

function latestAnswer() public view returns (int256) {
return value;
}
}
184 changes: 184 additions & 0 deletions contracts/src/registrar/ETHRegistrar.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
// SPDX-License-Identifier: MIT
// Portions from OpenZeppelin Contracts (last updated v5.0.0) (token/ERC1155/ERC1155.sol)
pragma solidity >=0.8.13;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ETHRegistry} from "../registry/ETHRegistry.sol";
import {IRegistry} from "../registry/IRegistry.sol";
import {IPriceOracle} from "./IPriceOracle.sol";
import {StringUtils} from "../utils/StringUtils.sol";

error UnexpiredCommitmentExists(bytes32 commitment);
error InsufficientValue();
error DurationTooShort(uint64 duration);
error CommitmentTooNew(bytes32 commitment);
error CommitmentTooOld(bytes32 commitment);
error NameNotAvailable(string label);
error NameExpired(bytes32 labelHash);

contract ETHRegistrar is Ownable {
using StringUtils for string;

uint256 public immutable MIN_COMMIT_AGE;
uint256 public immutable MAX_COMMIT_AGE;
uint256 public constant MIN_REGISTRATION_DURATION = 28 days;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that we have an owner, maybe we want to make this configurable?

mapping(bytes32 => uint256) public commitments;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solidity supports naming mapping keys and values now, which would add clarity here.

Suggested change
mapping(bytes32 => uint256) public commitments;
mapping(bytes32 hash => uint256 inception) public commitments;

bytes32 public constant ETH_LABELHASH = keccak256(abi.encodePacked("eth"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can save some bytecode by making this a module level constant (and therefore not a public variable).

ETHRegistry public registry;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be immutable, right?

IPriceOracle public prices;

event NameRegistered(
string label,
bytes32 labelHash,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this necessary?

address owner,
uint256 base,
uint256 premium,
uint256 expires
);

event NameRenewed(
string label,
bytes32 indexed labelHash,
uint256 cost,
uint256 expires
);

event CommitmentMade(bytes32 commitment);

constructor(
ETHRegistry _registry,
IPriceOracle _prices,
uint256 _minCommitAge,
uint256 _maxCommitAge
) Ownable(msg.sender) {
// constructor
prices = _prices;
registry = _registry;
MIN_COMMIT_AGE = _minCommitAge;
MAX_COMMIT_AGE = _maxCommitAge;
}

function makeCommitment(
string calldata label,
address owner,
uint64 duration,
bytes32 secret
) public pure returns (bytes32) {
return keccak256(abi.encode(label, owner, duration, secret));
}

function commit(bytes32 commitment) public {
if (commitments[commitment] + MAX_COMMIT_AGE >= block.timestamp) {
revert UnexpiredCommitmentExists(commitment);
}
commitments[commitment] = block.timestamp;
emit CommitmentMade(commitment);
}

function available(string memory label) public view returns (bool) {
(uint64 expiry, ) = registry.nameData(uint256(keccak256(bytes(label))));
return expiry < block.timestamp;
}

function rentPrice(
string memory name,
uint256 duration
) public view returns (IPriceOracle.Price memory price) {
bytes32 label = keccak256(bytes(name));
(uint64 expiry, ) = registry.nameData(uint256(label));
price = prices.price(name, expiry, duration);
}

function register(
string calldata label,
address owner,
uint64 duration,
bytes32 secret
) public payable {
_consumeCommitment(
label,
duration,
makeCommitment(label, owner, duration, secret)
);

// check if msg.value given is enough
IPriceOracle.Price memory price = rentPrice(label, duration);
if (msg.value < price.base + price.premium) {
revert InsufficientValue();
}

uint64 expires = uint64(block.timestamp) + duration;

registry.register(label, msg.sender, registry, 0, expires);

emit NameRegistered(
label,
keccak256(bytes(label)),
owner,
price.base,
price.premium,
expires
);

// send back excess
if (msg.value > (price.base + price.premium)) {
payable(msg.sender).transfer(
msg.value - (price.base + price.premium)
);
}
}

function renew(string calldata label, uint64 duration) public payable {
bytes32 labelHash = keccak256(bytes(label));
// Get current expiry of name
(uint64 currentExpiry, ) = registry.nameData(uint256(labelHash));
if (currentExpiry < block.timestamp) {
revert NameExpired(labelHash);
}

// Calculate new expiry
uint64 expires = currentExpiry + duration;

// Check if the provided value is sufficient
IPriceOracle.Price memory price = rentPrice(label, duration);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this just swallow any extra?

if (msg.value < price.base + price.premium) {
revert InsufficientValue();
}
registry.renew(uint256(labelHash), expires);
emit NameRenewed(label, labelHash, duration, expires);
}

function withdraw() public {
payable(owner()).transfer(address(this).balance);
}

function valid(string memory label) public pure returns (bool) {
return label.strlen() >= 3;
}

/* Internal functions */

function _consumeCommitment(
string memory label,
uint64 duration,
bytes32 commitment
) internal {
// Require an old enough commitment.
if (commitments[commitment] + MIN_COMMIT_AGE > block.timestamp) {
revert CommitmentTooNew(commitment);
}

// If the commitment is too old, or the name is registered, stop
if (commitments[commitment] + MAX_COMMIT_AGE <= block.timestamp) {
revert CommitmentTooOld(commitment);
}
if (!available(label)) {
revert NameNotAvailable(label);
}

delete (commitments[commitment]);

if (duration < MIN_REGISTRATION_DURATION) {
revert DurationTooShort(duration);
}
}
}
138 changes: 138 additions & 0 deletions contracts/src/registrar/ExponentialPremiumPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
//SPDX-License-Identifier: MIT
pragma solidity ~0.8.17;

import "./StablePriceOracle.sol";

contract ExponentialPremiumPriceOracle is StablePriceOracle {
uint256 constant GRACE_PERIOD = 90 days;
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 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);
}
}
22 changes: 22 additions & 0 deletions contracts/src/registrar/IPriceOracle.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//SPDX-License-Identifier: MIT
pragma solidity >=0.8.17 <0.9.0;

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);
}
Loading