Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ docker*.tgz

# We don't ever use the generated manifests
.openzeppelin
/.vscode
34 changes: 12 additions & 22 deletions contracts/price/IZNSCurvePricer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,14 @@ interface IZNSCurvePricer is ICurvePriceConfig, IZNSPricer {
* @notice Reverted when multiplier passed by the domain owner
* is equal to 0 or more than 10^18, which is too large.
*/
error InvalidMultiplierPassed(uint256 multiplier);

/**
* @notice Reverted when `priceConfig` set by the owner does not result in a proper asymptotic curve
* and one of it's incorrect values causes the price spike at maxLength, meaning that the price
* for a domain label shorter than `baseLength` (the one before `minPrice`) becomes higher than `minPrice`.
*/
error InvalidConfigCausingPriceSpikes(
bytes32 configsDomainHash,
uint256 minPrice,
uint256 previousToMinPrice
);
error InvalidPrecisionMultiplierPassed(bytes32 domainHash);

/**
* @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 `minPrice` is set in `CurvePriceConfig`
* @param price The new minPrice value
*/
event MinPriceSet(bytes32 domainHash, uint256 price);

/**
* @notice Emitted when the `baseLength` is set in `CurvePriceConfig`
* @param length The new baseLength value
Expand All @@ -60,18 +43,25 @@ interface IZNSCurvePricer is ICurvePriceConfig, IZNSPricer {
*/
event FeePercentageSet(bytes32 domainHash, uint256 feePercentage);

/**
* @notice Emitted when the `curveMultiplier` is set in state
* @param curveMultiplier The new curveMultiplier value
*/
event CurveMultiplierSet(bytes32 domainHash, uint256 curveMultiplier);


/**
* @notice Emitted when the full `CurvePriceConfig` is set in state
* @param maxPrice The new `maxPrice` value
* @param minPrice The new `minPrice` 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
*/
event PriceConfigSet(
bytes32 domainHash,
uint256 maxPrice,
uint256 minPrice,
uint256 curveMultiplier,
uint256 maxLength,
uint256 baseLength,
uint256 precisionMultiplier,
Expand Down Expand Up @@ -111,12 +101,12 @@ interface IZNSCurvePricer is ICurvePriceConfig, IZNSPricer {

function setMaxPrice(bytes32 domainHash, uint256 maxPrice) external;

function setMinPrice(bytes32 domainHash, uint256 minPrice) external;

function setBaseLength(bytes32 domainHash, uint256 length) external;

function setMaxLength(bytes32 domainHash, uint256 length) external;

function setCurveMultiplier(bytes32 domainHash, uint256 curveMultiplier) external;

function setPrecisionMultiplier(bytes32 domainHash, uint256 multiplier) external;

function setFeePercentage(bytes32 domainHash, uint256 feePercentage) external;
Expand Down
217 changes: 136 additions & 81 deletions contracts/price/ZNSCurvePricer.sol

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions contracts/registrar/IZNSSubRegistrar.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { IZNSPricer } from "../types/IZNSPricer.sol";
*/
interface IZNSSubRegistrar is IDistributionConfig {
/**
* @notice Reverted when someone other than parent owner is trying to buy
a subdomain under the parent that is locked\
* @notice Reverted when someone other than parent owner is trying to buy a subdomain
* under the parent that is locked
* or when the parent provided does not exist.
*/
error ParentLockedOrDoesntExist(bytes32 parentHash);
Expand Down
10 changes: 5 additions & 5 deletions contracts/types/ICurvePriceConfig.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ pragma solidity 0.8.26;
* @dev **`CurvePriceConfig` struct properties:**
*
* - `maxPrice` (uint256): Maximum price for a domain returned at <= `baseLength`
* - `minPrice` (uint256): Minimum price for a domain returned at > `maxLength`
* - `maxLength` (uint256): Maximum length of a domain name. If the name is longer - we return the `minPrice`
* - `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.
Expand All @@ -25,12 +25,12 @@ interface ICurvePriceConfig {
*/
uint256 maxPrice;
/**
* @notice Minimum price for a domain returned at > `maxLength`
* @notice Multiplier which we use to bend a curve of price on interval from `baseLength` to `maxLength`.
*/
uint256 minPrice;
uint256 curveMultiplier;
/**
* @notice Maximum length of a domain name. If the name is longer than this
* value we return the `minPrice`
* value we return the price that was at the `maxLength`
*/
uint256 maxLength;
/**
Expand Down
10 changes: 10 additions & 0 deletions contracts/types/IZNSPricer.sol
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ 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
Expand Down
40 changes: 21 additions & 19 deletions src/deploy/campaign/environments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
DEFAULT_DECIMALS,
DECAULT_PRECISION,
DEFAULT_PRICE_CONFIG,
getCurvePrice,
NO_MOCK_PROD_ERR,
STAKING_TOKEN_ERR,
INVALID_CURVE_ERR,
Expand Down Expand Up @@ -109,11 +108,12 @@ export const getConfig = async ({
domainToken: {
name: process.env.DOMAIN_TOKEN_NAME ? process.env.DOMAIN_TOKEN_NAME : ZNS_DOMAIN_TOKEN_NAME,
symbol: process.env.DOMAIN_TOKEN_SYMBOL ? process.env.DOMAIN_TOKEN_SYMBOL : ZNS_DOMAIN_TOKEN_SYMBOL,
defaultRoyaltyReceiver: royaltyReceiver,
defaultRoyaltyReceiver: royaltyReceiver!,
defaultRoyaltyFraction: royaltyFraction,
},
rootPriceConfig: priceConfig,
zeroVaultAddress: zeroVaultAddressConf,
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
zeroVaultAddress: zeroVaultAddressConf!,
mockMeowToken: process.env.MOCK_MEOW_TOKEN === "true",
stakingTokenAddress: process.env.STAKING_TOKEN_ADDRESS!,
postDeploy: {
Expand Down Expand Up @@ -211,10 +211,10 @@ const getValidateRootPriceConfig = () => {
? ethers.parseEther(process.env.MAX_PRICE)
: DEFAULT_PRICE_CONFIG.maxPrice;

const minPrice =
process.env.MIN_PRICE
? ethers.parseEther(process.env.MIN_PRICE)
: DEFAULT_PRICE_CONFIG.minPrice;
const curveMultiplier =
process.env.curveMultiplier
? process.env.curveMultiplier
: DEFAULT_PRICE_CONFIG.curveMultiplier;

const maxLength =
process.env.MAX_LENGTH
Expand All @@ -237,15 +237,15 @@ const getValidateRootPriceConfig = () => {

const priceConfig : ICurvePriceConfig = {
maxPrice,
minPrice,
curveMultiplier: BigInt(curveMultiplier),
maxLength,
baseLength,
precisionMultiplier,
feePercentage,
isSet: true,
};

requires(validatePrice(priceConfig), INVALID_CURVE_ERR);
validateConfig(priceConfig);

return priceConfig;
};
Expand All @@ -256,14 +256,16 @@ const requires = (condition : boolean, message : string) => {
}
};

// No price spike before `minPrice` kicks in at `maxLength`
const validatePrice = (config : ICurvePriceConfig) => {
const strA = "a".repeat(Number(config.maxLength));
const strB = "b".repeat(Number(config.maxLength + 1n));

const priceA = getCurvePrice(strA, config);
const priceB = getCurvePrice(strB, config);

// if B > A, then the price spike is invalid
return (priceB <= priceA);
const validateConfig = (config : ICurvePriceConfig) => {
const PERCENTAGE_BASIS = 10000n;

if (
(config.curveMultiplier === 0n && config.baseLength === 0n) ||
(config.maxLength < config.baseLength) ||
((config.maxLength < config.baseLength) || config.maxLength === 0n) ||
(config.curveMultiplier === 0n || config.curveMultiplier > 10n**18n) ||
(config.feePercentage > PERCENTAGE_BASIS)
) {
requires(false, INVALID_CURVE_ERR);
}
};
2 changes: 1 addition & 1 deletion src/deploy/missions/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

export interface ICurvePriceConfig {
maxPrice : bigint;
minPrice : bigint;
curveMultiplier : bigint;
maxLength : bigint;
baseLength : bigint;
precisionMultiplier : bigint;
Expand Down
23 changes: 0 additions & 23 deletions test/DeployCampaignInt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import {
INVALID_ENV_ERR,
NO_MOCK_PROD_ERR,
STAKING_TOKEN_ERR,
INVALID_CURVE_ERR,
MONGO_URI_ERR,
} from "./helpers";
import {
Expand Down Expand Up @@ -796,28 +795,6 @@ describe("Deploy Campaign Test", () => {
}
});

it("Fails to validate if invalid curve for pricing", async () => {
process.env.MOCK_MEOW_TOKEN = "false";
process.env.STAKING_TOKEN_ADDRESS = MeowMainnet.address;
process.env.BASE_LENGTH = "3";
process.env.MAX_LENGTH = "5";
process.env.MAX_PRICE = "0";
process.env.MIN_PRICE = ethers.parseEther("3").toString();

try {
await getConfig({
env: "prod",
deployer: deployAdmin,
zeroVaultAddress: zeroVault.address,
governors: [deployAdmin.address, governor.address],
admins: [deployAdmin.address, admin.address],
});
/* eslint-disable @typescript-eslint/no-explicit-any */
} catch (e : any) {
expect(e.message).includes(INVALID_CURVE_ERR);
}
});

it("Fails to validate if no mongo uri or local URI in prod", async () => {
process.env.MOCK_MEOW_TOKEN = "false";
process.env.STAKING_TOKEN_ADDRESS = MeowMainnet.address;
Expand Down
Loading