diff --git a/contracts/price/IZNSCurvePricer.sol b/contracts/price/IZNSCurvePricer.sol index 0551a4c25..7b1817f0a 100644 --- a/contracts/price/IZNSCurvePricer.sol +++ b/contracts/price/IZNSCurvePricer.sol @@ -1,115 +1,70 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import { ICurvePriceConfig } from "../types/ICurvePriceConfig.sol"; -import { IZNSPricer } from "../types/IZNSPricer.sol"; +import { IZNSPricer } from "./IZNSPricer.sol"; -interface IZNSCurvePricer is ICurvePriceConfig, IZNSPricer { +interface IZNSCurvePricer is IZNSPricer { /** - * @notice Reverted when multiplier passed by the domain owner - * is equal to 0 or more than 10^18, which is too large. + * @notice Struct for each configurable variable for price and fee calculations. */ - error InvalidPrecisionMultiplierPassed(bytes32 domainHash); + struct CurvePriceConfig { + /** + * @notice Maximum price for a domain returned at <= `baseLength` + */ + uint256 maxPrice; + /** + * @notice Multiplier which we use to bend a curve of price on interval from `baseLength` to `maxLength`. + */ + uint256 curveMultiplier; + /** + * @notice Maximum length of a domain name. If the name is longer than this + * value we return the price that was at the `maxLength` + */ + uint256 maxLength; + /** + * @notice Base length of a domain name. If the name is less than or equal to + * this value we return the `maxPrice` + */ + uint256 baseLength; + /** + * @notice The precision multiplier of the price. This multiplier + * 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 10^18 - 10^2 = 10^16 + */ + uint256 precisionMultiplier; + /** + * @notice The registration fee value in percentage as basis points (parts per 10,000) + * so the 2% value would be represented as 200. + * See [getRegistrationFee](#getregistrationfee) for the actual fee calc process. + */ + uint256 feePercentage; + } /** - * @notice Emitted when the `maxPrice` is set in `CurvePriceConfig` - * @param price The new maxPrice value - */ - event MaxPriceSet(bytes32 domainHash, uint256 price); - - /** - * @notice Emitted when the `baseLength` is set in `CurvePriceConfig` - * @param length The new baseLength value - */ - event BaseLengthSet(bytes32 domainHash, uint256 length); - - /** - * @notice Emitted when the `maxLength` is set in `CurvePriceConfig` - * @param length The new maxLength value - */ - event MaxLengthSet(bytes32 domainHash, uint256 length); - - /** - * @notice Emitted when the `precisionMultiplier` is set in `CurvePriceConfig` - * @param precision The new precisionMultiplier value - */ - event PrecisionMultiplierSet(bytes32 domainHash, uint256 precision); - - /** - * @notice Emitted when the `feePercentage` is set in state - * @param feePercentage The new feePercentage value + * @notice Reverted when multiplier passed by the domain owner + * is equal to 0 or more than 10^18, which is too large. */ - event FeePercentageSet(bytes32 domainHash, uint256 feePercentage); + error InvalidPrecisionMultiplierPassed(); /** - * @notice Emitted when the `curveMultiplier` is set in state - * @param curveMultiplier The new curveMultiplier value + * @notice Reverted when `maxLength` smaller than `baseLength`. */ - event CurveMultiplierSet(bytes32 domainHash, uint256 curveMultiplier); - + error MaxLengthSmallerThanBaseLength(); /** - * @notice Emitted when the full `CurvePriceConfig` is set in state - * @param maxPrice The new `maxPrice` value - * @param curveMultiplier The new `curveMultiplier` value - * @param maxLength The new `maxLength` value - * @param baseLength The new `baseLength` value - * @param precisionMultiplier The new `precisionMultiplier` value + * @notice Reverted when `curveMultiplier` AND `baseLength` are 0. */ - event PriceConfigSet( - bytes32 domainHash, - uint256 maxPrice, - uint256 curveMultiplier, - uint256 maxLength, - uint256 baseLength, - uint256 precisionMultiplier, - uint256 feePercentage - ); - - function initialize( - address accessController_, - address registry_, - CurvePriceConfig calldata zeroPriceConfig_ - ) external; - - function getPrice( - bytes32 parentHash, - string calldata label, - bool skipValidityCheck - ) external view returns (uint256); - - function getFeeForPrice( - bytes32 parentHash, - uint256 price - ) external view returns (uint256); - - function getPriceAndFee( - bytes32 parentHash, - string calldata label, - bool skipValidityCheck - ) external view returns ( - uint256 price, - 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; + error DivisionByZero(); - function setPrecisionMultiplier(bytes32 domainHash, uint256 multiplier) external; + function encodeConfig( + CurvePriceConfig calldata config + ) external pure returns (bytes memory); - function setFeePercentage(bytes32 domainHash, uint256 feePercentage) external; - function setRegistry(address registry_) external; + function decodePriceConfig( + bytes memory priceConfig + ) external pure returns (CurvePriceConfig memory); } diff --git a/contracts/price/IZNSFixedPricer.sol b/contracts/price/IZNSFixedPricer.sol index 69cdfad8b..400a5befb 100644 --- a/contracts/price/IZNSFixedPricer.sol +++ b/contracts/price/IZNSFixedPricer.sol @@ -1,10 +1,11 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import { IZNSPricer } from "../types/IZNSPricer.sol"; +import { IZNSPricer } from "./IZNSPricer.sol"; + /** - * @title IZNSFixedPricer.sol Below is the doc for PriceConfig struct. + * @title IZNSFixedPricer.sol Below is the doc for FixedPriceConfig struct. * @notice Struct for price configurations per domainHash that is used in the `priceConfigs` mapping * - price The value determining how much a subdomain under a particular parent would cost * - feePercentage The value determining how much fee is charged for a subdomain registration @@ -14,55 +15,29 @@ import { IZNSPricer } from "../types/IZNSPricer.sol"; */ interface IZNSFixedPricer is IZNSPricer { /** - * @notice Emitted when the `PriceConfig.price` is set in state for a specific `domainHash` + * @notice Emitted when the `FixedPriceConfig.price` is set in state for a specific `domainHash` * @param domainHash The hash of the domain who sets the price for subdomains * @param newPrice The new price value set */ event PriceSet(bytes32 indexed domainHash, uint256 indexed newPrice); /** - * @notice Emitted when the `PriceConfig.feePercentage` is set in state for a specific `domainHash` + * @notice Emitted when the `FixedPriceConfig.feePercentage` is set in state for a specific `domainHash` * @param domainHash The hash of the domain who sets the feePercentage for subdomains * @param feePercentage The new feePercentage value set */ event FeePercentageSet(bytes32 indexed domainHash, uint256 indexed feePercentage); - struct PriceConfig { + struct FixedPriceConfig { uint256 price; uint256 feePercentage; - bool isSet; } - function initialize(address _accessController, address _registry) external; - - function setPrice(bytes32 domainHash, uint256 _price) external; - - function getPrice( - bytes32 parentHash, - string calldata label, - bool skipValidityCheck - ) external view returns (uint256); - - function setFeePercentage( - bytes32 domainHash, - uint256 feePercentage - ) external; - - function getFeeForPrice( - bytes32 parentHash, - uint256 price - ) external view returns (uint256); - - function getPriceAndFee( - bytes32 parentHash, - string calldata label, - bool skipValidityCheck - ) external view returns (uint256 price, uint256 fee); - - function setPriceConfig( - bytes32 domainHash, - PriceConfig calldata priceConfig - ) external; + function encodeConfig( + FixedPriceConfig memory config + ) external pure returns (bytes memory); - function setRegistry(address registry_) external; + function decodePriceConfig( + bytes memory priceConfig + ) external pure returns (FixedPriceConfig memory); } diff --git a/contracts/types/IZNSPricer.sol b/contracts/price/IZNSPricer.sol similarity index 71% rename from contracts/types/IZNSPricer.sol rename to contracts/price/IZNSPricer.sol index c6b7a711c..94f04d989 100644 --- a/contracts/types/IZNSPricer.sol +++ b/contracts/price/IZNSPricer.sol @@ -8,10 +8,9 @@ pragma solidity 0.8.26; */ 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. + * @notice Emitted when the given price config is not the expected length */ - error ParentPriceConfigNotSet(bytes32 parentHash); + error IncorrectPriceConfigLength(); /** * @notice Reverted when domain owner is trying to set it's stake fee percentage @@ -19,16 +18,6 @@ interface IZNSPricer { */ error FeePercentageValueTooLarge(uint256 feePercentage, uint256 maximum); - /** - * @notice Reverted when `maxLength` smaller than `baseLength`. - */ - error MaxLengthSmallerThanBaseLength(bytes32 domainHash); - - /** - * @notice Reverted when `curveMultiplier` AND `baseLength` are 0. - */ - error DivisionByZero(bytes32 domainHash); - /** * @dev `parentHash` param is here to allow pricer contracts * to have different price configs for different subdomains @@ -41,10 +30,10 @@ interface IZNSPricer { * possible to register. */ function getPrice( - bytes32 parentHash, + bytes memory parentPriceConfig, string calldata label, bool skipValidityCheck - ) external view returns (uint256); + ) external pure returns (uint256); /** * @dev Fees are only supported for PaymentType.STAKE ! @@ -52,17 +41,25 @@ interface IZNSPricer { * Instead `getPrice()` will be called. */ function getPriceAndFee( - bytes32 parentHash, + bytes memory parentPriceConfig, 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, + bytes memory parentPriceConfig, uint256 price - ) external view returns (uint256); + ) external pure returns (uint256); + + /** + * @notice Validate a given price config + * @param priceConfig The price config to validate + */ + function validatePriceConfig( + bytes memory priceConfig + ) external pure; } diff --git a/contracts/price/ZNSCurvePricer.sol b/contracts/price/ZNSCurvePricer.sol index 2066a1fe3..e5382e0f8 100644 --- a/contracts/price/ZNSCurvePricer.sol +++ b/contracts/price/ZNSCurvePricer.sol @@ -1,11 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { IZNSCurvePricer } from "./IZNSCurvePricer.sol"; import { StringUtils } from "../utils/StringUtils.sol"; -import { AAccessControlled } from "../access/AAccessControlled.sol"; -import { ARegistryWired } from "../registry/ARegistryWired.sol"; /** @@ -17,7 +14,7 @@ import { ARegistryWired } from "../registry/ARegistryWired.sol"; * The price after `maxLength` is fixed and equals the price on the hyperbola graph at the point `maxLength` * and is determined using the formula where `length` = `maxLength`. */ -contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, IZNSCurvePricer { +contract ZNSCurvePricer is IZNSCurvePricer { using StringUtils for string; @@ -35,35 +32,74 @@ 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(); + * @notice Encode a given CurvePriceConfig into bytes + * + * @param config The CurvePriceConfig to encode into bytes + */ + function encodeConfig( + CurvePriceConfig calldata config + ) external pure override returns (bytes memory) { + return + abi.encodePacked( + config.maxPrice, + config.curveMultiplier, + config.maxLength, + config.baseLength, + config.precisionMultiplier, + config.feePercentage + ); } /** - * @notice Proxy initializer to set the initial state of the contract after deployment. - * Only Owner of the 0x0 hash (Zero owned address) can call this function. - * @dev > Note the for PriceConfig we set each value individually and calling - * 2 important functions that validate all of the config's values against the formula: - * - `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. + * @notice Decode bytes into a CurvePriceConfig + * + * @param priceConfig The bytes to decode */ - function initialize( - address accessController_, - address registry_, - CurvePriceConfig calldata zeroPriceConfig_ - ) external override initializer { - _setAccessController(accessController_); - _setRegistry(registry_); + function decodePriceConfig( + bytes memory priceConfig + ) public pure override returns (CurvePriceConfig memory) { + _checkLength(priceConfig); + + ( + uint256 maxPrice, + uint256 curveMultiplier, + uint256 maxLength, + uint256 baseLength, + uint256 precisionMultiplier, + uint256 feePercentage + ) = abi.decode( + priceConfig, + ( + uint256, + uint256, + uint256, + uint256, + uint256, + uint256 + ) + ); - setPriceConfig(0x0, zeroPriceConfig_); + return CurvePriceConfig({ + maxPrice: maxPrice, + curveMultiplier: curveMultiplier, + maxLength: maxLength, + baseLength: baseLength, + precisionMultiplier: precisionMultiplier, + feePercentage: feePercentage + }); + } + + /** + * @notice Validate the inputs for each variable in a price config + * @dev Will revert if incoming config is invalid + * + * @param priceConfig The price config to evaluate + */ + function validatePriceConfig( + bytes memory priceConfig + ) public override pure { + _checkLength(priceConfig); + _validatePriceConfig(decodePriceConfig(priceConfig)); } /** @@ -75,280 +111,98 @@ 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); + ) public pure override returns (uint256) { + CurvePriceConfig memory config = decodePriceConfig(parentPriceConfig); if (!skipValidityCheck) { // Confirms string values are only [a-z0-9-] label.validate(); } - uint256 length = label.strlen(); - // No pricing is set for 0 length domains - if (length == 0) return 0; - - return _getPrice(parentHash, length); + return _getPrice(config, label.strlen()); } /** * @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 parentPriceConfig The price config of the parent domain under which fee is determined * @param price The price to get the fee for */ function getFeeForPrice( - bytes32 parentHash, + bytes memory parentPriceConfig, uint256 price - ) public view override returns (uint256) { - return (price * priceConfigs[parentHash].feePercentage) / PERCENTAGE_BASIS; + ) public pure override returns (uint256) { + return _getFeeForPrice( + decodePriceConfig(parentPriceConfig).feePercentage, + price + ); } /** * @notice Part of the IZNSPricer interface - one of the functions required * for any pricing contracts used with ZNS. Returns both price and fee for a given label * under the given parent. - * @param parentHash The hash of the parent domain under which price and fee are determined + * @param parentPriceConfig The hash of the parent domain under which price and fee are determined * @param label The label of the subdomain candidate to get the price and fee for before/during registration */ function getPriceAndFee( - bytes32 parentHash, + bytes calldata parentPriceConfig, 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; + ) external pure override returns (uint256 price, uint256 stakeFee) { + if (!skipValidityCheck) { + label.validate(); + } - emit PriceConfigSet( - domainHash, - priceConfig.maxPrice, - priceConfig.curveMultiplier, - priceConfig.maxLength, - priceConfig.baseLength, - priceConfig.precisionMultiplier, - priceConfig.feePercentage + price = _getPrice( + decodePriceConfig(parentPriceConfig), + label.strlen() ); - } - - /** - * @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); - } - function _validateSetCurveMultiplier( - bytes32 domainHash, - uint256 curveMultiplier, - CurvePriceConfig memory config - ) internal onlyOwnerOrOperator(domainHash) { - if (curveMultiplier == 0 && config.baseLength == 0) - revert DivisionByZero(domainHash); + stakeFee = getFeeForPrice(parentPriceConfig, price); - priceConfigs[domainHash].curveMultiplier = curveMultiplier; - } - - /** - * @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, - uint256 baseLength, - CurvePriceConfig memory config - ) internal onlyOwnerOrOperator(domainHash) { - - if (config.maxLength < baseLength) - revert MaxLengthSmallerThanBaseLength(domainHash); - - if (baseLength == 0 && config.curveMultiplier == 0) - revert DivisionByZero(domainHash); - - priceConfigs[domainHash].baseLength = baseLength; - } - - /** - * @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); + return (price, stakeFee); } - function _validateSetMaxLength( - bytes32 domainHash, - uint256 maxLength, - CurvePriceConfig memory config - ) internal onlyOwnerOrOperator(domainHash) { - if ( - (maxLength < config.baseLength) || - maxLength == 0 - ) revert MaxLengthSmallerThanBaseLength(domainHash); + //////////////////////// + //// INTERNAL FUNCS //// + //////////////////////// - priceConfigs[domainHash].maxLength = maxLength; + function _checkLength(bytes memory priceConfig) internal pure { + // 6 props * 32 bytes each = 192 bytes + if (priceConfig.length != 192) { + revert IncorrectPriceConfigLength(); + } } - /** - * @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); + function _getFeeForPrice(uint256 feePercentage, uint256 price) internal pure returns (uint256) { + return (price * feePercentage) / PERCENTAGE_BASIS; } - function _validateSetPrecisionMultiplier( - bytes32 domainHash, - uint256 multiplier - ) internal { - if (multiplier == 0 || multiplier > 10**18) revert InvalidPrecisionMultiplierPassed(domainHash); + function _validatePriceConfig(CurvePriceConfig memory config) internal pure { + if (config.curveMultiplier == 0 && config.baseLength == 0) + revert DivisionByZero(); - priceConfigs[domainHash].precisionMultiplier = multiplier; - } + if (config.maxLength < config.baseLength || config.maxLength == 0) + revert MaxLengthSmallerThanBaseLength(); - /** - * @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); - } + if (config.precisionMultiplier == 0 || config.precisionMultiplier > 10**18) + revert InvalidPrecisionMultiplierPassed(); - function _validateSetFeePercentage( - bytes32 domainHash, - uint256 feePercentage - ) internal onlyOwnerOrOperator(domainHash) { - if (feePercentage > PERCENTAGE_BASIS) + if (config.feePercentage > PERCENTAGE_BASIS) revert FeePercentageValueTooLarge( - feePercentage, + config.feePercentage, PERCENTAGE_BASIS ); - - priceConfigs[domainHash].feePercentage = feePercentage; - } - - /** - * @notice Sets the registry address in state. - * @dev This function is required for all contracts inheriting `ARegistryWired`. - */ - function setRegistry(address registry_) external override(ARegistryWired, IZNSCurvePricer) onlyAdmin { - _setRegistry(registry_); } /** @@ -372,18 +226,18 @@ 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 config The parent price config * @param length The length of the domain name */ function _getPrice( - bytes32 parentHash, + CurvePriceConfig memory config, uint256 length - ) internal view returns (uint256) { - CurvePriceConfig memory config = priceConfigs[parentHash]; - + ) internal pure returns (uint256) { // We use `maxPrice` as 0 to indicate free domains if (config.maxPrice == 0) return 0; + if (length == 0) return 0; + // Setting baseLength to 0 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 @@ -395,13 +249,4 @@ contract ZNSCurvePricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, I (config.baseLength * FACTOR_SCALE + config.curveMultiplier * (length - config.baseLength))) / config.precisionMultiplier * config.precisionMultiplier; } - - /** - * @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. - */ - // solhint-disable-next-line - function _authorizeUpgrade(address newImplementation) internal view override { - accessController.checkGovernor(msg.sender); - } } diff --git a/contracts/price/ZNSFixedPricer.sol b/contracts/price/ZNSFixedPricer.sol index d074b3c77..9916ae172 100644 --- a/contracts/price/ZNSFixedPricer.sol +++ b/contracts/price/ZNSFixedPricer.sol @@ -1,44 +1,49 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import { AAccessControlled } from "../access/AAccessControlled.sol"; -import { ARegistryWired } from "../registry/ARegistryWired.sol"; import { IZNSFixedPricer } from "./IZNSFixedPricer.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { StringUtils } from "../utils/StringUtils.sol"; - /** * @notice Pricer contract that uses the most straightforward fixed pricing model * that doesn't depend on the length of the label. */ -contract ZNSFixedPricer is AAccessControlled, ARegistryWired, UUPSUpgradeable, IZNSFixedPricer { +contract ZNSFixedPricer is IZNSFixedPricer { using StringUtils for string; uint256 public constant PERCENTAGE_BASIS = 10000; /** - * @notice Mapping of domainHash to price config set by the domain owner/operator + * @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 */ - mapping(bytes32 domainHash => PriceConfig config) public priceConfigs; - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); + function encodeConfig( + FixedPriceConfig memory config + ) external pure override returns (bytes memory) { + return abi.encodePacked( + config.price, + config.feePercentage + ); } - function initialize(address _accessController, address _registry) external override initializer { - _setAccessController(_accessController); - setRegistry(_registry); - } + function decodePriceConfig( + bytes memory priceConfig + ) public pure override returns (FixedPriceConfig memory) { + _checkLength(priceConfig); - /** - * @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); + ( + uint256 price, + uint256 feePercentage + ) = abi.decode(priceConfig, (uint256, uint256)); + + FixedPriceConfig memory config = FixedPriceConfig( + price, + feePercentage + ); + + return config; } /** @@ -50,126 +55,93 @@ 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; + return decodePriceConfig(parentPriceConfig).price; } /** - * @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); - } - - /** - * @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 + * @notice Verify that the given price config is valid for this pricer + * @param priceConfig The price config to validate */ - 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 override pure { + _checkLength(priceConfig); + + FixedPriceConfig memory config = decodePriceConfig(priceConfig); + + if (config.feePercentage > PERCENTAGE_BASIS) + revert FeePercentageValueTooLarge( + config.feePercentage, + PERCENTAGE_BASIS + ); } /** * @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 + * @param price The price for a domain */ function getFeeForPrice( - bytes32 parentHash, + bytes memory parentPriceConfig, uint256 price - ) public view override returns (uint256) { - return (price * priceConfigs[parentHash].feePercentage) / PERCENTAGE_BASIS; + ) public pure override returns (uint256) { + return _getFeeForPrice( + decodePriceConfig(parentPriceConfig).feePercentage, + price + ); } /** * @notice Part of the IZNSPricer interface - one of the functions required * for any pricing contracts used with ZNS. Returns both price and fee for a given label * under the given parent. - * @param parentHash The hash of the parent domain under which price and fee are determined + * @param parentPriceConfig The price config of the parent domain under which price and fee are determined * @param label The label of the subdomain candidate to get the price and fee for before/during registration * @param skipValidityCheck If true, skips the validity check for the label */ function getPriceAndFee( - bytes32 parentHash, + bytes memory parentPriceConfig, 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); + ) external pure override returns (uint256 price, uint256 fee) { + // To match the IZNSPricer interface, we have unused params here + FixedPriceConfig memory config = decodePriceConfig(parentPriceConfig); + return ( + config.price, + _getFeeForPrice(config.feePercentage, config.price) + ); } - /** - * @notice Sets the registry address in state. - * @dev This function is required for all contracts inheriting `ARegistryWired`. - */ - function setRegistry(address registry_) public override(ARegistryWired, IZNSFixedPricer) onlyAdmin { - _setRegistry(registry_); - } + //////////////////////// + //// INTERNAL FUNCS //// + //////////////////////// - /** - * @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); + function _checkLength(bytes memory priceConfig) internal pure { + // 2 props * 32 bytes each = 64 bytes + if (priceConfig.length != 64) { + revert IncorrectPriceConfigLength(); + } } - /** - * @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. - */ - // solhint-disable-next-line - function _authorizeUpgrade(address newImplementation) internal view override { - accessController.checkGovernor(msg.sender); + function _getFeeForPrice( + uint256 feePercentage, + uint256 price + ) internal pure returns (uint256) { + return (price * feePercentage) / PERCENTAGE_BASIS; } } diff --git a/contracts/types/IDistributionConfig.sol b/contracts/registrar/IDistributionConfig.sol similarity index 61% rename from contracts/types/IDistributionConfig.sol rename to contracts/registrar/IDistributionConfig.sol index 084f07826..f5cb0fbd3 100644 --- a/contracts/types/IDistributionConfig.sol +++ b/contracts/registrar/IDistributionConfig.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import { IZNSPricer } from "../types/IZNSPricer.sol"; +import { IZNSPricer } from "../price/IZNSPricer.sol"; /** @@ -24,7 +24,6 @@ import { IZNSPricer } from "../types/IZNSPricer.sol"; * + `STAKE`: The subdomains are paid for by staking an amount of token chosen by the owner to ZNSTreasury */ interface IDistributionConfig { - enum AccessType { LOCKED, OPEN, @@ -36,9 +35,38 @@ interface IDistributionConfig { STAKE } + /** + * @notice Struct to define the entirety of the distribution of subdomains for a domain + * + * @param pricerContract The address of the contract used for pricing subdomains + * @param paymentType The type of payment system used for selling subdomains + * @param accessType The type of access that users have + */ struct DistributionConfig { IZNSPricer pricerContract; PaymentType paymentType; AccessType accessType; + bytes 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, + bytes pricerConfig, + PaymentType paymentType, + AccessType accessType + ); } diff --git a/contracts/registrar/IZNSRootRegistrar.sol b/contracts/registrar/IZNSRootRegistrar.sol index fd3fe9fef..9c5f05898 100644 --- a/contracts/registrar/IZNSRootRegistrar.sol +++ b/contracts/registrar/IZNSRootRegistrar.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import { IDistributionConfig } from "../types/IDistributionConfig.sol"; +import { IDistributionConfig } from "./IDistributionConfig.sol"; import { PaymentConfig } from "../treasury/IZNSTreasury.sol"; @@ -110,10 +110,23 @@ interface IZNSRootRegistrar is IDistributionConfig { ); /** - * @notice Emitted when the `rootPricer` address is set in state. + * @notice Emitted when the `rootPricer` address and the `rootPriceConfig` + * values are set in state. * @param rootPricer The new address of any IZNSPricer type contract + * @param priceConfig The encoded bytes for the price config */ - event RootPricerSet(address rootPricer); + event RootPricerSet( + address rootPricer, + bytes priceConfig + ); + + /** + * @notice Emitted when the `rootPriceConfig` value is set in state. + * @param priceConfig The encoded bytes for the price config + */ + event RootPriceConfigSet( + bytes indexed priceConfig + ); /** * @notice Emitted when the `treasury` address is set in state. @@ -137,6 +150,7 @@ interface IZNSRootRegistrar is IDistributionConfig { address accessController_, address registry_, address rootPricer_, + bytes memory priceConfig_, address treasury_, address domainToken_ ) external; @@ -159,7 +173,14 @@ interface IZNSRootRegistrar is IDistributionConfig { function setRegistry(address registry_) external; - function setRootPricer(address rootPricer_) external; + function setRootPricerAndConfig( + address rootPricer_, + bytes memory priceConfig_ + ) external; + + function setRootPriceConfig( + bytes memory priceConfig_ + ) external; function setTreasury(address treasury_) external; diff --git a/contracts/registrar/IZNSSubRegistrar.sol b/contracts/registrar/IZNSSubRegistrar.sol index d114dad8e..edf026ac6 100644 --- a/contracts/registrar/IZNSSubRegistrar.sol +++ b/contracts/registrar/IZNSSubRegistrar.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import { IDistributionConfig } from "../types/IDistributionConfig.sol"; +import { IDistributionConfig } from "./IDistributionConfig.sol"; import { PaymentConfig } from "../treasury/IZNSTreasury.sol"; -import { IZNSPricer } from "../types/IZNSPricer.sol"; +import { IZNSPricer } from "../price/IZNSPricer.sol"; /** @@ -40,31 +40,12 @@ interface IZNSSubRegistrar is IDistributionConfig { /** * @notice Emitted when a new `DistributionConfig.pricerContract` is set for a domain. */ - event PricerContractSet( + event PricerDataSet( bytes32 indexed domainHash, + bytes indexed priceConfig, 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. */ @@ -86,46 +67,37 @@ interface IZNSSubRegistrar is IDistributionConfig { */ event RootRegistrarSet(address registrar); + function initialize( + address _accessController, + address _registry, + address _rootRegistrar + ) external; + function distrConfigs( bytes32 domainHash ) external view returns ( IZNSPricer pricerContract, PaymentType paymentType, - AccessType accessType + AccessType accessType, + bytes memory priceConfig ); - function isMintlistedForDomain( - bytes32 domainHash, - address candidate - ) external view returns (bool); - - function initialize( - address _accessController, - address _registry, - address _rootRegistrar - ) external; - - function registerSubdomain(SubdomainRegisterArgs calldata regArgs) external returns (bytes32); + function registerSubdomain( + SubdomainRegisterArgs calldata registration + ) external returns (bytes32); function registerSubdomainBulk( SubdomainRegisterArgs[] calldata args ) external returns (bytes32[] memory); - /** - * @notice Helper function to hash a child label with a parent domain hash. - */ - function hashWithParent( - bytes32 parentHash, - string memory label - ) external pure returns (bytes32); - function setDistributionConfigForDomain( bytes32 parentHash, DistributionConfig calldata config ) external; - function setPricerContractForDomain( + function setPricerDataForDomain( bytes32 domainHash, + bytes memory priceConfig, IZNSPricer pricerContract ) external; @@ -152,4 +124,14 @@ interface IZNSSubRegistrar is IDistributionConfig { function setRegistry(address registry_) external; function setRootRegistrar(address registrar_) external; + + function isMintlistedForDomain( + bytes32 domainHash, + address candidate + ) external view returns (bool); + + function hashWithParent( + bytes32 parentHash, + string calldata label + ) external pure returns (bytes32); } diff --git a/contracts/registrar/ZNSRootRegistrar.sol b/contracts/registrar/ZNSRootRegistrar.sol index d60c1d32a..f0bfdbdc5 100644 --- a/contracts/registrar/ZNSRootRegistrar.sol +++ b/contracts/registrar/ZNSRootRegistrar.sol @@ -8,11 +8,13 @@ import { IZNSTreasury } from "../treasury/IZNSTreasury.sol"; import { IZNSDomainToken } from "../token/IZNSDomainToken.sol"; import { IZNSAddressResolver } from "../resolver/IZNSAddressResolver.sol"; import { IZNSSubRegistrar } from "../registrar/IZNSSubRegistrar.sol"; -import { IZNSPricer } from "../types/IZNSPricer.sol"; +import { IZNSPricer } from "../price/IZNSPricer.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import { StringUtils } from "../utils/StringUtils.sol"; import { ZeroAddressPassed, + ZeroValuePassed, + AddressIsNotAContract, DomainAlreadyExists, NotAuthorizedForDomain } from "../utils/CommonErrors.sol"; @@ -39,6 +41,7 @@ contract ZNSRootRegistrar is using StringUtils for string; IZNSPricer public rootPricer; + bytes public rootPriceConfig; IZNSTreasury public treasury; IZNSDomainToken public domainToken; IZNSSubRegistrar public subRegistrar; @@ -56,6 +59,7 @@ contract ZNSRootRegistrar is * @param accessController_ Address of the ZNSAccessController contract * @param registry_ Address of the ZNSRegistry contract * @param rootPricer_ Address of the IZNSPricer type contract that Zero chose to use for the root domains + * @param priceConfig_ IZNSPricer pricer config data encoded as bytes for the root domain * @param treasury_ Address of the ZNSTreasury contract * @param domainToken_ Address of the ZNSDomainToken contract */ @@ -63,12 +67,14 @@ contract ZNSRootRegistrar is address accessController_, address registry_, address rootPricer_, + bytes calldata priceConfig_, address treasury_, address domainToken_ ) external override initializer { _setAccessController(accessController_); setRegistry(registry_); - setRootPricer(rootPricer_); + + setRootPricerAndConfig(rootPricer_, priceConfig_); setTreasury(treasury_); setDomainToken(domainToken_); } @@ -101,8 +107,7 @@ contract ZNSRootRegistrar is bytes32 domainHash = keccak256(bytes(args.name)); // Get price for the domain - uint256 domainPrice = rootPricer.getPrice(0x0, args.name, true); - + uint256 domainPrice = rootPricer.getPrice(rootPriceConfig, args.name, true); _coreRegister( CoreRegisterArgs({ parentHash: bytes32(0), @@ -218,7 +223,9 @@ contract ZNSRootRegistrar is 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.domainOwner, ""); } @@ -247,7 +254,7 @@ contract ZNSRootRegistrar is */ function _processPayment(CoreRegisterArgs memory args) internal { // args.stakeFee can be 0 - uint256 protocolFee = rootPricer.getFeeForPrice(0x0, args.price + args.stakeFee); + uint256 protocolFee = rootPricer.getFeeForPrice(rootPriceConfig, args.price + args.stakeFee); if (args.isStakePayment) { // for all root domains or subdomains with stake payment treasury.stakeForDomain( @@ -315,7 +322,7 @@ contract ZNSRootRegistrar is bool stakeRefunded = false; // send the stake back if it exists if (stakedAmount > 0) { - uint256 protocolFee = rootPricer.getFeeForPrice(0x0, stakedAmount); + uint256 protocolFee = rootPricer.getFeeForPrice(rootPriceConfig, stakedAmount); treasury.unstakeForDomain(domainHash, owner, protocolFee); stakeRefunded = true; @@ -358,6 +365,7 @@ contract ZNSRootRegistrar is /** * @notice Setter function for the `ZNSRegistry` address in state. * Only ADMIN in `ZNSAccessController` can call this function. + * * @param registry_ Address of the `ZNSRegistry` contract */ function setRegistry(address registry_) public override(ARegistryWired, IZNSRootRegistrar) onlyAdmin { @@ -367,15 +375,34 @@ contract ZNSRootRegistrar is /** * @notice Setter for the IZNSPricer type contract that Zero chooses to handle Root Domains. * Only ADMIN in `ZNSAccessController` can call this function. - * @param rootPricer_ Address of the IZNSPricer type contract to set as pricer of Root Domains + * + * @param pricer_ Address of the IZNSPricer type contract to set as pricer of Root Domains + * @param priceConfig_ The price config, encoded as bytes, for the given IZNSPricer contract */ - function setRootPricer(address rootPricer_) public override onlyAdmin { - if (rootPricer_ == address(0)) + function setRootPricerAndConfig( + address pricer_, + bytes memory priceConfig_ + ) public override onlyAdmin { + if (pricer_ == address(0)) revert ZeroAddressPassed(); - rootPricer = IZNSPricer(rootPricer_); + if (pricer_.code.length == 0) revert AddressIsNotAContract(); + + _setRootPriceConfig(IZNSPricer(pricer_), priceConfig_); + rootPricer = IZNSPricer(pricer_); + + emit RootPricerSet(pricer_, priceConfig_); + } - emit RootPricerSet(rootPricer_); + /** + * @notice Set the price configuration for root domains + * @dev Note this function takes in a pricer contract address as a param + * but does not modify this address in state. + * + * @param priceConfig_ The price configuration for root domains, encoded as bytes + */ + function setRootPriceConfig(bytes memory priceConfig_) public override onlyAdmin { + _setRootPriceConfig(rootPricer, priceConfig_); } /** @@ -426,4 +453,14 @@ contract ZNSRootRegistrar is function _authorizeUpgrade(address newImplementation) internal view override { accessController.checkGovernor(msg.sender); } + + function _setRootPriceConfig(IZNSPricer pricer_, bytes memory priceConfig_) internal { + if (priceConfig_.length == 0) revert ZeroValuePassed(); + + pricer_.validatePriceConfig(priceConfig_); + + rootPriceConfig = priceConfig_; + + emit RootPriceConfigSet(priceConfig_); + } } diff --git a/contracts/registrar/ZNSSubRegistrar.sol b/contracts/registrar/ZNSSubRegistrar.sol index 71d6f2a11..8f852bd92 100644 --- a/contracts/registrar/ZNSSubRegistrar.sol +++ b/contracts/registrar/ZNSSubRegistrar.sol @@ -1,14 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.26; -import { IZNSPricer } from "../types/IZNSPricer.sol"; +import { IZNSPricer } from "../price/IZNSPricer.sol"; import { IZNSRootRegistrar, CoreRegisterArgs } from "./IZNSRootRegistrar.sol"; import { IZNSSubRegistrar } from "./IZNSSubRegistrar.sol"; import { AAccessControlled } from "../access/AAccessControlled.sol"; import { ARegistryWired } from "../registry/ARegistryWired.sol"; import { StringUtils } from "../utils/StringUtils.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { ZeroAddressPassed, NotAuthorizedForDomain } from "../utils/CommonErrors.sol"; +import { + ZeroAddressPassed, + NotAuthorizedForDomain +} from "../utils/CommonErrors.sol"; /** @@ -135,14 +138,14 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, if (coreRegisterArgs.isStakePayment) { (coreRegisterArgs.price, coreRegisterArgs.stakeFee) = IZNSPricer(address(parentConfig.pricerContract)) .getPriceAndFee( - args.parentHash, + parentConfig.priceConfig, args.label, true ); } else { coreRegisterArgs.price = IZNSPricer(address(parentConfig.pricerContract)) .getPrice( - args.parentHash, + parentConfig.priceConfig, args.label, true ); @@ -240,11 +243,15 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, if (address(config.pricerContract) == address(0)) revert ZeroAddressPassed(); + // Will revert if invalid + IZNSPricer(config.pricerContract).validatePriceConfig(config.priceConfig); + distrConfigs[domainHash] = config; emit DistributionConfigSet( domainHash, config.pricerContract, + config.priceConfig, config.paymentType, config.accessType ); @@ -256,10 +263,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 config The price config data for the given pricer * @param pricerContract The new pricer contract to set */ - function setPricerContractForDomain( + function setPricerDataForDomain( bytes32 domainHash, + bytes memory config, IZNSPricer pricerContract ) public override { if (!registry.isOwnerOrOperator(domainHash, msg.sender)) @@ -268,9 +277,12 @@ contract ZNSSubRegistrar is AAccessControlled, ARegistryWired, UUPSUpgradeable, if (address(pricerContract) == address(0)) revert ZeroAddressPassed(); + IZNSPricer(pricerContract).validatePriceConfig(config); + distrConfigs[domainHash].pricerContract = pricerContract; + distrConfigs[domainHash].priceConfig = config; - emit PricerContractSet(domainHash, address(pricerContract)); + emit PricerDataSet(domainHash, config, address(pricerContract)); } /** diff --git a/contracts/registry/ZNSRegistry.sol b/contracts/registry/ZNSRegistry.sol index 555770557..6a9947979 100644 --- a/contracts/registry/ZNSRegistry.sol +++ b/contracts/registry/ZNSRegistry.sol @@ -4,7 +4,10 @@ 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, + NotAuthorizedForDomain +} from "../utils/CommonErrors.sol"; /** @@ -174,7 +177,7 @@ contract ZNSRegistry is AAccessControlled, UUPSUpgradeable, IZNSRegistry { * @notice Given a resolver type, returns the address of the resolver contract for that type or 0x0 if not found * @param resolverType The resolver type as a string, e.g. "address" */ - function getResolverType(string calldata resolverType) public view override returns(address) { + function getResolverType(string calldata resolverType) public view override returns (address) { return resolvers[resolverType]; } diff --git a/contracts/types/ICurvePriceConfig.sol b/contracts/types/ICurvePriceConfig.sol deleted file mode 100644 index 4a3fd5af7..000000000 --- a/contracts/types/ICurvePriceConfig.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - - -/** - * @dev **`CurvePriceConfig` struct properties:** - * - * - `maxPrice` (uint256): Maximum price for a domain returned at <= `baseLength` - * - `maxLength` (uint256): Maximum length of a domain name. If the name is longer - - * we return the price that was at the `maxLength`. - * - `baseLength` (uint256): Base length of a domain name. If the name is shorter or equal - we return the `maxPrice` - * - `precisionMultiplier` (uint256): The precision multiplier of the price. This multiplier - * 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 10^18 - 10^2 = 10^16 - */ -interface ICurvePriceConfig { -/** - * @notice Struct for each configurable variable for price calculations. - * Does NOT include variables for calcs of registration fees. - */ - struct CurvePriceConfig { - /** - * @notice Maximum price for a domain returned at <= `baseLength` - */ - uint256 maxPrice; - /** - * @notice Multiplier which we use to bend a curve of price on interval from `baseLength` to `maxLength`. - */ - uint256 curveMultiplier; - /** - * @notice Maximum length of a domain name. If the name is longer than this - * value we return the price that was at the `maxLength` - */ - uint256 maxLength; - /** - * @notice Base length of a domain name. If the name is less than or equal to - * this value we return the `maxPrice` - */ - uint256 baseLength; - /** - * @notice The precision multiplier of the price. This multiplier - * 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 10^18 - 10^2 = 10^16 - */ - uint256 precisionMultiplier; - /** - * @notice The registration fee value in percentage as basis points (parts per 10,000) - * so the 2% value would be represented as 200. - * See [getRegistrationFee](#getregistrationfee) for the actual fee calc process. - */ - uint256 feePercentage; - - /** - * @notice A boolean value signifying the fact that this whole config has been set. - * This value is checked by other contracts to avoid giving out free domains for users who - * haven't yet managed to set their config. - */ - bool isSet; - } -} diff --git a/contracts/upgrade-test-mocks/distribution/ZNSSubRegistrarMock.sol b/contracts/upgrade-test-mocks/distribution/ZNSSubRegistrarMock.sol index 98010586d..8d1576a04 100644 --- a/contracts/upgrade-test-mocks/distribution/ZNSSubRegistrarMock.sol +++ b/contracts/upgrade-test-mocks/distribution/ZNSSubRegistrarMock.sol @@ -6,7 +6,7 @@ pragma solidity 0.8.26; import { ZNSSubRegistrar } from "../../registrar/ZNSSubRegistrar.sol"; import { IZNSSubRegistrar } from "../../registrar/IZNSSubRegistrar.sol"; import { UpgradeMock } from "../UpgradeMock.sol"; -import { IZNSPricer } from "../../types/IZNSPricer.sol"; +import { IZNSPricer } from "../../price/IZNSPricer.sol"; import { IZNSRootRegistrar, CoreRegisterArgs } from "../../registrar/IZNSRootRegistrar.sol"; import { AAccessControlled } from "../../access/AAccessControlled.sol"; import { ARegistryWired } from "../../registry/ARegistryWired.sol"; @@ -31,6 +31,7 @@ struct DistributionConfig { IZNSPricer pricerContract; PaymentType paymentType; AccessType accessType; + bytes priceConfig; address newAddress; uint256 newUint; } @@ -137,14 +138,14 @@ contract ZNSSubRegistrarUpgradeMock is if (coreRegisterArgs.isStakePayment) { (coreRegisterArgs.price, coreRegisterArgs.stakeFee) = IZNSPricer(address(parentConfig.pricerContract)) .getPriceAndFee( - regArgs.parentHash, + parentConfig.priceConfig, regArgs.label, true ); } else { coreRegisterArgs.price = IZNSPricer(address(parentConfig.pricerContract)) .getPrice( - regArgs.parentHash, + parentConfig.priceConfig, regArgs.label, true ); @@ -184,8 +185,9 @@ contract ZNSSubRegistrarUpgradeMock is distrConfigs[domainHash] = config; } - function setPricerContractForDomain( + function setPricerDataForDomain( bytes32 domainHash, + bytes memory config, IZNSPricer pricerContract ) public { if (!registry.isOwnerOrOperator(domainHash, msg.sender)) @@ -194,7 +196,10 @@ contract ZNSSubRegistrarUpgradeMock is if (address(pricerContract) == address(0)) revert ZeroAddressPassed(); + IZNSPricer(pricerContract).validatePriceConfig(config); + distrConfigs[domainHash].pricerContract = pricerContract; + distrConfigs[domainHash].priceConfig = config; } function setPaymentTypeForDomain( diff --git a/contracts/utils/CommonErrors.sol b/contracts/utils/CommonErrors.sol index 3d17507ea..f629ccd9f 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); diff --git a/docs/contracts/price/IZNSFixedPricer.md b/docs/contracts/price/IZNSFixedPricer.md index 94e90280c..7d527fd89 100644 --- a/docs/contracts/price/IZNSFixedPricer.md +++ b/docs/contracts/price/IZNSFixedPricer.md @@ -46,7 +46,6 @@ Emitted when the `PriceConfig.feePercentage` is set in state for a specific `dom struct PriceConfig { uint256 price; uint256 feePercentage; - bool isSet; } ``` diff --git a/src/deploy/campaign/get-config.ts b/src/deploy/campaign/get-config.ts index d112c1498..072951b2f 100644 --- a/src/deploy/campaign/get-config.ts +++ b/src/deploy/campaign/get-config.ts @@ -6,7 +6,7 @@ import { ZNS_DOMAIN_TOKEN_SYMBOL, DEFAULT_DECIMALS, DEFAULT_PRECISION, - DEFAULT_PRICE_CONFIG, + DEFAULT_CURVE_PRICE_CONFIG, NO_MOCK_PROD_ERR, STAKING_TOKEN_ERR, INVALID_CURVE_ERR, @@ -213,22 +213,22 @@ const getValidateRootPriceConfig = () => { const maxPrice = process.env.MAX_PRICE ? ethers.parseEther(process.env.MAX_PRICE) - : DEFAULT_PRICE_CONFIG.maxPrice; + : DEFAULT_CURVE_PRICE_CONFIG.maxPrice; const curveMultiplier = process.env.CURVE_MULTIPLIER ? BigInt(process.env.CURVE_MULTIPLIER) - : DEFAULT_PRICE_CONFIG.curveMultiplier; + : DEFAULT_CURVE_PRICE_CONFIG.curveMultiplier; const maxLength = process.env.MAX_LENGTH ? BigInt(process.env.MAX_LENGTH) - : DEFAULT_PRICE_CONFIG.maxLength; + : DEFAULT_CURVE_PRICE_CONFIG.maxLength; const baseLength = process.env.BASE_LENGTH ? BigInt(process.env.BASE_LENGTH) - : DEFAULT_PRICE_CONFIG.baseLength; + : DEFAULT_CURVE_PRICE_CONFIG.baseLength; const decimals = process.env.DECIMALS ? BigInt(process.env.DECIMALS) : DEFAULT_DECIMALS; const precision = process.env.PRECISION ? BigInt(process.env.PRECISION) : DEFAULT_PRECISION; @@ -246,7 +246,6 @@ const getValidateRootPriceConfig = () => { baseLength, precisionMultiplier, feePercentage, - isSet: true, }; validateConfig(priceConfig); diff --git a/src/deploy/missions/contracts/curve-pricer.ts b/src/deploy/missions/contracts/curve-pricer.ts index 562d8bcd1..fb2a412f9 100644 --- a/src/deploy/missions/contracts/curve-pricer.ts +++ b/src/deploy/missions/contracts/curve-pricer.ts @@ -1,8 +1,6 @@ import { BaseDeployMission, - TDeployArgs, } from "@zero-tech/zdc"; -import { ProxyKinds } from "../../constants"; import { znsNames } from "./names"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; @@ -16,22 +14,9 @@ IZNSCampaignConfig, IZNSContracts > { proxyData = { - isProxy: true, - kind: ProxyKinds.uups, + isProxy: false, }; contractName = znsNames.curvePricer.contract; instanceName = znsNames.curvePricer.instance; - - async deployArgs () : Promise { - const { - accessController, - registry, - config: { - rootPriceConfig, - }, - } = this.campaign; - - return [await accessController.getAddress(), await registry.getAddress(), rootPriceConfig]; - } } diff --git a/src/deploy/missions/contracts/fixed-pricer.ts b/src/deploy/missions/contracts/fixed-pricer.ts index ee93f4bcd..2f445b72b 100644 --- a/src/deploy/missions/contracts/fixed-pricer.ts +++ b/src/deploy/missions/contracts/fixed-pricer.ts @@ -1,7 +1,5 @@ -import { ProxyKinds } from "../../constants"; import { BaseDeployMission, - TDeployArgs, } from "@zero-tech/zdc"; import { znsNames } from "./names"; import { HardhatRuntimeEnvironment } from "hardhat/types"; @@ -15,19 +13,9 @@ IZNSCampaignConfig, IZNSContracts > { proxyData = { - isProxy: true, - kind: ProxyKinds.uups, + isProxy: false, }; contractName = znsNames.fixedPricer.contract; instanceName = znsNames.fixedPricer.instance; - - async deployArgs () : Promise { - const { - accessController, - registry, - } = this.campaign; - - return [await accessController.getAddress(), await registry.getAddress()]; - } } diff --git a/src/deploy/missions/contracts/root-registrar.ts b/src/deploy/missions/contracts/root-registrar.ts index 5182d045c..340e95054 100644 --- a/src/deploy/missions/contracts/root-registrar.ts +++ b/src/deploy/missions/contracts/root-registrar.ts @@ -7,6 +7,7 @@ import { znsNames } from "./names"; import { HardhatRuntimeEnvironment } from "hardhat/types"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { IZNSCampaignConfig, IZNSContracts } from "../../campaign/types"; +import { encodePriceConfig } from "../../../../test/helpers"; export class ZNSRootRegistrarDM extends BaseDeployMission< @@ -30,6 +31,7 @@ IZNSContracts curvePricer, treasury, domainToken, + config, } = this.campaign; return [ @@ -37,6 +39,7 @@ IZNSContracts await registry.getAddress(), // we use CurvePricer as the IZNSPricer for root domains await curvePricer.getAddress(), + encodePriceConfig(config.rootPriceConfig), await treasury.getAddress(), await domainToken.getAddress(), ]; diff --git a/src/deploy/missions/types.ts b/src/deploy/missions/types.ts index 7a5de5e27..cc4f0903c 100644 --- a/src/deploy/missions/types.ts +++ b/src/deploy/missions/types.ts @@ -6,5 +6,4 @@ export interface ICurvePriceConfig { baseLength : bigint; precisionMultiplier : bigint; feePercentage : bigint; - isSet : boolean; } diff --git a/src/tenderly/devnet/run-all-flows.ts b/src/tenderly/devnet/run-all-flows.ts index e9c3337eb..431a2492b 100644 --- a/src/tenderly/devnet/run-all-flows.ts +++ b/src/tenderly/devnet/run-all-flows.ts @@ -3,8 +3,11 @@ import * as ethers from "ethers"; import { deployZNS, hashDomainLabel, PaymentType, - DEFAULT_PRICE_CONFIG, AccessType, + IFullDistributionConfig, + encodePriceConfig, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + IRegisterWithSetupArgs, } from "../../../test/helpers"; import { registrationWithSetup } from "../../../test/helpers/register-setup"; @@ -30,9 +33,10 @@ export const runAllFlows = async () => { const rootPrice = BigInt(ethers.parseEther("200")); const rootFeePercentage = BigInt(250); - const fullRootConfig = { + const fullRootConfig : IFullDistributionConfig = { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ price: rootPrice, feePercentage: rootFeePercentage }), paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, @@ -40,10 +44,6 @@ export const runAllFlows = async () => { token: await zns.meowToken.getAddress(), beneficiary: governor.address, }, - priceConfig: { - price: rootPrice, - feePercentage: rootFeePercentage, - }, }; // get some funds and approve funds for treasury @@ -57,9 +57,10 @@ export const runAllFlows = async () => { }); const subdomainLabel = "subdomain"; - const fullSubConfig = { + const fullSubConfig : IFullDistributionConfig = { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -67,7 +68,6 @@ export const runAllFlows = async () => { token: await zns.meowToken.getAddress(), beneficiary: user.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }; await zns.meowToken.transfer(user.address, ethers.parseEther("10000")); diff --git a/test/ControlledDomains.test.ts b/test/ControlledDomains.test.ts index 4451b5d47..5eadd7567 100644 --- a/test/ControlledDomains.test.ts +++ b/test/ControlledDomains.test.ts @@ -7,7 +7,7 @@ import { IZNSContracts } from "../src/deploy/campaign/types"; import { defaultRootRegistration, registrationWithSetup } from "./helpers/register-setup"; import { expect } from "chai"; import { - AccessType, distrConfigEmpty, DISTRIBUTION_LOCKED_NOT_EXIST_ERR, + AccessType, DEFAULT_CURVE_PRICE_CONFIG_BYTES, distrConfigEmpty, DISTRIBUTION_LOCKED_NOT_EXIST_ERR, NONEXISTENT_TOKEN_ERC_ERR, NOT_AUTHORIZED_ERR, NOT_FULL_OWNER_ERR, paymentConfigEmpty, } from "./helpers"; @@ -29,7 +29,7 @@ const makeSetupFixture = async () => { config, }); - const zns = campaign.state.contracts; + const zns = campaign.state.contracts ; const mongoAdapter = campaign.dbAdapter; // Give funds and approve @@ -48,6 +48,7 @@ const makeSetupFixture = async () => { // Register the root domain for the Registrar const baseRootDomainHash = await registrationWithSetup({ zns, + tokenOwner: parentOwner.address, user: parentOwner, domainLabel: "controlling-root", }); @@ -266,9 +267,10 @@ describe("Controlled Domains Test", () => { it(`should NOT let ${name} token owner access domain management functions`, async () => { await expect( - zns.subRegistrar.connect(tokenOwner).setPricerContractForDomain( + zns.subRegistrar.connect(tokenOwner).setPricerDataForDomain( controlledSubHash, - zns.fixedPricer.target, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + await zns.fixedPricer.getAddress(), ) ).to.be.revertedWithCustomError( zns.subRegistrar, diff --git a/test/DeployCampaign.integration.test.ts b/test/DeployCampaign.integration.test.ts index 01c8cdde3..35ba32aff 100644 --- a/test/DeployCampaign.integration.test.ts +++ b/test/DeployCampaign.integration.test.ts @@ -5,7 +5,7 @@ import { runZnsCampaign } from "../src/deploy/zns-campaign"; import { ethers } from "ethers"; import { IDistributionConfig } from "./helpers/types"; import { expect } from "chai"; -import { hashDomainLabel, PaymentType, AccessType } from "./helpers"; +import { hashDomainLabel, PaymentType, AccessType, DEFAULT_CURVE_PRICE_CONFIG_BYTES } from "./helpers"; import { approveBulk, getPriceBulk, @@ -94,6 +94,7 @@ describe("zNS + zDC Single Integration Test", () => { // CurvePricer, stake, open distConfig = { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }; diff --git a/test/DeployCampaignInt.test.ts b/test/DeployCampaignInt.test.ts index e41a59975..6e70d6cbd 100644 --- a/test/DeployCampaignInt.test.ts +++ b/test/DeployCampaignInt.test.ts @@ -15,7 +15,7 @@ import { } from "@zero-tech/zdc"; import { DEFAULT_ROYALTY_FRACTION, - DEFAULT_PRICE_CONFIG, + DEFAULT_CURVE_PRICE_CONFIG, ZNS_DOMAIN_TOKEN_NAME, ZNS_DOMAIN_TOKEN_SYMBOL, INVALID_ENV_ERR, @@ -84,7 +84,7 @@ describe("Deploy Campaign Test", () => { defaultRoyaltyReceiver: deployAdmin.address, defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, }, - rootPriceConfig: DEFAULT_PRICE_CONFIG, + rootPriceConfig: DEFAULT_CURVE_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, stakingTokenAddress: MEOWzChainData.address, mockMeowToken: true, @@ -368,7 +368,7 @@ describe("Deploy Campaign Test", () => { defaultRoyaltyReceiver: deployAdmin.address, defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, }, - rootPriceConfig: DEFAULT_PRICE_CONFIG, + rootPriceConfig: DEFAULT_CURVE_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, // TODO dep: what do we pass here for test flow? we don't have a deployed MeowToken contract stakingTokenAddress: "", @@ -670,7 +670,7 @@ describe("Deploy Campaign Test", () => { expect(localConfig.domainToken.symbol).to.eq(ZNS_DOMAIN_TOKEN_SYMBOL); expect(localConfig.domainToken.defaultRoyaltyReceiver).to.eq(zeroVault.address); expect(localConfig.domainToken.defaultRoyaltyFraction).to.eq(DEFAULT_ROYALTY_FRACTION); - expect(localConfig.rootPriceConfig).to.deep.eq(DEFAULT_PRICE_CONFIG); + expect(localConfig.rootPriceConfig).to.deep.eq(DEFAULT_CURVE_PRICE_CONFIG); }); it("Confirms encoding functionality works for env variables", async () => { @@ -860,7 +860,7 @@ describe("Deploy Campaign Test", () => { defaultRoyaltyReceiver: deployAdmin.address, defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, }, - rootPriceConfig: DEFAULT_PRICE_CONFIG, + rootPriceConfig: DEFAULT_CURVE_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, stakingTokenAddress: MEOWzChainData.address, mockMeowToken: true, @@ -1051,7 +1051,7 @@ describe("Deploy Campaign Test", () => { defaultRoyaltyReceiver: deployAdmin.address, defaultRoyaltyFraction: DEFAULT_ROYALTY_FRACTION, }, - rootPriceConfig: DEFAULT_PRICE_CONFIG, + rootPriceConfig: DEFAULT_CURVE_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, stakingTokenAddress: MEOWzChainData.address, mockMeowToken: true, diff --git a/test/ZNSAddressResolver.test.ts b/test/ZNSAddressResolver.test.ts index 22893cb7c..5920136e6 100644 --- a/test/ZNSAddressResolver.test.ts +++ b/test/ZNSAddressResolver.test.ts @@ -18,6 +18,7 @@ import { } from "./helpers"; import { getProxyImplAddress } from "./helpers/utils"; + // eslint-disable-next-line @typescript-eslint/no-var-requires const { expect } = require("chai"); @@ -44,6 +45,7 @@ describe("ZNSAddressResolver", () => { governorAddresses: [deployer.address], adminAddresses: [deployer.address], }; + zns = await deployZNS(params); // Have to get this value for every test, but can be fixed diff --git a/test/ZNSCurvePricer.test.ts b/test/ZNSCurvePricer.test.ts index 816ca0367..efc06c9e6 100644 --- a/test/ZNSCurvePricer.test.ts +++ b/test/ZNSCurvePricer.test.ts @@ -5,51 +5,40 @@ import { ethers } from "ethers"; import { deployZNS, getCurvePrice, - DEFAULT_PRECISION_MULTIPLIER, - validateUpgrade, - PaymentType, - NOT_AUTHORIZED_ERR, INVALID_PRECISION_MULTIPLIER_ERR, INVALID_LENGTH_ERR, - INVALID_LABEL_ERR, INITIALIZED_ERR, AC_UNAUTHORIZED_ERR, ZERO_ADDRESS_ERR, FEE_TOO_LARGE_ERR, + INVALID_LABEL_ERR, + FEE_TOO_LARGE_ERR, INVALID_BASE_OR_MAX_LENGTH_ERR, DIVISION_BY_ZERO_ERR, + encodePriceConfig, + decodePriceConfig, + INVALID_CONFIG_LENGTH_ERR, } from "./helpers"; import { - AccessType, - DEFAULT_DECIMALS, - DEFAULT_PRICE_CONFIG, - DEFAULT_PROTOCOL_FEE_PERCENT, + DEFAULT_CURVE_PRICE_CONFIG, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, } from "./helpers/constants"; -import { ADMIN_ROLE, GOVERNOR_ROLE } from "../src/deploy/constants"; -import { ZNSCurvePricer, ZNSCurvePricerUpgradeMock__factory, ZNSCurvePricer__factory } from "../typechain"; -import { registrationWithSetup } from "./helpers/register-setup"; -import { getProxyImplAddress, getRandomString } from "./helpers/utils"; import { IZNSContractsLocal } from "./helpers/types"; import { getMongoAdapter } from "@zero-tech/zdc"; +import { ICurvePriceConfig } from "../src/deploy/missions/types"; require("@nomicfoundation/hardhat-chai-matchers"); -const { ZeroHash } = ethers; - describe("ZNSCurvePricer", () => { let deployer : SignerWithAddress; let user : SignerWithAddress; let admin : SignerWithAddress; - let randomAcc : SignerWithAddress; let zns : IZNSContractsLocal; - let domainHash : string; - - const defaultDomain = "wilder"; beforeEach(async () => { [ deployer, user, admin, - randomAcc, ] = await hre.ethers.getSigners(); zns = await deployZNS({ @@ -60,26 +49,6 @@ describe("ZNSCurvePricer", () => { await zns.meowToken.connect(user).approve(await zns.treasury.getAddress(), ethers.MaxUint256); await zns.meowToken.mint(user.address, 26000000000000000000000n); - - const fullConfig = { - distrConfig: { - paymentType: PaymentType.DIRECT, - pricerContract: await zns.curvePricer.getAddress(), - accessType: AccessType.OPEN, - }, - paymentConfig: { - token: await zns.meowToken.getAddress(), - beneficiary: user.address, - }, - priceConfig: DEFAULT_PRICE_CONFIG, - }; - - domainHash = await registrationWithSetup({ - zns, - user, - domainLabel: "testdomain", - fullConfig, - }); }); after(async () => { @@ -87,42 +56,22 @@ describe("ZNSCurvePricer", () => { await dbAdapter.dropDB(); }); - it("Should NOT let initialize the implementation contract", async () => { - const factory = new ZNSCurvePricer__factory(deployer); - const impl = await getProxyImplAddress(await zns.curvePricer.getAddress()); - const implContract = factory.attach(impl) as ZNSCurvePricer; - - await expect( - implContract.initialize( - await zns.accessController.getAddress(), - await zns.registry.getAddress(), - DEFAULT_PRICE_CONFIG - ) - ).to.be.revertedWithCustomError(implContract, INITIALIZED_ERR); - }); - - it("Confirms values were initially set correctly", async () => { - const valueCalls = [ - zns.curvePricer.priceConfigs(domainHash), - ]; - - const [ - priceConfigFromSC, - ] = await Promise.all(valueCalls); + describe("#encodeConfig and #decodeConfig", () => { + it("Confirms encoding is the same offchain and onchain", async () => { + const onchain = await zns.curvePricer.encodeConfig(DEFAULT_CURVE_PRICE_CONFIG); + const offchain = encodePriceConfig(DEFAULT_CURVE_PRICE_CONFIG); - const priceConfigArr = Object.values(DEFAULT_PRICE_CONFIG); - - priceConfigArr.forEach( - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - (val, idx) => expect(val).to.eq(priceConfigFromSC[idx]) - ); + expect(onchain).to.eq(offchain); + }); - const regFromSC = await zns.curvePricer.registry(); - const acFromSC = await zns.curvePricer.getAccessController(); + it("Confirms decoding is the same offchain and onchain", async () => { + const onchain = await zns.curvePricer.decodePriceConfig(DEFAULT_CURVE_PRICE_CONFIG_BYTES); + const offchain = decodePriceConfig(DEFAULT_CURVE_PRICE_CONFIG_BYTES); - expect(regFromSC).to.eq(await zns.registry.getAddress()); - expect(acFromSC).to.eq(await zns.accessController.getAddress()); + Object.values(offchain).forEach((value, index) => { + expect(onchain[index]).to.eq(value); + }); + }); }); describe("#getPrice", async () => { @@ -130,44 +79,51 @@ describe("ZNSCurvePricer", () => { const { price, stakeFee, - } = await zns.curvePricer.getPriceAndFee(domainHash, "", true); + } = await zns.curvePricer.getPriceAndFee( + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + "", + true + ); expect(price).to.eq(0); expect(stakeFee).to.eq(0); }); it("Reverts for a label with no length if label validation is not skipped", async () => { - await expect(zns.curvePricer.getPrice(domainHash, "", false)).to.be.revertedWithCustomError( + await expect(zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, "", false)).to.be.revertedWithCustomError( zns.curvePricer, INVALID_LENGTH_ERR ); }); it("Reverts for invalid label if label validation is not skipped", async () => { - await expect(zns.curvePricer.getPrice(domainHash, "wilder!", false)).to.be.revertedWithCustomError( + await expect( + zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, "wilder!", false) + ).to.be.revertedWithCustomError( zns.curvePricer, INVALID_LABEL_ERR ); }); - it("Returns the base price for domains that are equal to the base length", async () => { + it("Returns the max price for domains that are equal to the base length", async () => { // Using the default length of 3 const domain = "eth"; - const params = await zns.curvePricer.priceConfigs(domainHash); + const decodedPriceConfig = decodePriceConfig(DEFAULT_CURVE_PRICE_CONFIG_BYTES); - const domainPrice = await zns.curvePricer.getPrice(domainHash, domain, true); - expect(domainPrice).to.eq(params.maxPrice); + const domainPrice = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, domain, true); + expect(domainPrice).to.eq((decodedPriceConfig as ICurvePriceConfig).maxPrice); }); - it("Returns the base price for domains that are less than the base length", async () => { + it("Returns the max price for domains that are less than the base length", async () => { const domainA = "et"; const domainB = "e"; - const params = await zns.curvePricer.priceConfigs(domainHash); - let domainPrice = await zns.curvePricer.getPrice(domainHash, domainA, true); - expect(domainPrice).to.eq(params.maxPrice); + const decodedPriceConfig = decodePriceConfig(DEFAULT_CURVE_PRICE_CONFIG_BYTES); - (domainPrice = await zns.curvePricer.getPrice(domainHash, domainB, true)); - expect(domainPrice).to.eq(params.maxPrice); + let domainPrice = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, domainA, true); + expect(domainPrice).to.eq((decodedPriceConfig as ICurvePriceConfig).maxPrice); + + domainPrice = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, domainB, true); + expect(domainPrice).to.eq((decodedPriceConfig as ICurvePriceConfig).maxPrice); }); it("Returns expected prices for a domain greater than the base length", async () => { @@ -179,14 +135,14 @@ describe("ZNSCurvePricer", () => { // that both forumlas: SC + helper are correct // this value has been calces with the default priceConfig - const domainOneExpPrice = await getCurvePrice(domainOne, DEFAULT_PRICE_CONFIG); - const domainTwoExpPrice = await getCurvePrice(domainTwo, DEFAULT_PRICE_CONFIG); + const domainOneExpPrice = await getCurvePrice(domainOne, DEFAULT_CURVE_PRICE_CONFIG); + const domainTwoExpPrice = await getCurvePrice(domainTwo, DEFAULT_CURVE_PRICE_CONFIG); const domainOneRefValue = BigInt("4545450000000000000000"); const domainTwoRefValue = BigInt("7692300000000000000000"); - const domainOnePriceSC = await zns.curvePricer.getPrice(domainHash, domainOne, true); - const domainTwoPriceSC = await zns.curvePricer.getPrice(domainHash, domainTwo, true); + const domainOnePriceSC = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, domainOne, true); + const domainTwoPriceSC = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, domainTwo, true); expect(domainOnePriceSC).to.eq(domainOneRefValue); expect(domainOnePriceSC).to.eq(domainOneExpPrice); @@ -203,8 +159,8 @@ describe("ZNSCurvePricer", () => { "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstu"; - const expectedPrice = await getCurvePrice(domain, DEFAULT_PRICE_CONFIG); - const domainPrice = await zns.curvePricer.getPrice(domainHash, domain, true); + const expectedPrice = await getCurvePrice(domain, DEFAULT_CURVE_PRICE_CONFIG); + const domainPrice = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, domain, true); expect(domainPrice).to.eq(expectedPrice); }); @@ -216,16 +172,16 @@ describe("ZNSCurvePricer", () => { const medium = "wilderworld"; const long = "wilderworldbeastspetsnftscatscalicosteve"; - const expectedShortPrice = await getCurvePrice(short, DEFAULT_PRICE_CONFIG); - const shortPrice = await zns.curvePricer.getPrice(domainHash, short, true); + const expectedShortPrice = await getCurvePrice(short, DEFAULT_CURVE_PRICE_CONFIG); + const shortPrice = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, short, true); expect(expectedShortPrice).to.eq(shortPrice); - const expectedMediumPrice = await getCurvePrice(medium, DEFAULT_PRICE_CONFIG); - const mediumPrice = await zns.curvePricer.getPrice(domainHash, medium, true); + const expectedMediumPrice = await getCurvePrice(medium, DEFAULT_CURVE_PRICE_CONFIG); + const mediumPrice = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, medium, true); expect(expectedMediumPrice).to.eq(mediumPrice); - const expectedLongPrice = await getCurvePrice(long, DEFAULT_PRICE_CONFIG); - const longPrice = await zns.curvePricer.getPrice(domainHash, long, true); + const expectedLongPrice = await getCurvePrice(long, DEFAULT_CURVE_PRICE_CONFIG); + const longPrice = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, long, true); expect(expectedLongPrice).to.eq(longPrice); }); @@ -237,13 +193,11 @@ describe("ZNSCurvePricer", () => { "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyz" + "a"; - const expectedPrice = getCurvePrice(domain, DEFAULT_PRICE_CONFIG); - const domainPrice = await zns.curvePricer.getPrice(domainHash, domain, true); + const expectedPrice = getCurvePrice(domain, DEFAULT_CURVE_PRICE_CONFIG); + const domainPrice = await zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, domain, true); expect(domainPrice).to.eq(expectedPrice); }); - }); - describe("#setPriceConfig", () => { it("Can't price a name that has invalid characters", async () => { // Valid names must match the pattern [a-z0-9] const labelA = "WILDER"; @@ -251,767 +205,157 @@ describe("ZNSCurvePricer", () => { const labelC = "!%$#^*?!#👍3^29"; const labelD = "wo.rld"; - - await expect(zns.curvePricer.getPrice(domainHash, labelA, false)) + await expect(zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, labelA, false)) .to.be.revertedWithCustomError(zns.curvePricer, INVALID_LABEL_ERR); - await expect(zns.curvePricer.getPrice(domainHash, labelB, false)) + await expect(zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, labelB, false)) .to.be.revertedWithCustomError(zns.curvePricer, INVALID_LABEL_ERR); - await expect(zns.curvePricer.getPrice(domainHash, labelC, false)) + await expect(zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, labelC, false)) .to.be.revertedWithCustomError(zns.curvePricer, INVALID_LABEL_ERR); - await expect(zns.curvePricer.getPrice(domainHash, labelD, false)) + await expect(zns.curvePricer.getPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, labelD, false)) .to.be.revertedWithCustomError(zns.curvePricer, INVALID_LABEL_ERR); }); - it("Should set the config for any existing domain hash, including 0x0", async () => { - const newConfig = { - baseLength: BigInt("6"), - maxLength: BigInt("35"), - maxPrice: ethers.parseEther("150"), - curveMultiplier: DEFAULT_PRICE_CONFIG.curveMultiplier, - precisionMultiplier: DEFAULT_PRECISION_MULTIPLIER, - feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, - isSet: true, - }; - - // as a user of "domainHash" that's not 0x0 - await zns.curvePricer.connect(user).setPriceConfig(domainHash, newConfig); - - // as a ZNS deployer who owns the 0x0 hash - await zns.curvePricer.connect(deployer).setPriceConfig(ZeroHash, newConfig); - - const configUser = await zns.curvePricer.priceConfigs(domainHash); - - expect(configUser.baseLength).to.eq(newConfig.baseLength); - expect(configUser.maxLength).to.eq(newConfig.maxLength); - expect(configUser.maxPrice).to.eq(newConfig.maxPrice); - expect(configUser.curveMultiplier).to.eq(newConfig.curveMultiplier); - expect(configUser.precisionMultiplier).to.eq(newConfig.precisionMultiplier); - expect(configUser.feePercentage).to.eq(newConfig.feePercentage); - - const configDeployer = await zns.curvePricer.priceConfigs(ZeroHash); - - expect(configDeployer.baseLength).to.eq(newConfig.baseLength); - expect(configDeployer.maxLength).to.eq(newConfig.maxLength); - expect(configDeployer.maxPrice).to.eq(newConfig.maxPrice); - expect(configDeployer.curveMultiplier).to.eq(newConfig.curveMultiplier); - expect(configDeployer.precisionMultiplier).to.eq(newConfig.precisionMultiplier); - expect(configDeployer.feePercentage).to.eq(newConfig.feePercentage); - }); - - it("Should revert if called by anyone other than owner or operator", async () => { - const newConfig = { - baseLength: BigInt("6"), - maxLength: BigInt("20"), - maxPrice: ethers.parseEther("10"), - curveMultiplier: DEFAULT_PRICE_CONFIG.curveMultiplier, - precisionMultiplier: DEFAULT_PRECISION_MULTIPLIER, - feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, - isSet: true, - }; - - await expect( - zns.curvePricer.connect(randomAcc).setPriceConfig(domainHash, newConfig) - ).to.be.revertedWithCustomError( - zns.curvePricer, - NOT_AUTHORIZED_ERR - ); + it("Returns price as 0 when the baseLength is 0", async () => { + const localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.baseLength = 0n; - await expect( - zns.curvePricer.connect(randomAcc).setPriceConfig(ZeroHash, newConfig) - ).to.be.revertedWithCustomError( - zns.curvePricer, - NOT_AUTHORIZED_ERR - ); - }); + const asBytes = encodePriceConfig(localConfig); - it("Should emit PriceConfigSet event with correct parameters", async () => { - const newConfig = { - baseLength: BigInt("6"), - maxLength: BigInt("35"), - maxPrice: ethers.parseEther("150"), - curveMultiplier: DEFAULT_PRICE_CONFIG.curveMultiplier, - precisionMultiplier: DEFAULT_PRECISION_MULTIPLIER, - feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, - isSet: true, - }; - - const tx = zns.curvePricer.connect(user).setPriceConfig(domainHash, newConfig); - - await expect(tx).to.emit(zns.curvePricer, "PriceConfigSet").withArgs( - domainHash, - newConfig.maxPrice, - newConfig.curveMultiplier, - newConfig.maxLength, - newConfig.baseLength, - newConfig.precisionMultiplier, - newConfig.feePercentage, - ); + const price = await zns.curvePricer.getPrice(asBytes, "test", true); + expect(price).to.eq(0n); }); }); - describe("#setMaxPrice", () => { - it("Allows an authorized user to set the max price", async () => { - const newMaxPrice = DEFAULT_PRICE_CONFIG.maxPrice + ethers.parseEther("10"); - - await zns.curvePricer.connect(user).setMaxPrice(domainHash, newMaxPrice); - - const params = await zns.curvePricer.priceConfigs(domainHash); - expect(params.maxPrice).to.eq(newMaxPrice); - }); - - it("Disallows an unauthorized user to set the max price", async () => { - const newMaxPrice = ethers.parseEther("0.7"); - - const tx = zns.curvePricer.connect(admin).setMaxPrice(domainHash, newMaxPrice); - await expect(tx).to.be.revertedWithCustomError(zns.curvePricer, NOT_AUTHORIZED_ERR); + describe("#validatePriceConfig", () => { + it("Succeeds when a valid config is provided", async () => { + await expect( + await zns.curvePricer.validatePriceConfig( + DEFAULT_CURVE_PRICE_CONFIG_BYTES + )).to.not.be.reverted; }); - it("Allows setting the max price to zero", async () => { - const newMaxPrice = BigInt("0"); + it("Fails when the curve multiplier is 0 and the base length is 0", async () => { + const localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.curveMultiplier = 0n; + localConfig.baseLength = 0n; - await zns.curvePricer.connect(user).setMaxPrice(domainHash, newMaxPrice); - const params = await zns.curvePricer.priceConfigs(domainHash); + const asBytes = encodePriceConfig(localConfig); - expect(params.maxPrice).to.eq(newMaxPrice); - }); - - it("Return 0 price for any domain when maxPrice is 0", async () => { - const newMaxPrice = BigInt("0"); - - await zns.curvePricer.connect(user).setMaxPrice(domainHash, newMaxPrice); - - // run 5 times to cover it more - for (let i = 1; i < 6; i++) { - const label = getRandomString(i * 2); - expect( - await zns.curvePricer.getPrice( - domainHash, - label, - false - ) - ).to.eq(0n); + try { + await expect( + zns.curvePricer.validatePriceConfig(asBytes) + ).to.be.revertedWith( + DIVISION_BY_ZERO_ERR + ); + } catch (e) { + expect((e as Error).message).to.include(DIVISION_BY_ZERO_ERR); } }); - it("Correctly sets max price", async () => { - const newMaxPrice = DEFAULT_PRICE_CONFIG.maxPrice + ethers.parseEther("553"); - await zns.curvePricer.connect(user).setMaxPrice(domainHash, newMaxPrice); - - const params = await zns.curvePricer.priceConfigs(domainHash); - expect(params.maxPrice).to.eq(newMaxPrice); - }); - - it("Causes any length domain to have a price of 0 if the maxPrice is 0", async () => { - const newMaxPrice = BigInt("0"); - - await zns.curvePricer.connect(user).setMaxPrice(domainHash, newMaxPrice); - - const shortDomain = "a"; - const longDomain = "abcdefghijklmnopqrstuvwxyz"; - - const shortPrice = await zns.curvePricer.getPrice(domainHash, shortDomain, true); - const longPrice = await zns.curvePricer.getPrice(domainHash, longDomain, true); - - expect(shortPrice).to.eq(BigInt("0")); - expect(longPrice).to.eq(BigInt("0")); - }); + it("Fails when max length is less than base length", async () => { + const localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.maxLength = 0n; + localConfig.baseLength = 100n; - it("The price of a domain is modified relatively when the basePrice is changed", async () => { - const newMaxPrice = DEFAULT_PRICE_CONFIG.maxPrice + ethers.parseEther("9"); - - const expectedPriceBefore = await getCurvePrice(defaultDomain, DEFAULT_PRICE_CONFIG); - const priceBefore= await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - - expect(expectedPriceBefore).to.eq(priceBefore); - - await zns.curvePricer.connect(user).setMaxPrice(domainHash, newMaxPrice); - - const newConfig = { - ...DEFAULT_PRICE_CONFIG, - maxPrice: newMaxPrice, - }; - - const expectedPriceAfter = await getCurvePrice(defaultDomain, newConfig); - const priceAfter = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - - expect(expectedPriceAfter).to.eq(priceAfter); - expect(expectedPriceAfter).to.be.gt(expectedPriceBefore); - expect(priceAfter).to.be.gt(priceBefore); - }); - }); - - describe("#setCurveMultiplier", async () => { - it("Return max price if curve multiplier set to 0", async () => { - const newMultiplier = BigInt("0"); - - await zns.curvePricer.connect(user).setCurveMultiplier(domainHash, newMultiplier); - - // run 5 times to cover it more - for (let i = 1; i < 6; i++) { - const domainString = getRandomString(i * i); - - const price = await zns.curvePricer.getPrice( - domainHash, - domainString, - false - ); + const asBytes = encodePriceConfig(localConfig); + try { await expect( - price - ).to.be.equal( - DEFAULT_PRICE_CONFIG.maxPrice + zns.curvePricer.validatePriceConfig(asBytes) + ).to.be.revertedWith( + INVALID_BASE_OR_MAX_LENGTH_ERR ); + } catch (e) { + expect((e as Error).message).to.include(INVALID_BASE_OR_MAX_LENGTH_ERR); } }); - it("Reverts when the method is called by a non-owner or operator", async () => { - await expect( - zns.curvePricer.connect(deployer).setCurveMultiplier(domainHash, 2000n) - ).to.be.revertedWithCustomError( - zns.curvePricer, - NOT_AUTHORIZED_ERR - ).withArgs( - deployer, - domainHash - ); - }); + it("Fails when maxLength is 0", async () => { + const localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.baseLength = 0n; // Set baseLength to 0 to avoid maxLength < baseLength failure + localConfig.maxLength = 0n; - it("Should revert when `baseLength` is already 0 and we pass `curveMultiplier` as 0", async () => { - await zns.curvePricer.connect(user).setBaseLength( - domainHash, - 0n - ); + const asBytes = encodePriceConfig(localConfig); - await expect( - zns.curvePricer.connect(user).setCurveMultiplier( - domainHash, - 0n - ) - ).to.be.revertedWithCustomError( - zns.curvePricer, - DIVISION_BY_ZERO_ERR - ); + try { + await expect( + zns.curvePricer.validatePriceConfig(asBytes) + ).to.be.revertedWith( + INVALID_BASE_OR_MAX_LENGTH_ERR + ); + } catch (e) { + expect((e as Error).message).to.include(INVALID_BASE_OR_MAX_LENGTH_ERR); + } }); - it("Should return max price for base length domain labels and 0 for other, which longer", async () => { - // Case where we can set domain strings longer than `baseLength` for free - // (numerator must be less than denominator) - - // constants for playing the scenario (one of many cases): - // `maxPrice` = 25 000 - // `baseLength` <= 40 - // `curveMultiplier` >= 10 000 000 000 - - const newConfig = { - maxPrice: ethers.parseEther("25000"), - curveMultiplier: BigInt("10000000000"), - maxLength: BigInt(100), - baseLength: BigInt(40), - precisionMultiplier: DEFAULT_PRECISION_MULTIPLIER, - feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, - isSet: true, - }; - - await zns.curvePricer.connect(user).setPriceConfig(domainHash, newConfig); - - const check = async (label : string, res : bigint) => { - const price = await zns.curvePricer.getPrice( - domainHash, - label, - false - ); + it("Fails when precision muliplier is 0 or greater than 10^18", async () => { + let localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.precisionMultiplier = 0n; - expect( - price - ).to.equal( - res - ); - }; + let asBytes = encodePriceConfig(localConfig); - // `baseLength` = 40, so run this 4 times - for (let i = 1; i <= newConfig.baseLength / 10n; i++) { - await check( - getRandomString(i * 10), - newConfig.maxPrice + try { + await expect( + zns.curvePricer.validatePriceConfig(asBytes) + ).to.be.revertedWith( + INVALID_PRECISION_MULTIPLIER_ERR ); + } catch (e) { + expect((e as Error).message).to.include(INVALID_PRECISION_MULTIPLIER_ERR); } + localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.precisionMultiplier = ethers.parseEther("10"); - for (let i = 5; i <= 15; i++) { - await check( - getRandomString(i * 10), - 0n + asBytes = encodePriceConfig(localConfig); + + try { + await expect( + zns.curvePricer.validatePriceConfig(asBytes) + ).to.be.revertedWith( + INVALID_PRECISION_MULTIPLIER_ERR ); + } catch (e) { + expect((e as Error).message).to.include(INVALID_PRECISION_MULTIPLIER_ERR); } - - await zns.curvePricer.connect(user).setPriceConfig(domainHash, DEFAULT_PRICE_CONFIG); - - }); - }); - - describe("#setPrecisionMultiplier", () => { - it("Allows an authorized user to set the precision multiplier", async () => { - const newMultiplier = BigInt("1"); - - await zns.curvePricer.connect(user).setPrecisionMultiplier(domainHash, newMultiplier); - - const params = await zns.curvePricer.priceConfigs(domainHash); - expect(params.precisionMultiplier).to.eq(newMultiplier); - }); - - it("Disallows an unauthorized user from setting the precision multiplier", async () => { - const newMultiplier = BigInt("2"); - - const tx = zns.curvePricer.connect(admin).setCurveMultiplier(domainHash, newMultiplier); - await expect(tx).to.be.revertedWithCustomError(zns.curvePricer, NOT_AUTHORIZED_ERR); - }); - - it("Fails when setting `precisionMultiplier` to zero", async () => { - const zeroMultiplier = BigInt("0"); - - await expect( - zns.curvePricer.connect(user).setPrecisionMultiplier( - domainHash, - zeroMultiplier - ) - ).to.be.revertedWithCustomError( - zns.curvePricer, - INVALID_PRECISION_MULTIPLIER_ERR - ).withArgs(domainHash); - }); - - it("Successfuly sets the precision multiplier when above 0", async () => { - const newMultiplier = BigInt("3"); - await zns.curvePricer.connect(user).setPrecisionMultiplier(domainHash, newMultiplier); - - const params = await zns.curvePricer.priceConfigs(domainHash); - expect(params.precisionMultiplier).to.eq(newMultiplier); - }); - - it("Verifies new prices are affected after changing the precision multiplier", async () => { - const atIndex = 7; - - const before = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - const beforePriceString = before.toString(); - - expect(beforePriceString.charAt(atIndex)).to.eq("0"); - - // Default precision is 2 decimals, so increasing this value should represent in prices - // as a non-zero nect decimal place - const newPrecision = BigInt(3); - const newPrecisionMultiplier = BigInt(10) ** DEFAULT_DECIMALS - newPrecision; - - await zns.curvePricer.connect(user).setPrecisionMultiplier(domainHash, newPrecisionMultiplier); - - const after = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - const afterPriceString = after.toString(); - - expect(afterPriceString.charAt(atIndex)).to.not.eq("0"); - - }); - - it("Should revert when setting precisionMultiplier higher than 10^18", async () => { - const newMultiplier = ethers.parseEther("100"); - await expect( - zns.curvePricer.connect(user).setPrecisionMultiplier(domainHash, newMultiplier) - ).to.be.revertedWithCustomError( - zns.curvePricer, - INVALID_PRECISION_MULTIPLIER_ERR - ); - }); - }); - - describe("#setBaseLength", () => { - it("Allows an authorized user to set the base length", async () => { - const newLength = 5; - - await zns.curvePricer.connect(user).setBaseLength(domainHash, newLength); - const params = await zns.curvePricer.priceConfigs(domainHash); - - expect(params.baseLength).to.eq(newLength); - }); - - it("Disallows an unauthorized user to set the base length", async () => { - const newLength = 5; - - const tx = zns.curvePricer.connect(admin).setBaseLength(domainHash, newLength); - await expect(tx).to.be.revertedWithCustomError(zns.curvePricer, NOT_AUTHORIZED_ERR); - }); - - it("Allows setting the base length to zero", async () => { - const newLength = 0; - - await zns.curvePricer.connect(user).setBaseLength(domainHash, newLength); - const params = await zns.curvePricer.priceConfigs(domainHash); - - expect(params.baseLength).to.eq(newLength); }); - it("Should revert when `curveMultiplier` is already 0 and we pass `baseLength` as 0", async () => { - await zns.curvePricer.connect(user).setCurveMultiplier( - domainHash, - 0n - ); - - await expect( - zns.curvePricer.connect(user).setBaseLength( - domainHash, - 0n - ) - ).to.be.revertedWithCustomError( - zns.curvePricer, - DIVISION_BY_ZERO_ERR - ); - }); - - it("Causes any length domain to cost the base fee when set to max length of 255", async () => { - const newLength = 255; - // We have to set `maxLength` firstly, cause `baseLength` cannot be bigger than `maxLength` - await zns.curvePricer.connect(user).setMaxLength(domainHash, newLength); - await zns.curvePricer.connect(user).setBaseLength(domainHash, newLength); - const params = await zns.curvePricer.priceConfigs(domainHash); - - const shortDomain = "a"; - const longDomain = "abcdefghijklmnopqrstuvwxyz"; - - const shortPrice = await zns.curvePricer.getPrice(domainHash, shortDomain, true); - const longPrice = await zns.curvePricer.getPrice(domainHash, longDomain, true); - - expect(shortPrice).to.eq(params.maxPrice); - expect(longPrice).to.eq(params.maxPrice); - }); - - it("Causes prices to adjust correctly when length is increased", async () => { - const newLength = 8; - const paramsBefore = await zns.curvePricer.priceConfigs(domainHash); - - const expectedPriceBefore = await getCurvePrice(defaultDomain, DEFAULT_PRICE_CONFIG); - const priceBefore = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - expect(priceBefore).to.eq(expectedPriceBefore); - expect(priceBefore).to.not.eq(paramsBefore.maxPrice); - - await zns.curvePricer.connect(user).setBaseLength(domainHash, newLength); - - const paramsAfter = await zns.curvePricer.priceConfigs(domainHash); + it("Fails when fee percentage is greater than 100%", async () => { + const localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.feePercentage = BigInt("10001"); - const newConfig = { - ...DEFAULT_PRICE_CONFIG, - baseLength: BigInt(newLength), - }; - - const expectedPriceAfter = await getCurvePrice(defaultDomain, newConfig); - const priceAfter = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - expect(priceAfter).to.eq(expectedPriceAfter); - expect(priceAfter).to.eq(paramsAfter.maxPrice); - }); - - it("Causes prices to adjust correctly when length is decreased", async () => { - const length = 8; - await zns.curvePricer.connect(user).setBaseLength(domainHash, length); - - const newConfig1 = { - ...DEFAULT_PRICE_CONFIG, - baseLength: BigInt(length), - }; - - const paramsBefore = await zns.curvePricer.priceConfigs(domainHash); - - const expectedPriceBefore = await getCurvePrice(defaultDomain, newConfig1); - const priceBefore = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - expect(priceBefore).to.eq(expectedPriceBefore); - expect(priceBefore).to.eq(paramsBefore.maxPrice); - - const newLength = 5; - await zns.curvePricer.connect(user).setBaseLength(domainHash, newLength); - - const newConfig2 = { - ...DEFAULT_PRICE_CONFIG, - baseLength: BigInt(newLength), - }; - - const paramsAfter = await zns.curvePricer.priceConfigs(domainHash); - - const expectedPriceAfter = await getCurvePrice(defaultDomain, newConfig2); - const priceAfter = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - expect(priceAfter).to.eq(expectedPriceAfter); - expect(priceAfter).to.not.eq(paramsAfter.maxPrice); - }); - - it("Returns the price = 0 whenever the baseLength is 0", async () => { - const newRootLength = 0; - await zns.curvePricer.connect(user).setBaseLength(domainHash, newRootLength); - - const price = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - - expect( - price - ).to.eq( - 0n - ); - }); + const asBytes = encodePriceConfig(localConfig); - it("Adjusts prices correctly when setting base lengths to different values", async () => { - for (let i = 0; i < 5; i++) { - const newRootLength = i * 2; - await zns.curvePricer.connect(user).setBaseLength(domainHash, newRootLength); - const newConfig = { - ...DEFAULT_PRICE_CONFIG, - baseLength: BigInt(newRootLength), - }; - - const expectedRootPrice = await getCurvePrice(defaultDomain, newConfig); - const rootPrice = await zns.curvePricer.getPrice(domainHash, defaultDomain, true); - - expect(rootPrice).to.eq(expectedRootPrice); + try { + await expect( + zns.curvePricer.validatePriceConfig(asBytes) + ).to.be.revertedWith( + FEE_TOO_LARGE_ERR + ); + } catch (e) { + expect((e as Error).message).to.include(FEE_TOO_LARGE_ERR); } }); - it("Should revert when `baseLength` bigger than `maxLength`", async () => { - await zns.curvePricer.connect(user).setMaxLength( - domainHash, - 10n - ); - + it("Fails when the config bytes are invalid length", async () => { await expect( - zns.curvePricer.connect(user).setBaseLength( - domainHash, - 20n - ) + zns.curvePricer.validatePriceConfig(DEFAULT_FIXED_PRICER_CONFIG_BYTES) ).to.be.revertedWithCustomError( zns.curvePricer, - INVALID_BASE_OR_MAX_LENGTH_ERR + INVALID_CONFIG_LENGTH_ERR ); }); }); - describe("#setMaxLength", () => { - it("Allows an authorized user to set the max length", async () => { - const newLength = 5; - - await zns.curvePricer.connect(user).setMaxLength(domainHash, newLength); - const params = await zns.curvePricer.priceConfigs(domainHash); - - expect(params.maxLength).to.eq(newLength); - }); - - it("Disallows an unauthorized user to set the max length", async () => { - const newLength = 5; - - const tx = zns.curvePricer.connect(admin).setMaxLength(domainHash, newLength); - await expect(tx).to.be.revertedWithCustomError(zns.curvePricer, NOT_AUTHORIZED_ERR); - }); - - it("Doesn't allow setting the `maxLength` to zero", async () => { - // require setting `baseLength` smaller or equal `maxLength` - const newLength = 0; - - await zns.curvePricer.connect(user).setBaseLength(domainHash, 0n); - - await expect( - zns.curvePricer.connect(user).setMaxLength(domainHash, newLength) - ).to.be.revertedWithCustomError( - zns.curvePricer, - INVALID_BASE_OR_MAX_LENGTH_ERR - ).withArgs( - domainHash - ); - }); - - it("Should revert when `maxLength` smaller than `baseLength`", async () => { - await zns.curvePricer.connect(user).setBaseLength( - domainHash, - 20n - ); - - await expect( - zns.curvePricer.connect(user).setMaxLength( - domainHash, - 10n - ) - ).to.be.revertedWithCustomError( - zns.curvePricer, - INVALID_BASE_OR_MAX_LENGTH_ERR - ); - }); - }); - - describe("#setFeePercentage", () => { - it("Successfully sets the fee percentage", async () => { - const newFeePerc = BigInt(222); - await zns.curvePricer.connect(user).setFeePercentage(domainHash, newFeePerc); - const { feePercentage: feeFromSC } = await zns.curvePricer.priceConfigs(domainHash); - - expect(feeFromSC).to.eq(newFeePerc); - }); - - it("Disallows an unauthorized user to set the fee percentage", async () => { - const newFeePerc = BigInt(222); - const tx = zns.curvePricer.connect(admin) - .setFeePercentage(domainHash, newFeePerc); - await expect(tx).to.be.revertedWithCustomError(zns.curvePricer, NOT_AUTHORIZED_ERR); - }); - - it("should revert when trying to set feePercentage higher than PERCENTAGE_BASIS", async () => { - const newFeePerc = BigInt(10001); - await expect( - zns.curvePricer.connect(user).setFeePercentage(domainHash, newFeePerc) - ).to.be.revertedWithCustomError( - zns.curvePricer, - FEE_TOO_LARGE_ERR - ).withArgs(newFeePerc, 10000n); - }); - }); - describe("#getRegistrationFee", () => { it("Successfully gets the fee for a price", async () => { const stake = ethers.parseEther("0.2"); - const fee = await zns.curvePricer.getFeeForPrice(domainHash, stake); + const fee = await zns.curvePricer.getFeeForPrice( + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + stake + ); const expectedFee = stake * 222n / 10000n; expect(fee).to.eq(expectedFee); }); }); - - describe("#setAccessController", () => { - it("Successfully sets the access controller", async () => { - const currentAccessController = await zns.curvePricer.getAccessController(); - expect(currentAccessController).to.not.eq(randomAcc.address); - - const tx = await zns.curvePricer.setAccessController(randomAcc.address); - - const newAccessController = await zns.curvePricer.getAccessController(); - expect(newAccessController).to.eq(randomAcc.address); - - await expect(tx).to.emit(zns.curvePricer, "AccessControllerSet").withArgs(randomAcc.address); - }); - - it("Disallows an unauthorized user to set the access controller", async () => { - const tx = zns.curvePricer.connect(user).setAccessController(randomAcc.address); - await expect(tx).to.be.revertedWithCustomError(zns.accessController, AC_UNAUTHORIZED_ERR) - .withArgs(user.address,ADMIN_ROLE); - }); - - it("Disallows setting the access controller to the zero address", async () => { - const tx = zns.curvePricer.connect(admin).setAccessController(ethers.ZeroAddress); - await expect(tx).to.be.revertedWithCustomError( - zns.curvePricer, - ZERO_ADDRESS_ERR - ); - }); - }); - - describe("#setRegistry", () => { - it("Should successfully set the registry", async () => { - const currentRegistry = await zns.curvePricer.registry(); - expect(currentRegistry).to.not.eq(randomAcc.address); - - const tx = await zns.curvePricer.connect(admin).setRegistry(randomAcc.address); - - const newRegistry = await zns.curvePricer.registry(); - expect(newRegistry).to.eq(randomAcc.address); - - await expect(tx).to.emit(zns.curvePricer, "RegistrySet").withArgs(randomAcc.address); - }); - - it("Should NOT set the registry if called by anyone other than ADMIN_ROLE", async () => { - const tx = zns.curvePricer.connect(user).setRegistry(randomAcc.address); - await expect(tx).to.be.revertedWithCustomError(zns.accessController, AC_UNAUTHORIZED_ERR) - .withArgs(user.address,ADMIN_ROLE); - }); - }); - - describe("General validation", () => { - it("Should revert when all passed variables are 0", async () => { - await expect( - zns.curvePricer.connect(user).setPriceConfig( - domainHash, - { - maxPrice: 0n, - curveMultiplier: 0n, - maxLength: 0n, - baseLength: 0n, - precisionMultiplier: 0n, - feePercentage: 0n, - isSet: true, - } - ) - ).to.be.revertedWithCustomError( - zns.curvePricer, - // because its a first check - INVALID_PRECISION_MULTIPLIER_ERR - ).withArgs(domainHash); - }); - }); - - describe("Events", () => { - it("Emits MaxPriceSet", async () => { - const newMaxPrice = DEFAULT_PRICE_CONFIG.maxPrice + 1n; - - const tx = zns.curvePricer.connect(user).setMaxPrice(domainHash, newMaxPrice); - await expect(tx).to.emit(zns.curvePricer, "MaxPriceSet").withArgs(domainHash, newMaxPrice); - }); - - it("Emits BaseLengthSet", async () => { - const newLength = 5; - - const tx = zns.curvePricer.connect(user).setBaseLength(domainHash, newLength); - await expect(tx).to.emit(zns.curvePricer, "BaseLengthSet").withArgs(domainHash, newLength); - }); - }); - - describe("UUPS", () => { - it("Allows an authorized user to upgrade the contract", async () => { - // CurvePricer to upgrade to - const factory = new ZNSCurvePricer__factory(deployer); - const newCurvePricer = await factory.deploy(); - await newCurvePricer.waitForDeployment(); - - // Confirm the deployer is a governor, as set in `deployZNS` helper - await expect(zns.accessController.checkGovernor(deployer.address)).to.not.be.reverted; - - const tx = zns.curvePricer.connect(deployer).upgradeToAndCall( - await newCurvePricer.getAddress(), - "0x" - ); - await expect(tx).to.not.be.reverted; - }); - - it("Fails to upgrade if the caller is not authorized", async () => { - // CurvePricer to upgrade to - const factory = new ZNSCurvePricerUpgradeMock__factory(deployer); - const newCurvePricer = await factory.deploy(); - await newCurvePricer.waitForDeployment(); - - // Confirm the account is not a governor - await expect(zns.accessController.checkGovernor(randomAcc.address)).to.be.reverted; - - const tx = zns.curvePricer.connect(randomAcc).upgradeToAndCall( - await newCurvePricer.getAddress(), - "0x" - ); - - await expect(tx).to.be.revertedWithCustomError(zns.accessController, AC_UNAUTHORIZED_ERR) - .withArgs(randomAcc.address, GOVERNOR_ROLE); - }); - - it("Verifies that variable values are not changed in the upgrade process", async () => { - const factory = new ZNSCurvePricerUpgradeMock__factory(deployer); - const newCurvePricer = await factory.deploy(); - await newCurvePricer.waitForDeployment(); - - await zns.curvePricer.connect(user).setBaseLength(domainHash, "7"); - await zns.curvePricer.connect(user).setMaxPrice( - domainHash, - DEFAULT_PRICE_CONFIG.maxPrice + 15n - ); - - const contractCalls = [ - zns.curvePricer.registry(), - zns.curvePricer.getAccessController(), - zns.curvePricer.priceConfigs(domainHash), - zns.curvePricer.getPrice(domainHash, "wilder", true), - ]; - - await validateUpgrade(deployer, zns.curvePricer, newCurvePricer, factory, contractCalls); - }); - }); }); diff --git a/test/ZNSDomainToken.test.ts b/test/ZNSDomainToken.test.ts index ede6316af..982d0c815 100644 --- a/test/ZNSDomainToken.test.ts +++ b/test/ZNSDomainToken.test.ts @@ -52,6 +52,7 @@ describe("ZNSDomainToken", () => { governorAddresses: [deployer.address], adminAddresses: [deployer.address], }; + zns = await deployZNS( deployParams ); diff --git a/test/ZNSFixedPricer.test.ts b/test/ZNSFixedPricer.test.ts index 9f956fb1e..1934abcff 100644 --- a/test/ZNSFixedPricer.test.ts +++ b/test/ZNSFixedPricer.test.ts @@ -1,39 +1,41 @@ import { - ADMIN_ROLE, - deployFixedPricer, deployZNS, - GOVERNOR_ROLE, - INITIALIZED_ERR, INVALID_LABEL_ERR, - NOT_AUTHORIZED_ERR, - PaymentType, + INVALID_LABEL_ERR, DEFAULT_PERCENTAGE_BASIS, - DEFAULT_PRICE_CONFIG, - validateUpgrade, AccessType, AC_UNAUTHORIZED_ERR, FEE_TOO_LARGE_ERR, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, + DEFAULT_FIXED_PRICE_CONFIG, + encodePriceConfig, + decodePriceConfig, + DEFAULT_PROTOCOL_FEE_PERCENT, + IZNSContractsLocal, + IFixedPriceConfig, + INVALID_CONFIG_LENGTH_ERR, + FEE_TOO_LARGE_ERR, + PaymentType, + AccessType, } from "./helpers"; import * as hre from "hardhat"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import * as ethers from "ethers"; import { registrationWithSetup } from "./helpers/register-setup"; import { expect } from "chai"; -import { ZNSFixedPricer__factory, ZNSFixedPricer, ZNSFixedPricerUpgradeMock__factory } from "../typechain"; -import { getProxyImplAddress } from "./helpers/utils"; -import { IZNSContractsLocal } from "./helpers/types"; describe("ZNSFixedPricer", () => { let deployer : SignerWithAddress; let admin : SignerWithAddress; let user : SignerWithAddress; - let random : SignerWithAddress; let zeroVault : SignerWithAddress; let zns : IZNSContractsLocal; let domainHash : string; + let parentPrice : bigint; let parentFeePercentage : bigint; before(async () => { - [deployer, admin, user, zeroVault, random] = await hre.ethers.getSigners(); + [deployer, admin, user, zeroVault] = await hre.ethers.getSigners(); + parentPrice = ethers.parseEther("2223"); parentFeePercentage = BigInt(2310); @@ -41,7 +43,6 @@ describe("ZNSFixedPricer", () => { deployer, governorAddresses: [deployer.address, deployer.address], adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, }); @@ -50,112 +51,58 @@ describe("ZNSFixedPricer", () => { const fullConfig = { distrConfig: { - paymentType: PaymentType.DIRECT, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ price: parentPrice, feePercentage: parentFeePercentage }), + paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: user.address, }, - priceConfig: { - price: parentPrice, - feePercentage: parentFeePercentage, - }, }; domainHash = await registrationWithSetup({ zns, user, + tokenOwner: user.address, domainLabel: "test", fullConfig, }); }); - it("should deploy with correct parameters", async () => { - expect(await zns.fixedPricer.getAccessController()).to.equal(await zns.accessController.getAddress()); - expect(await zns.fixedPricer.registry()).to.equal(await zns.registry.getAddress()); - }); - - it("should NOT initialize twice", async () => { - await expect(zns.fixedPricer.initialize( - await zns.accessController.getAddress(), - await zns.registry.getAddress(), - )).to.be.revertedWithCustomError(zns.fixedPricer, INITIALIZED_ERR); - }); - - it("Should NOT let initialize the implementation contract", async () => { - const factory = new ZNSFixedPricer__factory(deployer); - const impl = await getProxyImplAddress(await zns.fixedPricer.getAddress()); - const implContract = factory.attach(impl) as ZNSFixedPricer; - - await expect( - implContract.initialize( - deployer.address, - random.address, - ) - ).to.be.revertedWithCustomError(implContract, INITIALIZED_ERR); - }); - - it("should set config for 0x0 hash", async () => { - const { - price, - feePercentage, - } = await zns.fixedPricer.priceConfigs(ethers.ZeroHash); - - expect(price).to.equal(0); - expect(feePercentage).to.equal(0); - - const newPrice = ethers.parseEther("9182263"); - const newFee = BigInt(2359); - - // deployer owns 0x0 hash at initialization time - await zns.fixedPricer.connect(deployer).setPriceConfig( - ethers.ZeroHash, - { - price: newPrice, - feePercentage: newFee, - isSet: true, - } - ); - - const { - price: newPriceAfter, - feePercentage: newFeeAfter, - } = await zns.fixedPricer.priceConfigs(ethers.ZeroHash); + it("Confirms encoding is the same offchain and onchain", async () => { + const onchain = await zns.fixedPricer.encodeConfig(DEFAULT_FIXED_PRICE_CONFIG); + const offchain = encodePriceConfig(DEFAULT_FIXED_PRICE_CONFIG); - expect(newPriceAfter).to.equal(newPrice); - expect(newFeeAfter).to.equal(newFee); - }); - - it("should not allow to be deployed by anyone other than ADMIN_ROLE", async () => { - await expect( - deployFixedPricer({ - deployer: random, - acAddress: await zns.accessController.getAddress(), - regAddress: await zns.registry.getAddress(), - }), - ).to.be.revertedWithCustomError(zns.accessController, AC_UNAUTHORIZED_ERR) - .withArgs(random.address, ADMIN_ROLE); + expect(onchain).to.eq(offchain); }); - it("#setPrice() should work correctly and emit #PriceSet event", async () => { - const newPrice = ethers.parseEther("1823"); - const tx = zns.fixedPricer.connect(user).setPrice(domainHash, newPrice); + it("Confirms decoding is the same offchain and onchain", async () => { + const onchain = await zns.fixedPricer.decodePriceConfig(DEFAULT_FIXED_PRICER_CONFIG_BYTES); + const offchain = decodePriceConfig(DEFAULT_FIXED_PRICER_CONFIG_BYTES) as IFixedPriceConfig; - await expect(tx).to.emit(zns.fixedPricer, "PriceSet").withArgs(domainHash, newPrice); - - expect( - await zns.fixedPricer.getPrice(domainHash, "testname", true) - ).to.equal(newPrice); + expect(onchain.price).to.eq(offchain.price); + expect(onchain.feePercentage).to.eq(offchain.feePercentage); }); it("#getPrice should return the correct price", async () => { const newPrice = ethers.parseEther("3213"); - await zns.fixedPricer.connect(user).setPrice(domainHash, newPrice); + const newConfig = { + price: newPrice, + feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, + }; + + const asBytes = encodePriceConfig(newConfig); + + await zns.subRegistrar.connect(user).setPricerDataForDomain( + domainHash, + asBytes, + zns.fixedPricer.target + ); expect( - await zns.fixedPricer.getPrice(domainHash, "testname", false) + await zns.fixedPricer.getPrice(asBytes, "testname", false) ).to.equal(newPrice); }); @@ -168,225 +115,79 @@ describe("ZNSFixedPricer", () => { it("#getPriceAndFee() should return the correct price and fee", async () => { const newPrice = ethers.parseEther("3213"); const newFee = BigInt(1234); - await zns.fixedPricer.connect(user).setPrice(domainHash, newPrice); - await zns.fixedPricer.connect(user).setFeePercentage(domainHash, newFee); - - const { - price, - fee, - } = await zns.fixedPricer.getPriceAndFee(domainHash, "testname", false); - - expect(price).to.equal(newPrice); - expect(fee).to.equal(newPrice * newFee / DEFAULT_PERCENTAGE_BASIS); - }); - - it("#setPrice() should revert if called by anyone other than domain owner", async () => { - await expect( - zns.fixedPricer.connect(random).setPrice(domainHash, ethers.parseEther("1")) - ).to.be.revertedWithCustomError( - zns.fixedPricer, - NOT_AUTHORIZED_ERR - ); - }); - - it("#setFeePercentage() should set the fee correctly and emit #FeePercentageSet event", async () => { - const newFee = BigInt(1234); - const tx = zns.fixedPricer.connect(user).setFeePercentage(domainHash, newFee); - - await expect(tx).to.emit(zns.fixedPricer, "FeePercentageSet").withArgs(domainHash, newFee); - const { - feePercentage, - } = await zns.fixedPricer.priceConfigs(domainHash); - expect(feePercentage).to.equal(newFee); - }); - - it("#setFeePercentage() should revert if called by anyone other than domain owner", async () => { - await expect( - zns.fixedPricer.connect(random).setFeePercentage(domainHash, BigInt(1)) - ).to.be.revertedWithCustomError( - zns.fixedPricer, - NOT_AUTHORIZED_ERR - ); - }); + const newConfig = { + price: newPrice, + feePercentage: newFee, + }; - it("#setFeePercentage() should revert when trying to set feePercentage higher than PERCENTAGE_BASIS", async () => { - await expect( - zns.fixedPricer.connect(user).setFeePercentage(domainHash, DEFAULT_PERCENTAGE_BASIS + 1n) - ).to.be.revertedWithCustomError( - zns.fixedPricer, - FEE_TOO_LARGE_ERR - ); - }); + const asBytes = encodePriceConfig(newConfig); - // eslint-disable-next-line max-len - it("#setPriceConfig() should set the price config correctly and emit #PriceSet and #FeePercentageSet events", async () => { - const newPrice = ethers.parseEther("1823"); - const newFee = BigInt("12"); - const tx = zns.fixedPricer.connect(user).setPriceConfig( + await zns.subRegistrar.connect(user).setPricerDataForDomain( domainHash, - { - price: newPrice, - feePercentage: newFee, - isSet: true, - } + asBytes, + zns.fixedPricer.target ); - await expect(tx).to.emit(zns.fixedPricer, "PriceSet").withArgs(domainHash, newPrice); - await expect(tx).to.emit(zns.fixedPricer, "FeePercentageSet").withArgs(domainHash, newFee); + const [ price, fee ] = await zns.fixedPricer.getPriceAndFee( + asBytes, + "testname", + false + ); - const { - price, - feePercentage, - } = await zns.fixedPricer.priceConfigs(domainHash); expect(price).to.equal(newPrice); - expect(feePercentage).to.equal(newFee); - }); - - it("#setPriceConfig() should revert if called by anyone other than domain owner or operator", async () => { - await expect( - zns.fixedPricer.connect(random).setPriceConfig(domainHash, { - price: BigInt(1), - feePercentage: BigInt(1), - isSet: true, - }) - ).to.be.revertedWithCustomError( - zns.fixedPricer, - NOT_AUTHORIZED_ERR - ); + expect(fee).to.equal(newPrice * newFee / DEFAULT_PERCENTAGE_BASIS); }); - it("#setRegistry() should set the correct address", async () => { - await zns.fixedPricer.connect(admin).setRegistry(random.address); - - expect( - await zns.fixedPricer.registry() - ).to.equal(random.address); + it("#getFeeForPrice should return the correct fee for a given price", async () => { + const newPrice = ethers.parseEther("3213"); + const newFee = BigInt(1234); - // set back for other tests - await zns.fixedPricer.connect(admin).setRegistry(await zns.registry.getAddress()); - }); + const newConfig = { + price: newPrice, + feePercentage: newFee, + }; - it("#setRegistry() should revert if called by anyone other than ADMIN_ROLE", async () => { - await expect( - zns.fixedPricer.connect(random).setRegistry(random.address) - ).to.be.revertedWithCustomError(zns.accessController, AC_UNAUTHORIZED_ERR) - .withArgs(random.address, ADMIN_ROLE); - }); + const asBytes = encodePriceConfig(newConfig); - it("#setAccessController() should revert if called by anyone other than ADMIN_ROLE", async () => { - await expect( - zns.fixedPricer.connect(random).setAccessController(random.address) - ).to.be.revertedWithCustomError(zns.accessController, AC_UNAUTHORIZED_ERR) - .withArgs(random.address, ADMIN_ROLE); - }); + await zns.subRegistrar.connect(user).setPricerDataForDomain( + domainHash, + asBytes, + zns.fixedPricer.target + ); - // keep this as the last test - it("#setAccessController() should set the correct address", async () => { - await zns.fixedPricer.connect(admin).setAccessController(random.address); + const fee = await zns.fixedPricer.getFeeForPrice(asBytes, newPrice); - expect( - await zns.fixedPricer.getAccessController() - ).to.equal(random.address); + expect(fee).to.equal(newPrice * newFee / DEFAULT_PERCENTAGE_BASIS); }); - describe("UUPS", () => { - before(async () => { - zns = await deployZNS({ - deployer, - governorAddresses: [deployer.address, deployer.address], - adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, - zeroVaultAddress: zeroVault.address, - }); - - await zns.meowToken.connect(user).approve(await zns.treasury.getAddress(), ethers.MaxUint256); - await zns.meowToken.mint(user.address, ethers.parseEther("10000000000000")); - - const fullConfig = { - distrConfig: { - paymentType: PaymentType.DIRECT, - pricerContract: await zns.fixedPricer.getAddress(), - accessType: AccessType.OPEN, - }, - paymentConfig: { - token: await zns.meowToken.getAddress(), - beneficiary: user.address, - }, - priceConfig: { - price: parentPrice, - feePercentage: parentFeePercentage, - }, + describe("#validatePriceConfig", () => { + it("Should pass if price config is valid", async () => { + const config = { + price: ethers.parseEther("1000"), + feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, }; - domainHash = await registrationWithSetup({ - zns, - user, - domainLabel: "test", - fullConfig, - }); + await expect( + await zns.fixedPricer.validatePriceConfig(encodePriceConfig(config)) + ).to.not.be.reverted; }); - it("Allows an authorized user to upgrade the contract", async () => { - // FixedPricer to upgrade to - const factory = new ZNSFixedPricerUpgradeMock__factory(deployer); - const newFixedPricer = await factory.deploy(); - await newFixedPricer.waitForDeployment(); - - // Confirm the deployer is a governor, as set in `deployZNS` helper - await expect(zns.accessController.checkGovernor(deployer.address)).to.not.be.reverted; - - const tx = zns.fixedPricer.connect(deployer).upgradeToAndCall( - await newFixedPricer.getAddress(), - "0x" - ); - await expect(tx).to.not.be.reverted; - + it("should revert if the price config bytes has invalid length", async () => { await expect( - zns.fixedPricer.connect(deployer).initialize( - await zns.accessController.getAddress(), - await zns.registry.getAddress(), - ) - ).to.be.revertedWithCustomError(zns.fixedPricer, INITIALIZED_ERR); + zns.fixedPricer.validatePriceConfig("0x") + ).to.be.revertedWithCustomError(zns.fixedPricer, INVALID_CONFIG_LENGTH_ERR); }); - it("Fails to upgrade if the caller is not authorized", async () => { - // FixedPricer to upgrade to - const factory = new ZNSFixedPricerUpgradeMock__factory(deployer); - const newFixedPricer = await factory.deploy(); - await newFixedPricer.waitForDeployment(); - - // Confirm the account is not a governor - await expect(zns.accessController.checkGovernor(random.address)).to.be.reverted; - - const tx = zns.fixedPricer.connect(random).upgradeToAndCall( - await newFixedPricer.getAddress(), - "0x" - ); - - await expect(tx).to.be.revertedWithCustomError(zns.accessController, AC_UNAUTHORIZED_ERR) - .withArgs(random.address, GOVERNOR_ROLE); - }); + it("should revert if the fee percentage is too high", async () => { + const config = { + price: ethers.parseEther("1000"), + feePercentage: BigInt(10001), + }; - it("Verifies that variable values are not changed in the upgrade process", async () => { - const factory = new ZNSFixedPricerUpgradeMock__factory(deployer); - const newFixedPricer = await factory.deploy(); - await newFixedPricer.waitForDeployment(); - - await zns.fixedPricer.connect(user).setPrice(domainHash, "7"); - await zns.fixedPricer.connect(user).setFeePercentage( - domainHash, - BigInt(12) - ); - - const contractCalls = [ - zns.fixedPricer.registry(), - zns.fixedPricer.getAccessController(), - zns.fixedPricer.priceConfigs(domainHash), - zns.fixedPricer.getPrice(domainHash, "wilder", false), - ]; - - await validateUpgrade(deployer, zns.fixedPricer, newFixedPricer, factory, contractCalls); + await expect( + zns.fixedPricer.validatePriceConfig(encodePriceConfig(config)) + ).to.be.revertedWithCustomError(zns.fixedPricer, FEE_TOO_LARGE_ERR); }); }); }); diff --git a/test/ZNSRootRegistrar.test.ts b/test/ZNSRootRegistrar.test.ts index 635716b37..23a6085d0 100644 --- a/test/ZNSRootRegistrar.test.ts +++ b/test/ZNSRootRegistrar.test.ts @@ -17,19 +17,35 @@ import { NONEXISTENT_TOKEN_ERC_ERR, REGISTRAR_ROLE, DEFAULT_PRECISION_MULTIPLIER, - DEFAULT_PRICE_CONFIG, + DEFAULT_CURVE_PRICE_CONFIG, DEFAULT_PROTOCOL_FEE_PERCENT, NOT_AUTHORIZED_ERR, INVALID_LABEL_ERR, - paymentConfigEmpty, AC_UNAUTHORIZED_ERR, INSUFFICIENT_BALANCE_ERC_ERR, ZERO_ADDRESS_ERR, DOMAIN_EXISTS_ERR, + paymentConfigEmpty, + AC_UNAUTHORIZED_ERR, + INSUFFICIENT_BALANCE_ERC_ERR, + ZERO_ADDRESS_ERR, + DOMAIN_EXISTS_ERR, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, + ZERO_VALUE_CURVE_PRICE_CONFIG_BYTES, + ZERO_VALUE_FIXED_PRICE_CONFIG_BYTES, + DIVISION_BY_ZERO_ERR, + INVALID_CONFIG_LENGTH_ERR, } from "./helpers"; -import { IDistributionConfig, IRootdomainConfig, IZNSContractsLocal } from "./helpers/types"; import * as ethers from "ethers"; import { defaultRootRegistration, defaultSubdomainRegistration } from "./helpers/register-setup"; import { checkBalance } from "./helpers/balances"; -import { getPriceObject, getStakingOrProtocolFee } from "./helpers/pricing"; +import { decodePriceConfig, encodePriceConfig, getPriceObject, getStakingOrProtocolFee } from "./helpers/pricing"; +import { ADMIN_ROLE, GOVERNOR_ROLE, DOMAIN_TOKEN_ROLE } from "../src/deploy/constants"; +import { + DefaultRootRegistrationArgs, + IDistributionConfig, + IFixedPriceConfig, + IRootDomainConfig, + IZNSContractsLocal, +} from "./helpers/types"; import { getDomainHashFromEvent, getDomainRegisteredEvents } from "./helpers/events"; -import { ADMIN_ROLE, GOVERNOR_ROLE } from "../src/deploy/constants"; import { IERC20, ZNSRootRegistrar, @@ -42,6 +58,7 @@ import { getProxyImplAddress } from "./helpers/utils"; import { upgrades } from "hardhat"; import { getConfig } from "../src/deploy/campaign/get-config"; import { ZeroHash } from "ethers"; +import { ICurvePriceConfig } from "../src/deploy/missions/types"; import { IZNSContracts } from "../src/deploy/campaign/types"; require("@nomicfoundation/hardhat-chai-matchers"); @@ -82,7 +99,7 @@ describe("ZNSRootRegistrar", () => { zns = campaign.state.contracts; - // await zns.accessController.connect(deployer).grantRole(DOMAIN_TOKEN_ROLE, await zns.domainToken.getAddress()); + await zns.accessController.connect(deployer).grantRole(DOMAIN_TOKEN_ROLE, await zns.domainToken.getAddress()); mongoAdapter = campaign.dbAdapter; @@ -92,6 +109,7 @@ describe("ZNSRootRegistrar", () => { ); userBalanceInitial = ethers.parseEther("1000000000000000000"); + // Give funds to user await zns.meowToken.connect(user).approve(await zns.treasury.getAddress(), ethers.MaxUint256); await zns.meowToken.mint(user.address, userBalanceInitial); @@ -102,18 +120,19 @@ describe("ZNSRootRegistrar", () => { }); it("Should register an array of domains", async () => { - const registrations : Array = []; + const registrations : Array = []; for (let i = 0; i < 5; i++) { const isOdd = i % 2 !== 0; - const domainObj : IRootdomainConfig = { + const domainObj : IRootDomainConfig = { name: `domain${i + 1}`, domainAddress: user.address, tokenOwner: hre.ethers.ZeroAddress, tokenURI: `0://domainURI_${i + 1}`, distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: isOdd ? PaymentType.STAKE : PaymentType.DIRECT, accessType: isOdd ? AccessType.LOCKED : AccessType.OPEN, }, @@ -149,13 +168,14 @@ describe("ZNSRootRegistrar", () => { }); it("Should revert when register the same domain twice using #registerRootDomainBulk", async () => { - const domainObj = { + const domainObj : IRootDomainConfig = { name: "root", domainAddress: user.address, tokenOwner: ethers.ZeroAddress, tokenURI: "0://tokenURI", distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.LOCKED, }, @@ -177,6 +197,7 @@ describe("ZNSRootRegistrar", () => { pricerContract: await zns.curvePricer.getAddress(), paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, }; await defaultRootRegistration({ @@ -201,6 +222,7 @@ describe("ZNSRootRegistrar", () => { const tokenURI = "https://example.com/817c64af"; const distrConfig : IDistributionConfig = { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }; @@ -225,6 +247,7 @@ describe("ZNSRootRegistrar", () => { const distrConfig : IDistributionConfig = { pricerContract: await zns.curvePricer.getAddress(), paymentType: PaymentType.STAKE, + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, accessType: AccessType.OPEN, }; @@ -276,10 +299,16 @@ describe("ZNSRootRegistrar", () => { }); it("Should NOT initialize the implementation contract", async () => { - const factory = new ZNSRootRegistrar__factory(deployer); + + const otherFact = await hre.ethers.getContractFactory( + "ZNSRootRegistrar", + deployer + ); + + // const factory = new ZNSRootRegistrar__factory(deployer); const impl = await getProxyImplAddress(await zns.rootRegistrar.getAddress()); // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion - const implContract = factory.attach(impl) as ZNSRootRegistrar; + const implContract = otherFact.attach(impl) as ZNSRootRegistrar; await expect( implContract.initialize( @@ -288,6 +317,7 @@ describe("ZNSRootRegistrar", () => { operator.address, operator.address, operator.address, + operator.address, ) ).to.be.revertedWithCustomError(implContract, INITIALIZED_ERR); }); @@ -298,6 +328,7 @@ describe("ZNSRootRegistrar", () => { }); it("Confirms a new 0x0 owner can modify the configs in the treasury and curve pricer", async () => { + await zns.accessController.connect(deployer).grantRole(ADMIN_ROLE, user); await zns.registry.updateDomainOwner(ethers.ZeroHash, user.address); const newTreasuryConfig : PaymentConfigStruct = { @@ -324,29 +355,25 @@ describe("ZNSRootRegistrar", () => { ); // Modify the curve pricer - const newPricerConfig = { + const newPricerConfig : ICurvePriceConfig = { baseLength: BigInt("6"), maxLength: BigInt("35"), maxPrice: ethers.parseEther("150"), curveMultiplier: BigInt(1000), precisionMultiplier: DEFAULT_PRECISION_MULTIPLIER, feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, - isSet: true, }; - const pricerTx = await zns.curvePricer.connect(user).setPriceConfig( - ethers.ZeroHash, - newPricerConfig, + const asBytes = encodePriceConfig(newPricerConfig); + + const pricerTx = await zns.rootRegistrar.connect(user).setRootPricerAndConfig( + await zns.curvePricer.getAddress(), + asBytes, ); - await expect(pricerTx).to.emit(zns.curvePricer, "PriceConfigSet").withArgs( - ethers.ZeroHash, - newPricerConfig.maxPrice, - newPricerConfig.curveMultiplier, - newPricerConfig.maxLength, - newPricerConfig.baseLength, - newPricerConfig.precisionMultiplier, - newPricerConfig.feePercentage, + await expect(pricerTx).to.emit(zns.rootRegistrar, "RootPricerSet").withArgs( + await zns.curvePricer.getAddress(), + asBytes ); }); @@ -370,6 +397,7 @@ describe("ZNSRootRegistrar", () => { await zns.accessController.getAddress(), await zns.registry.getAddress(), await zns.curvePricer.getAddress(), + DEFAULT_CURVE_PRICE_CONFIG_BYTES, await zns.treasury.getAddress(), await zns.domainToken.getAddress(), ], @@ -387,6 +415,7 @@ describe("ZNSRootRegistrar", () => { await zns.accessController.getAddress(), randomUser.address, randomUser.address, + ZeroHash, randomUser.address, randomUser.address, ); @@ -501,7 +530,7 @@ describe("ZNSRootRegistrar", () => { token: ethers.ZeroAddress, beneficiary: ethers.ZeroAddress, }, - }); + } as IRootDomainConfig); await expect(tx2).to.emit(zns.rootRegistrar, "DomainRegistered").withArgs( ethers.ZeroHash, @@ -591,7 +620,7 @@ describe("ZNSRootRegistrar", () => { token: ethers.ZeroAddress, beneficiary: ethers.ZeroAddress, }, - }); + } as IRootDomainConfig); const hashFromTS = hashDomainLabel(defaultDomain); @@ -611,11 +640,13 @@ describe("ZNSRootRegistrar", () => { }); it("Successfully registers a domain with distrConfig and adds it to state properly", async () => { - const distrConfig = { + const distrConfig : IDistributionConfig = { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: ZERO_VALUE_FIXED_PRICE_CONFIG_BYTES, accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }; + const tokenURI = "https://example.com/817c64af"; await zns.rootRegistrar.connect(user).registerRootDomain({ @@ -689,7 +720,7 @@ describe("ZNSRootRegistrar", () => { totalPrice, expectedPrice, stakeFee, - } = getPriceObject(defaultDomain, DEFAULT_PRICE_CONFIG); + } = getPriceObject(defaultDomain, DEFAULT_CURVE_PRICE_CONFIG); await checkBalance({ token: zns.meowToken as IERC20, @@ -745,13 +776,18 @@ describe("ZNSRootRegistrar", () => { zns, domainName: defaultDomain, }); - const { price, stakeFee } = await zns.curvePricer.getPriceAndFee(ZeroHash, defaultDomain, true); + + // price returns as 0 when given config is 0, valid? + const { price, stakeFee } = await zns.curvePricer.getPriceAndFee( + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + defaultDomain, + true + ); await expect(tx).to.be.revertedWithCustomError( zns.meowToken, INSUFFICIENT_BALANCE_ERC_ERR - ) - .withArgs(user.address, 0n, price + stakeFee); + ).withArgs(user.address, 0n, price + stakeFee); }); it("Disallows creation of a duplicate domain", async () => { @@ -760,6 +796,7 @@ describe("ZNSRootRegistrar", () => { zns, domainName: defaultDomain, }); + const failTx = defaultRootRegistration({ user: deployer, zns, @@ -780,7 +817,7 @@ describe("ZNSRootRegistrar", () => { token: ethers.ZeroAddress, beneficiary: ethers.ZeroAddress, }, - }); + } as IRootDomainConfig); await expect(tx).to.not.be.reverted; }); @@ -836,8 +873,14 @@ describe("ZNSRootRegistrar", () => { }); it("Should NOT charge any tokens if price and/or stake fee is 0", async () => { - // set config on CurvePricer for the price to be 0 - await zns.curvePricer.connect(deployer).setMaxPrice(ethers.ZeroHash, "0"); + + const localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.maxPrice = 0n; + + await zns.rootRegistrar.connect(deployer).setRootPricerAndConfig( + await zns.curvePricer.getAddress(), + encodePriceConfig(localConfig), + ); const userBalanceBefore = await zns.meowToken.balanceOf(user.address); const vaultBalanceBefore = await zns.meowToken.balanceOf(zeroVault.address); @@ -853,7 +896,7 @@ describe("ZNSRootRegistrar", () => { token: ethers.ZeroAddress, beneficiary: ethers.ZeroAddress, }, - }); + } as IRootDomainConfig); const userBalanceAfter = await zns.meowToken.balanceOf(user.address); const vaultBalanceAfter = await zns.meowToken.balanceOf(zeroVault.address); @@ -1016,7 +1059,8 @@ describe("ZNSRootRegistrar", () => { // Validated staked values const { expectedPrice: expectedStaked, - } = getPriceObject(defaultDomain, DEFAULT_PRICE_CONFIG); + } = getPriceObject(defaultDomain, DEFAULT_CURVE_PRICE_CONFIG); + const { amount: staked, token } = await zns.treasury.stakedForDomain(domainHash); expect(staked).to.eq(expectedStaked); expect(token).to.eq(await zns.meowToken.getAddress()); @@ -1052,18 +1096,27 @@ describe("ZNSRootRegistrar", () => { domainName: defaultDomain, distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, - }); + } as DefaultRootRegistrationArgs); const domainHash = await getDomainHashFromEvent({ zns, user, }); - const price = await zns.curvePricer.getPrice(ethers.ZeroHash, defaultDomain, false); - const protocolFee = await zns.curvePricer.getFeeForPrice(ethers.ZeroHash, price); + const price = await zns.curvePricer.getPrice( + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + defaultDomain, + false + ); + + const protocolFee = await zns.curvePricer.getFeeForPrice( + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + price + ); const balanceBefore = await zns.meowToken.balanceOf(user.address); @@ -1083,6 +1136,7 @@ describe("ZNSRootRegistrar", () => { domainName: defaultDomain, distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: DEFAULT_FIXED_PRICER_CONFIG_BYTES, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -1101,15 +1155,21 @@ describe("ZNSRootRegistrar", () => { ); const ogPrice = BigInt(135); - await zns.fixedPricer.connect(user).setPriceConfig( + + const newConfig : IFixedPriceConfig = { + price: ogPrice, + feePercentage: BigInt(0), + }; + + const asBytes = encodePriceConfig(newConfig); + + await zns.subRegistrar.connect(user).setPricerDataForDomain( domainHash, - { - price: ogPrice, - feePercentage: BigInt(0), - isSet: true, - } + asBytes, + zns.fixedPricer.target, ); - expect(await zns.fixedPricer.getPrice(domainHash, defaultDomain, false)).to.eq(ogPrice); + + expect(await zns.fixedPricer.getPrice(asBytes, defaultDomain, false)).to.eq(ogPrice); const tokenId = BigInt( await getDomainHashFromEvent({ @@ -1171,7 +1231,7 @@ describe("ZNSRootRegistrar", () => { const { expectedPrice: expectedStaked, stakeFee: expectedStakeFee, - } = getPriceObject(defaultDomain, DEFAULT_PRICE_CONFIG); + } = getPriceObject(defaultDomain, DEFAULT_CURVE_PRICE_CONFIG); const { amount: staked, token } = await zns.treasury.stakedForDomain(domainHash); expect(staked).to.eq(expectedStaked); expect(token).to.eq(await zns.meowToken.getAddress()); @@ -1393,25 +1453,137 @@ describe("ZNSRootRegistrar", () => { }); }); - describe("#setRootPricer", () => { - it("#setRootPricer() should set the rootPricer correctly", async () => { + describe("#setRootPricerAndConfig", () => { + it("should set the rootPricer correctly", async () => { const newPricer = zns.fixedPricer.target; - await zns.rootRegistrar.connect(admin).setRootPricer(newPricer); + await zns.rootRegistrar.connect(admin).setRootPricerAndConfig( + newPricer, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, + ); expect(await zns.rootRegistrar.rootPricer()).to.eq(newPricer); // set back - await zns.rootRegistrar.connect(admin).setRootPricer(zns.curvePricer.target); + await zns.rootRegistrar.connect(admin).setRootPricerAndConfig( + zns.curvePricer.target, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + ); }); - it("#setRootPricer() should NOT let set 0x0 address as the new pricer", async () => { + it("Fails when setting 0x0 address as the new pricer", async () => { await expect( - zns.rootRegistrar.connect(admin).setRootPricer(ethers.ZeroAddress) + zns.rootRegistrar.connect(admin).setRootPricerAndConfig( + ethers.ZeroAddress, + ethers.ZeroHash + ) ).to.be.revertedWithCustomError( - zns.subRegistrar, + zns.rootRegistrar, ZERO_ADDRESS_ERR ); }); + // fails when giving an invalid config with a pricer + it("Fails when setting an invalid config with a pricer", async () => { + const invalidConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + invalidConfig.baseLength = 0n; + invalidConfig.curveMultiplier = 0n; + + const asBytes = encodePriceConfig(invalidConfig); + + await expect( + zns.rootRegistrar.connect(admin).setRootPricerAndConfig( + zns.curvePricer.target, + asBytes + ) + ).to.be.revertedWithCustomError( + zns.curvePricer, + DIVISION_BY_ZERO_ERR + ); + }); + + // fails when anyone except the admin tries to set the pricer + it("Fails when setting an invalid config with a pricer", async () => { + const invalidConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + invalidConfig.baseLength = 0n; + invalidConfig.curveMultiplier = 0n; + + const asBytes = encodePriceConfig(invalidConfig); + + await expect( + zns.rootRegistrar.connect(admin).setRootPricerAndConfig( + zns.curvePricer.target, + asBytes + ) + ).to.be.revertedWithCustomError( + zns.curvePricer, + DIVISION_BY_ZERO_ERR + ); + }); + }); + + describe("#setRootPriceConfig", () => { + it("should set the rootPricer config correctly", async () => { + // Verify the curve pricer is currently set + expect( + (await zns.rootRegistrar.rootPricer()) + ).to.eq(zns.curvePricer.target); + + const newMaxPrice = 1n; + + const localConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + localConfig.maxPrice = newMaxPrice; + + const asBytes = encodePriceConfig(localConfig); + + // It will allow a valid curve config to be set + await zns.rootRegistrar.connect(admin).setRootPriceConfig( + asBytes, + ); + + expect(await zns.rootRegistrar.rootPriceConfig()).to.eq(asBytes); + + const decoded = decodePriceConfig(await zns.rootRegistrar.rootPriceConfig()) as ICurvePriceConfig; + expect(decoded.maxPrice).to.eq(newMaxPrice); + }); + + it("Fails when setting 0x0 bytes as the new config", async () => { + await expect( + zns.rootRegistrar.connect(admin).setRootPriceConfig( + ethers.ZeroHash + )).to.be.revertedWithCustomError( + zns.curvePricer, + INVALID_CONFIG_LENGTH_ERR + ); + }); + + it("Fails when setting an invalid config with a pricer", async () => { + const invalidConfig = { ...DEFAULT_CURVE_PRICE_CONFIG }; + // Breaks the validation + invalidConfig.baseLength = 0n; + invalidConfig.curveMultiplier = 0n; + + const asBytes = encodePriceConfig(invalidConfig); + + await expect( + zns.rootRegistrar.connect(randomUser).setRootPriceConfig( + asBytes + ) + ).to.be.revertedWithCustomError( + zns.accessController, + AC_UNAUTHORIZED_ERR + ).withArgs(randomUser.address, ADMIN_ROLE); + }); + + it("Fails when setting an invalid config with a pricer", async () => { + // Trying to set a fixed pricer config for the curve pricer will fail + await expect( + zns.rootRegistrar.connect(admin).setRootPriceConfig( + DEFAULT_FIXED_PRICER_CONFIG_BYTES + ) + ).to.be.revertedWithCustomError( + zns.curvePricer, + INVALID_CONFIG_LENGTH_ERR + ); + }); }); }); @@ -1457,7 +1629,7 @@ describe("ZNSRootRegistrar", () => { const domainHash = hashDomainLabel(domainName); await zns.meowToken.connect(randomUser).approve(await zns.treasury.getAddress(), ethers.MaxUint256); - await zns.meowToken.mint(randomUser.address, DEFAULT_PRICE_CONFIG.maxPrice); + await zns.meowToken.mint(randomUser.address, DEFAULT_CURVE_PRICE_CONFIG.maxPrice); await zns.rootRegistrar.connect(randomUser).registerRootDomain({ name: domainName, @@ -1471,7 +1643,6 @@ describe("ZNSRootRegistrar", () => { }, }); - const contractCalls = [ zns.rootRegistrar.getAccessController(), zns.rootRegistrar.registry(), @@ -1481,7 +1652,7 @@ describe("ZNSRootRegistrar", () => { zns.treasury.stakedForDomain(domainHash), zns.domainToken.name(), zns.domainToken.symbol(), - zns.curvePricer.getPrice(ethers.ZeroHash, domainName, false), + zns.curvePricer.getPrice(ZERO_VALUE_CURVE_PRICE_CONFIG_BYTES, domainName, true), ]; await validateUpgrade(deployer, zns.rootRegistrar, registrar, registrarFactory, contractCalls); diff --git a/test/ZNSStringResolver.test.ts b/test/ZNSStringResolver.test.ts index 67bc891f8..f7736746a 100644 --- a/test/ZNSStringResolver.test.ts +++ b/test/ZNSStringResolver.test.ts @@ -259,6 +259,7 @@ describe("ZNSStringResolver", () => { await registrationWithSetup({ zns, + tokenOwner: operator.address, user: operator, domainLabel: curStringDomain, domainContent: ethers.ZeroAddress, @@ -280,6 +281,7 @@ describe("ZNSStringResolver", () => { await registrationWithSetup({ zns, user, + tokenOwner: user.address, domainLabel: curString, domainContent: ethers.ZeroAddress, }); @@ -308,6 +310,7 @@ describe("ZNSStringResolver", () => { await registrationWithSetup({ zns, user: deployer, + tokenOwner: deployer.address, domainLabel: curDomain, domainContent: ethers.ZeroAddress, }); @@ -419,6 +422,7 @@ describe("ZNSStringResolver", () => { await registrationWithSetup({ zns, + tokenOwner: deployer.address, user: deployer, domainLabel: curString, domainContent: ethers.ZeroAddress, diff --git a/test/ZNSSubRegistrar.test.ts b/test/ZNSSubRegistrar.test.ts index 20be77fe6..bf3765b25 100644 --- a/test/ZNSSubRegistrar.test.ts +++ b/test/ZNSSubRegistrar.test.ts @@ -13,7 +13,7 @@ import { deployZNS, distrConfigEmpty, DISTRIBUTION_LOCKED_NOT_EXIST_ERR, - fullDistrConfigEmpty, + FULL_DISTR_CONFIG_EMPTY, getPriceObject, getStakingOrProtocolFee, GOVERNOR_ROLE, @@ -25,16 +25,19 @@ import { paymentConfigEmpty, PaymentType, DEFAULT_PRECISION, - DEFAULT_PRICE_CONFIG, validateUpgrade, AC_UNAUTHORIZED_ERR, INSUFFICIENT_BALANCE_ERC_ERR, INSUFFICIENT_ALLOWANCE_ERC_ERR, ZERO_ADDRESS_ERR, - PARENT_CONFIG_NOT_SET_ERR, DOMAIN_EXISTS_ERR, SENDER_NOT_APPROVED_ERR, ZERO_PARENTHASH_ERR, + encodePriceConfig, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + DEFAULT_CURVE_PRICE_CONFIG, + decodePriceConfig, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, } from "./helpers"; import * as hre from "hardhat"; import * as ethers from "ethers"; @@ -53,7 +56,7 @@ import { } from "../typechain"; import { deployCustomDecToken } from "./helpers/deploy/mocks"; import { getProxyImplAddress } from "./helpers/utils"; - +import { ICurvePriceConfig } from "../src/deploy/missions/types"; describe("ZNSSubRegistrar", () => { let deployer : SignerWithAddress; @@ -98,7 +101,6 @@ describe("ZNSSubRegistrar", () => { deployer, governorAddresses: [deployer.address, governor.address], adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, }); // Give funds to users @@ -125,18 +127,19 @@ describe("ZNSSubRegistrar", () => { rootHash = await registrationWithSetup({ zns, user: rootOwner, + tokenOwner: rootOwner.address, domainLabel: "root", fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig(rootPriceConfig), + accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: rootPriceConfig, }, }); }); @@ -155,6 +158,7 @@ describe("ZNSSubRegistrar", () => { tokenURI: `0://tokenURI_${i + 1}`, distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: isOdd ? PaymentType.STAKE : PaymentType.DIRECT, accessType: isOdd ? AccessType.LOCKED : AccessType.OPEN, }, @@ -218,6 +222,7 @@ describe("ZNSSubRegistrar", () => { tokenURI: `0://tokenURI_${i + 1}`, distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: isOdd ? PaymentType.STAKE : PaymentType.DIRECT, accessType: isOdd ? AccessType.LOCKED : AccessType.OPEN, }, @@ -277,6 +282,7 @@ describe("ZNSSubRegistrar", () => { tokenURI: "0://tokenURI", distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.LOCKED, }, @@ -303,6 +309,7 @@ describe("ZNSSubRegistrar", () => { tokenURI: "0://tokenURI", distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.LOCKED, }, @@ -348,18 +355,19 @@ describe("ZNSSubRegistrar", () => { const specificParentHash = await registrationWithSetup({ zns, user: specificRootOwner, + tokenOwner: specificRootOwner.address, domainLabel: "specific0parent", fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig(rootPriceConfig), + accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: specificRootOwner.address, }, - priceConfig: rootPriceConfig, }, }); @@ -390,6 +398,7 @@ describe("ZNSSubRegistrar", () => { tokenURI: `uri${i}`, distrConfig: { pricerContract: ethers.ZeroAddress, + priceConfig: ethers.ZeroHash, paymentType: 0n, accessType: 0n, }, @@ -492,22 +501,15 @@ describe("ZNSSubRegistrar", () => { const newRootHash = await registrationWithSetup({ zns, user: rootOwner, + tokenOwner: rootOwner.address, domainLabel: "rootunsetfixed", setConfigs: false, fullConfig: { - distrConfig: { - accessType: AccessType.OPEN, - pricerContract: await zns.fixedPricer.getAddress(), - paymentType: PaymentType.DIRECT, - }, + distrConfig: distrConfigEmpty, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: { - price: BigInt(0), - feePercentage: BigInt(0), - }, }, }); @@ -525,8 +527,8 @@ describe("ZNSSubRegistrar", () => { }, }) ).to.be.revertedWithCustomError( - zns.curvePricer, - PARENT_CONFIG_NOT_SET_ERR + zns.subRegistrar, + DISTRIBUTION_LOCKED_NOT_EXIST_ERR ); }); @@ -536,19 +538,15 @@ describe("ZNSSubRegistrar", () => { const newRootHash = await registrationWithSetup({ zns, user: rootOwner, + tokenOwner: lvl2SubOwner.address, domainLabel: "rootunsetcurve", setConfigs: false, fullConfig: { - distrConfig: { - accessType: AccessType.OPEN, - pricerContract: await zns.curvePricer.getAddress(), - paymentType: PaymentType.DIRECT, - }, + distrConfig: distrConfigEmpty, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }); @@ -563,8 +561,8 @@ describe("ZNSSubRegistrar", () => { paymentConfig: paymentConfigEmpty, }) ).to.be.revertedWithCustomError( - zns.curvePricer, - PARENT_CONFIG_NOT_SET_ERR + zns.subRegistrar, + DISTRIBUTION_LOCKED_NOT_EXIST_ERR ); }); @@ -572,23 +570,24 @@ describe("ZNSSubRegistrar", () => { const subHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "sub", tokenURI: subTokenURI, fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ + price: ethers.parseEther("777.325"), + feePercentage: BigInt(0), + }), + accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig: { - price: ethers.parseEther("777.325"), - feePercentage: BigInt(0), - }, }, }); @@ -735,23 +734,24 @@ describe("ZNSSubRegistrar", () => { const subHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "a", tokenURI: subTokenURI, fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ + price: ethers.parseEther("777.325"), + feePercentage: BigInt(0), + }), + accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig: { - price: ethers.parseEther("777.325"), - feePercentage: BigInt(0), - }, }, }); @@ -770,10 +770,11 @@ describe("ZNSSubRegistrar", () => { const subHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "a".repeat(100000), tokenURI: subTokenURI, - fullConfig: fullDistrConfigEmpty, + fullConfig: FULL_DISTR_CONFIG_EMPTY, }); const tokenId = BigInt(subHash).toString(); @@ -843,15 +844,16 @@ describe("ZNSSubRegistrar", () => { const parentHash1 = await registrationWithSetup({ zns, user: rootOwner, + tokenOwner: rootOwner.address, domainLabel: "parentnoconfigdirect", fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig(rootPriceConfig), + accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, paymentConfig: paymentConfigEmpty, - priceConfig: rootPriceConfig, }, }); @@ -862,15 +864,16 @@ describe("ZNSSubRegistrar", () => { const parentHash2 = await registrationWithSetup({ zns, user: rootOwner, + tokenOwner: rootOwner.address, domainLabel: "parentnoconfigstake", fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, + accessType: AccessType.OPEN, paymentType: PaymentType.STAKE, }, paymentConfig: paymentConfigEmpty, - priceConfig: DEFAULT_PRICE_CONFIG, }, }); @@ -881,6 +884,7 @@ describe("ZNSSubRegistrar", () => { await expect( registrationWithSetup({ zns, + tokenOwner: lvl2SubOwner.address, user: lvl2SubOwner, parentHash: parentHash1, domainLabel: "sub1", @@ -890,6 +894,7 @@ describe("ZNSSubRegistrar", () => { await expect( registrationWithSetup({ zns, + tokenOwner: lvl2SubOwner.address, user: lvl2SubOwner, parentHash: parentHash2, domainLabel: "sub2", @@ -897,14 +902,21 @@ describe("ZNSSubRegistrar", () => { ).to.be.revertedWithCustomError(zns.treasury, NO_BENEFICIARY_ERR); // change stakeFee to 0 - await zns.curvePricer.connect(rootOwner).setFeePercentage( + const currDistrConfig = await zns.subRegistrar.distrConfigs(parentHash2); + const decodedConfig = decodePriceConfig(currDistrConfig.priceConfig); + + decodedConfig.feePercentage = BigInt(0); + + await zns.subRegistrar.connect(rootOwner).setPricerDataForDomain( parentHash2, - BigInt(0) + encodePriceConfig(decodedConfig), + currDistrConfig.pricerContract ); // try register a subdomain again const subHash = await registrationWithSetup({ zns, + tokenOwner: lvl2SubOwner.address, user: lvl2SubOwner, parentHash: parentHash2, domainLabel: "sub2", @@ -921,6 +933,11 @@ describe("ZNSSubRegistrar", () => { const fixedPrice = ethers.parseEther("1375.612"); const fixedFeePercentage = BigInt(200); + const fixedPriceConfigBytes = encodePriceConfig({ + price: fixedPrice, + feePercentage: fixedFeePercentage, + }); + before(async () => { [ deployer, @@ -942,7 +959,6 @@ describe("ZNSSubRegistrar", () => { deployer, governorAddresses: [deployer.address, governor.address], adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, }); @@ -970,6 +986,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: fixedPriceConfigBytes, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -977,7 +994,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: BigInt(0) }, }, }, { @@ -986,6 +1002,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, @@ -993,7 +1010,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, { @@ -1002,6 +1018,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -1009,7 +1026,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl3SubOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, { @@ -1018,6 +1034,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, @@ -1025,8 +1042,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl4SubOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, - }, }, { @@ -1035,6 +1050,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: fixedPriceConfigBytes, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -1042,7 +1058,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl5SubOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }, { @@ -1051,6 +1066,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, @@ -1058,7 +1074,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl6SubOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, ]; @@ -1080,13 +1095,15 @@ describe("ZNSSubRegistrar", () => { }); it("should be able to register multiple domains under multiple levels for the same owner", async () => { - const configs = [ + const configs : Array = [ { user: multiOwner, domainLabel: "multiownerdomone", + tokenOwner: multiOwner.address, fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ price: fixedPrice, feePercentage: BigInt(0) }), accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, @@ -1094,16 +1111,17 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: multiOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: BigInt(0) }, }, }, { user: multiOwner, + tokenOwner: multiOwner.address, domainLabel: "multiownerdomtwo", parentHash: regResults[0].domainHash, fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, accessType: AccessType.LOCKED, paymentType: PaymentType.STAKE, }, @@ -1111,16 +1129,17 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: zeroVault.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, { user: multiOwner, + tokenOwner: multiOwner.address, domainLabel: "multiownerdomthree", parentHash: regResults[1].domainHash, fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, accessType: AccessType.MINTLIST, paymentType: PaymentType.DIRECT, }, @@ -1128,16 +1147,17 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: multiOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, { user: multiOwner, + tokenOwner: multiOwner.address, domainLabel: "multiownerdomfour", parentHash: regResults[2].domainHash, fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ price: fixedPrice, feePercentage: fixedFeePercentage }), accessType: AccessType.OPEN, paymentType: PaymentType.STAKE, }, @@ -1145,16 +1165,17 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: zeroVault.address, }, - priceConfig: { price: fixedPrice, feePercentage: BigInt(0) }, }, }, { user: multiOwner, + tokenOwner: multiOwner.address, domainLabel: "multiownerdomfive", parentHash: regResults[3].domainHash, fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, @@ -1162,16 +1183,17 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: multiOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, { user: multiOwner, + tokenOwner: multiOwner.address, domainLabel: "multiownerdomsix", parentHash: regResults[4].domainHash, fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, accessType: AccessType.OPEN, paymentType: PaymentType.STAKE, }, @@ -1179,16 +1201,17 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: zeroVault.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, { user: multiOwner, + tokenOwner: multiOwner.address, domainLabel: "multiownerdomseven", parentHash: regResults[5].domainHash, fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ price: fixedPrice, feePercentage: fixedFeePercentage }), accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, @@ -1196,7 +1219,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: multiOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }, ]; @@ -1210,6 +1232,7 @@ describe("ZNSSubRegistrar", () => { acc : Promise>, { user, + tokenOwner, parentHash, domainLabel, fullConfig, @@ -1219,6 +1242,7 @@ describe("ZNSSubRegistrar", () => { const newHash = await registrationWithSetup({ zns, user, + tokenOwner, parentHash, domainLabel, fullConfig, @@ -1403,7 +1427,7 @@ describe("ZNSSubRegistrar", () => { }); it("should register a new 2 lvl path at lvl 3 of the existing path", async () => { - const newConfigs = [ + const newConfigs : Array = [ { user: branchLvl1Owner, domainLabel: "lvlthreenew", @@ -1411,6 +1435,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: fixedPriceConfigBytes, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -1418,7 +1443,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: branchLvl1Owner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }, { @@ -1427,6 +1451,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, @@ -1434,7 +1459,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: branchLvl2Owner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, ]; @@ -1491,10 +1515,13 @@ describe("ZNSSubRegistrar", () => { expect(parentDistrConfig.pricerContract).to.eq(await zns.curvePricer.getAddress()); // check a couple of fields from price config - const priceConfig = await zns.curvePricer.priceConfigs(lvl2Hash); + const distrConfig = await zns.subRegistrar.distrConfigs(lvl2Hash); + const priceConfig = decodePriceConfig(distrConfig.priceConfig); + const priceConfigFromDomain = decodePriceConfig(domainConfigs[1].fullConfig.distrConfig.priceConfig); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - if ("maxPrice" in domainConfigs[1].fullConfig.priceConfig!) { - expect(priceConfig.maxPrice).to.eq(domainConfigs[1].fullConfig.priceConfig.maxPrice); + if ("maxPrice" in priceConfigFromDomain) { + expect((priceConfig as ICurvePriceConfig).maxPrice).to.eq(priceConfigFromDomain.maxPrice); } // make sure the child's stake is still there @@ -1554,6 +1581,7 @@ describe("ZNSSubRegistrar", () => { const newHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash, domainLabel: domainConfigs[1].domainLabel, fullConfig: domainConfigs[1].fullConfig, @@ -1567,7 +1595,6 @@ describe("ZNSSubRegistrar", () => { lvl2Hash, ); - // someone else is taking it const newConfig = [ { user: branchLvl1Owner, @@ -1576,6 +1603,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ price: fixedPrice, feePercentage: fixedFeePercentage }), paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -1583,7 +1611,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: branchLvl1Owner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }, ]; @@ -1630,6 +1657,7 @@ describe("ZNSSubRegistrar", () => { await registrationWithSetup({ zns, user: rootOwner, + tokenOwner: rootOwner.address, parentHash: ethers.ZeroHash, domainLabel: domainConfigs[0].domainLabel, fullConfig: domainConfigs[0].fullConfig, @@ -1669,6 +1697,7 @@ describe("ZNSSubRegistrar", () => { await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: regResults[0].domainHash, domainLabel: domainConfigs[1].domainLabel, fullConfig: domainConfigs[1].fullConfig, @@ -1684,11 +1713,16 @@ describe("ZNSSubRegistrar", () => { const newHash = await registrationWithSetup({ zns, user: branchLvl1Owner, + tokenOwner: branchLvl1Owner.address, parentHash: regResults[0].domainHash, domainLabel: domainConfigs[1].domainLabel, fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ + price: fixedPrice, + feePercentage: BigInt(0), + }), paymentType: PaymentType.DIRECT, accessType: AccessType.MINTLIST, }, @@ -1696,7 +1730,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: branchLvl1Owner.address, }, - priceConfig: { price: fixedPrice, feePercentage: BigInt(0) }, }, }); @@ -1718,9 +1751,10 @@ describe("ZNSSubRegistrar", () => { const newChildHash = await registrationWithSetup({ zns, user: branchLvl2Owner, + tokenOwner: branchLvl2Owner.address, parentHash: newHash, domainLabel: "newchildddd", - fullConfig: fullDistrConfigEmpty, + fullConfig: FULL_DISTR_CONFIG_EMPTY, }); const childBalAfter = await zns.meowToken.balanceOf(branchLvl2Owner.address); @@ -1774,7 +1808,6 @@ describe("ZNSSubRegistrar", () => { deployer, governorAddresses: [deployer.address, governor.address], adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, }); @@ -1814,21 +1847,22 @@ describe("ZNSSubRegistrar", () => { rootHash = await registrationWithSetup({ zns, user: rootOwner, + tokenOwner: rootOwner.address, domainLabel: "root", fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ + price: ethers.parseEther("1375.612"), + feePercentage: BigInt(0), + }), paymentType: PaymentType.DIRECT, + accessType: AccessType.OPEN, }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: { - price: ethers.parseEther("1375.612"), - feePercentage: BigInt(0), - }, }, }); }); @@ -1848,11 +1882,13 @@ describe("ZNSSubRegistrar", () => { const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "fixedstake", fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, @@ -1860,7 +1896,6 @@ describe("ZNSSubRegistrar", () => { token: await token5.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -1884,10 +1919,11 @@ describe("ZNSSubRegistrar", () => { const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, - fullConfig: fullDistrConfigEmpty, + fullConfig: FULL_DISTR_CONFIG_EMPTY, }); const parentBalAfter = await token5.balanceOf(lvl2SubOwner.address); @@ -1925,6 +1961,7 @@ describe("ZNSSubRegistrar", () => { it("Does not charge the owner of a parent domain when they revoke a subdomain", async () => { const subdomainHash = await registrationWithSetup({ zns, + tokenOwner: rootOwner.address, user: rootOwner, parentHash: rootHash, domainLabel: "subdomain", @@ -1947,11 +1984,13 @@ describe("ZNSSubRegistrar", () => { const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "fixedstakenofee", fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), accessType: AccessType.OPEN, paymentType: PaymentType.STAKE, }, @@ -1959,7 +1998,6 @@ describe("ZNSSubRegistrar", () => { token: await token18.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -1981,6 +2019,7 @@ describe("ZNSSubRegistrar", () => { const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, @@ -2024,19 +2063,20 @@ describe("ZNSSubRegistrar", () => { const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "fixeddirectnofee", fullConfig: { distrConfig: { - paymentType: PaymentType.DIRECT, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), + paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, paymentConfig: { token: await token8.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -2058,10 +2098,11 @@ describe("ZNSSubRegistrar", () => { const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, - fullConfig: fullDistrConfigEmpty, + fullConfig: FULL_DISTR_CONFIG_EMPTY, }); const parentBalAfter = await token8.balanceOf(lvl2SubOwner.address); @@ -2102,25 +2143,25 @@ describe("ZNSSubRegistrar", () => { decimalValues.thirteen - DEFAULT_PRECISION ), feePercentage: BigInt(185), - isSet: true, }; const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "asympstake", fullConfig: { distrConfig: { - paymentType: PaymentType.STAKE, pricerContract: await zns.curvePricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), + paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, paymentConfig: { token: await token13.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -2147,10 +2188,11 @@ describe("ZNSSubRegistrar", () => { const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, - fullConfig: fullDistrConfigEmpty, + fullConfig: FULL_DISTR_CONFIG_EMPTY, }); const contractBalAfter = await token13.balanceOf(await zns.treasury.getAddress()); @@ -2189,17 +2231,18 @@ describe("ZNSSubRegistrar", () => { baseLength: BigInt(2), precisionMultiplier: BigInt(1), feePercentage: BigInt(0), - isSet: true, }; const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "curvestakenofee", fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), accessType: AccessType.OPEN, paymentType: PaymentType.STAKE, }, @@ -2207,7 +2250,6 @@ describe("ZNSSubRegistrar", () => { token: await token2.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -2229,6 +2271,7 @@ describe("ZNSSubRegistrar", () => { const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, @@ -2265,18 +2308,20 @@ describe("ZNSSubRegistrar", () => { it("CurvePricer - DirectPayment - no fee - 18 decimals", async () => { const priceConfig = { - ...DEFAULT_PRICE_CONFIG, + ...DEFAULT_CURVE_PRICE_CONFIG, feePercentage: BigInt(0), }; const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "curvedirectnofee", fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, @@ -2285,7 +2330,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -2298,6 +2342,7 @@ describe("ZNSSubRegistrar", () => { const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, @@ -2342,11 +2387,13 @@ describe("ZNSSubRegistrar", () => { const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "zeroprice", fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, @@ -2354,7 +2401,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -2366,6 +2412,7 @@ describe("ZNSSubRegistrar", () => { const label = "zeropricechild"; const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, @@ -2420,18 +2467,20 @@ describe("ZNSSubRegistrar", () => { it("CurvePricer + DirectPayment with price = 0 - should NOT perform any transfers", async () => { const priceConfig = { - ...DEFAULT_PRICE_CONFIG, + ...DEFAULT_CURVE_PRICE_CONFIG, maxPrice: BigInt(0), }; const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "zeropricead", fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, @@ -2439,7 +2488,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -2451,6 +2499,7 @@ describe("ZNSSubRegistrar", () => { const label = "zeropricechildad"; const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, @@ -2508,18 +2557,20 @@ describe("ZNSSubRegistrar", () => { it("CurvePricer + StakePayment with price = 0 - should NOT perform any transfers", async () => { const priceConfig = { - ...DEFAULT_PRICE_CONFIG, + ...DEFAULT_CURVE_PRICE_CONFIG, maxPrice: BigInt(0), }; const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "zeropriceas", fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), accessType: AccessType.OPEN, paymentType: PaymentType.STAKE, }, @@ -2527,7 +2578,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -2539,6 +2589,7 @@ describe("ZNSSubRegistrar", () => { const label = "zeropricechildas"; const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, @@ -2602,11 +2653,13 @@ describe("ZNSSubRegistrar", () => { const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "zeropricefs", fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig(priceConfig), accessType: AccessType.OPEN, paymentType: PaymentType.STAKE, }, @@ -2614,7 +2667,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig, }, }); @@ -2626,6 +2678,7 @@ describe("ZNSSubRegistrar", () => { const label = "zeropricechildfs"; const childHash = await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, @@ -2680,25 +2733,26 @@ describe("ZNSSubRegistrar", () => { it("Setting price config in incorrect decimals triggers incorrect pricing", async () => { // we will use token with 5 decimals, but set prices in 18 decimals - const priceConfigIncorrect = { + const priceConfigIncorrect : ICurvePriceConfig = { maxPrice: ethers.parseUnits("234.46", decimalValues.eighteen), curveMultiplier: BigInt(1000), maxLength: BigInt(20), baseLength: BigInt(2), precisionMultiplier: BigInt(1), feePercentage: BigInt(111), - isSet: true, }; // see `token` in paymentConfig const subdomainParentHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: rootHash, domainLabel: "incorrectparent", fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: encodePriceConfig(priceConfigIncorrect), accessType: AccessType.OPEN, paymentType: PaymentType.STAKE, }, @@ -2707,7 +2761,6 @@ describe("ZNSSubRegistrar", () => { token: await token5.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig: priceConfigIncorrect, }, }); @@ -2718,30 +2771,43 @@ describe("ZNSSubRegistrar", () => { maxPrice: ethers.parseUnits("234.46", decimalValues.five), }; + // For protocol fee calculations + const rootPriceConfig = await zns.rootRegistrar.rootPriceConfig(); + // calc prices off-chain const { expectedPrice: priceIncorrect, stakeFee: stakeFeeIncorrect, } = getPriceObject(label, priceConfigIncorrect); - const protocolFeeIncorrect = getStakingOrProtocolFee(priceIncorrect + stakeFeeIncorrect); + const protocolFeeIncorrect = getStakingOrProtocolFee( + priceIncorrect + stakeFeeIncorrect, + decodePriceConfig(rootPriceConfig).feePercentage + ); const { expectedPrice: priceCorrect, stakeFee: stakeFeeCorrect, } = getPriceObject(label, priceConfigCorrect); - const protocolFeeCorrect = getStakingOrProtocolFee(priceCorrect + stakeFeeCorrect); + const protocolFeeCorrect = getStakingOrProtocolFee( + priceCorrect + stakeFeeCorrect, + decodePriceConfig(rootPriceConfig).feePercentage + ); + + const distrConfig = await zns.subRegistrar.distrConfigs(subdomainParentHash); + + expect(distrConfig.priceConfig).to.eq(encodePriceConfig(priceConfigIncorrect)); - // get prices from SC const { price: priceFromSC, stakeFee: feeFromSC, } = await zns.curvePricer.getPriceAndFee( - subdomainParentHash, + distrConfig.priceConfig, label, true ); + const protocolFeeFromSC = await zns.curvePricer.getFeeForPrice( - ethers.ZeroHash, + rootPriceConfig, priceFromSC + feeFromSC ); @@ -2758,15 +2824,13 @@ describe("ZNSSubRegistrar", () => { BigInt(10) ** decimalValues.eighteen ); - // let's see how much a user actually paid - // we sending him 10^20 tokens await token5.connect(deployer).transfer( lvl3SubOwner.address, ethers.parseUnits("10000000000000000000", decimalValues.five) ); - // client tx approving the correct price will fail + // client tx approving the correct price will fail registration await token5.connect(lvl3SubOwner).approve( await zns.treasury.getAddress(), priceCorrect + stakeFeeCorrect + protocolFeeCorrect @@ -2792,6 +2856,7 @@ describe("ZNSSubRegistrar", () => { await registrationWithSetup({ zns, + tokenOwner: lvl3SubOwner.address, user: lvl3SubOwner, parentHash: subdomainParentHash, domainLabel: label, @@ -2807,11 +2872,13 @@ describe("ZNSSubRegistrar", () => { }); describe("Registration access", () => { - let fixedPrice : bigint; let domainConfigs : Array; let regResults : Array; + let fixedPrice : bigint; let fixedFeePercentage : bigint; + let configBytes : string; + before(async () => { [ deployer, @@ -2831,13 +2898,14 @@ describe("ZNSSubRegistrar", () => { deployer, governorAddresses: [deployer.address, governor.address], adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, }); fixedPrice = ethers.parseEther("397"); fixedFeePercentage = BigInt(200); + configBytes = encodePriceConfig({ price: fixedPrice, feePercentage: fixedFeePercentage }); + await Promise.all( [ rootOwner, @@ -2859,6 +2927,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: configBytes, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -2866,7 +2935,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }, { @@ -2875,6 +2943,7 @@ describe("ZNSSubRegistrar", () => { fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: configBytes, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -2882,7 +2951,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }, ]; @@ -2904,6 +2972,7 @@ describe("ZNSSubRegistrar", () => { const hash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, parentHash: regResults[1].domainHash, domainLabel: "ownercheck", }); @@ -3002,11 +3071,12 @@ describe("ZNSSubRegistrar", () => { domainConfigs: [ { user: lvl3SubOwner, + tokenOwner: lvl3SubOwner.address, domainLabel: "leveltwo", parentHash: regResults[1].domainHash, // when we do not specify accessType or config, it defaults to LOCKED // we can also set it as 0 specifically if setting a config - fullConfig: fullDistrConfigEmpty, + fullConfig: FULL_DISTR_CONFIG_EMPTY, }, ], }); @@ -3036,7 +3106,7 @@ describe("ZNSSubRegistrar", () => { expectedPrice, } = getPriceObject( domainLabel, - domainConfigs[1].fullConfig.priceConfig + decodePriceConfig(domainConfigs[1].fullConfig.distrConfig.priceConfig) ); const protocolFee = getStakingOrProtocolFee(expectedPrice); @@ -3077,11 +3147,16 @@ describe("ZNSSubRegistrar", () => { const parentHash = await registrationWithSetup({ zns, user: lvl3SubOwner, + tokenOwner: lvl3SubOwner.address, parentHash: regResults[1].domainHash, domainLabel: "mintlistparent", fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ + price: fixedPrice, + feePercentage: fixedFeePercentage, + }), paymentType: PaymentType.DIRECT, accessType: AccessType.MINTLIST, }, @@ -3089,7 +3164,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl3SubOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }); @@ -3104,6 +3178,7 @@ describe("ZNSSubRegistrar", () => { const hash = await registrationWithSetup({ zns, user: lvl4SubOwner, + tokenOwner: lvl4SubOwner.address, parentHash, domainLabel: "mintlisted", }); @@ -3273,8 +3348,9 @@ describe("ZNSSubRegistrar", () => { stakeFee, } = getPriceObject( label, - domainConfigs[1].fullConfig.priceConfig + decodePriceConfig(domainConfigs[1].fullConfig.distrConfig.priceConfig) ); + const paymentToParent = domainConfigs[1].fullConfig.distrConfig.paymentType === PaymentType.STAKE ? expectedPrice + stakeFee : expectedPrice; @@ -3314,9 +3390,10 @@ describe("ZNSSubRegistrar", () => { const parentHash = await registrationWithSetup({ zns, user: lvl3SubOwner, + tokenOwner: lvl3SubOwner.address, parentHash: regResults[1].domainHash, domainLabel: "parentnoconfig", - fullConfig: fullDistrConfigEmpty, // accessType is 0 when supplying empty config + fullConfig: FULL_DISTR_CONFIG_EMPTY, // accessType is 0 when supplying empty config }); await expect( @@ -3337,10 +3414,12 @@ describe("ZNSSubRegistrar", () => { }); describe("Existing subdomain ops", () => { - let fixedPrice : bigint; let domainConfigs : Array; let regResults : Array; let fixedFeePercentage : bigint; + let fixedPrice : bigint; + + let priceConfigBytes : string; before(async () => { [ @@ -3361,13 +3440,14 @@ describe("ZNSSubRegistrar", () => { deployer, governorAddresses: [deployer.address, governor.address], adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, }); fixedPrice = ethers.parseEther("397"); fixedFeePercentage = BigInt(200); + priceConfigBytes = encodePriceConfig({ price: fixedPrice, feePercentage: fixedFeePercentage }); + await Promise.all( [ rootOwner, @@ -3385,10 +3465,12 @@ describe("ZNSSubRegistrar", () => { domainConfigs = [ { user: rootOwner, + tokenOwner: rootOwner.address, domainLabel: "root", fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ price: fixedPrice, feePercentage: fixedFeePercentage }), paymentType: PaymentType.STAKE, accessType: AccessType.OPEN, }, @@ -3396,16 +3478,17 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }, { user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, domainLabel: "leveltwo", tokenURI: "http://example.com/leveltwo", fullConfig: { distrConfig: { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: priceConfigBytes, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -3413,16 +3496,17 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, }, - priceConfig: { price: fixedPrice, feePercentage: fixedFeePercentage }, }, }, { user: lvl3SubOwner, + tokenOwner: lvl3SubOwner.address, domainLabel: "lvlthree", tokenURI: "http://example.com/lvlthree", fullConfig: { distrConfig: { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }, @@ -3430,7 +3514,6 @@ describe("ZNSSubRegistrar", () => { token: await zns.meowToken.getAddress(), beneficiary: lvl3SubOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }, ]; @@ -3533,6 +3616,7 @@ describe("ZNSSubRegistrar", () => { const newConfig = { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: DEFAULT_FIXED_PRICER_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.MINTLIST, }; @@ -3577,15 +3661,18 @@ describe("ZNSSubRegistrar", () => { it("should NOT allow to set distribution config for a non-authorized account", async () => { const domainHash = regResults[1].domainHash; - const newConfig = { pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.MINTLIST, }; await expect( - zns.subRegistrar.connect(deployer).setDistributionConfigForDomain(domainHash, newConfig) + zns.subRegistrar.connect(deployer).setDistributionConfigForDomain( + domainHash, + newConfig + ) ).to.be.revertedWithCustomError( zns.subRegistrar, NOT_AUTHORIZED_ERR @@ -3597,12 +3684,16 @@ describe("ZNSSubRegistrar", () => { const newConfig = { pricerContract: ethers.ZeroAddress, + priceConfig: DEFAULT_FIXED_PRICER_CONFIG_BYTES, paymentType: PaymentType.STAKE, accessType: AccessType.MINTLIST, }; await expect( - zns.subRegistrar.connect(lvl3SubOwner).setDistributionConfigForDomain(domainHash, newConfig) + zns.subRegistrar.connect(lvl3SubOwner).setDistributionConfigForDomain( + domainHash, + newConfig + ) ).to.be.revertedWithCustomError( zns.subRegistrar, ZERO_ADDRESS_ERR @@ -3610,15 +3701,16 @@ describe("ZNSSubRegistrar", () => { }); }); - describe("#setPricerContractForDomain()", () => { + describe("#setPricerDataForDomain()", () => { it("should re-set pricer contract for an existing subdomain", async () => { const domainHash = regResults[2].domainHash; const pricerContractBefore = await zns.subRegistrar.distrConfigs(domainHash); expect(pricerContractBefore.pricerContract).to.eq(domainConfigs[2].fullConfig.distrConfig.pricerContract); - await zns.subRegistrar.connect(lvl3SubOwner).setPricerContractForDomain( + await zns.subRegistrar.connect(lvl3SubOwner).setPricerDataForDomain( domainHash, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, await zns.curvePricer.getAddress(), ); @@ -3626,8 +3718,9 @@ describe("ZNSSubRegistrar", () => { expect(pricerContractAfter.pricerContract).to.eq(await zns.curvePricer.getAddress()); // reset it back - await zns.subRegistrar.connect(lvl3SubOwner).setPricerContractForDomain( + await zns.subRegistrar.connect(lvl3SubOwner).setPricerDataForDomain( domainHash, + domainConfigs[2].fullConfig.distrConfig.priceConfig, domainConfigs[2].fullConfig.distrConfig.pricerContract, ); }); @@ -3636,8 +3729,9 @@ describe("ZNSSubRegistrar", () => { const domainHash = regResults[2].domainHash; await expect( - zns.subRegistrar.connect(lvl2SubOwner).setPricerContractForDomain( + zns.subRegistrar.connect(lvl2SubOwner).setPricerDataForDomain( domainHash, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, await zns.curvePricer.getAddress() ) ).to.be.revertedWithCustomError( @@ -3650,7 +3744,11 @@ describe("ZNSSubRegistrar", () => { const domainHash = regResults[2].domainHash; await expect( - zns.subRegistrar.connect(lvl3SubOwner).setPricerContractForDomain(domainHash, ethers.ZeroAddress) + zns.subRegistrar.connect(lvl3SubOwner).setPricerDataForDomain( + domainHash, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, + ethers.ZeroAddress + ) ).to.be.revertedWithCustomError( zns.subRegistrar, ZERO_ADDRESS_ERR @@ -3854,7 +3952,6 @@ describe("ZNSSubRegistrar", () => { deployer, governorAddresses: [deployer.address, governor.address], adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, }); @@ -3873,21 +3970,22 @@ describe("ZNSSubRegistrar", () => { rootHash = await registrationWithSetup({ zns, user: rootOwner, + tokenOwner: rootOwner.address, domainLabel: "root", fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ + price: fixedPrice, + feePercentage: BigInt(0), + }), + accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: { - price: fixedPrice, - feePercentage: BigInt(0), - }, }, }); }); @@ -3950,18 +4048,19 @@ describe("ZNSSubRegistrar", () => { const domainHash = await registrationWithSetup({ zns, user: lvl2SubOwner, + tokenOwner: lvl2SubOwner.address, domainLabel, parentHash: rootHash, fullConfig: { distrConfig: { - accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: encodePriceConfig({ + price: fixedPrice, + feePercentage: BigInt(0), + }), + accessType: AccessType.OPEN, paymentType: PaymentType.DIRECT, }, - priceConfig: { - price: fixedPrice, - feePercentage: BigInt(0), - }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: lvl2SubOwner.address, @@ -3971,6 +4070,8 @@ describe("ZNSSubRegistrar", () => { await zns.subRegistrar.setRootRegistrar(lvl2SubOwner.address); + const rootDistrConfig = await zns.subRegistrar.distrConfigs(rootHash); + const contractCalls = [ zns.subRegistrar.getAccessController(), zns.subRegistrar.registry(), @@ -3979,7 +4080,7 @@ describe("ZNSSubRegistrar", () => { zns.treasury.stakedForDomain(domainHash), zns.domainToken.name(), zns.domainToken.symbol(), - zns.fixedPricer.getPrice(rootHash, domainLabel, false), + zns.fixedPricer.getPrice(rootDistrConfig.priceConfig, domainLabel, false), ]; await validateUpgrade(deployer, zns.subRegistrar, registrar, registrarFactory, contractCalls); @@ -4012,6 +4113,7 @@ describe("ZNSSubRegistrar", () => { const subConfigToSet = { accessType: AccessType.MINTLIST, pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.STAKE, newAddress: lvl2SubOwner.address, newUint: BigInt(1912171236), @@ -4036,12 +4138,14 @@ describe("ZNSSubRegistrar", () => { const rootConfigAfter = await zns.subRegistrar.distrConfigs(rootHash); expect(rootConfigAfter.accessType).to.eq(rootConfigBefore.accessType); expect(rootConfigAfter.pricerContract).to.eq(rootConfigBefore.pricerContract); + expect(rootConfigAfter.priceConfig).to.eq(rootConfigBefore.priceConfig); expect(rootConfigAfter.paymentType).to.eq(rootConfigBefore.paymentType); - expect(rootConfigAfter.length).to.eq(3); + expect(rootConfigAfter.length).to.eq(4); const updatedStructConfig = { accessType: AccessType.OPEN, pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: DEFAULT_FIXED_PRICER_CONFIG_BYTES, paymentType: PaymentType.DIRECT, newAddress: lvl2SubOwner.address, newUint: BigInt(123), diff --git a/test/ZNSTreasury.test.ts b/test/ZNSTreasury.test.ts index 9ff904ac7..9d1332978 100644 --- a/test/ZNSTreasury.test.ts +++ b/test/ZNSTreasury.test.ts @@ -7,10 +7,11 @@ import { getPriceObject, NO_BENEFICIARY_ERR, INITIALIZED_ERR, - DEFAULT_PRICE_CONFIG, + DEFAULT_CURVE_PRICE_CONFIG, validateUpgrade, NOT_AUTHORIZED_ERR, getStakingOrProtocolFee, AC_UNAUTHORIZED_ERR, ZERO_ADDRESS_ERR, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, } from "./helpers"; import { DeployZNSParams, IZNSContractsLocal } from "./helpers/types"; import * as ethers from "ethers"; @@ -156,11 +157,13 @@ describe("ZNSTreasury", () => { const zeroVaultBalanceBeforeStake = await zns.meowToken.balanceOf(zeroVault.address); const expectedStake = await zns.curvePricer.getPrice( - ethers.ZeroHash, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, domainName, false ); - const fee = await zns.curvePricer.getFeeForPrice(ethers.ZeroHash, expectedStake); + + const rootConfig = await zns.rootRegistrar.rootPriceConfig(); + const fee = await zns.curvePricer.getFeeForPrice(rootConfig, expectedStake); await zns.treasury.connect(mockRegistrar).stakeForDomain( ethers.ZeroHash, @@ -204,7 +207,7 @@ describe("ZNSTreasury", () => { stakeFee: protocolFee, } = getPriceObject( domainName, - DEFAULT_PRICE_CONFIG + DEFAULT_CURVE_PRICE_CONFIG ); const tx = zns.treasury.connect(mockRegistrar).stakeForDomain( @@ -626,7 +629,7 @@ describe("ZNSTreasury", () => { const newLabel = "world"; const newHash = hashSubdomainName(newLabel); - const { expectedPrice, stakeFee } = getPriceObject(newLabel, DEFAULT_PRICE_CONFIG); + const { expectedPrice, stakeFee } = getPriceObject(newLabel, DEFAULT_CURVE_PRICE_CONFIG); await zns.treasury.connect(mockRegistrar).stakeForDomain( ethers.ZeroHash, diff --git a/test/gas/TransactionGasCosts.test.ts b/test/gas/TransactionGasCosts.test.ts index 18653f89c..c94adf275 100644 --- a/test/gas/TransactionGasCosts.test.ts +++ b/test/gas/TransactionGasCosts.test.ts @@ -1,6 +1,11 @@ import { IDistributionConfig, IZNSContractsLocal } from "../helpers/types"; import * as hre from "hardhat"; -import { AccessType, DEFAULT_TOKEN_URI, deployZNS, PaymentType, DEFAULT_PRICE_CONFIG } from "../helpers"; +import { AccessType, + DEFAULT_TOKEN_URI, deployZNS, + PaymentType, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, +} from "../helpers"; import * as ethers from "ethers"; import { registrationWithSetup } from "../helpers/register-setup"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; @@ -38,14 +43,17 @@ describe("Transaction Gas Costs Test", () => { deployer, governorAddresses: [deployer.address, governor.address], adminAddresses: [admin.address], - priceConfig: DEFAULT_PRICE_CONFIG, zeroVaultAddress: zeroVault.address, }); - await zns.curvePricer.connect(deployer).setPriceConfig(ethers.ZeroHash, DEFAULT_PRICE_CONFIG); + await zns.rootRegistrar.connect(deployer).setRootPricerAndConfig( + await zns.curvePricer.getAddress(), + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + ); config = { pricerContract: await zns.fixedPricer.getAddress(), + priceConfig: DEFAULT_FIXED_PRICER_CONFIG_BYTES, paymentType: PaymentType.DIRECT, accessType: AccessType.OPEN, }; @@ -58,23 +66,25 @@ describe("Transaction Gas Costs Test", () => { ].map(async ({ address }) => zns.meowToken.mint(address, ethers.parseEther("1000000"))) ); + await zns.meowToken.connect(rootOwner).approve(await zns.treasury.getAddress(), ethers.MaxUint256); rootHashDirect = await registrationWithSetup({ zns, + tokenOwner: hre.ethers.ZeroAddress, user: rootOwner, domainLabel: "rootdirect", fullConfig: { distrConfig: { accessType: AccessType.OPEN, pricerContract: await zns.curvePricer.getAddress(), + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, paymentType: PaymentType.DIRECT, }, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: rootOwner.address, }, - priceConfig: DEFAULT_PRICE_CONFIG, }, }); diff --git a/test/gas/gas-costs.json b/test/gas/gas-costs.json index bdc944958..4d2d44c5f 100644 --- a/test/gas/gas-costs.json +++ b/test/gas/gas-costs.json @@ -1,4 +1,4 @@ { - "Root Domain Price": "463728", - "Subdomain Price": "458612" + "Root Domain Price": "536588", + "Subdomain Price": "542676" } \ No newline at end of file diff --git a/test/helpers/constants.ts b/test/helpers/constants.ts index b339f8d32..c49c14e49 100644 --- a/test/helpers/constants.ts +++ b/test/helpers/constants.ts @@ -1,5 +1,8 @@ import { ethers } from "hardhat"; import { ICurvePriceConfig } from "../../src/deploy/missions/types"; +import { IDistributionConfig, IFixedPriceConfig, IFullDistributionConfig, IPaymentConfig } from "./types"; +import { encodePriceConfig } from "./pricing"; +import { ZeroHash } from "ethers"; export const DEFAULT_RESOLVER_TYPE = "address"; export const ZNS_DOMAIN_TOKEN_NAME = "ZERO ID"; @@ -27,16 +30,47 @@ export const PaymentType = { STAKE: 1n, }; -export const DEFAULT_PRICE_CONFIG : ICurvePriceConfig = { +export const DEFAULT_CURVE_PRICE_CONFIG : ICurvePriceConfig = { maxPrice: ethers.parseEther("25000"), curveMultiplier: BigInt("1000"), maxLength: BigInt(50), baseLength: BigInt(4), precisionMultiplier: DEFAULT_PRECISION_MULTIPLIER, feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, - isSet: true, }; +export const DEFAULT_CURVE_PRICE_CONFIG_BYTES = encodePriceConfig(DEFAULT_CURVE_PRICE_CONFIG); + +export const ZERO_VALUE_CURVE_PRICE_CONFIG_BYTES = ZeroHash + + ZeroHash.slice(2) + + ZeroHash.slice(2) + + ZeroHash.slice(2) + + ZeroHash.slice(2) + + ZeroHash.slice(2); + + +export const DEFAULT_FIXED_PRICE_CONFIG : IFixedPriceConfig = { + price: ethers.parseEther("50"), + feePercentage: DEFAULT_PROTOCOL_FEE_PERCENT, +}; + +export const FULL_DISTR_CONFIG_EMPTY : IFullDistributionConfig = { + distrConfig: { + pricerContract: ethers.ZeroAddress, + paymentType: PaymentType.DIRECT, + accessType: AccessType.LOCKED, + priceConfig: ZeroHash, + }, + paymentConfig: { + token: ethers.ZeroAddress, + beneficiary: ethers.ZeroAddress, + }, +}; + +export const ZERO_VALUE_FIXED_PRICE_CONFIG_BYTES = ZeroHash + ZeroHash.slice(2); + +export const DEFAULT_FIXED_PRICER_CONFIG_BYTES = encodePriceConfig(DEFAULT_FIXED_PRICE_CONFIG); + export const curvePriceConfigEmpty : ICurvePriceConfig = { maxPrice: BigInt(0), curveMultiplier: BigInt(0), @@ -44,24 +78,23 @@ export const curvePriceConfigEmpty : ICurvePriceConfig = { baseLength: BigInt(0), precisionMultiplier: BigInt(0), feePercentage: BigInt(0), - isSet: true, }; -export const paymentConfigEmpty = { +export const paymentConfigEmpty : IPaymentConfig = { token: ethers.ZeroAddress, beneficiary: ethers.ZeroAddress, }; -export const distrConfigEmpty = { +export const distrConfigEmpty : IDistributionConfig = { pricerContract: ethers.ZeroAddress, paymentType: PaymentType.DIRECT, accessType: AccessType.LOCKED, + priceConfig: ZeroHash, }; -export const fullDistrConfigEmpty = { - distrConfig: distrConfigEmpty, - priceConfig: undefined, +export const fullConfigEmpty : IFullDistributionConfig = { paymentConfig: paymentConfigEmpty, + distrConfig: distrConfigEmpty, }; export const implSlotErc1967 = "0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc"; diff --git a/test/helpers/deploy-helpers.ts b/test/helpers/deploy-helpers.ts index 7f933bc3a..13ef40202 100644 --- a/test/helpers/deploy-helpers.ts +++ b/test/helpers/deploy-helpers.ts @@ -6,7 +6,7 @@ import { IZNSCampaignConfig, IZNSContracts } from "../../src/deploy/campaign/typ import { ethers } from "ethers"; import { IDistributionConfig, IZNSContractsLocal } from "./types"; import { expect } from "chai"; -import { hashDomainLabel, paymentConfigEmpty } from "."; +import { DEFAULT_CURVE_PRICE_CONFIG_BYTES, hashDomainLabel, paymentConfigEmpty } from "."; import { ICurvePriceConfig } from "../../src/deploy/missions/types"; import { TLogger } from "@zero-tech/zdc"; @@ -59,22 +59,26 @@ export const getPriceBulk = async ( parent = ethers.ZeroHash; } - // temp, can do one call `getPRiceAndFee` but debugging where failure occurs - const price = await zns.curvePricer.getPrice(parent, domain, true); - const stakeFee = await zns.curvePricer.getFeeForPrice(parent, price); + let config : string; - // TODO fix this to be one if statement - if (parentHashes) { - const protocolFee = await zns.curvePricer.getFeeForPrice(ethers.ZeroHash, price + stakeFee); + if (parent === ethers.ZeroHash) { + // roots + config = await zns.rootRegistrar.rootPriceConfig(); + const price = await zns.curvePricer.getPrice(config, domain, true); + + const protocolFee = await zns.curvePricer.getFeeForPrice(config, price); + prices.push(price + protocolFee); - prices.push(price + stakeFee + protocolFee); } else { - const protocolFee = await zns.curvePricer.getFeeForPrice(ethers.ZeroHash, price); + // subs + config = await (await zns.subRegistrar.distrConfigs(parent)).priceConfig; + const price = await zns.curvePricer.getPrice(config, domain, true); - prices.push(price + protocolFee); + const stakeFee = await zns.curvePricer.getFeeForPrice(config, price); + const protocolFee = await zns.curvePricer.getFeeForPrice(config, price + stakeFee); + prices.push(price + stakeFee + protocolFee); } - index++; } @@ -86,7 +90,7 @@ export const registerRootDomainBulk = async ( domains : Array, config : IZNSCampaignConfig, tokenUri : string, - distConfig : IDistributionConfig, + distrConfig : IDistributionConfig, priceConfig : ICurvePriceConfig, zns : IZNSContractsLocal | IZNSContracts, logger : TLogger, @@ -99,13 +103,14 @@ export const registerRootDomainBulk = async ( name: domain, domainAddress: config.zeroVaultAddress, tokenURI: `${tokenUri}${index}`, - tokenOwner: hre.ethers.ZeroAddress, - distrConfig: distConfig, + tokenOwner: signers[index], + distrConfig, paymentConfig: { token: await zns.meowToken.getAddress(), beneficiary: config.zeroVaultAddress, }, }); + logger.info("Deploy transaction submitted, waiting..."); if (hre.network.name !== "hardhat") { await tx.wait(3); @@ -113,15 +118,17 @@ export const registerRootDomainBulk = async ( } const balanceAfter = await zns.meowToken.balanceOf(signers[index].address); - const [price, protocolFee] = await zns.curvePricer.getPriceAndFee(ethers.ZeroHash, domain, true); + const [price, protocolFee] = await zns.curvePricer.getPriceAndFee(distrConfig.priceConfig, domain, true); expect(balanceAfter).to.be.eq(balanceBefore - price - protocolFee); const domainHash = hashDomainLabel(domain); expect(await zns.registry.exists(domainHash)).to.be.true; - // TODO figure out if we want to do this on prod? - // To mint subdomains from this domain we must first set the price config and the payment config - await zns.curvePricer.connect(signers[index]).setPriceConfig(domainHash, priceConfig); + await zns.subRegistrar.connect(signers[index]).setPricerDataForDomain( + domainHash, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + zns.curvePricer.target + ); index++; } @@ -165,8 +172,10 @@ export const registerSubdomainBulk = async ( if (signers[index].address === owner) { expect(balanceAfter).to.be.eq(balanceBefore); } else { - const [price, stakeFee] = await zns.curvePricer.getPriceAndFee(parents[index], subdomain, true); - const protocolFee = await zns.curvePricer.getFeeForPrice(ethers.ZeroHash, price + stakeFee); + const parentConfig = (await zns.subRegistrar.distrConfigs(parents[index])).priceConfig; + + const [price, stakeFee] = await zns.curvePricer.getPriceAndFee(parentConfig, subdomain, true); + const protocolFee = await zns.curvePricer.getFeeForPrice(DEFAULT_CURVE_PRICE_CONFIG_BYTES, price + stakeFee); expect(balanceAfter).to.be.eq(balanceBefore - price - stakeFee - protocolFee); } diff --git a/test/helpers/deploy/deploy-zns.ts b/test/helpers/deploy/deploy-zns.ts index 5349f4770..46d40085a 100644 --- a/test/helpers/deploy/deploy-zns.ts +++ b/test/helpers/deploy/deploy-zns.ts @@ -30,7 +30,6 @@ import { domainTokenName, erc1967ProxyName, fixedPricerName, - DEFAULT_PRICE_CONFIG, curvePricerName, registrarName, registryName, @@ -41,10 +40,10 @@ import { ZNS_DOMAIN_TOKEN_SYMBOL, DEFAULT_ROYALTY_FRACTION, DEFAULT_RESOLVER_TYPE, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, } from "../constants"; import { DOMAIN_TOKEN_ROLE, REGISTRAR_ROLE } from "../../../src/deploy/constants"; import { getProxyImplAddress } from "../utils"; -import { ICurvePriceConfig } from "../../../src/deploy/missions/types"; import { meowTokenName, meowTokenSymbol } from "../../../src/deploy/missions/contracts"; @@ -187,8 +186,10 @@ export const deployMeowToken = async ( address: tokenAddress, }); - console.log(`${meowTokenMockName} deployed at: - implementation: ${tokenAddress}`); + console.log( + `${meowTokenMockName} deployed at: + implementation: ${tokenAddress}` + ); } // Mint 10,000 ZERO for self @@ -233,9 +234,11 @@ export const deployAddressResolver = async ( address: impl, }); - console.log(`ZNSAddressResolver deployed at: - proxy: ${proxyAddress} - implementation: ${impl}`); + console.log( + `ZNSAddressResolver deployed at: + proxy: ${proxyAddress} + implementation: ${impl}` + ); } return resolver as unknown as ZNSAddressResolver; @@ -243,51 +246,25 @@ export const deployAddressResolver = async ( export const deployCurvePricer = async ({ deployer, - accessControllerAddress, - registryAddress, - priceConfig, isTenderlyRun, } : { deployer : SignerWithAddress; - accessControllerAddress : string; - registryAddress : string; - priceConfig : ICurvePriceConfig; isTenderlyRun : boolean; }) : Promise => { const curveFactory = new ZNSCurvePricer__factory(deployer); - - const curvePricer = await upgrades.deployProxy( - curveFactory, - [ - accessControllerAddress, - registryAddress, - priceConfig, - ], - { - kind: "uups", - } - ); + const curvePricer = await curveFactory.deploy(); await curvePricer.waitForDeployment(); - const proxyAddress = await curvePricer.getAddress(); + const address = await curvePricer.getAddress(); if (isTenderlyRun) { - await hre.tenderly.verify({ - name: erc1967ProxyName, - address: proxyAddress, - }); - - const impl = await getProxyImplAddress(proxyAddress); - await hre.tenderly.verify({ name: curvePricerName, - address: impl, + address, }); - console.log(`${curvePricerName} deployed at: - proxy: ${proxyAddress} - implementation: ${impl}`); + console.log(`${curvePricerName} deployed at: ${address}`); } return curvePricer as unknown as ZNSCurvePricer; @@ -359,6 +336,7 @@ export const deployRootRegistrar = async ( await accessController.getAddress(), config.registryAddress, config.curvePricerAddress, + config.curvePriceConfig, config.treasuryAddress, config.domainTokenAddress, ], @@ -395,46 +373,29 @@ export const deployRootRegistrar = async ( export const deployFixedPricer = async ({ deployer, - acAddress, - regAddress, isTenderlyRun = false, } : { deployer : SignerWithAddress; - acAddress : string; - regAddress : string; isTenderlyRun ?: boolean; }) => { const pricerFactory = new ZNSFixedPricer__factory(deployer); - const fixedPricer = await upgrades.deployProxy( - pricerFactory, - [ - acAddress, - regAddress, - ], - { - kind: "uups", - } - ); + const fixedPricer = await pricerFactory.deploy(); await fixedPricer.waitForDeployment(); - const proxyAddress = await fixedPricer.getAddress(); - if (isTenderlyRun) { - await hre.tenderly.verify({ - name: erc1967ProxyName, - address: proxyAddress, - }); - - const impl = await getProxyImplAddress(proxyAddress); + const address = await fixedPricer.getAddress(); + if (isTenderlyRun) { await hre.tenderly.verify({ name: fixedPricerName, - address: impl, + address, }); - console.log(`${fixedPricerName} deployed at: - proxy: ${proxyAddress} - implementation: ${impl}`); + console.log( + `${fixedPricerName} deployed at: + proxy: ${address} + implementation: ${address}` + ); } return fixedPricer as unknown as ZNSFixedPricer; @@ -508,7 +469,6 @@ export const deployZNS = async ({ deployer, governorAddresses, adminAddresses, - priceConfig = DEFAULT_PRICE_CONFIG, zeroVaultAddress = deployer.address, isTenderlyRun = false, } : DeployZNSParams) : Promise => { @@ -559,9 +519,6 @@ export const deployZNS = async ({ const curvePricer = await deployCurvePricer({ deployer, - accessControllerAddress: await accessController.getAddress(), - registryAddress: await registry.getAddress(), - priceConfig, isTenderlyRun, }); @@ -575,9 +532,10 @@ export const deployZNS = async ({ }); const config : RegistrarConfig = { - treasuryAddress: await treasury.getAddress(), registryAddress: await registry.getAddress(), curvePricerAddress: await curvePricer.getAddress(), + curvePriceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, + treasuryAddress: await treasury.getAddress(), domainTokenAddress: await domainToken.getAddress(), }; @@ -590,8 +548,6 @@ export const deployZNS = async ({ const fixedPricer = await deployFixedPricer({ deployer, - acAddress: await accessController.getAddress(), - regAddress: await registry.getAddress(), isTenderlyRun, }); diff --git a/test/helpers/errors.ts b/test/helpers/errors.ts index b37dd3935..f9314eef9 100644 --- a/test/helpers/errors.ts +++ b/test/helpers/errors.ts @@ -7,6 +7,8 @@ export const ERC721_NOT_APPROVED_ERR = "ERC721InsufficientApproval"; export const ALREADY_FULL_OWNER_ERR = "AlreadyFullOwner"; export const CANNOT_BURN_TOKEN_ERR = "CannotBurnToken"; +export const HARDHAT_INFER_ERR = "Transaction reverted and Hardhat couldn't infer the reason"; + // AccessControl export const AC_UNAUTHORIZED_ERR = "AccessControlUnauthorizedAccount"; @@ -21,8 +23,6 @@ export const DOMAIN_EXISTS_ERR = "DomainAlreadyExists"; export const NOT_AUTHORIZED_ERR = "NotAuthorizedForDomain"; export const NOT_FULL_OWNER_ERR = "NotFullDomainOwner"; -// IZNSPricer.sol -export const PARENT_CONFIG_NOT_SET_ERR = "ParentPriceConfigNotSet"; export const FEE_TOO_LARGE_ERR = "FeePercentageValueTooLarge"; // ZNSCurvePricer.sol @@ -31,6 +31,12 @@ export const INVALID_PRICE_CONFIG_ERR = "InvalidConfigCausingPriceSpikes"; export const INVALID_BASE_OR_MAX_LENGTH_ERR = "MaxLengthSmallerThanBaseLength"; export const DIVISION_BY_ZERO_ERR = "DivisionByZero"; +// ZNSRootRegistrar.sol +export const NOT_OWNER_OF_ERR = "NotTheOwnerOf"; + +// IZNSFixedPRicer.sol +export const INVALID_CONFIG_LENGTH_ERR = "IncorrectPriceConfigLength"; + // Subdomain Registrar // eslint-disable-next-line max-len export const DISTRIBUTION_LOCKED_NOT_EXIST_ERR = "ParentLockedOrDoesntExist"; diff --git a/test/helpers/flows/registration.ts b/test/helpers/flows/registration.ts index 6c767a3dd..e5d72b1cc 100644 --- a/test/helpers/flows/registration.ts +++ b/test/helpers/flows/registration.ts @@ -2,12 +2,24 @@ import { IDomainConfigForTest, IPathRegResult, IZNSContractsLocal } from "../types"; import { registrationWithSetup } from "../register-setup"; import { ethers } from "ethers"; -import { getPriceObject, getStakingOrProtocolFee } from "../pricing"; +import { decodePriceConfig, getPriceObject, getStakingOrProtocolFee } from "../pricing"; import { expect } from "chai"; import { getDomainRegisteredEvents } from "../events"; import { PaymentType } from "../constants"; import { getTokenContract } from "../tokens"; +import { ICurvePriceConfig } from "../../../src/deploy/missions/types"; +interface DomainObj { + domainHash : string; + userBalanceBefore : bigint; + userBalanceAfter : bigint; + parentBalanceBefore : bigint; + parentBalanceAfter : bigint; + treasuryBalanceBefore : bigint; + treasuryBalanceAfter : bigint; + zeroVaultBalanceBefore : bigint; + zeroVaultBalanceAfter : bigint; +} export const registerDomainPath = async ({ zns, @@ -15,7 +27,7 @@ export const registerDomainPath = async ({ } : { zns : IZNSContractsLocal; domainConfigs : Array; -}) => domainConfigs.reduce( +}) : Promise> => domainConfigs.reduce( async ( acc : Promise>, config, @@ -73,7 +85,7 @@ export const registerDomainPath = async ({ const treasuryBalanceAfter = await paymentTokenContract.balanceOf(await zns.treasury.getAddress()); const zeroVaultBalanceAfter = await paymentTokenContract.balanceOf(zns.zeroVaultAddress); - const domainObj = { + const domainObj : DomainObj = { domainHash, userBalanceBefore, userBalanceAfter, @@ -118,14 +130,16 @@ export const validatePathRegistration = async ({ parentHashFound = !!regResults[idx - 1] ? regResults[idx - 1].domainHash : ethers.ZeroHash; } + const rootConfigBytes = await zns.rootRegistrar.rootPriceConfig(); + const { - maxPrice: curveMaxPrice, + maxPrice, curveMultiplier, - maxLength: curveMaxLength, - baseLength: curveBaseLength, - precisionMultiplier: curvePrecisionMultiplier, - feePercentage: curveFeePercentage, - } = await zns.curvePricer.priceConfigs(ethers.ZeroHash); + maxLength, + baseLength, + precisionMultiplier, + feePercentage, + } = decodePriceConfig(rootConfigBytes) as ICurvePriceConfig; let expParentBalDiff; let expTreasuryBalDiff; @@ -136,12 +150,12 @@ export const validatePathRegistration = async ({ } = getPriceObject( domainLabel, { - maxPrice: curveMaxPrice, + maxPrice, curveMultiplier, - maxLength: curveMaxLength, - baseLength: curveBaseLength, - precisionMultiplier: curvePrecisionMultiplier, - feePercentage: curveFeePercentage, + maxLength, + baseLength, + precisionMultiplier, + feePercentage, }, )); expParentBalDiff = BigInt(0); @@ -157,8 +171,10 @@ export const validatePathRegistration = async ({ ({ price: expectedPrice, fee: stakeFee, - } = await zns.fixedPricer.getPriceAndFee(parentHashFound, domainLabel, false)); + } = await zns.fixedPricer.getPriceAndFee(config.priceConfig, domainLabel, false)); } else { + const priceConfig = await (await zns.subRegistrar.distrConfigs(parentHashFound)).priceConfig; + const { maxPrice, curveMultiplier, @@ -166,7 +182,7 @@ export const validatePathRegistration = async ({ baseLength, precisionMultiplier, feePercentage, - } = await zns.curvePricer.priceConfigs(parentHashFound); + } = decodePriceConfig(priceConfig) as ICurvePriceConfig; ({ expectedPrice, @@ -198,7 +214,7 @@ export const validatePathRegistration = async ({ const protocolFee = getStakingOrProtocolFee( expectedPrice + stakeFee, - curveFeePercentage + feePercentage ); const { diff --git a/test/helpers/pricing.ts b/test/helpers/pricing.ts index 4395af5a7..7be560417 100644 --- a/test/helpers/pricing.ts +++ b/test/helpers/pricing.ts @@ -1,7 +1,13 @@ -import { DEFAULT_PERCENTAGE_BASIS, DEFAULT_PRICE_CONFIG } from "./constants"; +import { + DEFAULT_PERCENTAGE_BASIS, + DEFAULT_CURVE_PRICE_CONFIG, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, + DEFAULT_FIXED_PRICE_CONFIG, +} from "./constants"; import { IFixedPriceConfig } from "./types"; import { ICurvePriceConfig } from "../../src/deploy/missions/types"; +import { ethers } from "ethers"; /** * Get the domain name price base on its length when given @@ -13,7 +19,7 @@ import { ICurvePriceConfig } from "../../src/deploy/missions/types"; */ export const getCurvePrice = ( name : string, - priceConfig = DEFAULT_PRICE_CONFIG, + priceConfig = DEFAULT_CURVE_PRICE_CONFIG, ) : bigint => { // Get price configuration for contract const { @@ -30,7 +36,7 @@ export const getCurvePrice = ( return maxPrice; } - if (BigInt(name.length) > maxLength) { + if (length > maxLength) { length = maxLength; } @@ -44,7 +50,7 @@ export const getCurvePrice = ( export const getStakingOrProtocolFee = ( forAmount : bigint, - feePercentage : bigint = DEFAULT_PRICE_CONFIG.feePercentage, + feePercentage : bigint = DEFAULT_CURVE_PRICE_CONFIG.feePercentage, ) => forAmount * feePercentage / DEFAULT_PERCENTAGE_BASIS; /** @@ -57,7 +63,7 @@ export const getStakingOrProtocolFee = ( */ export const getPriceObject = ( name : string, - priceConfig : Partial | Partial = DEFAULT_PRICE_CONFIG, + priceConfig : Partial | Partial = DEFAULT_CURVE_PRICE_CONFIG, ) : { totalPrice : bigint; expectedPrice : bigint; @@ -65,6 +71,7 @@ export const getPriceObject = ( } => { let expectedPrice; const configLen = Object.keys(priceConfig).length; + if (configLen === 7 || configLen === 6) { expectedPrice = getCurvePrice(name, priceConfig as ICurvePriceConfig); } else if (configLen === 3 || configLen === 2) { @@ -84,4 +91,118 @@ export const getPriceObject = ( expectedPrice, stakeFee, }; -}; \ No newline at end of file +}; + +export const createEncodeFixedPriceConfig = (config : Partial) => { + const createdConfig : IFixedPriceConfig = { + price: config.price ?? DEFAULT_FIXED_PRICE_CONFIG.price, + feePercentage: config.feePercentage ?? DEFAULT_CURVE_PRICE_CONFIG.feePercentage, + }; + + return encodeFixedPriceConfig(createdConfig); +}; + +export const createEncodeCurvePriceConfig = (config : Partial) => { + const createdConfig : ICurvePriceConfig = { + maxPrice: config.maxPrice ?? DEFAULT_CURVE_PRICE_CONFIG.maxPrice, + maxLength: config.maxLength ?? DEFAULT_CURVE_PRICE_CONFIG.maxLength, + baseLength: config.baseLength ?? DEFAULT_CURVE_PRICE_CONFIG.baseLength, + curveMultiplier: config.curveMultiplier ?? DEFAULT_CURVE_PRICE_CONFIG.curveMultiplier, + precisionMultiplier: config.precisionMultiplier ?? DEFAULT_CURVE_PRICE_CONFIG.precisionMultiplier, + feePercentage: config.feePercentage ?? DEFAULT_CURVE_PRICE_CONFIG.feePercentage, + }; + + return encodeCurvePriceConfig(createdConfig); +}; + +export const encodePriceConfig = ( + config : ICurvePriceConfig | IFixedPriceConfig, +) => { + if (Object.keys(config).length > 2) { + return encodeCurvePriceConfig(config as ICurvePriceConfig); + } else { + return encodeFixedPriceConfig(config as IFixedPriceConfig); + } +}; + +export const decodePriceConfig = ( + config : string +) => { + if (config.length > DEFAULT_FIXED_PRICER_CONFIG_BYTES.length) { + return decodeCurvePriceConfig(config); + } else { + return decodeFixedPriceConfig(config); + } +}; + +const encodeCurvePriceConfig = (config : ICurvePriceConfig) => ethers.AbiCoder.defaultAbiCoder().encode( + [ + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + ], + [ + config.maxPrice, + config.curveMultiplier, + config.maxLength, + config.baseLength, + config.precisionMultiplier, + config.feePercentage, + ] +); + +const encodeFixedPriceConfig = (config : IFixedPriceConfig) => ethers.AbiCoder.defaultAbiCoder().encode( + [ + "uint256", + "uint256", + ], + [ + config.price, + config.feePercentage, + ] +); + +const decodeCurvePriceConfig = (config : string) => { + const results = ethers.AbiCoder.defaultAbiCoder().decode( + [ + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + "uint256", + ], + config + ); + + const toReturn : ICurvePriceConfig = { + maxPrice: results[0], + curveMultiplier: results[1], + maxLength: results[2], + baseLength: results[3], + precisionMultiplier: results[4], + feePercentage: results[5], + }; + + return toReturn; +}; + +const decodeFixedPriceConfig = (config : string) => { + const results = ethers.AbiCoder.defaultAbiCoder().decode( + [ + "uint256", + "uint256", + ], + config + ); + + const toReturn : IFixedPriceConfig = { + price: results[0], + feePercentage: results[1], + }; + + return toReturn; +}; diff --git a/test/helpers/register-setup.ts b/test/helpers/register-setup.ts index 927445cec..ae555f522 100644 --- a/test/helpers/register-setup.ts +++ b/test/helpers/register-setup.ts @@ -1,14 +1,15 @@ import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { IDistributionConfig, - IFixedPriceConfig, - IFullDistributionConfig, IPaymentConfig, IZNSContractsLocal, + IRegisterWithSetupArgs, + IPaymentConfig, + IZNSContractsLocal, + DefaultRootRegistrationArgs, } from "./types"; import { ContractTransactionReceipt, ethers } from "ethers"; import { getDomainHashFromEvent } from "./events"; -import { distrConfigEmpty, fullDistrConfigEmpty, DEFAULT_TOKEN_URI, paymentConfigEmpty } from "./constants"; +import { distrConfigEmpty, fullConfigEmpty, DEFAULT_TOKEN_URI, paymentConfigEmpty } from "./constants"; import { getTokenContract } from "./tokens"; -import { ICurvePriceConfig } from "../../src/deploy/missions/types"; import { expect } from "chai"; import { IZNSContracts } from "../../src/deploy/campaign/types"; @@ -24,16 +25,7 @@ export const defaultRootRegistration = async ({ tokenURI = DEFAULT_TOKEN_URI, distrConfig = distrConfigEmpty, paymentConfig = paymentConfigEmpty, -} : { - user : SignerWithAddress; - zns : IZNSContractsLocal | IZNSContracts; - domainName : string; - tokenOwner ?: string; - domainContent ?: string; - tokenURI ?: string; - distrConfig ?: IDistributionConfig; - paymentConfig ?: IPaymentConfig; -}) : Promise => { +} : DefaultRootRegistrationArgs) : Promise => { const supplyBefore = await zns.domainToken.totalSupply(); const tx = await zns.rootRegistrar.connect(user).registerRootDomain({ @@ -62,19 +54,23 @@ export const approveForParent = async ({ user : SignerWithAddress; domainLabel : string; }) => { - const { pricerContract } = await zns.subRegistrar.distrConfigs(parentHash); + const { pricerContract, priceConfig } = await zns.subRegistrar.distrConfigs(parentHash); + let price = BigInt(0); let parentFee = BigInt(0); + if (pricerContract === await zns.curvePricer.getAddress()) { - [price, parentFee] = await zns.curvePricer.getPriceAndFee(parentHash, domainLabel, false); + [price, parentFee] = await zns.curvePricer.getPriceAndFee(priceConfig, domainLabel, false); } else if (pricerContract === await zns.fixedPricer.getAddress()) { - [price, parentFee] = await zns.fixedPricer.getPriceAndFee(parentHash, domainLabel, false); + [price, parentFee] = await zns.fixedPricer.getPriceAndFee(priceConfig, domainLabel, false); } const { token: tokenAddress } = await zns.treasury.paymentConfigs(parentHash); const tokenContract = getTokenContract(tokenAddress, user); - const protocolFee = await zns.curvePricer.getFeeForPrice(ethers.ZeroHash, price + parentFee); + + const rootPriceConfig = await zns.rootRegistrar.rootPriceConfig(); + const protocolFee = await zns.curvePricer.getFeeForPrice(rootPriceConfig, price + parentFee); const toApprove = price + parentFee + protocolFee; return tokenContract.connect(user).approve(await zns.treasury.getAddress(), toApprove); @@ -133,19 +129,9 @@ export const registrationWithSetup = async ({ tokenOwner, domainContent = user.address, tokenURI = DEFAULT_TOKEN_URI, - fullConfig = fullDistrConfigEmpty, + fullConfig = fullConfigEmpty, setConfigs = true, -} : { - zns : IZNSContractsLocal | IZNSContracts; - user : SignerWithAddress; - parentHash ?: string; - domainLabel : string; - tokenOwner ?: string; - domainContent ?: string; - tokenURI ?: string; - fullConfig ?: IFullDistributionConfig; - setConfigs ?: boolean; -}) => { +} : IRegisterWithSetupArgs) => { const hasConfig = !!fullConfig; const distrConfig = hasConfig ? fullConfig.distrConfig @@ -186,27 +172,22 @@ export const registrationWithSetup = async ({ const domainHash = await getDomainHashFromEvent({ zns, user, - tokenOwner, }); if (!hasConfig) return domainHash; // set up prices - if (fullConfig.distrConfig.pricerContract === await zns.fixedPricer.getAddress() && setConfigs) { - await zns.fixedPricer.connect(user).setPriceConfig( + if (fullConfig.distrConfig.pricerContract === zns.fixedPricer.target && setConfigs) { + await zns.subRegistrar.connect(user).setPricerDataForDomain( domainHash, - { - ...fullConfig.priceConfig as IFixedPriceConfig, - isSet: true, - }, + fullConfig.distrConfig.priceConfig, + zns.fixedPricer.target ); } else if (fullConfig.distrConfig.pricerContract === await zns.curvePricer.getAddress() && setConfigs) { - await zns.curvePricer.connect(user).setPriceConfig( + await zns.subRegistrar.connect(user).setPricerDataForDomain( domainHash, - { - ...fullConfig.priceConfig as ICurvePriceConfig, - isSet: true, - }, + fullConfig.distrConfig.priceConfig, + zns.curvePricer.target ); } diff --git a/test/helpers/types.ts b/test/helpers/types.ts index 8cdd54344..3d7b8eb28 100644 --- a/test/helpers/types.ts +++ b/test/helpers/types.ts @@ -28,6 +28,8 @@ import { } from "../../typechain"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { ICurvePriceConfig } from "../../src/deploy/missions/types"; +import { Addressable } from "ethers"; +import { IZNSContracts } from "../../src/deploy/campaign/types"; export type Maybe = T | undefined; @@ -43,6 +45,7 @@ string & { token : string; beneficiary : string; } | ICurvePriceConfig | IFixedPriceConfig +| IDistributionConfig >; export type ZNSContractMockFactory = @@ -73,9 +76,10 @@ export interface IFixedPriceConfig { } export interface RegistrarConfig { - treasuryAddress : string; registryAddress : string; curvePricerAddress : string; + curvePriceConfig : string; + treasuryAddress : string; domainTokenAddress : string; } @@ -97,14 +101,14 @@ export interface DeployZNSParams { deployer : SignerWithAddress; governorAddresses : Array; adminAddresses : Array; - priceConfig ?: ICurvePriceConfig; registrationFeePerc ?: bigint; zeroVaultAddress ?: string; isTenderlyRun ?: boolean; } export interface IDistributionConfig { - pricerContract : string; + pricerContract : string | Addressable; + priceConfig : string; paymentType : bigint; accessType : bigint; } @@ -115,20 +119,50 @@ export interface IPaymentConfig { } export interface IFullDistributionConfig { - paymentConfig : IPaymentConfig; distrConfig : IDistributionConfig; - priceConfig : ICurvePriceConfig | IFixedPriceConfig | undefined; + paymentConfig : IPaymentConfig; } -export interface IDomainConfigForTest { +export interface CreateConfigArgs { + user : SignerWithAddress; + tokenOwner ?: string; + domainLabel ?: string; + parentHash ?: string; + distrConfig ?: Partial; + paymentConfig ?: Partial; +} + +interface ConfigArgsBase { user : SignerWithAddress; domainLabel : string; + tokenOwner ?: string; domainContent ?: string; parentHash ?: string; + tokenURI ?: string; +} + +export interface IDomainConfigForTest extends ConfigArgsBase { fullConfig : IFullDistributionConfig; +} + +export interface IRegisterWithSetupArgs extends ConfigArgsBase { + zns : IZNSContractsLocal | IZNSContracts; + fullConfig ?: IFullDistributionConfig; + setConfigs ?: boolean; +} + +export interface DefaultRootRegistrationArgs { + user : SignerWithAddress; + zns : IZNSContractsLocal | IZNSContracts; + domainName : string; + tokenOwner ?: string; + domainContent ?: string; tokenURI ?: string; + distrConfig ?: IDistributionConfig; + paymentConfig ?: IPaymentConfig; } + export interface IPathRegResult { domainHash : string; userBalanceBefore : bigint; @@ -141,7 +175,7 @@ export interface IPathRegResult { zeroVaultBalanceAfter : bigint; } -export interface IRootdomainConfig { +export interface IRootDomainConfig { name : string; domainAddress : string; tokenOwner : string; diff --git a/test/helpers/utils.ts b/test/helpers/utils.ts index 50f12059c..8be5e77e2 100644 --- a/test/helpers/utils.ts +++ b/test/helpers/utils.ts @@ -1,6 +1,24 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import { ethers, upgrades } from "hardhat"; -import { implSlotErc1967 } from "./constants"; +import { + AccessType, + DEFAULT_CURVE_PRICE_CONFIG_BYTES, + DEFAULT_FIXED_PRICER_CONFIG_BYTES, + distrConfigEmpty, + implSlotErc1967, + paymentConfigEmpty, + PaymentType, +} from "./constants"; +import { + CreateConfigArgs, + IDistributionConfig, + IDomainConfigForTest, + IFullDistributionConfig, + IPaymentConfig, + IZNSContractsLocal, +} from "./types"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; +import { HardhatRuntimeEnvironment } from "hardhat/types"; export const getProxyImplAddress = async (proxyAddress : string) => { let impl; @@ -31,4 +49,181 @@ export const getRandomString = (length : number) => { } return result; -}; \ No newline at end of file +}; + +const createConfig = async ( + zns : IZNSContractsLocal, + args : CreateConfigArgs +) : Promise => { + let distrConfigToUse : IDistributionConfig; + let paymentConfigToUse : IPaymentConfig; + + if (args.distrConfig) { + // Option variables are only used only if not specified specified + const paymentTypeOption = Math.random() < 0.5 ? PaymentType.DIRECT : PaymentType.STAKE; + + let accessTypeOption; + const accessTypeSwitch = Math.random(); + + if (accessTypeSwitch < 0.3333) { + accessTypeOption = AccessType.OPEN; + } else if (accessTypeSwitch < 0.6666) { + accessTypeOption = AccessType.LOCKED; + } else { + accessTypeOption = AccessType.MINTLIST; + } + + distrConfigToUse = { + pricerContract: args.distrConfig.pricerContract ? args.distrConfig.pricerContract : zns.curvePricer.target, + priceConfig: args.distrConfig.priceConfig ? args.distrConfig.priceConfig : DEFAULT_CURVE_PRICE_CONFIG_BYTES, + paymentType: args.distrConfig.paymentType ? args.distrConfig.paymentType : paymentTypeOption, + accessType: args.distrConfig.accessType ? args.distrConfig.accessType : accessTypeOption, + }; + + // Be sure we always set the contract and config to be the matching pair + if ( + ( + distrConfigToUse.pricerContract === zns.curvePricer.target + && distrConfigToUse.priceConfig.length !== DEFAULT_CURVE_PRICE_CONFIG_BYTES.length + ) || ( + distrConfigToUse.pricerContract === zns.fixedPricer.target + && distrConfigToUse.priceConfig.length !== DEFAULT_FIXED_PRICER_CONFIG_BYTES.length + ) + ) { + throw Error("Mismatch in distribution config: price config given does not match the price contract"); + } + } else { + distrConfigToUse = distrConfigEmpty; + } + + if (args.paymentConfig) { + paymentConfigToUse = { + token: args.paymentConfig.token ? args.paymentConfig.token : await zns.meowToken.getAddress(), + beneficiary: args.paymentConfig.beneficiary ? args.paymentConfig.beneficiary : args.user.address, + }; + } else { + // Only set default payment config if distrConfig is not empty + if (distrConfigToUse !== distrConfigEmpty) { + paymentConfigToUse = { + token: await zns.meowToken.getAddress(), + beneficiary: args.user.address, + }; + } else { + paymentConfigToUse = paymentConfigEmpty; + } + } + + const createdConfig : IDomainConfigForTest = { + user: args.user, + domainLabel: args.domainLabel!, // we know it is set at this point + tokenOwner: args.user.address, + parentHash: args.parentHash ?? ethers.ZeroHash, + fullConfig: { + distrConfig: distrConfigToUse, + paymentConfig: paymentConfigToUse, + }, + }; + + return createdConfig; +}; + +export class Utils { + hre : HardhatRuntimeEnvironment; + zns : IZNSContractsLocal; + + constructor ( + hre : HardhatRuntimeEnvironment, + zns : IZNSContractsLocal + ) { + this.hre = hre; + this.zns = zns; + } + + // Create a domain config for testing + async createConfig ( + args : CreateConfigArgs + ) : Promise { + return createConfig( + this.zns, + args + ); + } + + async createConfigs ( + args : Array + ) : Promise> { + const configs = []; + + for (const arg of args) { + // For variance in length, if one is not specified + // we create a random one of a random length + if (!arg.domainLabel) { + arg.domainLabel = await this.createLabel(Math.floor(Math.random() * 16) + 1); + } + + configs.push(await createConfig( + this.zns, + arg + // user, + // arg.tokenOwner ?? arg.user.address, + // arg.domainLabel ?? this.createLabel(), + // arg.parentHash ?? this.hre.ethers.ZeroHash, + // arg.distrConfig, + // arg.paymentConfig + )); + } + + return configs; + } + + async getDefaultFullConfigFixed (user : SignerWithAddress){ + return { + distrConfig: { + pricerContract: this.zns.fixedPricer.target, + priceConfig: DEFAULT_FIXED_PRICER_CONFIG_BYTES, + paymentType: PaymentType.DIRECT, + accessType: AccessType.OPEN, + }, + paymentConfig: { + token: this.zns.meowToken.target, + beneficiary: user.address, + }, + }; + } + + async getDefaultFullConfigCurve (user : SignerWithAddress) { + return { + distrConfig: { + pricerContract: this.zns.curvePricer.target, + priceConfig: DEFAULT_CURVE_PRICE_CONFIG_BYTES, + paymentType: PaymentType.DIRECT, + accessType: AccessType.OPEN, + }, + paymentConfig: { + token: this.zns.meowToken.target, + beneficiary: user.address, + }, + } as IFullDistributionConfig; + } + + // Create a random label of default length 10 + // Specify a length to override this value + async createLabel ( + length ?: number + ) : Promise { + let result = ""; + const chars = "abcdefghijklmnopqrstuvwxyz0123456789"; + + // If no length specified use 16 as a default + for (let i = 0; i < (length ?? 16); i++) { + result += chars.charAt(Math.floor(Math.random() * chars.length)); + } + + if (await this.zns.registry.exists(result)) { + // If the label already exists, recursively call to create a new one + return this.createLabel(length); + } + + return result; + } +} diff --git a/yarn.lock b/yarn.lock index 3027a3bd6..ad7f5581f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10969,4 +10969,4 @@ yocto-queue@^1.0.0: zksync-web3@^0.14.3: version "0.14.4" resolved "https://registry.yarnpkg.com/zksync-web3/-/zksync-web3-0.14.4.tgz#0b70a7e1a9d45cc57c0971736079185746d46b1f" - integrity sha512-kYehMD/S6Uhe1g434UnaMN+sBr9nQm23Ywn0EUP5BfQCsbjcr3ORuS68PosZw8xUTu3pac7G6YMSnNHk+fwzvg== + integrity sha512-kYehMD/S6Uhe1g434UnaMN+sBr9nQm23Ywn0EUP5BfQCsbjcr3ORuS68PosZw8xUTu3pac7G6YMSnNHk+fwzvg== \ No newline at end of file