@zoralabs/coins 0.9.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/.turbo/turbo-build.log +131 -114
  2. package/CHANGELOG.md +40 -0
  3. package/abis/BaseCoin.json +26 -118
  4. package/abis/BaseTest.json +47 -0
  5. package/abis/Coin.json +171 -63
  6. package/abis/CoinDopplerMultiCurve.json +38 -0
  7. package/abis/CoinRewardsV4.json +54 -0
  8. package/abis/CoinTest.json +53 -20
  9. package/abis/CoinUniV4Test.json +1053 -0
  10. package/abis/CoinV4.json +234 -211
  11. package/abis/DeployScript.json +47 -0
  12. package/abis/DeployedCoinVersionLookup.json +21 -0
  13. package/abis/DeployedCoinVersionLookupTest.json +716 -0
  14. package/abis/DifferentNamespaceVersionLookup.json +39 -0
  15. package/abis/DopplerUniswapV3Test.json +49 -93
  16. package/abis/ERC20.json +310 -0
  17. package/abis/FactoryTest.json +85 -7
  18. package/abis/FeeEstimatorHook.json +1528 -0
  19. package/abis/HooksDeployment.json +23 -0
  20. package/abis/HooksTest.json +47 -0
  21. package/abis/ICoin.json +40 -71
  22. package/abis/ICoinV3.json +879 -0
  23. package/abis/ICoinV4.json +915 -0
  24. package/abis/IDeployedCoinVersionLookup.json +21 -0
  25. package/abis/IERC721.json +36 -36
  26. package/abis/IHasPoolKey.json +42 -0
  27. package/abis/IHasRewardsRecipients.json +54 -0
  28. package/abis/IHasSwapPath.json +60 -0
  29. package/abis/IMsgSender.json +15 -0
  30. package/abis/IPoolConfigEncoding.json +46 -0
  31. package/abis/ISwapPathRouter.json +92 -0
  32. package/abis/IUniversalRouter.json +61 -0
  33. package/abis/IUnlockCallback.json +21 -0
  34. package/abis/IV4Quoter.json +310 -0
  35. package/abis/IZoraFactory.json +191 -11
  36. package/abis/IZoraV4CoinHook.json +348 -4
  37. package/abis/MockERC20.json +21 -0
  38. package/abis/MultiOwnableTest.json +47 -0
  39. package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
  40. package/abis/PrintUpgradeCommand.json +9 -0
  41. package/abis/ProxyShim.json +24 -0
  42. package/abis/StateLibrary.json +80 -0
  43. package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
  44. package/abis/TestV4Swap.json +9 -0
  45. package/abis/UpgradeCoinImpl.json +47 -0
  46. package/abis/UpgradesTest.json +67 -0
  47. package/abis/Vm.json +1482 -111
  48. package/abis/VmSafe.json +856 -32
  49. package/abis/ZoraFactoryImpl.json +339 -1
  50. package/abis/ZoraV4CoinHook.json +455 -5
  51. package/addresses/8453.json +8 -4
  52. package/addresses/84532.json +8 -4
  53. package/dist/index.cjs +1920 -169
  54. package/dist/index.cjs.map +1 -1
  55. package/dist/index.js +1916 -169
  56. package/dist/index.js.map +1 -1
  57. package/dist/wagmiGenerated.d.ts +2599 -183
  58. package/dist/wagmiGenerated.d.ts.map +1 -1
  59. package/package/wagmiGenerated.ts +1928 -165
  60. package/package.json +8 -3
  61. package/remappings.txt +6 -1
  62. package/script/CoinsDeployerBase.sol +74 -11
  63. package/script/DeployDevFactory.s.sol +21 -0
  64. package/script/PrintUpgradeCommand.s.sol +13 -0
  65. package/script/Simulate.s.sol +1 -10
  66. package/script/TestBackingCoinSwap.s.sol +146 -0
  67. package/script/TestV4Swap.s.sol +136 -0
  68. package/script/UpgradeFactoryImpl.s.sol +1 -1
  69. package/src/BaseCoin.sol +176 -0
  70. package/src/Coin.sol +87 -202
  71. package/src/CoinV4.sol +121 -0
  72. package/src/ZoraFactoryImpl.sol +208 -36
  73. package/src/hooks/ZoraV4CoinHook.sol +195 -0
  74. package/src/hooks/{BaseCoinDeployHook.sol → deployment/BaseCoinDeployHook.sol} +3 -3
  75. package/src/hooks/{BuySupplyWithSwapRouterHook.sol → deployment/BuySupplyWithSwapRouterHook.sol} +7 -5
  76. package/src/interfaces/ICoin.sol +31 -39
  77. package/src/interfaces/ICoinV3.sol +71 -0
  78. package/src/interfaces/ICoinV4.sol +69 -0
  79. package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
  80. package/src/interfaces/IMsgSender.sol +9 -0
  81. package/src/interfaces/IPoolConfigEncoding.sol +14 -0
  82. package/src/interfaces/ISwapPathRouter.sol +14 -0
  83. package/src/interfaces/IZoraFactory.sol +65 -27
  84. package/src/interfaces/IZoraV4CoinHook.sol +116 -0
  85. package/src/libs/CoinCommon.sol +15 -0
  86. package/src/libs/CoinConfigurationVersions.sol +116 -1
  87. package/src/libs/CoinConstants.sol +5 -0
  88. package/src/libs/CoinDopplerMultiCurve.sol +134 -0
  89. package/src/libs/CoinDopplerUniV3.sol +19 -171
  90. package/src/libs/CoinRewards.sol +195 -0
  91. package/src/libs/CoinRewardsV4.sol +180 -0
  92. package/src/libs/CoinSetup.sol +57 -0
  93. package/src/libs/CoinSetupV3.sol +6 -67
  94. package/src/libs/DopplerMath.sol +156 -0
  95. package/src/libs/HooksDeployment.sol +84 -0
  96. package/src/libs/MarketConstants.sol +4 -0
  97. package/src/libs/PoolStateReader.sol +22 -0
  98. package/src/libs/UniV3BuySell.sol +74 -292
  99. package/src/libs/UniV4SwapHelper.sol +65 -0
  100. package/src/libs/UniV4SwapToCurrency.sol +109 -0
  101. package/src/libs/V4Liquidity.sol +129 -0
  102. package/src/types/PoolConfiguration.sol +15 -0
  103. package/src/utils/DeployedCoinVersionLookup.sol +52 -0
  104. package/src/version/ContractVersionBase.sol +1 -1
  105. package/test/Coin.t.sol +78 -88
  106. package/test/CoinDopplerUniV3.t.sol +32 -171
  107. package/test/CoinUniV4.t.sol +752 -0
  108. package/test/{Hooks.t.sol → DeploymentHooks.t.sol} +2 -6
  109. package/test/Factory.t.sol +80 -47
  110. package/test/MultiOwnable.t.sol +6 -3
  111. package/test/Upgrades.t.sol +6 -5
  112. package/test/mocks/MockERC20.sol +12 -0
  113. package/test/utils/BaseTest.sol +106 -56
  114. package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
  115. package/test/utils/FeeEstimatorHook.sol +84 -0
  116. package/test/utils/ProxyShim.sol +17 -0
  117. package/wagmi.config.ts +4 -0
  118. package/.env +0 -1
  119. package/.turbo/turbo-update-contract-version.log +0 -22
  120. package/abis/CoinSetupV3.json +0 -7
  121. package/abis/HookDeployer.json +0 -68
  122. package/abis/IHookDeployer.json +0 -42
  123. package/src/libs/CoinLegacy.sol +0 -48
  124. package/src/libs/CoinLegacyMarket.sol +0 -182
@@ -1,9 +1,124 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.23;
3
3
 
4
+ import {CoinConstants} from "./CoinConstants.sol";
5
+
4
6
  library CoinConfigurationVersions {
5
7
  uint8 constant LEGACY_POOL_VERSION = 1;
6
8
  uint8 constant DOPPLER_UNI_V3_POOL_VERSION = 2;
9
+ uint8 constant DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION = 4;
10
+
11
+ function getVersion(bytes memory poolConfig) internal pure returns (uint8 version) {
12
+ return (version) = abi.decode(poolConfig, (uint8));
13
+ }
14
+
15
+ function isV3(uint8 version) internal pure returns (bool) {
16
+ return version == DOPPLER_UNI_V3_POOL_VERSION || version == LEGACY_POOL_VERSION;
17
+ }
18
+
19
+ function isV4(uint8 version) internal pure returns (bool) {
20
+ return version == DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION;
21
+ }
22
+
23
+ function decodeVersionAndCurrency(bytes memory poolConfig) internal pure returns (uint8 version, address currency) {
24
+ (version, currency) = abi.decode(poolConfig, (uint8, address));
25
+ }
26
+
27
+ function decodeDopplerUniV3(
28
+ bytes memory poolConfig
29
+ )
30
+ internal
31
+ pure
32
+ returns (uint8 version, address currency, int24 tickLower_, int24 tickUpper_, uint16 numDiscoveryPositions_, uint256 maxDiscoverySupplyShare_)
33
+ {
34
+ (version, currency, tickLower_, tickUpper_, numDiscoveryPositions_, maxDiscoverySupplyShare_) = abi.decode(
35
+ poolConfig,
36
+ (uint8, address, int24, int24, uint16, uint256)
37
+ );
38
+ }
39
+
40
+ function encodeDopplerUniV3(
41
+ address currency,
42
+ int24 tickLower_,
43
+ int24 tickUpper_,
44
+ uint16 numDiscoveryPositions_,
45
+ uint256 maxDiscoverySupplyShare_
46
+ ) internal pure returns (bytes memory) {
47
+ return abi.encode(DOPPLER_UNI_V3_POOL_VERSION, currency, tickLower_, tickUpper_, numDiscoveryPositions_, maxDiscoverySupplyShare_);
48
+ }
49
+
50
+ function decodeLegacy(bytes memory poolConfig) internal pure returns (uint8 version, address currency, int24 tickLower_) {
51
+ (version, currency, tickLower_) = abi.decode(poolConfig, (uint8, address, int24));
52
+ }
53
+
54
+ function decodeVanillaUniV4(bytes memory poolConfig) internal pure returns (uint8 version, address currency, int24 tickLower_) {
55
+ (version, currency, tickLower_) = abi.decode(poolConfig, (uint8, address, int24));
56
+ }
57
+
58
+ function encodeDopplerMultiCurveUniV4(
59
+ address currency,
60
+ int24[] memory tickLower_,
61
+ int24[] memory tickUpper_,
62
+ uint16[] memory numDiscoveryPositions_,
63
+ uint256[] memory maxDiscoverySupplyShare_
64
+ ) internal pure returns (bytes memory) {
65
+ return abi.encode(DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION, currency, tickLower_, tickUpper_, numDiscoveryPositions_, maxDiscoverySupplyShare_);
66
+ }
67
+
68
+ function decodeDopplerMultiCurveUniV4(
69
+ bytes memory poolConfig
70
+ )
71
+ internal
72
+ pure
73
+ returns (
74
+ uint8 version,
75
+ address currency,
76
+ int24[] memory tickLower_,
77
+ int24[] memory tickUpper_,
78
+ uint16[] memory numDiscoveryPositions_,
79
+ uint256[] memory maxDiscoverySupplyShare_
80
+ )
81
+ {
82
+ (version, currency, tickLower_, tickUpper_, numDiscoveryPositions_, maxDiscoverySupplyShare_) = abi.decode(
83
+ poolConfig,
84
+ (uint8, address, int24[], int24[], uint16[], uint256[])
85
+ );
86
+ }
87
+
88
+ function defaultDopplerUniV3(address currency) internal pure returns (bytes memory) {
89
+ return
90
+ encodeDopplerUniV3(
91
+ currency,
92
+ CoinConstants.DEFAULT_DISCOVERY_TICK_LOWER,
93
+ CoinConstants.DEFAULT_DISCOVERY_TICK_UPPER,
94
+ CoinConstants.DEFAULT_NUM_DISCOVERY_POSITIONS,
95
+ CoinConstants.DEFAULT_DISCOVERY_SUPPLY_SHARE
96
+ );
97
+ }
98
+
99
+ function defaultDopplerMultiCurveUniV4(address currency) internal pure returns (bytes memory) {
100
+ int24[] memory tickLower = new int24[](2);
101
+ int24[] memory tickUpper = new int24[](2);
102
+ uint16[] memory numDiscoveryPositions = new uint16[](2);
103
+ uint256[] memory maxDiscoverySupplyShare = new uint256[](2);
104
+
105
+ // todo: configure defaults
106
+ // Curve 1
107
+ tickLower[0] = -328_000;
108
+ tickUpper[0] = -300_000;
109
+ numDiscoveryPositions[0] = 2;
110
+ maxDiscoverySupplyShare[0] = 0.1e18;
111
+
112
+ // Curve 2
113
+ tickLower[1] = -200_000;
114
+ tickUpper[1] = -100_000;
115
+ numDiscoveryPositions[1] = 2;
116
+ maxDiscoverySupplyShare[1] = 0.1e18;
117
+
118
+ return encodeDopplerMultiCurveUniV4(currency, tickLower, tickUpper, numDiscoveryPositions, maxDiscoverySupplyShare);
119
+ }
7
120
 
8
- error InvalidPoolVersion();
121
+ function defaultConfig(address currency) internal pure returns (bytes memory) {
122
+ return defaultDopplerUniV3(currency);
123
+ }
9
124
  }
@@ -49,4 +49,9 @@ library CoinConstants {
49
49
  /// @notice The percentage of the LP fee allocated to the Doppler protocol
50
50
  /// @dev 500 basis points = 5% of the 1% LP FEE
51
51
  uint256 public constant DOPPLER_MARKET_REWARD_BPS = 500;
52
+
53
+ int24 internal constant DEFAULT_DISCOVERY_TICK_LOWER = -777000;
54
+ int24 internal constant DEFAULT_DISCOVERY_TICK_UPPER = 222000;
55
+ uint16 internal constant DEFAULT_NUM_DISCOVERY_POSITIONS = 10; // will be 11 total with tail position
56
+ uint256 internal constant DEFAULT_DISCOVERY_SUPPLY_SHARE = 0.495e18; // half of the 990m total pool supply
52
57
  }
@@ -0,0 +1,134 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {PoolConfiguration} from "../interfaces/ICoin.sol";
5
+ import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
6
+ import {LpPosition} from "../types/LpPosition.sol";
7
+ import {MarketConstants} from "./MarketConstants.sol";
8
+ import {FullMath} from "../utils/uniswap/FullMath.sol";
9
+ import {TickMath} from "../utils/uniswap/TickMath.sol";
10
+ import {IDopplerErrors} from "../interfaces/IDopplerErrors.sol";
11
+ import {DopplerMath} from "./DopplerMath.sol";
12
+
13
+ library CoinDopplerMultiCurve {
14
+ error ArrayLengthMismatch();
15
+ error ZeroDiscoveryPositions();
16
+ error ZeroDiscoverySupplyShare();
17
+ error InvalidTickRangeMisordered(int24 tickLower, int24 tickUpper);
18
+ error ConfigTickLowerMustBeLessThanTickUpper();
19
+
20
+ /**
21
+ * @notice Configures multi-curve liquidity based on the provided parameters.
22
+ * @param isCoinToken0 A boolean indicating if the coin is token0 (true) or token1 (false) in the pair.
23
+ * This affects tick ordering and price calculations.
24
+ * @param poolConfig_ ABI encoded data containing the pool configuration parameters.
25
+ * It is expected to be encoded in the following order:
26
+ * - version (uint8): The version of the pool configuration.
27
+ * (e.g., 2 for UniswapV3, 4 for Doppler/Uniswap V4).
28
+ * - currency (address): The address of the currency token (e.g., WETH) paired with the coin.
29
+ * - tickLower (int24[]): An array of lower tick boundaries for each liquidity curve.
30
+ * - tickUpper (int24[]): An array of upper tick boundaries for each liquidity curve.
31
+ * - numDiscoveryPositions (uint16[]): An array specifying the number of discrete liquidity
32
+ * positions within each curve's discovery phase.
33
+ * - maxDiscoverySupplyShare (uint256[]): An array of WAD-scaled values (1e18) representing
34
+ * the maximum share of the coin's total supply
35
+ * allocated to each curve's discovery phase.
36
+ * @return sqrtPriceX96 The initial square root price of the pool, scaled to X96 format.
37
+ * @return poolConfiguration A struct containing the configured pool parameters,
38
+ * including version, number of positions, fee, tick spacing,
39
+ * and arrays for discovery positions, tick boundaries, and supply shares.
40
+ */
41
+ function setupPool(bool isCoinToken0, bytes memory poolConfig_) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
42
+ (, , int24[] memory tickLower_, int24[] memory tickUpper_, uint16[] memory numDiscoveryPositions_, uint256[] memory maxDiscoverySupplyShare_) = abi
43
+ .decode(poolConfig_, (uint8, address, int24[], int24[], uint16[], uint256[]));
44
+
45
+ uint256 numCurves = tickLower_.length;
46
+ if (numCurves != tickUpper_.length || numCurves != numDiscoveryPositions_.length || numCurves != maxDiscoverySupplyShare_.length) {
47
+ revert ArrayLengthMismatch();
48
+ }
49
+
50
+ uint256 totalDiscoverySupplyShare;
51
+ uint256 totalDiscoveryPositions;
52
+
53
+ int24 boundryTickLower = DopplerMath.alignTickToTickSpacing(isCoinToken0, TickMath.MAX_TICK, MarketConstants.TICK_SPACING);
54
+ int24 boundryTickUpper = DopplerMath.alignTickToTickSpacing(isCoinToken0, TickMath.MIN_TICK, MarketConstants.TICK_SPACING);
55
+
56
+ // For each curve:
57
+ for (uint256 i; i < numCurves; i++) {
58
+ // Ensure a value is specified
59
+ require(numDiscoveryPositions_[i] > 0, ZeroDiscoveryPositions());
60
+ require(maxDiscoverySupplyShare_[i] > 0, ZeroDiscoverySupplyShare());
61
+
62
+ // Aggregate the total discovery positions and supply across curves
63
+ totalDiscoveryPositions += numDiscoveryPositions_[i];
64
+ totalDiscoverySupplyShare += maxDiscoverySupplyShare_[i];
65
+
66
+ int24 currentTickLower = DopplerMath.alignTickToTickSpacing(isCoinToken0, tickLower_[i], MarketConstants.TICK_SPACING);
67
+ int24 currentTickUpper = DopplerMath.alignTickToTickSpacing(isCoinToken0, tickUpper_[i], MarketConstants.TICK_SPACING);
68
+
69
+ require(currentTickLower < currentTickUpper, ConfigTickLowerMustBeLessThanTickUpper());
70
+
71
+ // Sort the tick values based on token order
72
+ tickLower_[i] = isCoinToken0 ? currentTickLower : -currentTickUpper;
73
+ tickUpper_[i] = isCoinToken0 ? currentTickUpper : -currentTickLower;
74
+
75
+ boundryTickLower = boundryTickLower < tickLower_[i] ? boundryTickLower : tickLower_[i];
76
+ boundryTickUpper = boundryTickUpper > tickUpper_[i] ? boundryTickUpper : tickUpper_[i];
77
+ }
78
+
79
+ require(boundryTickLower < boundryTickUpper, InvalidTickRangeMisordered(boundryTickLower, boundryTickUpper));
80
+ require(totalDiscoveryPositions > 1 && totalDiscoveryPositions <= 200, IDopplerErrors.NumDiscoveryPositionsOutOfRange());
81
+ require(totalDiscoverySupplyShare < MarketConstants.WAD, IDopplerErrors.MaxShareToBeSoldExceeded(totalDiscoverySupplyShare, MarketConstants.WAD));
82
+
83
+ sqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? boundryTickLower : boundryTickUpper);
84
+
85
+ poolConfiguration = PoolConfiguration({
86
+ version: CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION,
87
+ numPositions: uint16(totalDiscoveryPositions + 1), // Add one for the final tail position
88
+ fee: MarketConstants.LP_FEE_V4,
89
+ tickSpacing: MarketConstants.TICK_SPACING,
90
+ numDiscoveryPositions: numDiscoveryPositions_,
91
+ tickLower: tickLower_,
92
+ tickUpper: tickUpper_,
93
+ maxDiscoverySupplyShare: maxDiscoverySupplyShare_
94
+ });
95
+ }
96
+
97
+ /// @notice Calculates the LP positions for a given multi-curve configuration
98
+ function calculatePositions(bool isCoinToken0, PoolConfiguration memory poolConfiguration) internal pure returns (LpPosition[] memory positions) {
99
+ positions = new LpPosition[](poolConfiguration.numPositions);
100
+
101
+ uint256 discoverySupply;
102
+ uint256 currentPositionOffset;
103
+ uint256 numCurves = poolConfiguration.tickLower.length;
104
+
105
+ for (uint256 i; i < numCurves; i++) {
106
+ uint256 curveSupply = FullMath.mulDiv(MarketConstants.POOL_LAUNCH_SUPPLY, poolConfiguration.maxDiscoverySupplyShare[i], MarketConstants.WAD);
107
+
108
+ (positions, curveSupply) = DopplerMath.calculateLogNormalDistribution(
109
+ poolConfiguration.tickLower[i],
110
+ poolConfiguration.tickUpper[i],
111
+ MarketConstants.TICK_SPACING,
112
+ isCoinToken0,
113
+ curveSupply,
114
+ poolConfiguration.numDiscoveryPositions[i],
115
+ positions,
116
+ currentPositionOffset
117
+ );
118
+
119
+ discoverySupply += curveSupply;
120
+ currentPositionOffset += poolConfiguration.numDiscoveryPositions[i];
121
+ }
122
+
123
+ uint256 tailSupply = MarketConstants.POOL_LAUNCH_SUPPLY - discoverySupply;
124
+
125
+ // Calculate the tail position (the last position in the array)
126
+ positions[poolConfiguration.numPositions - 1] = DopplerMath.calculateLpTail(
127
+ poolConfiguration.tickLower[numCurves - 1],
128
+ poolConfiguration.tickUpper[numCurves - 1],
129
+ isCoinToken0,
130
+ tailSupply,
131
+ MarketConstants.TICK_SPACING
132
+ );
133
+ }
134
+ }
@@ -1,7 +1,6 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.23;
3
3
 
4
- import {PoolConfiguration} from "../interfaces/ICoin.sol";
5
4
  import {TickMath} from "../utils/uniswap/TickMath.sol";
6
5
  import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
7
6
  import {ICoin} from "../interfaces/ICoin.sol";
@@ -11,13 +10,13 @@ import {FullMath} from "../utils/uniswap/FullMath.sol";
11
10
  import {SqrtPriceMath} from "../utils/uniswap/SqrtPriceMath.sol";
12
11
  import {LiquidityAmounts} from "../utils/uniswap/LiquidityAmounts.sol";
13
12
  import {IDopplerErrors} from "../interfaces/IDopplerErrors.sol";
13
+ import {DopplerMath} from "./DopplerMath.sol";
14
+ import {PoolConfiguration} from "../types/PoolConfiguration.sol";
14
15
 
15
16
  library CoinDopplerUniV3 {
16
17
  function setupPool(bool isCoinToken0, bytes memory poolConfig_) internal pure returns (uint160 sqrtPriceX96, PoolConfiguration memory poolConfiguration) {
17
- (, , int24 tickLower_, int24 tickUpper_, uint16 numDiscoveryPositions_, uint256 maxDiscoverySupplyShare_) = abi.decode(
18
- poolConfig_,
19
- (uint8, address, int24, int24, uint16, uint256)
20
- );
18
+ (, , int24 tickLower_, int24 tickUpper_, uint16 numDiscoveryPositions_, uint256 maxDiscoverySupplyShare_) = CoinConfigurationVersions
19
+ .decodeDopplerUniV3(poolConfig_);
21
20
 
22
21
  require(numDiscoveryPositions_ > 1 && numDiscoveryPositions_ <= 200, IDopplerErrors.NumDiscoveryPositionsOutOfRange());
23
22
 
@@ -25,178 +24,27 @@ library CoinDopplerUniV3 {
25
24
  revert IDopplerErrors.MaxShareToBeSoldExceeded(maxDiscoverySupplyShare_, MarketConstants.WAD);
26
25
  }
27
26
 
28
- int24 savedTickLower = isCoinToken0 ? tickLower_ : -tickUpper_;
29
- int24 savedTickUpper = isCoinToken0 ? tickUpper_ : -tickLower_;
27
+ uint256[] memory maxDiscoverySupplyShare = new uint256[](1);
28
+ uint16[] memory numDiscoveryPositions = new uint16[](1);
29
+ int24[] memory savedTickLower = new int24[](1);
30
+ int24[] memory savedTickUpper = new int24[](1);
30
31
 
31
- sqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? savedTickLower : savedTickUpper);
32
+ maxDiscoverySupplyShare[0] = maxDiscoverySupplyShare_;
33
+ numDiscoveryPositions[0] = numDiscoveryPositions_;
34
+ savedTickLower[0] = isCoinToken0 ? tickLower_ : -tickUpper_;
35
+ savedTickUpper[0] = isCoinToken0 ? tickUpper_ : -tickLower_;
36
+
37
+ sqrtPriceX96 = TickMath.getSqrtPriceAtTick(isCoinToken0 ? savedTickLower[0] : savedTickUpper[0]);
32
38
 
33
39
  poolConfiguration = PoolConfiguration({
34
40
  version: CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION,
41
+ fee: MarketConstants.LP_FEE,
42
+ tickSpacing: MarketConstants.TICK_SPACING,
35
43
  tickLower: savedTickLower,
36
44
  tickUpper: savedTickUpper,
37
- numPositions: numDiscoveryPositions_,
38
- maxDiscoverySupplyShare: maxDiscoverySupplyShare_
45
+ numPositions: numDiscoveryPositions_ + 1, // Add one for the final tail position
46
+ maxDiscoverySupplyShare: maxDiscoverySupplyShare,
47
+ numDiscoveryPositions: numDiscoveryPositions
39
48
  });
40
49
  }
41
-
42
- function calculatePositions(bool isCoinToken0, PoolConfiguration memory poolConfiguration) internal pure returns (LpPosition[] memory positions) {
43
- positions = new LpPosition[](poolConfiguration.numPositions);
44
-
45
- uint256 discoverySupply = FullMath.mulDiv(MarketConstants.POOL_LAUNCH_SUPPLY, poolConfiguration.maxDiscoverySupplyShare, MarketConstants.WAD);
46
-
47
- (positions, discoverySupply) = calculateLogNormalDistribution(
48
- poolConfiguration.tickLower,
49
- poolConfiguration.tickUpper,
50
- MarketConstants.TICK_SPACING,
51
- isCoinToken0,
52
- discoverySupply,
53
- // Populate all positions before the last position (the tail position)
54
- poolConfiguration.numPositions - 1, // Only discovery positions
55
- positions
56
- );
57
-
58
- uint256 tailSupply = MarketConstants.POOL_LAUNCH_SUPPLY - discoverySupply;
59
-
60
- // Calculate the tail position (the last position in the array)
61
- positions[poolConfiguration.numPositions - 1] = calculateLpTail(
62
- poolConfiguration.tickLower,
63
- poolConfiguration.tickUpper,
64
- isCoinToken0,
65
- tailSupply,
66
- MarketConstants.TICK_SPACING
67
- );
68
- }
69
-
70
- /// @notice Calculates the distribution of liquidity positions across tick ranges.
71
- /// @dev For example, with 1000 tokens and 10 bins starting at tick 0:
72
- /// - Creates positions: [0,10], [1,10], [2,10], ..., [9,10]
73
- /// - Each position gets an equal share of tokens (100 tokens each)
74
- /// This creates a linear distribution of liquidity across the tick range
75
- /// @dev Changed in DopplerUniswapV3:
76
- /// - Added `LpPosition[] memory newPositions` as an input parameter, removing the internal allocation (`new LpPosition[](totalPositions + 1)`).
77
- /// - Removed the calculation and accumulation of the `reserves` variable entirely.
78
- /// - Return value changed from `(LpPosition[] memory, uint256)` (positions, reserves) to `(LpPosition[] memory, uint256)` (positions, totalAssetsSold).
79
- /// @param tickLower The lower tick of the LP range set
80
- /// @param tickUpper The upper tick of the LP range set
81
- /// @param tickSpacing The tick spacing of the LP range set
82
- /// @param isToken0 Whether the base asset is the token0 of the pair
83
- /// @param discoverySupply The total supply of the base asset to be sold
84
- /// @param totalPositions The total number of positions in the LP range set
85
- /// @param newPositions The array of new positions to be created
86
- /// @return newPositions The array of new positions to be created
87
- /// @return totalAssetsSold The total assets used in the LP range set
88
- function calculateLogNormalDistribution(
89
- int24 tickLower,
90
- int24 tickUpper,
91
- int24 tickSpacing,
92
- bool isToken0,
93
- uint256 discoverySupply,
94
- uint16 totalPositions,
95
- LpPosition[] memory newPositions
96
- ) internal pure returns (LpPosition[] memory, uint256) {
97
- int24 farTick = isToken0 ? tickUpper : tickLower;
98
- int24 closeTick = isToken0 ? tickLower : tickUpper;
99
-
100
- int24 spread = tickUpper - tickLower;
101
-
102
- uint160 farSqrtPriceX96 = TickMath.getSqrtPriceAtTick(farTick);
103
- uint256 amountPerPosition = FullMath.mulDiv(discoverySupply, MarketConstants.WAD, totalPositions * MarketConstants.WAD);
104
- uint256 totalAssetsSold;
105
-
106
- for (uint256 i; i < totalPositions; i++) {
107
- // calculate the ticks position * 1/n to optimize the division
108
- int24 startingTick = isToken0
109
- ? closeTick + int24(uint24(FullMath.mulDiv(i, uint256(uint24(spread)), totalPositions)))
110
- : closeTick - int24(uint24(FullMath.mulDiv(i, uint256(uint24(spread)), totalPositions)));
111
-
112
- // round the tick to the nearest bin
113
- startingTick = alignTickToTickSpacing(isToken0, startingTick, tickSpacing);
114
-
115
- if (startingTick != farTick) {
116
- uint160 startingSqrtPriceX96 = TickMath.getSqrtPriceAtTick(startingTick);
117
-
118
- // if discoverySupply is 0, we skip the liquidity calculation as we are burning max liquidity
119
- // in each position
120
- uint128 liquidity;
121
- if (discoverySupply != 0) {
122
- liquidity = isToken0
123
- ? LiquidityAmounts.getLiquidityForAmount0(startingSqrtPriceX96, farSqrtPriceX96, amountPerPosition)
124
- : LiquidityAmounts.getLiquidityForAmount1(farSqrtPriceX96, startingSqrtPriceX96, amountPerPosition);
125
-
126
- totalAssetsSold += (
127
- isToken0
128
- ? SqrtPriceMath.getAmount0Delta(startingSqrtPriceX96, farSqrtPriceX96, liquidity, true)
129
- : SqrtPriceMath.getAmount1Delta(farSqrtPriceX96, startingSqrtPriceX96, liquidity, true)
130
- );
131
- }
132
-
133
- newPositions[i] = LpPosition({
134
- tickLower: farSqrtPriceX96 < startingSqrtPriceX96 ? farTick : startingTick,
135
- tickUpper: farSqrtPriceX96 < startingSqrtPriceX96 ? startingTick : farTick,
136
- liquidity: liquidity
137
- });
138
- }
139
- }
140
-
141
- require(totalAssetsSold <= discoverySupply, IDopplerErrors.CannotMintZeroLiquidity());
142
-
143
- return (newPositions, totalAssetsSold);
144
- }
145
-
146
- /// @notice Calculates the final LP position that extends from the far tick to the pool's min/max tick
147
- /// @dev This position ensures price equivalence between Uniswap v2 and v3 pools beyond the LBP range
148
- /// @dev Changed in DopplerUniswapV3:
149
- /// - Removed parameters: `id`, `reserves`
150
- /// - Liquidity calculation is based *solely* on the provided `tailSupply` within the calculated tail tick range using `LiquidityAmounts.getLiquidityForAmount0` or `getLiquidityForAmount1`.
151
- function calculateLpTail(
152
- int24 tickLower,
153
- int24 tickUpper,
154
- bool isToken0,
155
- uint256 tailSupply,
156
- int24 tickSpacing
157
- ) internal pure returns (LpPosition memory lpTail) {
158
- int24 posTickLower = isToken0 ? tickUpper : alignTickToTickSpacing(false, TickMath.MIN_TICK, tickSpacing);
159
- int24 posTickUpper = isToken0 ? alignTickToTickSpacing(true, TickMath.MAX_TICK, tickSpacing) : tickLower;
160
-
161
- require(posTickLower < posTickUpper, IDopplerErrors.InvalidTickRangeMisordered(posTickLower, posTickUpper));
162
-
163
- // Calculate the sqrtPrices for the tail range boundaries
164
- uint160 sqrtPriceA = TickMath.getSqrtPriceAtTick(posTickLower);
165
- uint160 sqrtPriceB = TickMath.getSqrtPriceAtTick(posTickUpper);
166
-
167
- // Calculate liquidity only based on the tail range supply
168
- uint128 lpTailLiquidity = isToken0
169
- ? LiquidityAmounts.getLiquidityForAmount0(sqrtPriceA, sqrtPriceB, tailSupply)
170
- : LiquidityAmounts.getLiquidityForAmount1(sqrtPriceA, sqrtPriceB, tailSupply);
171
-
172
- lpTail = LpPosition({tickLower: posTickLower, tickUpper: posTickUpper, liquidity: lpTailLiquidity});
173
- }
174
-
175
- /// @notice Aligns a tick to the nearest tick spacing
176
- /// @dev The tickSpacing parameter cannot be zero
177
- /// @param isToken0 Whether the base asset is the token0 of the pair
178
- /// @param tick The tick to align
179
- /// @param tickSpacing The tick spacing of the pair
180
- /// @return alignedTick The aligned tick
181
- function alignTickToTickSpacing(bool isToken0, int24 tick, int24 tickSpacing) internal pure returns (int24) {
182
- if (isToken0) {
183
- // Round down if isToken0
184
- if (tick < 0) {
185
- // If the tick is negative, we round up (negatively) the negative result to round down
186
- return ((tick - tickSpacing + 1) / tickSpacing) * tickSpacing;
187
- } else {
188
- // Else if positive, we simply round down
189
- return (tick / tickSpacing) * tickSpacing;
190
- }
191
- } else {
192
- // Round up if isToken1
193
- if (tick < 0) {
194
- // If the tick is negative, we round down the negative result to round up
195
- return (tick / tickSpacing) * tickSpacing;
196
- } else {
197
- // Else if positive, we simply round up
198
- return ((tick + tickSpacing - 1) / tickSpacing) * tickSpacing;
199
- }
200
- }
201
- }
202
50
  }