@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
@@ -13,21 +13,15 @@ import {LpPosition} from "../types/LpPosition.sol";
13
13
  import {PoolConfiguration} from "../interfaces/ICoin.sol";
14
14
  import {IUniswapV3Pool} from "../interfaces/IUniswapV3Pool.sol";
15
15
  import {IAirlock} from "../interfaces/IAirlock.sol";
16
- import {UniV3Config, CoinV3Config} from "./CoinSetupV3.sol";
16
+ import {CoinV3Config} from "./CoinSetupV3.sol";
17
17
  import {Address} from "@openzeppelin/contracts/utils/Address.sol";
18
- import {CoinLegacyMarket} from "./CoinLegacyMarket.sol";
19
18
  import {CoinDopplerUniV3} from "./CoinDopplerUniV3.sol";
20
19
  import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
21
-
22
- struct CoinConfig {
23
- address protocolRewardRecipient;
24
- address platformReferrer;
25
- address currency;
26
- address payoutRecipient;
27
- address protocolRewards;
28
- address poolAddress;
29
- PoolConfiguration poolConfiguration;
30
- UniV3Config uniswapV3Config;
20
+ import {CoinRewards, CoinConfig} from "./CoinRewards.sol";
21
+ struct SellResult {
22
+ uint256 payoutSize;
23
+ uint256 tradeReward;
24
+ uint256 trueOrderSize;
31
25
  }
32
26
 
33
27
  library UniV3BuySell {
@@ -36,14 +30,16 @@ library UniV3BuySell {
36
30
  error AddressZero();
37
31
  error InvalidPoolVersion();
38
32
 
39
- function _handleBuy(
33
+ function handleBuy(
40
34
  address recipient,
41
35
  uint256 orderSize,
42
36
  uint256 minAmountOut,
43
37
  uint160 sqrtPriceLimitX96,
44
38
  address tradeReferrer,
45
- address coin,
46
- CoinConfig memory coinConfig
39
+ CoinConfig memory coinConfig,
40
+ address currency,
41
+ ISwapRouter swapRouter,
42
+ IWETH weth
47
43
  ) internal returns (uint256 amountOut, uint256 tradeReward, uint256 trueOrderSize) {
48
44
  if (recipient == address(0)) {
49
45
  revert AddressZero();
@@ -56,12 +52,12 @@ library UniV3BuySell {
56
52
  trueOrderSize = orderSize - tradeReward;
57
53
 
58
54
  // Handle incoming currency
59
- _handleIncomingCurrency(orderSize, trueOrderSize, coinConfig.currency, coinConfig.uniswapV3Config.weth, coinConfig.uniswapV3Config.swapRouter);
55
+ _handleIncomingCurrency(orderSize, trueOrderSize, currency, swapRouter, weth);
60
56
 
61
57
  // Set up the swap parameters
62
58
  ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
63
- tokenIn: coinConfig.currency,
64
- tokenOut: coin,
59
+ tokenIn: currency,
60
+ tokenOut: address(this),
65
61
  fee: MarketConstants.LP_FEE,
66
62
  recipient: recipient,
67
63
  amountIn: trueOrderSize,
@@ -70,60 +66,21 @@ library UniV3BuySell {
70
66
  });
71
67
 
72
68
  // Execute the swap
73
- amountOut = ISwapRouter(coinConfig.uniswapV3Config.swapRouter).exactInputSingle(params);
69
+ amountOut = ISwapRouter(swapRouter).exactInputSingle(params);
74
70
 
75
- _handleTradeRewards(tradeReward, tradeReferrer, coinConfig);
71
+ CoinRewards.handleTradeRewards(tradeReward, tradeReferrer, coinConfig, currency, weth);
76
72
  }
77
73
 
78
- /// @notice Executes a buy order
79
- /// @param recipient The recipient address of the coins
80
- /// @param orderSize The amount of coins to buy
81
- /// @param tradeReferrer The address of the trade referrer
82
- /// @param sqrtPriceLimitX96 The price limit for Uniswap V3 pool swap
83
- function buy(
84
- address recipient,
74
+ function _executeSwap(
85
75
  uint256 orderSize,
86
76
  uint256 minAmountOut,
87
77
  uint160 sqrtPriceLimitX96,
88
- address tradeReferrer,
89
- address coin,
90
- CoinConfig memory coinConfig
91
- ) internal returns (uint256, uint256) {
92
- (uint256 amountOut, uint256 tradeReward, uint256 trueOrderSize) = _handleBuy(
93
- recipient,
94
- orderSize,
95
- minAmountOut,
96
- sqrtPriceLimitX96,
97
- tradeReferrer,
98
- coin,
99
- coinConfig
100
- );
101
-
102
- handleMarketRewards(coin, coinConfig);
103
-
104
- emit ICoin.CoinBuy(msg.sender, recipient, tradeReferrer, amountOut, coinConfig.currency, tradeReward, trueOrderSize);
105
-
106
- return (orderSize, amountOut);
107
- }
108
-
109
- function _handleSell(
110
- address recipient,
111
- uint256 beforeCoinBalance,
112
- uint256 orderSize,
113
- uint256 minAmountOut,
114
- uint160 sqrtPriceLimitX96,
115
- address tradeReferrer,
116
- CoinConfig memory coinConfig
117
- ) internal returns (uint256 payoutSize, uint256 tradeReward, uint256 trueOrderSize) {
118
- // Ensure the recipient is not the zero address
119
- if (recipient == address(0)) {
120
- revert AddressZero();
121
- }
122
-
123
- // Set the swap parameters
78
+ address currency,
79
+ ISwapRouter swapRouter
80
+ ) internal returns (uint256 amountOut) {
124
81
  ISwapRouter.ExactInputSingleParams memory params = ISwapRouter.ExactInputSingleParams({
125
82
  tokenIn: address(this),
126
- tokenOut: coinConfig.currency,
83
+ tokenOut: currency,
127
84
  fee: MarketConstants.LP_FEE,
128
85
  recipient: address(this),
129
86
  amountIn: orderSize,
@@ -131,78 +88,66 @@ library UniV3BuySell {
131
88
  sqrtPriceLimitX96: sqrtPriceLimitX96
132
89
  });
133
90
 
134
- // Execute the swap
135
- uint256 amountOut = ISwapRouter(coinConfig.uniswapV3Config.swapRouter).exactInputSingle(params);
91
+ amountOut = swapRouter.exactInputSingle(params);
92
+ }
136
93
 
137
- // Record the coin balance of this contract after the swap
94
+ function _handleRefund(uint256 beforeCoinBalance, uint256 orderSize, address recipient) internal returns (uint256 trueOrderSize) {
138
95
  uint256 afterCoinBalance = IERC20(address(this)).balanceOf(address(this));
139
-
140
96
  trueOrderSize = orderSize;
141
97
 
142
- // If the swap was partially executed:
143
98
  if (afterCoinBalance > beforeCoinBalance) {
144
- // Calculate the refund
145
99
  uint256 coinRefund = afterCoinBalance - beforeCoinBalance;
146
-
147
- // Update the order size
148
100
  trueOrderSize -= coinRefund;
149
-
150
- // Transfer the refund back to the seller
151
101
  IERC20(address(this)).safeTransfer(recipient, coinRefund);
152
102
  }
103
+ }
153
104
 
154
- // If currency is WETH, convert to ETH
155
- if (coinConfig.currency == coinConfig.uniswapV3Config.weth) {
156
- IWETH(coinConfig.uniswapV3Config.weth).withdraw(amountOut);
105
+ function _handlePayoutAndRewards(
106
+ uint256 amountOut,
107
+ address recipient,
108
+ address tradeReferrer,
109
+ CoinConfig memory coinConfig,
110
+ address currency,
111
+ IWETH weth
112
+ ) internal returns (uint256 payoutSize, uint256 tradeReward) {
113
+ if (currency == address(weth)) {
114
+ weth.withdraw(amountOut);
157
115
  }
158
116
 
159
- // Calculate the trade reward
160
117
  tradeReward = _calculateReward(amountOut, CoinConstants.TOTAL_FEE_BPS);
161
-
162
- // Calculate the payout after the fee
163
118
  payoutSize = amountOut - tradeReward;
164
119
 
165
- _handlePayout(payoutSize, recipient, coinConfig.currency, coinConfig.uniswapV3Config.weth);
120
+ _handlePayout(payoutSize, recipient, currency, weth);
166
121
 
167
- _handleTradeRewards(tradeReward, tradeReferrer, coinConfig);
122
+ CoinRewards.handleTradeRewards(tradeReward, tradeReferrer, coinConfig, currency, weth);
168
123
  }
169
124
 
170
- /// @notice Executes a sell order
171
- /// @param recipient The recipient of the currency
172
- /// @param _orderSize The amount of coins to sell
173
- /// @param minAmountOut The minimum amount of currency to receive
174
- /// @param sqrtPriceLimitX96 The price limit for the swap
175
- /// @param tradeReferrer The address of the trade referrer
176
- function sell(
125
+ function handleSell(
177
126
  address recipient,
178
127
  uint256 beforeCoinBalance,
179
- uint256 _orderSize,
128
+ uint256 orderSize,
180
129
  uint256 minAmountOut,
181
130
  uint160 sqrtPriceLimitX96,
182
131
  address tradeReferrer,
183
- CoinConfig memory coinConfig
184
- ) internal returns (uint256 trueOrderSize, uint256 payoutSize) {
185
- uint256 tradeReward;
186
- (payoutSize, tradeReward, trueOrderSize) = _handleSell(
187
- recipient,
188
- beforeCoinBalance,
189
- _orderSize,
190
- minAmountOut,
191
- sqrtPriceLimitX96,
192
- tradeReferrer,
193
- coinConfig
194
- );
195
-
196
- handleMarketRewards(address(this), coinConfig);
197
-
198
- emit ICoin.CoinSell(msg.sender, recipient, tradeReferrer, trueOrderSize, coinConfig.currency, tradeReward, payoutSize);
132
+ CoinConfig memory coinConfig,
133
+ address currency,
134
+ ISwapRouter swapRouter,
135
+ IWETH weth
136
+ ) internal returns (SellResult memory result) {
137
+ if (recipient == address(0)) {
138
+ revert AddressZero();
139
+ }
140
+
141
+ uint256 amountOut = _executeSwap(orderSize, minAmountOut, sqrtPriceLimitX96, currency, swapRouter);
142
+ result.trueOrderSize = _handleRefund(beforeCoinBalance, orderSize, recipient);
143
+ (result.payoutSize, result.tradeReward) = _handlePayoutAndRewards(amountOut, recipient, tradeReferrer, coinConfig, currency, weth);
199
144
  }
200
145
 
201
146
  /// @dev Handles incoming currency transfers for buy orders; if WETH is the currency the caller has the option to send native-ETH
202
147
  /// @param orderSize The total size of the order in the currency
203
148
  /// @param trueOrderSize The actual amount being used for the swap after fees
204
- function _handleIncomingCurrency(uint256 orderSize, uint256 trueOrderSize, address currency, address weth, address swapRouter) internal {
205
- if (currency == weth && msg.value > 0) {
149
+ function _handleIncomingCurrency(uint256 orderSize, uint256 trueOrderSize, address currency, ISwapRouter swapRouter, IWETH weth) internal {
150
+ if (currency == address(weth) && msg.value > 0) {
206
151
  if (msg.value != orderSize) {
207
152
  revert ICoin.EthAmountMismatch();
208
153
  }
@@ -212,7 +157,7 @@ library UniV3BuySell {
212
157
  }
213
158
 
214
159
  IWETH(weth).deposit{value: trueOrderSize}();
215
- IWETH(weth).approve(swapRouter, trueOrderSize);
160
+ IWETH(weth).approve(address(swapRouter), trueOrderSize);
216
161
  } else {
217
162
  // Ensure ETH is not sent with a non-ETH pair
218
163
  if (msg.value != 0) {
@@ -227,100 +172,27 @@ library UniV3BuySell {
227
172
  revert ICoin.ERC20TransferAmountMismatch();
228
173
  }
229
174
 
230
- IERC20(currency).approve(swapRouter, trueOrderSize);
175
+ IERC20(currency).approve(address(swapRouter), trueOrderSize);
231
176
  }
232
177
  }
233
178
 
234
179
  /// @dev Handles sending ETH and ERC20 payouts and refunds to recipients
235
180
  /// @param orderPayout The amount of currency to pay out
236
181
  /// @param recipient The address to receive the payout
237
- function _handlePayout(uint256 orderPayout, address recipient, address currency, address weth) internal {
238
- if (currency == weth) {
182
+ function _handlePayout(uint256 orderPayout, address recipient, address currency, IWETH weth) internal {
183
+ if (currency == address(weth)) {
239
184
  Address.sendValue(payable(recipient), orderPayout);
240
185
  } else {
241
186
  IERC20(currency).safeTransfer(recipient, orderPayout);
242
187
  }
243
188
  }
244
189
 
245
- /// @dev Handles calculating and depositing fees to an escrow protocol rewards contract
246
- function _handleTradeRewards(uint256 totalValue, address _tradeReferrer, CoinConfig memory coinConfig) internal {
247
- address protocolRewardRecipient = coinConfig.protocolRewardRecipient;
248
- address platformReferrer = coinConfig.platformReferrer;
249
- address currency = coinConfig.currency;
250
- address weth = coinConfig.uniswapV3Config.weth;
251
- address payoutRecipient = coinConfig.payoutRecipient;
252
- IProtocolRewards protocolRewards = IProtocolRewards(coinConfig.protocolRewards);
253
-
254
- if (_tradeReferrer == address(0)) {
255
- _tradeReferrer = protocolRewardRecipient;
256
- }
257
-
258
- uint256 tokenCreatorFee = _calculateReward(totalValue, CoinConstants.TOKEN_CREATOR_FEE_BPS);
259
- uint256 platformReferrerFee = _calculateReward(totalValue, CoinConstants.PLATFORM_REFERRER_FEE_BPS);
260
- uint256 tradeReferrerFee = _calculateReward(totalValue, CoinConstants.TRADE_REFERRER_FEE_BPS);
261
- uint256 protocolFee = totalValue - tokenCreatorFee - platformReferrerFee - tradeReferrerFee;
262
-
263
- if (currency == weth) {
264
- address[] memory recipients = new address[](4);
265
- uint256[] memory amounts = new uint256[](4);
266
- bytes4[] memory reasons = new bytes4[](4);
267
-
268
- recipients[0] = payoutRecipient;
269
- amounts[0] = tokenCreatorFee;
270
- reasons[0] = bytes4(keccak256("COIN_CREATOR_REWARD"));
271
-
272
- recipients[1] = platformReferrer;
273
- amounts[1] = platformReferrerFee;
274
- reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_REWARD"));
275
-
276
- recipients[2] = _tradeReferrer;
277
- amounts[2] = tradeReferrerFee;
278
- reasons[2] = bytes4(keccak256("COIN_TRADE_REFERRER_REWARD"));
279
-
280
- recipients[3] = protocolRewardRecipient;
281
- amounts[3] = protocolFee;
282
- reasons[3] = bytes4(keccak256("COIN_PROTOCOL_REWARD"));
283
-
284
- IProtocolRewards(protocolRewards).depositBatch{value: totalValue}(recipients, amounts, reasons, "");
285
- }
286
-
287
- if (currency != weth) {
288
- IERC20(currency).safeTransfer(payoutRecipient, tokenCreatorFee);
289
- IERC20(currency).safeTransfer(platformReferrer, platformReferrerFee);
290
- IERC20(currency).safeTransfer(_tradeReferrer, tradeReferrerFee);
291
- IERC20(currency).safeTransfer(protocolRewardRecipient, protocolFee);
292
- }
293
-
294
- emit ICoin.CoinTradeRewards(
295
- payoutRecipient,
296
- platformReferrer,
297
- _tradeReferrer,
298
- protocolRewardRecipient,
299
- tokenCreatorFee,
300
- platformReferrerFee,
301
- tradeReferrerFee,
302
- protocolFee,
303
- currency
304
- );
305
- }
306
-
307
- function _distributeMarketRewards(
308
- LpPosition[] memory positions,
309
- address poolAddress,
310
- address currency,
311
- address coin,
312
- CoinConfig memory coinConfig
313
- ) internal returns (ICoin.MarketRewards memory) {
314
- uint256 totalAmountToken0;
315
- uint256 totalAmountToken1;
316
- uint256 amount0;
317
- uint256 amount1;
318
-
190
+ function _collectFees(LpPosition[] storage positions, address poolAddress) internal returns (uint256 totalAmountToken0, uint256 totalAmountToken1) {
319
191
  for (uint256 i; i < positions.length; i++) {
320
192
  // Must burn to update the collect mapping on the pool
321
193
  IUniswapV3Pool(poolAddress).burn(positions[i].tickLower, positions[i].tickUpper, 0);
322
194
 
323
- (amount0, amount1) = IUniswapV3Pool(poolAddress).collect(
195
+ (uint256 amount0, uint256 amount1) = IUniswapV3Pool(poolAddress).collect(
324
196
  address(this),
325
197
  positions[i].tickLower,
326
198
  positions[i].tickUpper,
@@ -331,119 +203,29 @@ library UniV3BuySell {
331
203
  totalAmountToken0 += amount0;
332
204
  totalAmountToken1 += amount1;
333
205
  }
334
-
335
- address token0 = currency < address(this) ? currency : address(this);
336
- address token1 = currency < address(this) ? address(this) : currency;
337
-
338
- ICoin.MarketRewards memory rewards;
339
-
340
- rewards = _transferMarketRewards(token0, totalAmountToken0, rewards, coin, coinConfig);
341
- rewards = _transferMarketRewards(token1, totalAmountToken1, rewards, coin, coinConfig);
342
-
343
- emit ICoin.CoinMarketRewards(coinConfig.payoutRecipient, coinConfig.platformReferrer, coinConfig.protocolRewardRecipient, coinConfig.currency, rewards);
344
-
345
- return rewards;
346
206
  }
347
207
 
348
208
  /// @dev Collects and distributes accrued fees from all LP positions
349
- function handleMarketRewards(address coin, CoinConfig memory coinConfig) internal returns (ICoin.MarketRewards memory) {
350
- address poolAddress = coinConfig.poolAddress;
351
- address currency = coinConfig.currency;
352
-
353
- bool isCoinToken0 = coin < currency;
354
- LpPosition[] memory positions = calculatePositions(isCoinToken0, coinConfig.poolConfiguration);
209
+ function handleMarketRewards(
210
+ CoinConfig memory coinConfig,
211
+ address currency,
212
+ address poolAddress,
213
+ LpPosition[] storage positions,
214
+ IWETH weth,
215
+ address doppler
216
+ ) internal returns (ICoin.MarketRewards memory rewards) {
217
+ address coin = address(this);
218
+ (uint256 totalAmountToken0, uint256 totalAmountToken1) = _collectFees(positions, poolAddress);
355
219
 
356
- return _distributeMarketRewards(positions, poolAddress, currency, coin, coinConfig);
357
- }
220
+ address token0 = currency < coin ? currency : coin;
221
+ address token1 = currency < coin ? coin : currency;
358
222
 
359
- function _transferMarketRewards(
360
- address token,
361
- uint256 totalAmount,
362
- ICoin.MarketRewards memory rewards,
363
- address coin,
364
- CoinConfig memory coinConfig
365
- ) internal returns (ICoin.MarketRewards memory) {
366
- address payoutRecipient = coinConfig.payoutRecipient;
367
- address platformReferrer = coinConfig.platformReferrer;
368
- address protocolRewardRecipient = coinConfig.protocolRewardRecipient;
369
- address currency = coinConfig.currency;
370
- address weth = coinConfig.uniswapV3Config.weth;
371
- address airlock = coinConfig.uniswapV3Config.airlock;
372
- address protocolRewards = coinConfig.protocolRewards;
373
-
374
- if (totalAmount > 0) {
375
- address dopplerRecipient = IAirlock(airlock).owner();
376
- uint256 dopplerPayout = _calculateReward(totalAmount, CoinConstants.DOPPLER_MARKET_REWARD_BPS);
377
- uint256 creatorPayout = _calculateReward(totalAmount, CoinConstants.CREATOR_MARKET_REWARD_BPS);
378
- uint256 platformReferrerPayout = _calculateReward(totalAmount, CoinConstants.PLATFORM_REFERRER_MARKET_REWARD_BPS);
379
- uint256 protocolPayout = totalAmount - creatorPayout - platformReferrerPayout - dopplerPayout;
380
-
381
- if (token == weth) {
382
- IWETH(weth).withdraw(totalAmount);
383
-
384
- rewards.totalAmountCurrency = totalAmount;
385
- rewards.creatorPayoutAmountCurrency = creatorPayout;
386
- rewards.platformReferrerAmountCurrency = platformReferrerPayout;
387
- rewards.protocolAmountCurrency = protocolPayout;
388
-
389
- address[] memory recipients = new address[](4);
390
- recipients[0] = payoutRecipient;
391
- recipients[1] = platformReferrer;
392
- recipients[2] = protocolRewardRecipient;
393
- recipients[3] = dopplerRecipient;
394
-
395
- uint256[] memory amounts = new uint256[](4);
396
- amounts[0] = rewards.creatorPayoutAmountCurrency;
397
- amounts[1] = rewards.platformReferrerAmountCurrency;
398
- amounts[2] = rewards.protocolAmountCurrency;
399
- amounts[3] = dopplerPayout;
400
-
401
- bytes4[] memory reasons = new bytes4[](4);
402
- reasons[0] = bytes4(keccak256("COIN_CREATOR_MARKET_REWARD"));
403
- reasons[1] = bytes4(keccak256("COIN_PLATFORM_REFERRER_MARKET_REWARD"));
404
- reasons[2] = bytes4(keccak256("COIN_PROTOCOL_MARKET_REWARD"));
405
- reasons[3] = bytes4(keccak256("COIN_DOPPLER_MARKET_REWARD"));
406
-
407
- IProtocolRewards(protocolRewards).depositBatch{value: totalAmount}(recipients, amounts, reasons, "");
408
- IProtocolRewards(protocolRewards).withdrawFor(dopplerRecipient, dopplerPayout);
409
- } else if (token == coin) {
410
- rewards.totalAmountCoin = totalAmount;
411
- rewards.creatorPayoutAmountCoin = creatorPayout;
412
- rewards.platformReferrerAmountCoin = platformReferrerPayout;
413
- rewards.protocolAmountCoin = protocolPayout;
414
-
415
- IERC20(coin).safeTransfer(payoutRecipient, rewards.creatorPayoutAmountCoin);
416
- IERC20(coin).safeTransfer(platformReferrer, rewards.platformReferrerAmountCoin);
417
- IERC20(coin).safeTransfer(protocolRewardRecipient, rewards.protocolAmountCoin);
418
- IERC20(coin).safeTransfer(dopplerRecipient, dopplerPayout);
419
- } else {
420
- rewards.totalAmountCurrency = totalAmount;
421
- rewards.creatorPayoutAmountCurrency = creatorPayout;
422
- rewards.platformReferrerAmountCurrency = platformReferrerPayout;
423
- rewards.protocolAmountCurrency = protocolPayout;
424
-
425
- IERC20(currency).safeTransfer(payoutRecipient, creatorPayout);
426
- IERC20(currency).safeTransfer(platformReferrer, platformReferrerPayout);
427
- IERC20(currency).safeTransfer(protocolRewardRecipient, protocolPayout);
428
- IERC20(currency).safeTransfer(dopplerRecipient, dopplerPayout);
429
- }
430
- }
223
+ rewards = CoinRewards.transferBothRewards(token0, totalAmountToken0, token1, totalAmountToken1, coin, coinConfig, currency, weth, doppler);
431
224
 
432
- return rewards;
225
+ emit ICoin.CoinMarketRewards(coinConfig.payoutRecipient, coinConfig.platformReferrer, coinConfig.protocolRewardRecipient, currency, rewards);
433
226
  }
434
227
 
435
- /// @dev Utility for computing amounts in basis points.
436
228
  function _calculateReward(uint256 amount, uint256 bps) internal pure returns (uint256) {
437
- return (amount * bps) / 10_000;
438
- }
439
-
440
- function calculatePositions(bool isCoinToken0, PoolConfiguration memory poolConfiguration) internal pure returns (LpPosition[] memory positions) {
441
- if (poolConfiguration.version == CoinConfigurationVersions.LEGACY_POOL_VERSION) {
442
- positions = CoinLegacyMarket.calculatePositions(isCoinToken0, poolConfiguration);
443
- } else if (poolConfiguration.version == CoinConfigurationVersions.DOPPLER_UNI_V3_POOL_VERSION) {
444
- positions = CoinDopplerUniV3.calculatePositions(isCoinToken0, poolConfiguration);
445
- } else {
446
- revert InvalidPoolVersion();
447
- }
229
+ return CoinRewards.calculateReward(amount, bps);
448
230
  }
449
231
  }
@@ -0,0 +1,65 @@
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 {Commands} from "@uniswap/universal-router/contracts/libraries/Commands.sol";
6
+ import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
7
+ import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
8
+ import {CoinCommon} from "./CoinCommon.sol";
9
+ import {IPermit2} from "permit2/src/interfaces/IPermit2.sol";
10
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
11
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
12
+ import {ISwapPathRouter} from "../interfaces/ISwapPathRouter.sol";
13
+ import {IHasPoolKey} from "../interfaces/ICoinV4.sol";
14
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
15
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
16
+ import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
17
+
18
+ library UniV4SwapHelper {
19
+ function buildExactInputSingleSwapCommand(
20
+ address currencyIn,
21
+ uint128 amountIn,
22
+ address currencyOut,
23
+ uint128 minAmountOut,
24
+ PoolKey memory key,
25
+ bytes memory hookData
26
+ ) internal pure returns (bytes memory commands, bytes[] memory inputs) {
27
+ bool zeroForOne = Currency.unwrap(key.currency0) == currencyIn;
28
+
29
+ // now buy some coin for usdc
30
+ commands = abi.encodePacked(uint8(Commands.V4_SWAP));
31
+
32
+ bytes memory actions = abi.encodePacked(uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), uint8(Actions.TAKE_ALL));
33
+
34
+ bytes[] memory params = new bytes[](3);
35
+
36
+ // First parameter: swap configuration
37
+ params[0] = abi.encode(
38
+ IV4Router.ExactInputSingleParams({
39
+ poolKey: key,
40
+ zeroForOne: zeroForOne,
41
+ amountIn: amountIn, // amount of tokens we're swapping
42
+ amountOutMinimum: minAmountOut, // minimum amount we expect to receive
43
+ hookData: hookData
44
+ })
45
+ );
46
+
47
+ // Second parameter: specify input tokens for the swap
48
+ // encode SETTLE_ALL parameters
49
+ params[1] = abi.encode(currencyIn, amountIn);
50
+
51
+ // Third parameter: specify output tokens from the swap
52
+ // encode TAKE_ALL parameters
53
+ params[2] = abi.encode(currencyOut, minAmountOut);
54
+
55
+ inputs = new bytes[](1);
56
+
57
+ // Combine actions and params into inputs
58
+ inputs[0] = abi.encode(actions, params);
59
+ }
60
+
61
+ function approveTokenWithPermit2(IPermit2 permit2, address router, address token, uint160 amount, uint48 expiration) internal {
62
+ IERC20(token).approve(address(permit2), type(uint256).max);
63
+ permit2.approve(token, router, amount, expiration);
64
+ }
65
+ }
@@ -0,0 +1,109 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.23;
3
+
4
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
5
+ import {ISwapPathRouter} from "../interfaces/ISwapPathRouter.sol";
6
+ import {IHasPoolKey} from "../interfaces/ICoinV4.sol";
7
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
8
+ import {IPoolManager, PoolKey} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
9
+ import {SwapParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
10
+ import {BalanceDelta, BalanceDeltaLibrary} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
11
+ import {IHasSwapPath} from "../interfaces/ICoinV4.sol";
12
+ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
13
+ import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
14
+ import {IDeployedCoinVersionLookup} from "../interfaces/IDeployedCoinVersionLookup.sol";
15
+ import {IZoraV4CoinHook} from "../interfaces/IZoraV4CoinHook.sol";
16
+ import {CoinConfigurationVersions} from "./CoinConfigurationVersions.sol";
17
+
18
+ library UniV4SwapToCurrency {
19
+ using BalanceDeltaLibrary for BalanceDelta;
20
+
21
+ function swapToPath(
22
+ IPoolManager poolManager,
23
+ uint128 amount0,
24
+ uint128 amount1,
25
+ Currency currencyIn,
26
+ PathKey[] memory path
27
+ ) internal returns (Currency lastCurrency, uint128 lastCurrencyBalance) {
28
+ require(path.length > 0, IZoraV4CoinHook.PathMustHaveAtLeastOneStep());
29
+
30
+ // do first swap - the first swap updates output the balance with the initial balance that existed before the swap
31
+ (lastCurrency, lastCurrencyBalance) = _doFirstSwapFromCoinToCurrency(poolManager, path[0], currencyIn, amount0, amount1);
32
+
33
+ // for each path, swap the currency to the next currency
34
+ for (uint256 i = 1; i < path.length; i++) {
35
+ (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(path[i], lastCurrency);
36
+ lastCurrencyBalance = uint128(_swap(poolManager, poolKey, zeroForOne, -int128(lastCurrencyBalance), ""));
37
+ lastCurrency = zeroForOne ? poolKey.currency1 : poolKey.currency0;
38
+ }
39
+ }
40
+
41
+ function _doFirstSwapFromCoinToCurrency(
42
+ IPoolManager poolManager,
43
+ PathKey memory pathKey,
44
+ Currency coin,
45
+ uint128 amount0,
46
+ uint128 amount1
47
+ ) private returns (Currency outputCurrency, uint128 outputAmount) {
48
+ (PoolKey memory poolKey, bool zeroForOne) = _getPoolAndSwapDirection(pathKey, coin);
49
+
50
+ uint128 coinAmount = zeroForOne ? amount0 : amount1;
51
+
52
+ outputCurrency = zeroForOne ? poolKey.currency1 : poolKey.currency0;
53
+
54
+ uint128 initialAmountCurrency = zeroForOne ? amount1 : amount0;
55
+
56
+ // if not swapping any coin for currency, output amount is amount of currency
57
+ if (coinAmount == 0) {
58
+ outputAmount = initialAmountCurrency;
59
+ } else {
60
+ outputAmount = uint128(_swap(poolManager, poolKey, zeroForOne, -int128(coinAmount), bytes("")));
61
+ }
62
+ }
63
+
64
+ function _swap(
65
+ IPoolManager poolManager,
66
+ PoolKey memory poolKey,
67
+ bool zeroForOne,
68
+ int256 amountSpecified,
69
+ bytes memory hookData
70
+ ) private returns (int128 reciprocalAmount) {
71
+ // for protection of exactOut swaps, sqrtPriceLimit is not exposed as a feature in this contract
72
+ unchecked {
73
+ BalanceDelta delta = poolManager.swap(
74
+ poolKey,
75
+ SwapParams(zeroForOne, amountSpecified, zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),
76
+ hookData
77
+ );
78
+
79
+ reciprocalAmount = (zeroForOne == amountSpecified < 0) ? delta.amount1() : delta.amount0();
80
+ }
81
+ }
82
+
83
+ /// @notice Get the pool and swap direction for a given PathKey
84
+ /// @param params the given PathKey
85
+ /// @param currencyIn the input currency
86
+ /// @return poolKey the pool key of the swap
87
+ /// @return zeroForOne the direction of the swap, true if currency0 is being swapped for currency1
88
+ function _getPoolAndSwapDirection(PathKey memory params, Currency currencyIn) internal pure returns (PoolKey memory poolKey, bool zeroForOne) {
89
+ Currency currencyOut = params.intermediateCurrency;
90
+ (Currency currency0, Currency currency1) = currencyIn < currencyOut ? (currencyIn, currencyOut) : (currencyOut, currencyIn);
91
+
92
+ zeroForOne = currencyIn == currency0;
93
+ poolKey = PoolKey(currency0, currency1, params.fee, params.tickSpacing, params.hooks);
94
+ }
95
+
96
+ function getSubSwapPath(address currency, IDeployedCoinVersionLookup coinVersionLookup) internal view returns (PathKey[] memory) {
97
+ if (!_hasSwapPath(currency, coinVersionLookup)) {
98
+ return new PathKey[](0);
99
+ }
100
+ return IHasSwapPath(currency).getPayoutSwapPath(coinVersionLookup).path;
101
+ }
102
+
103
+ function _hasSwapPath(address currency, IDeployedCoinVersionLookup coinVersionLookup) private view returns (bool) {
104
+ if (CoinConfigurationVersions.isV4(coinVersionLookup.getVersionForDeployedCoin(currency))) {
105
+ return IERC165(currency).supportsInterface(type(IHasSwapPath).interfaceId);
106
+ }
107
+ return false;
108
+ }
109
+ }