Skip to content

Commit

Permalink
feat(RNSDomainPrice): add bulkOverrideTiers (#194)
Browse files Browse the repository at this point in the history
  • Loading branch information
TuDo1403 authored Mar 13, 2024
2 parents 747c81b + daef22e commit 55b5cea
Show file tree
Hide file tree
Showing 7 changed files with 184 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,20 @@ import { console2 as console } from "forge-std/console2.sol";
import { ITransparentUpgradeableProxy } from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
import { ProxyAdmin } from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol";
import { ContractKey } from "foundry-deployment-kit/configs/ContractConfig.sol";
import { Network, Config__Mainnet20231205 } from "script/20231205-deploy-upgrade-auction-and-deploy-rns-operation/20231205_MainnetConfig.s.sol";
import { INSAuction, RNSAuction, RNSUnified, Migration__20231123_UpgradeAuctionClaimeUnbiddedNames as UpgradeAuctionScript } from "script/20231123-upgrade-auction-claim-unbidded-names/20231123_UpgradeAuctionClaimUnbiddedNames.s.sol";
import { RNSOperation, Migration__20231124_DeployRNSOperation as DeployRNSOperationScript } from "script/20231124-deploy-rns-operation/20231124_DeployRNSOperation.s.sol";
import {
Network,
Config__Mainnet20231205
} from "script/20231205-deploy-upgrade-auction-and-deploy-rns-operation/20231205_MainnetConfig.s.sol";
import {
INSAuction,
RNSAuction,
RNSUnified,
Migration__20231123_UpgradeAuctionClaimeUnbiddedNames as UpgradeAuctionScript
} from "script/20231123-upgrade-auction-claim-unbidded-names/20231123_UpgradeAuctionClaimUnbiddedNames.s.sol";
import {
RNSOperation,
Migration__20231124_DeployRNSOperation as DeployRNSOperationScript
} from "script/20231124-deploy-rns-operation/20231124_DeployRNSOperation.s.sol";

contract Migration__20231205_UpgradeRNSAuctionAndDeployRNSOperation is Config__Mainnet20231205 {
function run() public trySetUp onMainnet {
Expand Down
1 change: 0 additions & 1 deletion src/RNSAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,6 @@ contract RNSAuction is Initializable, AccessControlEnumerable, INSAuction {
/**
* @inheritdoc INSAuction
*/

function setBidGapRatio(uint256 ratio) external onlyRole(DEFAULT_ADMIN_ROLE) {
_setBidGapRatio(ratio);
}
Expand Down
128 changes: 104 additions & 24 deletions src/RNSDomainPrice.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ contract RNSDomainPrice is Initializable, AccessControlEnumerable, INSDomainPric
using LibPeriodScaler for PeriodScaler;
using PythConverter for PythStructs.Price;

/// @dev The threshold tier value (in USD) for Tier 1: > $200
uint256 private constant TIER_1_FROM_EXCLUDED_THRESHOLD = 200e18;
/// @dev The threshold tier value (in USD) for Tier 2 in range of ($50; $200]
uint256 private constant TIER_2_FROM_EXCLUDED_THRESHOLD = 50e18;
/// @inheritdoc INSDomainPrice
uint8 public constant USD_DECIMALS = 18;
/// @inheritdoc INSDomainPrice
Expand Down Expand Up @@ -54,6 +58,8 @@ contract RNSDomainPrice is Initializable, AccessControlEnumerable, INSDomainPric
mapping(bytes32 lbHash => TimestampWrapper usdPrice) internal _dp;
/// @dev Mapping from name => inverse bitwise of renewal fee overriding.
mapping(bytes32 lbHash => uint256 usdPrice) internal _rnFeeOverriding;
/// @dev Mapping from label hash to overriden tier
mapping(bytes32 lbHash => uint8 tier) internal _tierOverriding;

constructor() payable {
_disableInitializers();
Expand Down Expand Up @@ -167,6 +173,15 @@ contract RNSDomainPrice is Initializable, AccessControlEnumerable, INSDomainPric
return ~usdFee;
}

/**
* @inheritdoc INSDomainPrice
*/
function getOverriddenTier(string calldata label) external view returns (Tier tier) {
uint8 tierValue = _tierOverriding[label.hashLabel()];
if (tierValue == 0) revert TierIsNotOverriden();
return Tier(~tierValue);
}

/**
* @inheritdoc INSDomainPrice
*/
Expand All @@ -190,6 +205,26 @@ contract RNSDomainPrice is Initializable, AccessControlEnumerable, INSDomainPric
}
}

/**
* @inheritdoc INSDomainPrice
*/
function bulkOverrideTiers(bytes32[] calldata lbHashes, Tier[] calldata tiers) external onlyRole(OVERRIDER_ROLE) {
uint256 length = lbHashes.length;
if (length == 0 || length != tiers.length) revert InvalidArrayLength();
uint8 inverseBitwise;
address operator = _msgSender();

for (uint256 i; i < length;) {
inverseBitwise = ~uint8(tiers[i]);
_tierOverriding[lbHashes[i]] = inverseBitwise;
emit TierOverridingUpdated(operator, lbHashes[i], tiers[i]);

unchecked {
++i;
}
}
}

/**
* @inheritdoc INSDomainPrice
*/
Expand Down Expand Up @@ -240,6 +275,27 @@ contract RNSDomainPrice is Initializable, AccessControlEnumerable, INSDomainPric
ronPrice = convertUSDToRON(usdPrice);
}

/**
* @inheritdoc INSDomainPrice
*/
function getTier(string memory label) public view returns (Tier tier) {
bytes32 lbHash = label.hashLabel();
uint8 overriddenTier = _tierOverriding[lbHash];

if (overriddenTier != 0) return Tier(~overriddenTier);

(UnitPrice memory yearlyRenewalFeeByLength,,) = _tryGetRenewalFee({ label: label, duration: 365 days });
uint256 tierValue = yearlyRenewalFeeByLength.usd + _getDomainPrice(lbHash) / 2;

if (tierValue > TIER_1_FROM_EXCLUDED_THRESHOLD) {
return Tier.Tier1;
} else if (tierValue > TIER_2_FROM_EXCLUDED_THRESHOLD) {
return Tier.Tier2;
} else {
return Tier.Tier3;
}
}

/**
* @inheritdoc INSDomainPrice
*/
Expand All @@ -248,32 +304,14 @@ contract RNSDomainPrice is Initializable, AccessControlEnumerable, INSDomainPric
view
returns (UnitPrice memory basePrice, UnitPrice memory tax)
{
uint256 nameLen = label.strlen();
bytes32 lbHash = label.hashLabel();
uint256 overriddenRenewalFee = _rnFeeOverriding[lbHash];

if (overriddenRenewalFee != 0) {
basePrice.usd = duration * ~overriddenRenewalFee;
} else {
uint256 renewalFeeByLength = _rnFee[Math.min(nameLen, _rnfMaxLength)];
basePrice.usd = duration * renewalFeeByLength;
uint256 id = LibRNSDomain.toId(LibRNSDomain.RON_ID, label);
INSAuction auction = _auction;
if (auction.reserved(id)) {
INSUnified rns = auction.getRNSUnified();
uint256 expiry = LibSafeRange.addWithUpperbound(rns.getRecord(id).mut.expiry, duration, type(uint64).max);
(INSAuction.DomainAuction memory domainAuction,) = auction.getAuction(id);
uint256 claimedAt = domainAuction.bid.claimedAt;
if (claimedAt != 0 && expiry - claimedAt > auction.MAX_AUCTION_DOMAIN_EXPIRY()) {
revert ExceedAuctionDomainExpiry();
}
// Tax is added to the name reserved for the auction
tax.usd = Math.mulDiv(_taxRatio, _getDomainPrice(lbHash), MAX_PERCENTAGE);
bytes4 revertReason;
(basePrice, tax, revertReason) = _tryGetRenewalFee(label, duration);
if (revertReason != bytes4(0x0)) {
assembly ("memory-safe") {
mstore(0x0, revertReason)
revert(0x0, 0x04)
}
}

tax.ron = convertUSDToRON(tax.usd);
basePrice.ron = convertUSDToRON(basePrice.usd);
}

/**
Expand Down Expand Up @@ -398,6 +436,48 @@ contract RNSDomainPrice is Initializable, AccessControlEnumerable, INSDomainPric
emit PythOracleConfigUpdated(_msgSender(), pyth, maxAcceptableAge, pythIdForRONUSD);
}

/**
* @dev Tries to get the renewal fee for a given domain label and duration.
* It returns the base price, tax, and a revert reason if applicable.
* @param label The domain label.
* @param duration The duration for which the domain is being renewed.
* @return basePrice The base price in USD for ˝renewing the domain.
* @return tax The tax amount in USD for renewing the domain.
* @return revertReason The revert reason if the renewal fee exceeds the auction domain expiry.
*/
function _tryGetRenewalFee(string memory label, uint256 duration)
internal
view
returns (UnitPrice memory basePrice, UnitPrice memory tax, bytes4 revertReason)
{
uint256 nameLen = label.strlen();
bytes32 lbHash = label.hashLabel();
uint256 overriddenRenewalFee = _rnFeeOverriding[lbHash];

if (overriddenRenewalFee != 0) {
basePrice.usd = duration * ~overriddenRenewalFee;
} else {
uint256 renewalFeeByLength = _rnFee[Math.min(nameLen, _rnfMaxLength)];
basePrice.usd = duration * renewalFeeByLength;
uint256 id = LibRNSDomain.toId(LibRNSDomain.RON_ID, label);
INSAuction auction = _auction;
if (auction.reserved(id)) {
INSUnified rns = auction.getRNSUnified();
uint256 expiry = LibSafeRange.addWithUpperbound(rns.getRecord(id).mut.expiry, duration, type(uint64).max);
(INSAuction.DomainAuction memory domainAuction,) = auction.getAuction(id);
uint256 claimedAt = domainAuction.bid.claimedAt;
if (claimedAt != 0 && expiry - claimedAt > auction.MAX_AUCTION_DOMAIN_EXPIRY()) {
return (basePrice, tax, ExceedAuctionDomainExpiry.selector);
}
// Tax is added to the name reserved for the auction
tax.usd = Math.mulDiv(_taxRatio, _getDomainPrice(lbHash), MAX_PERCENTAGE);
}
}

tax.ron = convertUSDToRON(tax.usd);
basePrice.ron = convertUSDToRON(basePrice.usd);
}

/**
* @dev Returns the current domain price applied the business rule: deduced x% each y seconds.
*/
Expand Down
39 changes: 39 additions & 0 deletions src/interfaces/INSDomainPrice.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,17 @@ import { IPyth } from "@pythnetwork/IPyth.sol";
interface INSDomainPrice {
error InvalidArrayLength();
error RenewalFeeIsNotOverriden();
error TierIsNotOverriden();
error ExceedAuctionDomainExpiry();

/// @dev The tier of a domain.
enum Tier {
Unknown,
Tier1,
Tier2,
Tier3
}

struct RenewalFee {
uint256 labelLength;
uint256 fee;
Expand All @@ -27,6 +36,8 @@ interface INSDomainPrice {
event RenewalFeeByLengthUpdated(address indexed operator, uint256 indexed labelLength, uint256 renewalFee);
/// @dev Emitted when the renew fee of a domain is overridden. Value of `inverseRenewalFee` is 0 when not overridden.
event RenewalFeeOverridingUpdated(address indexed operator, bytes32 indexed labelHash, uint256 inverseRenewalFee);
/// @dev Emitted when the tier of a domain is overridden.
event TierOverridingUpdated(address indexed operator, bytes32 indexed labelHash, Tier indexed tier);

/// @dev Emitted when the domain price is updated.
event DomainPriceUpdated(
Expand Down Expand Up @@ -119,13 +130,27 @@ interface INSDomainPrice {
view
returns (UnitPrice memory basePrice, UnitPrice memory tax);

/**
* @dev Returns the tier of a label.
* @param label The domain label to register (Eg, 'foo' for 'foo.ron').
* @return tier The tier of the label.
*/
function getTier(string calldata label) external view returns (Tier tier);

/**
* @dev Returns the renewal fee of a label. Reverts if not overridden.
* @notice This method is to help developers check the domain renewal fee overriding. Consider using method
* {getRenewalFee} instead for full handling of renewal fees.
*/
function getOverriddenRenewalFee(string memory label) external view returns (uint256 usdFee);

/**
* @dev Returns the tier of a label. Reverts if not overridden.
* @notice This method is to help developers check the domain tier overriding. Consider using method {getTier} instead
* for full handling of tiers.
*/
function getOverriddenTier(string memory label) external view returns (Tier tier);

/**
* @dev Bulk override renewal fees.
*
Expand All @@ -140,6 +165,20 @@ interface INSDomainPrice {
*/
function bulkOverrideRenewalFees(bytes32[] calldata lbHashes, uint256[] calldata usdPrices) external;

/**
* @dev Bulk override tiers.
*
* Requirements:
* - The method caller is operator.
* - The input array lengths must be larger than 0 and the same.
*
* Emits events {TierOverridingUpdated}.
*
* @param lbHashes Array of label hashes. (Eg, ['foo'].map(keccak256) for 'foo.ron')
* @param tiers Array of tiers. Leave 2^256 - 1 to remove overriding.
*/
function bulkOverrideTiers(bytes32[] calldata lbHashes, Tier[] calldata tiers) external;

/**
* @dev Bulk try to set domain prices. Returns a boolean array indicating whether domain prices at the corresponding
* indexes if set or not.
Expand Down
19 changes: 18 additions & 1 deletion src/utils/RNSOperation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ contract RNSOperation is Ownable {

bytes32[] memory lbHashes = new bytes32[](labels.length);
for (uint256 i; i < lbHashes.length; ++i) {
lbHashes[i] = keccak256(bytes(labels[i]));
lbHashes[i] = LibRNSDomain.hashLabel(labels[i]);
}
uint256[] memory usdPrices = new uint256[](yearlyUSDPrices.length);
for (uint256 i; i < usdPrices.length; ++i) {
Expand All @@ -65,6 +65,23 @@ contract RNSOperation is Ownable {
domainPrice.bulkOverrideRenewalFees(lbHashes, usdPrices);
}

/**
* @dev Allows the owner to bulk override the tiers for specified RNS domains.
* @param labels The array of labels for the RNS domains.
* @param tiers The array of tiers for the corresponding RNS domains.
* @dev The `tiers` array should represent the tiers for each domain.
*/
function bulkOverrideTiers(string[] calldata labels, INSDomainPrice.Tier[] calldata tiers) external onlyOwner {
require(labels.length == tiers.length, "RNSOperation: length mismatch");

bytes32[] memory lbHashes = new bytes32[](labels.length);
for (uint256 i; i < lbHashes.length; ++i) {
lbHashes[i] = LibRNSDomain.hashLabel(labels[i]);
}

domainPrice.bulkOverrideTiers(lbHashes, tiers);
}

/**
* @dev Allows the owner to reclaim unbidded RNS domain names and transfer them to specified addresses.
* @param tos The array of addresses to which the unbidded domains will be transferred.
Expand Down
4 changes: 2 additions & 2 deletions test/RNSUnified/RNSUnified.namehash.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
pragma solidity ^0.8.19;

import "./RNSUnified.t.sol";
import { LibString } from "solady/utils/LibString.sol";
import { LibString as SoladyLibString } from "solady/utils/LibString.sol";

contract RNSUnified_NameHash_Test is RNSUnifiedTest {
using LibString for *;
using SoladyLibString for *;

function testGas_namehash(string calldata domainName) external view {
_rns.namehash(domainName);
Expand Down
8 changes: 7 additions & 1 deletion test/RNSUnified/RNSUnified.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,13 @@ abstract contract RNSUnifiedTest is Test {
address logic = address(new RNSUnified());
_rns = RNSUnified(
address(
new TransparentUpgradeableProxy(logic, _proxyAdmin, abi.encodeCall(RNSUnified.initialize, (_admin, _pauser, _controller, _protectedSettler, GRACE_PERIOD, BASE_URI)))
new TransparentUpgradeableProxy(
logic,
_proxyAdmin,
abi.encodeCall(
RNSUnified.initialize, (_admin, _pauser, _controller, _protectedSettler, GRACE_PERIOD, BASE_URI)
)
)
)
);

Expand Down

0 comments on commit 55b5cea

Please sign in to comment.