-
Notifications
You must be signed in to change notification settings - Fork 4
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
base: main
Are you sure you want to change the base?
Changes from all commits
dc942d4
e009081
d93047c
257b58a
d3e15e0
8c09e3a
7283c82
2583b02
f618ddd
14ae5ea
5002fb3
d514113
19a097e
c97afcf
291e107
1e487d9
afcb7b2
21e0a01
a80d478
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,3 @@ | ||
# Dotenv file | ||
.env | ||
|
||
node_modules/ |
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" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,4 +16,5 @@ docs/ | |
node_modules/ | ||
cache_hardhat/ | ||
artifacts/ | ||
typechain-types/ | ||
typechain-types/ | ||
*.vscode |
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; | ||
} | ||
} |
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; | ||||||
mapping(bytes32 => uint256) public commitments; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
|
||||||
bytes32 public constant ETH_LABELHASH = keccak256(abi.encodePacked("eth")); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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, | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||||||
} | ||||||
} | ||||||
} |
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); | ||
} | ||
} |
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); | ||
} |
There was a problem hiding this comment.
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?