From 2df0232bffbd4e32d06ebb37aebfa0ccb49b240c Mon Sep 17 00:00:00 2001 From: James Earle Date: Fri, 25 Apr 2025 11:41:13 -0400 Subject: [PATCH 1/4] add and verify ability to encode and decode struct data correctly --- contracts/price/ZNSCurvePricer.sol | 63 ++++++++++++++++++++++++++++++ test/ZNSCurvePricer.test.ts | 13 ++++++ 2 files changed, 76 insertions(+) diff --git a/contracts/price/ZNSCurvePricer.sol b/contracts/price/ZNSCurvePricer.sol index 2066a1fe3..dd11bdfd5 100644 --- a/contracts/price/ZNSCurvePricer.sol +++ b/contracts/price/ZNSCurvePricer.sol @@ -66,6 +66,69 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I setPriceConfig(0x0, zeroPriceConfig_); } + bytes public data; + function encodeConfig( + CurvePriceConfig calldata config + ) external { + bytes32 maxPrice = bytes32(config.maxPrice); + bytes32 curveMultiplier = bytes32(config.curveMultiplier); + bytes32 maxLength = bytes32(config.maxLength); + bytes32 baseLength = bytes32(config.baseLength); + bytes32 precisionMultiplier = bytes32(config.precisionMultiplier); + bytes32 feePercentage = bytes32(config.feePercentage); + bytes32 isSet = bytes32(abi.encode(config.isSet)); + + + // TODO hash with salt? then can unhash when decode? + data = + abi.encodePacked( + maxPrice, + curveMultiplier, + maxLength, + baseLength, + precisionMultiplier, + feePercentage, + isSet + ); + } + + function decodeConfig( + bytes calldata inData + ) external pure returns (CurvePriceConfig memory config) { + ( + uint256 maxPrice, + uint256 curveMultiplier, + uint256 maxLength, + uint256 baseLength, + uint256 precisionMultiplier, + uint256 feePercentage, + bool isSet + ) = abi.decode( + inData, + ( + uint256, + uint256, + uint256, + uint256, + uint256, + uint256, + bool + ) + ); + + CurvePriceConfig memory localConfig = CurvePriceConfig( + maxPrice, + curveMultiplier, + maxLength, + baseLength, + precisionMultiplier, + feePercentage, + isSet + ); + + return localConfig; + } + /** * @notice Get the price of a given domain name * @dev `skipValidityCheck` param is added to provide proper revert when the user is diff --git a/test/ZNSCurvePricer.test.ts b/test/ZNSCurvePricer.test.ts index f0cd4a40d..d825019cb 100644 --- a/test/ZNSCurvePricer.test.ts +++ b/test/ZNSCurvePricer.test.ts @@ -27,6 +27,7 @@ import { registrationWithSetup } from "./helpers/register-setup"; import { getProxyImplAddress, getRandomString } from "./helpers/utils"; import { IZNSContractsLocal } from "./helpers/types"; import { getMongoAdapter } from "@zero-tech/zdc"; +import { znsCurvePricerMockSol } from "../typechain/contracts/upgrade-test-mocks/distribution"; require("@nomicfoundation/hardhat-chai-matchers"); @@ -87,6 +88,18 @@ describe("ZNSCurvePricer", () => { await dbAdapter.dropDB(); }); + it.only("encode/decode", async () => { + await zns.curvePricer.encodeConfig(DEFAULT_PRICE_CONFIG); + + const data = await zns.curvePricer.data() + console.log(data); + + const newConfig = await zns.curvePricer.decodeConfig(data); + + // Decodes correctly + console.log(newConfig); + }) + it("Should NOT let initialize the implementation contract", async () => { const factory = new ZNSCurvePricer__factory(deployer); const impl = await getProxyImplAddress(await zns.curvePricer.getAddress()); From 0432849935362aac819a840d6da61bc4f34422c8 Mon Sep 17 00:00:00 2001 From: James Earle Date: Fri, 25 Apr 2025 12:45:35 -0400 Subject: [PATCH 2/4] wip add templates for extra funcs in registry to for additional data --- contracts/price/ZNSCurvePricer.sol | 2 -- contracts/registry/IZNSRegistry.sol | 2 ++ contracts/registry/ZNSRegistry.sol | 39 ++++++++++++++++++++++++++++- 3 files changed, 40 insertions(+), 3 deletions(-) diff --git a/contracts/price/ZNSCurvePricer.sol b/contracts/price/ZNSCurvePricer.sol index dd11bdfd5..9be9684a1 100644 --- a/contracts/price/ZNSCurvePricer.sol +++ b/contracts/price/ZNSCurvePricer.sol @@ -78,8 +78,6 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I bytes32 feePercentage = bytes32(config.feePercentage); bytes32 isSet = bytes32(abi.encode(config.isSet)); - - // TODO hash with salt? then can unhash when decode? data = abi.encodePacked( maxPrice, diff --git a/contracts/registry/IZNSRegistry.sol b/contracts/registry/IZNSRegistry.sol index f73a74ff6..3eec41b66 100644 --- a/contracts/registry/IZNSRegistry.sol +++ b/contracts/registry/IZNSRegistry.sol @@ -21,6 +21,8 @@ interface IZNSRegistry { struct DomainRecord { address owner; address resolver; + address pricer; + bytes priceConfig; } /** diff --git a/contracts/registry/ZNSRegistry.sol b/contracts/registry/ZNSRegistry.sol index 555770557..842640df7 100644 --- a/contracts/registry/ZNSRegistry.sol +++ b/contracts/registry/ZNSRegistry.sol @@ -146,6 +146,18 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { return records[domainHash].resolver; } + function getDomainPricer( + bytes32 domainHash + ) external view returns (address) { // TODO override + return records[domainHash].pricer; + } + + function getDomainPriceConfig( + bytes32 domainHash + ) external view returns (bytes memory) { // TODO override + return records[domainHash].priceConfig; + } + /** * @notice Creates a new domain record. Only callable by the `ZNSRootRegistrar.sol` * or an address that has REGISTRAR_ROLE. This is one of the last calls in the Register @@ -160,9 +172,13 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { function createDomainRecord( bytes32 domainHash, address owner, + address pricer, + bytes calldata priceConfig, string calldata resolverType - ) external override onlyRegistrar { + ) external onlyRegistrar { // TODO override _setDomainOwner(domainHash, owner); + // _setDomainPricer + // _setDomainPriceConfig // We allow creation of partial domain data with no resolver address if (bytes(resolverType).length != 0) { @@ -218,6 +234,8 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { // `exists` is checked implicitly through the modifier _setDomainOwner(domainHash, owner); _setDomainResolver(domainHash, resolverType); + // _setDomainPricer + // _setDomainPriceConfig } /** @@ -304,6 +322,25 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { emit DomainResolverSet(domainHash, resolver); } + function _setDomainPricer( + bytes32 domainHash, + address pricer + ) internal { + // if (pricer == address(0)) revert ZeroAddress + // if (!supportsInterface(type(pricer).interfaceId)) revert InvalidPricer + // records[domainHash].pricer = pricer; + // emit DomainPricerSet + } + + function _setDomainPriceConfig( + bytes32 domainHash, + bytes calldata priceConfig + ) internal { + // if (config == 0x0) revert EmptyBytes; + // records[domainHash].priceConfig = config; + // emit DomainPriceConfigSet + } + /** * @notice To use UUPS proxy we override this function and revert if `msg.sender` isn't authorized * @param newImplementation The implementation contract to upgrade to From 58d07438afef8f630d8d22fa024a81c9a378813f Mon Sep 17 00:00:00 2001 From: James Earle Date: Fri, 25 Apr 2025 16:06:27 -0400 Subject: [PATCH 3/4] add storage in registry for pricer and priceConfig, create getters and setter for pair --- contracts/registrar/ZNSRootRegistrar.sol | 16 +++++++- contracts/registry/IZNSRegistry.sol | 28 ++++++++++++++ contracts/registry/ZNSRegistry.sol | 47 ++++++++++++++++-------- contracts/utils/CommonErrors.sol | 4 ++ 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/contracts/registrar/ZNSRootRegistrar.sol b/contracts/registrar/ZNSRootRegistrar.sol index 5cb841e91..675193116 100644 --- a/contracts/registrar/ZNSRootRegistrar.sol +++ b/contracts/registrar/ZNSRootRegistrar.sol @@ -179,13 +179,25 @@ contract ZNSRootRegistrar is // If the `domainAddress` is not provided upon registration, a user can call `ZNSAddressResolver.setAddress` // to set the address themselves. if (args.domainAddress != address(0)) { - registry.createDomainRecord(args.domainHash, args.registrant, "address"); + registry.createDomainRecord( + args.domainHash, + args.registrant, + address(0), // TODO TEMP + bytes("0"), // TODO TEMP + "address" + ); IZNSAddressResolver(registry.getDomainResolver(args.domainHash)) .setAddress(args.domainHash, args.domainAddress); } else { // By passing an empty string we tell the registry to not add a resolver - registry.createDomainRecord(args.domainHash, args.registrant, ""); + registry.createDomainRecord( + args.domainHash, + args.registrant, + address(0), // TODO TEMP + bytes("0"), // TODO TEMP + "" + ); } // Because we check in the web app for the existance of both values in a payment config, diff --git a/contracts/registry/IZNSRegistry.sol b/contracts/registry/IZNSRegistry.sol index 3eec41b66..7025831cb 100644 --- a/contracts/registry/IZNSRegistry.sol +++ b/contracts/registry/IZNSRegistry.sol @@ -45,6 +45,18 @@ interface IZNSRegistry { address indexed resolver ); + // TODO natspec + event DomainPricerSet( + bytes32 indexed domainHash, + address indexed pricer + ); + + // TODO natspec + event DomainPriceConfigSet( + bytes32 indexed domainHash, + bytes indexed priceConfig + ); + /** * @notice Emits when a domain record is deleted * @param domainHash The hash of a domain's name @@ -117,9 +129,19 @@ interface IZNSRegistry { bytes32 domainHash ) external view returns (address); + function getDomainPricer( + bytes32 domainHash + ) external view returns (address); + + function getDomainPriceConfig( + bytes32 domainHash + ) external view returns (bytes memory); + function createDomainRecord( bytes32 domainHash, address owner, + address pricer, + bytes calldata priceConfig, string calldata resolverType ) external; @@ -149,5 +171,11 @@ interface IZNSRegistry { string calldata resolverType ) external; + function updateDomainPricerAndConfig( + bytes32 domainHash, + address pricer, + bytes memory priceConfig + ) external; + function deleteRecord(bytes32 domainHash) external; } diff --git a/contracts/registry/ZNSRegistry.sol b/contracts/registry/ZNSRegistry.sol index 842640df7..8e733f243 100644 --- a/contracts/registry/ZNSRegistry.sol +++ b/contracts/registry/ZNSRegistry.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.26; import { IZNSRegistry } from "./IZNSRegistry.sol"; import { AAccessControlled } from "../access/AAccessControlled.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { ZeroAddressPassed, NotAuthorizedForDomain } from "../utils/CommonErrors.sol"; +import { ZeroAddressPassed, ZeroValuePassed, AddressIsNotAContract, NotAuthorizedForDomain } from "../utils/CommonErrors.sol"; /** @@ -148,13 +148,13 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { function getDomainPricer( bytes32 domainHash - ) external view returns (address) { // TODO override + ) external view override returns (address) { return records[domainHash].pricer; } function getDomainPriceConfig( bytes32 domainHash - ) external view returns (bytes memory) { // TODO override + ) external view override returns (bytes memory) { return records[domainHash].priceConfig; } @@ -173,12 +173,15 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { bytes32 domainHash, address owner, address pricer, - bytes calldata priceConfig, + bytes memory priceConfig, string calldata resolverType - ) external onlyRegistrar { // TODO override + ) external override onlyRegistrar { _setDomainOwner(domainHash, owner); - // _setDomainPricer - // _setDomainPriceConfig + + if (pricer != address(0) && priceConfig.length > 0) { + _setDomainPricer(domainHash, pricer); + _setDomainPriceConfig(domainHash, priceConfig); + } // We allow creation of partial domain data with no resolver address if (bytes(resolverType).length != 0) { @@ -272,6 +275,17 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { _setDomainResolver(domainHash, resolverType); } + // TODO natspec + // when changing pricer, must also change price config + function updateDomainPricerAndConfig( + bytes32 domainHash, + address pricer, + bytes memory priceConfig + ) external override onlyOwnerOrOperator(domainHash) { + _setDomainPricer(domainHash, pricer); + _setDomainPriceConfig(domainHash, priceConfig); + } + /** * @notice Deletes a domain's record from this contract's state. * This can ONLY be called by the `ZNSRootRegistrar.sol` contract as part of the Revoke flow @@ -326,19 +340,22 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { bytes32 domainHash, address pricer ) internal { - // if (pricer == address(0)) revert ZeroAddress - // if (!supportsInterface(type(pricer).interfaceId)) revert InvalidPricer - // records[domainHash].pricer = pricer; - // emit DomainPricerSet + if (pricer == address(0)) revert ZeroAddressPassed(); + if (pricer.code.length == 0) revert AddressIsNotAContract(); + // TODO ERC165 `supportsInterface` + + records[domainHash].pricer = pricer; + emit DomainPricerSet(domainHash, pricer); } function _setDomainPriceConfig( bytes32 domainHash, - bytes calldata priceConfig + bytes memory priceConfig ) internal { - // if (config == 0x0) revert EmptyBytes; - // records[domainHash].priceConfig = config; - // emit DomainPriceConfigSet + if (priceConfig.length == 0) revert ZeroValuePassed(); + + records[domainHash].priceConfig = priceConfig; + emit DomainPriceConfigSet(domainHash, priceConfig); } /** diff --git a/contracts/utils/CommonErrors.sol b/contracts/utils/CommonErrors.sol index 11b22462f..c8328c6d0 100644 --- a/contracts/utils/CommonErrors.sol +++ b/contracts/utils/CommonErrors.sol @@ -4,6 +4,10 @@ pragma solidity 0.8.26; error ZeroAddressPassed(); +error ZeroValuePassed(); + +error AddressIsNotAContract(); + error DomainAlreadyExists(bytes32 domainHash); error NotAuthorizedForDomain(address caller, bytes32 domainHash); From c4a565867234253a82a63f61bd01fb493a834dcc Mon Sep 17 00:00:00 2001 From: James Earle Date: Tue, 29 Apr 2025 17:32:40 -0700 Subject: [PATCH 4/4] WIP add flow in registrars (mostly) and specifics for fixed pricer as well --- contracts/price/IZNSCurvePricer.sol | 30 +- contracts/price/IZNSFixedPricer.sol | 19 +- contracts/price/ZNSCurvePricer.sol | 318 ++++++------------ contracts/price/ZNSFixedPricer.sol | 128 +++---- contracts/registrar/IZNSRootRegistrar.sol | 6 +- contracts/registrar/IZNSSubRegistrar.sol | 38 +-- contracts/registrar/ZNSRootRegistrar.sol | 31 +- contracts/registrar/ZNSSubRegistrar.sol | 29 +- contracts/registry/IZNSRegistry.sol | 50 +-- contracts/registry/ZNSRegistry.sol | 98 +++--- contracts/types/IDistributionConfig.sol | 23 ++ contracts/types/IZNSPricer.sol | 23 +- .../distribution/ZNSSubRegistrarMock.sol | 5 +- 13 files changed, 350 insertions(+), 448 deletions(-) diff --git a/contracts/price/IZNSCurvePricer.sol b/contracts/price/IZNSCurvePricer.sol index 0551a4c25..5f36c94f8 100644 --- a/contracts/price/IZNSCurvePricer.sol +++ b/contracts/price/IZNSCurvePricer.sol @@ -11,7 +11,8 @@ interface IZNSCurvePricer is ICurvePriceConfig, IZNSPricer { * @notice Reverted when multiplier passed by the domain owner * is equal to 0 or more than 10^18, which is too large. */ - error InvalidPrecisionMultiplierPassed(bytes32 domainHash); + // error InvalidPrecisionMultiplierPassed(bytes32 domainHash); + error InvalidPrecisionMultiplierPassed(); /** * @notice Emitted when the `maxPrice` is set in `CurvePriceConfig` @@ -70,12 +71,16 @@ interface IZNSCurvePricer is ICurvePriceConfig, IZNSPricer { function initialize( address accessController_, - address registry_, - CurvePriceConfig calldata zeroPriceConfig_ + address registry_ + // CurvePriceConfig calldata zeroPriceConfig_ ) external; + function encodeConfig( + CurvePriceConfig calldata config + ) external returns(bytes memory); + function getPrice( - bytes32 parentHash, + bytes memory parentPriceConfig, string calldata label, bool skipValidityCheck ) external view returns (uint256); @@ -94,22 +99,5 @@ interface IZNSCurvePricer is ICurvePriceConfig, IZNSPricer { uint256 stakeFee ); - function setPriceConfig( - bytes32 domainHash, - CurvePriceConfig calldata priceConfig - ) external; - - function setMaxPrice(bytes32 domainHash, uint256 maxPrice) external; - - function setBaseLength(bytes32 domainHash, uint256 length) external; - - function setMaxLength(bytes32 domainHash, uint256 length) external; - - function setCurveMultiplier(bytes32 domainHash, uint256 curveMultiplier) external; - - function setPrecisionMultiplier(bytes32 domainHash, uint256 multiplier) external; - - function setFeePercentage(bytes32 domainHash, uint256 feePercentage) external; - function setRegistry(address registry_) external; } diff --git a/contracts/price/IZNSFixedPricer.sol b/contracts/price/IZNSFixedPricer.sol index 69cdfad8b..ffe654f50 100644 --- a/contracts/price/IZNSFixedPricer.sol +++ b/contracts/price/IZNSFixedPricer.sol @@ -35,22 +35,14 @@ interface IZNSFixedPricer is IZNSPricer { function initialize(address _accessController, address _registry) external; - function setPrice(bytes32 domainHash, uint256 _price) external; - function getPrice( - bytes32 parentHash, + bytes memory parentPriceConfig, string calldata label, bool skipValidityCheck - ) external view returns (uint256); - - function setFeePercentage( - bytes32 domainHash, - uint256 feePercentage - ) external; + ) external pure returns (uint256); function getFeeForPrice( - bytes32 parentHash, - uint256 price + bytes memory parentPriceConfig ) external view returns (uint256); function getPriceAndFee( @@ -59,10 +51,5 @@ interface IZNSFixedPricer is IZNSPricer { bool skipValidityCheck ) external view returns (uint256 price, uint256 fee); - function setPriceConfig( - bytes32 domainHash, - PriceConfig calldata priceConfig - ) external; - function setRegistry(address registry_) external; } diff --git a/contracts/price/ZNSCurvePricer.sol b/contracts/price/ZNSCurvePricer.sol index 9be9684a1..da0371286 100644 --- a/contracts/price/ZNSCurvePricer.sol +++ b/contracts/price/ZNSCurvePricer.sol @@ -34,12 +34,6 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I */ uint256 public constant FACTOR_SCALE = 1000; - /** - * @notice Mapping of domainHash to the price config for that domain set by the parent domain owner. - * @dev Zero, for pricing root domains, uses this mapping as well under 0x0 hash. - */ - mapping(bytes32 domainHash => CurvePriceConfig config) public priceConfigs; - /// @custom:oz-upgrades-unsafe-allow constructor constructor() { _disableInitializers(); @@ -53,32 +47,38 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I * - `setPrecisionMultiplier()` to validate precision multiplier * @param accessController_ the address of the ZNSAccessController contract. * @param registry_ the address of the ZNSRegistry contract. - * @param zeroPriceConfig_ a number of variables that participate in the price calculation for subdomains. */ function initialize( address accessController_, - address registry_, - CurvePriceConfig calldata zeroPriceConfig_ + address registry_ + // CurvePriceConfig calldata zeroPriceConfig_ ) external override initializer { _setAccessController(accessController_); _setRegistry(registry_); - setPriceConfig(0x0, zeroPriceConfig_); + // TODO how / where do we set the 0x0 price config? + // in root registrar init? + // setPriceConfig(0x0, zeroPriceConfig_); } - bytes public data; + /** + * @notice Real encoding happens off chain, but we keep this here as a + * helper function for users to ensure that their data is correct + * + * @param config The price config to encode + */ function encodeConfig( CurvePriceConfig calldata config - ) external { + ) external pure returns(bytes memory) { bytes32 maxPrice = bytes32(config.maxPrice); bytes32 curveMultiplier = bytes32(config.curveMultiplier); bytes32 maxLength = bytes32(config.maxLength); bytes32 baseLength = bytes32(config.baseLength); bytes32 precisionMultiplier = bytes32(config.precisionMultiplier); bytes32 feePercentage = bytes32(config.feePercentage); - bytes32 isSet = bytes32(abi.encode(config.isSet)); + bytes32 isSet = bytes32(abi.encode(config.isSet)); // maybe dont need - data = + return abi.encodePacked( maxPrice, curveMultiplier, @@ -90,9 +90,10 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I ); } - function decodeConfig( - bytes calldata inData - ) external pure returns (CurvePriceConfig memory config) { + // todo move internal func when finished + function _decodePriceConfig( + bytes memory priceConfig + ) internal pure returns(CurvePriceConfig memory) { ( uint256 maxPrice, uint256 curveMultiplier, @@ -102,7 +103,7 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I uint256 feePercentage, bool isSet ) = abi.decode( - inData, + priceConfig, ( uint256, uint256, @@ -114,7 +115,7 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I ) ); - CurvePriceConfig memory localConfig = CurvePriceConfig( + CurvePriceConfig memory config = CurvePriceConfig( maxPrice, curveMultiplier, maxLength, @@ -124,7 +125,19 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I isSet ); - return localConfig; + return config; + } + + function validatePriceConfig( + bytes memory priceConfig + ) external pure { + CurvePriceConfig memory localConfig = _decodePriceConfig(priceConfig); + + _validatePrecisionMultiplier(localConfig.precisionMultiplier); + _validateBaseLength(localConfig.baseLength, localConfig); + _validateCurveMultiplier(localConfig.curveMultiplier, localConfig); + _validateMaxLength(localConfig.maxLength, localConfig); + _validateFeePercentage(localConfig.feePercentage); } /** @@ -136,16 +149,22 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I * Note that if calling this function directly to find out the price, a user should always pass "false" * as `skipValidityCheck` param, otherwise, the price will be returned for an invalid label that is not * possible to register. - * @param parentHash The hash of the parent domain under which price is determined + * @param parentPriceConfig The hash of the parent domain under which price is determined * @param label The label of the subdomain candidate to get the price for before/during registration * @param skipValidityCheck If true, skips the validity check for the label */ function getPrice( - bytes32 parentHash, + bytes memory parentPriceConfig, string calldata label, bool skipValidityCheck ) public view override returns (uint256) { - if (!priceConfigs[parentHash].isSet) revert ParentPriceConfigNotSet(parentHash); + CurvePriceConfig memory config = _decodePriceConfig(parentPriceConfig); + + // TODO if still want/need isset leave this, otherwise delete + // and just pass bytes to internal func to decode there + // ideally if always set at the same time as pricerContract then shouldnt need + // but need to be sure they cant be set separately first + if (!config.isSet) revert ParentPriceConfigNotSet(); if (!skipValidityCheck) { // Confirms string values are only [a-z0-9-] @@ -156,7 +175,7 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I // No pricing is set for 0 length domains if (length == 0) return 0; - return _getPrice(parentHash, length); + return _getPrice(parentPriceConfig, length); } /** @@ -170,7 +189,8 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I bytes32 parentHash, uint256 price ) public view override returns (uint256) { - return (price * priceConfigs[parentHash].feePercentage) / PERCENTAGE_BASIS; + // todo + // return (price * priceConfigs[parentHash].feePercentage) / PERCENTAGE_BASIS; } /** @@ -185,223 +205,90 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I string calldata label, bool skipValidityCheck ) external view override returns (uint256 price, uint256 stakeFee) { - price = getPrice(parentHash, label, skipValidityCheck); - stakeFee = getFeeForPrice(parentHash, price); - return (price, stakeFee); - } - - /** - * @notice Setter for `priceConfigs[domainHash]`. Only domain owner/operator can call this function. - * @dev Validates the value of the `precisionMultiplier`. - * fires `PriceConfigSet` event. - * Only the owner of the domain or an allowed operator can call this function. - * > This function should ALWAYS be used to set the config, since it's the only place where `isSet` is set to true. - * > Use the other individual setters to modify only, since they do not set this variable! - * @param domainHash The domain hash to set the price config for - * @param priceConfig The new price config to set - */ - function setPriceConfig( - bytes32 domainHash, - CurvePriceConfig calldata priceConfig - ) public override { - _validateSetPrecisionMultiplier(domainHash, priceConfig.precisionMultiplier); - _validateSetBaseLength(domainHash, priceConfig.baseLength, priceConfig); - priceConfigs[domainHash].maxPrice = priceConfig.maxPrice; - _validateSetCurveMultiplier(domainHash, priceConfig.curveMultiplier, priceConfig); - _validateSetMaxLength(domainHash, priceConfig.maxLength, priceConfig); - _validateSetFeePercentage(domainHash, priceConfig.feePercentage); - priceConfigs[domainHash].isSet = true; - - emit PriceConfigSet( - domainHash, - priceConfig.maxPrice, - priceConfig.curveMultiplier, - priceConfig.maxLength, - priceConfig.baseLength, - priceConfig.precisionMultiplier, - priceConfig.feePercentage - ); - } - - /** - * @notice Sets the max price for domains. Validates the config with the new price. - * Fires `MaxPriceSet` event. - * Only domain owner can call this function. - * > `maxPrice` can be set to 0 along with `baseLength` to make all domains free! - * > `maxPrice` cannot be 0 when: - * - `maxLength` is 0; - * - `baseLength` AND `curveMultiplier` are 0; - * @dev In the case of 0 we do not validate, since setting it to 0 will make all subdomains free. - * @param domainHash The domain hash to set the `maxPrice` for it - * @param maxPrice The maximum price to set - */ - function setMaxPrice( - bytes32 domainHash, - uint256 maxPrice - ) external override onlyOwnerOrOperator(domainHash) { - priceConfigs[domainHash].maxPrice = maxPrice; - emit MaxPriceSet(domainHash, maxPrice); - } - - /** - * @notice Sets the multiplier for domains calculations - * to allow the hyperbolic price curve to be bent all the way to a straight line. - * Validates the config with the new multiplier in case where `baseLength` is 0 too. - * Fires `CurveMultiplier` event. - * Only domain owner can call this function. - * - If `curveMultiplier` = 1.000 - default. Makes a canonical hyperbola fucntion. - * - It can be "0", which makes all domain prices max. - * - If it is less than 1.000, then it pulls the bend towards the straight line. - * - If it is bigger than 1.000, then it makes bigger slope on the chart. - * @param domainHash The domain hash to set the price config for - * @param curveMultiplier Multiplier for bending the price function (graph) - */ - function setCurveMultiplier( - bytes32 domainHash, - uint256 curveMultiplier - ) external override onlyOwnerOrOperator(domainHash) { - CurvePriceConfig memory config = priceConfigs[domainHash]; - _validateSetCurveMultiplier(domainHash, curveMultiplier, config); - emit CurveMultiplierSet(domainHash, curveMultiplier); + // TODO + // price = getPrice(parentHash, label, skipValidityCheck); + // stakeFee = getFeeForPrice(parentHash, price); + // return (price, stakeFee); } - function _validateSetCurveMultiplier( - bytes32 domainHash, + // /** + // * @notice Setter for `priceConfigs[domainHash]`. Only domain owner/operator can call this function. + // * @dev Validates the value of the `precisionMultiplier`. + // * fires `PriceConfigSet` event. + // * Only the owner of the domain or an allowed operator can call this function. + // * > This function should ALWAYS be used to set the config, since it's the only place where `isSet` is set to true. + // * > Use the other individual setters to modify only, since they do not set this variable! + // * @param domainHash The domain hash to set the price config for + // * @param priceConfig The new price config to set + // */ + // function setPriceConfig( + // bytes32 domainHash, + // CurvePriceConfig calldata priceConfig + // ) public override { + + // // call to individual validations + // _validateSetPrecisionMultiplier(domainHash, priceConfig.precisionMultiplier); + // _validateSetBaseLength(domainHash, priceConfig.baseLength, priceConfig); + // priceConfigs[domainHash].maxPrice = priceConfig.maxPrice; + // _validateSetCurveMultiplier(domainHash, priceConfig.curveMultiplier, priceConfig); + // _validateSetMaxLength(domainHash, priceConfig.maxLength, priceConfig); + // _validateSetFeePercentage(domainHash, priceConfig.feePercentage); + // priceConfigs[domainHash].isSet = true; + + // emit PriceConfigSet( // have this emit in registrar + // domainHash, + // priceConfig.maxPrice, + // priceConfig.curveMultiplier, + // priceConfig.maxLength, + // priceConfig.baseLength, + // priceConfig.precisionMultiplier, + // priceConfig.feePercentage + // ); + // } + + function _validateCurveMultiplier( uint256 curveMultiplier, CurvePriceConfig memory config - ) internal onlyOwnerOrOperator(domainHash) { + ) internal pure { if (curveMultiplier == 0 && config.baseLength == 0) - revert DivisionByZero(domainHash); - - priceConfigs[domainHash].curveMultiplier = curveMultiplier; + revert DivisionByZero(); } - /** - * @notice Set the value of the domain name length boundary where the `maxPrice` applies - * e.g. A value of '5' means all domains <= 5 in length cost the `maxPrice` price - * Validates the config with the new length. Fires `BaseLengthSet` event. - * Only domain owner/operator can call this function. - * > `baseLength` can be set to 0 to make all domains free. - * > `baseLength` can be = `maxLength` to make all domain prices max. - * > This indicates to the system that we are - * > currently in a special phase where we define an exact price for all domains - * > e.g. promotions or sales - * @param domainHash The domain hash to set the `baseLength` for - * @param baseLength Boundary to set - */ - function setBaseLength( - bytes32 domainHash, - uint256 baseLength - ) external override onlyOwnerOrOperator(domainHash) { - CurvePriceConfig memory config = priceConfigs[domainHash]; - _validateSetBaseLength(domainHash, baseLength, config); - emit BaseLengthSet(domainHash, baseLength); - } - - function _validateSetBaseLength( - bytes32 domainHash, + function _validateBaseLength( uint256 baseLength, CurvePriceConfig memory config - ) internal onlyOwnerOrOperator(domainHash) { - + ) internal pure { if (config.maxLength < baseLength) - revert MaxLengthSmallerThanBaseLength(domainHash); + revert MaxLengthSmallerThanBaseLength(); if (baseLength == 0 && config.curveMultiplier == 0) - revert DivisionByZero(domainHash); - - priceConfigs[domainHash].baseLength = baseLength; + revert DivisionByZero(); } - /** - * @notice Set the maximum length of a domain name to which price formula applies. - * All domain names (labels) that are longer than this value will cost the lowest price at maxLength. - * Validates the config with the new length. - * Fires `MaxLengthSet` event. - * Only domain owner/operator can call this function. - * > `maxLength` can't be set to 0 or less than `baseLength`! - * > If `maxLength` = `baseLength` it makes all domain prices max. - * @param domainHash The domain hash to set the `maxLength` for - * @param maxLength The maximum length to set - */ - function setMaxLength( - bytes32 domainHash, - uint256 maxLength - ) external override onlyOwnerOrOperator(domainHash) { - CurvePriceConfig memory config = priceConfigs[domainHash]; - _validateSetMaxLength(domainHash, maxLength, config); - emit MaxLengthSet(domainHash, maxLength); - } - - function _validateSetMaxLength( - bytes32 domainHash, + function _validateMaxLength( uint256 maxLength, CurvePriceConfig memory config - ) internal onlyOwnerOrOperator(domainHash) { + ) internal pure { if ( (maxLength < config.baseLength) || maxLength == 0 - ) revert MaxLengthSmallerThanBaseLength(domainHash); - - priceConfigs[domainHash].maxLength = maxLength; - } - - /** - * @notice Sets the precision multiplier for the price calculation. - * Multiplier This should be picked based on the number of token decimals - * to calculate properly. - * e.g. if we use a token with 18 decimals, and want precision of 2, - * our precision multiplier will be equal to `10^(18 - 2) = 10^16` - * Fires `PrecisionMultiplierSet` event. - * Only domain owner/operator can call this function. - * > Multiplier should be less or equal to 10^18 and greater than 0! - * @param domainHash The domain hash to set `PrecisionMultiplier` - * @param multiplier The multiplier to set - */ - function setPrecisionMultiplier( - bytes32 domainHash, - uint256 multiplier - ) public override onlyOwnerOrOperator(domainHash) { - _validateSetPrecisionMultiplier(domainHash, multiplier); - emit PrecisionMultiplierSet(domainHash, multiplier); + ) revert MaxLengthSmallerThanBaseLength(); } - function _validateSetPrecisionMultiplier( - bytes32 domainHash, + function _validatePrecisionMultiplier( uint256 multiplier - ) internal { - if (multiplier == 0 || multiplier > 10**18) revert InvalidPrecisionMultiplierPassed(domainHash); - - priceConfigs[domainHash].precisionMultiplier = multiplier; - } - - /** - * @notice Sets the fee percentage for domain registration. - * @dev Fee percentage is set according to the basis of 10000, outlined in `PERCENTAGE_BASIS`. - * Fires `FeePercentageSet` event. - * Only domain owner/operator can call this function. - * @param domainHash The domain hash to set the fee percentage for - * @param feePercentage The fee percentage to set - */ - function setFeePercentage( - bytes32 domainHash, - uint256 feePercentage - ) public override onlyOwnerOrOperator(domainHash) { - _validateSetFeePercentage(domainHash, feePercentage); - emit FeePercentageSet(domainHash, feePercentage); + ) internal pure { + if (multiplier == 0 || multiplier > 10**18) revert InvalidPrecisionMultiplierPassed(); } - function _validateSetFeePercentage( - bytes32 domainHash, + function _validateFeePercentage( uint256 feePercentage - ) internal onlyOwnerOrOperator(domainHash) { + ) internal pure { if (feePercentage > PERCENTAGE_BASIS) revert FeePercentageValueTooLarge( feePercentage, PERCENTAGE_BASIS ); - - priceConfigs[domainHash].feePercentage = feePercentage; } /** @@ -433,14 +320,15 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I * what we care about, then multiplied by the same precision multiplier to get the actual value * with truncated values past precision. So having a value of `15.235234324234512365 * 10^18` * with precision `2` would give us `15.230000000000000000 * 10^18` - * @param parentHash The parent hash + * @param priceConfig The parent price config * @param length The length of the domain name */ function _getPrice( - bytes32 parentHash, + bytes memory priceConfig, uint256 length ) internal view returns (uint256) { - CurvePriceConfig memory config = priceConfigs[parentHash]; + // todo if we keep isset then this func takes in config struct itself + CurvePriceConfig memory config = _decodePriceConfig(priceConfig); // We use `maxPrice` as 0 to indicate free domains if (config.maxPrice == 0) return 0; diff --git a/contracts/price/ZNSFixedPricer.sol b/contracts/price/ZNSFixedPricer.sol index e3be189a9..5ad3fd613 100644 --- a/contracts/price/ZNSFixedPricer.sol +++ b/contracts/price/ZNSFixedPricer.sol @@ -20,7 +20,7 @@ contract ZNSFixedPricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I /** * @notice Mapping of domainHash to price config set by the domain owner/operator */ - mapping(bytes32 domainHash => PriceConfig config) public priceConfigs; + // mapping(bytes32 domainHash => PriceConfig config) public priceConfigs; /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -33,12 +33,42 @@ contract ZNSFixedPricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I } /** - * @notice Sets the price for a domain. Only callable by domain owner/operator. Emits a `PriceSet` event. - * @param domainHash The hash of the domain who sets the price for subdomains - * @param _price The new price value set - */ - function setPrice(bytes32 domainHash, uint256 _price) public override onlyOwnerOrOperator(domainHash) { - _setPrice(domainHash, _price); + * @notice Real encoding happens off chain, but we keep this here as a + * helper function for users to ensure that their data is correct + * + * @param config The price to encode + */ + function encodeConfig( + PriceConfig memory config + ) external pure returns(bytes memory) { + // TODO necessary to have this if only single var? + bytes32 price = bytes32(config.price); + bytes32 feePercentage = bytes32(config.feePercentage); + bytes32 isSet = bytes32(abi.encode(config.isSet)); // TODO maybe dont need? + + return abi.encodePacked( + price, + feePercentage, + isSet + ); + } + + function _decodePriceConfig( + bytes memory priceConfig + ) internal pure returns(PriceConfig memory) { + ( + uint256 price, + uint256 feePercentage, + bool isSet + ) = abi.decode(priceConfig, (uint256, uint256, bool)); + + PriceConfig memory config = PriceConfig( + price, + feePercentage, + isSet + ); + + return config; } /** @@ -50,70 +80,44 @@ contract ZNSFixedPricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I * Note that if calling this function directly to find out the price, a user should always pass "false" * as `skipValidityCheck` param, otherwise, the price will be returned for an invalid label that is not * possible to register. - * @param parentHash The hash of the parent domain to check the price under + * @param parentPriceConfig The hash of the parent domain to check the price under * @param label The label of the subdomain candidate to check the price for * @param skipValidityCheck If true, skips the validity check for the label */ // solhint-disable-next-line no-unused-vars function getPrice( - bytes32 parentHash, + bytes memory parentPriceConfig, string calldata label, bool skipValidityCheck - ) public override view returns (uint256) { - if (!priceConfigs[parentHash].isSet) revert ParentPriceConfigNotSet(parentHash); - + ) public override pure returns (uint256) { if (!skipValidityCheck) { // Confirms string values are only [a-z0-9-] label.validate(); } - return priceConfigs[parentHash].price; - } + PriceConfig memory config = _decodePriceConfig(parentPriceConfig); - /** - * @notice Sets the feePercentage for a domain. Only callable by domain owner/operator. - * Emits a `FeePercentageSet` event. - * @dev `feePercentage` is set as a part of the `PERCENTAGE_BASIS` of 10,000 where 1% = 100 - * @param domainHash The hash of the domain who sets the feePercentage for subdomains - * @param feePercentage The new feePercentage value set - */ - function setFeePercentage( - bytes32 domainHash, - uint256 feePercentage - ) public override onlyOwnerOrOperator(domainHash) { - _setFeePercentage(domainHash, feePercentage); + return config.price; } - /** - * @notice Setter for `priceConfigs[domainHash]`. Only domain owner/operator can call this function. - * @dev Sets both `PriceConfig.price` and `PriceConfig.feePercentage` in one call, fires `PriceSet` - * and `FeePercentageSet` events. - * > This function should ALWAYS be used to set the config, since it's the only place where `isSet` is set to true. - * > Use the other individual setters to modify only, since they do not set this variable! - * @param domainHash The domain hash to set the price config for - * @param priceConfig The new price config to set - */ - function setPriceConfig( - bytes32 domainHash, - PriceConfig calldata priceConfig - ) external override { - setPrice(domainHash, priceConfig.price); - setFeePercentage(domainHash, priceConfig.feePercentage); - priceConfigs[domainHash].isSet = true; + function validatePriceConfig(bytes memory priceConfig) external pure { + // TODO + // not really needed for single variable pricers + // but to fulfill interface IZNSPricer we must define this + // decode like curvepricer, then modify validators in same way } /** * @notice Part of the IZNSPricer interface - one of the functions required * for any pricing contracts used with ZNS. It returns fee for a given price * based on the value set by the owner of the parent domain. - * @param parentHash The hash of the parent domain under which fee is determined - * @param price The price to get the fee for + * @param parentPriceConfig The hash of the parent domain under which fee is determined */ function getFeeForPrice( - bytes32 parentHash, - uint256 price - ) public view override returns (uint256) { - return (price * priceConfigs[parentHash].feePercentage) / PERCENTAGE_BASIS; + bytes memory parentPriceConfig + ) public pure override returns (uint256) { + PriceConfig memory config = _decodePriceConfig(parentPriceConfig); + return (config.price * config.feePercentage) / PERCENTAGE_BASIS; } /** @@ -129,9 +133,9 @@ contract ZNSFixedPricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I string calldata label, bool skipValidityCheck ) external view override returns (uint256 price, uint256 fee) { - price = getPrice(parentHash, label, skipValidityCheck); - fee = getFeeForPrice(parentHash, price); - return (price, fee); + // price = getPrice(parentHash, label, skipValidityCheck); + // fee = getFeeForPrice(parentHash, price); + // return (price, fee); } /** @@ -142,28 +146,6 @@ contract ZNSFixedPricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I _setRegistry(registry_); } - /** - * @notice Internal function for set price - * @param domainHash The hash of the domain - * @param price The new price - */ - function _setPrice(bytes32 domainHash, uint256 price) internal { - priceConfigs[domainHash].price = price; - emit PriceSet(domainHash, price); - } - - /** - * @notice Internal function for setFeePercentage - * @param domainHash The hash of the domain - * @param feePercentage The new feePercentage - */ - function _setFeePercentage(bytes32 domainHash, uint256 feePercentage) internal { - if (feePercentage > PERCENTAGE_BASIS) - revert FeePercentageValueTooLarge(feePercentage, PERCENTAGE_BASIS); - - priceConfigs[domainHash].feePercentage = feePercentage; - emit FeePercentageSet(domainHash, feePercentage); - } /** * @notice To use UUPS proxy we override this function and revert if `msg.sender` isn't authorized * @param newImplementation The new implementation contract to upgrade to. diff --git a/contracts/registrar/IZNSRootRegistrar.sol b/contracts/registrar/IZNSRootRegistrar.sol index b98da35ce..d6ef0589c 100644 --- a/contracts/registrar/IZNSRootRegistrar.sol +++ b/contracts/registrar/IZNSRootRegistrar.sol @@ -129,6 +129,7 @@ interface IZNSRootRegistrar is IDistributionConfig { address accessController_, address registry_, address rootPricer_, + bytes memory priceConfig_, address treasury_, address domainToken_ ) external; @@ -153,7 +154,10 @@ interface IZNSRootRegistrar is IDistributionConfig { function setRegistry(address registry_) external; - function setRootPricer(address rootPricer_) external; + function setRootPricer( + address rootPricer_, + bytes memory priceConfig_ + ) external; function setTreasury(address treasury_) external; diff --git a/contracts/registrar/IZNSSubRegistrar.sol b/contracts/registrar/IZNSSubRegistrar.sol index ae5bc7bdd..0f089bc3f 100644 --- a/contracts/registrar/IZNSSubRegistrar.sol +++ b/contracts/registrar/IZNSSubRegistrar.sol @@ -4,6 +4,7 @@ pragma solidity 0.8.26; import { IDistributionConfig } from "../types/IDistributionConfig.sol"; import { PaymentConfig } from "../treasury/IZNSTreasury.sol"; import { IZNSPricer } from "../types/IZNSPricer.sol"; +// import { IDistributionConfig } from "../types/IDistributionConfig.sol"; /** @@ -30,26 +31,6 @@ interface IZNSSubRegistrar is IDistributionConfig { address indexed pricerContract ); - /** - * @notice Emitted when a new `DistributionConfig.paymentType` is set for a domain. - */ - event PaymentTypeSet(bytes32 indexed domainHash, PaymentType paymentType); - - /** - * @notice Emitted when a new `DistributionConfig.accessType` is set for a domain. - */ - event AccessTypeSet(bytes32 indexed domainHash, AccessType accessType); - - /** - * @notice Emitted when a new full `DistributionConfig` is set for a domain at once. - */ - event DistributionConfigSet( - bytes32 indexed domainHash, - IZNSPricer pricerContract, - PaymentType paymentType, - AccessType accessType - ); - /** * @notice Emitted when a `mintlist` is updated for a domain. */ @@ -71,13 +52,13 @@ interface IZNSSubRegistrar is IDistributionConfig { */ event RootRegistrarSet(address registrar); - function distrConfigs( - bytes32 domainHash - ) external view returns ( - IZNSPricer pricerContract, - PaymentType paymentType, - AccessType accessType - ); + + // TODO implementation + // function getDistrConfigs( + // bytes32 domainHash + // ) external view returns ( + // DistributionConfig memory distrConfig + // ); function isMintlistedForDomain( bytes32 domainHash, @@ -109,8 +90,9 @@ interface IZNSSubRegistrar is IDistributionConfig { DistributionConfig calldata config ) external; - function setPricerContractForDomain( + function setPricerDataForDomain( bytes32 domainHash, + bytes memory priceConfig, IZNSPricer pricerContract ) external; diff --git a/contracts/registrar/ZNSRootRegistrar.sol b/contracts/registrar/ZNSRootRegistrar.sol index 675193116..010997941 100644 --- a/contracts/registrar/ZNSRootRegistrar.sol +++ b/contracts/registrar/ZNSRootRegistrar.sol @@ -11,7 +11,12 @@ import { IZNSSubRegistrar } from "../registrar/IZNSSubRegistrar.sol"; import { IZNSPricer } from "../types/IZNSPricer.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { StringUtils } from "../utils/StringUtils.sol"; -import { ZeroAddressPassed, DomainAlreadyExists } from "../utils/CommonErrors.sol"; +import { + ZeroAddressPassed, + ZeroValuePassed, + AddressIsNotAContract, + DomainAlreadyExists +} from "../utils/CommonErrors.sol"; /** @@ -59,12 +64,14 @@ contract ZNSRootRegistrar is address accessController_, address registry_, address rootPricer_, + bytes memory priceConfig_, address treasury_, address domainToken_ ) external override initializer { _setAccessController(accessController_); setRegistry(registry_); - setRootPricer(rootPricer_); + // TODO pass this as param? or set some default? + setRootPricer(rootPricer_, priceConfig_); setTreasury(treasury_); setDomainToken(domainToken_); } @@ -105,8 +112,9 @@ contract ZNSRootRegistrar is revert DomainAlreadyExists(domainHash); // Get price for the domain - uint256 domainPrice = rootPricer.getPrice(0x0, name, true); - + // TODO fix below + // uint256 domainPrice = rootPricer.getPrice(0x0, name, true); + uint256 domainPrice = 0; // TEMP _coreRegister( CoreRegisterArgs( bytes32(0), @@ -182,8 +190,6 @@ contract ZNSRootRegistrar is registry.createDomainRecord( args.domainHash, args.registrant, - address(0), // TODO TEMP - bytes("0"), // TODO TEMP "address" ); @@ -194,8 +200,6 @@ contract ZNSRootRegistrar is registry.createDomainRecord( args.domainHash, args.registrant, - address(0), // TODO TEMP - bytes("0"), // TODO TEMP "" ); } @@ -355,10 +359,19 @@ contract ZNSRootRegistrar is * Only ADMIN in `ZNSAccessController` can call this function. * @param rootPricer_ Address of the IZNSPricer type contract to set as pricer of Root Domains */ - function setRootPricer(address rootPricer_) public override onlyAdmin { + function setRootPricer( + address rootPricer_, + bytes memory priceConfig_ + ) public override onlyAdmin { if (rootPricer_ == address(0)) revert ZeroAddressPassed(); + if (priceConfig_.length == 0) revert ZeroValuePassed(); + + if (rootPricer_.code.length == 0) revert AddressIsNotAContract(); + + IZNSPricer(rootPricer).validatePriceConfig(priceConfig_); + rootPricer = IZNSPricer(rootPricer_); emit RootPricerSet(rootPricer_); diff --git a/contracts/registrar/ZNSSubRegistrar.sol b/contracts/registrar/ZNSSubRegistrar.sol index b1b8a65a8..0ae7efdfa 100644 --- a/contracts/registrar/ZNSSubRegistrar.sol +++ b/contracts/registrar/ZNSSubRegistrar.sol @@ -9,7 +9,14 @@ import { ARegistryWired } from "../registry/ARegistryWired.sol"; import { StringUtils } from "../utils/StringUtils.sol"; import { PaymentConfig } from "../treasury/IZNSTreasury.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { DomainAlreadyExists, ZeroAddressPassed, NotAuthorizedForDomain } from "../utils/CommonErrors.sol"; +import { + DomainAlreadyExists, + ZeroAddressPassed, + ZeroValuePassed, + NotAuthorizedForDomain +} from "../utils/CommonErrors.sol"; + +import { IDistributionConfig } from "../types/IDistributionConfig.sol"; /** @@ -31,7 +38,7 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, * These configs are used to determine how subdomains are distributed for every parent. * @dev Note that the rules outlined in the DistributionConfig are only applied to direct children! */ - mapping(bytes32 domainHash => DistributionConfig config) public override distrConfigs; + mapping(bytes32 domainHash => DistributionConfig config) public distrConfigs; struct Mintlist { mapping(uint256 idx => mapping(address candidate => bool allowed)) list; @@ -98,6 +105,7 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, if (registry.exists(domainHash)) revert DomainAlreadyExists(domainHash); + // If this exists we know it has already been validated here DistributionConfig memory parentConfig = distrConfigs[parentHash]; bool isOwnerOrOperator = registry.isOwnerOrOperator(parentHash, msg.sender); @@ -137,7 +145,7 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, } else { coreRegisterArgs.price = IZNSPricer(address(parentConfig.pricerContract)) .getPrice( - parentHash, + parentConfig.priceConfig, label, true ); @@ -186,8 +194,15 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, if (address(config.pricerContract) == address(0)) revert ZeroAddressPassed(); + if (config.priceConfig.length == 0) + revert ZeroValuePassed(); + + // Will revert if invalid + IZNSPricer(config.pricerContract).validatePriceConfig(config.priceConfig); + distrConfigs[domainHash] = config; + // emit price config? emit DistributionConfigSet( domainHash, config.pricerContract, @@ -202,10 +217,12 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, * Only domain owner/operator can call this function. * Fires `PricerContractSet` event. * @param domainHash The domain hash to set the pricer contract for + * @param priceConfig The price config data for the given pricer * @param pricerContract The new pricer contract to set */ - function setPricerContractForDomain( + function setPricerDataForDomain( bytes32 domainHash, + bytes memory priceConfig, IZNSPricer pricerContract ) public override { if (!registry.isOwnerOrOperator(domainHash, msg.sender)) @@ -214,7 +231,11 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, if (address(pricerContract) == address(0)) revert ZeroAddressPassed(); + if (priceConfig.length == 0) + revert ZeroValuePassed(); + distrConfigs[domainHash].pricerContract = pricerContract; + distrConfigs[domainHash].priceConfig = priceConfig; emit PricerContractSet(domainHash, address(pricerContract)); } diff --git a/contracts/registry/IZNSRegistry.sol b/contracts/registry/IZNSRegistry.sol index 7025831cb..001774925 100644 --- a/contracts/registry/IZNSRegistry.sol +++ b/contracts/registry/IZNSRegistry.sol @@ -21,8 +21,8 @@ interface IZNSRegistry { struct DomainRecord { address owner; address resolver; - address pricer; - bytes priceConfig; + // address pricer; + // bytes priceConfig; } /** @@ -46,16 +46,16 @@ interface IZNSRegistry { ); // TODO natspec - event DomainPricerSet( - bytes32 indexed domainHash, - address indexed pricer - ); + // event DomainPricerSet( + // bytes32 indexed domainHash, + // address indexed pricer + // ); - // TODO natspec - event DomainPriceConfigSet( - bytes32 indexed domainHash, - bytes indexed priceConfig - ); + // // TODO natspec + // event DomainPriceConfigSet( + // bytes32 indexed domainHash, + // bytes indexed priceConfig + // ); /** * @notice Emits when a domain record is deleted @@ -129,19 +129,19 @@ interface IZNSRegistry { bytes32 domainHash ) external view returns (address); - function getDomainPricer( - bytes32 domainHash - ) external view returns (address); + // function getDomainPricer( + // bytes32 domainHash + // ) external view returns (address); - function getDomainPriceConfig( - bytes32 domainHash - ) external view returns (bytes memory); + // function getDomainPriceConfig( + // bytes32 domainHash + // ) external view returns (bytes memory); function createDomainRecord( bytes32 domainHash, address owner, - address pricer, - bytes calldata priceConfig, + // address pricer, + // bytes calldata priceConfig, string calldata resolverType ) external; @@ -161,6 +161,8 @@ interface IZNSRegistry { function updateDomainRecord( bytes32 domainHash, address owner, + // address pricer, + // bytes memory priceConfig, string calldata resolverType ) external; @@ -171,11 +173,11 @@ interface IZNSRegistry { string calldata resolverType ) external; - function updateDomainPricerAndConfig( - bytes32 domainHash, - address pricer, - bytes memory priceConfig - ) external; + // function updateDomainPricerAndConfig( + // bytes32 domainHash, + // address pricer, + // bytes memory priceConfig + // ) external; function deleteRecord(bytes32 domainHash) external; } diff --git a/contracts/registry/ZNSRegistry.sol b/contracts/registry/ZNSRegistry.sol index 8e733f243..75df3b60a 100644 --- a/contracts/registry/ZNSRegistry.sol +++ b/contracts/registry/ZNSRegistry.sol @@ -146,17 +146,17 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { return records[domainHash].resolver; } - function getDomainPricer( - bytes32 domainHash - ) external view override returns (address) { - return records[domainHash].pricer; - } - - function getDomainPriceConfig( - bytes32 domainHash - ) external view override returns (bytes memory) { - return records[domainHash].priceConfig; - } + // function getDomainPricer( + // bytes32 domainHash + // ) external view override returns (address) { + // return records[domainHash].pricer; + // } + + // function getDomainPriceConfig( + // bytes32 domainHash + // ) external view override returns (bytes memory) { + // return records[domainHash].priceConfig; + // } /** * @notice Creates a new domain record. Only callable by the `ZNSRootRegistrar.sol` @@ -172,16 +172,16 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { function createDomainRecord( bytes32 domainHash, address owner, - address pricer, - bytes memory priceConfig, + // address pricer, + // bytes memory priceConfig, string calldata resolverType ) external override onlyRegistrar { _setDomainOwner(domainHash, owner); - if (pricer != address(0) && priceConfig.length > 0) { - _setDomainPricer(domainHash, pricer); - _setDomainPriceConfig(domainHash, priceConfig); - } + // if (pricer != address(0) && priceConfig.length > 0) { + // _setDomainPricer(domainHash, pricer); + // _setDomainPriceConfig(domainHash, priceConfig); + // } // We allow creation of partial domain data with no resolver address if (bytes(resolverType).length != 0) { @@ -232,13 +232,15 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { function updateDomainRecord( bytes32 domainHash, address owner, + // address pricer, + // bytes memory priceConfig, string calldata resolverType ) external override onlyOwner(domainHash) { // `exists` is checked implicitly through the modifier _setDomainOwner(domainHash, owner); _setDomainResolver(domainHash, resolverType); - // _setDomainPricer - // _setDomainPriceConfig + // _setDomainPricer(domainHash, pricer); + // _setDomainPriceConfig(domainHash, priceConfig); } /** @@ -277,14 +279,14 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { // TODO natspec // when changing pricer, must also change price config - function updateDomainPricerAndConfig( - bytes32 domainHash, - address pricer, - bytes memory priceConfig - ) external override onlyOwnerOrOperator(domainHash) { - _setDomainPricer(domainHash, pricer); - _setDomainPriceConfig(domainHash, priceConfig); - } + // function updateDomainPricerAndConfig( + // bytes32 domainHash, + // address pricer, + // bytes memory priceConfig + // ) external override onlyOwnerOrOperator(domainHash) { + // _setDomainPricer(domainHash, pricer); + // _setDomainPriceConfig(domainHash, priceConfig); + // } /** * @notice Deletes a domain's record from this contract's state. @@ -336,27 +338,27 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { emit DomainResolverSet(domainHash, resolver); } - function _setDomainPricer( - bytes32 domainHash, - address pricer - ) internal { - if (pricer == address(0)) revert ZeroAddressPassed(); - if (pricer.code.length == 0) revert AddressIsNotAContract(); - // TODO ERC165 `supportsInterface` - - records[domainHash].pricer = pricer; - emit DomainPricerSet(domainHash, pricer); - } - - function _setDomainPriceConfig( - bytes32 domainHash, - bytes memory priceConfig - ) internal { - if (priceConfig.length == 0) revert ZeroValuePassed(); - - records[domainHash].priceConfig = priceConfig; - emit DomainPriceConfigSet(domainHash, priceConfig); - } + // function _setDomainPricer( + // bytes32 domainHash, + // address pricer + // ) internal { + // if (pricer == address(0)) revert ZeroAddressPassed(); + // if (pricer.code.length == 0) revert AddressIsNotAContract(); + // // TODO ERC165 `supportsInterface` + + // records[domainHash].pricer = pricer; + // emit DomainPricerSet(domainHash, pricer); + // } + + // function _setDomainPriceConfig( + // bytes32 domainHash, + // bytes memory priceConfig + // ) internal { + // if (priceConfig.length == 0) revert ZeroValuePassed(); + + // records[domainHash].priceConfig = priceConfig; + // emit DomainPriceConfigSet(domainHash, priceConfig); + // } /** * @notice To use UUPS proxy we override this function and revert if `msg.sender` isn't authorized diff --git a/contracts/types/IDistributionConfig.sol b/contracts/types/IDistributionConfig.sol index 084f07826..ff6b3eefb 100644 --- a/contracts/types/IDistributionConfig.sol +++ b/contracts/types/IDistributionConfig.sol @@ -40,5 +40,28 @@ interface IDistributionConfig { IZNSPricer pricerContract; PaymentType paymentType; AccessType accessType; + address pricer; + bytes priceConfig; + bool isSet; // instead of isSet in individual priceconfig } + + /** + * @notice Emitted when a new `DistributionConfig.paymentType` is set for a domain. + */ + event PaymentTypeSet(bytes32 indexed domainHash, PaymentType paymentType); + + /** + * @notice Emitted when a new `DistributionConfig.accessType` is set for a domain. + */ + event AccessTypeSet(bytes32 indexed domainHash, AccessType accessType); + + /** + * @notice Emitted when a new full `DistributionConfig` is set for a domain at once. + */ + event DistributionConfigSet( + bytes32 indexed domainHash, + IZNSPricer pricerContract, + PaymentType paymentType, + AccessType accessType + ); } diff --git a/contracts/types/IZNSPricer.sol b/contracts/types/IZNSPricer.sol index c6b7a711c..a528cced5 100644 --- a/contracts/types/IZNSPricer.sol +++ b/contracts/types/IZNSPricer.sol @@ -11,7 +11,7 @@ interface IZNSPricer { * @notice Reverted when someone is trying to buy a subdomain under a parent that is not set up for distribution. * Specifically it's prices for subdomains. */ - error ParentPriceConfigNotSet(bytes32 parentHash); + error ParentPriceConfigNotSet(); /** * @notice Reverted when domain owner is trying to set it's stake fee percentage @@ -22,12 +22,14 @@ interface IZNSPricer { /** * @notice Reverted when `maxLength` smaller than `baseLength`. */ - error MaxLengthSmallerThanBaseLength(bytes32 domainHash); + error MaxLengthSmallerThanBaseLength(); + // error MaxLengthSmallerThanBaseLength(bytes32 domainHash); /** * @notice Reverted when `curveMultiplier` AND `baseLength` are 0. */ - error DivisionByZero(bytes32 domainHash); + error DivisionByZero(); + // error DivisionByZero(bytes32 domainHash); /** * @dev `parentHash` param is here to allow pricer contracts @@ -41,7 +43,7 @@ interface IZNSPricer { * possible to register. */ function getPrice( - bytes32 parentHash, + bytes memory parentPriceConfig, string calldata label, bool skipValidityCheck ) external view returns (uint256); @@ -55,14 +57,19 @@ interface IZNSPricer { bytes32 parentHash, string calldata label, bool skipValidityCheck - ) external view returns (uint256 price, uint256 fee); + ) external pure returns (uint256 price, uint256 fee); /** * @notice Returns the fee for a given price. * @dev Fees are only supported for PaymentType.STAKE ! */ function getFeeForPrice( - bytes32 parentHash, - uint256 price - ) external view returns (uint256); + bytes memory parentPriceConfig + ) external pure returns (uint256); + + function validatePriceConfig( + bytes memory priceConfig + ) external pure; + + // todo public encode func? } diff --git a/contracts/upgrade-test-mocks/distribution/ZNSSubRegistrarMock.sol b/contracts/upgrade-test-mocks/distribution/ZNSSubRegistrarMock.sol index eaf21610c..40a12af88 100644 --- a/contracts/upgrade-test-mocks/distribution/ZNSSubRegistrarMock.sol +++ b/contracts/upgrade-test-mocks/distribution/ZNSSubRegistrarMock.sol @@ -31,6 +31,9 @@ struct DistributionConfig { IZNSPricer pricerContract; PaymentType paymentType; AccessType accessType; + address pricer; + bytes priceConfig; + bool isSet; // todo instead of isSet in individual priceconfig, need for mock? address newAddress; uint256 newUint; } @@ -139,7 +142,7 @@ contract ZNSSubRegistrarUpgradeMock is } else { coreRegisterArgs.price = IZNSPricer(address(parentConfig.pricerContract)) .getPrice( - parentHash, + parentConfig.priceConfig, label, true );