@zoralabs/coins 0.9.0 → 1.0.1

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 (131) hide show
  1. package/.turbo/turbo-build.log +179 -114
  2. package/CHANGELOG.md +46 -0
  3. package/abis/BaseCoin.json +26 -118
  4. package/abis/BaseTest.json +47 -0
  5. package/abis/BuySupplyWithSwapRouterHook.json +40 -0
  6. package/abis/Coin.json +171 -63
  7. package/abis/CoinDopplerMultiCurve.json +38 -0
  8. package/abis/CoinRewardsV4.json +54 -0
  9. package/abis/CoinTest.json +53 -20
  10. package/abis/CoinUniV4Test.json +1091 -0
  11. package/abis/CoinV4.json +234 -211
  12. package/abis/DeployScript.json +47 -0
  13. package/abis/DeployedCoinVersionLookup.json +21 -0
  14. package/abis/DeployedCoinVersionLookupTest.json +716 -0
  15. package/abis/DifferentNamespaceVersionLookup.json +39 -0
  16. package/abis/DopplerUniswapV3Test.json +49 -93
  17. package/abis/ERC20.json +310 -0
  18. package/abis/FactoryTest.json +85 -7
  19. package/abis/FeeEstimatorHook.json +1515 -0
  20. package/abis/HooksDeployment.json +23 -0
  21. package/abis/HooksTest.json +60 -0
  22. package/abis/ICoin.json +40 -71
  23. package/abis/ICoinV3.json +879 -0
  24. package/abis/ICoinV4.json +915 -0
  25. package/abis/IDeployedCoinVersionLookup.json +21 -0
  26. package/abis/IERC721.json +36 -36
  27. package/abis/IHasPoolKey.json +42 -0
  28. package/abis/IHasRewardsRecipients.json +54 -0
  29. package/abis/IHasSwapPath.json +60 -0
  30. package/abis/IMsgSender.json +15 -0
  31. package/abis/IPoolConfigEncoding.json +46 -0
  32. package/abis/ISwapPathRouter.json +92 -0
  33. package/abis/IUniversalRouter.json +61 -0
  34. package/abis/IUnlockCallback.json +21 -0
  35. package/abis/IV4Quoter.json +310 -0
  36. package/abis/IZoraFactory.json +210 -11
  37. package/abis/IZoraV4CoinHook.json +348 -4
  38. package/abis/MockERC20.json +21 -0
  39. package/abis/MultiOwnableTest.json +47 -0
  40. package/abis/{CoinConfigurationVersions.json → Position.json} +1 -1
  41. package/abis/PrintUpgradeCommand.json +9 -0
  42. package/abis/ProxyShim.json +24 -0
  43. package/abis/StateLibrary.json +80 -0
  44. package/abis/TestDeployedCoinVersionLookupImplementation.json +39 -0
  45. package/abis/TestV4Swap.json +9 -0
  46. package/abis/UpgradeCoinImpl.json +47 -0
  47. package/abis/UpgradesTest.json +81 -0
  48. package/abis/Vm.json +1482 -111
  49. package/abis/VmSafe.json +856 -32
  50. package/abis/ZoraFactoryImpl.json +339 -1
  51. package/abis/ZoraV4CoinHook.json +442 -5
  52. package/addresses/8453.json +7 -4
  53. package/addresses/84532.json +8 -5
  54. package/addresses/dev/8453.json +10 -0
  55. package/dist/index.cjs +1932 -167
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.js +1928 -167
  58. package/dist/index.js.map +1 -1
  59. package/dist/wagmiGenerated.d.ts +2606 -160
  60. package/dist/wagmiGenerated.d.ts.map +1 -1
  61. package/foundry.toml +1 -0
  62. package/package/wagmiGenerated.ts +1941 -164
  63. package/package.json +8 -3
  64. package/remappings.txt +6 -1
  65. package/script/Deploy.s.sol +1 -1
  66. package/script/DeployDevFactory.s.sol +21 -0
  67. package/script/DeployHooks.s.sol +1 -1
  68. package/script/PrintUpgradeCommand.s.sol +13 -0
  69. package/script/Simulate.s.sol +1 -10
  70. package/script/TestBackingCoinSwap.s.sol +147 -0
  71. package/script/TestV4Swap.s.sol +136 -0
  72. package/script/UpgradeCoinImpl.sol +2 -2
  73. package/script/UpgradeFactoryImpl.s.sol +2 -2
  74. package/src/BaseCoin.sol +190 -0
  75. package/src/Coin.sol +87 -202
  76. package/src/CoinV4.sol +121 -0
  77. package/src/ZoraFactoryImpl.sol +208 -36
  78. package/{script → src/deployment}/CoinsDeployerBase.sol +111 -17
  79. package/src/hooks/ZoraV4CoinHook.sol +212 -0
  80. package/src/hooks/{BaseCoinDeployHook.sol → deployment/BaseCoinDeployHook.sol} +3 -3
  81. package/src/hooks/deployment/BuySupplyWithSwapRouterHook.sol +140 -0
  82. package/src/interfaces/ICoin.sol +31 -39
  83. package/src/interfaces/ICoinV3.sol +71 -0
  84. package/src/interfaces/ICoinV4.sol +69 -0
  85. package/src/interfaces/IDeployedCoinVersionLookup.sol +11 -0
  86. package/src/interfaces/IMsgSender.sol +9 -0
  87. package/src/interfaces/IPoolConfigEncoding.sol +14 -0
  88. package/src/interfaces/ISwapPathRouter.sol +14 -0
  89. package/src/interfaces/IZoraFactory.sol +67 -28
  90. package/src/interfaces/IZoraV4CoinHook.sol +116 -0
  91. package/src/libs/CoinCommon.sol +15 -0
  92. package/src/libs/CoinConfigurationVersions.sol +116 -1
  93. package/src/libs/CoinConstants.sol +5 -0
  94. package/src/libs/CoinDopplerMultiCurve.sol +134 -0
  95. package/src/libs/CoinDopplerUniV3.sol +19 -171
  96. package/src/libs/CoinRewards.sol +195 -0
  97. package/src/libs/CoinRewardsV4.sol +179 -0
  98. package/src/libs/CoinSetup.sol +57 -0
  99. package/src/libs/CoinSetupV3.sol +6 -67
  100. package/src/libs/DopplerMath.sol +156 -0
  101. package/src/libs/HooksDeployment.sol +128 -0
  102. package/src/libs/MarketConstants.sol +4 -0
  103. package/src/libs/PoolStateReader.sol +22 -0
  104. package/src/libs/UniV3BuySell.sol +74 -292
  105. package/src/libs/UniV4SwapHelper.sol +65 -0
  106. package/src/libs/UniV4SwapToCurrency.sol +109 -0
  107. package/src/libs/V4Liquidity.sol +122 -0
  108. package/src/types/PoolConfiguration.sol +15 -0
  109. package/src/utils/DeployedCoinVersionLookup.sol +52 -0
  110. package/src/version/ContractVersionBase.sol +1 -1
  111. package/test/Coin.t.sol +78 -88
  112. package/test/CoinDopplerUniV3.t.sol +32 -171
  113. package/test/CoinUniV4.t.sol +777 -0
  114. package/test/{Hooks.t.sol → DeploymentHooks.t.sol} +53 -16
  115. package/test/Factory.t.sol +80 -47
  116. package/test/MultiOwnable.t.sol +6 -3
  117. package/test/Upgrades.t.sol +97 -5
  118. package/test/mocks/MockERC20.sol +12 -0
  119. package/test/utils/BaseTest.sol +162 -57
  120. package/test/utils/DeployedCoinVersionLookup.t.sol +127 -0
  121. package/test/utils/FeeEstimatorHook.sol +84 -0
  122. package/test/utils/ProxyShim.sol +17 -0
  123. package/wagmi.config.ts +4 -0
  124. package/.env +0 -1
  125. package/.turbo/turbo-update-contract-version.log +0 -22
  126. package/abis/CoinSetupV3.json +0 -7
  127. package/abis/HookDeployer.json +0 -68
  128. package/abis/IHookDeployer.json +0 -42
  129. package/src/hooks/BuySupplyWithSwapRouterHook.sol +0 -78
  130. package/src/libs/CoinLegacy.sol +0 -48
  131. package/src/libs/CoinLegacyMarket.sol +0 -182
@@ -1,7 +1,11 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.23;
3
3
 
4
- interface IZoraFactory {
4
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
5
+ import {PoolKeyStruct} from "./ICoin.sol";
6
+ import {IDeployedCoinVersionLookup} from "./IDeployedCoinVersionLookup.sol";
7
+
8
+ interface IZoraFactory is IDeployedCoinVersionLookup {
5
9
  /// @notice Emitted when a coin is created
6
10
  /// @param caller The msg.sender address
7
11
  /// @param payoutRecipient The address of the creator payout recipient
@@ -26,21 +30,51 @@ interface IZoraFactory {
26
30
  string version
27
31
  );
28
32
 
33
+ /// @notice Emitted when a coin is created
34
+ /// @param caller The msg.sender address
35
+ /// @param payoutRecipient The address of the creator payout recipient
36
+ /// @param platformReferrer The address of the platform referrer
37
+ /// @param currency The address of the currency
38
+ /// @param uri The URI of the coin
39
+ /// @param name The name of the coin
40
+ /// @param symbol The symbol of the coin
41
+ /// @param coin The address of the coin
42
+ /// @param poolKey The uniswap v4 pool key
43
+ /// @param version The coin contract version
44
+ event CoinCreatedV4(
45
+ address indexed caller,
46
+ address indexed payoutRecipient,
47
+ address indexed platformReferrer,
48
+ address currency,
49
+ string uri,
50
+ string name,
51
+ string symbol,
52
+ address coin,
53
+ PoolKey poolKey,
54
+ bytes32 poolKeyHash,
55
+ string version
56
+ );
57
+
29
58
  /// @notice Thrown when the amount of ERC20 tokens transferred does not match the expected amount
30
59
  error ERC20TransferAmountMismatch();
31
60
 
32
61
  /// @notice Thrown when ETH is sent with a transaction but the currency is not WETH
33
62
  error EthTransferInvalid();
34
63
 
35
- /// @notice Creates a new coin contract
64
+ /// @notice Creates a new coin contract with an optional hook that runs after the coin is deployed.
65
+ /// Requires a salt to be specified, which enabled the coin to be deployed deterministically, and at
66
+ /// a predictable address.
36
67
  /// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
37
68
  /// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
38
69
  /// @param uri The coin metadata uri
39
70
  /// @param name The name of the coin
40
71
  /// @param symbol The symbol of the coin
41
- /// @param poolConfig The config parameters for the Uniswap v3 pool; `abi.encode(address currency, int24 tickLower, int24 tickUpper, uint16 numDiscoveryPositions, uint256 maxDiscoverySupplyShare)`
72
+ /// @param poolConfig The config parameters for the coin's pool
42
73
  /// @param platformReferrer The address of the platform referrer
43
- /// @param orderSize The order size for the first buy; must match msg.value for ETH/WETH pairs
74
+ /// @param postDeployHook The address of the hook to run after the coin is deployed
75
+ /// @param postDeployHookData The data to pass to the hook
76
+ /// @return coin The address of the deployed coin
77
+ /// @return postDeployHookDataOut The data returned by the hook
44
78
  function deploy(
45
79
  address payoutRecipient,
46
80
  address[] memory owners,
@@ -49,43 +83,41 @@ interface IZoraFactory {
49
83
  string memory symbol,
50
84
  bytes memory poolConfig,
51
85
  address platformReferrer,
52
- uint256 orderSize
53
- ) external payable returns (address, uint256);
86
+ address postDeployHook,
87
+ bytes calldata postDeployHookData,
88
+ bytes32 coinSalt
89
+ ) external payable returns (address coin, bytes memory postDeployHookDataOut);
54
90
 
55
- /// @notice Creates a new coin contract
56
- /// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
57
- /// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
58
- /// @param uri The coin metadata uri
91
+ /// @notice Predicts the address of a coin contract that will be deployed with the given parameters
92
+ /// @param msgSender The address of the msg.sender
59
93
  /// @param name The name of the coin
60
94
  /// @param symbol The symbol of the coin
61
- /// @param platformReferrer The address to receive platform referral rewards
62
- /// @param currency The address of the trading currency; address(0) for ETH/WETH
63
- /// @param tickLower The lower tick for the Uniswap V3 LP position; ignored for ETH/WETH pairs
64
- /// @param orderSize The order size for the first buy; must match msg.value for ETH/WETH pairs
95
+ /// @param poolConfig The pool config
96
+ /// @param platformReferrer The platform referrer
97
+ /// @param coinSalt The salt used to deploy the coin
98
+ /// @return The address of the coin contract
99
+ function coinAddress(
100
+ address msgSender,
101
+ string memory name,
102
+ string memory symbol,
103
+ bytes memory poolConfig,
104
+ address platformReferrer,
105
+ bytes32 coinSalt
106
+ ) external view returns (address);
107
+
108
+ /// @dev Deprecated: use `deploy` instead that has a salt and hook specified
65
109
  function deploy(
66
110
  address payoutRecipient,
67
111
  address[] memory owners,
68
112
  string memory uri,
69
113
  string memory name,
70
114
  string memory symbol,
115
+ bytes memory poolConfig,
71
116
  address platformReferrer,
72
- address currency,
73
- int24 tickLower,
74
117
  uint256 orderSize
75
118
  ) external payable returns (address, uint256);
76
119
 
77
- /// @notice Creates a new coin contract with an optional hook that runs after the coin is deployed
78
- /// @param payoutRecipient The recipient of creator reward payouts; this can be updated by an owner
79
- /// @param owners The list of addresses that will be able to manage the coin's payout address and metadata uri
80
- /// @param uri The coin metadata uri
81
- /// @param name The name of the coin
82
- /// @param symbol The symbol of the coin
83
- /// @param poolConfig The config parameters for the Uniswap v3 pool; `abi.encode(address currency, int24 tickLower, int24 tickUpper, uint16 numDiscoveryPositions, uint256 maxDiscoverySupplyShare)`
84
- /// @param platformReferrer The address of the platform referrer
85
- /// @param hook The address of the hook to run after the coin is deployed
86
- /// @param hookData The data to pass to the hook
87
- /// @return coin The address of the deployed coin
88
- /// @return hookDataOut The data returned by the hook
120
+ /// @dev Deprecated: use `deploy` instead that has a salt and hook specified
89
121
  function deployWithHook(
90
122
  address payoutRecipient,
91
123
  address[] memory owners,
@@ -98,6 +130,10 @@ interface IZoraFactory {
98
130
  bytes calldata hookData
99
131
  ) external payable returns (address coin, bytes memory hookDataOut);
100
132
 
133
+ function coinImpl() external view returns (address);
134
+
135
+ function implementation() external view returns (address);
136
+
101
137
  /// @notice Thrown when the hook is invalid
102
138
  error InvalidHook();
103
139
 
@@ -105,4 +141,7 @@ interface IZoraFactory {
105
141
  /// @param currentName The name of the current contract
106
142
  /// @param newName The name of the contract being upgraded to
107
143
  error UpgradeToMismatchedContractName(string currentName, string newName);
144
+
145
+ /// @notice Thrown when a method is deprecated
146
+ error Deprecated();
108
147
  }
@@ -0,0 +1,116 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
5
+ import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
6
+ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
7
+ import {LpPosition} from "../types/LpPosition.sol";
8
+ import {ICoin} from "./ICoin.sol";
9
+
10
+ interface IZoraV4CoinHook {
11
+ /// @notice Emitted when a swap is executed.
12
+ /// @param sender The address of the sender.
13
+ /// @param swapSender The address of the swap sender.
14
+ /// @param isTrustedSwapSenderAddress Whether the swap sender is a trusted address. (Based on a registry of trusted addresses)
15
+ /// @param key The pool key struct to identify the pool.
16
+ /// @param poolKeyHash The hash of the pool key for indexing.
17
+ /// @param params The swap parameters.
18
+ /// @param amount0 The amount of token0.
19
+ /// @param amount1 The amount of token1.
20
+ /// @param isCoinBuy Whether the swap is a coin buy.
21
+ /// @param hookData The data passed into the hook for the swap.
22
+ event Swapped(
23
+ address indexed sender,
24
+ address indexed swapSender,
25
+ bool isTrustedSwapSenderAddress,
26
+ PoolKey key,
27
+ bytes32 indexed poolKeyHash,
28
+ SwapParams params,
29
+ int128 amount0,
30
+ int128 amount1,
31
+ bool isCoinBuy,
32
+ bytes hookData,
33
+ uint160 sqrtPriceX96
34
+ );
35
+
36
+ /// @notice Thrown when a non-coin is used to initialize a pool with this hook.
37
+ /// @param coin The address of the coin.
38
+ error NotACoin(address coin);
39
+
40
+ /// @notice Coin version lookup cannot be the zero address.
41
+ error CoinVersionLookupCannotBeZeroAddress();
42
+
43
+ /// @notice Thrown when a pool is not initialized for the hook.
44
+ /// @param key The pool key struct to identify the pool.
45
+ error NoCoinForHook(PoolKey key);
46
+
47
+ /// @notice Thrown when a attempting to swap with a path that has no steps.
48
+ error PathMustHaveAtLeastOneStep();
49
+
50
+ /// @notice The pool coin struct. Lists all the contract-created positions for the coin.
51
+ struct PoolCoin {
52
+ /// @notice The address of the coin.
53
+ address coin;
54
+ /// @notice The positions of the pool coin.
55
+ LpPosition[] positions;
56
+ }
57
+
58
+ /// @notice The rewards accrued from the market's liquidity position
59
+ /// @param creatorPayoutAmountCurrency The amount of currency payed out to the creator
60
+ /// @param creatorPayoutAmountCoin The amount of coin payed out to the creator
61
+ /// @param platformReferrerAmountCurrency The amount of currency payed out to the platform referrer
62
+ /// @param platformReferrerAmountCoin The amount of coin payed out to the platform referrer
63
+ /// @param tradeReferrerAmountCurrency The amount of currency payed out to the trade referrer
64
+ /// @param tradeReferrerAmountCoin The amount of coin to pay to the trade referrer
65
+ /// @param protocolAmountCurrency The amount of currency to pay to the protocol
66
+ /// @param protocolAmountCoin The amount of coin to pay to the protocol
67
+ /// @param dopplerAmountCurrency The amount of currency to pay to doppler
68
+ /// @param dopplerAmountCoin The amount of coin to pay to doppler
69
+ struct MarketRewardsV4 {
70
+ uint256 creatorPayoutAmountCurrency;
71
+ uint256 creatorPayoutAmountCoin;
72
+ uint256 platformReferrerAmountCurrency;
73
+ uint256 platformReferrerAmountCoin;
74
+ uint256 tradeReferrerAmountCurrency;
75
+ uint256 tradeReferrerAmountCoin;
76
+ uint256 protocolAmountCurrency;
77
+ uint256 protocolAmountCoin;
78
+ uint256 dopplerAmountCurrency;
79
+ uint256 dopplerAmountCoin;
80
+ }
81
+
82
+ /// @notice Emitted when market rewards are distributed
83
+ /// @param coin The address of the coin
84
+ /// @param currency The address of the currency
85
+ /// @param payoutRecipient The address of the creator rewards payout recipient
86
+ /// @param platformReferrer The address of the platform referrer
87
+ /// @param protocolRewardRecipient The address of the protocol reward recipient
88
+ /// @param dopplerRecipient The address of the doppler recipient
89
+ /// @param tradeReferrer The address of the trade referrer
90
+ /// @param marketRewards The rewards accrued from the market's liquidity position
91
+ event CoinMarketRewardsV4(
92
+ address coin,
93
+ address currency,
94
+ address payoutRecipient,
95
+ address platformReferrer,
96
+ address tradeReferrer,
97
+ address protocolRewardRecipient,
98
+ address dopplerRecipient,
99
+ MarketRewardsV4 marketRewards
100
+ );
101
+
102
+ /// @notice Returns the pool coin for a given pool key hash.
103
+ /// @param poolKeyHash The hash of the pool key for indexing.
104
+ /// @return poolCoin The pool coin confirmation data.
105
+ function getPoolCoinByHash(bytes23 poolKeyHash) external view returns (IZoraV4CoinHook.PoolCoin memory);
106
+
107
+ /// @notice Returns the pool coin for a given pool key.
108
+ /// @param key The pool key.
109
+ /// @return poolCoin The pool coin confirmation data.
110
+ function getPoolCoin(PoolKey memory key) external view returns (IZoraV4CoinHook.PoolCoin memory);
111
+
112
+ /// @notice Returns whether the sender is a trusted message sender.
113
+ /// @param sender The address of the sender.
114
+ /// @return isTrusted Whether the sender is a trusted message sender.
115
+ function isTrustedMessageSender(address sender) external view returns (bool);
116
+ }
@@ -0,0 +1,15 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
5
+
6
+ library CoinCommon {
7
+ // Helper function to sort tokens and determine if coin is token0
8
+ function sortTokens(address coin, address currency) internal pure returns (bool isCoinToken0) {
9
+ return coin < currency;
10
+ }
11
+
12
+ function hashPoolKey(PoolKey memory key) internal pure returns (bytes32) {
13
+ return keccak256(abi.encode(key));
14
+ }
15
+ }
@@ -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
+ }