@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
@@ -0,0 +1,212 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {BaseHook} from "@uniswap/v4-periphery/src/utils/BaseHook.sol";
5
+ import {Hooks} from "@uniswap/v4-core/src/libraries/Hooks.sol";
6
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
7
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
8
+ import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
9
+ import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
10
+ import {IZoraV4CoinHook} from "../interfaces/IZoraV4CoinHook.sol";
11
+ import {IMsgSender} from "../interfaces/IMsgSender.sol";
12
+ import {IHasSwapPath} from "../interfaces/ICoinV4.sol";
13
+ import {LpPosition} from "../types/LpPosition.sol";
14
+ import {V4Liquidity} from "../libs/V4Liquidity.sol";
15
+ import {CoinRewardsV4} from "../libs/CoinRewardsV4.sol";
16
+ import {ICoinV4} from "../interfaces/ICoinV4.sol";
17
+ import {IDeployedCoinVersionLookup} from "../interfaces/IDeployedCoinVersionLookup.sol";
18
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
19
+ import {CoinCommon} from "../libs/CoinCommon.sol";
20
+ import {PoolConfiguration} from "../types/PoolConfiguration.sol";
21
+ import {CoinDopplerMultiCurve} from "../libs/CoinDopplerMultiCurve.sol";
22
+ import {PoolStateReader} from "../libs/PoolStateReader.sol";
23
+ import {IHasSwapPath} from "../interfaces/ICoinV4.sol";
24
+ import {CoinConfigurationVersions} from "../libs/CoinConfigurationVersions.sol";
25
+
26
+ /// @title ZoraV4CoinHook
27
+ /// @notice Uniswap V4 hook that automatically handles fee collection and reward distributions on every swap,
28
+ /// paying out all rewards in a backing currency.
29
+ /// @dev This hook executes on afterSwap withdraw fees, swap for a backing currency, and distribute rewards.
30
+ /// On pool initialization, it creates multiple liquidity positions based on the coin's pool configuration.
31
+ /// On every swap, it automatically:
32
+ /// 1. Collects accrued LP fees from all positions
33
+ /// 2. Swaps collected fees to the backing currency through multi-hop paths
34
+ /// 3. Distributes converted fees as rewards
35
+ /// @author oveddan
36
+ contract ZoraV4CoinHook is BaseHook, IZoraV4CoinHook {
37
+ using BalanceDeltaLibrary for BalanceDelta;
38
+
39
+ /// @notice Mapping of trusted message senders - these are addresses that are trusted to provide a
40
+ /// an original msg.sender
41
+ mapping(address => bool) internal trustedMessageSender;
42
+
43
+ /// @notice Mapping of pool keys to coins.
44
+ mapping(bytes32 => IZoraV4CoinHook.PoolCoin) internal poolCoins;
45
+
46
+ /// @notice The coin version lookup contract - used to determine if an address is a coin and what version it is.
47
+ IDeployedCoinVersionLookup internal immutable coinVersionLookup;
48
+
49
+ /// @notice The constructor for the ZoraV4CoinHook.
50
+ /// @param poolManager_ The Uniswap V4 pool manager
51
+ /// @param coinVersionLookup_ The coin version lookup contract - used to determine if an address is a coin and what version it is.
52
+ /// @param trustedMessageSenders_ The addresses of the trusted message senders - these are addresses that are trusted to provide a
53
+ constructor(IPoolManager poolManager_, IDeployedCoinVersionLookup coinVersionLookup_, address[] memory trustedMessageSenders_) BaseHook(poolManager_) {
54
+ if (address(coinVersionLookup_) == address(0)) {
55
+ revert CoinVersionLookupCannotBeZeroAddress();
56
+ }
57
+
58
+ coinVersionLookup = coinVersionLookup_;
59
+
60
+ for (uint256 i = 0; i < trustedMessageSenders_.length; i++) {
61
+ trustedMessageSender[trustedMessageSenders_[i]] = true;
62
+ }
63
+ }
64
+
65
+ /// @notice Returns the uniswap v4 hook settings / permissions.
66
+ /// @dev The permissions currently requested are: afterInitialize and afterSwap.
67
+ function getHookPermissions() public pure override returns (Hooks.Permissions memory) {
68
+ return
69
+ Hooks.Permissions({
70
+ beforeInitialize: false,
71
+ afterInitialize: true,
72
+ beforeAddLiquidity: false,
73
+ afterAddLiquidity: false,
74
+ beforeRemoveLiquidity: false,
75
+ afterRemoveLiquidity: false,
76
+ beforeSwap: false,
77
+ afterSwap: true,
78
+ beforeDonate: false,
79
+ afterDonate: false,
80
+ beforeSwapReturnDelta: false,
81
+ afterSwapReturnDelta: false,
82
+ afterAddLiquidityReturnDelta: false,
83
+ afterRemoveLiquidityReturnDelta: false
84
+ });
85
+ }
86
+
87
+ /// @inheritdoc IZoraV4CoinHook
88
+ function isTrustedMessageSender(address sender) external view returns (bool) {
89
+ return trustedMessageSender[sender];
90
+ }
91
+
92
+ /// @inheritdoc IZoraV4CoinHook
93
+ function getPoolCoinByHash(bytes23 poolKeyHash) external view returns (IZoraV4CoinHook.PoolCoin memory) {
94
+ return poolCoins[poolKeyHash];
95
+ }
96
+
97
+ /// @inheritdoc IZoraV4CoinHook
98
+ function getPoolCoin(PoolKey memory key) external view returns (IZoraV4CoinHook.PoolCoin memory) {
99
+ return poolCoins[CoinCommon.hashPoolKey(key)];
100
+ }
101
+
102
+ /// @notice Internal fn generating the positions for a given pool key.
103
+ /// @param coin The coin address.
104
+ /// @param key The pool key for the coin.
105
+ /// @return positions The contract-created liquidity positions the positions for the coin's pool.
106
+ function _generatePositions(ICoinV4 coin, PoolKey memory key) internal view returns (LpPosition[] memory positions) {
107
+ bool isCoinToken0 = Currency.unwrap(key.currency0) == address(coin);
108
+
109
+ positions = CoinDopplerMultiCurve.calculatePositions(isCoinToken0, coin.getPoolConfiguration());
110
+ }
111
+
112
+ /// @notice Internal fn called when a pool is initialized.
113
+ /// @dev This hook is called from BaseHook library from uniswap v4.
114
+ /// @param sender The address of the sender.
115
+ /// @param key The pool key.
116
+ /// @return selector The selector of the afterInitialize hook to confirm the action.
117
+ function _afterInitialize(address sender, PoolKey calldata key, uint160, int24) internal override returns (bytes4) {
118
+ address coin = sender;
119
+ if (!CoinConfigurationVersions.isV4(coinVersionLookup.getVersionForDeployedCoin(coin))) {
120
+ revert NotACoin(coin);
121
+ }
122
+
123
+ LpPosition[] memory positions = _generatePositions(ICoinV4(coin), key);
124
+
125
+ poolCoins[CoinCommon.hashPoolKey(key)] = PoolCoin({coin: coin, positions: positions});
126
+
127
+ V4Liquidity.lockAndMint(poolManager, key, positions);
128
+
129
+ return BaseHook.afterInitialize.selector;
130
+ }
131
+
132
+ /// @notice Internal fn called when a swap is executed.
133
+ /// @dev This hook is called from BaseHook library from uniswap v4.
134
+ /// This hook:
135
+ /// 1. Collects accrued LP fees from all positions
136
+ /// 2. Swaps collected fees to the backing currency through multi-hop paths
137
+ /// 3. Distributes converted fees as rewards
138
+ /// @param sender The address of the sender.
139
+ /// @param key The pool key.
140
+ /// @param params The swap parameters.
141
+ /// @param delta The balance delta.
142
+ /// @param hookData The hook data.
143
+ /// @return selector The selector of the afterSwap hook to confirm the action.
144
+ function _afterSwap(
145
+ address sender,
146
+ PoolKey calldata key,
147
+ SwapParams calldata params,
148
+ BalanceDelta delta,
149
+ bytes calldata hookData
150
+ ) internal virtual override returns (bytes4, int128) {
151
+ bytes32 poolKeyHash = CoinCommon.hashPoolKey(key);
152
+
153
+ // get the coin address and positions for the pool key; they must have been set in the afterInitialize callback
154
+ address coin = poolCoins[poolKeyHash].coin;
155
+ require(coin != address(0), NoCoinForHook(key));
156
+
157
+ // get path for swapping the payout to a single currency
158
+ IHasSwapPath.PayoutSwapPath memory payoutSwapPath = IHasSwapPath(coin).getPayoutSwapPath(coinVersionLookup);
159
+
160
+ // Collect accrued LP fees from all positions, swap them to the target payout currency,
161
+ // and transfer the converted amount to this hook contract for distribution
162
+ (, , Currency receivedCurrency, uint128 receivedAmount) = CoinRewardsV4.collectFeesAndConvertToPayout(
163
+ poolManager,
164
+ key,
165
+ poolCoins[poolKeyHash].positions,
166
+ payoutSwapPath
167
+ );
168
+
169
+ // Distribute the collected and converted fees to all reward recipients (creator, referrers, protocol, etc.)
170
+ CoinRewardsV4.distributeMarketRewards(receivedCurrency, receivedAmount, ICoinV4(coin), CoinRewardsV4.getTradeReferral(hookData));
171
+
172
+ {
173
+ (address swapper, bool isTrustedSwapSenderAddress) = _getOriginalMsgSender(sender);
174
+ bool isCoinBuy = params.zeroForOne ? Currency.unwrap(key.currency1) == address(coin) : Currency.unwrap(key.currency0) == address(coin);
175
+ emit Swapped(
176
+ sender,
177
+ swapper,
178
+ isTrustedSwapSenderAddress,
179
+ key,
180
+ poolKeyHash,
181
+ params,
182
+ delta.amount0(),
183
+ delta.amount1(),
184
+ isCoinBuy,
185
+ hookData,
186
+ PoolStateReader.getSqrtPriceX96(key, poolManager)
187
+ );
188
+ }
189
+
190
+ return (BaseHook.afterSwap.selector, 0);
191
+ }
192
+
193
+ /// @notice Internal fn called when the PoolManager is unlocked. Used to mint initial liquidity positions.
194
+ function unlockCallback(bytes calldata data) external onlyPoolManager returns (bytes memory) {
195
+ V4Liquidity.handleMintPositionsCallback(poolManager, data);
196
+ }
197
+
198
+ /// @notice Internal fn to get the original message sender.
199
+ /// @param sender The address of the sender.
200
+ /// @return swapper The original message sender.
201
+ /// @return senderIsTrusted Whether the sender is a trusted message sender.
202
+ function _getOriginalMsgSender(address sender) internal view returns (address swapper, bool senderIsTrusted) {
203
+ senderIsTrusted = trustedMessageSender[sender];
204
+
205
+ // If getter function reverts, we return a 0 address by default and continue execution.
206
+ try IMsgSender(sender).msgSender() returns (address _swapper) {
207
+ swapper = _swapper;
208
+ } catch {
209
+ swapper = address(0);
210
+ }
211
+ }
212
+ }
@@ -1,9 +1,9 @@
1
1
  // SPDX-License-Identifier: MIT
2
2
  pragma solidity ^0.8.13;
3
3
 
4
- import {ICoin} from "../interfaces/ICoin.sol";
5
- import {IZoraFactory} from "../interfaces/IZoraFactory.sol";
6
- import {ICoinDeployHook} from "../interfaces/ICoinDeployHook.sol";
4
+ import {ICoin} from "../../interfaces/ICoin.sol";
5
+ import {IZoraFactory} from "../../interfaces/IZoraFactory.sol";
6
+ import {ICoinDeployHook} from "../../interfaces/ICoinDeployHook.sol";
7
7
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
8
8
 
9
9
  /// @title Immutable State
@@ -0,0 +1,140 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {BaseCoinDeployHook} from "./BaseCoinDeployHook.sol";
5
+ import {ICoin} from "../../interfaces/ICoin.sol";
6
+ import {IZoraFactory} from "../../interfaces/IZoraFactory.sol";
7
+ import {ISwapRouter} from "../../interfaces/ISwapRouter.sol";
8
+ import {IWETH} from "../../interfaces/IWETH.sol";
9
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
10
+ import {Coin} from "../../Coin.sol";
11
+ import {ICoinV3} from "../../interfaces/ICoinV3.sol";
12
+ import {ICoinV4} from "../../interfaces/ICoinV4.sol";
13
+ import {CoinConfigurationVersions} from "../../libs/CoinConfigurationVersions.sol";
14
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
15
+ import {IPoolManager, SwapParams} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
16
+ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
17
+ import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
18
+
19
+ /// @title BuySupplyWithSwapRouter
20
+ /// @notice A hook that buys supply for a coin that is priced in an erc20 token a backing currency, using a Uniswap V3 SwapRouter.
21
+ /// Supports both single-hop and multi-hop swaps using uniswap v3. Supports buying the coin supply whether the coin is a v3 or v4 coin.
22
+ /// @author @oveddan
23
+ contract BuySupplyWithSwapRouterHook is BaseCoinDeployHook {
24
+ ISwapRouter immutable swapRouter;
25
+ IPoolManager immutable poolManager;
26
+ using BalanceDeltaLibrary for BalanceDelta;
27
+
28
+ error Erc20NotReceived();
29
+ error InvalidSwapRouterCall();
30
+ error SwapReverted(bytes error);
31
+ error CoinBalanceNot0(uint256 balance);
32
+ error CurrencyBalanceNot0(uint256 balance);
33
+
34
+ constructor(IZoraFactory _factory, address _swapRouter, address _poolManager) BaseCoinDeployHook(_factory) {
35
+ swapRouter = ISwapRouter(_swapRouter);
36
+ poolManager = IPoolManager(_poolManager);
37
+ }
38
+
39
+ /// @notice Hook that buys supply for a coin that is priced in an erc20 token with ETH, using a Uniswap SwapRouter.
40
+ /// Returns abi encoded (uint256 amountCurrency, uint256 coinsPurchased) - amountCurrency is the amount of currency received from the swap and sent to the coin for the purchase,
41
+ /// and coinsPurchased is the amount of coins purchased using the amountCurrency that was received from the swap
42
+ function _afterCoinDeploy(address, ICoin coin, bytes calldata hookData) internal override returns (bytes memory) {
43
+ address currency = coin.currency();
44
+
45
+ (address buyRecipient, bytes memory swapRouterCall) = abi.decode(hookData, (address, bytes));
46
+
47
+ uint256 amountCurrency = _handleSwap(currency, swapRouterCall);
48
+
49
+ uint256 coinsPurchased = _handleBuy(buyRecipient, coin, amountCurrency);
50
+
51
+ return abi.encode(amountCurrency, coinsPurchased);
52
+ }
53
+
54
+ function _handleSwap(address currency_, bytes memory swapRouterCall) internal returns (uint256 amountCurrency) {
55
+ // call the swap router, with the msg.value
56
+ _validateSwapRouterCall(swapRouterCall);
57
+
58
+ (bool success, bytes memory result) = address(swapRouter).call{value: msg.value}(swapRouterCall);
59
+
60
+ require(success, SwapReverted(result));
61
+
62
+ amountCurrency = abi.decode(result, (uint256));
63
+
64
+ // validate that this contract received the correct amount of currency
65
+ require(IERC20(currency_).balanceOf(address(this)) == amountCurrency, Erc20NotReceived());
66
+ }
67
+
68
+ function _validateSwapRouterCall(bytes memory swapRouterCall) internal pure {
69
+ // validate that the swap router call is valid - only exactInput and exactInputSingle are supported
70
+
71
+ bytes4 selector = _getSelectorFromCall(swapRouterCall);
72
+
73
+ require(selector == ISwapRouter.exactInput.selector || selector == ISwapRouter.exactInputSingle.selector, InvalidSwapRouterCall());
74
+ }
75
+
76
+ function _getSelectorFromCall(bytes memory _call) internal pure returns (bytes4 selector) {
77
+ assembly {
78
+ selector := mload(add(_call, 32))
79
+ }
80
+ }
81
+
82
+ function _handleBuy(address buyRecipient, ICoin coin, uint256 amountCurrency) internal returns (uint256 coinsPurchased) {
83
+ IERC20(coin.currency()).approve(address(coin), amountCurrency);
84
+
85
+ if (CoinConfigurationVersions.isV4(factory.getVersionForDeployedCoin(address(coin)))) {
86
+ coinsPurchased = _executeV4Buy(buyRecipient, ICoinV4(payable(address(coin))), amountCurrency);
87
+ } else {
88
+ coinsPurchased = _executeV3Buy(buyRecipient, ICoinV3(payable(address(coin))), amountCurrency);
89
+ }
90
+
91
+ // make sure that this contract has no balance of the coin remaining
92
+ uint256 coinBalance = IERC20(address(coin)).balanceOf(address(this));
93
+ require(coinBalance == 0, CoinBalanceNot0(coinBalance));
94
+ // make sure that this contract has no balance of the currency remaining
95
+ uint256 currencyBalance = IERC20(coin.currency()).balanceOf(address(this));
96
+ require(currencyBalance == 0, CurrencyBalanceNot0(currencyBalance));
97
+ }
98
+
99
+ function _executeV3Buy(address buyRecipient, ICoinV3 coin, uint256 amountCurrency) internal returns (uint256 coinsPurchased) {
100
+ (, coinsPurchased) = ICoinV3(payable(address(coin))).buy(buyRecipient, amountCurrency, 0, 0, address(0));
101
+ }
102
+
103
+ function _executeV4Buy(address buyRecipient, ICoinV4 coin, uint256 amountCurrency) internal returns (uint256 coinsPurchased) {
104
+ bytes memory data = abi.encode(buyRecipient, coin, amountCurrency);
105
+
106
+ bytes memory result = poolManager.unlock(data);
107
+
108
+ coinsPurchased = abi.decode(result, (uint256));
109
+ }
110
+
111
+ error OnlyPoolManager();
112
+
113
+ /// @notice Internal fn called when the PoolManager is unlocked. Used to swap the backing currency for the coin.
114
+ function unlockCallback(bytes calldata data) external returns (bytes memory) {
115
+ require(msg.sender == address(poolManager), OnlyPoolManager());
116
+
117
+ (address buyRecipient, ICoinV4 coin, uint256 amountCurrency) = abi.decode(data, (address, ICoinV4, uint256));
118
+
119
+ bool zeroForOne = coin.currency() == Currency.unwrap(coin.getPoolKey().currency0);
120
+
121
+ BalanceDelta delta = poolManager.swap(
122
+ coin.getPoolKey(),
123
+ SwapParams(zeroForOne, -(int128(uint128(amountCurrency))), zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),
124
+ ""
125
+ );
126
+
127
+ int128 amountCoin = zeroForOne ? delta.amount1() : delta.amount0();
128
+
129
+ // sync the currency balance before transferring to the pool manager
130
+ poolManager.sync(Currency.wrap(coin.currency()));
131
+ // transfer the currency to the pool manager for the swap
132
+ IERC20(coin.currency()).transfer(address(poolManager), uint256(uint128(amountCurrency)));
133
+ // collect the coin from the pool manager
134
+ poolManager.take(Currency.wrap(address(coin)), buyRecipient, uint256(uint128(amountCoin)));
135
+
136
+ poolManager.settle();
137
+
138
+ return abi.encode(uint256(uint128(amountCoin)));
139
+ }
140
+ }
@@ -4,18 +4,34 @@ pragma solidity ^0.8.23;
4
4
  import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
5
5
  import {IERC7572} from "./IERC7572.sol";
6
6
  import {IDopplerErrors} from "./IDopplerErrors.sol";
7
+ import {PoolKey} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
8
+ import {PoolConfiguration} from "../types/PoolConfiguration.sol";
7
9
 
8
- /// @notice The configuration of the pool
9
- /// @dev This is used to configure the pool's liquidity positions
10
- struct PoolConfiguration {
10
+ struct PoolConfigurationV4 {
11
11
  uint8 version;
12
- int24 tickLower;
13
- int24 tickUpper;
14
- uint16 numPositions;
15
- uint256 maxDiscoverySupplyShare;
12
+ PoolKey poolKey;
13
+ int24 tick;
16
14
  }
17
15
 
18
- interface ICoin is IERC165, IERC7572, IDopplerErrors {
16
+ struct PoolKeyStruct {
17
+ address currency0;
18
+ address currency1;
19
+ uint24 fee;
20
+ int24 tickSpacing;
21
+ address hooks;
22
+ }
23
+
24
+ interface IHasRewardsRecipients {
25
+ function payoutRecipient() external view returns (address);
26
+
27
+ function platformReferrer() external view returns (address);
28
+
29
+ function protocolRewardRecipient() external view returns (address);
30
+
31
+ function dopplerFeeRecipient() external view returns (address);
32
+ }
33
+
34
+ interface ICoin is IERC165, IERC7572, IDopplerErrors, IHasRewardsRecipients {
19
35
  /// @notice Thrown when an operation is attempted with a zero address
20
36
  error AddressZero();
21
37
 
@@ -73,6 +89,9 @@ interface ICoin is IERC165, IERC7572, IDopplerErrors {
73
89
  /// @notice Thrown when a Doppler pool does not have more than 2 discovery positions
74
90
  error DopplerPoolMustHaveMoreThan2DiscoveryPositions();
75
91
 
92
+ /// @notice Thrown when an invalid pool version is specified
93
+ error InvalidPoolVersion();
94
+
76
95
  /// @notice The rewards accrued from the market's liquidity position
77
96
  struct MarketRewards {
78
97
  uint256 totalAmountCurrency;
@@ -177,33 +196,6 @@ interface ICoin is IERC165, IERC7572, IDopplerErrors {
177
196
  /// @param name The coin name
178
197
  event ContractMetadataUpdated(address indexed caller, string newURI, string name);
179
198
 
180
- /// @notice Executes a buy order
181
- /// @param recipient The recipient address of the coins
182
- /// @param orderSize The amount of coins to buy
183
- /// @param tradeReferrer The address of the trade referrer
184
- /// @param sqrtPriceLimitX96 The price limit for Uniswap V3 pool swap
185
- function buy(
186
- address recipient,
187
- uint256 orderSize,
188
- uint256 minAmountOut,
189
- uint160 sqrtPriceLimitX96,
190
- address tradeReferrer
191
- ) external payable returns (uint256, uint256);
192
-
193
- /// @notice Executes a sell order
194
- /// @param recipient The recipient of the currency
195
- /// @param orderSize The amount of coins to sell
196
- /// @param minAmountOut The minimum amount of currency to receive
197
- /// @param sqrtPriceLimitX96 The price limit for the swap
198
- /// @param tradeReferrer The address of the trade referrer
199
- function sell(
200
- address recipient,
201
- uint256 orderSize,
202
- uint256 minAmountOut,
203
- uint160 sqrtPriceLimitX96,
204
- address tradeReferrer
205
- ) external returns (uint256, uint256);
206
-
207
199
  /// @notice Enables a user to burn their tokens
208
200
  /// @param amount The amount of tokens to burn
209
201
  function burn(uint256 amount) external;
@@ -212,11 +204,11 @@ interface ICoin is IERC165, IERC7572, IDopplerErrors {
212
204
  /// @return The token URI
213
205
  function tokenURI() external view returns (string memory);
214
206
 
215
- /// @notice Returns the address of the platform referrer
216
- /// @return The platform referrer's address
217
- function platformReferrer() external view returns (address);
218
-
219
207
  /// @notice Returns the address of the currency
220
208
  /// @return The currency's address
221
209
  function currency() external view returns (address);
210
+
211
+ /// @notice Returns the address of the Airlock
212
+ /// @return The Airlock's address
213
+ function airlock() external view returns (address);
222
214
  }
@@ -0,0 +1,71 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {ICoin} from "./ICoin.sol";
5
+ import {LpPosition} from "../types/LpPosition.sol";
6
+ import {PoolConfiguration} from "./ICoin.sol";
7
+
8
+ interface ICoinV3 is ICoin {
9
+ /// @notice Returns the WETH address
10
+ function WETH() external view returns (address);
11
+
12
+ /// @notice Returns the address of the Uniswap V3 factory
13
+ function v3Factory() external view returns (address);
14
+
15
+ /// @notice Initializes a new coin
16
+ /// @param payoutRecipient_ The address of the coin creator
17
+ /// @param tokenURI_ The metadata URI
18
+ /// @param name_ The coin name
19
+ /// @param symbol_ The coin symbol
20
+ /// @param platformReferrer_ The address of the platform referrer
21
+ /// @param currency_ The address of the currency
22
+ /// @param poolAddress_ The address of the pool
23
+ /// @param poolConfiguration_ The configuration of the pool
24
+ function initialize(
25
+ address payoutRecipient_,
26
+ address[] memory owners_,
27
+ string memory tokenURI_,
28
+ string memory name_,
29
+ string memory symbol_,
30
+ address platformReferrer_,
31
+ address currency_,
32
+ address poolAddress_,
33
+ PoolConfiguration memory poolConfiguration_,
34
+ LpPosition[] memory positions_
35
+ ) external;
36
+
37
+ /// @notice Executes a buy order
38
+ /// @param recipient The recipient address of the coins
39
+ /// @param orderSize The amount of coins to buy
40
+ /// @param tradeReferrer The address of the trade referrer
41
+ /// @param sqrtPriceLimitX96 The price limit for Uniswap V3 pool swap
42
+ function buy(
43
+ address recipient,
44
+ uint256 orderSize,
45
+ uint256 minAmountOut,
46
+ uint160 sqrtPriceLimitX96,
47
+ address tradeReferrer
48
+ ) external payable returns (uint256, uint256);
49
+
50
+ /// @notice Executes a sell order
51
+ /// @param recipient The recipient of the currency
52
+ /// @param orderSize The amount of coins to sell
53
+ /// @param minAmountOut The minimum amount of currency to receive
54
+ /// @param sqrtPriceLimitX96 The price limit for the swap
55
+ /// @param tradeReferrer The address of the trade referrer
56
+ function sell(
57
+ address recipient,
58
+ uint256 orderSize,
59
+ uint256 minAmountOut,
60
+ uint160 sqrtPriceLimitX96,
61
+ address tradeReferrer
62
+ ) external returns (uint256, uint256);
63
+
64
+ /// @notice Force claim any accrued secondary rewards from the market's liquidity position.
65
+ /// @dev This function is a fallback, secondary rewards will be claimed automatically on each buy and sell.
66
+ /// @param pushEthRewards Whether to push the ETH directly to the recipients.
67
+ function claimSecondaryRewards(bool pushEthRewards) external;
68
+
69
+ /// @notice Returns the address of the Uniswap V3 pool
70
+ function poolAddress() external view returns (address);
71
+ }
@@ -0,0 +1,69 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {ICoin} from "./ICoin.sol";
5
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
6
+ import {PoolConfiguration} from "../types/PoolConfiguration.sol";
7
+ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
8
+ import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
9
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
10
+ import {IDeployedCoinVersionLookup} from "./IDeployedCoinVersionLookup.sol";
11
+
12
+ /// @notice Returns the pool key for the coin
13
+ interface IHasPoolKey {
14
+ /// @notice Returns the Uniswap V4 pool key associated with this coin
15
+ /// @return The PoolKey struct containing pool identification parameters
16
+ function getPoolKey() external view returns (PoolKey memory);
17
+ }
18
+
19
+ /// @notice Returns the pool configuration for the coin
20
+ interface IHasSwapPath {
21
+ /// @notice Struct containing the swap path configuration for converting fees to payout currency
22
+ /// @param path Array of PathKey structs defining the multi-hop swap route
23
+ /// @param currencyIn The input currency to start the swap path from
24
+ struct PayoutSwapPath {
25
+ PathKey[] path;
26
+ Currency currencyIn;
27
+ }
28
+
29
+ /// @notice Returns the swap path configuration for converting this coin to its final payout currency
30
+ /// @dev This enables multi-hop swaps through intermediate currencies to reach the target payout token
31
+ /// @param coinVersionLookup Contract for looking up deployed coin versions to build recursive paths
32
+ /// @return PayoutSwapPath struct containing the complete swap route configuration
33
+ function getPayoutSwapPath(IDeployedCoinVersionLookup coinVersionLookup) external view returns (PayoutSwapPath memory);
34
+ }
35
+
36
+ interface ICoinV4 is ICoin, IHasPoolKey, IHasSwapPath {
37
+ /// @notice Returns the pool configuration settings for this coin's Uniswap V4 pool
38
+ /// @return PoolConfiguration struct containing pool-specific settings and parameters
39
+ function getPoolConfiguration() external view returns (PoolConfiguration memory);
40
+
41
+ /// @notice Returns the hooks contract used by this coin's Uniswap V4 pool
42
+ /// @return The IHooks contract interface that handles pool lifecycle events
43
+ function hooks() external view returns (IHooks);
44
+
45
+ /// @notice Initializes the coin
46
+ /// @dev Called by the factory contract when the contract is deployed.
47
+ /// @param payoutRecipient_ The address of the payout recipient. Can be updated by the owner. Cannot be 0 address.
48
+ /// @param owners_ The addresses of the owners. All owners have the same full admin access. Cannot be 0 address.
49
+ /// @param tokenURI_ The URI of the token. Can be updated by the owner.
50
+ /// @param name_ The name of the token. Cannot be updated.
51
+ /// @param symbol_ The symbol of the token. Cannot be updated.
52
+ /// @param platformReferrer_ The address of the platform referrer. Cannot be updated.
53
+ /// @param currency_ The currency of the coin. Cannot be updated. Can be the zero address for ETH.
54
+ /// @param poolKey_ The pool key for the coin. Derived in the factory.
55
+ /// @param sqrtPriceX96 The initial sqrt price for the pool
56
+ /// @param poolConfiguration_ The configuration for the pool
57
+ function initialize(
58
+ address payoutRecipient_,
59
+ address[] memory owners_,
60
+ string memory tokenURI_,
61
+ string memory name_,
62
+ string memory symbol_,
63
+ address platformReferrer_,
64
+ address currency_,
65
+ PoolKey memory poolKey_,
66
+ uint160 sqrtPriceX96,
67
+ PoolConfiguration memory poolConfiguration_
68
+ ) external;
69
+ }
@@ -0,0 +1,11 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ /// @title IDeployedCoinVersionLookup
5
+ /// @notice Interface for querying the version of a deployed coin
6
+ interface IDeployedCoinVersionLookup {
7
+ /// @notice Gets the version for a deployed coin
8
+ /// @param coin The address of the coin
9
+ /// @return version The version of the coin (0 if not found)
10
+ function getVersionForDeployedCoin(address coin) external view returns (uint8);
11
+ }
@@ -0,0 +1,9 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ /// @notice Interface for getting the correct message sender.
5
+ interface IMsgSender {
6
+ /// @notice Returns the address of the message sender.
7
+ /// @return The address of the message sender.
8
+ function msgSender() external view returns (address);
9
+ }
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ /// @dev contract to enable encoding pool config from the client
5
+ interface IPoolConfigEncoding {
6
+ function encodeMultiCurvePoolConfig(
7
+ uint8 version,
8
+ address currency,
9
+ int24[] memory tickLower,
10
+ int24[] memory tickUpper,
11
+ uint16[] memory numDiscoveryPositions,
12
+ uint256[] memory maxDiscoverySupplyShare
13
+ ) external pure returns (bytes memory);
14
+ }
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.25;
3
+
4
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
5
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
6
+
7
+ interface ISwapPathRouter {
8
+ struct Path {
9
+ PoolKey key;
10
+ Currency currencyIn;
11
+ }
12
+
13
+ function getSwapPath(PoolKey memory key, Currency toSwapOut) external view returns (Path[] memory path);
14
+ }